| name | diagnosing-bugs |
| description | 面向棘手缺陷和性能回退的诊断闭环。适用于用户要求“诊断”“排查”“debug”,或报告功能损坏、抛出异常、运行失败、响应缓慢时。 |
诊断缺陷
这是一套处理棘手缺陷的纪律。只有在明确说明理由时,才可以跳过某个阶段。
探索代码库时,先读取 CONTEXT.md(如果存在),建立对相关模块的清晰认识;同时检查所涉及区域的 ADR。
阶段 1——建立反馈闭环
这一步就是整个 Skill 的核心。 其余工作都只是机械执行。只要拥有一个针对当前缺陷的紧密通过/失败信号,并且这个信号会在当前缺陷上变红,就一定能够找到原因;二分定位、假设检验和插桩都只是利用这个信号。没有这样的信号,盯着代码看再久也无济于事。
在这一阶段投入远高于其他阶段的精力。积极尝试,发挥创造力,不要放弃。
构建闭环的方法——大致按以下顺序尝试
- 在能够触达缺陷的 seam 上编写失败测试,可以是单元测试、集成测试或端到端测试。
- 针对正在运行的开发服务器编写 Curl / HTTP 脚本。
- 使用固定输入运行 CLI 命令,并将 stdout 与已知正确的快照进行 diff。
- 编写无头浏览器脚本(Playwright / Puppeteer),驱动 UI,并对 DOM、控制台或网络行为作出断言。
- 重放已捕获的轨迹。 将真实网络请求、payload 或事件日志保存到磁盘,再隔离地重放对应代码路径。
- 一次性测试支架。 启动系统的最小子集,例如一个服务加模拟依赖,通过一次函数调用触发缺陷代码路径。
- 性质测试或模糊测试闭环。 如果问题表现为“输出偶尔错误”,运行 1000 组随机输入,寻找失败模式。
- 二分测试支架。 如果缺陷出现在两个已知状态之间,例如两个 commit、数据集或版本,自动执行“在状态 X 启动、检查、重复”,使其能够交给
git bisect run。
- 差分闭环。 让同一个输入分别经过旧版本和新版本,或两组配置,再比较输出。
- HITL Bash 脚本。 这是最后手段。必须由人点击时,用
scripts/hitl-loop.template.sh 引导人执行,使闭环仍保持结构化。捕获到的输出应回传给你。
建立正确的反馈闭环后,缺陷已经解决了九成。
收紧闭环
把闭环当作一个产品。建立出一个闭环后,继续将它收紧:
- 能否让它更快?例如缓存初始化、跳过无关启动过程、缩小测试范围。
- 能否让信号更准确?应针对具体症状断言,不能只检查“没有崩溃”。
- 能否让结果更确定?例如固定时间、固定随机种子、隔离文件系统、冻结网络。
一个需要 30 秒且结果不稳定的闭环,几乎和没有闭环一样;一个 2 秒即可稳定给出结论的闭环才足够紧密,这会成为调试中的强大能力。
非确定性缺陷
这里追求更高的复现率,无需等待一次完全干净的复现。循环触发 100 次、并行执行、增加压力、缩窄时序窗口、注入 sleep。复现率达到 50% 的偶发问题可以调试,只有 1% 时则很难。持续提高复现率,直到它能够支撑调试。
确实无法建立闭环时
停止,并明确说明当前情况。列出已经尝试的方法。向用户请求以下任一项:(a) 能够复现问题的环境访问权限;(b) 已捕获的产物,例如 HAR 文件、日志转储、core dump、带时间戳的录屏;(c) 在生产环境中加入临时插桩的许可。没有闭环时,不得进入假设阶段。
完成标准——一个紧密且能够变红的闭环
阶段 1 的完成条件是:闭环足够紧密且具备变红能力(red-capable)。你能够指出一条命令,例如脚本路径、测试命令或 curl;已经至少亲自运行过一次,并贴出命令和输出;同时满足以下条件:
如果在这条命令存在之前,你已经开始阅读代码并构建理论,立即停止。直接跳到假设,正是本 Skill 要防止的失败。 没有能够变红的命令,就不能进入阶段 2。
阶段 2——复现并最小化
运行闭环,观察它变红,也就是让缺陷出现。
确认:
最小化
闭环变红后,将复现场景缩减成仍然会变红的最小场景。逐项削减输入、调用方、配置、数据和步骤;每次只删一项,随后重新运行闭环。最终只保留对失败不可或缺的部分。
最小复现会缩小阶段 3 的假设空间,减少需要怀疑的活动部件;它还会在阶段 5 中成为干净的回归测试。
完成条件是:所有剩余元素都不可或缺,删除其中任意一项都会让闭环变绿。
在完成复现和最小化前,不得继续。
阶段 3——提出假设
在验证任何假设前,先生成 3~5 个按可能性排序的假设。只提出一个假设会让判断锚定在最早出现的合理想法上。
每个假设必须可以证伪,并明确写出它作出的预测。
格式:“如果 是原因,那么改变 会让缺陷消失,或改变 会让缺陷更加严重。”
无法写出预测的想法只是一种感觉;删除它,或把它打磨成可验证假设。
开始验证前,把排序后的列表展示给用户。 用户通常掌握能够立即改变排序的领域信息,例如“我们刚部署了与第 3 项有关的改动”,也可能知道哪些假设已经排除。这是成本很低、节省时间很多的检查点。不要因此阻塞执行;用户暂时离开时,继续按照你的排序推进。
阶段 4——插桩
每次探测都必须对应阶段 3 中的一项具体预测。每次只改变一个变量。
工具优先级:
- 环境支持时优先使用调试器或 REPL 检查。一个断点胜过十条日志。
- 在能够区分不同假设的边界处添加定向日志。
- 绝不“把所有东西都打印出来再 grep”。
为每条调试日志添加唯一前缀,例如 [DEBUG-a4f2]。结束时只需一次 grep 即可清理。没有标签的日志容易残留;带标签的日志必须删除。
性能分支。 对性能回退而言,日志通常不是正确工具。应先建立基线测量,例如计时支架、performance.now()、profiler 或查询计划,然后再进行二分。先测量,再修复。
阶段 5——修复并补充回归测试
在修复前编写回归测试,但前提是存在正确的 seam。
正确 seam 应让测试按照调用现场中的真实形态触发缺陷。唯一可用的 seam 过浅时,例如缺陷依赖多个调用方,而测试只能覆盖单个调用方;或单元测试无法重现触发问题的调用链,在那里编写回归测试只会制造虚假信心。
如果不存在正确 seam,这本身就是调查结论。 记录下来。代码库架构正在阻止你锁定该缺陷,并应在下一阶段明确标记。
存在正确 seam 时:
- 将最小复现转成该 seam 上的失败测试。
- 观察测试失败。
- 应用修复。
- 观察测试通过。
- 针对最初的、尚未最小化的完整场景,重新运行阶段 1 的反馈闭环。
阶段 6——清理与复盘
宣布完成前,必须满足:
随后追问:怎样才能从源头避免这个缺陷? 如果答案涉及架构变化,例如缺少良好测试 seam、调用关系纠缠或存在隐性耦合,应带着具体发现把后续工作交给 /improve-codebase-architecture Skill。必须在修复完成后再提出这项建议;此时掌握的信息远多于刚开始时。