先讲一件真事
前阵子做一个不算复杂的需求------就是把内部一个风险中心的前端从单条查看改成批量操作,外加几条新链路。我用 Claude Code 跑,规规矩矩切了 15 个切片,每个切片都过了单元测试。最后一数,122 个单元测试全绿,build 过,vue-tsc 过,前后还跑了 3 轮独立 reviewer,都没挑出毛病。
我寻思这把稳了,让同事来联调。他 pnpm dev 一启动,首页白屏。
控制台一报,两个 bug:
crypto-js在某条链路里被当成 ESMimport,但它实际是 UMD 形态,运行时挂掉;- 框架里
Request默认 baseURL 是/mock,真环境根本没这个前缀,请求全 404。
这两个 bug,单元测试、类型检查、构建、静态 diff 审查,一个都没抓到。
那一刻我挺受挫的。不是 bug 难,是这套「看起来很严谨」的流程给了我一个虚假的绿灯。编译绿不代表能跑,切片绿不代表整体可用。
后来我琢磨了很久,把问题归到一点:我的闭环里缺了一层「运行时」的闸门。再往深想,缺的不止这一层------整个 AI 编码的工作流,缺的都是「可验证」这三个字。
于是我把这套想法做成一个 Claude Code skill,叫 loop-eng。下面讲我怎么定义这个闭环、为什么这么定义,以及你怎么用。
AI 编码到底病在哪
先说现象,再说病因。我观察到的 AI 编码翻车,几乎都逃不出这五类:
一是一把梭。 你给个模糊需求,模型闷头写一堆回来,等你 review 的时候发现方向早偏了。问题不在模型能力,在它没有先跟你对齐「完成长什么样」就开干。
二是半成品堆叠。 一次产出十几个文件改动,看着热闹,但没有一片是能单独跑、单独验证的。一旦出问题,根本定位不到是哪一步引入的。
三是「写完了」不可证伪。 模型说"已完成",你问它怎么算完成,它也答不上来。没有一条可执行的断言来定义 done,"完成"就只是它的主观判断。
四是闷头推进。 实现到一半撞见一个隐藏约束(比如数据其实已经带某字段了),模型不回头,硬顺着错误前提往下写,错误层层累积。
五是编译绿 ≠ 能跑。 就是我开头那个故事。tsc 和 build 是编译期检查,抓不到运行时问题------ESM 形态、框架默认值、代理配置、环境变量,这些只有真启动才暴露。
这五条里,前四条是流程问题,第五条是闸门选错层级的问题。loop-eng 就是冲着这五条去的。
Loop Engineering 是什么
就是 TDD + 增量交付 + maker-checker 的缝合。我把它总结成三个要素,理解这三点比记步骤重要:
切片。 每轮只做一个能独立验证的最小增量------一个页面、一个端点、一个修复。不堆半成品。判据很简单:这一片单独跑得通吗?能单独提交吗?如果不能,就是切大了。
闸门。 用一条可执行断言来定义 done。关键词是「可执行」「断言」------不是 checklist,不是"我看了下没问题"。而且这条断言必须能先失败(先红),改完才变绿。先红这一步至关重要:它证明这条闸门真的在守这个边界,而不是摆设。如果一开始就是绿的,说明你写的断言跟当前行为无关,等于没守。
回流。 任何阶段发现新事实------实现时撞见隐藏约束、验证时发现漏了 case、需求方改主意------不要硬推,回到上游对应阶段修正。这是 loop 之所以是 loop 的地方。但注意:回流是冻结当前工作,不是删掉重来。已写的代码可能部分可复用,删了就浪费了。
这三点贯穿整个流程。下面看流程。
五阶段闭环
①框定 → ②建模 → ③建闸 → ④实现 → ⑤验证
▲ │
└──── 新事实 / 遗漏 / 需求变更 ───┘
每个阶段我都定了统一的三段式:触发条件 → 动作 → 出口标准。这么定是有原因的------AI 跑流程最容易在「什么时候算这个阶段做完了」上含糊,出口标准就是堵这个口子的。
① 框定:把模糊敲成明确
触发:需求进来,通常模糊。
动作:能用代码回答的先去翻代码(别拿能查到的事问用户,烦);需要用户拍板的,一次只问一个决策,而且每个都给推荐答案 + 理由。必须敲定五件事:边界 (做什么不做什么)、架构 (数据/状态放哪)、基准 (冲突时以哪边为准)、范围/批次 (切几片)、完成定义(怎么算 done)。
出口:「完成」写成一句可验证标准,登记进 loop-state 的 goal 字段。
这一步是最容易被跳过的。模型天生想直接写代码,你得用流程逼它先对齐。我自己踩过:有一次没敲定"导出是 PDF 还是也支持 Excel",模型默认做了 PDF,结果用户要 Excel,返工。
② 建模:对照代码找矛盾(按需)
触发:仅当需求涉及领域关系、状态流转、术语冲突时。纯前端展示、CRUD、配置类------跳过,压成一句确认。
动作:把用户的陈述和代码的实际行为 cross-reference,有冲突立刻指出。"你说订单有三种状态,但代码里是五种,哪个对?"------这种矛盾不挑出来,后面全错。
出口:无未消化的矛盾,或矛盾已显式记为待回流。
这一步我标了「按需」,因为不是所有任务都需要建模。强行给一行 bugfix 套领域建模,那是仪式感拖慢。
③ 建闸:先写一条会失败的闸门
触发:完成定义已定。
动作:选闸门层级。
- 强闸门 :单元/集成测试,先红后绿,默认首选。如果项目没测试基建(比如前端缺组件测试框架),先花一个切片搭最小脚手架,不要因为没基建就直接降级。
- 弱闸门 :目标行为确实难以纯函数断言时(PDF 产物、图表渲染、外部 IO 时序),用快照/契约/脚本断言。注意:快照不是建个基线就完事,得先写一条「与预期不符」的可失败断言;人工验收 checklist 不能单独当闸门,必须配一条能失败的自动化断言一起守。
- 运行时闸门 (前端/框架集成/部署配置类切片必须额外叠加 ):
build/tsc只是编译闸门,抓不到运行时问题。必须启动 dev/服务 + 真请求探针------curl页面返回非空非错误 HTML、API 经代理返回预期数据字段------断言可先红。编译绿 ≠ 运行时能跑。
出口:有一条现在会失败的闸门。红了才进下一步。
这一阶段就是治我开头那个白屏病的药。如果当时我在切片里叠了运行时闸门,crypto-js 的 ESM 问题和 baseURL 问题,第一次 curl 首页就该炸出来,根本撑不到联调。
④ 实现:小步走到绿
触发:闸门已红。
动作:小步改,改到闸门转绿。切片粒度判据------一个切片 = 一个能独立跑绿的闸门 + 一个有意义的提交信息。超过 3 个文件或跨 2 个无关模块,拆。
出口:闸门绿。红灯期间不跳下一阶段、不提交。
这条很朴素,但有效。它逼你每一步都有反馈,而不是写 500 行再一起跑。
⑤ 验证:独立校验 + 全绿 + 记录
触发:闸门转绿。
动作分四步:
- 跑客观 gate:全量测试、类型检查、构建------必须全绿。
- 强制独立 verifier :spawn 一个独立 subagent 当 checker(写代码的那个不能兼任)。只给它 diff + 闸门 + goal,要求它「找理由拒绝」------逐项查:diff 是不是最小、有没有漏边界、闸门是不是真守住了这个边界(而不是碰巧通过)、有没有预期外副作用。涉及前端/集成的,verifier 还得真启动服务跑页面,确认非白屏。
- verifier 判定:approved 才算 done;rejected(必附理由)触发回流------闸门漏 case 回 ③,实现有问题回 ④,理解偏差回 ①。
- 记录:本轮进度/决策记
loop-state,跨会话的领域知识/架构决策记 memory。
出口:客观 gate 全绿 + verifier approved + loop-state 更新。
maker/checker 分离这一条,是我最不想妥协的。同一个 agent 又写又审,等于自己给自己批作业,模型倾向于放过自己的实现。独立 verifier 只看 diff 和 goal,它的视角天然是「挑刺」而不是「理解意图」,这正是你需要的。
降级规则也得说清楚:环境没有 subagent 机制时,做一次 role-separated verifier pass(切换视角重审 diff),但必须在 loop-state 里披露「非完全独立校验」。高-risk 切片(认证、权限、数据迁移、计费、部署)不得降级------必须真独立 verifier,或者转 needs-human 让人来看。
回流引擎:loop 的引擎
上面那个图里有个回流的箭头,我说细点。回流不是"推倒重来",是结构化回退,固定四步:
- 冻结当前切片:已写代码/闸门暂存,不删不提交,标记「待回流」。
- 修正上游:回到对应阶段(①目标 / ②建模 / ③闸门),更新 loop-state。
- 重跑或重写:分两类------闸门级回流(⑤发现 case 漏了,补断言重跑红绿);目标级回流(①边界或②模型变了,旧闸门作废,重写闸门,冻结的旧代码可能部分作废)。
- 记回流原因:在 loop-state 记一条(触发点 / 新事实 / 回到哪),防止下一轮重复踩。
为什么是冻结不是删除?保留已做的工作可以复用,也防止「回流」被滥用成「推倒重来」。回流期间不算「红灯推进」违规------冻结 + 记原因后是显式阶段回退,不是闷头往下做。
回流触发点我都列死了,避免模型自己瞎选回退点:
- ②建模发现领域模型错误 → 回 ②重模
- ④实现撞隐藏约束 → 回 ①调边界
- ⑤验证发现没覆盖的 case → 回 ③补测试;verifier rejected → 按理由回 ③/④/①
- 需求变更 → 回 ①重新对齐
卡死了怎么办:逃生通道
不是所有闸门都能顺利转绿。我定的规则:同一闸门连续 2 轮没转绿,就走逃生通道,不允许闷头重试。三步升级:
- 拆分:把当前切片再切小,先让子切片转绿。
- 降级 :强闸门 → 弱闸门。但理由只能是「目标行为确实难以纯函数断言」,不能因为「过不了闸」就降级------那就是作弊了。降级后仍须有可失败断言。
- 求助:把卡点 + 已尝试方案抛给用户决策。用户给方案后记入 loop-state,按目标级回流处理。
这呼应一条老原则:出 bug 先写重现测试再修。卡死本质是闸门无法重现通过,逃生是结构化求助,不是盲目重试。
实战教程:用 loop-eng 修一个暗色模式 bug
光讲道理没用,走一遍。这是个真实场景------风险中心切暗色模式后,echarts 图表颜色还是亮色的,跟暗底不搭。
第一步:安装
bash
git clone https://github.com/iceggboom/loop-eng.git ~/.claude/skills/loop-eng
放到全局 ~/.claude/skills/ 下,所有项目都能用;只想给某个项目用,就放到那个项目的 .claude/skills/。
第二步:调用
在 Claude Code 里:
arduino
/loop-eng 风险中心切到暗色模式后,echarts 图表的颜色还是亮色的,跟暗底不搭,看着很难受。帮我修一下。
第三步:看它怎么走
①框定:它不会直接去改颜色。先翻代码找图表配置在哪、主题切换是怎么触发的。可能问你一两个决策,比如"图表色是走 CSS 变量还是 JS 配置"------但它大概率自己能查到,不会问你。
②建模(这里需要):它会发现一个关键事实------图表色是写在 JS option 里的,不走 CSS token,所以切主题时 CSS 变量变了但 option 没重算。这就是根因。这一步把"颜色不对"这个表象,定位成"option 没响应主题切换"这个真问题。
③建闸 :先写一条会失败的测试------切到暗色后,断言图表 option 里的颜色更新成了暗色色板。这条测试现在红,因为代码还没改。这一步证明闸门在守"主题切换会重算 option"这个边界。
④实现:监听主题切换事件,重算 option。小步改到测试转绿。
⑤验证:跑全量测试 + 类型检查 + 构建,全绿。然后 spawn 独立 verifier,只给它 diff + 闸门 + goal,让它找理由拒绝------比如"diff 是不是最小""有没有漏边界(其他图表组件是不是也走同一套配置,会不会被这个改动影响)"。verifier approved,才算完。
整个过程,你不是在看模型"写代码",而是在跟它一起"对一个边界负责"。这是 loop-eng 想给的那种感觉。
什么时候别用 loop-eng
这点很重要,不是所有任务都该套闭环。强行套,等于用大炮打蚊子。
举个反例:
列表页分页器点下一页没反应,我看了一下是
handleClick里page少加了 1,帮我改一下。
这种一行明确的修复,直接改 + 跑相关测试就行。loop-eng 应该识别出"这不需要框定、不需要建闸、不需要切批次",直接动手。如果它对这种需求也走五阶段,那就是滥用,反而拖慢。
判据:
- 用:新功能、复杂 bug、重构------容易跑偏、需要严谨闭环的多步任务。
- 不用:一行修复、纯问答、读代码理解。直接做。
我自己定了一条铁律:切片可独立验证、红灯不推进、决策随做随记、卡死即走逃生通道、⑤须独立 verifier。这五条里,"不用 loop-eng"的场景本质上就是不需要这五条约束的场景。
一些反思
做这个 skill 的过程中,我自己改了好几版,有几个点想单独说:
先红这一步,一开始我没强调够。 早期版本只说"写闸门",没说"必须先失败"。结果模型有时候写的断言跟当前行为无关,一跑就绿,闸门形同虚设。后来我把"先红"写进出口标准,闸门质量明显上来。先红是闸门存在的证据。
独立 verifier 降级这个口子,我犹豫过要不要开。 最后还是开了,因为有些环境确实没 subagent。但我加了披露要求和 high-risk 不得降级两条限制。妥协可以,但要透明,且关键场景不妥协。
运行时闸门是最后加的。 就是因为那个白屏事故。在这之前我也以为"测试 + build + tsc 全绿"就够了。那次之后我才意识到,编译期和运行时是两个世界,前端/框架集成类切片没有运行时探针,就是裸奔。这个教训我写进了 skill 的反例警示里,提醒未来的自己别再犯。
回流用冻结而不是删除,是吃过亏的。 早期有一次回流,模型直接把已写的代码删了重来,结果发现旧实现有一半是对的,白费。冻结保留可复用部分,这个设计虽然小,但很值。
怎么拿到
仓库在这:github.com/iceggboom/l...
里面有完整的 SKILL.md(skill 主体)、templates/loop-state.md(跨轮状态模板)、evals/evals.json(4 个评估用例 + rubric,含一个"不该用 loop-eng"的负例)。
如果你想在自己的 Claude Code 里试,clone 到 .claude/skills/loop-eng/ 就行。
最后说一句掏心窝的:AI 编码这个事,能力已经不是瓶颈了,瓶颈在怎么让能力稳定地落在正确的边界内。Loop Engineering 不是什么银弹,它就是把 TDD、增量交付、maker-checker 这些老东西,按 AI 编码的特点重新组织了一遍。但它管用------至少,它不会再给你一个虚假的绿灯。
白屏那次之后,我没再遇到过"全绿却不能跑"的尴尬。这对我来说就够了。