先分享一个真实经历。
前段时间接手了一个看似不太复杂的任务——把内部风险中心的前端界面,从单条查看改造为批量操作,再新增几条业务链路。我使用 Claude Code,将其拆解为 15 个独立切片,每个切片都完成了单元测试。
结果如何?122 个单元测试全部通过,build 和 vue-tsc 也顺利通过,前后还找了 3 位不同的同事帮忙审查代码,都没发现问题。

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