From e3ef4ec4bcbd6be3f5b3ffb171b102af9a090f17 Mon Sep 17 00:00:00 2001 From: Aaron Bockelie Date: Sat, 20 Jun 2026 01:17:36 -0500 Subject: [PATCH] docs(catalog): adopt legacy.defining_adr; re-vendor #133 doclint MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit agent-ways #133 un-hardcodes the retired-range scan's defining-ADR exemption, moving it from a baked-in `ADR-900-` prefix to `adr.yaml` config. Adopt the config and re-vendor the tool together so the backport is clean. - adr.yaml: add `legacy.defining_adr: ADR-900` — the ADR that vacated 1–99 and thus names retired numbers legitimately is exempt; everything else stays policed - re-vendor docs/scripts/{doclint.py,doc} verbatim from canonical (#133). The canonical tools are now ADR-number-agnostic, so our prior local citation patch on `doc` is gone — zero local delta, both byte-identical to canonical - ADR-908: refresh the "Upstream" note — tools no longer hardcode an ADR number; document the defining_adr exemption - documentation way: note the defining-ADR exemption Behavioral test: an in-range ref injected into ADR-900 is exempt; the same ref in a non-defining ADR is flagged. Lint 0/0 (docs + ADRs enforced). --- .claude/ways/kg/documentation/way.md | 3 +- .../ADR-908-documentation-strategy.md | 18 +++++++---- docs/architecture/adr.yaml | 1 + docs/scripts/doc | 7 +++-- docs/scripts/doclint.py | 30 ++++++++++++------- 5 files changed, 38 insertions(+), 21 deletions(-) diff --git a/.claude/ways/kg/documentation/way.md b/.claude/ways/kg/documentation/way.md index 3f3ebefcf..6ba9664dd 100644 --- a/.claude/ways/kg/documentation/way.md +++ b/.claude/ways/kg/documentation/way.md @@ -52,7 +52,8 @@ resolvable edges, no supersede cycles, and the vacated-range guard. overwritten every build — their frontmatter is **emitted by the generator** (`cli/scripts/*`), never hand-injected, or the next regen wipes it. - **Retired-range guard is ON** (`legacy: {retired: true}`): no doc/ADR/source may - reference the vacated legacy range (ADR numbers 1–99). The scan honors `.gitignore`, so + reference the vacated legacy range (ADR numbers 1–99) — except the defining ADR + (`legacy.defining_adr: ADR-900`), which names those numbers legitimately. The scan honors `.gitignore`, so gitignored corpora (e.g. `examples/.../claude-ai-history`) and scratch are skipped. Raw audit/scan byproducts go to `*-raw.json` (gitignored), not committed. - mkdocs strips unknown frontmatter, so catalog ids never reach readers — they are diff --git a/docs/architecture/access-workflow/ADR-908-documentation-strategy.md b/docs/architecture/access-workflow/ADR-908-documentation-strategy.md index 23281c71f..76dd99177 100644 --- a/docs/architecture/access-workflow/ADR-908-documentation-strategy.md +++ b/docs/architecture/access-workflow/ADR-908-documentation-strategy.md @@ -160,12 +160,18 @@ tooling — was generalized out of this repo into the agent-ways framework as it canonical *documentation model* (the framework's own `ADR-302`, unrelated to this project's `ADR-302`). This repo is that model's **reference implementation**, and `docs/scripts/{doc,doclint.py}` are **vendored copies** of the canonical tools -(copy-not-symlink, refreshed from canonical). That is why the vendored sources -cite `ADR-302` (the upstream model) and credit `ADR-908`/`ADR-900` (this repo's -local decisions) — the two numbers name two different things and both are correct. -The framework-side reference is prose only: do **not** add `ADR-302` to this ADR's -`related:` edges, since here that id resolves to the multimodal-ingestion ADR and -would be a false graph edge. +(copy-not-symlink, refreshed from canonical). The canonical tools are +**ADR-number-agnostic** — they refer to "your project's documentation-catalog ADR" +rather than hardcoding a number — so the vendored copies need no local citation +patch; *this* ADR (ADR-908) is that documentation-catalog ADR for this repo. One +caution survives the generalization: the framework's `ADR-302` is a different +document from this project's `ADR-302` (multimodal ingestion), so do **not** add +`ADR-302` to this ADR's `related:` edges — here it would resolve to the wrong node +and become a false graph edge. + +The vacated-range guard (`legacy: {retired: true}`) exempts the **defining ADR** +named by `legacy.defining_adr` (here `ADR-900`), which legitimately names retired +1–99 numbers; every other doc, ADR, and source file is still policed. ### Numbering freeze diff --git a/docs/architecture/adr.yaml b/docs/architecture/adr.yaml index 042468998..862e7cae9 100644 --- a/docs/architecture/adr.yaml +++ b/docs/architecture/adr.yaml @@ -83,6 +83,7 @@ legacy: range: [1, 99] label: "Retired (renumbered into domains 2026-06-15)" retired: true # vacated range — doclint fails the build on any reference into 1–99 + defining_adr: ADR-900 # the ADR that vacated the range; exempt — it names retired numbers legitimately # Viewer command for `adr view` # Use {file} as placeholder for the file path diff --git a/docs/scripts/doc b/docs/scripts/doc index 1dda31b2b..ddfda0c04 100755 --- a/docs/scripts/doc +++ b/docs/scripts/doc @@ -2,9 +2,10 @@ """ doc — a librarian for the documentation catalog. -Presents the catalog (frontmatter `id`/`domain`/`mode`, ADR-908) over the shared -domain bands: a domain×mode coverage matrix, page listings, and a gap report that -weighs doc coverage against ADR count per domain. +Presents the catalog (frontmatter `id`/`domain`/`mode`) over the shared domain +bands: a domain×mode coverage matrix, page listings, and a gap report that weighs +doc coverage against ADR count per domain. See your project's +documentation-catalog ADR for the model. This is the *diagnostic* front-end. The *test* is `doclint` (the CI linter); `doc lint` invokes it. Sibling of the `adr` tool — same `adr.yaml` config. diff --git a/docs/scripts/doclint.py b/docs/scripts/doclint.py index fc7f47d7d..0159d0ecd 100755 --- a/docs/scripts/doclint.py +++ b/docs/scripts/doclint.py @@ -4,9 +4,9 @@ Extends the ADR linter (`docs/scripts/adr lint`) from ADRs to the whole `docs/` tree, treating docs and ADRs as a single *decision graph*: nodes are records, -edges are `related`/`supersedes` references. See ADR-302 (unified documentation -model); generalized from the knowledge-graph-system reference implementation -(its ADR-908/ADR-900). +edges are `related`/`supersedes` references. See your project's +documentation-catalog decision record for the model; this tool was generalized +from the knowledge-graph-system reference implementation. It checks: @@ -25,8 +25,10 @@ ignored, so a repo can adopt the catalog gradually instead of all-at-once. - **mkdocs nav is optional.** No `mkdocs.yml` → the orphan check is skipped. - **The retired-range guard is opt-in.** Set `legacy: {retired: true}` in - `adr.yaml` to fail on references into a vacated pre-domain range (the - ADR-900 move). Repos that still use their legacy range leave it off (default). + `adr.yaml` to fail on references into a vacated pre-domain range. Set + `legacy: {defining_adr: ADR-NNN}` to exempt the ADR that vacated the range (it + names retired numbers legitimately). Repos still using their legacy range leave + both off (default). - **Project root is discovered** (git, then walking up for `docs/architecture/adr.yaml`), so the script works whether it is a symlink into the ways corpus (agent-ways dogfooding) or a vendored copy in a project. @@ -99,7 +101,6 @@ def get_project_root() -> Path: ".sh", ".yml", ".yaml", ".json"} RETIRED_SKIP_PARTS = {"node_modules", "dist", "site", ".git"} RETIRED_EXEMPT_NAMES = {"adr.yaml"} -RETIRED_EXEMPT_PREFIXES = ("ADR-900-",) RETIRED_ALLOW_MARKER = "doclint-allow-retired" ADR_ANYREF_RE = re.compile(r"\bADR-0*(\d+)(\.\d+)?\b") @@ -165,7 +166,7 @@ def parse_frontmatter(path: Path) -> dict: def _as_ref_list(value) -> list: """Coerce a related/supersedes value into target strings, stripping wikilinks. - ADR-302 edges are Obsidian `[[wikilinks]]`; the inside is the catalog id or + Catalog edges are Obsidian `[[wikilinks]]`; the inside is the catalog id or ADR reference. The aliased form `[[target|alias]]` keeps only `target`. Bare strings are accepted too (pre-wikilink ADRs). """ @@ -377,15 +378,20 @@ def _retired_scan_files(): yield from REPO.rglob("*") # non-git fallback -def check_retired_refs(lo: int, hi: int): - """Scan repo for references into a vacated pre-domain range (opt-in).""" +def check_retired_refs(lo: int, hi: int, exempt_prefixes: tuple = ()): + """Scan repo for references into a vacated pre-domain range (opt-in). + + exempt_prefixes: filename prefixes to skip — typically the ADR that vacated + the range (it names retired numbers legitimately), from adr.yaml + legacy.defining_adr. + """ hits = [] for f in _retired_scan_files(): if not f.is_file() or f.suffix not in RETIRED_SCAN_EXTS: continue if RETIRED_SKIP_PARTS & set(f.parts): continue - if f.name in RETIRED_EXEMPT_NAMES or f.name.startswith(RETIRED_EXEMPT_PREFIXES): + if f.name in RETIRED_EXEMPT_NAMES or (exempt_prefixes and f.name.startswith(exempt_prefixes)): continue try: text = f.read_text() @@ -473,7 +479,9 @@ def effective(node, severity): legacy = cfg.get("legacy", {}) or {} if legacy.get("retired"): lo, hi = (int(x) for x in legacy.get("range", [1, 99])) - retired_hits = check_retired_refs(lo, hi) + defining = legacy.get("defining_adr") + exempt = (f"{defining}-",) if defining else () + retired_hits = check_retired_refs(lo, hi, exempt) if retired_hits: print(f"\nRetired-range references (ADR-{lo}..{hi} are vacated):") for rel, ln, ref in sorted(retired_hits):