关于 verify-cli-run
一种无需额外工具的 CLI 验证技能,适合需要确认命令实际产生了什么变化的开发者。它预先声明预期结果,再通过文件快照和差异判断,并将证据不足与失败区分开来。
主要功能
- 01运行前写明预期结果
- 02比较执行前后的文件状态
- 03区分证据与命令自述
- 04给出是、否、未知或无责结论
- 05记录未观测到的范围
适用场景
- 01验证构建是否改写目标文件
- 02检查迁移是否只创建预期文件
- 03核实 CLI 编辑的代码变化
- 04判断远程操作是否需要向服务端确认
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ reticlehq/ reticle --skill verify-cli-run安装后在 Claude Code 中输入/verify-cli-run确认已加载。
详细介绍
证明命令确实做了它声称的事
命令以 0 退出,并打印了 ✓ done。你知道该工具进入了自己的成功分支,却不知道实际发生了什么。
这就是验证 CLI 的全部问题:人人都会检查的两件事,都是工具在描述自己。 退出代码由执行操作的同一段代码决定。输出也是它写的。工具对自己所做之事判断有误时,这两方面都会一致地出错。正因如此,它们一致并不能证明任何事。
这项技能无需安装任何工具。它只需要四条规则和一些 git。
决定一切的规则
证明后果的证据,不能来自执行操作的对象。
使用事实前,先给每项事实评级:
| 你掌握的信息 | 等级 | 能证明命令成功了吗? |
|---|---|---|
| 磁盘上的文件,运行前后对比 | 后果 | 能。 文件系统决定写入是否落地,而不是工具 |
| 事后向 API 发起请求并得到响应 | 后果 | 能。 回答你的是另一方,而不是工具 |
| 退出代码 | 存在性 | 不能。它由工具决定 |
| stdout / stderr | 上下文 | 不能。它由工具写入 |
工具自己的 --verbose 报告 | 上下文 | 不能 |
底部三行中的所有信息都是真实信息,但没有一项是证据。用它们解释判定,绝不要用它们得出判定。
1. 运行前先说清后果
这是整个方法。在看到输出后写下的预期,总能被说成符合实际发生的事;提前写下的预期,只可能达成或未达成。
运行命令前,先在记录中用一行写下:
EXPECT: dist/index.js is rewritten, and no file outside dist/ changes.
EXPECT: src/utils.ts gains a function called parseConfig.
EXPECT: the migration creates 3 files under migrations/ and nothing else.
如果无法提前说清某个后果,就无法验证它。如果说不出,就如实说明并停止。这是诚实的 no-fault,不是通过。
2. 先做快照
在 git 仓库中。 这是理想情况,只需一行:
git status --porcelain > /tmp/before.txt
git status --porcelain 是近乎完美的证据渠道:结构化、成本低,而且独立于工具。Git 的索引记录的是实际写入磁盘的内容,而不是工具打算做什么。
不在 git 仓库中,或者路径位于仓库之外:
find <declared-root> -type f -newermt '1970-01-01' -exec shasum -a 256 {} + | sort > /tmp/before.txt
声明监控根目录时遵循三条规则:
- 除非它们就是目标,否则排除
node_modules、.git/objects和大型构建缓存。 对整个node_modules计算哈希会涉及数万个文件。 - 明确声明根目录。 工具可以在任何位置写入;你监控的是自己选定的列表。
- 列表之外的任何位置都是盲区,第 6 步要报告这一点,而不是假装它没有发生。
3. 检查目标是否已经实现
CLI 工作中最常见的假阳性:dist/index.js 已经存在,于是“构建生成了 dist/index.js”就被判定为通过,即使这次重建什么也没做。
运行前,检查预期后果是否已经成立。如果已经成立,命令就无法证明它。要么选择命令会改变的后果,要么先删除该产物,并说明你这样做了。
4. 运行命令,并分别捕获信息
<the command> > /tmp/out.txt 2> /tmp/err.txt; echo "exit=$?"
保留退出代码,但不要把它当作证据。用它解释判定,并留意它与文件系统状态不一致的情况。
需要了解三点:
- 退出代码会回绕:
sh -c 'exit 256'会以 0 退出。很大的代码不一定代表你以为的意思。 130表示SIGINT,137通常表示进程被强制终止。这些只说明运行是如何_结束_的,不说明运行是否成功。- 有些工具什么也没做却以 0 退出。AI 编码 CLI 经常如此。
5. 检查差异,并以差异作为证据
git status --porcelain > /tmp/after.txt; diff /tmp/before.txt /tmp/after.txt
git diff -- <the paths you expected to change>
现在,只根据这份差异回答你在第 1 步的预期。
每次都值得做两项检查,因为不费吹灰之力:
- 没有其他改动。 构建时还改写了锁文件,或格式化工具重新格式化了没人要求改动的文件,这些都应当作为发现报告。
- 检查内容,而不只是文件是否存在。 一个存在的文件,比不上一个包含预期内容的文件。不过请注意其中的不对称性:文件是否出现由文件系统决定;文件中的内容则完全由工具写入。存在与否是比内容更有力的证据。
6. 给出四种判定之一
不是两种。多出的两个选项才是关键:它们能防止把无知一概当作好消息。
| 判定 | 适用情况 |
|---|---|
| yes | 你在第 1 步所说的结果能在差异中看到,而且此前尚未成立 |
| no | 差异与之矛盾,或工具声称的结果未在文件系统中体现 |
| unknown | 你看不到所需的信息。没有监视任何内容、监视窗口不对,或效果发生在你没有查看的地方 |
| no-fault | 一切都在监视之中,没有异常,也没有任何声明需要验证 |
unknown 不是失败,绝不能将其报告为失败。 “我看不到”和“它坏了”会让人朝相反的方向行动:前者表示再检查一次,后者表示去修复某些东西。
并且要说清楚什么证据支持了 yes。"dist/index.js 在磁盘上发生了变化"是一个判定。“构建表示成功”不是;如果你只有这一点,诚实的答案就是 unknown。
7. 说明你没有看到什么
一个无法说明自己漏掉了什么的判定,与一个什么都看到了的判定无法区分。每次都要指出缺口。一行就够:
Did not observe: the network (cannot see whether it called out); writes outside ./src and ./dist;
files created and deleted during the run; anything the tool's child processes did.
有四种缺口始终存在,默认情况下值得一一指出:
- 网络。 你无法看到命令是否拨号连接了某个端点。如果声称的结果涉及远程操作(推送、部署、PR),文件系统无法回答。
- 已声明根目录之外的写入。
~/.config、全局缓存、/tmp。大多数 AI 编码 CLI 每次运行都会写入自己的状态目录。 - 临时文件。 在运行期间创建后又删除。前后快照只能看到净效果。
- 子进程以及任何存续时间超过命令的操作:分叉进程、延迟刷新、后台上传。
两项无需成本、却能发现很多问题的操作
运行两次。 构建、格式化、代码生成步骤或迁移,在第二次运行时应产生空差异。每次重复运行都会持续改动内容的工具存在缺陷;检查这一点不需要预期值。
<command> && git status --porcelain > /tmp/a.txt && <command> && git status --porcelain > /tmp/b.txt && diff /tmp/a.txt /tmp/b.txt
向远端核实。 对于网络工具,文件系统帮不上忙,但服务端可以。gh pr view、git ls-remote,或对已部署 URL 执行 curl。这是让另一方回答你,而不是让工具自述,因此属于结果级证据,也是远程操作声明唯一可用的证明。
不要用工具自己的本地记录来替代。git push 会写入 .git/refs/remotes/origin/main,而那个 sha 是git 对其认为远端所说内容的转录。它在磁盘上,但仍然是工具在描述自己。去问远端。
示例:AI 编码 CLI
这是证据最充分的情形,因为这些工具的产品本身就是文件编辑,而它们的执行记录是最不可靠的内容。
EXPECT: src/config.ts gains a function `parseConfig`. Nothing outside src/ changes.
$ git status --porcelain > /tmp/before.txt
$ grep -c "parseConfig" src/config.ts # already-true check → 0
$ claude -p "add a parseConfig function to src/config.ts" ; echo "exit=$?"
$ git status --porcelain > /tmp/after.txt ; diff /tmp/before.txt /tmp/after.txt
M src/config.ts
$ git diff src/config.ts | grep "^+.*parseConfig"
+export function parseConfig(raw: string): Config {
VERDICT: yes, bought by the working-tree diff, which is independent of the agent's report.
It was not already true (grep returned 0 before).
Did not see: writes under ~/.claude (the tool records every session there); the network;
whether a permission prompt declined an edit it also claimed.
请注意,判定依据不是退出代码,也不是 Agent 声称已添加该函数的那段话。
何时应升级到此技能之外的方法
这种方法有四个局限,而且每个局限都有实际的解决办法:
| 你需要 | 使用 |
|---|---|
| 查看命令是否调用了主机 | 代理,或事后询问另一端 |
| 操作交互式面板或 TUI | 伪终端;shell 无法向其中输入内容 |
| 验证 Web 应用 而非 CLI | Reticle 和 verify-ui-change 技能 |
| 生成他人可以重新核查的结论 | 验证产物,而非文字记录 |
唯一不该做的事
不要因为差异未符合预期,就降低你在第 1 步中写下的预期。看到结果后重写目标,会让验证步骤沦为自我合理化;而且事后从文字记录中看不出来,因为修改后的预期看起来和原始预期完全一样。
如果差异不符合预期,这就是发现的问题。请如实报告。