# 系统化调试协议：四阶段 + 边界埋点

> 用途：遇到任何 bug / 测试失败 / 非预期行为 / 性能问题 / 构建失败时，动手修之前走这套。
> 铁律：`NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST`。Phase 1 没完成就禁止提任何修复。
> 核心：症状修复 = 失败。永远先定位根因再动手。
> 出处对标 obra/superpowers systematic-debugging（已 curl 200 核验），本文为科研/数据/模型场景加强并补可复制埋点模板。

## 何时用（越想跳过越要用）

ANY 技术问题都用。**尤其**当：时间压力大（紧急时最想瞎猜）、"就一个小修复"看起来很显然、你已经试过多次修复、上次修复没生效、你并不完全理解这个问题。
**别因为这些跳过**：问题看起来简单（简单 bug 也有根因）、你在赶时间（赶 = 返工）、有人催"现在就修"（系统化比瞎试快）。

## Phase 1：根因调查（修复前必做，五步缺一不可）

1. **逐字读错误信息**：不跳过 error/warning，它们常含确切答案。读完整栈，记下行号、文件路径、错误码。
2. **稳定复现**：能可靠触发吗？确切步骤？每次都发生吗？不能复现 → 采集更多数据，**不要猜**。
3. **查最近改动**：什么变了？`git diff`、最近提交、新依赖、配置/环境差异。
4. **多组件系统在每个边界加诊断埋点**（见下方模板）：在每个组件边界记录入参/出参、校验环境/配置传播、检查每层状态。**先跑一次拿到证据看是哪层断的**，再去查那个具体组件。
5. **反向追踪数据流**：坏值从哪产生？谁用坏值调用了它？一路向上追到源头，**在源头修而非症状处修**。

## Phase 2：模式分析（修复前先找到模式）

1. 在同一代码库找**可工作的相似实现**。
2. 若在套用某模式，**完整读参考实现**——不跳读，每行都读。
3. 列出 working 与 broken 的**每一处差异**，再小也列，别假设"这个不可能有影响"。
4. 理解依赖：它还需要什么组件/配置/环境/假设。

## Phase 3：假设与验证（科学方法）

1. **提单一假设**，写下来："我认为 X 是根因，因为 Y。" 要具体不要含糊。
2. **最小验证**：做能验证该假设的最小改动，一次只动一个变量，不一次修多处。
3. **验证后再前进**：成 → Phase 4；不成 → 提**新**假设，**别在旧修复上叠新修复**。
4. **不懂就说不懂**：说"我不理解 X"，别假装懂，去研究或求助。

## Phase 4：实施（修根因不修症状）

1. **先写复现该 bug 的失败测试**（用 TDD；无框架就写一次性脚本）——修之前必须有。
2. **单一修复**：针对已确认的根因，一次一处改动，无"顺手"改进、无捆绑重构。
3. **验证**：测试现在过了吗？没弄坏别的测试吗？问题真解决了吗？
4. **没修好就停**：数一下试了几次。<3 次 → 回 Phase 1 带新信息重析；**≥3 次 → 停手质疑架构**，不许第 4 次盲修。
5. **3+ 次失败 = 架构问题**（不是失败的假设）：每次修复都在不同地方牵出新的共享状态/耦合、每次修都要"大重构"、每次修在别处造出新症状 → 停下追问根本设计："这个模式本身成立吗？我们是不是纯靠惯性在硬撑？该重构架构还是继续修症状？" 与人讨论后再动。

## 红旗信号（出现即回 Phase 1）

"先快修，回头再查" / "随便改个 X 看看" / "一次多改几处再跑测试" / "跳过测试我手动验" / "八成是 X，先修了" / "我不完全懂但这样也许能行" / "模式说 X 但我换个方式套" / 还没追数据流就在提解决方案 / "再试最后一次"（已试 2+ 次）/ 每次修复都在不同地方冒新问题。

## 合理化借口 → 反驳

| 借口 | 现实 |
|---|---|
| "问题简单不用走流程" | 简单问题也有根因，流程对简单 bug 很快。 |
| "紧急没时间走流程" | 系统化比瞎试-检查的反复折腾快。 |
| "先试这个再调查" | 第一次修复定下基调，从一开始就做对。 |
| "修好后再补测试" | 没测试的修复留不住，先写测试才证明它修对了。 |
| "一次多修几处省时间" | 没法隔离哪个起了作用，还会引入新 bug。 |
| "参考太长，我适配一下模式" | 部分理解必然出 bug，完整读。 |
| "我看到问题了，直接修" | 看到症状 ≠ 理解根因。 |
| "再试最后一次"（2+ 次后） | 3+ 次失败 = 架构问题，质疑模式别再盲修。 |

## 科研场景特有根因热点（本地加强）

- **数值/形状**：先打印 `shape`/`dtype`/`device`/有无 NaN-Inf（`np.isnan().any()`/`torch.isfinite().all()`），多半坏在边界处的广播或类型提升。
- **随机性**：复现不了先固定所有种子（python `random`、`numpy`、`torch`、`PYTHONHASHSEED`、DataLoader `worker_init_fn`、cudnn deterministic），把"随机失败"变成"确定失败"再查。
- **数据泄漏/划分**：指标好得反常 → 查 train/test 是否在标准化或特征工程前就划分，源头修（见 Phase 1.5 反向追踪）。
- **环境差异**：本机过 CI 挂 → 对比 Python/库版本、`uv.lock`、OS 路径分隔符、locale/编码（Windows GBK）。
- **设备/精度**：CPU 过 GPU 挂或 fp32 过 fp16 挂 → 边界埋点记录每层的 dtype/device。

## 边界诊断埋点模板（复制即用）

可运行脚本见同目录 `../assets/project-scaffold/scripts/debug_instrument.sh`（多组件边界证据采集）与 `boundary_trace.py`（数据流边界打点装饰器，含合成自测）。原则：**先一次跑出"哪层断"的证据，再聚焦那一层**，而不是在最外层反复盲改。
