devAlice
← Alice 之道

7. 验证循环 — 在系统中锁定「完成」的定义

操作者感到「完成了」的那一刻是最危险的。七步验证循环在报告发出前自动触发,覆盖 lint·构建·测试·diff,SKIP 条件有据可查,绕过时系统会留下痕迹。

这是 Alice Way 系列的第 7 篇。第 6 篇 多智能体委托 中,自动验证作为委托工作的关卡出现。本篇把那个验证提升一个层级——无论工作是直接完成还是委托完成,系统在某人即将说出「完成」的那一刻强制执行一个循环。

我认为验证循环的价值不在于捕获错误,而在于把「完成」的定义从感觉转移到系统。以前我们依赖操作者的判断;如今七步循环在报告发出前自动运行,由于那个强制介入,「完成」才成为可信赖的信号而非自我感觉。

0. 验证循环是系统对「心智认为已完成」的重新确认

工作感觉完成的那一刻是最危险的。心智倾向于「好,就这样了」的释然,遗漏的流程难以被注意到。lint 没运行,构建崩溃,测试被跳过——而「完成」仍然被输入了。

验证循环是打断那一刻的装置。就在「完成」即将被写下之前,lint、构建、测试、安全、diff 检查自动运行,报告在它们通过前无法发出。 系统重新确认心智认为已完成的内容。

本篇是对那个循环由哪七步构成、什么触发自动运行、以及绕过如何处理的记录。

自动触发机制是 Anthropic Claude Code 的 hooks 系统,已在第 5 篇——Hooks and automation中引用。本篇是操作者在那个机制之上构建的验证步骤的记录。

1. 为何需要自动触发

如果操作者每次都必须有意识地调用验证——有时会被跳过。跳过的验证积累——最终生产环境崩溃。如果跳过是认知极限,强制执行就必须在认知之外。

验证模式跳过可能性适合度
操作者每次有意识地运行高(疲劳或在流程中)
只在 CI 中运行0——但在提交/推送后,太晚了⚠️ 辅助支持
Hook 在报告前自动触发0——报告本身被阻断

第三种是答案。在报告发出前强制验证,遗漏就在系统层面被阻断。CI 是辅助安全网,不是前线防御。

2. 七步验证循环

我收敛出来的结论。每步在失败时立即停止,并报告清晰的消息。

2.1 Lint

样式、未使用的变量、明显的错误。最快,捕获最多。先运行——这里失败节省了后续步骤的时间。

2.2 类型检查

TypeScript / mypy / 其他静态类型检查器。捕获 lint 无法捕获的更深层错误。

2.3 构建

实际构建是否崩溃。阻止编译器无法通过的变更被合并。

2.4 测试

单元测试 + 集成测试。集成测试针对真实资源,而非 mock——曾经 mock 测试通过但生产迁移崩溃,那次事故定下了这条规则。

2.5 安全检查

密钥硬编码、SQL 注入模式、操作者真实姓名泄露——grep 风险。每次变更运行一份 8 条的检查清单。任何命中立即停止。

2.6 Diff 审查

修改的行是否符合操作者的意图。是否有什么非预期的东西悄悄混入。最后一道人眼关卡。

2.7 报告输出检查

报告文本本身是否携带泄露风险(密钥、内部标识符、真实姓名)。在报告发出前再做一次 grep。

除非全部七步通过,否则「完成」报告无法发出。任何失败——报告被阻断,操作者收到一行「步骤 X 失败」。

3. 自动触发条件 — 何时运行

验证循环自动触发的条件。

3.1 显式触发 — 「完成」措辞前

就在操作者试图输出「finished」/「done」/「ready」/等同表达时,hook 介入。在输出之前,不是之后——之后就太晚了。

3.2 显式触发 — 「PR 已准备好」/「可合并」报告

「PR 已准备好」这样的消息也触发同一个 hook。验证不在 PR 发出后运行,而在之前。

3.3 显式触发 — 「可提交」报告

「准备好提交了」——同上。不阻断提交本身,但在那个信号到达操作者之前再次强制验证。

3.4 显式调用 — /verify

当操作者有意识地想要验证时,slash command 调用同一个循环。两个入口指向同一套流程,与 hook 相同。

4. 何时自动触发被 SKIP

并非每项工作都需要验证。以下条件被声明为自动触发的 SKIP。

  • 只读工作 — 扫描、审查、调查。没有代码变更,没有什么可验证的。
  • 仅文档变更 — 只触及 .md 文件。lint/构建在这里没有意义。
  • 有意的草稿 — 实验、原型。操作者声明。
  • 操作者明确说「跳过验证」 — 明确绕过。

SKIP 条件存在于人格中,所以 hook 自动判断。操作者不必每次都说"跳过这个"。

5. 绕过留下的痕迹

有时操作者必须有意绕过——调试中、临时工作、进行中的热修复。

绕过是可以的。但绕过的事实总是落入日志。

[verify] BYPASSED at 2026-05-17 14:23 — reason: emergency hotfix
[verify] Skipped: lint / build / test / security / diff / report
[verify] Note: re-run /verify manually after hotfix lands

这样——绕过是可追溯的,「为什么这个没有被验证?」的事后调查很容易。绕过不被阻断,但痕迹留下来。

如果绕过不可能,操作者就会完全关掉验证。一旦关掉,就永远关掉了。

6. 处理失败

验证失败时操作者看到的内容。

[verify] FAILED at step: Test (3/7)
[verify] 2 tests failed:
  - integration/auth.test.ts > "github oauth callback redirects to next"
  - integration/db.test.ts > "rls policy blocks other user's row"
[verify] Full log: /tmp/verify-2026-05-17-1423.log
[verify] Report blocked.

核心是——失败信息压缩成一个屏幕。在哪里失败、问题是什么、详细日志在哪里。操作者从那个单一屏幕决定下一步(修复 / 绕过 / SKIP)。

把完整日志倒进控制台会让操作者自己重新总结,那本身就是新的负担。

7. 陷阱 — 验证失败的模式

7.1 太慢

如果验证超过 30 秒——操作者开始想「这太慢了」,频繁使用绕过。一旦绕过成为默认,验证就失去了意义。→ 最便宜的步骤先(lint/类型检查捕获大多数失败),昂贵的步骤后。

7.2 误报太多

如果验证经常报假警报——操作者开始不信任它。不信任 → 绕过。→ 验证本身必须是可信的。一旦发现误报,立即修复验证逻辑本身。

7.3 只有通过/失败,没有具体信息

只说「失败」,没有在哪里/为何——操作者必须自己挖掘日志。负担转回操作者。→ 输出保持简短但具体(哪个步骤,哪个测试,哪一行)。

7.4 SKIP 条件太窄

如果验证在几乎所有操作上都触发,操作者就会疲劳。对只读工作进行验证——验证本身变成噪声。→ 在人格中说明足够的 SKIP 条件。

8. 压缩成一条原则

验证循环设计的核心压缩成一句话:

「就在操作者即将说出『完成』的那一刻,系统介入一次停顿。只有通过了,那个词才能发出。绕过是可以的,但总是留下痕迹。」

这条成立时,验证循环成为在操作者释然时刻暂停一拍的安全网。它崩溃时——验证被关掉,或被误报所动摇,或绕过成为默认。

下一篇涵盖上述一切(验证、记忆、skills、hooks)所依赖的基础资源——令牌经济,即什么准入 context window、什么保持在外面。