mcpField
S
systematic-debugging

systematic-debugging

github.com/nousresearch开发258.5k482 次安装今天更新
代码仓库

关于 systematic-debugging

Systematic Debugging 是一项指导 Agent 在修改代码前调查技术故障的技能。其四阶段流程强调可复现的测试循环、基于证据的根因分析、假设验证和修复确认。

主要功能

  1. 01修复前先调查根因
  2. 02建立快速且可复现的故障循环
  3. 03追踪代码变更、数据流和组件边界
  4. 04逐一验证排序后的假设
  5. 05添加回归测试并确认修复

适用场景

  1. 01调查失败的测试或生产环境 Bug
  2. 02复现间歇性行为以便分析
  3. 03追踪跨系统组件的问题
  4. 04验证修复方案且避免无关改动

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/nousresearch/hermes-agent --skill systematic-debugging

安装后在 Claude Code 中输入/systematic-debugging确认已加载。

详细介绍

系统化调试

概述

随机修复会浪费时间,还会引入新 Bug。快速打补丁会掩盖根本问题。

核心原则: 尝试修复之前,务必找到根本原因。只修复症状等于失败。

不遵守流程的字面要求,就是违背调试的精神。

铁律

NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST

如果尚未完成阶段 1,就不能提出修复方案。

反馈循环规则

反馈循环就是调试工作。在阅读代码并构建理论之前,先创建或找到一条紧密的命令:它能在用户遇到的确切症状出现时失败,并在 Bug 修复后通过。紧密的循环运行快速、结果确定、可由 Agent 执行,并且足够具体,能捕获这个 Bug,而不只是检查「程序不会崩溃」。

难以建立干净的复现时,就投入不成比例的精力来构建循环。没有能触发失败的循环却靠猜测,正是这项技能旨在避免的失败模式。

适用场景

任何技术问题都应使用:

  • 测试失败
  • 生产环境中的 Bug
  • 意外行为
  • 性能问题
  • 构建失败
  • 集成问题

以下情况尤其要使用:

  • 时间紧迫时(紧急情况容易让人忍不住猜测)
  • 「只需快速修复一下」看起来显而易见时
  • 已经尝试过多种修复方案时
  • 之前的修复无效时
  • 尚未完全理解问题时

不要在以下情况跳过:

  • 问题看起来很简单时(简单的 Bug 也有根本原因)
  • 赶时间时(匆忙行事必然导致返工)
  • 有人要求立即修复时(系统化调试比反复折腾更快)

四个阶段

必须完成每个阶段后才能进入下一阶段。


阶段 1:根本原因调查

尝试任何修复之前:

1. 仔细阅读错误信息
  • 不要略过错误或警告
  • 它们通常包含确切的解决方案
  • 完整阅读堆栈跟踪
  • 记下行号、文件路径和错误代码

操作: 对相关源文件使用 read_file。使用 search_files 在代码库中查找错误字符串。

2. 建立紧密的反馈循环
  • 能否用一条命令触发用户遇到的确切症状?
  • 这条命令是否会因这个 Bug 失败,并且只有 Bug 修复后才会通过?
  • 运行速度是否足够快,能反复执行?
  • 结果是否确定?对于不稳定的 Bug,能否提高复现率以便调试?
  • 如果无法复现 → 收集更多数据,不要猜测。

构建循环的方法——大致按以下顺序尝试:

  1. 在能够触达 Bug 的边界处编写失败测试:单元测试、集成测试或端到端测试。
  2. 使用 HTTP 脚本 / curl 请求正在运行的开发服务。
  3. 使用测试输入调用 CLI,并将 stdout/stderr 与预期输出进行差异比较。
  4. 使用无头浏览器脚本(Playwright/Puppeteer)断言 DOM、控制台或网络状态。
  5. 重放捕获的跟踪记录:HAR、请求载荷、事件日志、队列消息或 webhook 请求体。
  6. 编写一次性测试框架,启动系统中最小且有用的部分,并调用失败路径。
  7. 如果 Bug 是在广泛输入空间内间歇性地产生错误输出,则构建属性测试 / 模糊测试循环。
  8. 如果 Bug 出现在两个已知状态之间,则构建适用于 git bisect run 的二分查找测试框架。
  9. 构建差异比较循环,对比旧版与新版、两种配置、两家提供商或两组数据集。
  10. 仅作为最后手段使用人机协作脚本:将人工操作步骤编写成脚本并捕获结果,使循环保持结构化。

建立循环后,进一步收紧循环:

  • 提升速度:缓存初始化、缩小范围、跳过无关的初始化步骤。
  • 提高信号清晰度:断言确切症状,而不是泛泛地检查是否成功。
  • 提高确定性:固定时间、设置随机种子、隔离文件系统、冻结网络。

