with one click
verify-and-fix
交付前需验证完成,或修 bug 需修病因不修症状时。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Menu
交付前需验证完成,或修 bug 需修病因不修症状时。
Install with Codex or Claude Copy this prompt, paste it into Codex, Claude, or another assistant, and let it review the skill page and install it for you.
Based on SOC occupation classification
需求模糊、含隐藏假设或 X-Y problem,需先澄清再动手时。
审查代码(自己的或别人的),需有重点、分层次地看时。
Git 改动需编写规范提交信息时。
遇到 bug、报错、崩溃或行为异常,需定位根因时。
设计或改进错误处理与日志时。
在 Lion-Skills 仓库创建或修改 skill 时。
| name | verify-and-fix |
| description | 交付前需验证完成,或修 bug 需修病因不修症状时。 |
把"声称完成"变成"经验证完成"。核心:代码写完 ≠ 测试过 ≠ 类型对 ≠ 真的没问题——这三个"≠"是大多数返工和线上事故的源头。本 skill 的纪律是:交付前用工具实际跑一遍验证,修 bug 时找根因而非压症状,绝不为"通过"而弱化检查。
灵感来自 multi-agent "loop engineering"(写代码的 agent 与检查的 agent 分离、循环到全绿)。但本 skill 提炼的是纪律本身——无论单 agent 迭代还是多 agent loop,验证-修复的纪律不变。loop 是一种强制实现,纪律才是本质。
不该用:纯探索/原型阶段(本来就不要求正确,验证是负担);明确无失败可能的确定性逻辑(过度验证也是负担)。
与相邻 skill 的衔接:verify-and-fix 在 task-breakdown 的下游——每个任务的"完成定义"就是它的验证目标。task 拆出"做完 X 后能验证 Y",verify-and-fix 负责"实际去验证 Y、不通过就修"。它把 task-breakdown 里的"完成定义"从纸面标准变成实际跑过的证据。
"我看了一遍,应该没问题"——这是最危险的完成声明。代码审查(肉眼看)不能替代运行验证。肉眼能发现风格、明显逻辑错,但发现不了:实际运行时的类型不匹配、边界数据触发的分支、依赖交互、并发时序。
完成的标准必须是实际跑过的证据:
重构场景尤其要防回归:把 for 循环换成 find、换库、提函数、改异步为同步——这类"等价改写"最易悄悄丢失原代码处理的边界(原循环在没找到时可能返回 null/抛业务错,find 改写后这个分支没了)。验证重构时,问自己:原代码处理过哪些情况?新写法每一条都覆盖了吗? 尤其是边界——空集合、找不到、异常输入。重构的验证标准比新写更高:新写只验"能跑",重构还要验"行为没变"。
反例:用户说"我把 for 循环改成 find,应该没问题",你附和"嗯看着对"——但
find可能返回 undefined,.name就抛错。这种 bug 肉眼看不出来,跑一下立刻暴露。
修 bug 最常见的错法:在报错末端兜底,把错误压住,而不查它为什么报错。
Cannot read 'map' of undefined → 加可选链 data?.map(...) 压住。错误消失了,但 data 为什么是 undefined 的病因还在——换个场景又会炸。result is 4, expected 5 → 把断言改成 toBe(4)。测试变绿了,但"为什么是 4 而不是 5"的问题被掩盖。区分"合理兜底"和"掩盖 bug":
user.nickname ?? user.name——昵称本来就可不填。判断尺子:问"这个 undefined/null 在什么情况下出现?"——能说出一个合理的业务场景("用户没填昵称")→ 合理兜底;说不清、或答"反正运行时出现了"→ 是 bug,查源头。
修病因的路径:沿错误向上游追溯——报错点 ← 数据从哪来 ← 谁产生/传递的 ← 什么条件下变成这样。修在最上游的"产生"处,而不是最下游的"消费"处。
断言失败时的灰度——先确认期望值的权威性。测试报"实际 4,期望 5"时,别本能地认定"代码错了、断言对"。先问:期望值 5 从哪来? 是规格文档/业务约定的硬需求 → 代码错了,修代码;还是开发者随手写的猜测("我觉得应该是 5")→ 可能断言本身错了。区分两种"改断言":
判断尺子:改完的断言,描述的是"应该怎样"还是"现在怎样"? 前者(哪怕是改出来的)可能正确;后者(让断言迁就现状)一定错。
这是不可越界的底线。当测试/类型检查挡路时,正确的反应是修代码让它真正通过,错误的是改检查让它放行:
toBe(5) → toBe(4))——测试退化成"描述现状",失去验证意义@ts-ignore / any 压住类型错(除非有明确临时理由 + 留 TODO)try/catch 吞掉异常让流程"通过"|| [] / || {} 给本该有值的东西兜个默认值让它"不报错"为什么这条是红线:弱化检查制造的是欺骗性通过——绿灯看起来像"完成",实际是"绕过了验证"。它比直接失败更危险,因为失败会逼你修,欺骗性通过会让你带着 bug 交付。检查挡路是信号,不是障碍——它在告诉你"这里有真实问题"。
唯一例外:临时调试想跑通流程时可以临时禁用某项检查,但必须留显式标记(TODO + 理由),且不进主分支。
修 bug 时,失败报告(报错、堆栈、行号、复现步骤)是定位根因的唯一线索。别在传递过程中"总结"或"过滤"它:
TypeError: Cannot read 'name' of undefined at user.js:12,调用栈:getProfile←render,复现:用户列表为空时触发"。为什么:根因往往藏在行号、调用栈、触发条件里。"有个 bug"这种总结把这些全丢了,逼修的人从零重新发现。哪怕你觉得某些信息"无关",也原样保留——是否相关由修的人判断,不由你过滤。
在多 agent loop 里这条尤其关键:checker 的失败报告必须原样转发给 builder,编排器不能自作主张总结("checker 说有点问题"会把行号丢了)。单 agent 迭代同理——别把上一轮的报错"消化"成模糊印象,留着原文对照。
修完一个 bug,只跑出问题的那个测试是不够的——你的修复可能让别处悄悄坏了。修复后的验证范围应该是全部相关测试,不是只跑改动点:
为什么:修复本质是改动,改动就有副作用。只验"原本报错的地方不报了"会漏掉"修复让别处报错了"。这就是回归——表面上修好了 A,实际引入了 B。养成习惯:修完默认跑全量相关测试,把"只跑改的那个"当作偷懒的危险信号。
验证范围要匹配改动的影响面,不是无脑跑全量(那是另一种浪费):
判断尺子:这次改动可能波及哪些代码? 影响面 = 验证范围。低估影响面(把共享改动当叶子改)是回归的常见源头;高估影响面(叶子改动跑全量)只是慢,不危险——拿不准时往大了跑。
修了 A 引入 B、修 B 又引入 A——这种循环不罕见。没有刹车的循环会无限转下去,越改越烂。预设停止条件:
刹车不是放弃,是防止把"修不好"变成"越改越糟"。到达刹车条件时,正确动作是退回重新设计或求助,而不是继续在同一处硬磨。
| 问题 | 修法 |
|---|---|
| 没跑就声称完成("看着应该没问题") | 跑测试/类型检查/实际运行,用证据替代肉眼判断 |
| 修症状(末端兜底压住报错) | 向上游追溯根因,在最上游产生处修 |
| 为通过弱化检查(改断言/删测试/any/吞异常) | 红线不可越——修代码真正通过,而非改检查放行 |
| 把失败报告总结成"有个 bug" | 原样保留报错/行号/堆栈/复现步骤 |
| 修复无限循环、越改越乱 | 预设刹车(轮次/重复失败/回归/无进展),到点停并退回 |
| 把合理兜底(可选字段)当 bug 修,或反过来 | 问"空值何时出现"——有合理业务场景=兜底,说不清=bug |
| 重构只验"能跑",不验"行为没变" | 重构要覆盖原代码处理过的所有边界,标准比新写更高 |
| 修复后只跑出问题的测试 | 跑全部相关测试(调用方/共享逻辑/全量),防回归 |