You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Tooling: add docs-reachable check + --docs-graph view to keep the doc tree connected from AGENTS.md
Every CLAUDE.md, DETAILS.md, and `docs/` file should be discoverable by an agent (or human) entering at `AGENTS.md` and walking references. Nothing enforced that before, so docs silently went orphan (14 unlinked guides, 4 subsystem CLAUDE.md). Two tools on one shared graph core (`docs_graph.go`):
- **`docs-reachable`** (`IsFast`, error not warn): fails when an enforced doc can't be reached from `AGENTS.md`. BFS over references, where a reference is any mention (Markdown link, `@import`, backtick path, bare path token) treated equally. A `CLAUDE.md` also counts as reached when a reachable doc names its *directory* (architecture.md lists subsystems as `` `dir/` ``, and Claude Code auto-injects from the dir anyway); every other doc must be named. Matching is generous (relative, root-relative, ≥2-segment suffix) so over-connecting hides a would-be orphan rather than inventing a false CI failure. `docs/specs` + `docs/notes` (self-declared ephemeral scratch) and the repo-root loader `CLAUDE.md` are excluded. Wired into the CI `hygiene` job. Shrink-wrapping allowlist (`docs-reachable-allowlist.json`) for intentional exemptions; goal is an empty list.
- **`pnpm check --docs-graph`**: renders the discoverability tree from `AGENTS.md` in the same box-drawing style as `--graph`, closest-to-root placement, `(dir reference)` tags, and a red orphan list at the bottom. A "show me" tool for spotting deeply-nested or orphaned docs.
Surfaces 18 real orphans today (left untouched here): they're for connecting, not exempting.
Docs: new § "Docs reachable" in `checks/DETAILS.md`, module-map + check-count rows, `--docs-graph` in the runner CLAUDE.md, and the allowlist consent contract extended in the file-length-allowlist rule.
|`claude-md-length.go`| Warn-only push-tier scanner: warns when a `CLAUDE.md` exceeds 600 words (`DETAILS.md` is the unlimited pull tier, not scanned). Allowlist with the same shrink-wrap semantics as file-length. See § CLAUDE.md length. |
20
20
|`claude-md-length-allowlist.json`| Allowlist for claude-md-length: `{ "files": { "path": wordCount } }`. Same ratchet/consent rules as file-length. |
21
+
|`docs_graph.go`| Shared doc-discoverability graph: reachability from `AGENTS.md` over references between docs. Powers both the `docs-reachable` check and the `--docs-graph` renderer. See § Docs reachable. |
22
+
|`docs-reachable.go`| Errors (not warn-only) when any `CLAUDE.md` / `DETAILS.md` / `docs/` file can't be reached from `AGENTS.md`. Allowlist with the same shrink-wrap/consent semantics as file-length. See § Docs reachable. |
23
+
|`docs-reachable-allowlist.json`| Allowlist for docs-reachable: `{ "files": { "path": reason } }` of docs intentionally unreachable. Goal is empty. Shrink-wraps gone/now-reachable entries; adding one needs David's OK. |
21
24
|`e2e-durations.go`| E2E test duration flagger (warn-only): parses the Playwright JSON reports after each E2E run and flags tests over the 2 s budget. Embedded in both E2E checks, not a registry check. See § E2E test duration flagger. |
22
25
|`e2e-duration-allowlist.json`| Per-platform (`macos` / `linux`) allowlist for the duration flagger: `{ "<spec>::<describe chain>::<title>": reason }`. Entries need a reason; new entries need David's OK. |
23
26
|`website-bundle-size.go`| Warn-only website `dist/` size budget: warns when the total grows >10% over the committed baseline. Self-skips without `dist/`. See § Website bundle-size baseline. |
@@ -185,6 +188,33 @@ local runs drop dead/under-threshold entries and ratchet >10%-slack entries down
185
188
change. Adding or raising an entry needs David's OK (`.claude/rules/file-length-allowlist.md`); the fix for an oversized
186
189
`CLAUDE.md` is to move depth into `DETAILS.md`, not bump the number.
187
190
191
+
## Docs reachable
192
+
193
+
`docs-reachable` (`IsFast`, an **error** not a warn: the doc tree must stay connected) enforces that every `CLAUDE.md`,
194
+
`DETAILS.md`, and `docs/` file is discoverable from the single root `AGENTS.md` by link-walking, so a reader entering
195
+
there can find every doc. `docs_graph.go` builds the graph (shared with the `--docs-graph` renderer in
196
+
`../docs_graph_render.go`); `docs-reachable.go` is the check shell + allowlist.
197
+
198
+
How reachability is decided (`BuildDocGraph`):
199
+
200
+
-**One root, `AGENTS.md`.** A doc is reached when a doc already reached from the root references it. BFS, so each doc
201
+
is placed under its closest-to-root reference (a cycle just hits an already-reached node and stops).
202
+
-**A reference is any mention, syntax-agnostic:** Markdown link, `@import`, backtick path, or bare path token are all
203
+
equal. We watch intent, not form. Matching is generous (relative-to-source, repo-root-relative, and ≥2-segment path
204
+
suffix), because over-connecting only hides a would-be orphan, while a false orphan would be a noisy CI failure.
205
+
-**The CLAUDE.md asymmetry:** a `DETAILS.md` or `docs/` file must be named, but a `CLAUDE.md` also counts as reached
206
+
when a reachable doc mentions its _directory_ (`architecture.md` lists most subsystems as `` `some/dir/` ``, and
207
+
Claude Code auto-injects a `CLAUDE.md` from its directory regardless). Such edges are tagged `ViaDir`; the renderer
208
+
shows "(dir reference)".
209
+
-**Ephemeral dirs are excluded** from the enforced candidate set: `docs/specs` and `docs/notes` self-declare in their
210
+
READMEs as temporary scratch "wiped periodically", so requiring each to be linked fights their purpose
211
+
(`ephemeralDocDirs`). The repo-root `CLAUDE.md` (the loader shim that only `@import`s the entry docs) is excluded too.
212
+
213
+
`docs-reachable-allowlist.json` maps a doc path → the reason it's intentionally unreachable. The goal is an empty list:
214
+
connect docs rather than exempt them. Shrink-wrap drops entries whose file is gone or which became reachable; adding or
215
+
keeping one needs David's OK (`.claude/rules/file-length-allowlist.md`). To inspect the whole tree and spot
216
+
deeply-nested or orphaned docs visually, run `pnpm check --docs-graph`.
217
+
188
218
## E2E test duration flagger
189
219
190
220
The E2E suites were hard-won down to under 2 s per test; `e2e-durations.go` defends that. After a successful E2E run,
@@ -253,16 +283,16 @@ RUSTSEC ignores — that's a quarterly task in `docs/maintenance.md`.
"$comment": "Docs intentionally NOT reachable from AGENTS.md, each mapped to its reason. The goal is an empty list: connect docs instead of exempting them. The docs-reachable check shrink-wraps stale entries (file gone or now reachable) on local runs; adding or keeping an entry needs David's explicit consent (see .claude/rules/file-length-allowlist.md).",
"%d %s unreachable from AGENTS.md%s. Link each from a doc that's already reachable (a CLAUDE.md also counts as reached when a reachable doc mentions its directory):\n%s",
0 commit comments