给文档事实加指纹,让过期触发构建失败
文章提出为文档中可机器校验的内容记录输入指纹,在相关事实变化后通过构建门禁发现过期内容。还提供 Git 历史检查命令,帮助评估代码与文档更新脱节的程度。
AI 深度解读
文档最难发现的错误,往往是合并时完全正确,几个版本后却悄悄过期。代码继续发布,文档构建也一直是绿的,读者照着操作才发现参数、类型或必填条件已经变了。
作者提出的办法,是给能从代码、Schema 或 CLI 输出中提取的事实加上指纹:事实变化,CI 就报错。这个思路适合参数表和字段说明,但示例代码只完成了变化检测,并没有证明文档正文已经同步更新。理解这条边界,比直接复制脚本更有用。
文档检查应该跟着事实变化触发
常见文档审查发生在修改 Markdown 的时候。问题是,接口字段改了,负责改接口的人未必会碰文档,审查自然也不会发生。
作者建议先看仓库历史,花大约两分钟判断这种脱节是否存在:
先用 git log --oneline --since="12 months ago" -- docs | wc -l 统计过去 12 个月的文档提交,再用 git log --oneline --since="12 months ago" -- ':!docs' -- src/ schemas/ cli/ | wc -l 查看相关代码路径的提交规模,最后用 git log --format='%h %ad %s' --date=short -- docs | head -20 查看最近 20 条文档记录。
这些命令提供的是排查线索。第二条并不能严格统计「改了代码却没改文档」的提交,因为同一次提交可能同时涉及两边。数字悬殊可以提示风险,不能直接算作文档失效率。
接下来要划定检查范围。作者区分的是「能从权威来源重新计算的内容」与「由人作出的陈述」,而不是 AI 写作与人工写作。参数名称、字段类型可以提取;设计理由、性能承诺和兼容性判断,通常不能靠同一套方法验证。
原文要求把页面拆成四类内容块,还提到只有前两类允许生成,但提供的正文没有给出分类表。团队采用时需要自己补齐规则,不能假定这里已经有一套完整规范。
指纹记录输入事实,为什么更稳定
假设一个表格依赖 CLI 参数和用户邮箱字段,就在 Markdown 中声明依赖:
<!-- facts: cli.flags, schema.user.email | sha256=REPLACE_ME -->
| Flag | Type | Description |
|---|---|---|
| `--dry-run` | bool | Print the plan and exit without applying it. |
<!-- /facts -->
要看的是第一行:sha256 对应提取出来的事实值,不是表格正文。改标点、调整排版,不会使指纹失效;新增参数或者字段类型变化,才会触发检查。
作者给出的 docfacts.py 是面向 Python 3.11+ 的参考实现。cli_flags() 调用 python -m myapp --help,用正则提取参数名并排序;schema_field() 读取 schemas/user.json,返回字段的 type 和 required。EXTRACTORS 把事实标识映射到提取函数,包括 cli.flags、schema.user.email 和 schema.user.age。
指纹计算的核心只有几行:
def fingerprint(ids: list[str]) -> str:
payload = {i: EXTRACTORS[i]() for i in ids}
blob = json.dumps(payload, sort_keys=True, separators=(",", ":")).encode()
return hashlib.sha256(blob).hexdigest()[:16]
重点是最后两行:先固定 JSON 键顺序和分隔方式,再取 SHA-256 的前 16 个十六进制字符,避免相同事实因为序列化格式不同而反复报警。作者认为,这个长度足以满足文档门禁的碰撞需求,同时方便阅读。
扫描器遍历 docs 下的 *.md,重新计算每个标记块的指纹。普通运行发现不一致就以退出码 1 失败;带 --write 时写入新指纹。未知事实标识会抛出 KeyError,避免提取器被删除后检查悄悄失效。
不过,REPLACE_ME 不能直接交给示例脚本初始化。它的 BLOCK 正则只接受 [0-9a-f]{16},占位符不会被匹配,整个块可能被跳过。接入前必须补上初始化和非法标记检查。
AI 可以改表格,接受新事实仍要有人负责
作者设计的流程是:发现漂移后,先运行 python docfacts.py --write 更新指纹,再查看差异、重新提取并比较事实、修改正文,最后由具名负责人提交。提交信息应说明实际变化,例如 docs: user.email is now required。
CI 运行不带 --write 的 python docfacts.py,阻止剩余的不一致进入主分支。
AI 的职责被限制在机械改写:输入事实 JSON 和当前内容块,只输出替换正文,保留相同事实标识与 <!-- /facts --> 边界。不受 JSON 支持的新陈述应该被拒绝,而不是混进参数表里。
更新指纹只是接受了新输入,并不等于正文已经正确。
这是参考实现最需要补充解释的地方。--write 只替换指纹,不修改正文,也不输出旧事实与新事实的逐项差异。即便表格一字未改,只要指纹刷新,下一次 CI 也可能通过。作者要求人工比较事实,但脚本没有实现这一步,团队需要另行提供差异证据和审查流程。
作者还推荐用 MonkeyCode 改写派生块,并把免费服务器作为检查脚本的运行位置。原文明确披露这是产品推广合作;免费模型、免费服务器属于运营方的可用性声明,没有基准测试支撑,当前额度也需要另行确认。这套门禁思路并不依赖 MonkeyCode。
我的判断
我认可把检查触发点从 Markdown 扩展到事实来源。相比每次请模型通读文档找错,明确的依赖和确定性提取更容易定位问题,也方便解释一次失败究竟由哪个字段引起。
但它验证的是输入有没有变化,不能独立验证正文是否忠实表达输入。若参数表可以完全从 Schema 或 CLI 定义生成,直接生成表格通常更省事;指纹方案更适合需要保留人工描述、同时又依赖少量结构化事实的内容。
提取器本身也必须审查。示例只从帮助文本提取参数名,参数默认值或说明变化未必触发报警;如果使用标准 JSON Schema,必填字段通常记录在父对象的 required 数组里,示例直接读取字段节点的 required,可能取错。作者也明确说了:这些函数只是替身,没有在读者仓库里跑过。
事实粒度同样影响体验。一个块依赖 40 个事实,就可能频繁要求更新,最终让审查者习惯性放行。拆小内容块,比增加更多指纹更重要。
作者建议,少于约 20 页、没有机器可读 Schema 或 CLI 帮助输出、没有 PR CI 的项目先别采用,季度通读可能更便宜。这些阈值没有对照数据,属于经验之谈。性能、兼容性、安全保证,以及设计文档、RFC、法律文本,也不能因为门禁变绿就获得背书。
能拿来做什么
- 从最近出过错的一页入手,只标记其中的参数表或字段表,观察一个月能否捕获真实漂移,再决定是否扩展。
- 给 Node CLI 接入时,优先从参数定义或稳定的结构化输出提取事实,并明确覆盖名称、类型、默认值中的哪些内容。
- 在 PR 中同时展示事实的新旧值、正文差异和指纹变化,让负责人能够说明自己接受了什么,避免只提交一串新哈希。
- 让 AI 根据事实 JSON 修改限定内容块,再由人工核对并提交;把未识别标记、未知事实标识和提取失败都纳入明确报错。