对于非确定性 Bug,眼下的目标是提高复现率,而非追求完美。将触发流程运行 100 次、并行执行、增加压力、缩短时间窗口或注入休眠。复现率为 50% 的不稳定问题可以调试;复现率为 1% 的通常不行。

操作: 使用 terminal 工具运行紧密的反馈循环:

# Run a specific failing test
pytest tests/test_module.py::test_name -v

# Or run a scripted repro
python scripts/repro_bug.py

# Or run a high-repetition flaky repro
for i in {1..100}; do pytest tests/test_flake.py::test_name -q || break; done
3. 检查近期变更
  • 有哪些变更可能导致这个问题?
  • Git 差异、近期提交
  • 新增依赖、配置变更

操作:

# Recent commits
git log --oneline -10

# Uncommitted changes
git diff

# Changes in specific file
git log -p --follow src/problematic_file.py | head -100
4. 在多组件系统中收集证据

当系统包含多个组件时(API → 服务 → 数据库、CI → 构建 → 部署):

提出修复方案之前,先添加诊断埋点:

对于每个组件边界:

  • 记录进入组件的数据
  • 记录离开组件的数据
  • 验证环境和配置是否传递
  • 检查每一层的状态

运行一次,收集证据以确定问题出在哪里。 然后分析证据,找出故障组件。 然后调查该组件。

5. 追踪数据流

当错误发生在调用栈深处时:

  • 错误值从哪里来?
  • 是什么调用了这个函数,并传入错误值?
  • 持续向上追踪,直到找到源头
  • 在源头修复,而不是处理症状

操作: 使用 search_files 追踪引用:

# Find where the function is called
search_files("function_name(", path="src/", file_glob="*.py")

# Find where the variable is set
search_files("variable_name\\s*=", path="src/", file_glob="*.py")
阶段 1 完成检查清单
  • 已完整阅读并理解错误消息
  • 已准备好一个紧凑的循环命令,并至少运行过一次
  • 该循环能够准确复现失败:它断言用户遇到的确切症状,而不是相近的问题
  • 该循环具有确定性,或者不稳定问题的复现率足够高,可以进行调试
  • 已识别并审查近期变更
  • 已收集证据(日志、状态、数据流)
  • 已将问题定位到特定组件/代码
  • 能够陈述并验证根因假设

停止: 在理解问题发生的原因之前,不要进入阶段 2。


阶段 2:模式分析

先找到模式,再进行修复:

0. 精简复现步骤

循环能够复现失败后,将复现步骤缩减到仍然失败的最小场景。每次只删除一项输入、调用方、配置、数据或步骤,并在每次删减后重新运行循环。只保留导致失败所必需的内容。

当删除任何剩余元素都会让循环转为成功时,就算完成。精简复现可以缩小假设范围,而且往往能成为最清晰的回归测试。

1. 查找可正常工作的示例
  • 在同一代码库中找到类似的可正常工作代码
  • 哪些类似的功能正常,而哪些功能有问题?

操作: 使用 search_files 查找可比较的模式:

search_files("similar_pattern", path="src/", file_glob="*.py")
2. 对照参考实现
  • 如果要实现某种模式,请完整阅读参考实现
  • 不要略读——逐行阅读
  • 完全理解该模式后再应用
3. 找出差异
  • 正常代码和有问题的代码之间有什么不同?
  • 列出所有差异,无论多么细微
  • 不要认定“这不可能有影响”
4. 理解依赖关系
  • 这需要哪些其他组件?
  • 需要哪些设置、配置和环境?
  • 它有哪些假设?

阶段 3:假设与测试

采用科学方法:

1. 提出可证伪假设并按可能性排序
  • 在测试任何一个假设之前,先提出 3–5 个合理的假设。
  • 按可能性和证伪成本从低到高排序。
  • 说明每个假设的预测:“如果 X 是原因,那么更改或观察 Y 应该会导致 Z。”
  • 丢弃或完善无法给出可测试预测的假设。

如果用户在场,测试前先展示排序后的列表。他们可能掌握相关领域知识,能立即调整排序。如果用户暂时不在,按你的排序继续。

2. 尽量精简地测试
  • 用尽可能小的探查来测试排序最高的假设。
  • 每次只更改一个变量。
  • 不要一次修复多个问题。
  • 如果可以,优先使用调试器/REPL 检查;一个断点胜过十条日志。
  • 如果添加日志,用唯一前缀标记每条临时日志行,例如 [DEBUG-a4f2],这样只需搜索一次就能清理干净。
3. 继续之前先验证
  • 是否有效?→ 阶段 4
  • 无效?→ 提出新假设
  • 不要在此基础上继续叠加修复
4. 遇到不确定时
  • 说“我不理解 X”
  • 不要假装自己明白
  • 向用户寻求帮助
  • 进一步调查

第 4 阶段:实现

修复根本原因,而不是表面症状:

