# Review Loop

> 提交前的自动 review 迭代环——委派独立 context 的子 agent 编队 review、修、跑验证、复审，迭代到「运行验证通过 + 无高置信 correctness 问题」才放行；2 轮不收敛留痕放行，全程无人在环。由 /commit 自动调用，也可手动跑

- Skill: `pkulijing/review-loop` (Agent Skill, multi-file: 3 files)
- Install (CLI): `npx skillmds@latest add pkulijing/review-loop`
- Raw SKILL.md: https://api.skillmd.com/api/skills/pkulijing/review-loop/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: AI & ML
- Author: pkulijing (https://skillmd.com/u/pkulijing)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/pkulijing/review-loop

---


对**当前工作树的改动**跑一轮自动 review 迭代，直到 clean 才放行提交。本 skill 是 review 机制的**单一真源** —— 宪法与 `/commit` 只留触发点与一句判据，细节都在这里。

## 为什么存在（结论）

写代码的 context 自带先入之见：同一个对话里自审，reviewer「知道」代码想干什么，最容易漏掉的恰是「实际写的和想的不一样」。本 skill 把「**独立 context** review → 修 → 验证 → 复审 → 迭代到干净」固化成 commit 前的自动环，全程无人在环。

**收敛靠「运行验证 + 高置信过滤」，不是靠 reviewer 挑不出为止。**

**已知局限（诚实声明）**：

- **写代码一侧的盲区**：reviewer 与写这段 diff 的同为 Claude 模型家族，属**同模型自审**，对并发 / 多线程 / 难复现改动有已知盲区；独立的是 **context** 而非模型，升重档只是缓解、不等于消除。需要跨模型第二意见时人工手动引入。
- **review 一侧的盲区（同源，方向相反）**：**reviewer 自己也会做选择性论证** —— 给出真实数据、真实计算，但**只报支持其结论的那一半**，从而把一个正确的结论「推翻」。实证：一条置信 82 的 finding 主张某处归因站不住，附了带实测数字的反例（换个坐标系前提后偏差只剩 +11.5 px），复算才发现它只报了 Δcy 没报 Δcx —— 偏差没有消失，只是从一个分量转移到了另一个；而一个已被独立证据确证自洽的对照样本在它的前提下反而崩掉，**被证伪的是前提，不是样本**。
  这比 reviewer 挑刺更危险：挑刺会被置信闸滤掉，而**一个带实测数字的反例天然高置信**，很容易被直接采纳，把正确结论改错。对策是 6.1 的「推翻类 finding 必须自己复算」那条纪律。

> ⚠ **要改本 skill 的收敛判据 / 档位表 / 降级链门槛 / 「明确不做」清单之前，先读 `references/rationale.md`**（CC 端绝对路径 `$HOME/.claude/skills/review-loop/references/rationale.md`）—— 下面每条规则对应哪次翻车、为什么不能再松一格（病根清单、为什么不依赖 CC 内置 `/code-review`、同模型自审的硬实证），全在那里。不读就改，等于把防线连同它的来历一起改掉。日常跑一轮 loop 不必读。

## 收敛判据（三要素并闸）

一轮收敛**当且仅当**三者同时成立。判据是「基础功能没坏 + 有没有高置信真会出错的问题」，不是 P 级数字、更不是「reviewer 还能不能再挑一个」。

| 闸 | 内容 | 备注 |
| --- | --- | --- |
| **A · 运行验证** | 受影响测试全绿 + happy-path 主流程跑通 | **硬前置，排在 B 之前**；纯文档 / 指令规则文件判 N/A（见 6.3） |
| **B · 无高置信 correctness finding** | 无「附 `file:line` 证据 + 高置信真会在生产触发」的正确性 / 逻辑 / 安全问题 | 含被标 P2 的；低置信 / 无证据 / pre-existing / pedantic / linter 域一律不阻断 |
| **C · 已定前提未被重复质疑** | reviewer 质疑一个人类已拍板的决策不算 bug | 把该前提补进下轮 prompt 继续；**不要为了让它闭嘴而推翻人类已定的决策** |

必须是**循环**而非单次：修复本身可能引入新问题，首轮 review 也未必看全。

## Step 1 · 确认有变更

`git status`。干净无变更 → 打印「无待 review 变更」退出（clean）。

review 的对象是**整个工作树的全部改动** —— `/commit` 调本 skill 时还没人分批 `git add`，故不区分 staged / unstaged。

## Step 2 · 琐碎改动跳过判定

**只有真正的用户文档 / 纯机械改动**才自动跳过：仅改 `docs/` 下文件或 README 的非流程说明段、仅改代码注释 / docstring、单行或极小的机械 fix（笔误、格式、排版）。

**这些绝不自动跳过**（哪怕是纯文字）：

- **指令 / 规则文件** —— `skills/*.md`、`GLOBAL_AGENTS.md`、`playbooks/*.md`、`agents/*.md` 与 `.claude/` 下的 agent 配置。它们**就是开发流程与安全边界本身**，跳过 review 等于让门禁在修改自身时失效。`agents/*.md` 尤其如此：改一个 `model` 或 `effort` 就改了整道门禁的强度。
- **配置变更** —— CI 权限、部署目标、认证 / CORS、依赖版本、构建 / 运行时开关，一行就可能改变安全态或线上行为。

**有疑则不跳。**

## Step 3 · 选档

### 三条成本硬规则

1. **成本四维，各有各的钉死处**：成本 = **数量 × 模型 × 思考档 × 范围**。
   - **模型与思考档**由 `agents/*.md` 的 frontmatter 钉死（`model` + `effort`），**不继承主会话** —— 主会话可以照常跑 `xhigh`，编队不跟。
   - **数量**由下面的档位表钉死。
   - **范围**由委派 prompt 钉死：「只审本次 diff 及其接壤代码（调用点、被调方、紧邻上下文），禁止全库扫描」。
2. **永远在独立 context 的子 agent 里跑**（唯一例外是 Step 5 的降级）。独立 context 是本机制的首要属性；且主会话直跑会把整轮文件阅读永久写进主对话历史、之后每轮重发。实测：两轮 review 在子 agent 内烧 ~32 万 token，主会话只增加了两份 finding 列表。
3. **编队只有两档，不自行加码**：不多起 reviewer、不追加角度、**不传 `model` 入参去盖 agent 定义**。要调编队规格就改 `agents/*.md`（那是单一真源），不要在委派时临时加码。

| 档 | 编队（`subagent_type` × 数量 → 角度） | 触发 |
| --- | --- | --- |
| **默认** | `review-orchestrator` ×1；`code-reviewer` ×3 → 角度 ①②③ | 一切需要 review 的改动 |
| **重** | `review-orchestrator` ×1；`code-reviewer` ×4 → 角度 ①②③④；`code-reviewer-deep` ×1 → 角度 ⑤ | 命中下列任一复杂特征 |

档位之间的差别是**角度数 + 深审模型**，**不是思考深度** —— 三个类型统一跑 `medium`，档位表里看不到 effort，是因为它已经被 agent 定义钉死了。

**升重档的复杂特征**（正是「审浅了会漏真 bug」、也是同模型自审盲区最大的地方）：并发 / 多线程 / 异步生命周期（锁、跨线程队列、`join` / `cancel` / 优雅停、事件循环迁移）；跨进程 / 网络 / 容错（重试 / 幂等 / 部分失败 / 回滚 / 超时 / 降级）；状态机 / 竞态（排序假设、陈旧状态、重入、资源开关配对）；难以用测试复现，或横跨 3+ 模块的编排装配。**拿不准偏向升重档** —— 漏判一个并发 diff 的代价大于多花两个 reviewer。

**没有更轻档，也不再往下调思考档**：真正琐碎的已在 Step 2 跳过；没跳过的（配置、指令规则文件）每行都重。**角度数是检出率的主驱动，不能砍**；`medium` 已是编码场景的底。要动这一条先读 `references/rationale.md` §4。

## Step 4 · 委派独立 review orchestrator

起 **1 个 orchestrator 子 agent**（`subagent_type: review-orchestrator`），同步等它返回一份 finding 列表。**按你环境里 Agent 工具的实际 schema 填参** —— 该 schema 随 CC 版本漂移，别照抄记忆里的字段清单。

**走本档编队时不要传 `model` 入参。** 模型解析顺序是「环境变量 > 单次调用的 `model` 参数 > agent 定义的 frontmatter」—— 传了就会盖掉定义里钉死的那个（`code-reviewer-deep` 会被从 `opus` 打回去）。思考档没有单次调用入参，只认 frontmatter，所以**编队档位的唯一真源是 `agents/*.md`**。**唯一例外是 Step 5 的第 ② 档** —— 那里用的通用类型没有 frontmatter 可继承，`model` 反而必须传。

> **本段是 CC 端路径。** Codex 端没有 Agent 工具、也没有 `agents/` 这个概念（`install.sh` 只把 `agents/` 链到 CC 端），故在 Codex 上本步必然走 Step 5 的降级链，**且直接落到第 ③ 档**（没有 Agent 工具，第 ② 档同样起不来）—— 那是能力缺失，属正当降级，照 Step 5 留痕。

**先钉死工作目录**（这条排在任务书之前，因为它决定了后面六条审的是不是同一棵树）：主会话自己跑 `git rev-parse --show-toplevel` 取**绝对路径**，写进委派 prompt，并要求整个编队**一切操作都锚定这个根**（`git -C <根>` + 绝对路径读文件）、把它**原样**逐层转给每个 reviewer。

> **压缩版理由**：不传根，reviewer 会在主 checkout 上审**另一个分支的改动**并报 clean，**失败完全静默**；agent 线程的 cwd 每次 bash 调用都会重置，故 `cd` 只会把这个静默失败换个更隐蔽的形态放回来。**这两条的完整推导（含实测）见 `references/rationale.md` §5 —— 想放宽这条约束前必须先读它。**

**orchestrator 任务书**（六条缺一不可）：

1. **对象与范围**：用 `git -C <根> status` / `git -C <根> diff` 拿全部改动；只审 diff 及其接壤代码，禁止全库扫描。
2. **编队**：按档位表并行起 reviewer 子 agent，**每份委派 prompt 都带上那个仓库根**；各自独立审、互不通信，各返回 finding 列表（`file:line` + 严重度 + 理由 + 证据）。**起不了子 agent 时**自己按同一角度清单逐角度顺序审，并在结果顶部注明「reviewer 未并行」——**不许因此少审一个角度**。
   ⚠ **你必须在同一次返回里交出 finding 列表。** 起完 reviewer 之后**不许以「正在等待 / 三个都在跑」为内容返回** —— 那不是把等待交给了别人，那是把等待变成了**主会话轮询你**，而主会话每唤醒你一次，你都要重新建立一遍上下文（实测三次唤醒累计 ~26 万 subagent token）。你的环境里子 agent 若是后台异步的，就**等它们的完成通知再返回**；某个 reviewer 的结果确实取不回来，就直接走本条既有的降级路径（由你本人按同一角度清单顺序补审那个角度并注明），**而不是返回进度汇报**。
   **「取不回来」必须有可判定的界，否则「等通知」就成了无限期挂起**（2 轮上限保护的是迭代不收敛，保护不了你在**首次委派**就卡死 —— 那时根本还没进迭代计数）。判定：某个 reviewer **显式报错 / 异常退出**，或**已无存活的后台子 agent 却仍没有它的结果**，或你**追问过一次仍拿不到 finding 列表** —— 命中任一条就当它取不回来，立刻自己补审那个角度并注明，**不要继续等**。
3. **角度分工**：清单在本 skill 目录下的 `references/angles.md`（CC 端绝对路径 `$HOME/.claude/skills/review-loop/references/angles.md`）。**orchestrator 自己去读那个文件**，把对应角度那一节**逐字原文**转给该 reviewer —— 不改写、不压缩、不合并。清单是「低思考档也不漏审」的机制本身，压缩它等于抵消降档的前提。
4. **汇总**：跨 reviewer 去重；逐条按 0–100 置信打分 —— 0 = 伪报 / pre-existing；25 = 可能真但未验证；50 = 真但属 nit / 低频；75 = 双查过、很可能实际触发、直接影响功能；100 = 确证且高频。**< 80 直接丢弃**。75 分上下的存疑项，能用可执行探针（边界值、调用点核对、最小复现）验证的先验证再定分。
   **转给每个 reviewer 一条硬要求**：给反例时**必须报全所有相关量**（不许只报支持自己结论的那一个分量），并**在一个已知正确的对照样本上验证自己的前提**；做不到这两条就把该条降级为「存疑」而非反例。**你自己打分时同样按这条核**：一条主张推翻既有结论的 finding，若引用的量不完整，不管它带多少实测数字都不给 ≥80。
5. **返回**：单一结构化 finding 列表（`file:line`、置信分、证据、来源角度）；无 finding 则明确说 clean。**不修改任何文件。**
6. **已定设计前提**：把清单转传各 reviewer；对这些前提的质疑不算 finding。

**必须拿到 finding 才往下走**：环境提供同步开关就选同步；默认后台异步的环境等完成通知再继续，不要在结果返回前推进 loop。

> **这条对主会话和 orchestrator 同时生效，别只写给主会话。** 它原先只是主会话侧的一句纪律，而实际卡住的是 orchestrator —— 它把「等 reviewer」当成了一个可以返回的中间态，于是这条规则在运行时退化成「主会话反复追问 orchestrator」，追问两轮才拿到结果，且最终仍有两个角度是 orchestrator 自审完成的（**独立 context 这个首要属性在那两个角度上实际没兑现**）。故任务书第 2 条把同一条约束**显式写进了 orchestrator 那一侧**。
> **主会话侧的对策**：追问一次仍只拿到进度汇报，就按 Step 5 降级门槛判 —— 「返回的不是 finding 列表」正是那条门槛列举的能力缺失之一，直接走 ②（主会话自己当 orchestrator），**不要继续轮询**。

**「已定设计前提」清单怎么来**：子 agent 没有本轮对话的上下文，不告诉它哪些是人类已拍板的决策，它就会去质疑，产出一堆假 finding。**首轮委派前**先收集：本轮对话里人类明确拍板过的决策，加上 `docs/<N>-*/PLAN.md` 的「关键设计决策」段与 `PROMPT.md` 的「已决」段；清单为空就省略那一段。**迭代中追加**：reviewer 又质疑了某个已定决策，先核对它确属已拍板的（拿不准问用户），确认后追加进下轮 prompt。**不要自己替用户否决 reviewer 的意见。**

## Step 5 · 降级链

优先级：**① orchestrator 编队 > ② 主会话当 orchestrator + 通用 agent 编队 > ③ 主会话结构化自审 > 不 review（禁止）**。

| 档 | 编队形态 | 相对 ① 丢了什么 | 何时用 |
| --- | --- | --- | --- |
| ① | Step 4 的 `review-orchestrator` + `code-reviewer` | —— | 默认 |
| ② | **主会话自己当 orchestrator**：按档位表并行起 N 个**当前可用的通用 agent 类型**（`general-purpose` 等）各审一个角度，主会话只做跨 reviewer 去重、置信打分、探针验证 | 丢**由 agent 定义兜住的两条**：`effort` 钉死（Agent 工具无 effort 入参）、以及**结构性只读**（`general-purpose` 拿的是全量工具，没有 `code-reviewer` 那行 `disallowedTools`）。独立 context 与 `model` 保住 | `agents/*.md` 的类型不可用，但 Agent 工具本身能用 |
| ③ | 无编队，主会话逐角度自审 | **独立 context** —— 本机制的首要属性 | Agent 工具整个不可用（Codex 端 / 受限环境） |

**② 的委派 prompt 必须自带这两条**（①档由 agent 定义兜住，通用类型没有）：

- **传 `model` 入参**，值照抄 `agents/code-reviewer.md` 的 frontmatter —— 不传就跟着主会话跑。这是 Step 4「不要传 `model`」的唯一例外。
- **写死「只读不写：不修改任何文件」** —— 通用类型没有 `disallowedTools` 兜底，漏了这句 reviewer 真的能改工作树，而 review 阶段改动是静默的。

**② 不是将就，在无人值守会话里接近无损**：`agents/*.md` 存在的唯一理由是钉死 `model` 与 `effort`，而 `effort` 那一半防的是「主会话跑 `xhigh` 时全编队跟着烧」—— 那是**本机交互会话**的风险，云端 routine 没有 xhigh 主会话。**只要上面两条约束真写进了 prompt**，② 与 ① 差距很小；③ 丢的却是首要属性，量级完全不同。

> **云端 `agents/` 不可用是常态、不是偶发**：CC 在**会话启动时**快照 agent 类型，而云端 routine 的 `agents/` 由会话内的 `install.sh` 才软链上，来不及。实测（CC 2.1.247）：会话中途新建的 agent 定义不会被拾取，调一次 Skill 工具（skills 靠它整体刷新）也不刷新 agents。撞上 `Agent type 'review-orchestrator' not found` 就**直接走 ②**，不必每次重判。

**降级门槛（三条硬规则，任一不满足就不许降）**：

1. **先真核验一次。** 要么**实际发起过一次 Agent 调用并失败**，要么**核对确认 Agent 工具不在当前工具列表里**（受限环境下无从发起，核对工具列表就是那次实际核验）—— 两者都是可复述的实际观察。纯推断（「我判断它起不来 / 不该调」）不构成理由。
2. **只有能力缺失才算失败。** 穷举：Agent 工具不在当前工具列表里、调用直接报错、子 agent 起不来、返回的不是 finding 列表。**策略类指令一律不算** —— 「除非用户要求否则别调 Agent / 别用 workflow」这类平台通用系统提示，在用户走 `/commit` / `/review-loop` 时**条件已被满足**（宪法要求 commit 前委派独立 context reviewer，这就是那个 user request），它是策略约束、不是能力缺失。真拿不准 → **停机问人**，不许自己挑降级路径：在代价不同、计划未预先授权的方案间替人类选择是方向性决策。
3. **一次只降一档，门槛逐档适用。** 核验到 ① 失败只授权你走 ②；要再落到 ③，必须**对 ② 另做一次核验**（通用 agent 类型也起不来）。**「① 起不来」永远不构成走 ③ 的理由** —— 两个自动 PR（#141 / #143）正是这么丢掉独立 context 的，而同一根因下的 #125 / #136 / #137 都在 ② 上跑完了。

**无人值守例外**：云端 routine 等无人在环的会话**没有「停机问人」这个选项** —— 判不准时按降级处理，并在留痕里写明「门槛判定存疑」及实际观察到的表现。挂起等于整次运行报废，比一次留痕充分的降级更糟。

降级后**角度覆盖不打折**：② 由主会话读 `references/angles.md`、把对应小节**逐字原文**转给每个 reviewer；③ 由主会话自己逐角度过一遍 diff。两者都用同一套置信 rubric 过滤，并在结果**顶部显著标注** —— **两档的标注不通用，别混用**：

> **②** ⚠ 本次编队降级为**通用 agent 类型** —— 独立 context 未失，`model` 已按 `agents/code-reviewer.md` 钉死、只读已由委派 prompt 约束；**损失是 `effort` 未由 frontmatter 钉死**，reviewer 继承了主会话思考档。
> 降级原因：<那次调用失败的实际表现：报错原文>

> **③** ⚠ 本次为**主会话结构化自审**（未经独立 context 把关）—— 开发对话的先入之见在场，难复现问题极易漏判。另需告知用户「主 context 会因此增大」。
> 降级原因：<① 与 ② **各自**失败的实际表现：报错原文 / 工具确实不在列表>

**留痕必须带证据**：降级标注只写结论不算数，要附那次核验的实际表现。落点：commit message（由 `/commit` 第 7 步写入）与 `REVIEW.md`；无 docs 目录的轮（如 `/quick`）写进对话输出。**写不出证据，本身就说明不该降级** —— 此时的正解是回去把委派做完，**绝不是编一次没发生过的调用来填这一栏**。

**绝不静默跳过。**

## Step 6 · 分诊 + 运行验证 + 迭代收敛

**先跑闸 A 再看闸 B** —— 先确认基础功能没被上一轮修废。

### 6.1 分诊 finding

orchestrator 已过滤过一层，主会话仍复核证据，按**置信 + 是否真会出错**走三条出口，**不看 P 级数字**：

- **高置信 correctness finding** → 未收敛，进 6.2 修复。
- **命中「已定设计前提」** → 不算 bug、不阻断、不计入迭代轮数（闸 C）。把该前提补进下轮委派 prompt。
- **低置信 / 无证据 / pre-existing / pedantic / 纯风格 / 可选优化** → 不阻断（顺手能改的可改，不强制、不计入迭代）。

> 这是防「无限挑刺」的最后一道闸：finding 若无 `file:line` 证据、或明显属推测式，直接判为不阻断丢弃 —— 不修、不因它继续迭代。

**推翻类 finding 必须自己复算**（与上面三条出口正交，先于它们执行）：

> 凡 reviewer 的 finding 主张**推翻某条已有结论的方向**（而非指出局部缺陷），采纳前**必须自己复算一遍**：① 核对它引用的量是否**完整** —— 有没有只报支持其结论的那一部分；② 它的前提在**已知正确的对照样本**上是否也成立 —— 一个确知为好的样本在某前提下崩掉，被证伪的是那个前提。
>
> **判据：reviewer 给的是「证据」还是「一半证据」。带数字 ≠ 完整。**

这条防的不是「reviewer 挑刺太多」，而是**反向的失败**：一条带实测数字的反例天然高置信、过得了 <80 的置信闸，照单全收就会把一个真实的阻断级缺陷降级成「假象」。**置信分衡量的是 reviewer 有多确信，不是它的论证有多完整** —— 前者高不蕴含后者，复算是唯一能把二者分开的动作。

复算后：结论被真正推翻 → 照常按高置信 finding 修；只有局部措辞 / 严谨性成立而方向不成立 → **只改那一部分，不动结论**，并把「该结论已复算确认」补进下轮委派 prompt 的「已定设计前提」，免得下一轮再被同样地推翻一次。

### 6.2 自动修复（不停下逐条等用户确认）

按问题性质分流：

- **有清晰输入输出契约的代码类问题**（业务逻辑 / 纯函数 / 算法 / 并发）→ **TDD 正序**：先写一个能复现该 bug 的最小测试、**跑它、确认它在未修实现下失败（红）** → 只改相关代码让它变绿 → 跑确认绿。写不出会红的测试，说明还没真正理解这个 bug，先别动实现。
  ⚠ **防假绿硬规则**：补写的测试**必须先在旧（未修）实现上验证为红**。旧实现上就绿的测试是**假绿**，证明不了它抓得住 bug。
  ⚠ **已有守护测试也要验证约束力，不只验新补的测试。** 新测试至少还有「红→绿」这一步逼着你证明它有效；**已有的守护测试没有任何机制会发现它已经静默失效** —— 它一直是绿的，看起来很健康。
  触发条件：本轮 review 触及了「**从实际发出的命令 / 请求里挑出元素再断言**」这类守护测试（不用 `--force`、不退回 native、部署路径与 ssh 选项、路径不越界这类**安全底线**）。做法是用一次 **mutation** 确认它真的会红，**四步的顺序是硬的，不许调换**：

  1. **先备份**：`cp <测试文件> <测试文件>.bak`。**这一步在动任何字节之前做，不是做完再想起来补。** review 的对象是整个工作树的改动（Step 1），被 mutation 碰到的文件因此很可能正处于 **untracked 或 tracked-but-dirty** —— 而 `git checkout` / `git restore` **还原不了 untracked 文件**，也会连带抹掉 dirty 文件里本轮未提交的改动。**没先备份就变异 = 没有任何机制能还原**，只能靠记忆重建，那既不是「字节级」，也根本不保证还得回去。
  2. **再变异**：故意把被断言的元素改错（换命令、改路径、删掉返回码判断）。
  3. **跑测试**，确认**变红**。
  4. **无条件还原**：`cp <测试文件>.bak <测试文件>` 后删掉 `.bak`。**不管第 3 步是红是绿、是否报错、是否中途异常，这一步都必须执行** —— 备份与还原是一对资源开关，异常路径上同样要配对。还原后再跑一次测试确认回到原状。

  变异后仍全绿 = 这条底线目前没有门禁，按高置信 correctness finding 处理。
  实证：把被测命令里的 `cat` 换成 `ls`（拿不到文件内容）**并**删掉返回码判断，两个测试文件 **21 passed 全绿** —— 两处会让功能在真实环境彻底失效的改动，守护测试一个都没捕捉到，因为断言只做了子串匹配。同型前科：一处「从 rsync 命令里挑远端路径做断言」的守护测试，因为只认含 `@` 的 token 而在合法配置下一个路径都挑不出来，**核心断言一次都没执行过**，把路径改成 `/etc/` 也照样放行。
  **这类断言必须自带「一个都没挑出来就判失败」的自检** —— 「挑出 0 个元素，然后 0 个都满足条件」是空真，是这类测试静默空转的标准形态。
- **纯风格 / 机械修复，或 bug 本质无法用测试复现**（纯 UI / 视觉，或**改的就是指令 / 文档本身**）→ 无红测试可写，直接改。
- **修复纪律**：只改与本次改动相关的代码，绝不顺手动无关文件。

### 6.3 运行验证子步（闸 A）—— 每轮修完必跑

修完后、复审前必须真正**运行代码**确认基础功能没坏（reviewer 只读不跑、发现不了这层）：

1. **有对应测试 → 必跑、失败阻断**：按项目类型跑受影响测试（Python+uv `uv run pytest`、Node `npm test`、Rust `cargo test`、Go `go test ./...`；跑受影响子集即可，无法精准定位则跑全量）。失败 → 未收敛，回 6.2（失败本身就是一条高置信 correctness 问题）。
2. **被改代码是编排器 / facade 却无 happy-path integration test → 先补一条再放行**（判据见 `playbooks/python.md` §3.7）。补一条最小 fixture 端到端跑主入口、**只验「跑通不报错」** —— 抓 missing import / 参数顺序 / `self.X` 没初始化等装配错误，正是「审废基础功能」的典型形态。
3. **无运行时面 → 判 N/A 跳过**：纯文档 / 指令规则文件没有可运行的代码单元（但这类仍走闸 B）。项目无任何测试框架且改动非编排器时同样 N/A，但**改动含业务逻辑时应按 TDD 补最小测试**而非直接跳过。

> **闸 A 与 lint 的分工**：lint 只证明静态无错、证明不了行为未回归；闸 A 真正跑起来验证基础功能。二者不可互替。

### 6.4 复审收敛

修复 + 运行验证通过后**回到 Step 1 重跑**（以最新工作树复审，档位沿用本轮选择、不降档；只把委派 prompt 收窄为「核对这几处 finding 是否已消除、修复是否引入新问题」）。三要素并闸全过 → 打印「review clean ✅」放行。

**终止保护 —— 2 轮自动上限 + 留痕放行（硬规则）**：自动修复每跑满 2 轮仍未收敛，或提前出现**振荡**（同类问题反复）/ **发散**（每轮全返新问题）→ 停环、留痕、放行，**不停下等用户**：

- 剩余未修 finding 全量写入 `docs/<N>-*/REVIEW.md` 的「未收敛遗留」段（每条：内容、为什么没修完 / 怎么权衡的）；无 docs 目录的轮（如 `/quick`）写进对话输出；
- 告知 `/commit` 在 message body 追加标注行：`Review: 2 轮未收敛，遗留 N 条 finding，见 docs/<N>-*/REVIEW.md`；
- 照常放行 commit。

**为什么留痕放行而非停下问人**：边际取舍终究是人的判断，而「停下问人」会让后台 / 云端会话永久挂起 —— 故把它连同证据前移到 `/finish`（推导见 `references/rationale.md` §6）。

**留痕**：每轮结论追加到 `docs/<N>-*/REVIEW.md`（报了什么 → 怎么修 → 复审结果）。非 `/start` 轮（无 docs 目录）跳过留痕。

## 明确不做

- **不做提交动作**：只把 diff review 到 clean，`git commit` 由 `/commit` 完成。
- **不调 CC 内置 `/code-review`**（缘由见 `references/rationale.md` §2）。人工手输仍可自行使用。
- **不自动引入跨模型第二意见**：判定链长、触发率近零、维护面外溢；需要时人工手动引入（硬实证见 `references/rationale.md` §3）。
- **不做敏感文件隔离**：委派的子 agent 与主会话处在**同一信任边界** —— 同为本机 CC 进程、能读的文件完全一样、受同一套权限约束，故不加「禁读 `.env*`」之类的指令（那只在把 diff 交给**外部**模型进程时才有意义）。真正的保证是「绝密内容不落工作树明文」。
- **不做「每次 stop 都触发」**：由「commit 前触发」界定边界，收敛即停。

