文档治理
同一份文档只装「改动触发条件」相同的内容。
写下任何一句之前问:什么事发生了,会让这份文档需要改? 答案不同的内容就该分家。文档腐烂几乎都是这一条被破坏的结果——需求被进度淹没、过期状态伪装成需求、两份文档争当状态权威。下面所有规则都是从它推出来的。
分对类,后面全是机械动作;分错类,再多规矩也救不回来。 三种疑难的判法:
像是同时属于两类 (既是决策又是状态)→ 拆开写 。决策进 ADR 或需求,状态进执行计划。别写在一处再两边互链——那正是双权威的起点。
哪一类都套不上 → 大概率不该写。它要么能从仓库推导,要么只是这一轮对话的产物。答不上来就先别写 ,想清楚了再补不迟。
像客观事实、却会变 (架构描述是典型)→ 问「它变的时候谁先知道 」。代码先变 → 别写,读代码就有;人先决定 → 写,那是约束。
五类文档
#
角色
触发条件
装什么
1
需求
「我们要什么变了」
目标、能力边界(能 / 不能回答什么)、验收分层、成功标准。最慢
2
架构
「实现结构变了」
分层边界 、模块所有权、"不许怎么做"
3
执行计划
「进度推进了」
当前状态、待启动 / 待数据、明确不做、优先级与路线、阶段授权与停止条件 。状态的唯一权威 ;不记录具体实现步骤
4
handoff
「切片换了」
当前目标、下一步、必读材料、验证方式、硬约束原文 。建议 ≤ 150 行
5
ADR 索引
不衰减
adr/README.md 是决策目录;每条 ADR 只被取代、不被改写
固定角色、文件名可配。README 不在治理范围 ——它是仓库门面,维护节奏与治理无关。
三处最容易错:
需求与架构必须分开。 合写的话每次实现调整都要动需求文件,既抬高误改风险,也让 git 历史分不清"需求变了"还是"实现变了"。
架构写边界,不写结构图。 一句话若能被 ls 或读代码得到就删掉;只有人脑里才有的(为什么这层不许碰那层)才留下。
handoff 不得自行扩大执行计划授权的范围。 授权在执行计划里定;handoff 只承接当前被授权的那一片。
四类都不装的东西 :逐轮思考过程、操作日志、完整测试输出、可从 git 恢复的历史。它们看着像"记录",实际是把叙述当成了状态。尤其是提交哈希 :已完成项后面挂一个短哈希这种写法,勾选框本身已经说完了"做完了",哈希只是 git log 的副本。
提交哈希绝对不要写进 ADR。 ADR 只被取代、不被改写 ,而哈希会因 rebase / force-push 失效(保密清理、历史重写都会)。两条规则一撞,就成了一条永远修不掉 的坏引用——想修得为删个哈希新开一条取代 ADR。要指向实现,写文件路径或函数名 :那些改名了还搜得到,哈希改了就什么都不是。
handoff 与提示词不复制正文 ,只给精确的文件 + 章节 指针,让读者按需展开。复制一段进来就是双权威的开始——两边都会各自更新。
各类的最小内容骨架见 WRITING.md。
建立
先扫描(脚本在本 skill 目录下——Claude Code 加载时会给出 Base directory,Codex 下就是 ~/.codex/skills/canon/。用绝对路径调用 ,工作目录通常是被治理的项目。空目录与非 git 仓库都能正常返回):
python "<本 skill 目录>/scripts/scan_docs.py" .
按现状选入口:
空项目 → 直接按五类建骨架。不要一上来铺满模板 ,硬约束还没有就空着。守卫从集合相等 那一条起步(GUARDS.md 形态①),其余等文档长出来再加。不是 git 仓库先提醒 git init。
有文档、没守卫 → 对已跟踪 文档过三判据(见 WRITING.md),产出 删 / 归档 / 保留 表,保留的按衰减率归进五类。
有代码、文档缺失或严重过期(接手) → 素材依次取自 git log(做过什么)→ 依赖与目录结构(项目是什么)→ 测试(哪些约束已被强制)→ 受保护目录 / .gitignore / CI 门禁(隐式硬约束)。反推出来的是猜测,不是权威 :必须交用户逐条确认,并在文档里标出哪些是推断。
上面全都答不出「否掉了哪个方案」——那只存在于历史会话里。 要补 ADR 就跑:
python "<本 skill 目录>/scripts/mine_sessions.py" .
它按工作目录从 Codex / Claude Code 的会话记录里挖决策候选,并已避开两个致命陷阱
(role: user 里混着 harness 注入与 tool_result;会话重放导致约 3.5 倍重复)。
产出是候选清单不是 ADR :还要逐条对照现有 ADR 去重,再交用户确认。
五类文档的产出顺序从可观测的往回推 :架构(读代码)→ 执行计划(读 git log)→ handoff(当前分支 / 没做完的活)→ ADR(从既有边界和绕路痕迹反推)→ 需求最后写 ——它最主观、最依赖用户确认,先写只会把猜测固化成"权威"。
三个入口之后统一做:硬约束以原文 写进 handoff → 生成守卫测试(四种形态见 GUARDS.md)→ 变异检验 。
三条最容易踩的
完整五条见 GUARDS.md「反模式」,这三条踩中率最高:
断言「当前值」而不是不变量。 assert "status: in_progress" in text 一推进就红,而修法永远是改测试去迎合文档——那是家务,检测力为零。改成断言字段存在且取值在合法枚举内。
遍历目录不加防空转。 目录被改名或被 gitignore 时 glob 静默返回空,断言就 vacuously 通过了。扫之前先 assert root.is_dir()。
跳过变异检验。 没红过的测试不知道自己在测什么,是有害 的装饰品——它给的是虚假的安全感。注入故障 → 确认红 → 按字节 还原 → 确认绿。一种变异不够,"整节删掉"和"标题改名"要分别试。
维护(工作推进中)
本 skill 无法自动触发 。真正的"自动"只有两条路:写进偏好 (靠模型记得)或写成 hook (settings.json,由 harness 强制)。本节定义更新什么、怎么更新 ,触发交给那两者。
路由——每条信息只写一个地方。 进度变了 → 只写执行计划;新决策 → 新建 ADR + 更新索引(不改旧 ADR );切片换了 → handoff;硬约束变了 → 改 handoff 正文并同步守卫断言 ;需求与架构只在"改主意了""实现结构变了"时才动。
动了文档结构,守卫要跟着动。 拆分、改名、搬迁之后,断言小节存在的守卫也要重新分配(小节跑到哪份文档,断言就跟到哪份)。只改文档不改守卫 = 守卫从此测的是别的东西。
写法——更新 = 改写那一段,不是追加。 追加式更新是文档膨胀和自相矛盾的共同 根源:更正加在一处、被推翻的结论留在另一处,读者两句都会读到。所以旧状态要改掉 ,被推翻的结论要删掉或划掉 ,新增能力并进 已有条目。
删除也要有触发条件 ,否则永远不会发生:被取代 · 已经能从仓库推导 · 切片结束(细节移出 handoff)· 成了孤儿(没有任何文档链接到它)。归档目录默认不读 ——它存在只是为了不丢,不是为了被参考。
"实质变化"才算更新。 把已有认识换个说法、补几个形容词、调整段落顺序——都不构成更新理由 。这是文档膨胀最常见的入口,因为它总是显得像在干活。
尺寸按阅读方式定 ,不是一刀切:handoff 每次被线性读完,必须小 (建议 ≤ 150 行);需求与架构是带着问题跳读的,可以长,但小节标题必须清晰。
别拿文档当活干。 当前切片的目标、边界、验收一旦明确,就该去写代码 / 测试 / 验证,而不是回头再润色文档。除非发现阻塞该动作的事实错误 ,不要先重写需求或架构。
审计(周期性)
逐项给结论,不只是"看过了":
可核对声明 — python "<本 skill 目录>/scripts/audit_doc_claims.py" .(路径 / 相对链接 / 提交哈希)
⚠ 脚本只扫 git 跟踪的 文件。刚新建或拆分出来的文档还没被跟踪,会被静默跳过 ——
先 git add 再跑,否则"0 死链"这个结论根本没覆盖新文件。(实跑踩过。)
归档目录不扫 (按「归档默认不读」);被 gitignore 的运行时产物路径、能在某个顶层
包目录下解析到的包内相对简写,都计入"已跳过"而不报成失效——判据故意松,
噪声大的审计比没有审计更糟 ,它会训练人忽略这份报告。
空转守卫 — 守卫扫的目录还在吗?被 gitignore 了吗?
家务型断言 — 有没有断言当前值而非不变量的?
双权威 — 两份文档都自称持有状态权威?同一份文档里同时断言 X 和 ¬X ?
仓库外的活拷贝 — 仓库里有某份文件,而实际生效的是装在别处的另一份 吗?
偏好 / 配置 / hook / 模板这类"要装到某个位置才起作用"的东西最容易中招:改的是活的那份,
仓库那份悄悄变旧。从 README 的安装步骤反推 有哪些外部拷贝,逐对比内容(忽略行尾符)。
⚠ 这条只能审计,不能写守卫 :干净 clone 上外部那份不存在,assert 会 vacuously 通过,
结论因机器而异。凡是"跨出仓库"的一致性,守卫都够不着。
衰减 — 长期没人改也没人读的该删吗?有没有混了两种衰减率(尤其需求与架构合写)?
修守卫优先于修文档 ——守卫坏了,文档错了也没人知道。
收尾
汇报按这个结构,别只说"做完了":
发现 — 文档现状与问题;没有就明说没有
改动 — 删 / 归档 / 保留了什么,新建了哪些守卫
验证 — 每条守卫的变异检验结果(红 → 还原 → 绿)、测试是否全绿、链接是否还通
待你拍板 — 需要人判断的事项。给 2–3 个具体选项和各自代价,不要开放式提问
仍不设防 — 这次没能覆盖的(叙述失真、无法核对的路径等)
自查:
[ ] 每条新增信息都能说出它属于哪个触发条件
[ ] 没有同一件事写在两份文档里
[ ] 被推翻的结论已删除,不与更正并存
[ ] 每条新守卫都做过变异检验
[ ] 精简或重写之后,逐条核过覆盖点没丢
最后一条不是客套:精简本身就会丢东西 。删比加更需要复核。
什么时候停下来交给用户
一条信息该不该存在,取决于业务判断 而不是治理规则
反推出来的内容用户还没确认(接手场景的常态)
要移动保密或私有 文件的位置——只报告,不擅自动手
同一类问题绕了三轮还没定 → 摆选项,别自己拍
边界
测试只能防结构漂移 和可核对事实漂移 ,防不了叙述失真 ——那只能靠人读。所以没人读的文档该删:它连唯一的防线都没有。
不引入预算控制、冻结流程这类重框架。治理靠的是少数几条能自动执行的不变量。
1 --- 2 name: canon 3 description: Establish and maintain a five-document set (requirements, architecture, execution plan, handoff, ADR index) split by decay rate, plus guard tests that enforce it. Use whenever the user asks to write, update, fix, tidy, or check project documentation — 更新文档, 先写文档, 写文档, 改文档, 补文档, 文档先行, 整理文档, 文档治理, 建立文档结构, 记一条决策, "update the docs", "docs first", "write the docs" — and when starting a new project's docs, taking over a codebase whose docs are missing or stale, recording a decision as an ADR, or auditing for doc drift. Not for authoring one-off prose documents, generating API reference, or code review. 4 --- 5 6 # 文档治理 7 8 **同一份文档只装「改动触发条件」相同的内容。** 9 10 写下任何一句之前问:*什么事发生了,会让这份文档需要改?* 答案不同的内容就该分家。文档腐烂几乎都是这一条被破坏的结果——需求被进度淹没、过期状态伪装成需求、两份文档争当状态权威。下面所有规则都是从它推出来的。 11 12 **分对类,后面全是机械动作;分错类,再多规矩也救不回来。** 三种疑难的判法: 13 14 - **像是同时属于两类**(既是决策又是状态)→ **拆开写**。决策进 ADR 或需求,状态进执行计划。别写在一处再两边互链——那正是双权威的起点。 15 - **哪一类都套不上** → 大概率不该写。它要么能从仓库推导,要么只是这一轮对话的产物。**答不上来就先别写**,想清楚了再补不迟。 16 - **像客观事实、却会变**(架构描述是典型)→ 问「它变的时候**谁先知道**」。代码先变 → 别写,读代码就有;人先决定 → 写,那是约束。 17 18 ## 五类文档 19 20 | # | 角色 | 触发条件 | 装什么 | 21 |---|---|---|---| 22 | 1 | **需求** | 「我们要什么变了」 | 目标、能力边界(能 / 不能回答什么)、验收分层、成功标准。**最慢** | 23 | 2 | **架构** | 「实现结构变了」 | 分层**边界**、模块所有权、"不许怎么做" | 24 | 3 | **执行计划** | 「进度推进了」 | 当前状态、待启动 / 待数据、明确不做、优先级与路线、**阶段授权与停止条件**。**状态的唯一权威**;不记录具体实现步骤 | 25 | 4 | **handoff** | 「切片换了」 | 当前目标、下一步、必读材料、验证方式、**硬约束原文**。建议 **≤ 150 行** | 26 | 5 | **ADR 索引** | **不衰减** | `adr/README.md` 是决策目录;每条 ADR 只被取代、不被改写 | 27 28 固定角色、文件名可配。**README 不在治理范围**——它是仓库门面,维护节奏与治理无关。 29 30 三处最容易错: 31 32 - **需求与架构必须分开。** 合写的话每次实现调整都要动需求文件,既抬高误改风险,也让 git 历史分不清"需求变了"还是"实现变了"。 33 - **架构写边界,不写结构图。** 一句话若能被 `ls` 或读代码得到就删掉;只有人脑里才有的(为什么这层不许碰那层)才留下。 34 - **handoff 不得自行扩大执行计划授权的范围。** 授权在执行计划里定;handoff 只承接当前被授权的那一片。 35 36 **四类都不装的东西**:逐轮思考过程、操作日志、完整测试输出、可从 git 恢复的历史。它们看着像"记录",实际是把叙述当成了状态。**尤其是提交哈希**:已完成项后面挂一个短哈希这种写法,勾选框本身已经说完了"做完了",哈希只是 `git log` 的副本。 37 38 **提交哈希绝对不要写进 ADR。** ADR **只被取代、不被改写**,而哈希会因 rebase / force-push 失效(保密清理、历史重写都会)。两条规则一撞,就成了一条**永远修不掉**的坏引用——想修得为删个哈希新开一条取代 ADR。要指向实现,写**文件路径或函数名**:那些改名了还搜得到,哈希改了就什么都不是。 39 40 **handoff 与提示词不复制正文**,只给**精确的文件 + 章节**指针,让读者按需展开。复制一段进来就是双权威的开始——两边都会各自更新。 41 42 各类的最小内容骨架见 [WRITING.md](WRITING.md)。 43 44 ## 建立 45 46 先扫描(脚本在本 skill 目录下——Claude Code 加载时会给出 Base directory,Codex 下就是 `~/.codex/skills/canon/`。**用绝对路径调用**,工作目录通常是被治理的项目。空目录与非 git 仓库都能正常返回): 47 48 ```bash 49 python "<本 skill 目录>/scripts/scan_docs.py" . 50 ``` 51 52 按现状选入口: 53 54 - **空项目** → 直接按五类建骨架。**不要一上来铺满模板**,硬约束还没有就空着。守卫从**集合相等**那一条起步([GUARDS.md](GUARDS.md) 形态①),其余等文档长出来再加。不是 git 仓库先提醒 `git init`。 55 - **有文档、没守卫** → 对**已跟踪**文档过三判据(见 [WRITING.md](WRITING.md)),产出 删 / 归档 / 保留 表,保留的按衰减率归进五类。 56 - **有代码、文档缺失或严重过期(接手)** → 素材依次取自 `git log`(做过什么)→ 依赖与目录结构(项目是什么)→ 测试(哪些约束已被强制)→ 受保护目录 / `.gitignore` / CI 门禁(隐式硬约束)。**反推出来的是猜测,不是权威**:必须交用户逐条确认,并在文档里标出哪些是推断。 57 58 **上面全都答不出「否掉了哪个方案」——那只存在于历史会话里。** 要补 ADR 就跑: 59 60 ```bash 61 python "<本 skill 目录>/scripts/mine_sessions.py" . 62 ``` 63 64 它按工作目录从 Codex / Claude Code 的会话记录里挖决策候选,并已避开两个致命陷阱 65 (`role: user` 里混着 harness 注入与 tool_result;会话重放导致约 3.5 倍重复)。 66 产出是**候选清单不是 ADR**:还要逐条对照现有 ADR 去重,再交用户确认。 67 68 五类文档的产出顺序**从可观测的往回推**:架构(读代码)→ 执行计划(读 `git log`)→ handoff(当前分支 / 没做完的活)→ ADR(从既有边界和绕路痕迹反推)→ **需求最后写**——它最主观、最依赖用户确认,先写只会把猜测固化成"权威"。 69 70 三个入口之后统一做:硬约束以**原文**写进 handoff → 生成守卫测试(四种形态见 [GUARDS.md](GUARDS.md))→ **变异检验**。 71 72 ## 三条最容易踩的 73 74 完整五条见 [GUARDS.md](GUARDS.md)「反模式」,这三条踩中率最高: 75 76 - **断言「当前值」而不是不变量。** `assert "status: in_progress" in text` 一推进就红,而修法永远是改测试去迎合文档——那是家务,检测力为零。改成断言字段存在且取值在合法枚举内。 77 - **遍历目录不加防空转。** 目录被改名或被 gitignore 时 glob 静默返回空,断言就 vacuously 通过了。扫之前先 `assert root.is_dir()`。 78 - **跳过变异检验。** 没红过的测试不知道自己在测什么,是**有害**的装饰品——它给的是虚假的安全感。注入故障 → 确认红 → 按**字节**还原 → 确认绿。一种变异不够,"整节删掉"和"标题改名"要分别试。 79 80 ## 维护(工作推进中) 81 82 > 本 skill **无法自动触发**。真正的"自动"只有两条路:写进**偏好**(靠模型记得)或写成 **hook**(settings.json,由 harness 强制)。本节定义**更新什么、怎么更新**,触发交给那两者。 83 84 **路由——每条信息只写一个地方。** 进度变了 → 只写执行计划;新决策 → 新建 ADR + 更新索引(**不改旧 ADR**);切片换了 → handoff;硬约束变了 → 改 handoff 正文**并同步守卫断言**;需求与架构只在"改主意了""实现结构变了"时才动。 85 86 **动了文档结构,守卫要跟着动。** 拆分、改名、搬迁之后,断言小节存在的守卫也要重新分配(小节跑到哪份文档,断言就跟到哪份)。只改文档不改守卫 = 守卫从此测的是别的东西。 87 88 **写法——更新 = 改写那一段,不是追加。** 追加式更新是文档膨胀和自相矛盾的**共同**根源:更正加在一处、被推翻的结论留在另一处,读者两句都会读到。所以旧状态要**改掉**,被推翻的结论要**删掉或划掉**,新增能力**并进**已有条目。 89 90 **删除也要有触发条件**,否则永远不会发生:被取代 · 已经能从仓库推导 · 切片结束(细节移出 handoff)· 成了孤儿(没有任何文档链接到它)。归档目录默认**不读**——它存在只是为了不丢,不是为了被参考。 91 92 **"实质变化"才算更新。** 把已有认识换个说法、补几个形容词、调整段落顺序——都**不构成更新理由**。这是文档膨胀最常见的入口,因为它总是显得像在干活。 93 94 **尺寸按阅读方式定**,不是一刀切:handoff 每次被线性读完,**必须小**(建议 ≤ 150 行);需求与架构是带着问题跳读的,可以长,但小节标题必须清晰。 95 96 **别拿文档当活干。** 当前切片的目标、边界、验收一旦明确,就该去写代码 / 测试 / 验证,而不是回头再润色文档。除非发现**阻塞该动作的事实错误**,不要先重写需求或架构。 97 98 ## 审计(周期性) 99 100 逐项给结论,不只是"看过了": 101 102 - [ ] **可核对声明** — `python "<本 skill 目录>/scripts/audit_doc_claims.py" .`(路径 / 相对链接 / 提交哈希) 103 ⚠ 脚本只扫 **git 跟踪的**文件。刚新建或拆分出来的文档还没被跟踪,会被**静默跳过**—— 104 先 `git add` 再跑,否则"0 死链"这个结论根本没覆盖新文件。(实跑踩过。) 105 归档目录**不扫**(按「归档默认不读」);被 gitignore 的运行时产物路径、能在某个顶层 106 包目录下解析到的包内相对简写,都计入"已跳过"而不报成失效——判据故意松, 107 **噪声大的审计比没有审计更糟**,它会训练人忽略这份报告。 108 - [ ] **空转守卫** — 守卫扫的目录还在吗?被 gitignore 了吗? 109 - [ ] **家务型断言** — 有没有断言当前值而非不变量的? 110 - [ ] **双权威** — 两份文档都自称持有状态权威?**同一份文档里同时断言 X 和 ¬X**? 111 - [ ] **仓库外的活拷贝** — 仓库里有某份文件,而**实际生效的是装在别处的另一份**吗? 112 偏好 / 配置 / hook / 模板这类"要装到某个位置才起作用"的东西最容易中招:改的是活的那份, 113 仓库那份悄悄变旧。**从 README 的安装步骤反推**有哪些外部拷贝,逐对比内容(忽略行尾符)。 114 ⚠ **这条只能审计,不能写守卫**:干净 clone 上外部那份不存在,`assert` 会 vacuously 通过, 115 结论因机器而异。凡是"跨出仓库"的一致性,守卫都够不着。 116 - [ ] **衰减** — 长期没人改也没人读的该删吗?有没有混了两种衰减率(尤其需求与架构合写)? 117 118 **修守卫优先于修文档**——守卫坏了,文档错了也没人知道。 119 120 ## 收尾 121 122 汇报按这个结构,别只说"做完了": 123 124 - **发现** — 文档现状与问题;没有就明说没有 125 - **改动** — 删 / 归档 / 保留了什么,新建了哪些守卫 126 - **验证** — 每条守卫的变异检验结果(红 → 还原 → 绿)、测试是否全绿、链接是否还通 127 - **待你拍板** — 需要人判断的事项。**给 2–3 个具体选项和各自代价,不要开放式提问** 128 - **仍不设防** — 这次没能覆盖的(叙述失真、无法核对的路径等) 129 130 自查: 131 132 ``` 133 [ ] 每条新增信息都能说出它属于哪个触发条件 134 [ ] 没有同一件事写在两份文档里 135 [ ] 被推翻的结论已删除,不与更正并存 136 [ ] 每条新守卫都做过变异检验 137 [ ] 精简或重写之后,逐条核过覆盖点没丢 138 ``` 139 140 最后一条不是客套:**精简本身就会丢东西**。删比加更需要复核。 141 142 ## 什么时候停下来交给用户 143 144 - 一条信息该不该存在,取决于**业务判断**而不是治理规则 145 - 反推出来的内容用户还没确认(接手场景的常态) 146 - 要移动**保密或私有**文件的位置——只报告,不擅自动手 147 - 同一类问题绕了三轮还没定 → 摆选项,别自己拍 148 149 ## 边界 150 151 - 测试只能防**结构漂移**和**可核对事实漂移**,**防不了叙述失真**——那只能靠人读。所以没人读的文档该删:它连唯一的防线都没有。 152 - 不引入预算控制、冻结流程这类重框架。治理靠的是少数几条能自动执行的不变量。