关于 diagnose
diagnose 是面向编码 Agent 的系统化疑难调试技能,要求先建立可重复的反馈循环,再逐步复现、验证假设、修复并清理。
主要功能
- 01构建自动化反馈循环
- 02复现并记录故障症状
- 03提出可证伪的排序假设
- 04按预测逐项进行探查
- 05添加回归测试并修复
- 06清理调试痕迹并复盘
适用场景
- 01定位偶发或难以复现的缺陷
- 02验证性能回归的可能原因
- 03为修复建立可靠回归检查
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ mksglu/ context-mode --skill diagnose安装后在 Claude Code 中输入/diagnose确认已加载。
附带文件scripts/hitl-loop.template.sh
详细介绍
诊断
专门用于处理棘手 bug 的方法。只有在明确说明理由时,才能跳过阶段。
探索代码库时,使用项目的领域术语表,清楚了解相关模块,并查看你要修改的部分的 ADR。
阶段 1 — 建立反馈循环
这就是关键技能。 其他一切都是机械操作。如果你有一个快速、确定、可由 Agent 运行的通过/失败信号,就能找到原因——二分查找、假设检验和插桩都只是利用这个信号。如果没有,再怎么盯着代码看也无济于事。
在这里投入不成比例的精力。积极尝试。发挥创意。绝不轻易放弃。
构建反馈循环的方法 — 大致按以下顺序尝试
- 失败测试:在能够触及 bug 的任意边界上进行测试——单元测试、集成测试、端到端测试。
- Curl / HTTP 脚本:针对正在运行的开发服务器。
- CLI 调用:使用固定输入样例,并将标准输出与已知正常的快照进行差异比对。
- 无头浏览器脚本(Playwright / Puppeteer):驱动 UI,并对 DOM/控制台/网络进行断言。
- 重放捕获的跟踪记录。 将真实的网络请求/负载/事件日志保存到磁盘,然后通过隔离环境中的代码路径重放。
- 一次性测试框架。 启动系统的最小子集(一个服务、模拟依赖),通过一次函数调用触发有问题的代码路径。
- 属性/模糊测试循环。 如果 bug 表现为“输出有时错误”,就运行 1000 个随机输入,寻找故障模式。
- 二分测试框架。 如果 bug 出现在两个已知状态(提交、数据集、版本)之间,就自动执行“在状态 X 启动、检查、重复”,以便运行
git bisect run。 - 差分循环。 将相同输入分别交给旧版本和新版本(或两种配置),并比较输出。
- HITL bash 脚本。 最后的手段。如果必须由人点击,就用
scripts/hitl-loop.template.sh引导_他们_,让循环仍然有条理。捕获的输出会反馈给你。
构建合适的反馈循环,bug 就解决了 90%。
不断改进循环本身
把循环当作产品。建立_一个_循环后,思考:
- 能否让它更快?(缓存设置、跳过无关初始化、缩小测试范围。)
- 能否让信号更明确?(针对具体症状断言,而不是只检查“没有崩溃”。)
- 能否让它更确定?(固定时间、设置随机数种子、隔离文件系统、冻结网络。)
一个耗时 30 秒且不稳定的循环,几乎不比没有循环强。一个耗时 2 秒且确定的循环,是调试的超能力。
非确定性 bug
目标不是构造出干净的复现步骤,而是提高复现率。重复触发 100 次、并行运行、增加压力、缩小时间窗口、注入休眠。复现率为 50% 的 bug 可以调试;1% 的不行——持续提高复现率,直到能够调试。
确实无法构建循环时
停下来并明确说明。列出你尝试过的方法。向用户请求:(a) 访问能够复现问题的环境,(b) 捕获的产物(HAR 文件、日志转储、核心转储、带时间戳的屏幕录像),或 (c) 添加临时生产环境插桩的许可。不要在没有循环的情况下继续提出假设。
在建立你确信可靠的循环之前,不要进入阶段 2。
阶段 2 — 复现
运行循环。观察 bug 出现。
确认:
- 循环产生了用户描述的故障模式——而不是附近碰巧发生的另一种故障。找错 bug,就会修错问题。
- 多次运行都能复现故障(对于非确定性 bug,则需要以足够高的频率复现,才能据此调试)。
- 已捕获确切症状(错误消息、错误输出、耗时过长),以便后续阶段确认修复确实解决了问题。
在复现 bug 之前不要继续。
阶段 3 — 提出假设
在测试任何假设之前,先生成 3–5 个按可能性排序的假设。只提出一个假设会让人锚定在第一个看似合理的想法上。
每个假设都必须 可证伪:说明它会做出什么预测。
格式:“如果 是原因,那么 <更改 Y> 会让 bug 消失 / <更改 Z> 会让 bug 恶化。”
如果无法说出预测,那这个假设就只是凭感觉——应当舍弃或进一步明确。
测试之前,先向用户展示排序后的列表。 用户通常掌握能立即改变排序的领域知识(“我们刚部署了与 #3 有关的更改”),或者知道哪些假设已经排除。这是一个成本低、收益大的检查点。不要因此停下来等待——如果用户暂时不在,就按你的排序继续。
阶段 4 — 添加探针
每个探针都必须对应阶段 3 中的某个具体预测。一次只更改一个变量。
工具优先级:
- 如果环境支持,优先使用 调试器 / REPL 检查。一个断点胜过十条日志。
- 在能区分不同假设的边界处添加 有针对性的日志。
- 绝不要“记录所有内容再用 grep 搜索”。
每条调试日志都要添加唯一前缀,例如 [DEBUG-a4f2]。最后清理时只需执行一次 grep。未标记的日志会留下;带标记的日志会被清除。
性能分支。 对于性能回退,日志通常不是正确做法。应先建立基准测量(计时测试框架、performance.now()、分析器、查询计划),再进行二分排查。先测量,再修复。
阶段 5 — 修复 + 回归测试
在修复之前编写回归测试——但前提是存在正确的测试切入点。
正确的测试切入点,是指测试能在调用点复现真实的 bug 模式。如果唯一可用的切入点太浅(bug 需要多个调用方,却只有单个调用方的测试;单元测试无法复现触发 bug 的调用链),那么在该处编写回归测试会带来虚假的信心。
如果不存在正确的测试切入点,这本身就是一个发现。 记录下来。代码库架构导致这个 bug 无法通过测试加以锁定。将此问题标记给下一阶段。
如果存在正确的测试切入点:
- 将最小化复现转换为该切入点处的失败测试。
- 确认它会失败。
- 应用修复。
- 确认它会通过。
- 针对原始(未最小化)场景重新运行阶段 1 的反馈循环。
阶段 6 — 清理 + 事后复盘
宣布完成之前,必须做到:
- 原始复现不再出现(重新运行阶段 1 的反馈循环)
- 回归测试通过(或已记录不存在测试切入点)
- 已移除所有
[DEBUG-...]插桩(对前缀执行grep) - 已删除临时原型(或将其移至明确标记的调试目录)
- 在 commit / PR 消息中说明最终证实正确的假设——让下一个调试人员从中学习
然后思考:什么本可以避免这个 bug? 如果答案涉及架构更改(缺少合适的测试切入点、调用方之间纠缠、存在隐藏耦合),请将具体情况交给 /improve-codebase-architecture 技能。在修复完成之后再提出建议,而不是之前——现在你掌握的信息比刚开始时更多。
从 mattpocock/skills 引入,版本为 b843cb5 — MIT License。有关更新说明,请参阅 skills/UPSTREAM-CREDITS.md。