fix: stale wiki/ path literals nach kb/ nachgezogen, mit Test-Guard gegen die naechste Umbenennung
CI / verify (push) Successful in 55s
Release / release (push) Successful in 39s

Files changed:
- CHANGES.md
- VERSION
- kb/entities/projects/Chemenu.md
- kb/log.md
- tools/chemenu/commands/_util.py
- tools/chemenu/commands/cite_cmd.py
- tools/chemenu/commands/git_publish.py
- tools/chemenu/commands/log_append.py
- tools/chemenu/commands/page_ops.py
- tools/chemenu/commands/provenance_cmd.py
- tools/chemenu/commands/raw_cmd.py
- tools/chemenu/commands/run_budget.py
- tools/chemenu/commands/touch.py
- tools/chemenu/commands/xref.py
- tools/chemenu/frontmatter_io.py
- tools/chemenu/lint_core.py
- tools/chemenu/tests/test_log_append.py
- tools/chemenu/tests/test_source_hygiene.py
- tools/chemenu/tests/test_touch.py
- tools/chemenu/tests/test_type_resolver.py
- tools/chemenu/type_resolver.py
- tools/wikitool
- types/type-spec.md
- types/type-spec.schema.yaml
This commit is contained in:
2026-09-17 08:59:25 +02:00
parent aa31d431fc
commit 24cd221b21
24 changed files with 154 additions and 36 deletions
+43 -1
View File
@@ -59,7 +59,7 @@ concern - readable here, never shipped as something to parse.
---
## 6.1.0-beta.6 - 2026-09-16 - new: scaffold materializes a schema default only for a required field
## 6.1.0-beta.7 - 2026-09-17 - Stale `wiki/` path literals swept out of tools/ and types/, with a test guarding against the next rename
**Author:** Torben Nehmer
@@ -73,6 +73,9 @@ concern - readable here, never shipped as something to parse.
- dist upgrade: --take-release nimmt fuer einen lokal geaenderten Pfad die Release-Fassung
- version notes antwortet auf einer ausgelieferten Instanz aus dem Release-Feed
- new: scaffold materializes a schema default only for a required field
**Low impact**
- Stale `wiki/` path literals swept out of tools/ and types/, with a test guarding against the next rename
<!-- /wikitool:bumps -->
### Upgrade-Prozedur als eigene Instruktion statt als Prosa in INSTALL.md
@@ -326,6 +329,45 @@ fehlender Schluessel sonst den Template-Filter-Suffix woertlich in den Body schr
den beiden Migrationsdokumenten), keine Migration noetig, und ein zurueckgerolltes Werkzeug
schriebe das Feld nur wieder mit.
### Stale `wiki/` path literals swept out of tools/ and types/, with a test guarding against the next rename
Die Wissensschicht wurde am 2026-08-21 von `wiki/` nach `kb/` umbenannt. Das Verzeichnis zog um,
die Zeichenkette nicht: 27 Stellen nannten weiter einen Pfad, den es nicht mehr gibt. Gemeldet
war davon eine - die Kopfzeile des Lint-Reports (``Scanned N pages under `wiki/` ``) - als
kosmetischer Einzelfall. Der Scan selbst war immer korrekt: `run_lint(kb_dir)` laeuft ueber
`kb/`, gezaehlt wird, was dort liegt. Falsch waren ausschliesslich die Beschriftungen.
Rund die Haelfte davon ist nutzersichtbar. Die Fehlermeldungen von `xref`, `cite`, `touch`,
`move`, `rm`, `rename`, `raw accept` und `log status` nannten `wiki/`, ebenso die `--help`-Texte
von `cite sync --all`, `provenance rebuild-index --dry-run` und `move --reconcile`. Dazu die
`description:`-Felder in `types/type-spec.schema.yaml`, die ueber `types describe` und ueber jede
Schema-Validierungsmeldung bei einem Agenten landen. Zwei Stellen waren doppelt falsch:
`git_publish.py` und `run_budget.py` verwiesen auf `wiki/concepts/Mass-Update Gate.md`, waehrend
die Seite unter `kb/concepts/workflows/Mass-Update Gate.md` liegt - dort war auch die
Collection-Ebene veraltet.
Nicht angefasst: `raw/` (unveraenderlich, was immer dort steht) und die Alteintraege dieser
Datei. Beide sind Aufzeichnungen dessen, was zu ihrer Zeit galt, keine Wegweiser - dieselbe
Unterscheidung, die `instructions/dev/issue-tracking.md` fuer den Tracker trifft.
Dass es vier Wochen unbemerkt blieb, ist der eigentliche Befund: kein Check liest ein Pfadliteral
in Quelltext. `docs verify` kam dafuer nicht in Frage, weil es `shipped_prose()` liest, also
Markdown - der Grossteil des Defekts sass in `.py`-Zeichenketten. Der Guard ist deshalb ein Test:
`tools/chemenu/tests/test_source_hygiene.py` scannt jede `.py`-Datei unter `tools/chemenu/` sowie
`tools/wikitool` gegen eine Tabelle stillgelegter Stufenpfade. Die naechste Umbenennung traegt
dort eine Zeile nach und bekommt jede vergessene Stelle als Testfehler, statt als Zeichenkette,
die ein Jahr lang niemand liest. Die zwei Ausnahmen stehen bewusst als Liste mit Begruendung und
nicht als geschickteres Muster: eine Fixture-URL, in der `wiki` ein Repository-Name ist, und die
Guard-Datei selbst, die die stillgelegten Pfade ja gerade deklariert.
`kb/entities/projects/Chemenu.md` trug denselben Fehler in einer Kerndaten-Zeile und wurde ueber
`touch` nachgezogen. "Dreilagig" blieb dort stehen: das deckt sich mit der Concept-Seite
`Three-Layer Architecture`, die `reports/` ausdruecklich als vierte *Phase* neben den drei
Schichten fuehrt.
`--patch`: keine Schnittstelle aendert sich, kein Verhalten, keine Migration. Ein
zurueckgerolltes Werkzeug gibt nur wieder die alten Beschriftungen aus.
---
## 6.0.1 - 2026-09-16 - docs toc/verify erreichen die .template-Form einer Referenzdatei
+1 -1
View File
@@ -1 +1 @@
6.1.0-beta.6
6.1.0-beta.7
+2 -2
View File
@@ -3,7 +3,7 @@ type: types/entity.md
entity_type: project
tags: [wiki, llm, knowledge-base]
created: 2026-08-04
modified: 2026-09-02
modified: 2026-09-17
related:
- implements: Personalization Plane
- implements: Issue Label Scheme
@@ -56,7 +56,7 @@ Das Repository hat bereits ein deterministisches CLI, `tools/wikitool` (Python,
- **Verantwortlich:** Torben
- **Lizenz:** AGPL-3.0 (Stack: `tools/`, `types/`), CC-BY-4.0 (Inhalte)
- **Repository:** `torben/chemenu` auf gitea.nehmer.net; bis 2026-09-01 `torben/llm-wiki-test1`
- **Architektur:** Dreilagig: raw/ (Quelle), wiki/ (Wissen), tools/ (deterministisches CLI)
- **Architektur:** Dreilagig: raw/ (Quelle), kb/ (Wissen), tools/ (deterministisches CLI)
## Beziehungen
+6
View File
@@ -209,3 +209,9 @@ Alle Checklistenpunkte erledigt: 29 Seiten getouched, `migrate verify` 0 finding
Korpusmigration zu #86/#60: confidence/confidence_base aus allen 152 betroffenen Entity-/Concept-Seiten entfernt (types/entity.schema.yaml, types/concept.schema.yaml deklarieren additionalProperties: false seit dem Stack-Teil von #60). Vier Einheiten entlang bestehender Area-Verzeichnisse (u1 kb/concepts/architectures+decisions+protocols+problems: 35, u2 kb/concepts/patterns+workflows: 45, u3 kb/entities/tools+people: 35, u4 kb/entities/technologies+projects+systems: 37), je per Skript work/confidence-removal/strip_confidence.py ueber chemenu.frontmatter_io.read_page/write_page (nie von Hand). migrate verify --from HEAD --fail-on-error zeigt fuer alle vier Einheiten 0 Befunde - modified:, Body, Referenzarrays und Feldreihenfolge unveraendert. lint --fail-on-error: 0 schema_validation_errors (voller Report unter reports/Lint Report 2026-09-10.md; die dort gemeldeten redundant_see_also-Funde sind vorbestehend, advisory und unabhaengig von dieser Migration). Ein Body-Treffer bleibt bewusst bestehen: kb/concepts/patterns/Confidence Scoring.md zitiert 'confidence: 0.XX' als YAML-Beispiel innerhalb eines Code-Blocks - das ist Content ueber das Pattern selbst, kein Frontmatter-Feld dieser Seite, und liegt ausserhalb des Body-unberuehrt-Scopes von #86. migrate done 5.0.0 --pages 152 gesetzt, kb_version steht auf 5.0.0. Workshop work/confidence-removal/ nach work/CONTRACT.md geschlossen und geloescht; die dauerhafte Ausgabe ist der bereinigte Korpus selbst. Naechster Schritt: ein gemeinsamer publish mit den Stack-Aenderungen aus #60 (kein eigener Publish fuer diese Einheit, siehe #60 Sequencing).
---
## [2026-09-17] update | Chemenu - Pfad kb/ in den Kerndaten korrigiert
Die Kerndaten-Zeile "Architektur" nannte noch `wiki/` als Wissensschicht; das Verzeichnis heisst seit der Umbenennung am 2026-08-21 `kb/`. Nur der Pfad wurde nachgezogen - "Dreilagig" bleibt stehen, weil es sich mit [[Three-Layer Architecture]] deckt, wo `reports/` als vierte Phase neben den drei Schichten gefuehrt wird. Teil eines Stack-Durchgangs, der dieselbe veraltete Zeichenkette an 27 Stellen unter tools/ und types/ beseitigt hat.
---
+1 -1
View File
@@ -179,7 +179,7 @@ def rel_path(path: Path) -> str:
def check_collision(name: str) -> None:
"""Fail if any page under wiki/ already has `name` as its filename stem.
"""Fail if any page under kb/ already has `name` as its filename stem.
The stem *is* the page title and wikilinks resolve by title alone, so two
files sharing a stem in different directories are indistinguishable to
+3 -3
View File
@@ -38,7 +38,7 @@ app = typer.Typer(help="Manage [^cite-id] footnote citations and their Footnotes
def _find_page(pages: dict[str, Page], title: str) -> Page:
if title not in pages:
fail(f"No page titled '{title}' found under wiki/.")
fail(f"No page titled '{title}' found under kb/.")
return pages[title]
@@ -100,7 +100,7 @@ def cite_add(
pages = load_kb_pages(config.KB_DIR)
page = _find_page(pages, page_title)
if source not in pages:
fail(f"No page titled '{source}' found under wiki/ - citing a page that doesn't exist would be a dangling reference.")
fail(f"No page titled '{source}' found under kb/ - citing a page that doesn't exist would be a dangling reference.")
marker_id, new_body, changed = upsert_citation(page, source, file)
marker = f"[^{marker_id}]"
@@ -159,7 +159,7 @@ def sync_page(page: Page) -> tuple[str, bool, list[str], list[str]]:
@app.command("sync")
def cite_sync(
page_title: Optional[str] = typer.Option(None, "--page", help="Sync just this page"),
all_pages: bool = typer.Option(False, "--all", help="Sync every page under wiki/"),
all_pages: bool = typer.Option(False, "--all", help="Sync every page under kb/"),
dry_run: bool = typer.Option(False, "--dry-run", help="Report what would change instead of writing"),
):
"""Prune orphan Footnotes definitions and re-render each page's block in
+1 -1
View File
@@ -14,7 +14,7 @@ whose push failed leaves a real, unpushed commit sitting on the branch, and
the next `publish` now pushes it instead of reporting "Nothing to commit"
forever.
Also implements the Mass-Update Gate (wiki/concepts/Mass-Update Gate.md):
Also implements the Mass-Update Gate (kb/concepts/workflows/Mass-Update Gate.md):
a push to origin/main is the one action in this system with a real,
irreversible external effect (publicly visible commit history, possible CI
triggers, other clients pulling). Small/normal publishes (< threshold
+3 -3
View File
@@ -1,4 +1,4 @@
"""Append correctly-formatted entries to wiki/log.md."""
"""Append correctly-formatted entries to kb/log.md."""
from __future__ import annotations
import re
@@ -10,7 +10,7 @@ import typer
from chemenu import config
from chemenu.commands._util import fail, rel_path, success, today_iso
app = typer.Typer(help="Manage wiki/log.md.")
app = typer.Typer(help="Manage kb/log.md.")
VALID_OPS = ["ingest", "query", "lint", "create", "update", "delete", "rename", "move"]
@@ -74,7 +74,7 @@ def log_status():
`lint` - the deterministic trigger for the Maintenance Schedule's "every
10 sources" full-lint cadence. Read-only."""
if not config.LOG_FILE.exists():
success("No wiki/log.md yet; nothing logged.")
success("No kb/log.md yet; nothing logged.")
return
entries = parse_log_entries(config.LOG_FILE.read_text(encoding="utf-8"))
count = ingests_since_last_lint(entries)
+4 -4
View File
@@ -236,7 +236,7 @@ def rename_command(
if references_only:
if new not in pages:
fail(
f"Neither '{old}' nor '{new}' is a page under wiki/. Repointing references "
f"Neither '{old}' nor '{new}' is a page under kb/. Repointing references "
f"to '{new}' would just move the dangling reference; create the page first "
"with `wikitool new ...`, or drop the reference with `wikitool xref remove`."
)
@@ -316,7 +316,7 @@ def rm_command(
pages = load_kb_pages(config.KB_DIR)
target = pages.get(page_title)
if target is None:
fail(f"No page titled '{page_title}' found under wiki/.")
fail(f"No page titled '{page_title}' found under kb/.")
inbound = inbound_pages(pages, page_title)
if inbound and not yes:
@@ -404,7 +404,7 @@ def move_command(
None, "--page", help="Exact title of the page to move to its computed location"
),
reconcile: bool = typer.Option(
False, "--reconcile", help="Move every page under wiki/ that is not at its computed location"
False, "--reconcile", help="Move every page under kb/ that is not at its computed location"
),
dry_run: bool = typer.Option(False, "--dry-run", help="List what would move without writing"),
):
@@ -475,7 +475,7 @@ def move_command(
target = pages.get(page_title)
if target is None:
fail(f"No page titled '{page_title}' found under wiki/.")
fail(f"No page titled '{page_title}' found under kb/.")
type_path = target.frontmatter.get("type")
if not type_path:
+1 -1
View File
@@ -171,7 +171,7 @@ def build_provenance_index(kb_dir: Path, raw_dir: Path) -> str:
@app.command("rebuild-index")
def rebuild_index(
dry_run: bool = typer.Option(False, "--dry-run", help="Print the result instead of writing wiki/provenance.md"),
dry_run: bool = typer.Option(False, "--dry-run", help="Print the result instead of writing kb/provenance.md"),
):
content = build_provenance_index(config.KB_DIR, config.RAW_DIR)
provenance_file = config.KB_DIR / "provenance.md"
+1 -1
View File
@@ -382,7 +382,7 @@ def raw_accept_command(
pages = load_kb_pages(config.KB_DIR)
target_page = pages.get(page)
if target_page is None:
fail(f"No page titled '{page}' found under wiki/. Create it first, or omit --page.")
fail(f"No page titled '{page}' found under kb/. Create it first, or omit --page.")
existing_rel = source_raw_files(target_page)
if not existing_rel:
fail(
+1 -1
View File
@@ -7,7 +7,7 @@ This closes the gap documented in AGENTS.md's "Gates" section: unlike a
prompt instruction ("stop after N steps"), this check runs
in-process on every `wikitool` invocation and cannot be skipped by the
calling agent "politely trying again". It mirrors the Mass-Update Gate
pattern (see git_publish.py / wiki/concepts/Mass-Update Gate.md), but that
pattern (see git_publish.py / kb/concepts/workflows/Mass-Update Gate.md), but that
gate is scoped to the *size* of a single publish, while this one is scoped to
*iteration volume* across a whole session (e.g. a wiki-ingest or wiki-lint run
that could otherwise loop unbounded over many entity/concept pages).
+1 -1
View File
@@ -232,7 +232,7 @@ def touch_command(
pages = load_kb_pages(config.KB_DIR)
page = pages.get(page_title)
if page is None:
fail(f"No page titled '{page_title}' found under wiki/. Create it first with `wikitool new ...`.")
fail(f"No page titled '{page_title}' found under kb/. Create it first with `wikitool new ...`.")
type_path = page.frontmatter.get("type")
if not type_path:
+1 -1
View File
@@ -37,7 +37,7 @@ app = typer.Typer(help="Manage bidirectional cross-references between wiki pages
def _find_page(pages: dict[str, Page], name: str) -> Page:
if name not in pages:
fail(f"No page titled '{name}' found under wiki/. Create it first with `wikitool new ...`.")
fail(f"No page titled '{name}' found under kb/. Create it first with `wikitool new ...`.")
return pages[name]
+1 -1
View File
@@ -1,5 +1,5 @@
"""Read/write markdown files with YAML frontmatter, matching the formatting
conventions already used across wiki/ (inline flow-style lists, unquoted
conventions already used across kb/ (inline flow-style lists, unquoted
dates).
We deliberately avoid a generic yaml.dump() for the frontmatter block because
+1 -1
View File
@@ -526,7 +526,7 @@ def _section(lines: list[str], title: str, items: list, formatter) -> None:
def render_markdown(report: dict) -> str:
lines = [f"# Structural Lint Report ({report['generated']})", ""]
lines.append(f"Scanned {report['page_count']} pages under `wiki/`. This report covers only")
lines.append(f"Scanned {report['page_count']} pages under `kb/`. This report covers only")
lines.append("mechanically-verifiable structural issues; see the Semantic Review section")
lines.append("below for judgment calls the LLM should complete.")
lines.append("")
+1 -1
View File
@@ -58,7 +58,7 @@ def test_log_status_reports_zero_with_no_log_file(tmp_path, monkeypatch, capsys)
monkeypatch.setattr(config, "LOG_FILE", tmp_path / "log.md")
log_status()
assert "No wiki/log.md yet" in capsys.readouterr().out
assert "No kb/log.md yet" in capsys.readouterr().out
def test_log_status_warns_at_the_ten_ingest_threshold(tmp_path, monkeypatch, capsys):
@@ -0,0 +1,70 @@
"""Guard against a renamed stage leaving its old path standing in source.
`wiki/` was renamed to `kb/` on 2026-08-21. The directory moved; the string did
not, in 27 places - error messages, `--help` text, docstrings and the lint
report's own header, all naming a directory that no longer exists. Nothing
caught it, because no check reads a path literal in source.
This is that check. It is deliberately a plain substring scan over the source
tree rather than a `docs verify` check: `docs verify` reads `shipped_prose()`,
which is markdown only, and the bulk of the defect sat in `.py` strings.
"""
from __future__ import annotations
from pathlib import Path
TOOLS_DIR = Path(__file__).resolve().parents[2]
# Retired stage path -> what replaced it. A future rename adds a row here in the
# same change that does the renaming, which is what makes the next occurrence a
# test failure instead of a string nobody reads for a year.
RETIRED_STAGE_PATHS = {
"wiki/": "kb/",
}
# Occurrences that are not stage paths at all. Kept as an explicit list with a
# reason rather than dodged by a cleverer regex: an exception a reader can see
# is worth more than one a pattern hides.
ALLOWED = {
# A fixture release URL, where `wiki` is a repository name in `owner/repo`.
("tests/test_dist_cmd.py", "https://example/torben/wiki/releases/tag/v0.3.1"),
}
SELF = Path(__file__).resolve()
def _scanned_files() -> list[Path]:
"""Every source file in `tools/` a stale path literal could hide in.
This module is excluded, and has to be: it is the one file whose job is to
name the retired paths, so scanning it would make the guard fail on its own
declaration.
"""
candidates = [*(TOOLS_DIR / "chemenu").rglob("*.py"), TOOLS_DIR / "wikitool"]
return sorted(path for path in candidates if path.resolve() != SELF)
def test_no_retired_stage_path_survives_in_source():
findings = []
for path in _scanned_files():
relative = path.relative_to(TOOLS_DIR / "chemenu" if path.suffix == ".py" else TOOLS_DIR)
for number, line in enumerate(path.read_text(encoding="utf-8").splitlines(), start=1):
for retired, replacement in RETIRED_STAGE_PATHS.items():
if retired not in line:
continue
if any(key == str(relative) and excerpt in line for key, excerpt in ALLOWED):
continue
findings.append(
f"{relative}:{number} names the retired path `{retired}` "
f"(now `{replacement}`): {line.strip()}"
)
assert findings == [], "Retired stage paths still in source:\n" + "\n".join(findings)
def test_the_guard_actually_scans_something():
"""A scan that silently matches no file passes for the wrong reason."""
scanned = _scanned_files()
assert len(scanned) > 40
assert TOOLS_DIR / "wikitool" in scanned
assert SELF not in scanned
+1 -1
View File
@@ -44,7 +44,7 @@ def _touch(**overrides):
def test_touch_bumps_modified(touch_wiki):
_touch(page_title="aurora")
frontmatter, _ = read_page(touch_wiki / "entities/systems/aurora.md")
# Unquoted YAML dates round-trip as date objects, matching the rest of wiki/.
# Unquoted YAML dates round-trip as date objects, matching the rest of kb/.
assert str(frontmatter["modified"]) == datetime.date.today().isoformat()
+1 -1
View File
@@ -149,7 +149,7 @@ def test_get_layout_reads_entity_type_specs_own_layout_field():
"person": "people",
}
assert all(spec.get("title") for spec in layout.values())
# Order drives wiki/index.md section order.
# Order drives kb/index.md section order.
assert list(layout) == ["project", "system", "tool", "technology", "person"]
+4 -4
View File
@@ -59,7 +59,7 @@ class TypeResolver:
if not type_path.endswith('.md'):
raise ValueError(f"Type path must end with .md: {type_path}")
# Try as repo-relative from /types/ or /wiki/**/types/
# Try as repo-relative from /types/ or /kb/**/types/
if type_path.startswith('types/'):
candidate = (self.repo_root / type_path).resolve()
# Ensure it's within the repo - resolve() first so `..` segments
@@ -77,7 +77,7 @@ class TypeResolver:
# Ensure it's within the repo and starts with types/ or has types/ in path
try:
candidate.relative_to(self.repo_root)
# Allow relative paths that resolve to types/ or wiki/**/types/
# Allow relative paths that resolve to types/ or kb/**/types/
if 'types' in str(candidate.relative_to(self.repo_root).parts):
if candidate.exists() and candidate.is_file():
return candidate
@@ -300,8 +300,8 @@ class TypeResolver:
def get_layout(self, type_path: str, source_file: Path = None) -> Optional[Dict[str, Dict[str, str]]]:
"""Return a type-spec's `layout:` frontmatter - a map of subtype value
to `{dir, title}`, declaring where instances of each subtype are
written under wiki/ and what section title/order to use in
wiki/index.md. This is the single source of truth for directory
written under kb/ and what section title/order to use in
kb/index.md. This is the single source of truth for directory
placement, so callers (e.g. `new_page.py`, `index_build.py`) never
need a hand-maintained `entity_type -> subdirectory` Python dict.
+1 -1
View File
@@ -18,7 +18,7 @@ if [ ! -x "$DIR/.venv/bin/python" ]; then
fi
# Do NOT cd into $DIR: that would resolve relative CLI arguments (e.g.
# --markdown "wiki/Lint Report.md") against tools/ instead of the caller's cwd.
# --markdown "kb/Lint Report.md") against tools/ instead of the caller's cwd.
# Add $DIR to PYTHONPATH instead so `chemenu` is importable regardless of
# where this script is invoked from.
export PYTHONPATH="$DIR${PYTHONPATH:+:$PYTHONPATH}"
+1 -1
View File
@@ -178,7 +178,7 @@ genuinely that uniform. Once the default was removed and the pages reclassified
actually were, the field split into six areas with none of them small. Declare `layout` for
*every* enum value once it does: a value missing from it still gets a directory (`subtype_dir`
falls back to naively pluralizing the value's own name), but that directory carries no title in
`wiki/index.md`, which is a silent gap rather than a deliberate flat collection. Leaving a whole
`kb/index.md`, which is a silent gap rather than a deliberate flat collection. Leaving a whole
type's `layout` off is for the case in the paragraph above - no subtype field to key on at all
- not for a subtype field one has simply not gotten around to mapping. A `dir:` must be a single
path segment, because an area is as deep as a page goes.
+4 -4
View File
@@ -26,8 +26,8 @@ properties:
type: object
description: >-
Optional map of subtype value -> {dir, title}, declaring where instances
of each subtype are written under wiki/ and what section title/order to
use in wiki/index.md. Only types with subtype-driven directory
of each subtype are written under kb/ and what section title/order to
use in kb/index.md. Only types with subtype-driven directory
placement (e.g. entity) need this; omit for types with a single flat
directory.
additionalProperties:
@@ -46,11 +46,11 @@ properties:
base_dir:
type: string
description: >-
Directory, relative to the wiki root (wiki/), where instances of this
Directory, relative to the KB root (kb/), where instances of this
type are written - e.g. `entities`. Required on every instantiable
type; omit only for types that are never instantiated as pages (e.g.
type-spec itself). Combined with `layout:` for subtype-driven
placement, giving wiki/<base_dir>/<layout[subtype].dir>/<Title>.md.
placement, giving kb/<base_dir>/<layout[subtype].dir>/<Title>.md.
pattern: "^[a-z][a-z0-9/_-]*$"
title_prefix:
type: string