1. 创建失败测试用例
  • 尽可能简单地复现问题
  • 尽可能编写自动化测试
  • 修复前必须先创建测试
  • 使用 test-driven-development 技能
2. 实施单一修复
  • 解决已确认的根本原因
  • 一次只做一项更改
  • 不要顺手改进其他内容
  • 不要捆绑重构
3. 验证修复
# Run the specific regression test
pytest tests/test_module.py::test_regression -v

# Run full suite — no regressions
pytest tests/ -q
4. 如果修复无效——三次规则
  • 停止。
  • 统计:你已经尝试了多少次修复?
  • 如果少于 3 次:返回第 1 阶段,根据新信息重新分析
  • 如果达到或超过 3 次:停止并质疑架构(见下方第 5 步)
  • 未经架构讨论,不要尝试第 4 次修复
5. 如果 3 次或更多修复均失败:质疑架构

表明存在架构问题的模式:

  • 每次修复都会在不同位置发现新的共享状态或耦合
  • 修复需要“大规模重构”才能实施
  • 每次修复都会在其他地方引发新的症状

停止并质疑基本假设:

  • 这种模式从根本上是否合理?
  • 我们是否只是“凭惯性坚持下去”?
  • 是否应该重构架构,而不是继续修复症状?

尝试更多修复之前,先与用户讨论。

这不是假设失效,而是架构错误。


危险信号——停止并遵循流程

如果你发现自己在想:

  • “先快速修复,以后再调查”
  • “只要试着改一下 X,看看是否有效”
  • “一次做多项更改,然后运行测试”
  • “跳过测试,我会手动验证”
  • “问题可能出在 X,我来修复”
  • “我还没完全弄明白,但这可能有效”
  • “模式要求做 X,但我会用另一种方式调整”
  • “以下是主要问题:[未经调查就列出修复方案]”
  • 尚未追踪数据流就提出解决方案
  • “再试一次修复”(已经尝试过 2 次或更多时)
  • 每次修复都会在不同位置发现新问题

所有这些都意味着:停止。返回第 1 阶段。

**如果 3 次或更多修复均失败:**质疑架构(第 4 阶段第 5 步)。

常见的自我辩解

借口现实
“问题很简单,不需要流程”简单问题也有根本原因。流程能快速处理简单 Bug。
“这是紧急情况,没时间走流程”系统化调试比反复猜测和试错更快。
“先试试这个,再调查”第一次修复会定下模式。从一开始就做好。
“确认修复有效后再写测试”未经测试的修复难以持久。先写测试才能验证修复。
“一次做多个修复能节省时间”无法确定哪项修复有效,还会引发新 Bug。
“参考资料太长,我会自行调整模式”只理解一部分必然会引发 Bug。请完整阅读。
“我看出问题了,我来修复”看到症状 ≠ 理解根本原因。
“再试一次修复”(失败 2 次或更多后)失败 3 次或更多 = 架构问题。质疑模式,不要再次修复。

快速参考

阶段关键活动成功标准
1. 根本原因阅读错误信息、复现问题、检查更改、收集证据、追踪数据流理解是什么问题以及为什么
2. 模式查找可用示例、进行比较、找出差异知道差异所在
3. 假设提出理论、进行最小化测试、一次只改变一个变量得到确认或提出新假设
4. 实现创建回归测试、修复根本原因、验证Bug 已解决,所有测试通过

Hermes Agent 集成

调查工具

在第 1 阶段使用以下 Hermes 工具:

  • search_files — 查找错误字符串、追踪函数调用、定位模式
  • read_file — 阅读带行号的源代码,以便精确分析
  • terminal — 运行测试、检查 Git 历史、复现 Bug
  • web_search/web_extract — 查询错误信息和库文档
使用 delegate_task

对于涉及多个组件的复杂调试,派遣调查子 Agent:

delegate_task(
    goal="Investigate why [specific test/behavior] fails",
    context="""
    Follow systematic-debugging skill:
    1. Read the error message carefully
    2. Reproduce the issue
    3. Trace the data flow to find root cause
    4. Report findings — do NOT fix yet

    Error: [paste full error]
    File: [path to failing code]
    Test command: [exact command]
    """,
    toolsets=['terminal', 'file']
)
使用测试驱动开发

修复错误时:

  1. 编写一个能复现错误的测试(RED)
  2. 系统地调试,找到根本原因
  3. 修复根本原因(GREEN)
  4. 测试验证修复有效,并防止回归

实际影响

根据调试过程的经验:

  • 系统化方法:15–30 分钟即可修复
  • 随机修复方法:折腾 2–3 小时
  • 首次修复成功率:95% 对比 40%
  • 引入的新错误:几乎为零,对比经常发生

不走捷径,不靠猜测。系统化方法始终更胜一筹。

评论

支持 Markdown