From c0dc2129bb153a126f161d51298779e58e5219cd Mon Sep 17 00:00:00 2001 From: Torben Nehmer Date: Sun, 13 Sep 2026 00:40:10 +0200 Subject: [PATCH] docs verify: nur als .template ausgeliefertes Linkziel gilt als aufgeloest (Defekt aus 0fb8fd6) Files changed: - CHANGES.md - VERSION - tools/chemenu/commands/docs_verify.py - tools/chemenu/tests/test_docs_verify.py --- CHANGES.md | 28 +++++++++++++++++++++- VERSION | 2 +- tools/chemenu/commands/docs_verify.py | 32 +++++++++++++++++++++---- tools/chemenu/tests/test_docs_verify.py | 28 ++++++++++++++++++++++ 4 files changed, 83 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 39a7b00..5f6a094 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -52,7 +52,7 @@ concern - readable here, never shipped as something to parse. --- -## 6.0.0-beta.1 - 2026-09-12 - docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt +## 6.0.0-beta.2 - 2026-09-13 - docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest **Author:** Torben Nehmer @@ -65,6 +65,9 @@ concern - readable here, never shipped as something to parse. - SKILL.md: relative Links durch repo-root-relative Pfade ersetzt, docs verify/instructions verify pruefen Linkziele - docs verify: der Linkziel-Check erreicht auch die instanz-eigenen kb/CONVENTIONS.md und COLLECTION.md - daher Grenzuebertritt +**Medium impact** +- docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest + **Low impact** - gates.md/session-setup.md: die Budget-Ausnahme von version regrade haengt an der Aufrufform @@ -117,6 +120,29 @@ eigenen Regel in `tools/README.md`, nicht durch einen Check - was `docs/version- genau diese Stelle sagt ("a person looking at the diff ... not a validator"), hat sich hier wiederholt. +### docs verify: ein nur als .template ausgeliefertes Linkziel gilt als aufgeloest + +Defektbehebung am Check aus den beiden Bumps darueber, gefunden unmittelbar nach deren Publish. +`check_reference_targets` meldete auf einem frisch exportierten Baum 13 tote Links - `kb/CONTRACT.md` +neunmal, dazu `german-terminology.md`, `kb-profiles.md` und `link-taxonomy.md` - und zwar dafuer, +dass der Export tut, was er soll. `kb/CONVENTIONS.md` und die vier `kb//COLLECTION.md` sind +instanz-eigen: die Distribution traegt `.template`, und die Instanz uebernimmt sie erst im +Personalisierungsschritt von `instructions/setup-instance.md` durch Umbenennen. Zwischen +`dist export` und diesem Schritt existiert die fertige Datei berechtigterweise nicht, waehrend die +stack-eigenen Dateien sie unter ihrem kuenftigen Namen verlinken - korrekt, denn so wird sie heissen. + +Ein Linkziel gilt jetzt auch dann als aufgeloest, wenn daneben `.template` liegt. Die +Ausnahme ist eng: fehlt beides, bleibt es ein Befund. Damit beschreibt der Check nicht laenger +"noch nicht personalisiert" als "kaputter Link" - diesen Zustand meldet `doctor` unter +`conventions` praezise und zustaendig. + +CI war davon nie rot: der Replay in `.gitea/workflows/ci.yml` uebernimmt die Templates, bevor er +`docs verify` aufruft, und der dokumentierte Weg in `setup-instance.md` stellt die Personalisierung +(Schritt 5/6) ebenfalls vor die Verifikation (Schritt 13). Getroffen haette es jeden, der nach dem +Export einmal zur Kontrolle `docs verify` aufruft. Aufgefallen ist es, weil die Verifikation des +vorherigen Publishes den Arbeitsbaum geprueft hatte und nicht den exportierten - ausgerechnet bei +einer Aenderung, deren ganzer Gegenstand Kopien in anderer Verzeichnistiefe sind. + --- ## 5.1.0 - 2026-09-12 - changelog: Kandidaten-Eintrag nach Impact gruppiert, version regrade zur Nachkorrektur, version release verlangt eine Zusammenfassung diff --git a/VERSION b/VERSION index 1cd446a..7bdb826 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -6.0.0-beta.1 +6.0.0-beta.2 diff --git a/tools/chemenu/commands/docs_verify.py b/tools/chemenu/commands/docs_verify.py index 372c03b..f745034 100644 --- a/tools/chemenu/commands/docs_verify.py +++ b/tools/chemenu/commands/docs_verify.py @@ -470,6 +470,13 @@ def check_toc_regions() -> list[str]: # escaping, which nothing here uses. MARKDOWN_LINK_RE = re.compile(r"\[[^\]]*\]\(([^)\s]+)\)") +# The suffix `dist export` re-keys an instance-owned file to, and the one +# `setup-instance.md` renames away again. Spelled here rather than imported +# from `ownership`, whose own `.template` handling answers a different +# question (which side an upstream merge keeps) over a narrower scope +# (paths under a content stage). +TEMPLATE_SUFFIX = ".template" + def is_external_or_anchor(target: str) -> bool: """A link this check does not resolve as a filesystem path: an absolute @@ -498,6 +505,17 @@ def check_reference_targets() -> list[str]: a stale one anyway. Code fences and inline code spans are masked first (`markdown_code.strip_code_spans`), so a passage that shows link syntax as an example is not mistaken for a real reference. + + **A target the stack ships only as a `.template` counts as resolving.** + `kb/CONVENTIONS.md` and every `kb//COLLECTION.md` are instance-owned: + a distribution carries `.template` and the instance adopts it by + renaming, during `instructions/setup-instance.md`'s personalization step. + Between `dist export` and that step the real file legitimately does not + exist yet - while `kb/CONTRACT.md` and three flat instructions link to it + by its adopted name, correctly, because that is the name it will have. + Reporting those as dead links would fail a fresh export for doing exactly + what it is supposed to do, and would describe "not personalized yet" as a + broken link when `doctor`'s `conventions` check already says it precisely. """ issues = [] for path in toc.target_files(): @@ -511,11 +529,15 @@ def check_reference_targets() -> list[str]: target_path = target.split("#", 1)[0] if not target_path: continue - if not (path.parent / target_path).resolve().exists(): - issues.append( - f"{rel_path(path)}:{line_number} links to `{target}`, which does not " - "resolve to an existing file" - ) + resolved = (path.parent / target_path).resolve() + if resolved.exists(): + continue + if resolved.with_name(resolved.name + TEMPLATE_SUFFIX).exists(): + continue + issues.append( + f"{rel_path(path)}:{line_number} links to `{target}`, which does not " + "resolve to an existing file" + ) return issues diff --git a/tools/chemenu/tests/test_docs_verify.py b/tools/chemenu/tests/test_docs_verify.py index 9e286bd..6c58762 100644 --- a/tools/chemenu/tests/test_docs_verify.py +++ b/tools/chemenu/tests/test_docs_verify.py @@ -586,6 +586,34 @@ def test_link_syntax_shown_as_an_example_in_a_fence_is_not_flagged(tmp_path, mon assert docs_verify.check_reference_targets() == [] +def test_a_target_shipped_only_as_a_template_is_not_dead(tmp_path, monkeypatch): + """Regression guard for a defect this check shipped with. A fresh + `dist export` carries `kb/CONVENTIONS.md.template`, not + `kb/CONVENTIONS.md` - the instance adopts it by renaming, during + `setup-instance.md`'s personalization step. `kb/CONTRACT.md` and three + flat instructions link to the adopted name, correctly. Before this + exemption the check reported 13 dead links on a just-exported tree, for + doing exactly what a fresh export is supposed to do.""" + (tmp_path / "kb").mkdir() + (tmp_path / "kb" / "CONVENTIONS.md.template").write_text("# Conventions\n", encoding="utf-8") + fake = tmp_path / "kb" / "CONTRACT.md" + fake.write_text("What this instance decided: [kb/CONVENTIONS.md](CONVENTIONS.md).\n", encoding="utf-8") + monkeypatch.setattr(docs_verify.toc, "target_files", lambda: [fake]) + assert docs_verify.check_reference_targets() == [] + + +def test_a_target_with_neither_the_file_nor_a_template_is_still_dead(tmp_path, monkeypatch): + """The exemption is narrow: it covers a file the stack ships as a + template, not any missing target.""" + (tmp_path / "kb").mkdir() + fake = tmp_path / "kb" / "CONTRACT.md" + fake.write_text("See [kb/CONVENTIONS.md](CONVENTIONS.md).\n", encoding="utf-8") + monkeypatch.setattr(docs_verify.toc, "target_files", lambda: [fake]) + issues = docs_verify.check_reference_targets() + assert len(issues) == 1 + assert "CONVENTIONS.md" in issues[0] + + def test_every_reference_files_link_targets_resolve(): """Forward direction, against the real tree: every relative link in a file `toc.target_files()` covers must resolve - this is what a `../` count