ARTICLE · 1063233
文档检查 · 检查放在文档里,等于没有
你仓库的文档里,可能就有一句早就不成立的话——没人发现,agent 也没说。
这篇是把这类话捞出来,两样东西,都能直接复制走:一份补漏的检查,补上现成工具看不见的那类命令引用;三条审计约束,管机器查不出的约定过期。这两样我都在自己的仓库里跑过。
先说结论:检查写在文档里等于没有,得挂在门口。 下面的事都围着这一句。
关注「老刘的Agent手记」——agent 工具一个个拆开、自己跑一遍再给判断;想拿文件原件的,后台回复「文档检查」。
📦 这份资料包里有什么
👉 滑动
PART 01
补上那段检查
命令引用
PART 02
挂上去
别只放在文档里
PART 03
三条判据卡
机器查不出的
PART 04
拿去用
三份原样复制
PART ///
十秒自检
现在就能做
01
PART
先补上现成工具看不见的那一类
PATCH · 命令引用
现成的工具里有一个 driftlint。文档里写着 npm run deploy——deploy 不在 scripts 里,它就报错。
可 npm test 这种写法,它一个字都不说:命令名直接跟在 npm 后面,中间没有 run。这恰恰是我们写文档时最顺手的写法。
它只认「npm run + 名字」这一种格式,别的命令写法一概不看——pnpm build、yarn dev 都一样。
补它,逻辑就三句话:
一、把文档里反引号包着的命令全捞出来
二、每条命令的最后一个词,就是脚本名
三、拿去 package.json 的 scripts 里找,没有就报
脚本我写在最后一节了,27 行,不用读——存成 scripts/check-commands.sh 就能用。
我那个小项目的文档里留了一行早就不存在的 npm test,它只报这一行;有问题时退出码是 1,所以能挂在钩子上拦住提交——下一节说怎么挂。
一句说明:它只认 package.json,也只读文档、不写任何文件。你的项目要是用 justfile、Makefile,把取脚本名的那两行换掉,三句话的逻辑不变。npm install 这类内置命令它跳过了;项目里压根没有 package.json,它会一声不响地退出——那不等于“检查过了”。
02
PART
挂上去
HOOK · 不挂就等于没有
两段配置——挂 pre-commit,提交时拦;挂 CI,推送时查——都在最后一节,选一个或都上。
⚠️ .git/hooks/ 这个目录不进版本库,换台机器、重新克隆就没了。团队要一起用,把它放在仓库里的 .githooks/ 下,再执行一次 git config core.hooksPath .githooks——这样它跟着代码走。
这是挂了之后的效果。文档里留一行 npm test——就是第一节说的、现成工具漏掉的那种写法。两道检查挨着跑,第一道没看出问题,第二道把它拦下来,那条提交没进历史:
$ npx -y @alifurkangokce/driftlint
driftlint: 0 errors, 0 warnings across 1 context file
your agent context files agree with your code. rare.
$ bash scripts/check-commands.sh
✗ 文档里写着 npm test,scripts 里没有 test
上面这几处,改完再提交。
把那行改对,再提交就过了。这一条是关键:拦住的不是提醒,是提交本身。
也正因为这样,两道都得挂:driftlint 那一道只管 npm run 那种格式,补的这道管它看不见的写法。只挂一道,另一道漏的错照样进历史。
挂哪儿,是我自己跑出来的结论:
查“文档点名的东西还在不在”,放提交时 —— 快,不拖你
查“内容有没有过期”(比文件时间),放推送时或者 CI 里 —— 它要翻 git 历史,慢
那份判据卡的审计,每次推送带一次,再每周过一遍
最后一句实话:driftlint 是 8 月 11 号发的第一版,一个多月更了 11 个版本,最近一周下载 66 次。它哪天不在了、改得你不认识了,或者你不想多装一个包,把 npx 那行删掉,剩下的照样跑。
03
PART
机器查不出的那类:三条判据卡
RUBRIC · 审计约束
路径和命令,脚本对得了。但有一类机器永远看不出来:约定过期了。
文档写着“我们的组件还在用类组件”,其实早就全换成函数组件了;写着“改表结构必须走评审”,那个流程半年前取消了。
这种没有唯一答案,只能让另一个 agent 读。但它读的时候你得给它三条约束——不给,它会“顺手把文档更新一下”,然后跟你说更完了。
一、只出报告,不改文件(改不改,我点头之后再说)二、每条发现必须能说成“某处写着 X,但 X 不成立”;写不成的,别放进报告三、分不清是文档错了还是代码歪了,两个都列出来,交给人
第二句是整张卡的重心。像“组件应该用函数写”这种话,说不成那个格式——它不是“某处写着 X 但 X 不成立”,它是个没落地的观点。这一句卡住,报告里剩下的就都是能查证的。
能直接粘给它的一段:
读一遍 AGENTS.md 和 docs/ 下的文档,对照当前代码,只回答一个问题:
这里面哪一条,现在的代码已经不这么干了?
三条规矩:
一、只出报告,别改任何文件——改不改,我点头之后再说
二、每条发现必须能说成“某处写着 X,但 X 不成立”;说不成的,别写进报告
三、分不清是文档错了还是代码歪了,两个都列出来交给我
每条给我:哪份文档、哪一行、代码在第几行。
还有两件事,比指令本身更要紧:
别让它一边干活一边顺手更文档。 它对自己刚干的活,判断力最差。
agent 自己说“我更新过了”,不算数。 要独立的一步,专门对账。
04
PART
拿去用
APPENDIX · 三份原样复制
三份,原样复制。不用读。 前两份今天就能做完;第三份,等你有了 CI 那天再回来拿。
一、补漏的检查 → scripts/check-commands.sh
#!/usr/bin/env bash
# check-commands.sh —— 文档里点名的命令,还在吗?
set -uo pipefail
DOCS="AGENTS.md CLAUDE.md README.md docs"
[ -f package.json ] || exit 0
have=$(sed -n '/"scripts"/,/}/p' package.json \
| grep -oE '"[^"]+":' | tr -d '":' | grep -vx scripts | sort -u)
RE='`(npm|pnpm|yarn|bun)( run)? [a-zA-Z0-9:_-]+`'
cmds=$(grep -ohrE --include='*.md' "$RE" $DOCS 2>/dev/null \
| tr -d '`' | sort -u)
fail=0
while IFS= read -r c; do
name=${c##* }
case "$name" in
install|ci|add|remove|audit|publish|update|init|ls|help) continue ;;
esac
if ! grep -qx "$name" <<< "$have"; then
echo "✗ 文档里写着 $c,scripts 里没有 $name"
fail=1
fi
done <<< "$cmds"
[ $fail -eq 0 ] && echo "✓ 文档点名的命令都还在"
[ $fail -eq 0 ] || { echo; echo "上面这几处,改完再提交。"; }
exit $fail
二、挂在提交上 → .githooks/pre-commit(放仓库里,chmod +x,再跑一次 git config core.hooksPath .githooks)
#!/bin/sh
npx -y @alifurkangokce/driftlint || exit 1
bash scripts/check-commands.sh || exit 1
三、挂 CI → .github/workflows/docs.yml
# .github/workflows/docs.yml
name: docs
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0 # 时间漂移那类要完整历史,浅克隆会算错
- run: npx -y @alifurkangokce/driftlint
- run: bash scripts/check-commands.sh
还有一层在 PART 03 · 三条判据卡:三条约束,加那段可粘给 agent 的指令——留到你要清文档的那天用。
///
LAST
十秒自检
LAST · 现在就能做
翻一遍你的文档,问一句:
这里面哪句话是错的,也不会有人发现?那一句,就是你要接的那根线。