Diagnose and Fix Skill
可由 agent 按场景自动触发,也可手动调用:
@diagnose-and-fix "<问题描述>"
@diagnose-and-fix <问题列表文件> <编号>
@diagnose-and-fix # 进入交互模式,引导用户描述问题
设计理念
本 skill 强制执行"理解优先于修改"原则:
- Phase 1 — 诊断:系统性探索项目,验证问题,产出诊断报告
- Phase 2 — 确认:用户审阅报告,选择修复方案
- Phase 3 — 修复:按确认方案执行修改、测试、验证、提交
诊断不充分时,Phase 2 会阻断流程,避免基于错误假设盲目修改。
Phase 1 — 系统性诊断
Step 1.1 — 问题接收与澄清
解析调用参数,提取问题描述。若信息不足,向用户询问以下几项(知道的填,不确定的跳过):问题现象、预期行为、复现步骤、错误信息原文、已知相关文件/函数。
Step 1.2 — 项目结构侦察
在读任何具体文件前,先建立项目全貌——用你的文件检索工具(Glob/Grep/Read,别逐个裸 cat),快速摸清三件事:
- 语言与框架:看依赖清单(
go.mod/package.json/Cargo.toml/pubspec.yaml/pom.xml/requirements.txt等) - 架构与核心目录:入口在哪、代码主干如何组织
- 测试框架:有没有、是什么
目的是为后续定位画一张地图,不是把整棵目录树读进上下文。仓库较大时可派 Explore subagent 并行侦察、只取结论。
用一两句话汇报侦察结果:语言/框架、架构模式、核心目录、测试框架(未检测到就注明)。
Step 1.3 — 问题定位与代码追踪
围绕问题描述检索并阅读相关代码。目标是找到根因,而不是停在第一个看起来可疑的地方——所以下面几个角度都要覆盖到,但顺序按实际线索走,不必教条:
- 关键词检索:用错误信息、函数名、变量名定位入口(用 Grep 工具按文件类型过滤,别用裸
grep) - 调用链:从入口向下追到问题点,记录每一跳的文件:行号
- 数据流:追踪数据从输入到输出的转换路径,标出可疑的转换点
- 边界与异常路径:空值、错误传播、并发、资源释放、状态一致性——bug 常藏在这里
问题涉及外部 API / 协议 / SDK 时(对接第三方调不通、签名或鉴权失败、返回与文档不符),追踪对象从仓内代码扩到外部契约:先找到官方文档或可运行的参考实现(官方 SDK 源码、GitHub demo),把 headers、body、参数生成逻辑与工具函数实现逐字段比对——「看起来差不多」不算比对过。禁止无证据归因:「可能是 X」必须先验证再写进报告。若同一假设在没有新信息的情况下已重复试错两轮,停止同方向试错,改走参考实现比对或与可工作版本的 diff。
把所有发现记下来,为 Step 1.4 验证和 Step 1.5 报告提供原始素材。
Step 1.4 — 问题验证
验证问题是否真实存在,避免基于错误假设修复:
| 验证项 | 结论 |
|---|---|
| 代码中是否存在描述的行为? | ✅ 确认 / ❌ 不存在 / ⚠️ 部分存在 |
| 问题是否可被当前代码路径触发? | ✅ 可触发 / ❌ 条件不满足 / ⚠️ 概率性 |
| 问题描述是否与代码实际行为一致? | ✅ 一致 / ❌ 描述有误 / ⚠️ 需更多信息 |
两种提前收束(均不修改代码,对应末尾「终态」):
- 问题不存在 / 描述与代码对不上:告知用户实际观察到的行为,询问重新描述还是跳过。用户确认跳过 → 终态
skipped_inconsistent。 - 问题已被缓解 / 无需修复:说明现状,确认后——若输入为问题列表则按 Step 3.6 标记(
fix_solution: no-op: <原因>、commit: none)——终态skipped_noop。
否则继续 Step 1.5。
Step 1.5 — 诊断报告生成
修复方案:生成与推荐规则
报告里的每个方案都是可执行的推荐,不是草图——给出具体改法、落点文件/函数。生成与推荐时守住下面这条线:
- 推荐标准:选「最标准、最佳实践、且匹配本项目规模」的方案,不是最复杂的。忽略实现工时,只权衡正确性与运维契合度;推荐方案要对得上【根本原因】分类,治根因而非补症状。
- 右尺寸:不引入超出问题所需的抽象、可配置性、分层;也不用「就地打补丁」掩盖根因。能 50 行解决就别写 200 行。
- 遵循项目约定:若仓库有
conventions/或既成模式,优先选贴合它们的方案;若启用了code-conventionsskill,加载它作为规范来源。 - 方案数量按需:
- 根因清晰、最佳解唯一 → 只给一个推荐方案,附一句「为何不考虑其它思路」。
- 存在 2–3 个各有取舍、且代码本身无法裁决的合理方案 → 才并列,每个配一句 trade-off,并对推荐项标
[推荐],在【方案比较】里说明核心取舍。
- quick-fix 直通:当修复无歧义、纯机械、无需任何设计讨论(如错误日志级别、缺失 nil/error 检查、硬编码值提取为配置、配置键拼写、死导入)→ 在方案名后标
[quick-fix],可省去多方案对比,直接进确认。任何需要设计取舍的改动都不是 quick-fix。
输出完整的结构化诊断报告:
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
📋 诊断报告
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
【问题摘要】
描述:<一句话概括问题>
严重性:P0 崩溃/数据损坏 | P1 功能失效 | P2 行为异常 | P3 体验问题
验证状态:✅ 已确认 | ⚠️ 部分确认 | ❌ 未确认
【代码分析】
问题位置:
- <文件路径>(第N行,FunctionName)
- <文件路径>(第N行,FunctionName) ← 若涉及多处
调用链:
<入口> → <中间层> → <问题点>
相关代码片段:
```<语言>
// <文件>:<行号>
<关键代码,精简,仅展示问题相关部分>
```
【根本原因】
<清晰、具体的一到三段描述>
原因分类:逻辑错误 | 竞态条件 | 资源泄漏 | 配置错误 | 接口契约违反 | 缺失校验 | 其他
【影响范围】
直接影响:<受影响的功能/模块>
潜在影响:<可能的连带问题>
数据风险:<是否有数据一致性风险>
【修复建议】 (方案数量按上方规则;机械修复在名称后标 [quick-fix])
方案 A — <名称> [推荐]
做法:<具体改法,含落点文件/函数>
优点:<为什么好(对得上根本原因)>
缺点/风险:<需要注意>
改动范围:<预计修改哪些文件>
改动量:小(<20行)| 中(20-100行)| 大(>100行)
方案 B — <名称> ← 仅多方案时
做法:<具体改法>
优点:<为什么好>
缺点/风险:<需要注意>
改动范围:<预计修改哪些文件>
改动量:小 | 中 | 大
【方案比较】
单方案:一句话说明为何不考虑其它思路。
多方案:说明为什么推荐 A 而非 B,核心取舍是什么。
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
请选择:
A) 采用方案 A 进行修复
B) 采用方案 B 进行修复 ← 仅多方案时列出
C) 我有其他想法:___
D) 报告有误,我想补充信息
E) 仅需报告,暂不修复
Phase 2 — 用户确认
等待用户回复,按选择处理:
- A / B:进入 Phase 3,执行对应方案
- C(自定义):用户说明想法后,评估可行性,给出简短分析,再次确认后进入 Phase 3
- D(补充信息):接收补充,回到 Step 1.3 重新分析,更新报告后再次等待确认
- E(仅报告):输出
📄 诊断完成,未执行修复后结束(终态user_declined)。不写问题列表(如需手动改状态,见末尾「终态」说明)
Phase 3 — 修复执行
Step 3.1 — 修改代码
按确认方案最小范围修改,保持原代码风格。追踪所有改动文件和行号。
改完后简述:每个文件改了什么、总增删行数。
Step 3.2 — 测试
复用 Step 1.2 侦察到的测试框架,不从零重新探测。命令优先级:项目自定义入口(Makefile test 目标 / package.json scripts.test)→ 该语言的约定测试命令 → 均无则询问用户。
优先只跑覆盖本次改动的测试(按包/目录/文件缩小范围)以快速反馈;通过后再视耗时跑一次相关全量,确认无回归。
测试失败时:展示错误摘要,说明准备如何调整,询问确认后回到 Step 3.1 重试,最多 3 次。超限则告知用户,停止流程,不提交,不标记(终态 skipped_test_failed)。
输出:🧪 测试通过(N 用例) 或 ❌ 测试失败:<摘要>
Step 3.3 — 格式化
沿用 Step 1.2 的语言/工具链判定,只对本次修改的文件执行。优先用项目自带的格式化入口(Makefile fmt/lint 目标、package.json scripts.format/scripts.lint、仓库内 formatter 配置如 .prettierrc/rustfmt.toml/.golangci.yml),无则回退该语言的默认格式化工具。
未找到工具时跳过并提示,不阻塞流程。
输出:🎨 格式化完成 或 ⚠️ 未找到格式化工具,已跳过
Step 3.4 — 修复验证与代码 review
两道独立把关,都通过才进提交。
(1) 修复验证 — 独立 subagent
与 Step 1.4「验证问题存在」对称:修复后必须独立确认「问题已消除」,而不是默认改完就好。派一个验证 subagent(Explore 或 general-purpose),只喂:原问题描述、Step 1.4 的三项结论、本次改动文件:行号。让它不预设修复成功,对照 Step 1.4 逐条复核,重点落在根因/复现路径:
| 验证项 | 修复前(Step 1.4) | 期望修复后 |
|---|---|---|
| 描述的行为是否仍存在? | ✅ 确认 | ❌ 已消除 |
| 问题路径是否仍可触发? | ✅ 可触发 | ❌ 不可触发 |
| 根因是否已被消除? | — | ✅ 已消除 / ⚠️ 部分 / ❌ 未消除 |
subagent 只回结论、不改代码。判定「未消除 / 部分消除」即视为修复未达标 → 回 Step 3.1 重改(与 Step 3.2 测试共享同一 3 次重试上限,超限则停止、不提交、不标记,终态 skipped_test_failed)。
(2) 代码 review — 引用现有 skill
若启用了 code-review 或 requesting-code-review skill,调用它审查本次 diff,重点:是否真正治根因(对得上诊断报告)、有无引入回归、是否符合选定方案与项目约定。未启用则跳过并提示。
- review 报出的 P0/P1 先处理(回 Step 3.1,计入同一重试预算)再继续;
- P2/P3 记入完成摘要,交用户决定是否本次一并处理。
输出:🔍 修复验证:问题已消除 + 🧐 review:N 项(P0:x P1:x P2:x P3:x) 或对应失败/跳过摘要。
Step 3.5 — 提交
修复方案已在 Phase 2 确认,此处直接提交,无需再次询问。
提交前先看 git log 近若干条提交,沿用项目既有的 commit 风格(type 集合、scope 命名、语言);项目无明显约定时按下方缺省规范。
再执行 git diff --name-only HEAD 核查改动文件:仅当混入与本次修复无关的改动时才停下告知用户由其决定;否则只 add 本次修改的文件后直接提交:
git add <仅本次修改的文件>
git commit -m "<subject>" -m "<body>" -m "<footer>"
Commit 消息规范(缺省):
<type>(<scope>): <subject> ← 动词原形开头,≤72字符,不加句号
[body] ← 解释"为什么",引用诊断报告根本原因,可选
Fixes: <问题描述一句话>
- type:
fix(bug)/refactor(重构)/perf(性能)/security(安全)/feat;项目另有惯用 type 则从其惯例。 - scope:取自本次改动所在的模块/包/目录,与项目既有 scope 命名保持一致。
输出:🎉 <hash7> — <subject>
Step 3.6 — 标记问题列表(仅当输入为问题列表文件时)
若本次输入是问题列表文件 + 编号,修复完成后把对应条目标记为已修复——但不提交这次变更。这是有意为之:本 skill 聚焦单个问题的诊断与修复,问题列表的提交时机交给你统一掌控(要批量循环处理整个问题列表,用 diagnose-and-fix-batch skill)。
按原文件格式写入:
| 字段 | 值 |
|---|---|
| status | resolved |
| resolved_at | YYYY-MM-DD |
| fix_solution | 已选方案名 + 一句描述 |
| commit | 代码提交的 <hash7>,未提交则填 uncommitted |
写入格式:
- Markdown → 追加
### ✅ 修复记录小节 - JSON → 合并字段到对应对象
- YAML → 追加字段到对应条目
- 纯文本 → 追加
[resolved DATE | 方案 | commit: HASH]
文件只读时,输出待写内容供你手动应用。完成后提示:📝 已标记 #<编号> resolved(未提交,提交时机由你决定)
完成摘要
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
🏁 诊断与修复完成
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
问题:<一句话>
严重性:P<N>
根本原因:<一句话>
采用方案:<方案名>
改动文件:<N> 个
测试:通过(N 用例)
修复验证:问题已消除
代码 review:N 项(P0:x P1:x P2:x P3:x) 或 未启用
提交:<hash7> 或 未提交
问题列表:已标记 resolved(未提交) 或 不适用
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
终态
本 skill 处理单个问题的最终结果,归为下列其一。被 diagnose-and-fix-batch 编排时,subagent 据此映射上报;独立使用时仅作为收束说明。只有 completed 与 skipped_noop 会写问题列表(若输入为问题列表),其余跳过态不动列表,并提示用户可手动把状态设为 skipped。
| 终态 | 触发点 | 含义 |
|---|---|---|
completed |
Step 3.5/3.6 | 修复完成;代码已提交(git 未初始化等情况则未提交),问题列表已标记 resolved |
skipped_inconsistent |
Step 1.4 | 问题不存在 / 描述与代码对不上,用户确认跳过 |
skipped_noop |
Step 1.4 | 问题已缓解 / 无需修复,用户确认(列表标记 no-op) |
user_declined |
Phase 2-E | 用户选「仅报告,暂不修复」 |
skipped_test_failed |
Step 3.2/3.4 | 测试或修复后验证重试超 3 次,停止、不提交、不标记 |
错误处理
各 Step 内已说明的失败处理(验证不存在、测试/验证超限、review 分级)不再重复,此处只列横切与环境类异常:
| 情况 | 处理 |
|---|---|
| 问题描述过于模糊 | 进入交互模式,引导用户补充 |
| 涉及多个独立问题 | 拆分为子问题,分别给出修复建议,询问优先修复哪个 |
| git 未初始化 | 跳过提交,仍完成修改、测试、验证 |
| 工作区有无关改动 | 提交前告知用户,由其决定是否 stash |
| 代码 / 问题列表文件只读 | 输出待写内容供手动应用,不直接写入 |