跳到主要内容
返回写作

这篇记录更新了项目判断 / Stop That Shit

只让 Codex 导出一个文件,它却多做了一份 SHA-256

从一份无人读取的 .sha256 出发,说明 AI Coding Agent 的过度工程如何发生,以及怎样用消费者、下一步动作和 Stop Ladder 判断一项工作该不该做。

本文索引06
  1. 01Hash 有用,但要先找到它的消费者
  2. 02过度工程很少从大重构开始
  3. 03我把停止条件收成四个问题
  4. 04Bad Case 和 Good Case 要成对出现
  5. 05Skill 做判断,Guard 只检查看得清的动作
  6. 06以后再看到 .sha256,先别急着删

我只要一个结果文件。

Codex 把文件导出来了,旁边还多了一份 .sha256。看起来挺专业:完整性校验有了,流程也像是更可靠了。可后面的命令根本不会读取它。少了这份摘要,任务照样完成;多了它,也没有省掉任何一步。

这类额外工作最难处理的地方,不是它明显错误,而是它总能找到一个听起来正确的理由。

SHA-256 可以校验文件。兼容层可以照顾旧版本。全量测试可以增加信心。迁移框架可以为以后做准备。每个理由单独拿出来都成立,最后合在一起,任务却已经变成了另一件事。

Hash 有用,但要先找到它的消费者

判断一份摘要是否必要,我现在先问两个问题:

  1. 谁会读取它?
  2. 读取结果以后,下一步会发生什么变化?

发布产物旁边的 checksum 有明确消费者。下载端或 release verifier 读取摘要,发现不一致就拒绝产物。给大文件做 digest 也可能有用:如果 digest 没变,系统可以跳过一次昂贵的重复读取。

这两种情况里,hash 都替代了一次更贵的操作,或者直接控制了后续决策。

但如果 Agent 给两份 CSV 的每一行计算 SHA-256,算完以后仍然逐行比较,情况就不一样了。计算多了一轮,原来的比较一步没少。那份 hash 只让流程看起来更严谨。

所以问题从来不是“Hash 好不好”,而是这份 Hash 在当前任务里有没有工作。

过度工程很少从大重构开始

任务膨胀通常没有一个明显的爆炸点。它是一小步一小步长出来的。

  • 用户让 Agent 修一个配置错误,它顺手抽出一层通用配置接口;
  • 当前仓库没有第二种实现,它先加一个兼容 Adapter;
  • 用户只要本地工具,它先补云端同步的 feature flag;
  • 改动只触达一个模块,它还是跑完整测试矩阵;
  • 用户只让 Review,它为了“验证修复”直接改了文件。

这些动作都可以被包装成谨慎。问题在于,它们回答的是“以后也许会怎样”,不是“当前结果怎样才算完成”。

Agent 又特别擅长把可能性写成理由。一旦你只说“不要过度设计”,它仍然可以解释:这个抽象是必要的,这个兼容层更安全,这一轮全量测试能避免回归。

抽象提醒没有给它一个可以核对的停止条件。

我把停止条件收成四个问题

后来我把这套判断做成了 Stop That Shit(别再造史了)。产品名很直接,里面真正重要的是四个问题,也就是 Stop Ladder:

用户要求了吗?
它是完成当前结果所必需的吗?
哪段可达代码、数据、部署状态或验收条件证明了这一点?
省掉它,当前任务会在哪里失败?

这四个问题不是让 Agent 把所有额外工作都删掉。它们只是把“必要”从主观感觉拉回当前证据。

共享接口真的变了,就去修受影响的 caller。线上存在旧数据,就保留迁移。发布流程明确读取 checksum,就生成 checksum。测试是否要扩大,也应该看触达的契约和回归路径,而不是把“全量”当成默认的认真。

反过来,如果一项工作没有用户要求、没有当前消费者、没有可达证据,省掉以后验收也不会失败,那就不应该因为“可能更稳”而自动进入实现。

Bad Case 和 Good Case 要成对出现

只收集“Agent 做多了”的反例,很容易把规则推向另一个极端:看见 hashmigrationcache 就停。

所以 Stop That Shit 把一个 Bad Case 和一个只改变关键事实的 Good Case 放在一起。

BAD CASE
比较两份 CSV。
Agent 给每一行算 SHA-256,算完仍然逐行比较。
没有操作被省掉,停止。
 
GOOD CASE
生成发布产物的 checksum。
release verifier 会读取它,digest 不一致时拒绝产物。
当前流程确实需要,保留。

两边都有 SHA-256,结论却相反。决定不是来自关键词,而是来自消费者和后续动作。

这也是为什么我不想把 Stop That Shit 做成一个“越少越好”的代码行数工具。小 diff 也可能没有完成任务,大改动也可能是共享契约变化后的必要后果。目标不是做得最少,而是只做请求和结果真正需要的工作。

Skill 做判断,Guard 只检查看得清的动作

当前公开的 0.1.0 把产品分成两层。

Skill 负责语义判断:当前是 review 还是 change,额外工作有没有证据,哪些 caller、fixture 和测试属于必要后果。

Guard 处理 Host Hook 已经能看清的动作,例如只读 Review 里写文件、未经授权的新依赖、超出预算的 subagent,以及可识别的新 hash 操作。发布流程确实需要 checksum 时,可以显式放行:

$stop-that-shit change hash=allow -- 生成发布流程要求的校验和。

Guard 不会看见 migrationcache 两个词就猜它们多余,也不是一个安全沙箱。它处理的是受支持 Hook 路径上的任务授权,系统安全仍然由 Host 的 sandbox 和 approval 机制负责。

以后再看到 .sha256,先别急着删

先顺着流程往后看。

谁读取它?结果不同以后会发生什么?它替代了哪一步?如果没有它,当前验收会不会失败?

如果答案很具体,这份 hash 就应该留下。如果答案只有“以后可能有用”“看起来更完整”,那份 .sha256 已经给出了一个很好的停止信号。