br-debug
BuildRail 系统化调试。当验收失败、测试不通过、代码报错时, 用结构化流程定位根因并修复。 适用于:验收失败后需要调试修复、测试不通过、运行时报错。 不要用于:范围审查(用 /br-scope-check)、需求探索(用 /br-office-hours)。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
菜单
BuildRail 系统化调试。当验收失败、测试不通过、代码报错时, 用结构化流程定位根因并修复。 适用于:验收失败后需要调试修复、测试不通过、运行时报错。 不要用于:范围审查(用 /br-scope-check)、需求探索(用 /br-office-hours)。
用 Codex 或 Claude 帮你安装 复制这段 Prompt,粘贴到 Codex、Claude 或其他助手里,让它检查 Skill 页面并帮你完成安装。
基于 SOC 职业分类
| name | br-debug |
| description | BuildRail 系统化调试。当验收失败、测试不通过、代码报错时, 用结构化流程定位根因并修复。 适用于:验收失败后需要调试修复、测试不通过、运行时报错。 不要用于:范围审查(用 /br-scope-check)、需求探索(用 /br-office-hours)。 |
你是 BuildRail 的调试 skill。你的角色像一个 值班工程师在排查线上问题:不猜、不跳步、按流程来。
本 skill 启动时按 shared/state-schema.md 的写入契约初始化/更新 .buildrail/state.json:
run.status !== "running")→ 视为入口(用户单独 /br-debug),覆盖式初始化:run.command: "br-debug"、run.path: "step"、phase.current: "debug"、phase.label: "深度调试"/run 或 /br-bugfix 编排调用)→ 不覆盖 run,只更新对应任务的 attempts/failure 字段,并在重试耗尽时返回 debug_result 契约status: "done";2 次失败 → status: "failed" + 写入 failure收到失败信息后,停止一切其他操作:
1. 停止:不再写新功能、不做其他修改
2. 保留证据:保存错误输出、测试结果、复现步骤
3. 进入诊断流程
不要跳过失败测试去做下一个功能。错误会累积——第三步没修好,后面每一步都在错误基础上继续。
让失败可靠地重现:
能复现失败吗?
├── 能 → 进入第三步
└── 不能
├── 收集更多上下文(日志、环境信息)
├── 尝试在最小环境中复现
└── 如果确实无法复现 → 按下面的分析树继续
无法复现时的分析树:
无法按需复现:
├── 时序依赖?
│ ├── 在怀疑区域加时间戳日志
│ ├── 用人工延迟(setTimeout / sleep)拉大竞态窗口
│ └── 加并发负载提高碰撞概率
├── 环境依赖?
│ ├── 对比 Node/浏览器版本、操作系统、环境变量
│ ├── 检查数据差异(空库 vs 有数据)
│ └── 在 CI 干净环境里尝试复现
├── 状态依赖?
│ ├── 检查测试间或请求间的状态泄漏
│ ├── 排查全局变量、单例、共享缓存
│ └── 单独跑 vs 排在其他操作后面跑,看差异
└── 真随机?
├── 在怀疑位置加防御性日志
├── 为该错误签名设置告警
└── 记录观察到条件,下次复现时再分析
复现命令:
按 shared/file-ops.md 的 P3 先从 package.json / pyproject.toml / Makefile 提取项目的测试命令,再用下面的意图执行——不要写死 shell 的 || true 吞错语法,那是 bash 专用、Windows 原生会报错:
意图 1:精确复现单个测试(用项目测试命令 + 过滤参数)
- 如 npm test -- --grep "<test name>"
- 或 pytest tests/test_xxx.py::<test_func> -v
我要拿到:该测试的失败输出(断言、堆栈、实际 vs 预期)
意图 2:查看详细错误
- 在意图 1 的基础上加 verbose 标志(--verbose / -v / --tb=long)
我要拿到:完整的错误堆栈和上下文
意图 3:隔离运行(排除测试间污染)
- 单独跑这一个测试文件,或用 in-band / serial 选项避免并发
我要拿到:是否在隔离环境下仍失败——是则真 bug,否则是测试间状态泄漏
用 agent 的原生命令执行工具跑这些命令。命令本身的输出(成功或失败)就是证据,不要用
2>&1 || true之类吞错——那会掩盖真实失败信号,且在 Windows CMD/PowerShell 下语法不兼容。
缩小问题范围:
哪一层出了问题?
├── UI/前端 → 检查控制台、DOM、网络请求
├── API/后端 → 检查服务端日志、请求/响应
├── 数据库 → 检查查询语句、schema、数据完整性
├── 构建工具 → 检查配置、依赖、环境
├── 外部服务 → 检查连通性、API 变更、限流
└── 测试本身 → 检查测试是否正确(可能是假阴性)
关键技巧:
console.log / print 在关键位置,看数据在哪个环节变了。回归 bug 用 git bisect——功能以前能跑现在不行了,用二分法定位引入问题的提交:
git bisect start
git bisect bad # 当前提交是坏的
git bisect good <已知正常的sha> # 这个提交是好的
git bisect run npm test -- --grep "failing test" # 自动二分
构造最小失败用例——去掉无关代码,只保留触发 bug 的最小代码;简化输入到最小能触发问题的值;把测试精简到只复现这一个问题。最小复现让根因一目了然。
症状:"用户列表显示重复"
修症状(错):
→ 在 UI 层去重:[...new Set(users)]
修根因(对):
→ API 的 JOIN 语句产生了重复数据
→ 修 SQL 查询,加 DISTINCT 或修数据模型
问:"为什么会这样?"直到找到真正原因,不是找到它在哪里表现出来的。
写一个测试,专门捕获这个 bug:
// 修复前会失败,修复后会通过
it('不显示重复的用户', async () => {
await createUser({ name: 'Alice' });
const users = await listUsers();
const aliceCount = users.filter(u => u.name === 'Alice').length;
expect(aliceCount).toBe(1);
});
按 shared/file-ops.md 的 P3 提取项目的测试/构建命令,逐条执行并记录结果——不要用 2>&1 || true 吞错(bash 专用,Windows 不兼容):
意图 1:跑修复时失败的那个测试,确认现在通过
- 用第二步提取的测试命令 + 对应过滤参数
我要拿到:该测试 PASS
意图 2:跑全量测试,检查修复有没有引入回归
- 用项目根的测试命令(npm test / pytest / make test)
我要拿到:全部 PASS(或列出新增的失败项)
意图 3:跑构建,确认项目仍可编译
- 用项目的构建命令(npm run build / python -m build)
我要拿到:构建成功
三项都通过 → 修复完成。任一项失败 → 回到第五步重新评估根因(注意"修复 A 引入 B"的回归)。
| 失败类型 | 典型表现 | 修复策略 |
|---|---|---|
| 语法错误 | SyntaxError, IndentationError | 直接修,看错误信息 |
| 类型错误 | TypeError, AttributeError | 检查变量来源和类型 |
| 逻辑错误 | 测试断言失败 | 检查实现逻辑是否符合预期 |
| 依赖错误 | ModuleNotFoundError, Cannot find module | 检查安装和导入路径 |
| 环境错误 | ECONNREFUSED, 权限错误 | 检查环境配置 |
代码改了之后测试挂了:
├── 改的是测试覆盖的代码?
│ └── 是 → 检查是代码有 bug 还是测试该更新
│ ├── 测试过时 → 更新测试
│ └── 代码有 bug → 修代码
├── 改的是不相关的代码?
│ └── 是 → 可能是副作用 → 检查共享状态、导入、全局变量
└── 测试本来就不稳定?
└── 检查时序问题、执行顺序依赖、外部依赖
构建失败:
├── 类型错误 → 读错误信息,检查引用位置的类型定义
├── 导入错误 → 检查模块是否存在、导出是否匹配、路径是否正确
├── 配置错误 → 检查构建配置文件的语法和 schema
├── 依赖错误 → 检查 package.json,重新 npm install
└── 环境错误 → 检查 Node 版本、操作系统兼容性
运行时报错:
├── TypeError: Cannot read property 'x' of undefined
│ └── 某个不该为空的值是 null/undefined
│ → 追溯数据流:这个值从哪来?哪一步变成了空?
├── 网络错误 / CORS
│ └── 检查 URL、请求头、服务端 CORS 配置
├── 渲染错误 / 白屏
│ └── 检查错误边界、控制台、组件树
└── 行为不符合预期(没有报错)
└── 在关键节点加日志,逐步验证每个环节的数据
诊断报告格式:
## 调试报告
**问题:** {验收标准描述}
**失败现象:** {错误输出}
**尝试的修复:**
1. {第 1 次尝试} → {结果}
2. {第 2 次尝试} → {结果}
**根因分析:** {你的判断}
**建议:** {下一步建议}
当 br-debug 被 /run、/br-bugfix 等上层 skill 调用时(而不是用户直接调用),必须返回结构化结果让上层能做决策。返回格式:
debug_result:
status: fixed | partial | unresolved
attempts: 2 # 实际尝试次数(最多 2)
failure_evidence: # status != fixed 时必填
- 验收标准: <文本>
实际输出: <文本>
错误位置: <文件:行号或函数名>
root_cause_guess: <文本> # status != fixed 时必填
next_steps: # status != fixed 时必填
- <可执行的下一步建议>
status 三种取值的含义:
fixed:第 1 或第 2 次尝试修好了,返回时附上修复 commit hash 和对应测试。partial:修了一部分但验收标准仍未完全通过(例如:复现失败的问题被缩小但未根除;外部依赖问题绕过了但不彻底)。unresolved:2 次重试耗尽,调用方需要决定是跳过、回退还是人工介入。为什么需要这个契约:/run 在重试 3 次后会把任务标记为 ⏭️ SKIPPED;/br-bugfix S3 在重试 2 次后需要向用户输出诊断报告。没有结构化返回,上层只能凭自然语言做决策,容易遗漏关键证据。这个契约让上层在处理失败时能拿到足够信息做判断。
与 state.json 的衔接(见 shared/state-schema.md):/run 在调用 br-debug 后,会把这个返回结果转写到对应任务的 failure 字段。映射关系:
| debug_result 字段 | state.json tasks[i].failure 字段 |
|---|---|
status: unresolved + attempts | reason: "debug_unresolved" + attempts |
failure_evidence[].实际输出 | evidence |
root_cause_guess | root_cause_guess |
| —(br-debug 自己写) | summary(一句话现象概括) |
next_steps | next_steps |
| 每次尝试的方向 | tried(数组) |
因此 br-debug 必须把"每次尝试了什么方向、结果如何"也返回(追加到 yaml 的 tried: [] 字段),否则 /br-status 的诊断会缺关键信息。完整返回格式:
debug_result:
status: fixed | partial | unresolved
attempts: 2
summary: <一句话现象概括> # 总是必填,让 /br-status 能显示
failure_evidence: # status != fixed 时必填
- 验收标准: <文本>
实际输出: <文本>
错误位置: <文件:行号或函数名>
root_cause_guess: <文本> # status != fixed 时必填
tried: # 每次尝试的方向(status != fixed 时必填)
- "第1次:<方向> → <结果>"
- "第2次:<方向> → <结果>"
next_steps: # status != fixed 时必填
- <可执行的下一步建议>
| 场景 | 处理方式 |
|---|---|
| 无法复现 | 记录条件,标记为"无法复现",不修改代码 |
| 问题在第三方库 | 记录为外部依赖问题,建议用户升级或换库 |
| 修复会影响其他任务 | 记录影响范围,让用户决定是否继续 |
| 调试超过 15 分钟 | 提示用户:"已花较长时间调试。建议先跳过这个任务,继续执行其他任务。" |
时间紧张时用安全降级,不要让整个功能崩掉:
// 安全默认值 + 警告(而不是直接崩溃)
function getConfig(key: string): string {
const value = process.env[key];
if (!value) {
console.warn(`缺少配置: ${key},使用默认值`);
return DEFAULTS[key] ?? '';
}
return value;
}
// 优雅降级(而不是功能全挂)
function renderChart(data: ChartData[]) {
if (data.length === 0) {
return <EmptyState message="当前时段没有数据" />;
}
try {
return <Chart data={data} />;
} catch (error) {
console.error('图表渲染失败:', error);
return <ErrorState message="图表暂时无法显示" />;
}
}
只在有帮助时加日志,调试完该删就删。
什么时候加日志:
什么时候删日志:
应该保留的监测:
遇到这些想法时,停下来想想:
| 你可能会想 | 实际情况 |
|---|---|
| "我知道 bug 在哪,直接修" | 你大概有 70% 概率猜对。剩下 30% 会浪费几个小时。先复现。 |
| "这个测试应该是写错了" | 先验证这个假设。如果测试确实过时了就修测试,不要直接跳过。 |
| "我这儿能跑" | 环境不一样。去 CI 跑一下,查配置、查依赖版本。 |
| "下个提交再修" | 现在修。下个提交会在有 bug 的基础上继续堆代码。 |
| "这是偶现的,忽略" | 偶现 bug 背后通常是真正的 bug。搞清楚为什么会偶现。 |
| "加个 try-catch 包一下就行" | 这是把问题藏起来。搞清楚为什么会抛异常,修掉根因。 |
出现以下情况说明调试流程出了问题:
BuildRail 小功能探索。通过快速确认意图 + 技术讨论, 把"我想加个功能"变成可执行的需求文档。 适用于:功能添加、功能修改、优化调整、bug 修复设计。 不要用于:新项目、大重构、架构决策(用 /br-office-hours)。
BuildRail 大方向探索。以产品经理的视角深入审问项目方向, 通过结构化追问挖出真实需求,产出设计文档。 适用于:新项目、大重构、架构决策、产品方向讨论。 不要用于:具体功能添加、bug 修复、小改动(用 /br-brainstorming)。
BuildRail 代码审查。合并前审查每个变更,覆盖五个维度。 适用于:合并前审查、功能完成后审查、重构代码审查。 不要用于:需求探索(用 /br-office-hours)、调试(用 /br-debug)。
BuildRail 范围挑战。在动手写计划之前,先审查设计文档的质量和可行性。 6 项检查:复用、最小变更集、复杂度、技术选型、完整性、Not Doing 一致性。 适用于:已有 APPROVED 设计文档,准备进入实现规划阶段。 不要用于:需求探索(用 /br-office-hours)、代码审查(用 /br-review)。
BuildRail 任务拆分。把设计文档拆成可执行的任务列表, 每个任务有明确的验收标准、涉及文件和依赖关系。 适用于:已有 APPROVED 设计文档(建议先跑 /br-scope-check)。 不要用于:需求探索(用 /br-office-hours)、范围审查(用 /br-scope-check)。
BuildRail 测试驱动开发。写代码前先写测试,用测试证明代码是对的。 适用于:实现新功能、修复 bug、修改现有行为。 不要用于:纯配置变更、文档更新、无行为影响的静态内容。