Skip to content

Commit efbc89b

Browse files
os-project-managerclaude
authored andcommitted
fix(devx): docs drift check —— dev-only manifest 改动不再点亮整包文档,release 页改为只读分区 (#6893)
Docs Drift Check 在两类 PR 上给出的建议无法执行:一个只改了 package.json 里 一行 build 脚本的 PR 被报出 113 篇文档,而列表里还包含代码 PR 明令禁止编辑的 content/docs/releases/**。 两处改动,都不缩小真正的告警面: 1. dev-only manifest 例外(字段级,不是文件级)。<packageRoot>/package.json 若改动的顶层 key 全部落在 {scripts, devDependencies},则不计入变更包。 exports/main/dependencies/files/version 仍然计入。这是 #4183 修复的残余: 当时排除了 check 脚本本身,却保留了注册它的 package.json 那一行。 2. release 页改为「分区」而不是「过滤」。docs 仍是全集(审计范围依赖它), 新增 releaseOwnedDocs 作为它的子集,PR 评论把这些行放进单独的只读段落, 明确写「file an issue,不要在这里改」。这是 #4920 裁决的形状,不是它否决的 那个选项(排除 release 页曾被否决,因为最常被阅读的页面会静默失去审计)。 RELEASE_OWNED_PREFIX 现在有第三份字面副本;check-audit-scope.mjs 遍历 RELEASE_OWNED_CONSUMERS,任何一份与 AGENTS.md 的 guardrail 行不一致即红。 Fixes #6893
1 parent 4f3d232 commit efbc89b

4 files changed

Lines changed: 386 additions & 41 deletions

File tree

.github/workflows/docs-drift-check.yml

Lines changed: 34 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,24 +65,52 @@ jobs:
6565
const docs = data.docs || [];
6666
const pkgs = (data.changedPackages || []).map(p => p.name || p.dir);
6767
const marker = '<!-- docs-drift-check -->';
68+
// The release-owned rows are PARTITIONED OUT of the editable list, never
69+
// dropped (#6893, following the #4920 ruling). They keep getting audited —
70+
// `docs` above is still the full set the audit workflow is scoped to — but
71+
// listing them beside editable pages steers a reader who treats this comment
72+
// as a worklist into the one edit AGENTS.md forbids outright. So they get
73+
// their own section, carrying the instruction that makes them safe.
74+
const readOnly = data.releaseOwnedDocs || [];
75+
const editable = docs.filter(d => !readOnly.includes(d));
6876
let body;
6977
if (docs.length === 0) {
7078
body = `${marker}\n### 📓 Docs Drift Check\nNo hand-written docs reference the ${pkgs.length} changed package(s). ✅`;
7179
} else {
7280
const detail = (data.detail || []).reduce((m, d) => (m[d.doc] = d.via, m), {});
73-
const list = docs.map(d => `- \`${d}\`${detail[d] ? ` _(via ${detail[d].join(', ')})_` : ''}`).join('\n');
81+
const row = d => `- \`${d}\`${detail[d] ? ` _(via ${detail[d].join(', ')})_` : ''}`;
7482
body = [
7583
marker,
7684
'### 📓 Docs Drift Check',
7785
`This PR changes **${pkgs.length}** package(s): ${pkgs.map(p => `\`${p}\``).join(', ')}.`,
78-
'',
79-
`**${docs.length}** hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:`,
80-
'',
81-
list,
86+
];
87+
if (editable.length) {
88+
body.push(
89+
'',
90+
`**${editable.length}** hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:`,
91+
'',
92+
editable.map(row).join('\n'),
93+
);
94+
}
95+
if (readOnly.length) {
96+
body.push(
97+
'',
98+
`⛔ **${readOnly.length}** release-owned page(s) ${editable.length ? 'also ' : ''}reference the affected code. These are **read-only**:`,
99+
'',
100+
readOnly.map(row).join('\n'),
101+
'',
102+
'> `content/docs/releases/` is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release',
103+
'> notes are written centrally at release time, and a code PR that edits them is the exact PR',
104+
'> that guardrail exists to stop. They are still audited — read-only. If one of them is actually',
105+
'> wrong, **file an issue** or open a dedicated docs-only PR; do not edit it here.',
106+
);
107+
}
108+
body.push(
82109
'',
83110
'> Advisory only. To re-verify, run the `docs-accuracy-audit` workflow scoped to these files:',
84111
'> `node scripts/docs-audit/affected-docs.mjs origin/' + baseRef + '` → pass the list as `args.docs`.',
85-
].join('\n');
112+
);
113+
body = body.join('\n');
86114
}
87115
const { data: comments } = await github.rest.issues.listComments({
88116
owner: context.repo.owner, repo: context.repo.repo, issue_number: context.issue.number,

scripts/docs-audit/README.md

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ matching arm entirely, so a doc naming `@objectstack/service-automation` but not
4141
repo path was a guaranteed miss — #4162.) A deleted package falls back to the coarse
4242
`packages/<x>` token, which still substring-matches any doc naming the deleted path.
4343

44-
**Two exclusions:** change classes that cannot make an implementation-accuracy doc
44+
**Three exclusions:** change classes that cannot make an implementation-accuracy doc
4545
stale are dropped before the changed-package roots are derived:
4646

4747
1. **Test files** (`*.test.*` / `*.spec.*` at any depth, plus `__tests__` /
@@ -52,16 +52,46 @@ stale are dropped before the changed-package roots are derived:
5252
2. **Package tooling scripts** (`<packageRoot>/scripts/**`): build/verification
5353
tooling, not the runtime behaviour docs describe (#4183 flagged 106 docs for a diff
5454
whose only code change was a new check script). Narrow on purpose: `src/scripts/**`
55-
is runtime code and stays counted, and so does `package.json` — exports/deps
56-
changes ARE implementation. No package publishes runtime code from `scripts/`
55+
is runtime code and stays counted. No package publishes runtime code from `scripts/`
5756
(checked against every `files` allowlist; three plugins ship a lone
5857
`i18n-extract.config.ts` only for lack of a `files` field).
58+
3. **Dev-only manifest edits** (#6893): a `<packageRoot>/package.json` whose changed
59+
**top-level keys** are all in `{scripts, devDependencies}`. This is the only
60+
**field-level** exclusion — `package.json` as a file stays counted, because
61+
`exports` / `main` / `dependencies` / `files` / `version` changes ARE implementation.
62+
63+
It is the residue of exclusion 2: #4183 dropped the check *script* but kept the
64+
`package.json` line registering it, so the same PR still lit up the same doc set
65+
through the manifest. Measured over 400 merged commits, five had a `package.json` as
66+
their only `packages/**` implementation change, and **all five** touched nothing but
67+
those two keys — 152 doc-rows in total, none of which could be stale:
68+
69+
| commit | keys changed | docs flagged |
70+
|:--|:--|--:|
71+
| `df0605ba5` | `scripts` | 12 |
72+
| `2672f855f` | `scripts` | **113**#6893's headline number |
73+
| `a64315556` | `devDependencies` | 10 |
74+
| `77d9001c7` | `devDependencies` | 13 |
75+
| `466bd9285` | `devDependencies` | 4 |
76+
77+
The last three are `test(...)` commits: exactly the class exclusion 1 exists to kill,
78+
leaking through the manifest instead. The allowlist is an allowlist on purpose — an
79+
unknown or newly-invented key falls on the **counted** side — and unparseable, added
80+
or deleted manifests are counted too.
81+
82+
**Why it cannot narrow the net:** the classifier is per *file*. A PR that also touches
83+
that package's `src/**` derives the package root from those files anyway, so this arm
84+
only ever decides the case where the manifest is the package's sole change. Verified
85+
both directions on the real diffs (#6893): adding an `exports` entry to
86+
`packages/spec/package.json` still flags 113 docs, and a `scripts` entry *alongside* a
87+
`src/` edit also still flags 113 — with the manifest itself reported as skipped.
5988

6089
The excluded counts are reported in the summary line and as `testFilesSkipped` /
61-
`scriptFilesSkipped` in `--json`, so the narrowing is never silent. `--self-test` pins
62-
the classifiers *and* the package-root derivation against paths that must and must not
63-
match (`commands/test.ts` is implementation; `foo.conformance.test.ts` is not; a
64-
container directory must never come out as a package root).
90+
`scriptFilesSkipped` / `devOnlyManifestsSkipped` in `--json`, so the narrowing is never
91+
silent. `--self-test` pins the classifiers *and* the package-root derivation against
92+
paths that must and must not match (`commands/test.ts` is implementation;
93+
`foo.conformance.test.ts` is not; a container directory must never come out as a package
94+
root; `dependencies` is never dev-only).
6595

6696
**And one deliberate non-exclusion:** `packages/*/CHANGELOG.md` stays counted, even though
6797
release notes define behaviour no more than a test does. Extending the exclusion there
@@ -76,7 +106,9 @@ looks like the obvious next step and is a provable no-op, for two independent re
76106
2. Even if it did run, `changeset version` writes `package.json` next to every
77107
`CHANGELOG.md` it appends to — 45 of the former against 46 of the latter on the first
78108
page of #3910's diff — so dropping the CHANGELOGs would leave the derived package-root
79-
set bit-identical.
109+
set bit-identical. Exclusion 3 does **not** undercut this: what `changeset version`
110+
rewrites is `version` (and workspace `dependencies` ranges), neither of which is in
111+
the dev-only allowlist, so those manifests stay counted.
80112

81113
A hand-edited CHANGELOG outside a release is also close to nonexistent in practice. Left
82114
counted, and recorded here so the idea is not rediscovered as a gap.
@@ -168,6 +200,27 @@ and posts/updates a single advisory PR comment listing the docs that reference t
168200
changed code. **Never fails the build** — it only flags drift at the source, before it
169201
lands on `main`. Reviewers (or an on-demand audit run) decide whether to re-verify.
170202

203+
### The comment forks release-owned pages into a read-only section (#6893)
204+
205+
Same ruling as [1b](#release-owned-pages-are-in-scope-and-read-only-4920), one level
206+
down. The comment used to list `content/docs/releases/v17.mdx` in the same bulleted list
207+
as editable pages — so a reader treating the advisory as a worklist was being pointed at
208+
the one edit AGENTS.md forbids outright. The specimen that made it concrete: PR #6921
209+
changed two diagnostic strings in `packages/lint` and got back three rows, one of them
210+
that release page.
211+
212+
They are **not filtered out**. `docs` in `--json` stays the full set (it is what scopes
213+
the audit, and #4920 rejected excluding these pages for good reasons); `releaseOwnedDocs`
214+
is a **partition** of it — `releaseOwnedDocs ⊆ docs`, always — and the comment renders it
215+
under its own ⛔ heading telling the reader to file an issue instead of editing.
216+
217+
`affected-docs.mjs` therefore holds a third literal copy of `RELEASE_OWNED_PREFIX`,
218+
alongside AGENTS.md's guardrail row and the audit workflow's own const. Copies, because
219+
the workflow is evaluated in a sandbox VM that cannot import and a shared module would
220+
leave *it* the only unanchored one. `check-audit-scope.mjs` iterates
221+
`RELEASE_OWNED_CONSUMERS` and fails if any copy stops matching the guardrail row — **add
222+
a consumer, add it to that list.**
223+
171224
## 3. `docs-accuracy-audit` workflow — the LLM audit
172225

173226
A Claude Code multi-agent workflow (`.claude/workflows/docs-accuracy-audit.js`). For each

0 commit comments

Comments
 (0)