未认领。 审核 PR #4915(#4913)时发现,不在那条 PR 的范围内(它没有引入这个行为,只是把 ROOTS 从 2 个扩到 3 个),按 Prime Directive #10 单独归档。
现状
scripts/check-doc-authoring.mjs 的 collectFiles():
for (const r of ROOTS) { try { walk(r, files); } catch {} }
ROOTS 现在是 ['.claude', 'skills', 'content']。任何一条解析不到(目录改名、移动、被删),walk 抛出的 ENOENT 被 catch {} 原地吞掉,门禁继续跑完剩下的 root,然后打印:
✓ doc authoring guard: N files clean — no bare metadata literals.
N 变小了,exit 仍是 0。 从外部看,"三个 root 都干净"和"其中一个 root 压根没被扫"长得一模一样。
为什么值得单独修
这与 #4851 是同一个缺陷,只是在隔壁脚本里:.claude/workflows/docs-accuracy-audit.js 的 ALL_HANDWRITTEN 清单 11 条路径全部指向改名前的 protocol/objectos/*,于是内部文档审计静默漏审整个 protocol/kernel —— #4781 与 #4817 因此烂了约两个月才被发现。那次的代价是实测过的。
本会话这两天已连关五个同族(#4690 无 MANIFEST 也 exit 0、#4804 判据写在 stderr 而 gate 从不读、#4835 词表少一个入口、#4868 自检与真实调用路径不是同一条、#4890 门禁看不见自己所在的目录)。共同形状:检查在跑,而且是绿的,但结构上接触不到它的检查对象。
.claude 这条尤其脆:它是本地 agent 工具目录,比 content/ / skills/ 更可能被重组。
PR #4915 覆盖了什么、没覆盖什么
#4915 新增的 --self-test 用真实 walker 从真实 ROOTS 走一遍临时树,断言 .claude 进得去、.claude/worktrees 进不去。所以:
- ✅ 有人把
.claude 从 ROOTS 数组里删掉 → self-test 判红;
- ❌ 仓库里真实的
.claude/ 目录被改名 → ROOTS 数组没变,self-test 在临时树里照样绿,而真实扫描静默少一个 root。
建议
启动时校验每条 ROOT 都能解析,有一条不行就报错退出并点名,而不是继续跑。catch {} 若确有存在理由(例如某个 root 在某些检出形态下合法缺席),把那个理由写成显式的白名单,而不是一个吞掉一切的空 catch。
⚠️ 必须双向证明:故意改坏一条 ROOT,证明新校验判红;恢复后判绿。这个脚本本身就是"一直绿、判据可能是空的"的活标本,只观察到绿什么也证明不了。
顺带看一眼仓内 scripts/ 下还有没有同形状的 catch {} / 静默跳过(几个 check:* 脚本值得一起扫),有就在报告里列出来,由 PM 决定是纳入还是另立。
关联
未认领。 审核 PR #4915(#4913)时发现,不在那条 PR 的范围内(它没有引入这个行为,只是把 ROOTS 从 2 个扩到 3 个),按 Prime Directive #10 单独归档。
现状
scripts/check-doc-authoring.mjs的collectFiles():ROOTS现在是['.claude', 'skills', 'content']。任何一条解析不到(目录改名、移动、被删),walk抛出的 ENOENT 被catch {}原地吞掉,门禁继续跑完剩下的 root,然后打印:N 变小了,exit 仍是 0。 从外部看,"三个 root 都干净"和"其中一个 root 压根没被扫"长得一模一样。
为什么值得单独修
这与 #4851 是同一个缺陷,只是在隔壁脚本里:
.claude/workflows/docs-accuracy-audit.js的ALL_HANDWRITTEN清单 11 条路径全部指向改名前的protocol/objectos/*,于是内部文档审计静默漏审整个protocol/kernel—— #4781 与 #4817 因此烂了约两个月才被发现。那次的代价是实测过的。本会话这两天已连关五个同族(#4690 无 MANIFEST 也 exit 0、#4804 判据写在 stderr 而 gate 从不读、#4835 词表少一个入口、#4868 自检与真实调用路径不是同一条、#4890 门禁看不见自己所在的目录)。共同形状:检查在跑,而且是绿的,但结构上接触不到它的检查对象。
.claude这条尤其脆:它是本地 agent 工具目录,比content//skills/更可能被重组。PR #4915 覆盖了什么、没覆盖什么
#4915 新增的
--self-test用真实 walker 从真实ROOTS走一遍临时树,断言.claude进得去、.claude/worktrees进不去。所以:.claude从ROOTS数组里删掉 → self-test 判红;.claude/目录被改名 →ROOTS数组没变,self-test 在临时树里照样绿,而真实扫描静默少一个 root。建议
启动时校验每条
ROOT都能解析,有一条不行就报错退出并点名,而不是继续跑。catch {}若确有存在理由(例如某个 root 在某些检出形态下合法缺席),把那个理由写成显式的白名单,而不是一个吞掉一切的空 catch。顺带看一眼仓内
scripts/下还有没有同形状的catch {}/ 静默跳过(几个check:*脚本值得一起扫),有就在报告里列出来,由 PM 决定是纳入还是另立。关联
.claude/workflows/docs-accuracy-audit.js,已派发中check:i18n应把 unknown-authoring-key lint 判为失败 —— 否则第十份 extract 配置还会照抄同一个错 #4804 / check:init-service-contract 只认getService,不认getServiceAsync—— #4772 就是从这个洞里溜过去的 #4835 /merge.os-regen.driver指向「上一个装过依赖的 worktree」的绝对路径 —— 该 worktree 一删,全容器的生成物合并驱动就坏了 #4868 /.claude/skills/**的 markdown 不被任何门禁扫描 —— check:nul-bytes 只看 JS/TS,check:doc-authoring 的 ROOTS 不含 .claude/ #4890 —— 同族先例