From 24cd221b2177be4aff0a846fd9cefdc4ac227afd Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Thu, 17 Sep 2026 08:59:25 +0200 Subject: [PATCH] fix: stale `wiki/` path literals nach kb/ nachgezogen, mit Test-Guard gegen die naechste Umbenennung 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 --- CHANGES.md | 44 +++++++++++++- VERSION | 2 +- kb/entities/projects/Chemenu.md | 4 +- kb/log.md | 6 ++ tools/chemenu/commands/_util.py | 2 +- tools/chemenu/commands/cite_cmd.py | 6 +- tools/chemenu/commands/git_publish.py | 2 +- tools/chemenu/commands/log_append.py | 6 +- tools/chemenu/commands/page_ops.py | 8 +-- tools/chemenu/commands/provenance_cmd.py | 2 +- tools/chemenu/commands/raw_cmd.py | 2 +- tools/chemenu/commands/run_budget.py | 2 +- tools/chemenu/commands/touch.py | 2 +- tools/chemenu/commands/xref.py | 2 +- tools/chemenu/frontmatter_io.py | 2 +- tools/chemenu/lint_core.py | 2 +- tools/chemenu/tests/test_log_append.py | 2 +- tools/chemenu/tests/test_source_hygiene.py | 70 ++++++++++++++++++++++ tools/chemenu/tests/test_touch.py | 2 +- tools/chemenu/tests/test_type_resolver.py | 2 +- tools/chemenu/type_resolver.py | 8 +-- tools/wikitool | 2 +- types/type-spec.md | 2 +- types/type-spec.schema.yaml | 8 +-- 24 files changed, 154 insertions(+), 36 deletions(-) create mode 100644 tools/chemenu/tests/test_source_hygiene.py diff --git a/CHANGES.md b/CHANGES.md index be3c125..d5f4074 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -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 ### 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 diff --git a/VERSION b/VERSION index 9aa7fe9..ffb3907 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.1.0-beta.6 +6.1.0-beta.7 diff --git a/kb/entities/projects/Chemenu.md b/kb/entities/projects/Chemenu.md index ace5276..7a16df8 100644 --- a/kb/entities/projects/Chemenu.md +++ b/kb/entities/projects/Chemenu.md @@ -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 diff --git a/kb/log.md b/kb/log.md index 5ce6ad1..5ef3f57 100644 --- a/kb/log.md +++ b/kb/log.md @@ -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. + +--- diff --git a/tools/chemenu/commands/_util.py b/tools/chemenu/commands/_util.py index aa3fc95..c32dab9 100644 --- a/tools/chemenu/commands/_util.py +++ b/tools/chemenu/commands/_util.py @@ -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 diff --git a/tools/chemenu/commands/cite_cmd.py b/tools/chemenu/commands/cite_cmd.py index 70a749e..a898ec4 100644 --- a/tools/chemenu/commands/cite_cmd.py +++ b/tools/chemenu/commands/cite_cmd.py @@ -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 diff --git a/tools/chemenu/commands/git_publish.py b/tools/chemenu/commands/git_publish.py index aecbd50..182ce62 100644 --- a/tools/chemenu/commands/git_publish.py +++ b/tools/chemenu/commands/git_publish.py @@ -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 diff --git a/tools/chemenu/commands/log_append.py b/tools/chemenu/commands/log_append.py index 75c3bd5..1dade48 100644 --- a/tools/chemenu/commands/log_append.py +++ b/tools/chemenu/commands/log_append.py @@ -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) diff --git a/tools/chemenu/commands/page_ops.py b/tools/chemenu/commands/page_ops.py index 89ae418..8aaefc5 100644 --- a/tools/chemenu/commands/page_ops.py +++ b/tools/chemenu/commands/page_ops.py @@ -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: diff --git a/tools/chemenu/commands/provenance_cmd.py b/tools/chemenu/commands/provenance_cmd.py index a703ba0..3b215de 100644 --- a/tools/chemenu/commands/provenance_cmd.py +++ b/tools/chemenu/commands/provenance_cmd.py @@ -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" diff --git a/tools/chemenu/commands/raw_cmd.py b/tools/chemenu/commands/raw_cmd.py index 0206e5d..e8fbdac 100644 --- a/tools/chemenu/commands/raw_cmd.py +++ b/tools/chemenu/commands/raw_cmd.py @@ -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( diff --git a/tools/chemenu/commands/run_budget.py b/tools/chemenu/commands/run_budget.py index fe705df..5785b60 100644 --- a/tools/chemenu/commands/run_budget.py +++ b/tools/chemenu/commands/run_budget.py @@ -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). diff --git a/tools/chemenu/commands/touch.py b/tools/chemenu/commands/touch.py index c2eda29..6a3bf40 100644 --- a/tools/chemenu/commands/touch.py +++ b/tools/chemenu/commands/touch.py @@ -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: diff --git a/tools/chemenu/commands/xref.py b/tools/chemenu/commands/xref.py index 36e3d96..5fae390 100644 --- a/tools/chemenu/commands/xref.py +++ b/tools/chemenu/commands/xref.py @@ -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] diff --git a/tools/chemenu/frontmatter_io.py b/tools/chemenu/frontmatter_io.py index 44ada65..7f1b0b0 100644 --- a/tools/chemenu/frontmatter_io.py +++ b/tools/chemenu/frontmatter_io.py @@ -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 diff --git a/tools/chemenu/lint_core.py b/tools/chemenu/lint_core.py index 0fc6920..8a5275e 100644 --- a/tools/chemenu/lint_core.py +++ b/tools/chemenu/lint_core.py @@ -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("") diff --git a/tools/chemenu/tests/test_log_append.py b/tools/chemenu/tests/test_log_append.py index d28cf9d..ee77f48 100644 --- a/tools/chemenu/tests/test_log_append.py +++ b/tools/chemenu/tests/test_log_append.py @@ -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): diff --git a/tools/chemenu/tests/test_source_hygiene.py b/tools/chemenu/tests/test_source_hygiene.py new file mode 100644 index 0000000..8650b0a --- /dev/null +++ b/tools/chemenu/tests/test_source_hygiene.py @@ -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 diff --git a/tools/chemenu/tests/test_touch.py b/tools/chemenu/tests/test_touch.py index 601312c..868d637 100644 --- a/tools/chemenu/tests/test_touch.py +++ b/tools/chemenu/tests/test_touch.py @@ -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() diff --git a/tools/chemenu/tests/test_type_resolver.py b/tools/chemenu/tests/test_type_resolver.py index f522a8c..3eb125c 100644 --- a/tools/chemenu/tests/test_type_resolver.py +++ b/tools/chemenu/tests/test_type_resolver.py @@ -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"] diff --git a/tools/chemenu/type_resolver.py b/tools/chemenu/type_resolver.py index f90891d..6091e9a 100644 --- a/tools/chemenu/type_resolver.py +++ b/tools/chemenu/type_resolver.py @@ -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. diff --git a/tools/wikitool b/tools/wikitool index 3de0690..132eaa8 100755 --- a/tools/wikitool +++ b/tools/wikitool @@ -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}" diff --git a/types/type-spec.md b/types/type-spec.md index e37018f..2c17fd8 100644 --- a/types/type-spec.md +++ b/types/type-spec.md @@ -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. diff --git a/types/type-spec.schema.yaml b/types/type-spec.schema.yaml index 12db7c5..c9dd664 100644 --- a/types/type-spec.schema.yaml +++ b/types/type-spec.schema.yaml @@ -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///.md. + placement, giving kb/<base_dir>/<layout[subtype].dir>/<Title>.md. pattern: "^[a-z][a-z0-9/_-]*$" title_prefix: type: string