Skip to content

chore(tooling): doc-authoring 守卫纳入 docs/ 活语料,历史快照按路径豁免 (#4929) - #4978

Merged
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4929-doc-authoring-roots-docs
Aug 3, 2026
Merged

chore(tooling): doc-authoring 守卫纳入 docs/ 活语料,历史快照按路径豁免 (#4929)#4978
xuyushun441-sys merged 1 commit into
mainfrom
claude/issue-4929-doc-authoring-roots-docs

Conversation

@xuyushun441-sys

Copy link
Copy Markdown
Contributor

Fixes #4929

这条修的是什么

#4916 修的是第一个方向:声明在 ROOTS 里、却已经解析不到的 root(重命名/删除后被 catch {} 吞掉,扫描悄悄变窄却照样打印绿)。本单是它的对称方向:有没有一个真实存在、真的在教 metadata 编写、却从来就没被声明过的语料目录?

有,是 docs/。议题正文对全仓逐个围栏块统计过:

文件 ts 围栏块 defineX(
docs/notes/crm-development-standards.mdx 16 3
docs/adr/0015-external-datasource-federation.md 12 2
docs/adr/0010-metadata-protection-model.md 9 2
docs/adr/0057-system-data-lifecycle-and-retention.md 1 3
docs/adr/0017-object-has-many-view.md 1 1
docs/design/permission-model.md 1 3

AGENTS.md Prime Directive #13 要求每个 agent 在改动 ADR 治下的行为前先 grep ADR。一份 ADR 里的裸字面量因此会被下一个 agent 原样抄进 app 代码 —— 和 skills/ 里的一份坏样本没有任何区别,只是没人在看。

范围:docs 进 ROOTS,三个历史目录按路径豁免

const ROOTS = ['.claude', 'docs', 'skills', 'content'];

docs 而不是 docs/adr + docs/design + docs/notes 三条,理由和 #4913.claude 而非 .claude/skills 是同一条:以后新增的子目录一进来就在范围内,不会以完全相同的方式被漏第二次。手写的顶层指南(docs/protocol-upgrade-guide.mddocs/upgrading-to-11.md 等)也因此落在范围内 —— 它们是给读者的现行指令,本来就该在。

排除三个目录,走 #4915 引入的 SKIP_PATHS(按路径,不按目录名):

const SKIP_PATHS = new Set([
  '.claude/worktrees',
  'docs/audits',
  'docs/handoff',
  'docs/plans',
]);

注释里写明了为什么这是永久豁免、不是待办:它们是有日期的一次性过程记录 —— 某一天写下的、关于仓库当天状态的审计报告 / 交接note / 计划。里面没有任何一段是以「照这样写」提供给读者的,它们是当时为真的证据。把它们纳入等于让两个月前的一份 handoff 永久受今天的 lint 约束,而那种红只有两条出路:改记录(等于伪造史料),或者晚一场争论之后照样加豁免。判断线是「这份文档现在是否在教你怎么写 metadata」,不是「它是否在 docs/ 下」;一份从 plan 长成现行指南的文档,该做的是把它移出 docs/plans,而不是就地豁免。

时机:纳入时全仓零违规,所以现在纳入是零成本的。拖到有违规再纳入,就变成一次「要么改史料、要么加豁免」的争论。

双向证明(要求 1)

议题和 #4916 关注的缺陷家族,恰恰是「守卫在跑、是绿的、但结构上够不到它声称要查的东西」。所以只证明「加进去之后还是绿」等于什么都没证明 —— 那正是没加上时的输出。三段实测:

[1] 改前,fixture 在位 → 绿(守卫根本没打开 docs/)

=== [1] BEFORE change, fixtures present ===
✓ doc authoring guard: 3 files clean — no bare metadata literals.
exit=0

fixture 树里 docs/adr/0999-fixture-adr.mddocs/handoff/2026-06-fixture-handoff.md 放的是同一段裸字面量正文。

[2] 改后,同一棵树 → 红,且只点名 docs/adr

=== [2] AFTER change, docs/adr + docs/handoff fixtures present ===

✗ Bare metadata-literal authoring found in docs/skills (#2035). Use the defineX factory instead:

  docs/adr/0999-fixture-adr.md:4
    export const dashboard: Page = {

1 violation(s). Author via e.g. `definePage({ ... })` — ...
exit=1

docs/handoff 里内容完全相同的那一份没有被报出来。

[3] 改后,只留被排除目录的 fixture → 保持绿

=== [3] AFTER change, ONLY the excluded docs/handoff fixture present ===
✓ doc authoring guard: 3 files clean — no bare metadata literals.
exit=0

折进常驻 --self-test

上面三段是一次性的;真正长期有效的是把它做成每次都跑的断言。self-test 的临时树里新增了 7 份 fixture,三个豁免目录放的是docs/adr 完全相同的违规正文 —— 豁免哪天失效,这三份会直接变红说出来,而不是让两条断言都因为错误的原因通过。

两半必须一起断言,因为任一半单独成立都会被一个方向错误的范围满足:「docs/adr 判红」对一个吞下整个 docs/ 的范围同样成立,「docs/handoff 是绿的」对 #4929 之前那个从不打开 docs/ 的范围同样成立。

对 self-test 本身做了变异测试,确认它真的承重:

=== mutation A: drop 'docs' from ROOTS -> self-test must go red ===
  ✗ self-test "markdown files collected": expected 8, got 4
  ✗ self-test "docs/adr is walked": expected true, got false
  ✗ self-test "docs/notes is walked": expected true, got false
  ✗ self-test "top-level docs guides are walked": expected true, got false
  ✗ self-test "bare literal in docs/adr is a violation": expected true, got false
exit=1

=== mutation B: drop the three docs SKIP_PATHS -> self-test must go red ===
  ✗ self-test "markdown files collected": expected 8, got 11
  ✗ self-test "docs/audits is not walked": expected false, got true
  ✗ self-test "docs/handoff is not walked": expected false, got true
  ✗ self-test "docs/plans is not walked": expected false, got true
  ✗ self-test "total violations": expected 5, got 8
exit=1

main 上现存文件:零新增违规(要求 2)

扫描文件数 219 → 360(+141 = docs/ 177 份减去 audits 32 / plans 3 / handoff 1)。没有放宽任何规则,没有加任何 SKIP_FILES

> pnpm check:doc-authoring

✓ check-doc-authoring self-test: scope wiring (.claude and the live docs/ corpus in,
  .claude/worktrees and docs/{audits,handoff,plans} out), detection, and the dead-root
  hard error (red when a ROOT is renamed, green when restored) all hold.
✓ doc authoring guard: 360 files clean — no bare metadata literals.

ESLint 干净(npx eslint scripts/check-doc-authoring.mjs --no-inline-config,零输出)。

顺带记一笔

SKIP_PATHS 的条目没有死条目检测(ROOTS 有,#4916)。但这里的失效方向是安全的:某个豁免目录被重命名后,skip 静默失配,那批文件进入扫描 —— 变成一次响亮的红,不是一个安静的洞。已写进注释,没有为此扩大本 PR 的范围。

改动范围

  • scripts/check-doc-authoring.mjs(+88 / −9)
  • .changeset/doc-authoring-roots-docs.md(空 frontmatter,纯工具链,不发布任何包)

没有碰根 package.json、workflows,或 content/docs/releases/


Generated by Claude Code

#4916 修的是「声明了却解析不到的 root」。这是它的对称方向:一个真实存在、
真的在教 metadata 编写、却从来没进过 ROOTS 的目录。docs/ 就是那个目录 ——
docs/notes/crm-development-standards.mdx 有 16 个 ts 围栏块,ADR-0010/0015/
0017/0057 和 docs/design/permission-model.md 里都有 defineX(...)。Prime
Directive #13 让每个 agent 改 ADR 治下的行为前先 grep ADR,所以 ADR 里的裸
字面量会被下一个 agent 原样抄进 app 代码。

ROOTS 取 docs 而非三个子目录,理由同 #4913 取 .claude 而非 .claude/skills:
新增子目录一进来就在范围内。扫描文件数 219 → 360。

docs/audits、docs/handoff、docs/plans 用 #4915 的 SKIP_PATHS 按路径排除 ——
有日期的一次性过程记录,不是语料;纳入等于让史料永久受当前 lint 约束,而那种
红只有改记录(伪造)或事后照样加豁免两条出路。注释写明这是永久豁免而非待办。

纳入时全仓零违规,零成本。双向证明折进常驻 --self-test:docs/adr 的裸字面量
判红,同样内容放进三个豁免目录保持绿 —— 两半一起断言,因为任一半单独成立都会
被一个方向错误的范围满足。

Co-Authored-By: Claude Fable 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 6:17pm

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tooling size/m labels Aug 3, 2026
@xuyushun441-sys
xuyushun441-sys marked this pull request as ready for review August 3, 2026 18:19
@xuyushun441-sys
xuyushun441-sys added this pull request to the merge queue Aug 3, 2026
Merged via the queue into main with commit fe83042 Aug 3, 2026
19 checks passed
@xuyushun441-sys
xuyushun441-sys deleted the claude/issue-4929-doc-authoring-roots-docs branch August 3, 2026 18:38
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 的对称方向:docs/ 是真实存在、教 metadata 编写的语料,却从来不在 ROOTS 里

2 participants