Skip to content

Commit 6bc93dc

Browse files
fix(tooling): derive the docs-audit scope from the filesystem instead of a hand-kept list (#4851) (#4921)
`.claude/workflows/docs-accuracy-audit.js` carried its default audit scope inline, as a hand-kept `ALL_HANDWRITTEN` array behind a "keep in sync with `affected-docs.mjs --all`" comment. Nothing checked that promise, and it had rotted in BOTH directions: - 16 listed paths no longer existed — 10 of them the whole `content/docs/protocol/objectos/**` directory, renamed to `protocol/kernel/`. An audit agent pointed at a non-existent file reads nothing and reports `fixCount: 0`, indistinguishable in the run summary from a doc that was checked and found accurate. That is how #4781 and #4817 sat in `protocol/kernel/` for ~2 months under green "full" audits. - 48 existing docs were absent from the list — including all 9 of `protocol/kernel/**` and the whole `content/docs/capabilities/` directory. A run logging `FULL audit` was auditing 130 of 178 hand-written docs. The list stays inline because it must: a workflow script runs in a `node:vm` context with no require/import/filesystem, so it can neither walk `content/docs/` nor read a JSON artifact. So it is GENERATED instead — `scripts/docs-audit/check-audit-scope.mjs --write` derives it from `affected-docs.mjs --all` (one definition of "hand-written doc"), and `pnpm check:docs-audit-scope` fails in lint.yml when the block and `content/docs/` disagree in either direction, naming every entry. Missing markers or an unparseable block fail too — a gate that cannot find its subject must go red, not green. A CI gate only sees the default list, so two more nets cover the rest: - the workflow preflights its resolved scope (including a caller-supplied `args.docs`) and refuses to start, naming every path that does not exist; the preflight's own arithmetic is reconciled against the scope, so a verdict that cannot account for every path exactly once is a failed preflight, not a pass; - every audit agent reports `docExists` from the path that actually opens the file, and the run throws if any comes back false — a self-check that runs somewhere other than the real read path proves nothing about it (#4868). Same discipline as #4690 / #4777 / #4804 / #4835 / #4868 / #4890. Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 2e284b2 commit 6bc93dc

7 files changed

Lines changed: 769 additions & 10 deletions

File tree

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
---
2+
---
3+
4+
tooling: derive the docs-accuracy audit's scope from the filesystem and fail loudly when
5+
it drifts (#4851)
6+
7+
Release-nothing: touches `.claude/workflows/docs-accuracy-audit.js`,
8+
`scripts/docs-audit/`, the root `check:docs-audit-scope` script and one `lint.yml` step —
9+
no package code.
10+
11+
`.claude/workflows/docs-accuracy-audit.js` carried its default audit scope inline, as a
12+
hand-kept `ALL_HANDWRITTEN` array behind a "keep in sync with `affected-docs.mjs --all`"
13+
comment. Nothing checked that promise, and it had rotted in **both** directions:
14+
15+
- **16 listed paths no longer existed** — 10 of them the whole
16+
`content/docs/protocol/objectos/**` directory, renamed to `protocol/kernel/`. A doc
17+
path that resolves to nothing produces an audit agent that reads nothing and reports
18+
`fixCount: 0`, which in the run summary is indistinguishable from a doc that was
19+
checked and found accurate. That is how the accuracy defects in #4781
20+
(`runtime-capabilities.mdx` documenting a schema deleted in #3605) and #4817
21+
(`http-protocol.mdx` attributing the dispatcher's response shape to
22+
`/api/v1/discovery`) survived ~2 months of green "full" audits.
23+
- **48 existing docs were absent from it** — including all 9 of `protocol/kernel/**` and
24+
the entire `content/docs/capabilities/` directory. A run logging
25+
`FULL audit (no args.docs given)` was auditing 130 of 178 hand-written docs.
26+
27+
The list stays inline because it must: a workflow script runs in a `node:vm` context
28+
with no `require`, no `import` and no filesystem, so it can neither walk `content/docs/`
29+
nor read a JSON artifact. So it is now **generated** rather than hand-kept —
30+
`node scripts/docs-audit/check-audit-scope.mjs --write` derives it from
31+
`affected-docs.mjs --all` (one definition of "hand-written doc", not a second walk to
32+
drift), and `pnpm check:docs-audit-scope` fails in `lint.yml` when the block and
33+
`content/docs/` disagree in either direction, naming every entry.
34+
35+
Two more nets, because a CI gate can only see the *default* list:
36+
37+
- the workflow **preflights its resolved scope** — including a caller-supplied
38+
`args.docs` — and refuses to start, naming every path that does not exist. Its
39+
arithmetic is reconciled against the scope, so a preflight that cannot account for
40+
every path exactly once is a failed preflight, not a pass;
41+
- every audit agent now reports `docExists` from the path that actually opens the file,
42+
and the run **throws** if any comes back false. The preflight is not the real read
43+
path, and a self-check that runs somewhere other than the real path proves nothing
44+
about it (#4868).
45+
46+
Same discipline as #4690 / #4777 / #4804 / #4835 / #4868 / #4890: a check whose subject
47+
has gone missing must go red, never green-by-vacancy.

.claude/workflows/docs-accuracy-audit.js

Lines changed: 314 additions & 6 deletions
Large diffs are not rendered by default.

.github/workflows/lint.yml

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,20 @@ jobs:
9494
- name: Doc/skill authoring guard
9595
run: pnpm check:doc-authoring
9696

97+
# #4851: the docs-accuracy-audit workflow carries its default scope inline
98+
# (a workflow script runs in a vm with no filesystem, so it cannot enumerate
99+
# content/docs/ itself). Hand-kept, that list rotted in BOTH directions —
100+
# 16 entries pointing at files that no longer existed after the
101+
# protocol/objectos → protocol/kernel rename, and 48 existing docs missing
102+
# from it — while every "FULL audit" run reported green, which is how the
103+
# accuracy defects in #4781 and #4817 survived ~2 months. This regenerates
104+
# the list from the filesystem and fails when the two disagree either way.
105+
# It lives in this job deliberately: the change that breaks the list is a
106+
# docs rename, so a `packages/**` paths filter would blind the gate to
107+
# exactly its own failure mode.
108+
- name: Docs-audit scope is derived, not hand-kept
109+
run: pnpm check:docs-audit-scope
110+
97111
# ADR-0090 D3 vocabulary ratchet: "role" is reserved-forbidden in docs
98112
# and skills. Existing occurrences are frozen in the baseline (better-auth
99113
# boundary, ARIA samples, educational mentions); NEW occurrences fail.

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@
3434
"check:i18n-coverage": "node scripts/check-i18n-coverage.mjs",
3535
"check:nul-bytes": "node scripts/check-nul-bytes.mjs --self-test && node scripts/check-nul-bytes.mjs",
3636
"check:doc-authoring": "node scripts/check-doc-authoring.mjs --self-test && node scripts/check-doc-authoring.mjs",
37+
"check:docs-audit-scope": "node scripts/docs-audit/affected-docs.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs --self-test && node scripts/docs-audit/check-audit-scope.mjs",
3738
"check:role-word": "node scripts/check-role-word.mjs",
3839
"check:adr-anchors": "node scripts/check-adr-anchors.mjs",
3940
"check:org-identifier": "node scripts/check-org-identifier.mjs",

scripts/docs-audit/README.md

Lines changed: 43 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,44 @@ looks like the obvious next step and is a provable no-op, for two independent re
8181
A hand-edited CHANGELOG outside a release is also close to nonexistent in practice. Left
8282
counted, and recorded here so the idea is not rediscovered as a gap.
8383

84+
## 1b. `check-audit-scope.mjs` — the audit workflow's scope, derived not hand-kept
85+
86+
```bash
87+
node scripts/docs-audit/check-audit-scope.mjs # verify (also: pnpm check:docs-audit-scope)
88+
node scripts/docs-audit/check-audit-scope.mjs --write # regenerate the list from the filesystem
89+
node scripts/docs-audit/check-audit-scope.mjs --self-test
90+
```
91+
92+
The `docs-accuracy-audit` workflow (part 3) carries its default scope **inline**, as
93+
`ALL_HANDWRITTEN`. It has to: a workflow script runs inside a `node:vm` context whose
94+
only globals are `log`/`phase`/`console`/`budget`/timers plus
95+
`agent`/`parallel`/`pipeline`/`workflow`/`args`, with code generation disabled — no
96+
`require`, no `import`, no filesystem. It can neither walk `content/docs/` nor read a
97+
JSON artifact, so the list cannot be derived *at run time*.
98+
99+
It is therefore derived at *generation* time instead: `--write` rewrites the block from
100+
`affected-docs.mjs --all` (one definition of "hand-written doc", not two), and the plain
101+
run is a CI gate in `lint.yml` that fails when the block and `content/docs/` disagree
102+
**in either direction**.
103+
104+
Both directions matter, and only one had ever been noticed (#4851):
105+
106+
- **listed but missing** — the 10 `content/docs/protocol/objectos/**` paths left behind
107+
by the rename to `protocol/kernel/`, plus 6 others. An audit agent pointed at a
108+
non-existent file reads nothing and reports `fixCount: 0`, which in the run summary is
109+
indistinguishable from a doc that was checked and found accurate. That is how the
110+
accuracy defects in #4781 and #4817 sat in `protocol/kernel/` for ~2 months while full
111+
audits reported green.
112+
- **exists but unlisted** — 48 docs, including all of `protocol/kernel/**` and the whole
113+
`capabilities/` directory. A run logging `FULL audit (no args.docs given)` was
114+
auditing 130 of 178 docs.
115+
116+
The workflow additionally preflights its resolved scope — including a caller-supplied
117+
`args.docs`, which no CI gate can see — and aborts naming any path that does not exist;
118+
and each audit agent reports `docExists` from the read path itself, so a preflight that
119+
was wrong cannot be laundered into a green summary. The gate covers the default list,
120+
the preflight covers the caller's list, and the read path checks both.
121+
84122
## 2. CI gate — `.github/workflows/docs-drift-check.yml`
85123

86124
On any PR that touches `packages/**`, runs `affected-docs.mjs` against the base branch
@@ -119,6 +157,8 @@ opens a PR when there are fixes. See the routine prompt for the exact steps.
119157

120158
---
121159

122-
**Cost note:** a full audit of all 128 hand-written docs is ~2.8M output tokens / ~160
123-
agents. Always prefer the change-scoped list (`affected-docs.mjs`) over `--all` except for
124-
the periodic full backstop.
160+
**Cost note:** a full audit is ~2 agents per doc — measured at ~2.8M output tokens /
161+
~160 agents when the scope was 128 docs, and the hand-written set is 178 today (run
162+
`check-audit-scope.mjs` for the current number; don't trust a count written down here).
163+
Always prefer the change-scoped list (`affected-docs.mjs`) over `--all` except for the
164+
periodic full backstop.

scripts/docs-audit/affected-docs.mjs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/usr/bin/env node
22
// Map a set of `packages/**` code changes to the hand-written docs that reference
33
// the affected packages, so a doc-accuracy audit can be scoped to what actually
4-
// changed instead of re-auditing all 128 hand-written docs every time.
4+
// changed instead of re-auditing every hand-written doc (178 of them today) each time.
55
//
66
// Usage:
77
// node scripts/docs-audit/affected-docs.mjs [sinceRef] # docs affected by changes since <sinceRef> (default origin/main)

0 commit comments

Comments
 (0)