implement 刚把一个 Issue 建完,或者同事给你发来一个 PR——你盯着几百行
diff(两个 git 状态之间逐行对比出的改动清单),心里其实是两个独立的问题:
它写得对吗(有没有按这个仓库的规矩写代码),和它做的是那件事吗
(Issue 里要的东西是不是真做出来了)。code-review 就是把这两个问题拆成两条轴、
各派一个子代理去审、最后并排摆给你看的那个 skill。它钉在 0001 系统地图里主 build 链的末尾:
implement 在 commit 之前一定会跑它一遍;你也可以随时单独启动它,审一条分支、
一个 PR、一段还没做完的改动。它只读不写——审完留下的是一份对话里的报告,不多一个文件。
学完这节课,你能说清两条轴各自吃什么原料、五个步骤每步在防什么错、报告为什么绝不合并排名,
以及行为不对时该去改 SKILL.md 的哪一段。
0001 把已发布的 22 个 skill 分成配置层、编排层、纪律层。code-review 的位置一句话就能说清:
它是主 build 链的收尾审查。ask-matt 在描述主 flow 第 3 步时写得很明白:
implement 逐 Issue 构建、内部驱动 tdd 走红绿循环,然后在 commit 之前
「closes out by running /code-review」——用一次两条轴的审查给这次构建收尾。
同一段还给它留了第二个入口:你想审一条分支或一个 PR 时,随时可以单独把它拿出来用。
主 build 链的收尾段:
grill-with-docs → to-spec → to-tickets → implement
│ 内部驱动 /tdd,逐片构建
▼
构建完成
│ commit 之前
▼
code-review ──→ 报告(只存在于对话里)
▲
第二个入口:你手动 /code-review,审分支、PR、半成品改动
看依赖关系,要分两个方向数:
setup-matt-pocock-skills——SKILL.md 开头就写着,
如果仓库里还没有 docs/agents/issue-tracker.md(这个文件告诉 agent 怎么从 Issue tracker 取
Issue 内容),先去跑 /setup-matt-pocock-skills。其次是上游的 to-spec 和
to-tickets:它们产出的 spec 和 Issue,正是 Spec 轴拿来对照的「当初要的东西」。
注意这些依赖都是软的——找不到 spec 时它不会死,只会让 Spec 轴缺席(见第 5 节)。
implement。它的 SKILL.md 只有十来行,
其中一行就是「Once done, use /code-review to review the work」,然后才 commit 到当前分支。
也就是说,每一次走主 flow 的构建,收尾都固定经过这道审查。
它和近邻 tdd 不要搞混:tdd 是构建中的纪律(先写测试再写实现,
在约定好的接缝上测行为),code-review 是构建后的审查(diff 已经成型,
拿两把尺子量一遍)。一个管「怎么写」,一个管「写完验收」。
地图:0001 · 路由原文:ask-matt/SKILL.md 主 flow 第 3 步 · 调用方:implement/SKILL.md · 相邻课:0011 implement、0012 tdd
先定性:code-review 是 model-invoked 的——人和 AI 都能启动。
判据有两个,都查得到:它的 SKILL.md frontmatter 里没有
disable-model-invocation: true(对比 implement、ask-matt
这些只能人启动的 skill,frontmatter 里都带着这一行);它的 agents/openai.yaml
里也只有界面显示名「Code Review」,没有禁用隐式调用的 policy。
所以你有三种方式让它跑起来:
/code-review,最好顺手给出固定点
(比如 /code-review main)。这是审 PR、审分支、审半成品改动的标准入口。
description 写明了触发条件——
「Use when the user wants to review a branch, a PR, work-in-progress changes, or asks to
"review since X"」。你用自然语言说「帮我看看这个分支」「review 一下 main 以来的改动」,
它就会自己被加载。
implement 在收尾时通过正文里的一句
「use /code-review」把它拉进来(0001 讲过,这叫 prose 调用)。
触发场景对不对,看这张表:
| 情境 | 该用 code-review 吗 |
更该去哪 |
|---|---|---|
| 一个 Issue 刚建完,commit 前想验收一下这次改动 | 该(这正是 implement 收尾时自动做的事) |
— |
| 审别人发来的 PR,或审自己「main 以来」的一串提交 | 该(给出固定点,两条轴各审一遍) | — |
| 代码还没写,想先写测试再写实现地把某个行为做出来 | 不该(那是构建纪律,不是审查) | tdd(0012) |
| 线上有 bug,某个行为坏了、或者偶发失败 | 不该(审查 diff 不治病) | diagnosing-bugs(0015) |
| issue tracker 里堆了一堆别人报的 bug 和请求,还没分拣 | 不该(那是给 Issue 换状态、写成可执行单元的活) | triage(0014) |
| 觉得代码库整体形状在变差,想找重构候选 | 不该(它只审一个 diff,不做全库扫描) | improve-codebase-architecture(0017) |
| 不知道该走哪条流程 | 不该 | ask-matt(0019) |
触发条件原文:skills/engineering/code-review/SKILL.md 的 frontmatter · 调用面:code-review/agents/openai.yaml · 人读文档:docs/engineering/code-review.md (aihero.dev/skills-code-review)
整个 skill 只围绕一个设计决定:把审查拆成两条互相正交的轴。 「轴」就是一把独立的尺子,各量各的,量完不混:
为什么要拆开?SKILL.md 的「Why two axes」一节给了两个足以说服人的组合:
如果两把尺子的读数揉成一句「总体不错」,这两种情况都会被平均掉——一条轴的失败被另一条轴的通过掩盖。 所以这个 skill 把「分开报告」上升为硬性纪律,并在聚合步骤里明文禁止合并与跨轴排名(第 7 节细讲)。
code-review 是一次只读的、双轴的 diff 审查:
以你给的固定点为基准,Standards 和 Spec 两把尺子各量一遍,并排报告,永不合并。
它不改代码、不写文件、不替你决定修不修——
它只负责把「写得对吗」和「做对事吗」两个问题的答案分别摆清楚。
权威原文:code-review/SKILL.md 开篇与「Why two axes」一节 · 叙事版:docs/engineering/code-review.md 的「Two axes, never merged」
审查总得有个对比基准。固定点(fixed point)就是你指定的一个 git 参照物——
一个 commit 的 SHA、分支名、tag、main、HEAD~5 都行;
diff 的另一端永远是 HEAD(你当前分支的最新提交)。
用户说了什么,什么就是固定点;用户没说,这一步必须开口问,不许猜。
钉死之后,立刻把两样东西一次性抓在手里:
git diff <fixed-point>...HEAD # 三点:对比 merge-base
git log <fixed-point>..HEAD --oneline # 两点:列出这段范围内的提交
git rev-parse <fixed-point> # 先验证这个参照物真的存在
三点的写法是考点。merge-base 是你的分支和固定点所在分支的分叉点
——三点 diff 比的是「分叉点 → 你现在的 HEAD」,所以你看到的只是这条分支自己引入的变化。
如果误用两点(..),主干上别人后来提交的代码也会被算进 diff,
Standards 和 Spec 两个子代理就会去审查一堆根本不是这次工作的代码。
这一步最后一条纪律是 fail fast(快速失败):先用 git rev-parse
确认固定点解析得出来,再确认 diff 非空。参照物写错了、或者 diff 是空的,
必须在这里就报错停下——不能等到两个子代理已经并行撒出去了,
才在它们各自的环境里分别炸一次。错误发现得越早,定位成本越低,这是整个第 1 步存在的理由。
原文:code-review/SKILL.md「### 1. Pin the fixed point」
Spec 轴要回答「做对事吗」,前提是找得到「当初要做的事」。
这里用上 0001 和 CONTEXT.md 的统一词汇:Issue tracker 是存放 Issue 的工具
(GitHub Issues、Linear、或本地 .scratch/ 目录下的 markdown 约定),
Issue 是里面一条被跟踪的工作单元。这个 skill 按固定顺序找 spec,
找到即停:
| 优先级 | 去哪里找 | 具体怎么做 |
|---|---|---|
| 1 | 提交信息里的 Issue 引用 | 在 commit message 里找 #123、Closes #45、GitLab 的 !67 这类引用,然后按 docs/agents/issue-tracker.md 里写的工作流去 Issue tracker 把这条 Issue 的内容取回来 |
| 2 | 用户当参数传进来的路径 | 你启动 skill 时直接给了 spec 文件路径,就用它 |
| 3 | 仓库里的 PRD / spec 文件 | 在 docs/、specs/、.scratch/ 下找与分支名或功能名匹配的 spec 文件 |
| 4 | 开口问用户 | 以上都没有,就问 spec 在哪;如果用户说根本没有 spec,Spec 子代理整个跳过,报告里注明 "no spec available" |
还有一个前置条件要记牢:第一级「去 Issue tracker 取 Issue」依赖
docs/agents/issue-tracker.md 这个文件——它是 setup-matt-pocock-skills
在配置阶段写进你的仓库的。SKILL.md 开头明说:这个文件缺失就先跑
/setup-matt-pocock-skills(0002 那节课讲的就是它)。
原文:code-review/SKILL.md「### 2. Identify the spec source」 · 前置配置:0002 setup-matt-pocock-skills · spec 的生产者:0009 to-spec、0010 to-tickets
Standards 轴要回答「做得对吗」,它的尺子由两层叠成:
CODING_STANDARDS.md、CONTRIBUTING.md。这层因仓库而异。
两层之间靠两条规则绑死,缺一条这套机制就会咬人:
基线的 12 条异味逐条列在下面,按 SKILL.md 原文压缩。
每条的结构都一样:先说什么算这条异味,再给修复方向。子代理拿到 diff 后逐 hunk
(hunk:diff 里一段连续的改动块)对照这张表。
| 异味 | 它是什么(在 diff 里长什么样) | 修复方向 |
|---|---|---|
| Mysterious Name 神秘命名 |
一个函数、变量或类型的名字,看不出它做什么、装了什么 | 改名;如果想不出一个诚实的名字,说明设计本身就含糊 |
| Duplicated Code 重复代码 |
同一种逻辑形状,在这次改动的多个 hunk 或多个文件里重复出现 | 把公共形状抽出来,两处都调用它 |
| Feature Envy 依恋情结 |
一个方法伸手读另一个对象的数据,比读自己的还多 | 把这个方法搬到它羡慕的那个数据所在的对象上 |
| Data Clumps 数据泥团 |
同样几个字段或参数总是结伴出现——一个想出生的类型 | 把它们捆成一个类型,之后传这个类型 |
| Primitive Obsession 基本类型偏执 |
用一个基本类型或字符串,顶替一个本该拥有自己类型的领域概念 | 给这个概念一个属于它自己的小类型 |
| Repeated Switches 重复的 switch |
对同一个类型的同一串 switch/if 级联,在改动里反复出现 |
用多态替换,或者抽一张两处共享的映射表 |
| Shotgun Surgery 霰弹式手术 |
一个逻辑上的改动,逼着你在 diff 里散开修改很多文件 | 把「总是一起变的东西」收拢进同一个模块 |
| Divergent Change 发散式变化 |
一个文件或模块,因为好几个互不相关的原因被修改 | 拆开,让每个模块只为一个原因变化 |
| Speculative Generality 投机式泛化 |
为 spec 里并不存在的需求,预先加上的抽象、参数或钩子 | 删掉,内联回去,等真实需求出现再说 |
| Message Chains 消息链 |
长长的 a.b().c().d() 导航链,调用者不该依赖这么深的走法 |
在第一个对象上提供一个方法,把整段走法藏在它后面 |
| Middle Man 中间人 |
一个类或函数,大部分时候只是把调用继续转发给别人 | 砍掉它,直接调用真正的目标 |
| Refused Bequest 拒绝继承 |
一个子类或接口实现者,忽略或覆盖掉了它继承来的大部分东西 | 放弃继承,改用组合 |
原文:code-review/SKILL.md「### 3. Identify the standards sources」 · 呼应:0005 codebase-design
两条轴各派一个子代理(sub-agent:主 agent 派出去、各自独立干活的 agent),
而且是并行的——SKILL.md 的原文是「Send a single message with
two Agent tool calls」,一条消息里同时发出两个调用,两个都用
general-purpose 类型的子代理。并行的理由不是省时间,是隔离:
两个子代理各自抱着干净的上下文进场,Standards 的结论不会影响 Spec 的判断,反过来也一样——
这正是第 3 节「绝不合并」在执行层面的落实。
每个子代理的 prompt 里必须装什么,SKILL.md 列得很细:
| 子代理 | prompt 里必须带的东西 | 任务书(brief)的要点 |
|---|---|---|
| Standards | 完整的 diff 命令和提交清单;第 3 步找到的仓库标准文件清单;整份异味基线原文照贴——子代理没有别的渠道拿到它,不贴就等于没给尺子 | 逐文件 / hunk 报告:(a) 每处违反明文标准的地方,引用出处(哪个文件的哪条规则);(b) 每条发现的基线异味,报名字并引用 hunk。必须区分硬性违规与裁量项,声明仓库标准优先、跳过工具已强制的。400 词以内 |
| Spec | diff 命令和提交清单;spec 的路径或取回来的内容 | 报告三类问题:(a) spec 要求了但没做或只做了一半的需求;(b) diff 里做了但没人要求的行为——这叫范围蔓延(scope creep,悄悄超出需求边界的多余工作);(c) 看起来做了、但实现看起来是错的需求。每条结论必须引用 spec 原文那一行。400 词以内 |
两个细节值得记住:其一,400 词上限写在两个任务书里,是防止子代理把 「审查报告」写成「重构论文」的缰绳;其二,第 2 步如果已经确定没有 spec, Spec 子代理根本不派,只在最终报告里注明——省一次调用,也堵住编需求的路。
两个子代理交回报告后,主 agent 只做整理,不做裁决:
## Standards 和 ## Spec 两个标题下,
原文照摆,最多轻度清理排版。
两条红线明文写在 SKILL.md 里:不合并、不重排(do not merge or rerank findings), 以及不跨轴选总赢家(don't pick a single winner across axes)。 跨轴排名正是两条轴之所以要分开的理由想要防的事——一旦说一句「总体还行」, 一条轴的失败就被另一条轴的通过平均掉了(第 3 节的两种失败组合)。 总结行里「每轴最严重一条」是允许的,因为那是轴内的比较,没有跨轴。
原文:code-review/SKILL.md「### 4. Spawn both sub-agents in parallel」与「### 5. Aggregate」
把五步串起来看一个虚构的例子。假设你在分支 feature/invite-emails 上,
输入 /code-review main:
git rev-parse main 通过;git diff main...HEAD
拿到 3 个文件、约 200 行的 diff;git log main..HEAD --oneline 列出 4 个提交,
其中一个提交信息里写着 Closes #45。
docs/agents/issue-tracker.md 的流程
取回 Issue #45,正文里有一条「invite links expire after 7 days」。
CODING_STANDARDS.md,其中一条是
「错误必须包一层领域错误再抛出」;再叠上 12 条异味基线。
general-purpose 子代理,
Standards 的 prompt 里照贴了基线全文,Spec 的 prompt 里是 Issue #45 的内容。
最终报告的形状大致是(以下为教学用虚构示例):
## Standards
- src/invite/send.ts:42 — 违反 CODING_STANDARDS.md「错误必须包一层领域错误」:
直接把 axios 的原始错误抛给了调用者。(硬性违规)
- src/invite/send.ts:88 — 可能的 Feature Envy:buildEmail() 读了 user 对象的
7 个字段,自己所在模块的字段一个没用。(裁量项)
## Spec
- 缺失:Issue #45 要求「invite links expire after 7 days」,
diff 中没有任何与过期时间相关的实现。
- 范围蔓延:新增了 spec 未要求的 Slack 通知开关
(spec 中无对应行)。
—— Standards 2 条(最重:错误未包装);Spec 2 条(最重:过期时间未实现)
注意这份报告的形状纪律:两节分开、每条都带出处(标准的文件名加规则、spec 的原文行)、 异味标着「可能的」、总结行按轴分开说、没有一句跨轴的总评。 拿到它之后怎么行动是你或下一个流程的事——这个 skill 到此为止。
| 对象 | 会动它吗 | 说明 |
|---|---|---|
| 你的仓库文件 | 不改 | 全程只跑 git diff / git log / git rev-parse 这类只读命令;SKILL.md 没有规定写任何文件 |
| Issue tracker | 只读 | 只为取 spec 去读 Issue 内容;不写评论、不改状态、不新建 Issue |
| 你的代码 | 一行不改 | 报告指出的问题由你或后续流程去修,它自己不动手 |
| 对话上下文 | 写入 | 唯一的产物就是对话里的那份双轴报告;两个子代理各消耗一次并行调用 |
/code-review,直到两轴都干净或你都认了。
implement 的职责(它的 SKILL.md 最后一行:「Commit your work to the
current branch」),不是 code-review 的。
to-spec(0009)补一份 spec 再审;如果只是随手小改,
接受只有 Standards 轴的结果就好。
这个 skill 的全部行为都装在唯一一个 SKILL.md 里(没有 sibling 参考文件),
所以微调入口很好找——按下表对号改对应小节,别去改无关的地方:
| 症状 | 改 code-review/SKILL.md 的哪段 |
不要误改 |
|---|---|---|
| 报告把两条轴揉在一起、给了跨轴总排名 | 「### 5. Aggregate」的禁止句 + 「## Why two axes」的理由 | 两个子代理任务书里的 400 词上限 |
| 基线异味被当成硬性违规来报告 | 「### 3. Identify the standards sources」的 "Always a judgement call",以及第 4 步 Standards 任务书里对应的区分要求 | 12 条异味的定义本身(那是 Fowler 的原义,不是纪律问题) |
| 仓库明文认可的做法仍被基线挑刺 | 同一节的 "The repo overrides." 一句 | 异味清单(问题不在清单,在优先级规则) |
| 主干上别人的新提交被算进审查范围 | 「### 1. Pin the fixed point」的三点写法与 rev-parse 检查 | 子代理的 prompt 模板(它们只是照收 diff 命令) |
| 没有 spec 时,Spec 轴开始编需求 | 「### 2. Identify the spec source」第 4 条(跳过并注明缺席) | Spec 任务书里「引用 spec 原文」的要求(那条是对的) |
| 报告又臭又长,像在写论文 | 「### 4. Spawn both sub-agents in parallel」两个任务书里的 "Under 400 words" | 「引用出处」的要求(短不等于不许引用) |
| 两个子代理互相看到了对方的结论 | 第 4 步「Send a single message with two Agent tool calls」的并行要求 | 聚合步骤(污染发生在派发时,不在聚合时) |
| 你说「review 一下这个分支」时 AI 从不主动用它 | frontmatter 里 description 的 "Use when…" 一段(触发词) |
plugin.json(它本来就已发布,且是 model-invoked) |
先别往回翻,凭记忆答。选项的长度刻意对齐,不会从版式泄题。答错的题号回到对应小节重读后再选。
<fixed-point>...HEAD?grill-with-docs → to-spec → to-tickets → implement → code-review,
并说出 code-review 的两个启动入口(implement 在 commit 前自动跑、你手动对分支或 PR 跑);
(2) 默写 spec 的四级查找顺序(提交信息里的 Issue 引用 → 用户传的路径 → 仓库里的 PRD/spec 文件 → 问用户);
(3) 从 12 条异味里默写任意 6 条,每条配一句修复方向。
写完回第 1、5、6 节对答案。
本课主一手材料(请打开原文读,不要只背本页摘要):
skills/engineering/code-review/SKILL.md
—— 首选 primary source。五步流程、12 条异味基线、两个子代理的任务书原文、聚合红线、Why two axes,全部在这一个文件里。
docs/engineering/code-review.md
—— 给人看的叙事版(aihero.dev/skills-code-review):
「什么时候伸手」「前置条件」「它在哪」三节是本课的骨架。
速查页(本课同步): reference/code-review.html
导航: 上一课 0012 tdd (构建中的纪律:先写测试再写实现,活在接缝上)。 总览仍回 0001 系统地图。
建议下一课(0014):
0014 triage。
code-review 是主 build 链的收尾;triage 是另一条 on-ramp 的入口——
别人的 bug 报告和功能请求堆进 Issue tracker 时,它把原始 Issue 分拣成
agent 可以直接接手的成品 Issue,然后交给 implement 走回主链,
最后又会来到你刚学完的这道审查。
skills/engineering/code-review/SKILL.md 的原文,不会临场编造。
做完检索练习后,回复「练习结果 / 哪里卡住 / 开 0014 或先补前面某课」,我们安排下一课。