---
name: diagnose
description: 针对难以解决的错误和性能回归的有纪律的诊断循环。重现 → 最小化 → 假设 → 检测 → 修复 → 回归测试。当用户说"诊断这个"/"调试这个"、报告错误、说某东西坏了/抛出异常/失败、或描述性能回归时使用。
---

# 诊断

针对难以解决的错误的方法论。只有在明确有理由时才跳过阶段。

探索代码库时，使用项目的领域词汇表来建立相关模块的清晰心智模型，并检查你所触及区域的 ADR。

## 阶段 1 — 构建反馈回路

**这是核心技能。** 其他都是机械性的。如果你有一个快速、确定性的、可由代理运行的针对该错误的是/否信号，你就能找到原因——二分搜索、假设测试和检测都只是在消费这个信号。如果你没有这个信号，再看多少代码也没用。

把不成比例的努力花在这里。**要激进。要创新。拒绝放弃。**

### 构建反馈回路的方法——大致按此顺序尝试

1. **失败的测试**——在能触及 bug 的任何接缝处——单元测试、集成测试、端到端测试。
2. **Curl / HTTP 脚本**——针对正在运行的开发服务器。
3. **CLI 调用**——使用预置输入，将标准输出与已知良好的快照进行对比。
4. **无头浏览器脚本**（Playwright / Puppeteer）——驱动 UI，在 DOM/控制台/网络上进行断言。
5. **重放捕获的跟踪。** 将真实的网络请求/负载/事件日志保存到磁盘；在隔离环境中通过代码路径重放。
6. **一次性测试工具。** 搭建系统的最小子集（一个服务，模拟依赖），通过单个函数调用执行 bug 代码路径。
7. **属性/模糊测试循环。** 如果 bug 是"有时输出错误"，运行 1000 个随机输入并寻找故障模式。
8. **二分搜索工具。** 如果 bug 出现在两个已知状态之间（提交、数据集、版本），自动化"在状态 X 启动，检查，重复"，以便你可以运行 `git bisect run`。
9. **差分循环。** 将相同的输入通过旧版本 vs 新版本（或两种配置）运行，并对比输出。
10. **人机协作 bash 脚本。** 最后手段。如果必须有人点击，用 `scripts/hitl-loop.template.sh` 来驱动**他们**，使循环仍然有结构。捕获的输出反馈给你。

构建正确的反馈回路，bug 就解决了 90%。

### 迭代循环本身

把循环当作产品来对待。一旦你有了一个循环，问：

- 能更快吗？（缓存设置、跳过无关初始化、缩小测试范围。）
- 信号能更明确吗？（针对特定症状断言，而不是"没有崩溃"。）
- 能更确定吗？（固定时间、种子化 RNG、隔离文件系统、冻结网络。）

一个 30 秒的不稳定循环比没有循环好不了多少。一个 2 秒的确定性循环是调试超能力。

### 非确定性错误

目标不是干净的复现，而是**更高的复现率**。循环触发器 100 次、并行化、增加压力、缩小时间窗口、注入延迟。50% 飘忽的 bug 是可调试的；1% 不是——不断提高比率直到可调试为止。

### 当你真的无法构建循环时

停下来并明确说明。列出你尝试过的方法。向用户请求：(a) 访问复现所需的环境、(b) 捕获的制品（HAR 文件、日志转储、核心转储、带时间戳的屏幕录制），或 (c) 添加临时生产检测的权限。**没有循环就不要继续到假设阶段。**

在拥有你相信的循环之前，不要进入阶段 2。

## 阶段 2 — 重现

运行循环。观察 bug 出现。

确认：

- [ ] 循环产生**用户**描述的故障模式——而不是碰巧附近的另一个不同故障。错误的 bug = 错误的修复。
- [ ] 故障在多次运行中是可重现的（或者对于非确定性 bug，在足够高的比率下可重现以便调试）。
- [ ] 你已经捕获了确切的症状（错误消息、错误输出、缓慢的时间），以便后续阶段可以验证修复确实解决了它。

在重现 bug 之前不要继续。

## 阶段 3 — 假设

在测试任何一个假设之前，生成 **3-5 个排序的假设**。单一假设生成会锚定在第一个看似合理的想法上。

每个假设必须是**可证伪的**：陈述它所做的预测。

> 格式："如果 <X> 是原因，那么 <改变 Y> 将使 bug 消失 / <改变 Z> 将使 bug 更严重。"

如果你无法陈述预测，那这个假设只是一种感觉——放弃它或让它更精确。

**在测试之前向用户展示排序列表。** 他们通常有领域知识可以立即重新排序（"我们刚部署了 #3 的更改"），或者知道他们已经排除的假设。廉价检查点，节省大量时间。如果用户不在，不要阻塞——按你的排序继续。

## 阶段 4 — 检测

每个探测必须映射到阶段 3 的具体预测。**一次只改变一个变量。**

工具偏好：

1. **调试器 / REPL 检查**——如果环境支持。一个断点胜过十个日志。
2. **针对性日志**——在能够区分假设的边界处。
3. 绝不"记录一切然后 grep"。

**给每个调试日志打上唯一前缀标签**，例如 `[DEBUG-a4f2]`。最后清理时一次 grep 搞定。未标记的日志保留；标记的日志删除。

**性能分支。** 对于性能回归，日志通常是错误的。改为：建立基线测量（时间测试工具、`performance.now()`、性能分析器、查询计划），然后二分搜索。先测量，后修复。

## 阶段 5 — 修复 + 回归测试

**在修复之前**编写回归测试——但只有在有一个**正确的接缝**时才这样做。

一个正确的接缝是测试可以像在调用点一样执行**真实 bug 模式**。如果唯一可用的接缝太浅（单个调用者的测试当 bug 需要多个调用者时，无法复现触发 bug 的链的单元测试），在那里的回归测试会给出虚假的信心。

**如果没有正确的接缝，这本身就是发现。** 记下它。代码库架构阻止了将该 bug 锁定。将其标记出来供下一个阶段使用。

如果存在正确的接缝：

1. 将最小化的复现转化为该接缝处的失败测试。
2. 观察它失败。
3. 应用修复。
4. 观察它通过。
5. 针对原始（未最小化的）场景重新运行阶段 1 的反馈回路。

## 阶段 6 — 清理 + 事后分析

在宣布完成之前必须完成：

- [ ] 原始复现不再复现（重新运行阶段 1 的循环）
- [ ] 回归测试通过（或接缝缺失已记录）
- [ ] 所有 `[DEBUG-...]` 检测已移除（grep 前缀）
- [ ] 一次性原型已删除（或移到明确标记的调试位置）
- [ ] 正确的假设在提交/PR 消息中说明——以便下一个调试者学习

**然后问：什么本来可以防止这个 bug？** 如果答案涉及架构变更（没有好的测试接缝、调用者纠缠、隐藏耦合），将具体内容交给 `/improve-codebase-architecture` 技能。在修复**之后**提出建议，而不是之前——你现在拥有的信息比开始时更多。
