Skip to content

fix(tooling): check-doc-authoring 的死 ROOT 改为点名硬报错 (#4916) - #4934

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4916-doc-authoring-dead-root-hard-error
Aug 3, 2026
Merged

fix(tooling): check-doc-authoring 的死 ROOT 改为点名硬报错 (#4916)#4934
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4916-doc-authoring-dead-root-hard-error

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4916

scripts/check-doc-authoring.mjscollectFiles() 把每条 root 的遍历包在 try { walk(r, files); } catch {} 里。.claude / skills / content 任何一条被改名、移动或删除,ENOENT 就地被吞,门禁跑完剩下的 root 然后打印 ✓ doc authoring guard: N files clean,exit 0。从外部看,"三个 root 都干净"和"其中一个压根没被打开"是同一行绿字,只是 N 小了 —— 而没人读 N。

缺陷实测(旧代码,当前这棵树)

=== OLD code, all roots present ===
✓ doc authoring guard: 219 files clean — no bare metadata literals.
exit=0
=== OLD code, .claude renamed away ===
✓ doc authoring guard: 215 files clean — no bare metadata literals.
exit=0
=== OLD code, --self-test with .claude renamed (still green) ===
✓ check-doc-authoring self-test: scope wiring (.claude in, .claude/worktrees out) and detection both hold.
exit=0

219 → 215,exit 仍是 0。第三段是本单相对 #4915 的增量所在:#4913 加的 --self-test自己的临时树上断言,所以仓库里真实的 .claude/ 被改名时它照样绿。

选的是硬报错,不是白名单 —— 理由

没有白名单,也没有 optional: true 开关,这是刻意的:.claudeskillscontent 三条都是 git 跟踪的目录、里面有跟踪的文件,所以任何一个能在仓库根跑 pnpm check:doc-authoring 的检出都不会合法地缺其中之一 —— 议题里假设的"某个 root 在某些检出形态下合法缺席"经核查不存在,白名单没有要装的东西。

而"先留着以防万一"是有代价的:一个 optional 标记等于给下一个作者一条受支持的路去让这个失败闭嘴,而不是去跟进改名 —— 那就是空 catch {},只是说得客气些。真有 root 变成合法缺席的那天,那是一个需要连同条件和测试一起记录的决定,不是把检查放松。

顺带把 walk 内层的容错也去掉了:遍历途中出错同样意味着语料只读了一半,那也不该打印成一次干净的扫描。

新增 assertRootsResolvable() 在任何遍历之前跑,对每条 root 抛 DeadRootError 并带上名字(缺失 / 读不到 / 存在但不是目录三种判据分开)。

双向证明

红 —— 真实仓库树,.claude/ 改名走开:

✗ doc authoring guard: declared ROOT(s) do not resolve, so the scan would have been silently narrower:

  .claude — does not exist

Every entry in ROOTS (scripts/check-doc-authoring.mjs) must be a directory in the checkout,
and this check runs from the repo root. If a corpus directory was renamed or moved, update
ROOTS to follow it; if it was deleted, remove the entry deliberately. Do NOT restore a
tolerant skip: this used to be `catch {}`, and a dead root simply shrank the reported file
count while the gate kept printing green (#4916).

exit=1

红 —— 同时两条死根,两条都点名(存活的 root 不背锅):

  .claude — does not exist
  content — does not exist
exit=1

绿 —— 两条都恢复之后:

✓ doc authoring guard: 219 files clean — no bare metadata literals.
exit=0
✓ check-doc-authoring self-test: ... and the dead-root hard error (red when a ROOT is renamed, green when restored) all hold.
exit=0

这条红-绿证明已经折进 --self-test,常驻每一次 CI,不是只活在这段 PR 描述里:自检现在会在运行中途把一条 root 改名走开、要求判红且点名那条 root、且不提及存活的两条,再把另一条 root 换成同名文件、要求 exists but is not a directory 判据,然后两条都恢复、要求重新判绿。只观察到绿,对一个"失败表现为少扫一点"的门禁来说什么也证明不了 —— 所以自检每次都先观察红。

对称方向的调查结论(#4851 的经验)

#4851(PR #4921)在隔壁脚本上揭示的不只有 16 条死条目,还有 48 份从未被列过的文档。所以本单也查了反方向:有没有真实存在、应当被这条规则约束、却从来不在 ROOTS 里的语料目录?

有。 全仓 md/mdx 逐个走 ts 围栏块统计后:

本 PR 刻意没有改 ROOTS:纳入哪些 docs/ 子目录(docs/audits/ / docs/handoff/ 这类一次性过程记录纳入等于让历史快照永久受当前 lint 约束)是个范围决定,单独立单 #4929 交维护者定。

顺带扫到的同形状(未在本 PR 修)

三条 check:* 脚本是同一缺陷的更彻底版本 —— 扫描根读不到时 walker 返回空数组,于是收集到 0 个文件、循环一次不进、exit 0:check-single-authz-resolver.mjs:38check-startup-registry-verdict.mjs:217check-driver-conformance.mjs:147。单独立单 #4930,顺手全改会让这条 PR 失焦。判定为不同形状、不该一起改的(glob 展开中途的跳过、已记账的跳过、已声明的 exit 0)在该单里逐条写明。

验证

$ pnpm check:doc-authoring
✓ check-doc-authoring self-test: scope wiring (.claude in, .claude/worktrees out), detection, and the dead-root hard error (red when a ROOT is renamed, green when restored) all hold.
✓ doc authoring guard: 219 files clean — no bare metadata literals.
exit=0

$ npx eslint scripts/check-doc-authoring.mjs
exit=0

范围

只动了 scripts/check-doc-authoring.mjs 一个文件,外加一份空 frontmatter 的 changeset(tooling 改动,不发版)。没有改根 package.json,没有.github/workflows/ —— check:doc-authoring 已经在 package.json:36 里是 --self-test && 实跑 的组合,lint.yml:95 已经调它,新的自检断言自动生效,无需碰这两个本仓最热的冲突点。


Generated by Claude Code

…s dead (#4916)

`collectFiles()` walked each root inside `try { walk(r, files); } catch {}`.
Rename, move or delete any one of `.claude` / `skills` / `content` and its
ENOENT was swallowed in place: the scan finished the remaining roots and
printed `✓ doc authoring guard: N files clean`, exit 0. Measured on this tree
with `.claude/` renamed away, the old code reports 215 files clean, exit 0,
where the honest answer is 219 — "all three roots are clean" and "one root was
never opened" are the same green line with a smaller N, and nobody reads N.

`assertRootsResolvable()` now runs before any walking and throws a
`DeadRootError` naming every root that is missing, unreadable, or not a
directory; `main()` renders that as a red gate pointing at the dead root. No
whitelist and no `optional` flag: all three roots are git-tracked directories
with tracked files, so no checkout that can run this gate at the repo root is
legitimately missing one, and an optional marker would be a supported way to
silence the failure instead of following the rename — the empty catch, spelled
politely. The inner try is gone too: an error during the walk also means the
corpus was only partly read.

The proof is bidirectional and permanent. `--self-test` (#4913) already walked
a real temporary tree with the real walker; it now renames one root away
mid-run and requires red naming that root and not the survivors, replaces
another root with a file and requires the `not a directory` verdict, then
restores both and requires green again. This closes what #4913's self-test
could not: it stayed green with the repo's real `.claude/` renamed away,
because it asserts over its own temp tree.

Same discipline as #4690 / #4804 / #4835 / #4851 / #4868 / #4890.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018iARDqtrhQgz6fVHDeDkbQ
@vercel

vercel Bot commented Aug 3, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 3, 2026 4:33pm

Request Review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

check-doc-authoring 的 walk 用 catch {} 吞掉不存在的 ROOT —— 目录一改名,门禁静默变瞎并继续报绿

2 participants