关于 pre-merge
用于合并前创建 GitHub PR 并依据项目架构原则审查差异的流水线技能。支持作者、审阅者和循环模式;发现仅供参考,技能不会修复代码或阻止合并。
主要功能
- 01创建关联 PRD 和切片议题的 PR
- 02依据架构原则审查差异
- 03支持作者、审阅者和循环模式
- 04循环模式中附证据记录发现
- 05仅提供建议,不执行修复
适用场景
- 01为已验证的分支准备拉取请求
- 02审查其他贡献者的拉取请求
- 03跨多个会话跟踪审查发现
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ chrislacey89/ skills --skill pre-merge安装后在 Claude Code 中输入/pre-merge确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
review-checklist.mdreferences/comment-craft.mdnext-step-menu.mdrestated-claims.mdsidecar.shstate-backend.mdSYSTEM-OVERVIEW.mdwriting-for-humans.md详细介绍
合并前
创建一个 GitHub PR,关联回 PRD 和切片问题,然后根据项目的架构原则审查完整差异。此流程会生成建议性发现——不会阻止合并、自动修复代码或创建问题。
调用时机
这是一个主要流水线技能,在实现经过验证后、合并到 main 之前使用,或者在接手他人的 PR 进行审查时使用。
当分支已准备好创建 PR、进行架构审查和最终计划与代码核对时,使用 /pre-merge。当你要审查自己未编写的 PR 时,使用 /pre-merge --pr <number>。
不要将它用作实现验证、QA 受理或重构规划的替代方案。它假定工作已经完成,且已准备好接受审查。
模式
/pre-merge 以三种模式之一运行。三种模式都会复用 review-checklist.md 中定义的架构审查维度;区别在于它们读取的内容和生成的结果。
- 作者模式(默认) — 在自己的分支上调用,且不带
--pr参数。该技能会创建 PR(阶段 2),并将发现作为建议打印到终端(阶段 4),然后在此交还控制权——它不会自行提交任何修复,也不会开启第二轮审查。 这是/execute在 HITL 路径的步骤 6 中自动调用的模式。读取范围由分支状态决定,而非标志: 首次审查会读取整个分支;如果对已带有审查时效戳记的 PR 重新运行,且其头部此后已有变动,则会将戳记 SHA 之后的提交作为审查的对象,读取这段增量,而不会重新读取先前审查已覆盖的内容。阶段 1 的步骤 4 会作出该决定并打印出来。增量是审查对象,而非审查者可读取内容的限制:审查者应读取的内容由review-checklist.md决定,其中包括增量周围的代码树。 - 审查者模式 — 使用
/pre-merge --pr <number>,针对你未编写的 PR 运行。该技能会跳过创建 PR(因为 PR 已存在),并生成待审阅的评论文本(阶段 4),供你审阅并发布;其结构遵循references/comment-craft.md(5P gate、Triple-R、Comment Signals、MMG Exchange)。 - 循环模式 — 使用
/pre-merge --loop调用,或者在/execute于 AFK 运行中交接时自动进入。它像作者模式一样运行阶段 1–4,但阶段 1 步骤 4 的范围判定除外:循环模式始终读取整个分支,不采用戳记后的增量,随后继续进入阶段 5:它会在 PR 正文或工作项中记录每项发现,并附上支持或反驳该发现的证据;具体位置依据 references/state-backend.md § 审查记录的存放位置中的说明,然后交还控制权给操作者。在此模式下,发现不再只是终端输出,而是会成为有负责人的持久化记录。循环模式不会修复任何内容,也不会创建提交——由操作者决定如何处理每项发现(参见阶段 5 § 循环为何不负责修复)。
循环模式的退出条件是每项发现都有负责人——绝不是审查结果干净。 “零项发现”是禁止的终止信号,这与阶段 4 现有的最少发现数量保护相一致。之所以设置这项保护,是因为在非平凡差异上,审查结果几乎为空,意味着审查提前结束,而不是代码毫无瑕疵。如果循环在审查结果干净时终止,就会稳定地产生干净的审查结果,而不会有其他产出(Meadows,《追寻错误的目标》;Leveson:绝不要奖励较低的事故数量——要奖励主动报告本身)。
如果你在用户工作分支以外的分支上运行,且未提供 --pr,请询问一次用户是否指的是针对特定 PR 编号的审查者模式,而不要自行猜测——自动检测虽能省下一次按键,但模式判断错误会导致生成本应是本地建议的草稿评论,反之亦然。
适用场景
- Author-mode: QA 通过后、将功能分支合并到 main 之前;Ralph 完成 AFK 执行且你已验证行为之后;或者任何你想在合并前进行审查的分支,即使没有运行完整流水线也是如此。
- Reviewer-mode: 队友或外部贡献者提交 PR 时,你想针对其差异应用
review-checklist.md中的完整架构审查,并撰写建设性的评论。 - Loop-mode: 需要让审查发现留存到产生这些发现的会话结束之后时使用——例如从
/execute进行 AFK 交接,或者你要分几次审查某个分支,不想每次都重新推导发现时。适用于你需要解决的问题是跟不上审查发现,而不是修复问题。
执行流程
阶段 1:收集上下文
Author-mode:
-
询问 PRD issue 编号。 如果这次变更没有经过完整流水线,接受“none”。如果是从 AFK
/execute交接进入 loop-mode,则不要询问——从交接中获取 issue 编号(/execute第 6 步会传递该编号),若没有则视为“none”。此时没有人可以回答,因此无条件提问会让流程在第一步就卡住。在 sidecar 后端中,答案是W-<n>work item ID(第 2 步)。“none”会导致阶段 2 无事可记录 PR,也会让 Review Notes 和 loop-mode 台账无处存放:references/state-backend.md § Where the review's records live 说明了这些记录的处理方式。 -
如果提供了 PRD:
gh issue view <number> gh issue list --search "in:body #<prd-number>" --state all --json number,title,state,body --limit 100从每个切片 issue 正文中解析边界映射(Produces/Consumes 部分)。
在 sidecar 后端中(
bash "$SC" backend slices返回sidecar,详见 references/state-backend.md;下面的代码块会向其查询,并在遭到拒绝时停止,而不会回退到gh issue),PRD 及其切片都是 work item,第 1 步询问的编号是W-<n>ID:此分支实现的切片(由/execute传递),或其 PRD。不运行任何gh issue命令;关联关系和边界映射来自 work item:SC="<this skill's directory>/references/sidecar.sh" W="W-<n>" case "$(bash "$SC" backend slices)" in sidecar) ;; github) echo "backend is github: read the lineage with the issue block above"; exit 0 ;; *) echo "lineage aborted: sidecar.sh backend slices failed; not guessing the github path" >&2; exit 1 ;; esac J="$(bash "$SC" work view "$W" --json)" || { echo "lineage aborted: cannot read $W" >&2; exit 1; } case "$(jq -r '.kind // empty' <<<"$J")" in slice) PRD="$(jq -r '.parent // empty' <<<"$J")" ;; prd) PRD="$W" ;; *) echo "lineage aborted: $W has no kind" >&2; exit 1 ;; esac SLICES="$W" if [ -n "$PRD" ]; then bash "$SC" work view "$PRD" --json | jq -r '"# \(.id) (prd, \(.state))\n\(.body)"' SLICES="$(bash "$SC" work list --kind slice --parent "$PRD" --state all)" \ || { echo "lineage aborted: cannot list the slices of $PRD" >&2; exit 1; } fi while IFS= read -r S; do [ -n "$S" ] || continue bash "$SC" work view "$S" --json | jq -r '"# \(.id) (slice, \(.state))\n\(.body)"' done <<<"$SLICES"按照从 issue 正文中解析的方式,从这些正文中解析边界映射。保留
$W:阶段 2 会在其上记录 PR。 -
检测基分支并评估差异:
SC="<this skill's directory>/references/sidecar.sh" # $BASE_REF is the range endpoint; $BASE_BRANCH is the name `--base` takes. BASE_REF="$(bash "$SC" base-ref)" || { echo "base-ref failed: not guessing a base" >&2; exit 1; } BASE_BRANCH="$(bash "$SC" base-ref --name)" || { echo "base-ref --name failed: not guessing a base" >&2; exit 1; } git diff "$BASE_REF...HEAD" --stat git log --oneline "$BASE_REF..HEAD"如果 sidecar 声明了
base_ref,base-ref会输出该值;否则输出origin/HEAD默认值。如果退出码非零,则不会输出任何 ref:停止并转达其 stderr,不要猜测基分支。对于堆叠 PR 的切片,此处设置的变量不会传递到后续代码块:在每个使用该变量的代码块(适用于你所用后端的阶段 2 代码块,以及阶段 4 的 stamp 代码块)的BASE_BRANCH=占位符中填入同级切片的分支名称(即 PR 将以其为目标的上游分支)。如果与基分支没有差异,告知用户无需审查并停止。注意事项:
$BASE_REF的新鲜程度取决于上次git fetch的时间。如果origin/<base>无法解析,base-ref会基于本地分支进行检查,并在 stderr 中说明;如果计数看起来不对,请运行git fetch后重试。 -
确定审查范围——整个分支,还是 stamp 之后的变更。 此决定仅适用于作者模式。循环模式会跳过此步骤,并无条件设置
SCOPE_FROM="$BASE_REF",根据第 3 阶段循环模式的说明,每次调用都会读取整个分支。首次审查会读取整个分支。如果重新审查的 PR 已由此技能写入 stamp,且其 head 此后发生了变化——例如进行/fix-findings轮次,或有意删除锁后手动修复——则以 stamp 之后的变更为审查对象。根据 references/state-backend.md 中backend stamp的说明,stamp 位于 PR 正文或 sidecar 中。使用/closeout第 2 步中的同一表达式读取 PR 正文;scripts/test-review-currency-marker.sh会验证两者逐字节相同,而scripts/test-delta-review-scope.sh会针对两个后端,在此处列出的每种分支状态下用一个 fixture 运行此代码块:SC="<this skill's directory>/references/sidecar.sh" case "$(bash "$SC" backend stamp)" in sidecar) REVIEWED_SHA=$(bash "$SC" stamp read "$(git branch --show-current)") ;; github) REVIEWED_SHA=$(gh pr list --head "$(git branch --show-current)" --json body -q '.[0].body // ""' \ | sed -n 's/.*.*/\1/p' | tail -1) ;; *) echo "scope aborted: sidecar.sh backend stamp failed; not guessing the github path" >&2; exit 1 ;; esac SCOPE_FROM="$BASE_REF" if [ -n "$REVIEWED_SHA" ] \ && git merge-base --is-ancestor "$REVIEWED_SHA" HEAD 2>/dev/null \ && [ "$REVIEWED_SHA" != "$(git rev-parse HEAD)" ] \ && [ -z "$(git log --merges --format=%H "$REVIEWED_SHA..HEAD")" ]; then SCOPE_FROM="$REVIEWED_SHA" elif [ -n "$REVIEWED_SHA" ] \ && ! git merge-base --is-ancestor "$REVIEWED_SHA" HEAD 2>/dev/null \ && [ "$(git rev-parse -q --verify "$REVIEWED_SHA^{tree}")" = "$(git rev-parse 'HEAD^{tree}')" ]; then SCOPE_FROM="HEAD" fi if [ "$SCOPE_FROM" = HEAD ]; then echo "review scope: none, history-only rewrite — HEAD carries the tree reviewed at $REVIEWED_SHA" elif [ "$SCOPE_FROM" = "$BASE_REF" ]; then echo "review scope: whole branch, from $BASE_REF" else echo "review scope: post-stamp delta, from $SCOPE_FROM" fi git diff "$SCOPE_FROM...HEAD" --stat git log --oneline "$SCOPE_FROM..HEAD"有四种情况会有意回退到整个分支:没有 stamp(首次审查或手动创建的 PR);stamp 不是
HEAD的祖先,且HEAD已不再包含其 tree(分支已重写,内容也随之更改,或已审查的提交已不存在——/closeout将此称为更强的分歧信号,这并不意味着审查范围更小);stamp 等于HEAD(没有新的审查范围);以及 stamp 之后存在合并提交(stamp 之后的 diff 会包含基分支自身的变更,这些变更并非由当前分支上的任何人编写)。系统会打印此决定,确保终端输出和 PR 对读取内容的描述一致。保留了已审查 tree 的重写不算分歧。 当 stamp 对应的提交不是
HEAD的祖先,但仍在本地存在,且其 tree 与HEAD相同,则历史发生了变化,内容没有变化:折叠阶段 4 提供的交接选项(git rebase -i --autosquash)恰好会产生这种情况。代码块会打印review scope: none并设置SCOPE_FROM="HEAD"。跳过第 3 阶段,以及第 4 阶段中除 stamp 和交接菜单之外的所有内容;stamp 会重新记录为HEAD。stamp 认证的是已审查的内容,而 tree hash 正是该内容本身。如果本地没有 stamp 对应的提交,就无法比较 tree,此时重写会按上述情况回退到整个分支。对变更重新审查时,审查对象是这些变更,但 tree 仍可读取。 为第 3 阶段的每位审查者说明 stamp 之后的提交声称要关闭的发现——可以使用本会话中较早的输出、循环模式写入的台账(其位置见 references/state-backend.md § 审查记录的存放位置),或提交消息本身——并说明
/fix-findings的判定结果(如有)。每个提交首先要检查修复是否在发现所提及的每个位置都已关闭问题,这属于 Fix Completeness 维度;其次要检查新增文本与附近内容有何矛盾,这属于references/restated-claims.md中的普查。两项检查都需要周边 tree,而检查清单的上下文约定已允许读取这些内容;范围限制的是审查关注什么,而不是审查者可以打开什么。
审查者模式(--pr <number>):
-
获取 PR 及其 diff:
gh pr view <pr-number> --json number,title,headRefName,baseRefName,body,author,url,state gh pr diff <pr-number>如果 PR 已合并或关闭,告知用户并停止——已关闭 PR 上的审查评论会单独显示,通常用处不大。
-
根据 PR 正文确定 PRD issue。 查找
Closes #<n>、Refs #<n>,或指向某个 issue 的## PRD章节。如果找到,则像作者模式第 2 步一样运行gh issue view和 slice-issue 搜索,以加载 PRD 上下文和边界映射。如果 PR 没有 PRD 血缘关系,则按“无 PRD”分支处理——PRD 限定的维度会在各自**Runs in:**标记指定的阶段跳过自身检查。 -
从 PR JSON 中记录 diff 大小和基分支。 不进行本地分支计算——
gh pr diff会直接返回从合并基线到 head 的 diff。不要尝试在本地检出该 PR;你要审查的是 diff,而不是运行它。
阶段 2:创建 PR
在 reviewer-mode 下完全跳过此阶段——PR 已经存在,不是你创建的,重写别人的 PR 正文不在范围内。继续执行阶段 3。
在 sidecar 后端上,未推送的分支会最后执行此阶段。 在 author-mode 下,当 bash "$SC" backend stamp 返回 sidecar 且分支未共享时,还没有其他人看过这个分支。满足以下条件时,分支就是未共享的:git rev-parse -q --verify '@{u}' 没有输出,并且 git ls-remote --heads origin "$(git branch --show-current)" 也没有输出:普通的 git push origin <branch> 会发布分支,但不会设置上游,因此仅检查上游会把已发布的分支误判为私有分支(git-ls-remote)。这是对此测试的唯一说明;下面的折叠门槛会指向此处。跳到阶段 3,审查本地分支。印记存放在 sidecar 中,因此不需要 PR。只有当用户从交接菜单中选择打开 PR时,才会执行此阶段。在此之前,重新运行时会继续推迟此阶段,这样审查修复就能并入它们所修正的提交,在其他人查看历史记录之前完成。Pro Git 的规则就是如此(Chacon,第 3 章):对尚未共享的本地更改进行变基,绝不要对已推送的内容进行变基。阶段 4 中的关闭议题讨论串协调会在此后端读取工作项,而不是读取 PR,因此即使在推迟执行时也会运行。第 3 步对审查者也不会跳过:在推迟执行时,阶段 3 会从 /execute 交接中逐字提取 ## Review Notes 区块,而第 3 步仍会按照 references/state-backend.md § 审查记录的存放位置中的说明写入该区块。在 github 后端上,印记保存在 PR 正文中,因此要在审查前先在此处打开 PR。
-
检查是否已有 PR:
gh pr list --head $(git branch --show-current) --json number,url -
创建或更新 PR。 使用
gh pr create --base "$BASE_BRANCH"(对于堆叠 PR 切片,将同级切片的分支填入适用于你所用后端的代码块中的BASE_BRANCH=占位符),或使用gh pr edit。阶段 1 中的$BASE_BRANCH不会传到这一步:每个围栏代码块都在各自的 shell 中运行,因此下面的github代码块会自行询问base-ref --name。在
github后端上,根据以下模板编写标题和正文后:SC="<this skill's directory>/references/sidecar.sh" TITLE="<title>" BODY_FILE="<path of the file holding the body written from the templates below>" BASE_BRANCH="<sibling slice branch for a stacked PR, else empty>" # an earlier block's shell variable does not reach this one case "$(bash "$SC" backend stamp)" in github) ;; sidecar) echo "backend is sidecar: open the PR with the sidecar block below, not this block"; exit 0 ;; *) echo "PR aborted: sidecar.sh backend stamp failed; not guessing the github templates" >&2; exit 1 ;; esac [ -n "$BASE_BRANCH" ] || BASE_BRANCH=$(bash "$SC" base-ref --name) \ || { echo "PR aborted: sidecar.sh base-ref failed; not letting gh pick a base" >&2; exit 1; } gh pr create --base "$BASE_BRANCH" --head "$(git branch --show-current)" --title "$TITLE" --body-file "$BODY_FILE"
先确认该 pack 的文本存放在哪里。 根据 references/state-backend.md 的说明,bash "$SC" backend stamp 会返回结果,而下面的 sidecar 代码块会先询问,再执行任何操作。github 使用下面的两个模板。sidecar 表示仓库的 forge.write 不具备 prs:PR 仍会打开,但它只是普通 PR,模板中包含的 pack 信息都放在工作项上。其他任何返回结果都表示拒绝:停止操作,并转达 backend 在 stderr 上打印的内容,因为如果返回失败,模板会把 pack 文本写入客户端的 PR。
在 sidecar 后端上,标题和描述是普通的人类可读文本:说明更改内容和原因,并像 Summary 演练中那样写入 references/writing-for-humans.md。其中不包含以下模板中的任何标题,不包含 Closes #、#N 或 W-<n> 引用,也不包含 ## Review Notes。现有 PR 的正文保持原样——客户端可能编辑过它——并且只记录 URL:
SC="<this skill's directory>/references/sidecar.sh"
W="W-<n>" # the work item Phase 1 step 2 read; empty when step 1's answer was "none"
TITLE="<plain title>"
BASE_BRANCH="<sibling slice branch for a stacked PR, else empty>" # an earlier block's shell variable does not reach this one
case "$(bash "$SC" backend stamp)" in
sidecar) ;;
github) echo "backend is github: write the PR from the templates below, not this block"; exit 0 ;;
*) echo "PR aborted: sidecar.sh backend stamp failed; not guessing the github templates" >&2; exit 1 ;;
esac
DESC_FILE=$(mktemp)
# Write the plain description into "$DESC_FILE". Nothing below adds to it.
BRANCH=$(git branch --show-current)
KIND=$(bash "$SC" config get forge.kind) \
|| { echo "PR aborted: sidecar.sh config get forge.kind failed" >&2; rm -f "$DESC_FILE"; exit 1; }
if [ "$KIND" != github ]; then
SIDECAR=$(bash "$SC" resolve) \
|| { echo "PR aborted: sidecar.sh resolve failed" >&2; rm -f "$DESC_FILE"; exit 1; }
mkdir -p "$SIDECAR/prs"
PASTE="$SIDECAR/prs/$(printf '%s' "$BRANCH" | tr -c 'A-Za-z0-9._-' '_').md"
{ printf '%s\n\n' "$TITLE"; cat "$DESC_FILE"; } > "$PASTE"
rm -f "$DESC_FILE"
bash "$SC" commit -- "$PASTE" -m "pre-merge: PR text for $BRANCH" >/dev/null \
|| echo "the PR text is at $PASTE but is not committed to the sidecar" >&2
echo "forge.kind is ${KIND:-unset}, which gh cannot reach. Open the PR on the host with this title and description (saved at $PASTE):"
echo
cat "$PASTE"
exit 0
fi
PR_URL=$(gh pr list --head "$BRANCH" --json url -q '.[0].url // ""') \
|| { echo "PR lookup failed" >&2; rm -f "$DESC_FILE"; exit 1; }
if [ -z "$PR_URL" ]; then
[ -n "$BASE_BRANCH" ] || BASE_BRANCH=$(bash "$SC" base-ref --name) \
|| { echo "PR aborted: sidecar.sh base-ref failed" >&2; rm -f "$DESC_FILE"; exit 1; }
OUT=$(gh pr create --base "$BASE_BRANCH" --head "$BRANCH" --title "$TITLE" --body-file "$DESC_FILE") \
|| { echo "PR create failed" >&2; rm -f "$DESC_FILE"; exit 1; }
PR_URL=$(printf '%s\n' "$OUT" | tail -1)
fi
rm -f "$DESC_FILE"
if [ -n "$W" ]; then
bash "$SC" work set "$W" pr "$PR_URL" || { echo "PR opened, but $PR_URL is not recorded on $W" >&2; exit 1; }
else
echo "no work item: $PR_URL is recorded nowhere in the sidecar" >&2
fi
echo "$PR_URL"
对于 gh 无法访问的代码托管平台,代码块会在调用任何 gh 命令之前停止。forge.kind 不为 github 时,即使是自有仓库也会使用 sidecar 后端(references/state-backend.md),因此 GitLab、Bitbucket 或无法识别的主机都会走此分支。上面的第 1 步和第 2 步也不会在此执行。代码块会打印普通标题和描述,即需要人工手动带到主机上的唯一内容,并将相同文本保存到 sidecar 的 prs/ 下。然后只需向用户询问一次,获取他们打开 PR 后的 URL,并用此代码块记录:
SC="<this skill's directory>/references/sidecar.sh"
W="W-<n>" # the same work item as above; empty when step 1's answer was "none"
PR_URL="<the URL the human gave>"
case "$PR_URL" in
https://*|http://*) ;;
*) echo "not recorded: '$PR_URL' is not a URL" >&2; exit 1 ;;
esac
if [ -n "$W" ]; then
bash "$SC" work set "$W" pr "$PR_URL" || { echo "$PR_URL is not recorded on $W" >&2; exit 1; }
else
echo "no work item: $PR_URL is recorded nowhere in the sidecar" >&2
fi
echo "$PR_URL"
如果他们还没有打开 PR,就不记录任何内容并继续:之后运行 /pre-merge 时会再次打印文本并再次询问。代表操作员通过任何代码托管平台自身的 API 或 CLI 打开 PR,不在此处的范围内。
模板原本会写入的血缘关系已经记录在工作项上:它的 parent、blocked_by,现在还有它的 pr。/execute 的 ## Review Notes 也会写入工作项(见下文步骤 3),Phase 3 会从那里将其交给审阅者。scripts/test-guest-footprint.sh 会在 stubbed gh 上运行此区块和 /closeout Step 3 的合并操作,分别针对一个工作项和 "none",并且只要它们传递的内容中包含任何 pack 标记就会失败。它也会在 GitLab 远程服务上运行粘贴分支和 URL 区块,并对打印和保存的文本执行相同的标记扫描。
PR 正文模板(存在 PRD 时):
## Summary
[For trivial PRs — typo fixes, dep bumps, formatting-only, single-line reverts — 1–2 sentences derived from the PRD's Problem and Solution sections.
For non-trivial PRs — behavior changes, new surface area, bug fixes with non-obvious root causes, refactors crossing module boundaries — write a plain-language walkthrough: one paragraph of domain setup, what changed and why each piece was the right move, and why it matters. Aim for a reader who doesn't have the codebase in their head. See `references/writing-for-humans.md` for the shape and revision bar.]
## PRD
Closes #<prd-issue-number>
## Slices
- [x] #N — Title (for closed slices)
- [ ] #N — Title (for still-open slices)
## Key Decisions
[Bullet list of notable implementation decisions that refined or diverged from the PRD. Derived from commit messages and slice issue comments. Omit this section if nothing diverged.]
PR 正文模板(不存在 PRD 时):
## Summary
[For trivial PRs — typo fixes, dep bumps, formatting-only, single-line reverts — 1–2 sentences derived from the diff and commit messages.
For non-trivial PRs, write a plain-language walkthrough: one paragraph of domain setup, what changed and why each piece was the right move, and why it matters. See `references/writing-for-humans.md`.]
-
如果
/execute移交了## Review Notes区块,就写入该区块。 作者模式和循环模式的移交都会包含一段可重新运行的验证声明——运行了哪些命令及其退出状态、跳过了哪些层级及原因、在 Consumes 门控下吸收了哪些范围、哪些假设发生了变化,以及已知的薄弱点。将其原样放在## Summary之后;不要总结或改写。它的全部价值在于审阅者可以重新运行这些声明,而每一次总结都会成为由受审阅的 Agent 撰写的有损转换。区块的格式见/executeStep 6。在sidecar后端上,按照 references/state-backend.md § Where the review's records live 所述的位置写入,并使用bash "$SC" work comment "$W" --body-file <notes>。 -
打印 PR URL。
-
注意,印记会在之后写入。 Phase 4 会记录审阅实际覆盖的 head SHA,具体位置见 references/state-backend.md。它会在 Phase 3 运行之后写入,而不是在此处写入——在创建 PR 时写入印记,会错误地认证一场尚未发生的审阅。
Phase 3:架构审阅
请参阅 review-checklist.md,了解审阅维度及其违规模式。该文件就是完整清单——维度集合仅由其中的 ## N. 标题定义,本文不会重复列出。三种模式使用相同的维度;只有它们读取的 diff 不同:
- 作者模式——本地
git diff "$SCOPE_FROM...HEAD",其中 Phase 1 步骤 4 将$SCOPE_FROM设为$BASE_REF(首次审阅:整个分支),或设为印记 SHA(头部发生变化后的重新运行:印记之后的增量)。在增量重新运行时,每位审阅者还会收到简报,其中包含印记之后的提交声称已解决的问题以及任何阻断性判定;并会按照清单的上下文契约读取增量周围的代码树——审阅对象是增量,而非阅读范围的上限。 - 审阅者模式——
gh pr diff <pr-number>的输出。 - 循环模式——本地
git diff "$BASE_REF...HEAD"——每次调用都审阅整个分支;循环模式不采用增量范围。循环模式不会创建提交,因此后续调用中的 diff 只有在操作员推送修复后才会变化;阻止审阅再次报告已解决问题的是台账,而非缩小后的 diff。
引入委派是为了确保审阅者独立;大型 diff 上的并行处理是其一个特例。 /pre-merge 由 /execute Step 6 自动调用时,会在刚刚编写完代码的会话中运行,并携带实现 Agent 编写代码时所做的所有合理化判断。审阅维度本身没有问题,问题在于审阅者并不独立。Cohen 的发现是,作者的职责是为审阅者添加注释,而不是充当审阅者——因此,下文的子 Agent 拆分首先是为了让审阅者在干净的上下文中查看 diff,其次才是为了缩短大型 diff 的处理时间。
因此,在这三种模式下,委派都是无条件的——编写代码的会话绝不会在会话内审查自己的差异。唯一的例外取决于内容而非大小:Phase 2 的两个正文模板已列出的四类琐碎改动——拼写修正、依赖项升级、仅格式调整、单行回退——在会话内审查,这是 Phase 3 对琐碎与非琐碎所作的唯一划分。此豁免仅适用于 Phase 3;如果某个琐碎类别的差异大到触发 Phase 4 的最低发现数量保护机制,仍需在 Phase 4 委派审查。
大小决定的是子 Agent 的数量,而非审查是否离开编写会话:
- 小型差异(更改行数少于 200 行、文件少于 10 个):由一个子 Agent 执行清单标记为子 Agent 工作、且满足各自门槛的所有维度——清单中每个维度的章节都会说明其自身门槛;下方的说明会进一步阐述其中几项,但并非完整清单。
- 较大型差异:并行启动两个子 Agent,将同一组维度分配给它们。
无论哪种情况,由控制器负责的维度完全不受此选择影响——差异大小决定运行多少个子 Agent,绝不决定是否将清单规定不得委派给子 Agent 的维度也委派出去。
为什么触发条件取决于内容而非大小。 Cohen 的 Cisco 数据集凭自身证据就足以让这一代理标准退场:四次审查各自仅涉及 1–2 行,却都耗时超过 15 分钟,因为微小的表面改动带来了架构层面的影响。本仓库也有类似实例——一个 34 行、2 个文件的差异,远在旧阈值范围内,但编写会话的审查遗漏了其中的组合缺陷,而在干净上下文中进行的一轮审查共发现八个问题,并将该缺陷列为首要关注项(docs/solutions/architecture-decisions/self-review-blind-to-composition-2026-08-13.md)。移除该阈值,就意味着不再采用这个代理标准;四类豁免保留了该阈值原本要保护的内容,而且范围更窄。
子 Agent 不可用时,在会话内执行这些维度,并在发现结果中将此次运行标记为降级模式:审查者持有编写会话的上下文,因此已不具备独立性,阅读发现结果时应考虑这一点。这与 /improve-pipeline Phase 4 在无法启动辩证审查时所作的声明相同。降级模式是一种披露,不是第二种豁免——不要因为委派不方便就启用它。
上下文约定关乎来源,而非许可。 它包含三部分,封闭清单只是其中第一部分。
交接内容——三种模式均相同。 差异、review-checklist.md、references/writing-for-humans.md,以及(如有)## Review Notes 区块(来自 PR 正文,或延后运行时来自 /execute 交接;参见 Phase 2 中关于延后的段落);若使用 sidecar 后端,则交接该区块在 references/state-backend.md § Where the review's records live 所述位置的内容。不会提供其他内容给子 Agent,尤其不会提供实现会话的上下文。在 reviewer-mode 下审查由外部人员编写的 PR 时,不会有 ## Review Notes 区块,因为该 PR 从未运行过 /execute;这是输入缺失,而非遗漏了步骤。
**可自行获取——持久状态,在源头读取。**某个维度自身的流程可以让它的子 Agent 超出该列表,去查看合并后的树、分支的提交历史——git log、提交消息、逐次提交的差异——对已删除路径执行 git show、切片或 PRD 问题正文、软件包注册表、研究归档、已安装的 .d.ts、docs/solutions/,以及临时运行的 tsc --noEmit。提交历史之所以在列表中,是因为有两个维度离不开它:纵向切片完整性完全由提交结构定义,而精确范围将提交消息和分支名称列为最后可用的任务来源。它是独立的产物,不是差异的摘要,因此读取它不会触犯下方的规则。在审查者模式下,gh pr view <n> --json commits 无需本地检出即可获取提交消息和 SHA;它不会返回逐次提交的差异,因此需要这些差异的模式无法在此检查。这不是对约定的例外,而是约定本身在发挥作用。自己读取持久产物,正是保持独立性的方式——真正破坏独立性的,是收到由正在审查的会话预先消化过的产物。只有一条规则:**在源头读取并引用它,绝不接受控制器对它的描述。**审查者直接阅读差异,而不是阅读差异的摘要,就是同一条规则在始终存在的那个输入上的应用。
**绝对不提供——变更的上下文。**这个说法指的是作者会话的工作状态:它的推理、舍弃的备选方案、编写差异时整理出的理由,以及任何由控制器撰写、用来代替子 Agent 原本可以直接读取内容的叙述、摘要或转述。尽管 ## Review Notes 是作者撰写的,它仍会交给审查者,因为它是一份可核查的记录——/execute 步骤 6 要求提供审查者可以重新运行的命令和退出状态——而不是需要继承的诠释。
**这决定了哪些内容。**如果某项发现的证据,可通过该维度自身指定的流程从差异及上述类型的持久状态中获取,子 Agent 就能证实这项发现。如果它需要基于多个产物汇总出的视图,而没有任何一个指定流程能重建该视图,子 Agent 就无法证实;控制器类别正是以此为依据,并在每个归控制器负责的 **Runs in:** 标记中按维度说明,而不是在此处制定一条一概而论的规则。
**循环模式只增加一项内容:**从第 2 轮起,增加记录在台账中的状态和证据,这样本轮就不会再次报告操作者已经解决的发现。它绝不会加入上一轮的严重性判断;第 5 阶段的台账部分会完整说明需要传递和需要保留的内容,以及原因。
两个参考文件都是评审标准——说明要检查什么,以及最终文案必须达到什么标准。独立性约定不提供变更的上下文;它从来就不是要隐去评审所依据的标准。
划分依据来自清单,绝不在此重述。review-checklist.md 中的每个维度,其标题正下方都有一个 **Runs in:** 标记,明确指定三种负责方之一——sub-agent A、sub-agent B 或 the controller, in Phase 4(清单中的图例定义了各类别的主题)。查看这些标记,并将标记为某个子 Agent 负责的维度交给它。标记为控制器负责的维度不属于子 Agent 的工作,不会委派出去;第 4 阶段会在会话中执行这些维度,此时所需的 GitHub 状态已加载。
这不是一份清单。这里曾放过一份手动维护的人员分配副本,从一开始就写错并随之发布;scripts/test-review-dimension-partition.sh 的文件头记录了这一事件,并会针对规范中任何未分配的维度报错。
每个子 Agent 都会阅读完整差异和 review-checklist.md 中分配给自己的审查维度,然后按三级严重性格式返回发现——每条建议和问题都必须符合 references/writing-for-humans.md 中规定的结构和修订标准。该文档会作为发现文本的评分标准交给子 Agent,尽管文档本身的适用范围是问题和 PR 正文。观察项不受此要求约束,这是第 4 阶段明确豁免的——而不是文档自身的跳过条件列表;后者豁免的是工件类别,也不会产生任何发现。第 4 阶段说明了这一标准及其原因。
**审查者始终阅读实际差异,而不是其摘要。**摘要是由受审查的控制器编写的有损转换;新鲜上下文能让审查者不受合理化影响,却无法防止误报(Leveson:任何控制系统的表现都不会优于其测量通道)。
门槛、阈值和各维度的流程只在清单中说明,不在其他地方说明。 有些维度只有在差异满足自身条件时才会触发;有些维度带有数值区间、豁免条件或指定的验证流程。所有这些内容都位于对应维度的标题下——把任务分配给每个子 Agent,让它阅读自己负责的门槛。若在此重复,就是为同一项声明增设第二个有效位置,因此在一个文件中修改区间后,另一个文件就会过时:这属于 references/restated-claims.md 所定义的类别,也是此技能自身的 Deep Modules 条目会检测的类别。scripts/test-restated-review-operatives.sh 覆盖了其中一部分;它的文件头是唯一说明所覆盖部分的位置,本文不再重复。请先阅读该文件,再把测试通过视为此类问题已解决。
TypeScript 项目: /ts-audit 通过对变更的 .ts/.tsx 文件进行类型安全分析,补充架构审查。是否运行取决于模式:
- 作者模式和审查者模式:提及它,不要调用它。 对于
.ts或.tsx文件有重大变更的分支,将其作为一个选项提及——“如需更深入的 TypeScript 分析,可考虑对变更文件运行/ts-audit”——并由用户决定。这是有意设置的人机协同边界;下方的范围界定不会改变这一点。这里采用宽松的“重大”标准是合适的,因为判断失误的代价只是多写一句不必要的话。 - 循环模式:自动调用它,并将其发现与各维度的发现一同记录在第 5 阶段台账中。对于此时此刻的这些文件,它已经是推荐的下一步操作;过去之所以需要手动执行,唯一原因是没有任何机制推动这个循环。需要一项约束:调用规则需要有可判定的触发条件,而提及规则不需要:
- 触发条件——变更的
.ts/.tsx行数超过 50 行,或变更的.ts/.tsx文件数超过 2 个,按本轮读取的差异统计。未达到该条件时,跳过审计,并在台账的“本轮未运行的检查”一栏中记录跳过,而不是默默略过——Review-friendly Size 对被抑制的大小发现也采用了同一规则。“重大变更”可以作为人工判断是否提及工具的可用指引,却无法作为循环判断是否运行工具的可评估标准。 - 与之前轮次重复的发现应与台账进行比对,并保留为已有行,而不是重复添加。
验证,而非猜测——库的回调语义、子路径替换和提供商架构约束。 如果某项发现取决于库如何处理应用传给它的值(回调的返回值、传给钩子的对象、systemMessages/tools/middleware 集合语义),子 Agent 必须在发现中引用已安装的类型定义——注明
node_modules/<library>/**/*.d.ts文件路径和行号。如果存在此功能的研究档案条目,优先使用其中的callback_contracts_snapshot(参见research/SKILL.mdPhase 1.25)。如果某项发现取决于导入解析所经由的软件包子路径——即多运行时软件包的同级子路径之间,伪装成纯类型差异但会影响运行时的替换——优先使用installed_versions_snapshot(参见research/SKILL.mdPhase 5b);Boundary Map Contracts 的 spec-reality 检查项 2 会使用它作为门槛。如果某项发现取决于 SDK 封装提供商时的提供商架构约束——SDK 的类型签名接受某种形状(JSON Schema、Zod、工具定义),但底层提供商的实际契约不接受(例如,Gemini 的response_schema拒绝数值枚举、Anthropic 的tool.input_schema仅遵循 JSON Schema 的一部分、OpenAI Structured Outputs 的架构子集与 JSON Schema 2020-12 不一致)——引用必须包含 SDK 当前已安装版本对应的提供商契约文档,而不能只引用 SDK 的宽松.d.ts。对于这类发现,没有来源引用却使用含糊措辞(“如果库是替换 X 而不是合并”“如果接受此字段”“SDK 允许传入任意架构”)是不可接受的——只需一次 grep 或查看一个提供商文档页面就能找到依据,而故障在运行时不可见。要么引用来源并按严重性规则归类为 Observation/Suggestion/Concern,要么将其降级为具名后续事项,并明确要求“合并前验证”。
- 触发条件——变更的
Phase 4:呈现发现
先执行由控制器负责的维度,再汇总所有维度(或子 Agent)的发现。
由控制器负责的维度在此处、当前会话中执行。 review-checklist.md 中所有 **Runs in:** 标记为 the controller, in Phase 4 的维度都由你执行——Phase 3 没有委派这些维度,任何子 Agent 也不可能执行。之所以将这些维度标记为由控制器执行,是因为其流程需要同时查看多个切片:PRD、每个切片 issue,以及已合并切片的集合。Phase 1 已在此上下文中完整收集了这些内容,这正是这项工作落在此处的原因。
现在阅读 review-checklist.md 中每个此类维度的流程并执行。其发现属于常规发现:按检查清单指定的严重性标注,纳入以下三个层级,并在循环模式下像其他发现一样记录到账本中。特别是,由控制器负责的维度所提出的 Concern 仍然是 Concern,必须明确升级处理——调整的是检查的执行位置,而非其判定的分量。Concern 之上没有更高层级;如果某个维度的判定应阻止合并,就应在其 Concern 中明确写出该行动,Coverage Matrix Reconciliation 对未映射 Must 的处理正是如此。
然后按照与其他内容相同的证据规范处理这些发现:由控制器负责的维度并不豁免于引用支持依据的要求。它唯一豁免的是下方的委派发现检查;该检查用于发现子 Agent 的错误报告,而这里没有子 Agent。
如果某个由控制器负责的维度不满足执行条件——例如,Coverage Matrix Reconciliation 仅在此 PR 关闭多切片 PRD 的最后一个切片时触发——应在发现输出中说明,而不是悄然跳过;做法与 Review-friendly Size 声明已抑制的尺寸发现相同。维度没有报告发现,与维度从未执行过,在其他方面看起来完全相同;这种模糊性正是该维度数月来一直被遗漏的原因。
展示委派的发现前,先根据代码树核验。 委派消除了一种失败模式,却暴露了另一种:新的上下文能避免合理化,却无法避免错误报告(Leveson 在第 3 阶段谈到摘要时指出了这一点,对子 Agent 的输出同样适用)。循环模式已在第 5 阶段第 2 步处理了这一问题:当代码树与某项主张矛盾时,记录 refuted — <evidence>。作者模式和审查者模式没有相应机制,因此子 Agent 的错误报告会作为建议直接打印出来,读者与之之间毫无核验。现在,每次非简单审查都委派执行,这一缺口便出现在默认路径上。
因此,对于每项委派的发现,都要在此上下文中遵循第 3 阶段的“核验,不要猜疑”规则——查找支持或反驳它的 grep 结果、文件路径和行号;若发现取决于库的语义,则查阅已安装的类型定义——然后:
- 得到代码树支持——展示该发现,并在其中引用证据。
- 被代码树反驳——不要将其作为发现展示。将其记录一次,作为事实说明(
sub-agent B reported X; path:line contradicts it),在三个层级之后,与下文其他仅用于报告的说明一并打印,让核验过程清晰可见,而不是悄然删除。它不像 Scope Notes 那样取决于是否有 PRD;即使是没有 PRD 的审查,被反驳的发现也仍要有对应的一行记录。 - 无法根据代码树确认或反驳——如何处理取决于该发现是否属于“核验,不要猜疑”规则所管辖的类别。如果它取决于库的回调语义、子路径解析或提供方的架构约束,应用该规则的降级条款:不要将其列为 Observation、Suggestion 或 Concern,而应将其列为具名后续事项,并明确标注“合并前核验”。该规则已规定,这类没有引用依据的发现不应归入任何层级,而无法提供引用依据的控制器也并未改变引用状态。其他所有发现都按子 Agent 指定的层级展示,并说明未经核验的依据——降级条款不适用于它,也不授权将其归入该层级。
核验不是重新评判。父 Agent 核验主张;它不会重新排列子 Agent 的严重程度,也不会协调一个子 Agent 的发现与另一个子 Agent 的发现。
此规则仅管辖终端展示,循环模式仍执行第 4 阶段。 被反驳的发现仍须在台账中记录一行,状态为 open,并附上反驳依据——第 5 阶段第 1 步会纳入第 3 阶段的所有发现,不作筛选;第 2 步则明确规定保留被反驳的发现,因为是否删除是由操作人员作出的决定。在终端中隐藏被反驳的发现属于展示处理;从台账中删去它则会成为台账旨在防止的未注明报告。两条规则如有冲突,以台账为准。
最低发现数检查。 提交结果前,统计所有发现的总数——包括全部三个等级,以及任何降级为合并前验证的后续事项;这些只是打印位置发生了变化,并非发现不复存在。如果在 diff 第 3 阶段审阅中,发现总数少于 4 条——首次审阅时统计整个分支,重新运行时统计盖章后的增量,因此两次提交的修复不会触发此检查——并且 diff 规模不小(更改行数超过约 50 行,或涉及超过 2 个文件),就再进行一次有针对性的审查,明确查找可能遗漏的内容——范围偏移、悄然变化的假设、浅层模块、只覆盖顺利路径的测试,或逃过状态纪律检查的新状态文件。**第二轮审查必须由一个新的子 Agent 执行,并使用第 3 阶段指定的同一上下文——不能在当前上下文中重新阅读。**它从第 4 阶段启动,因此要明确说明这一点;并且要依据第 3 阶段,而不是这里的清单来读取上下文——包括循环模式额外加入的那一项:账本中记录的状态和证据。离契约这么远再列举,正是此前这句话遗漏账本、导致第二轮重新报告操作人员已经处理的发现的原因。它的发现必须满足与其他发现相同的读者标准。**明确告知它跳过由控制器负责的维度。**它会收到完整清单,而这些维度上文已经处理完毕——已执行,或已声明由自身门控而不执行。第 3 阶段的子 Agent 会收到带有任务分配的清单——其中标明了分配给它们的维度——因此不会交给它们任何由控制器负责的内容;第 4 阶段的第二轮审查则会收到没有任务分配的文件,所以必须告知它要跳过什么。否则,这轮审查要么悄然漏掉这些内容,要么在无法支持判定的上下文中重新执行。触发此检查,恰恰说明第一轮审查结果过于单薄;而偏差盲区意味着卫生检查不能由自己执行(Kahneman、Sibony 和 Sunstein,Noise)——在保留第一轮审查上下文的情况下重新阅读,只会重复那次单薄的审查,再将其包装成独立的第二轮审查。它的发现与其他委派的发现一样,都要经过上述证据检查。如果在非微不足道的 diff 中发现为零条或一条,这说明审查过早结束,而不是代码无可挑剔。如果第二轮后数量仍然偏少,就如实提交已有发现——不要为了达到配额而捏造发现。
每条发现都必须达到与 PR 正文相同的读者标准。 第 2 阶段已经要求,对于非微不足道的 PR,正文必须有通俗易懂的讲解。发现此前没有相应的标准,而它们正是这项技能的主要产出——正文只是围绕它们搭建的脚手架。读者无法理解的发现,与从未提出的发现一样不合格,而且这种不合格会悄然发生:等级照常打印,账本行也显示了负责人,但没有任何迹象表明信号没有传达出去。因此,应按照 references/writing-for-humans.md 中的结构和修订标准撰写每条 Suggestion 和 Concern——用领域术语指出系统的相关部分,把结论放在前面再补充限定,以它能防止什么或解锁什么收尾,然后删去赘述。
有两项例外。Observations 不受此要求约束,领域含义一目了然的发现也不受约束——一行关于命名模式的备注不需要交代领域背景。这里将这一例外作为本技能自身的规则,而不是借用文档的跳过条件列表;该列表免除的是产物类别(拼写错误修复、依赖升级、纯格式调整的 PR、单行回退、可轻易复现的 Bug),并未免除任何发现。这五类中有四类 diff 根本不会由第 3 阶段委派,因此审查子 Agent 持有的清单按设计就不适用于它;适用的是其下方的收尾规则——拿不准时,一句话胜过冗长的讲解。 其次,标准在于清晰,而非篇幅——如果一条发现变长了,却没有变得更清晰,就不符合标准。请结合上面的最低发现数量防护规则阅读:该规则禁止为了达到数量而编造发现,本规则则禁止给已有发现凑字数。
这项标准约束发现文本本身,因此适用于所有三种模式——作者模式的终端提示、基于相同发现构建的审查者模式草稿,以及循环模式的台账行(它们会在生成它们的会话结束后继续留存,而届时读者最不了解上下文)。
发现提交时就应符合标准;本阶段不会改写它们。 第 3 阶段会在撰写者的任务说明中明确这项义务——无论工作是委派完成还是在会话中完成——因此应在撰写时达到标准,而不是在此处事后补救。此禁令范围很窄,只涵盖一种行为:为了改进文句而重写返回的发现。在作者模式中,父级就是编写代码的会话,因此若将独立审查者的发现交给它改写,就会损害这次委派审查所换来的独立性。如果发现未达到标准,应在第 3 阶段的任务说明中解决,而不是在此处进行清理。
父级基于发现撰写的文本不受此禁令约束,而是直接受该标准约束——审查者模式的 Triple-R 理由和循环模式台账中的 Finding 单元格都由父级撰写,并且都必须符合标准。
作者模式会打印终端提示(如下)。审查者模式会将相同的发现转换为 PR 评论草稿文本(见下文“审查者模式评论草稿”)——维度和严重性分类相同,但输出形式不同。
在终端中以以下三个层级呈现(作者模式):
## Architectural Review
### Observations (for awareness — no action needed)
[Patterns noticed that aren't violations. Example: "The presence module
exports 6 functions — reasonable, but worth watching if it grows."]
### Suggestions (action optional — would improve quality)
[Grouped by dimension. Things that aren't violations but would make
the code better. Written to the reader bar above.]
### Concerns (action recommended — potential principle violation)
[Grouped by dimension. Each concern cites the principle, shows the
specific code, and explains why it matters — and reads cold to someone
who did not write the diff, per the reader bar above
(`references/writing-for-humans.md`).]
### Verify before merge (findings that hold no tier)
[Findings the "Verify, don't suspect" downgrade clause removed from the
tiers — library callback semantics, subpath resolution, or provider schema
constraints that nothing could cite. Each names the check that would settle
it. Omit the section when there are none.]
---
No action is required. These are advisory.
When ready, merge the PR at <PR-URL>.
此区块是作者模式执行的最后一项审查操作。 完成下方第 4 阶段的其余工作,然后在 ## Handoff 的后续步骤菜单(references/next-step-menu.md)处交接——是否处理某项发现由该菜单决定,并由用户选择,而非由本会话决定。
范围说明(仅当提供了包含切片问题的 PRD 时):
列出三个层级的发现后,指出计划的分解与实际差异之间任何显著的范围偏移:
- 差异中出现、但不在任何切片的 Boundary Map 中的工作(实施期间发现的遗漏范围)
- 已声明的 Produces 未出现在差异中的情况(已计划但被删减或推迟的工作)
- 实际差异涉及的切片范围与 Boundary Map 声明的范围差异很大的情况
这些是事实说明,不是审查发现。它们不会产生观察、建议或疑虑,而是记录计划与实际之间的偏差,以便用户和 /compound 判断是否值得记录某种模式。如果差异与计划的边界图大致一致,则完全省略本节。
验收标准复选框核对(仅报告)。 在加载切片问题时,将每个切片的 ## Acceptance Criteria 复选框状态与合并后的差异和验证结果实际证明的内容进行比较。将任何不一致标记为事实说明——例如,标准看起来已满足但复选框仍未勾选,或复选框已勾选但差异无法佐证。以 EARS 形式编写的标准(When/While/If … then,并带有说明行为的 shall 子句)自带搜索键:在差异中的测试名称和断言里查找该子句,并将其完全未出现的标准作为同类事实说明报告。匹配到子句只能证明存在测试,不能证明测试正确——这只是辅助读者完成上述比较,并非替代比较。只报告,不要编辑问题。/execute 是这些复选框的唯一写入者(见其第 5/6 步的回写);/pre-merge 仅提供建议,从不写入,因此这是合并门禁的后备检查,用于暴露偏差,而不会造成第二个编辑者。
**关闭 issue 的评论线程对账(仅报告)。**阶段 1 加载切片 issue 时不包含其评论线程,因此,若某项要求只存在于评论中,上述所有检查都无法发现它。从 GitHub 获取关闭集合,而不是从正文文本中获取——只包含那些 issue,不包括阶段 1 加载的所有切片——然后获取每个 issue 的评论线程,并与正文进行比较:
gh pr view <pr-number> --json closingIssuesReferences --jq '.closingIssuesReferences[].number'
gh issue view <closing-issue-number> --comments
对于评论中提到的具体能力,若正文的验收标准未涵盖,且你能指出它的使用方(某个 issue、切片、PRD 或已发布代码),则将其作为事实性备注报告。使用与 /execute 步骤 1 相同的筛选条件:没有明确使用方的探索性评论不符合条件。合并会关闭 issue,因此这是评论线程仍附属于未完成工作的最后时刻。报告即可;不要编辑 issue,也不要自行提交后续工作——/execute 仍是唯一写入者,这也能补足 /execute 从未处理过该 issue 的情况(手写 PR、外部贡献)。
在 sidecar 后端中,PR 不会关闭任何内容,因此 GitHub 上没有关闭集合可供获取。阶段 1 步骤 2 读取的工作项可作为替代:按照相同规则,将 work view --json 中的 comments 与其正文进行比较。上文的验收标准对账会从相同正文中读取复选框。
如果审查发现,主要教训与 Skill Kit 本身有关——例如 chrislacey89/skills 中的阶段边界不清、缺少交接指导或审查清单存在缺漏,而非下游代码库中的问题——请备注:“如果存在该技能,请考虑运行 /improve-pipeline。”不要调用它。
如果某项顾虑值得深入处理,请备注:“考虑对此区域运行 /request-refactor-plan。”不要调用它。
如果某项发现看起来像行为缺陷,请备注:“考虑运行 /qa 进行验证。”不要提交 issue。
如果即将合并的人不是差异的作者——或者尚未逐个 commit 理解其内容——请备注:“考虑在合并前运行 /walk-commits,进行交互式的逐个 commit 理解讲解。”不要调用它。这是理解与签核环节,与上述缺陷审查不同;它不能替代这些发现。
省略没有任何发现的层级。
**当单项发现的处置方式分成三种或更多时,请用对比形式呈现选项。**大多数发现都有明确的处置方式,无需特别说明。偶尔会遇到例外:同一项 Concern 可以在此分支修复、作为 issue 提交、说明理由后接受,或因判断有误而舍弃——这四种状态已由阶段 5 台账列出;每种状态都有自己的结果状态、成本和“适用条件”。当这种分支达到 references/next-step-menu.md § Show the comparison before you take the choice 的门槛——存在三个或更多互斥选项,且每个选项都有三个或更多属性,无法按单一维度排序——请用表格呈现:属性作为行,处置方式作为列,每个单元格都引用 file:line,或标记为“已断言”。未达到门槛时,照常使用文字说明。
本阶段只负责呈现,不负责做出选择,本段也不会改变这一点。对比只是让发现更易理解的呈现方式,就像周围的三层格式一样。处置权在操作者手中——作者模式下通过下方的交接菜单处置,循环模式下则通过向台账写入状态处置;对比的作用是在操作者行动前供其参考。不要仅因在此处呈现了对比,就向该菜单添加选项。
记录已审阅的 HEAD SHA(author-mode 和 loop-mode)。 上述审查针对某个特定提交。记录该提交,这样后续的 /closeout(也可能是在全新会话中)就能判断即将合并的差异是否仍是已审阅的差异。前瞻性记忆会悄无声息地失效(Norman),因此标记要写入持久状态,而不是留在操作者脑中;只有 /closeout 第 2 步的审查时效性前置条件会读取它。
只有在上述发现已呈现之后才能写入标记。该标记表示“已在此 SHA 完成审查”,因此若提前写入,就会为尚未执行的审查背书。
先询问标记存放在哪里。 根据 references/state-backend.md,bash "$SC" backend stamp 会给出答案。github 会运行下方的 PR 正文代码块,内容保持不变。sidecar 则改为运行此代码块,并且不会触碰 PR 正文:在该后端中,pack 不会向 PR 写入任何文本。其他任何情况——包括非零退出状态(此时不会打印任何内容)——都不会在任何路径写入标记:停止并转达 backend 在标准错误输出中打印的内容。若回答失败,却仍选择 PR 正文代码块,这是唯一错误的做法,因为它会在访客仓库中向客户端的 PR 写入 pack 文本。
SC="<this skill's directory>/references/sidecar.sh"
REVIEWED_SHA=$(git rev-parse HEAD)
BASE_BRANCH="<sibling slice branch for a stacked PR, else empty>" # the same branch Phase 2 was given; an earlier block's shell variable does not reach this one
# This block runs in its own shell, so Phase 1's $BASE_REF is not set here: a stacked base is
# recorded as origin/<branch> when that resolves (else the bare name), and otherwise ask again.
if [ -n "$BASE_BRANCH" ]; then
if git rev-parse --verify -q "origin/$BASE_BRANCH^{commit}" >/dev/null; then BASE_REF="origin/$BASE_BRANCH"; else BASE_REF="$BASE_BRANCH"; fi
else
BASE_REF=$(bash "$SC" base-ref) || { echo "stamp aborted: sidecar.sh base-ref failed" >&2; exit 1; }
fi
# The lock flag below is reachable only after the stamp is committed in the
# sidecar, for the same reason as on the PR-body path: a flag with no stamp
# locks the branch on the strength of a review nothing recorded.
if ! bash "$SC" stamp write "$(git branch --show-current)" "$REVIEWED_SHA" --base-ref "$BASE_REF"; then
echo "stamp aborted: sidecar stamp write failed" >&2; exit 1
fi
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}"
mkdir -p "$PROJECT_DIR/.claude" && touch "$PROJECT_DIR/.claude/.review-stamped"
# A stamped branch carries no classification marker. /execute Step 6 removes
# them, but a session that reached this review some other way may still hold one.
rm -f "$PROJECT_DIR/.claude/.tdd-active" "$PROJECT_DIR/.claude/.tdd-skipped" "$PROJECT_DIR/.claude/.lfg-active"
写入失败时,只重试一次,与 PR 正文路径相同。stamp write 会替换分支的单个标记文件,而不是追加内容,因此重试不会重复写入标记。
在 github 后端上:
REVIEWED_SHA=$(git rev-parse HEAD)
SHORT_SHA=${REVIEWED_SHA:0:7} # pinned, not `git rev-parse --short` — see the width note below
BODY_FILE=$(mktemp) # unique per run — parallel worktrees must not share a temp path
# Guard the read half. Check the fetch's exit status, not just the file's size:
# a 5xx makes `gh` fail, and the edit below would then replace the whole PR body
# with a stamp. `--json` parses the response, so a success exit means the body is
# whole; the emptiness check below is for a PR whose body is genuinely blank.
if ! gh pr view <pr-number> --json body -q .body > "$BODY_FILE"; then
echo "stamp aborted: PR body fetch failed" >&2; rm -f "$BODY_FILE"; exit 1
fi
[ -s "$BODY_FILE" ] || { echo "stamp aborted: PR body came back empty" >&2; rm -f "$BODY_FILE"; exit 1; }
ORIG_BYTES=$(wc -c < "$BODY_FILE")
# In "$BODY_FILE": replace the existing "## Review Currency" section if one is
# present; otherwise append this block, substituting the real SHAs:
#
# ## Review Currency
#
#
# Reviewed by `/pre-merge` at `<short-sha>`. Commits pushed after this SHA have
# not been through the review dimensions — `/closeout` surfaces the delta before merge.
#
# Guard the write half. With $SHORT_SHA pinned above, stamping only ever appends
# one block or swaps one fixed-width block for another, so the edited body is
# never shorter than what was read. Shorter means the edit dropped content.
[ "$(wc -c < "$BODY_FILE")" -ge "$ORIG_BYTES" ] || { echo "stamp aborted: edited body is shorter than the body read" >&2; rm -f "$BODY_FILE"; exit 1; }
# Guard the write half too. Everything below this line — including the local
# flag that locks the branch — runs only if the stamp actually landed in the PR.
# An unchecked `gh pr edit` would leave the PR body unstamped and the flag on
# disk, which locks the branch on the strength of a review nothing recorded.
if ! gh pr edit <pr-number> --body-file "$BODY_FILE"; then
echo "stamp aborted: PR body write failed" >&2; rm -f "$BODY_FILE"; exit 1
fi
rm -f "$BODY_FILE"
# The stamp's local twin. `.claude/.review-stamped` is read by
# .claude/hooks/enforce-classification.sh, which refuses implementation writes
# while it exists and .claude/.fix-findings-active does not — so a post-review
# fix has to go through /fix-findings or through a visible hand deletion.
# /closeout removes it at merge. The guard on the `gh pr edit` above is what
# makes this line reachable only after the stamp write succeeded; without it the
# touch would run on an aborted stamp too.
# $CLAUDE_PROJECT_DIR is unset outside a Claude Code session, and an unguarded
# expansion would resolve to "/.claude" — a silent no-write that leaves the
# lock disengaged while everything above reports success.
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(git rev-parse --show-toplevel)}"
mkdir -p "$PROJECT_DIR/.claude" && touch "$PROJECT_DIR/.claude/.review-stamped"
# A stamped branch carries no classification marker. /execute Step 6 removes
# them, but a session that reached this review some other way may still hold one.
rm -f "$PROJECT_DIR/.claude/.tdd-active" "$PROJECT_DIR/.claude/.tdd-skipped" "$PROJECT_DIR/.claude/.lfg-active"
- 这三项防护都会拒绝操作,而不会尝试修复;中止也不代表审查失败。 获取失败时重试一次,写入失败时也重试一次——写入会根据文件设置正文,而不是追加内容,因此重试不会重复写入标记;如果第二次尝试也中止,就报告情况,并在已呈现的发现仍然有效的前提下交还任务。限定重试次数很重要——针对不稳定 API 的无预算重试循环,是导致此防护所针对事故的一个未计时因素。丢失标记只会让
/closeout多一次关于审查时效性的提示。覆盖 PR 正文则会丢失事项关联信息、Closes #N行,以及/execute写入的## Review Notes——这些内容没有其他副本。 $SHORT_SHA固定为 7 个字符,而不是取自git rev-parse --short。--short选出的缩写长度不固定,会随着仓库增长而变长,因此重新写入标记可能会把 7 个字符的短格式换成 8 个字符的短格式,使正文比读取时多一个字节——此时写入防护会正确地拒绝写入,尽管这次写入本身是正确的。固定宽度,才能让防护所依赖的“绝不会更短”前提真正成立,而不是几乎成立。- HTML 注释必须携带完整的 40 位 SHA,绝不能使用短格式。 这两个占位符位于相邻行,不能互换:
<full-sha>是未截断的git rev-parse HEAD输出,<short-sha>是缩写形式,只能放在可见的说明行中。/closeout会将提取出的标记与headRefOid比较,而后者始终为 40 位,因此注释中的短 SHA 永远不可能匹配;此时,即使 PR 的 head 没有变化,门禁也会报告不一致,而一个虚惊一场的门禁只会被人一键略过。/closeout的解析器要求恰好 40 个十六进制字符,因此交换占位符不会悄悄退化为前缀匹配,而是会解析失败。在 Skill Kit 仓库中,scripts/test-review-currency-marker.sh会将此模板交由/closeout自身的提取逻辑来回验证,以此固定这项约定,避免两个技能悄然产生偏差。 - 每个 PR 恰好保留一个标记。 重新运行
/pre-merge时会替换已有区块,而不是再追加一个。/closeout会将标记作为单一值读取;两个标记会让“审查的是哪个 SHA”变得含糊,其中过时的那个答案更危险。 - 标记表示已覆盖相关提交,而非完整读取过整个树。 标记自身的可见行说明了它的含义:该 SHA 之后的提交尚未经过这些审查维度的检查。在增量范围内重新运行(Phase 1 步骤 4)即可满足这一要求,覆盖截至新 head 的每个提交——此前的审查覆盖到旧标记所对应的提交,本次审查覆盖其余提交——因此会使用同一模板,在
HEAD重新写入标记。区块中有意不记录范围:上面的防止内容变短检查会固定区块宽度,而下次对整个分支重新写入标记时,范围字段若变短就会导致操作中止。Phase 1 会改为在终端打印范围。 - 两部分都要保留。
/closeout会搜索 HTML 注释;可见行则会向快速浏览 PR 的人说明,为什么正文中会出现一个陌生的 SHA。 - 审查者模式不会写入标记,也不会触碰
.claude/.review-stamped。 它不会创建或重写 PR 正文(出于同样的原因,会跳过 Phase 2),而且其中的审查结果只是用户尚未发布的草稿——没有任何审查结果写入该 PR,因此无从认证。仅凭尚未发布的评论就锁定本地检出,会导致人在审查者并不拥有的分支上无法编辑。 - 根据这些审查结果进行的修复会让 head 超出标记所指位置。 这正是机制正常工作的表现,并非误报:这些提交确实尚未经过审查。
/closeout的步骤 2 会在合并时让这种不一致显现出来;上文写入的.claude/.review-stamped标记则会让编辑产生代价,因为它会将修复导向/fix-findings(一个未编写该代码的子 Agent),或迫使用户有意删除标记。该标记是本地且临时的;持久记录仍是审查标记,而此技能仍是唯一的写入者。 - 循环模式与作者模式一样,会写入标记。 它不会自行提交,因此 head 不会在它运行期间发生变化,上述规则也无需为循环模式设置例外。操作员根据记录进行的修复会让 head 超出标记所指位置,正如上一条所述:这些内容确实尚未经过审查。
在 Phase 4 输出的最后,如果——且仅当——此次工作产生了值得未来 /research 或 /write-a-prd 参考的持久经验,请建议在合并前将其记录到此 PR 中。 /compound 默认会将 docs/solutions/ 条目提交到这个仍处于开放状态的 PR 分支,然后返回此处重新运行,以便读取该提交并重新写入标记;这样一来,这条经验会在同一次合并前经过审查,并与代码一并原子性合并——应现在、而非 /closeout 之后的单独会话中记录。打印运行时交接行:
**Next session:** /compound
**Input:** PR #<pr-number> (capture the lesson onto this PR branch, before /closeout merges)
不要在重新运行 /compound 后再次打印此内容。 该次运行属于增量范围(Phase 1 步骤 4),其主题是 docs/solutions/ 条目——它会建议记录的经验教训,就是正在审查的提交。再次推荐它会交回给 /compound,而 /compound 又会交回到这里;两步都没有停止计数器。“当且仅当出现了值得长期保留的经验教训”这一条件正是阻止这种循环的关键,因为已记录的经验教训就不再是尚未记录的经验教训。判定依据是增量,而不是对会话的记忆:当盖章后的增量仅包含下文视为已记录经验教训的内容时,这条推荐已经处理完毕,退出方式是 /closeout。请根据范围判定步骤打印的增量来判断:
# This block runs in its own shell — $SCOPE_FROM was assigned in Phase 1 step
# 4's fenced block and does not survive here. Unset, the range below makes git
# diff HEAD against itself and print nothing, and the guard below would
# misread that empty diff as "delta is a /compound entry" on a delta that was
# never read. Same failure BASE_REF's guard at review-checklist.md:114 exists
# to catch.
: "${SCOPE_FROM:?resolve it with the Phase 1 step 4 scope-decision block before running this}"
# Capture and test for empty rather than asking `grep -qv` for the verdict.
# Two reasons, and the second is why this shape is not optional: `grep -q` exits
# at the first match and the producer dies of SIGPIPE, which `pipefail` reports
# as "no match" from a pipeline that matched (scripts/test-pipefail-safe-matchers.sh);
# and the `grep` on PATH in a Claude Code shell is a function wrapping ugrep,
# whose `-q -v` returns 1 on input that *does* contain a non-matching line —
# the exact inversion of the verdict, in the direction that suppresses the
# recommendation on a delta carrying code. `grep -v` without `-q` is correct
# under both.
outside=$(git diff --name-only "$SCOPE_FROM...HEAD" | grep -v -E '^(docs/solutions/|UBIQUITOUS_LANGUAGE\.md$)' || true)
if [ -z "$outside" ]; then
echo "delta is a /compound entry — do not re-recommend /compound; the exit is /closeout"
fi
两条路径都视为已记录的经验教训:docs/solutions/ 和根目录下的 UBIQUITOUS_LANGUAGE.md,后者由 /compound Phase 1 的 Vocabulary 检查在同一提交中暂存。增量中包含其他任何内容,都不属于这种情况;例如,一个同时暂存了 Phase 4 机制的 docs: compound 提交就不属于这种情况——主题行不能证明提交中包含什么,这也是 /closeout Step 2 的判别依据。
将 <pr-number> 替换为 Phase 2 创建的 PR 编号。如果工作只是按预先制定的计划顺利执行,没有意外、返工或不明显的决策,就跳过此行——问题正文和 PR 描述已经记录了相关情况,而没有可复用经验的 docs/solutions/ 条目只会让未来的读者养成走马观花的习惯。如有疑问,就跳过。值得记录经验教训的迹象包括:根因不明显的棘手 bug、PRD 中实际造成问题的 Rabbit Hole、存在重大权衡的架构决策,或值得复用的模式。完整的跳过清单请参阅 /compound 的“When NOT to Use”。
Phase 5:循环模式——为每项发现指定负责人
仅限循环模式。 作者模式和审查者模式在 Phase 4 结束;按设计,它们的发现仅供参考。
Phase 1–4 是传感器。如果没有任何机制来确认、调查并解决传感器报告的问题,那么报告渠道实际上就等同于根本没有传感器(Leveson,第 12 章)——而 Phase 4 的结束语本身就是“无需采取任何行动。这些内容仅供参考。” Phase 5 通过让发现变得持久来闭合这一回路:每项发现都会在 PR 的台账中占一行,而且台账会在产生它的会话结束后继续保留。
循环模式不会修复任何问题。 它负责审查、记录、收集证据并交还结果。每项发现该如何处理,由操作者决定。这一界限是设计的一部分,并非日后需要消除的限制——请参阅下文的“为什么循环模式不修复问题”。
循环由人工驱动。 循环模式不会自动运行审查轮次:它不会创建提交,因此没有增量可供它自行重新审查。操作者在处理上一轮发现后再次调用 /pre-merge --loop,才会开始新一轮审查。没有轮次上限,因为不存在需要设定上限的自动循环。
Step 1 — 在台账中记录每项发现
审查中的每项发现——review-checklist.md 中的每个维度,无论其 **Runs in:** 标记将其分配到哪个类别,以及在触发条件满足时的 /ts-audit——都必须有一行记录。这也包括 Phase 4 而非 Phase 3 运行的、由控制器负责的维度;正因如此,本句才会提到检查清单,而非给出数量:台账中的一行是一项确认,而之前的表述确认了一个本技能从未根据清单核实过的数量。不得筛选,不设严重性门槛,也不允许说“这项显然没问题”。循环模式悄然丢弃一项发现,正是 Leveson 规则所说的未获确认的报告。这也包括 Phase 4 证据检查已驳斥、因而未打印的发现:台账的输入是 Phase 3 的输出,而非 Phase 4 的输出。
发现进入台账时状态为待处理。open 表示“已记录,等待操作者决定”,这是循环模式唯一可以设置的状态。
步骤 2 — 附上证据,不要得出结论
发现项是关于代码的 主张,而不是代码本身。在验证此设计的代码库中,14 条发现项里有一条声称某个代码块“不会执行任何操作,因为修改步骤是 bash 注释”,三个独立分类器都将其评为高置信度、可采取行动。这是误报:以注释承载判断步骤的形式是本仓库中沿用已久的惯例,在 /execute 步骤 5 的验收标准回写中也以完全相同的方式使用。
因此,循环模式会逐条对照代码树检查发现项,并记录实际发现的内容,而不是应该怎么做:
- 支持或反驳该发现项的 grep 结果、文件路径和行号。
- 当发现项取决于库的语义时,记录已安装的类型定义(阶段 3 的“验证,不要臆测”规则)。
- 当代码树与主张矛盾时,添加一条简明的
refuted — <evidence>说明。
收集证据并不等于作出决定。被反驳的发现项仍会以 open 状态留在记录中,并附上反驳证据,因为将其移除本身就是一种决定,而决定应由操作人员作出。这样,操作人员看到的主张旁边会附有证明,而不是只有主张本身。
步骤 3 — 编写记录并交还
编写记录区块(如下),然后打印操作人员接手的内容:发现项数量、其中有多少条附有反驳证据,以及这些发现来自哪些维度。
/pre-merge 在循环模式下不会以不同方式盖章。循环模式不会创建提交,因此 HEAD 不会移动,阶段 4 的常规盖章规则保持不变。
处置记录
每次调用都会在全新的上下文中运行,因此如果没有持久化产物,它就会重新推导上一轮的发现项(Cohen:由自相似的审查者进行 N 次审查,并不等于 N 次独立审查)。记录就是这个产物,同时也是 Leveson 闭环规则所要求的已确认/已调查/已解决记录——一个产物,承担两项职责,原生集成于 GitHub。
它遵循 ## Review Currency 约定,但有一项有意的差异:在 PR 正文中放置一个带标记的区块,每个 PR 恰好一个,并包含机器可读的标记和人类可见的行;但盖章保存的是每次写入都会覆盖的单一当前值,而记录则是一个累积记录。
重写区块时,必须保留先前每一轮的所有行。“替换,不追加”适用于区块本身,而非其内容:第 2 轮会输出一个包含第 1 轮行和自身行的区块。若字面上进行替换并丢弃第 1 轮的行,就会毁掉本节引用 Leveson 来证明其合理性的那份记录——而且这种失败不会显现,因为剩下的区块看起来仍然完整。
## Review Disposition Ledger
Recorded by `/pre-merge` loop-mode. Every finding below has an owner. Rows marked `open` are waiting on a decision from you.
| Pass | Finding | Dimension | State | Evidence / outcome |
|---|---|---|---|---|
| 1 | Duplicate date formatter in pipeline | 1 — Deep Modules | fixed | `abc1234` — operator |
| 1 | Same pattern in `lib/export/` | 1 — Deep Modules | filed | #199 — operator |
| 1 | Stamp block "no-ops" | 7 — docs/solutions | open | refuted — `/execute` Step 5 uses the same shape ("Write verified status back to the issue") |
| 2 | Presence helper claimed as Effect Layer | 4 — Boundary Map | open | confirmed — `lib/presence.ts:12` exports a plain function |
**Checks not run this pass:** `/ts-audit` — delta is 12 changed `.ts` lines across 1 file, below the 50-line / 2-file trigger.
**标记值中不得包含尖括号。**上述字段应填写实际值(模型 ID、短 SHA、提示词名称),不得使用 <placeholder> 语法——HTML 注释中的 > 会终止简单的 `` 提取逻辑;该逻辑负责读取标记,遇到第一个占位符就会截断,之后的所有字段都会被静默丢弃。这并非假设:scripts/test-loop-ledger-markers.sh 正是针对存在此错误的模板编写的,并在首次运行时发现了它。请替换为真实值;如果某个值可能包含 >,请将其转义或编码。
本轮未运行的检查这一行用于记录被抑制的检查。跳过的检查不是发现项,也没有状态,因此不应放入表格;但完全省略这项记录会让人以为“所有检查都已运行”,而这正是记录旨在防止的静默截断问题。所有检查均已运行时,省略此行。
**Finding 单元格会在之后的某次审查中被冷读,由接手该分支的人阅读。**这是 Phase 4 读者标准适用范围最窄、也最重要的地方:用领域术语命名,并把结论放在前面,这样即使没有产生它的终端输出,该行也能独立成立——写 Duplicate date formatter in pipeline,而不是 Deep Modules issue。有两个单元格不受此标准约束。Evidence / outcome 单元格承载的是引用,而不是散文;把它填充成一句话,会掩盖操作者要检查的 grep 结果。Observation 行在这里也与 Phase 4 一样豁免——台账会记录每一项发现,因此 Observation 确实会有对应行,但命名模式说明不需要承载领域背景信息才能放进其中一行。
**State 列中的状态。**循环模式只写入 open。其余状态由操作者填写——fixed(附上提交)、filed(附上 issue 编号)、accepted(不修复,附上原因)或 dropped(说明发现有误的原因)。后续轮次中,/pre-merge 绝不会覆盖操作者的状态;它只会追加新发现,并保留已处理的行。
写入区块时,沿用 Phase 4 印章的 mktemp → 检查 fetch 的退出状态 → 读取正文 → 记录其字节数 → 编辑区块 → 如果结果更短则拒绝写入 → gh pr edit --body-file 模式。这里明确写出两个保护措施,而不是笼统地说“采用相同模式”,因为读者从可见的四个步骤还原这一模式时,最容易漏掉的正是这两个步骤——而台账是此技能最常改写的区块。gh pr edit 不会创建提交,因此台账和 review-notes 的写入不会移动 head,在评审时效性检查区间内属于无害写入。这里明确说明这一点,是因为如果未分类,“写入 PR”会被理解为会导致检查失效;而未分类的写入,正是检查在正常流程中出现首个误报的原因。
使用 sidecar 后端时,台账存放在工作项上,绝不放在 PR 中,具体位置见 references/state-backend.md § Review 记录的存放位置。每轮都会通过一条 bash "$SC" work comment "$W" --body-file <ledger> 写入整个区块,并按上述方式保留之前的行。操作者也以相同方式处理某行:评论一份状态已修改的副本,下一轮便从这份副本开始。评论无法缩短之前的评论,因此这里无需防止内容变短;而 work comment 只会在 sidecar 内提交,所以这些写入在评审时效性检查区间内也属于无害写入。没有工作项时,就没有可评论的对象:打印台账,说明它未持久化,并且不要将其写入 PR。
**哪些内容传递给下一轮,哪些内容不传递。**独立性与效率在这里相互牵制,采用以下划分来解决:
- 传递:状态和证据——哪些问题已修复、已提交 issue、已接受或已放弃,以及各自背后的 grep 结果。这些是关于代码当前状态的事实。没有这些信息,下一轮就会再次报告已解决的问题。
- **不传递:上一轮的严重性判断。**这正是锚定效应的来源。新的审查者独立地再次提出某个早先被评为轻微的问题,是一种信号;传递先前的评级则会压制这一信号。
这有意不是/handoff。该技能会将内容总结到一份临时的 mktemp 文档中,而这里两点都不合适:Leveson 规则要求的修正是“交接可核查的主张”,而不是“做出更好的总结”;并且每一次总结传递都是由上游控制器生成的有损转换。/handoff 自身的 Red Flags 已禁止在流水线技能的边界处生成文档;这条界限仍然有效。
固定评审者,否则跨轮次比较毫无意义
受委派的评审者是 AI 评判者,而 评判者是一个系统——模型 + 提示词 + 采样参数——而不是一个模型(Huyen,第 3 章)。将第 1 轮与第 3 轮比较——“这是我之前看到的同一条发现,还是一条新的发现?”——会在不知不觉中假设两次评审采用了相同的衡量方式。因此,循环模式会在账本的 loop-judge 标记中记录评审者的模型、review-checklist.md 修订版本和提示词形式。这三者中任何一项发生变化,都会开启一个新的循环,而不是继续当前循环——跨轮次比较不会跨越评判者变更。
为什么循环不会自动修复
显而易见的下一步,是让循环根据自身发现采取行动:修复本地问题,将其余问题上报。但这里有意没有采取这一步,值得说明原因,以免这条界限看起来像是疏忽。
- 低成本环节与高风险环节可以分开,且只有低成本环节有证据支撑。 驱动整个周期——重新运行评审、记住
/ts-audit、在多次调用之间保留状态——不会带来错误操作的风险。决定如何处理一条发现,则承担了全部风险。当真正的阻力在前者时,却自动化后者,颠倒了成本效益关系。 - 现有证据衡量的是一致性,而不是正确性。 FEASIBILITY 预研发现,三个独立分类器之间有 93% 的意见一致率——但意见一致不等于准确,而且同一语料中有一条发现被三个分类器都以高置信度评为可操作,实际上却是误报。目前还没有任何证据衡量自动修复是否会正确。
- 错误会累积叠加。 大约 40 项决策各自有 95% 的正确率,全部正确的概率约为 13%(Huyen)。通常的缓解方法是让错误倾向于上报——但大多数真实发现都需要判断,因此一个偏向正确的循环几乎会把所有问题都上报,毫无节省。
- 撤销的难度不对称。 为一个只负责记录的循环添加行动能力很容易;但如果它已经悄无声息地“修复”了某些内容,再移除这种能力就没那么容易了。
- 流水线自身的规则要求先以 HITL 方式证明可行。
/setup-ralph-loop已经基于完全相同的理由,将ralph.sh的评审阶段设为延后执行。在上一级应用同一条规则,是保持一致,而非畏缩。
账本让后续决策成为可能:它会积累真实发现及其真实结果,这正是自动化处置规则在获得信任前所需的语料。此项工作已作为后续事项跟踪,而非搁置——记录在 Skill Kit 仓库的 issue #190 中,其中列明了启动前所需的证据,并指出“以未实现为由关闭”也是可接受的结果。
健康信号
循环的健康信号是发现被揭示并分配了负责人,而不是剩余的发现数量。 如果每轮揭示的发现数量随着时间推移、跨运行逐渐下降,那么循环是在压制报告,而不是改进代码(Leveson:绝不要奖励较低的事件数量——应奖励报告本身)。重新校准标准,或将评审恢复为纯作者模式。
还要关注另一项指标:合并时账本中仍处于 open 状态的条目占比。如果账本中的所有条目都处于 open 状态,就意味着记录虽然写下来了,却无人理会;这只不过是多了几个步骤的、未闭环的审计流程。
循环安全覆盖
上述规则放在适用的位置,而不是集中列成清单,因此会在约束生效时被读到。这让规则集更难审核,因此下面给出了推导过程。STPA 第 1 步将不充分的控制操作分为四类,其中 四类中的三类不需要任何故障发生——这就是为什么逐项列出这些情况能发现仅列举可能的错误所遗漏的危害:
| 不充分的控制操作 | 覆盖该情况的规则 |
|---|---|
| 未提供 | 某项发现从未进入台账 → 第 1 步:每项发现都占一行,不筛选,不设严重性门槛 |
| 不安全地提供 | 循环对本应交还给操作者的发现采取行动 → 循环完全不采取行动;它唯一可以写入的状态是 open,且绝不覆盖操作者的状态 |
| 时机或顺序错误 | 某次遍历基于尚未完全应用的状态生成报告 → 循环不进行任何提交,因此不存在此类状态;操作者应用的修复发生在两次调用之间,而非调用过程中 |
| 过早停止/持续应用过久 | 发现尚未记录,循环却报告一切正常 → 健康信号反映的是已呈现的发现,而非剩余的发现;每次遍历的发现数量减少会被视为抑制发现 |
断路器:loop-mode 不会引入自己的断路器。 它不进行任何提交,也不运行自主遍历,因此 /execute 的重复失败和进展停滞规则在这里都不会触发。这些规则适用于操作者在两次调用之间进行的修复工作,而这部分已有相应规则。
审阅模式评论草稿
在审阅模式下,按照 references/comment-craft.md 将各维度的发现整理为 PR 评论文本。将草稿输出到终端(明确分组),供用户审阅和发布——此技能不得通过 gh pr comment 或 gh api 直接发布评论。用户是最终编辑者;自动发布关于他人 PR 的评论难以撤回,也绕过了 comment-craft 所要求的人际共情检查。
针对每项发现,按以下规则起草评论。完整方法见 references/comment-craft.md;以下为具体应用:
-
对每项发现执行 5P 检查。 如果问题没有正当理由,Pass(从草稿集中删除)。如果问题有效,但不属于此 PR 的范围,Postpone(作为「建议的后续事项」列出,而不是 PR 评论)。只有归为 Propose 的发现才能成为 PR 评论。
-
将严重程度映射到评论信号。 使用
review-checklist.md的严重程度分类选择前缀:Concern→needs change:(小幅修复)、needs rework:(重大重构)或align:(违反约定)Suggestion→levelup:(非阻塞式改进)Observation→ 直接删除(不暗示需要采取行动),或在值得指出时使用nitpick:
-
对需要采取行动的评论使用 Triple-R(任何阻塞性信号,以及大多数
levelup:)。请求(转换动词)、理由(客观依据——引用review-checklist.md中的原则、.d.ts行或先前的 PR)、结果(可衡量的最终状态)。理由部分要达到 Phase 4 的读者标准:引用并不等于解释,因此理由必须帮助重建上下文的作者理解来龙去脉,而不能只指出违反了哪条规则。请求和结果要简洁——它们是指令,无需扩展成段落。 -
注意语气。 将句首的「you」改为「we」。使用征询语气,不要直接命令。针对代码或其他产物,而不是作者本人。
-
为每条评论标注文件路径和行号。 没有代码定位的 PR 评论不如终端提示易于处理;沿用各维度发现中已有的文件和行号引用(尤其要遵循 Surgical Scope 的「引用具体代码块,不只回答是或否」规则)。
在终端中按以下格式输出:
## Reviewer-Mode Draft Comments — PR #<pr-number>
### Per-line comments (paste at the cited code position)
#### `path/to/file.ts:42`
**`needs change:` Move helper into existing utility module**
**Rationale** — the new `formatBillDate` in `src/pipeline/format.ts:42` duplicates `lib/dates/format.ts`'s shape. `pre-merge/review-checklist.md`'s Deep Modules dimension flags this as information leakage between two modules holding the same protocol detail.
**Result** — `formatBillDate` lives in `lib/dates/format.ts` and `src/pipeline/format.ts:42` imports it.
---
[next per-line comment]
### Top-level review summary (paste as PR-level comment)
[2-3 sentence summary of the review posture, naming the dimensions that ran and the highest-severity finding. Frame as collaborative, not adversarial — Rigby's "shippable code, not a defect tally."]
### Suggested follow-up issues (5P-Postpone — do not post in this PR)
- [Title] — out of scope for this PR; file as `<repo>/issues/new` if the team wants to track it
- [Title] — same
### Held back at 5P-Pass (no comments posted)
- [One-line note per dropped concern with the reason — kept for the user's audit, not for the PR]
如果通过 5P 检查后,每行评论数为零,审阅仍可给出一条顶层批准评论——使用协作式表述(例如「从结构角度看,可以发布了」而不是「LGTM」)。Tacke 指出,仅以 LGTM 表示批准是代码审查中的一种失败模式;如果你已检查 review-checklist.md 中的所有维度,却没有具体意见,这一结果本身就有意义,至少应说明检查了哪些维度。
如果预计会有分歧(例如,某项发现推翻了作者有意作出的选择),应先起草一条评论,提议线下进行 MMG Exchange:「我们能否简短沟通一下 X 方面的权衡,再由我留下详细评论?」而不要在 PR 上发起异议讨论。
此技能不是什么
- 不是测试运行器。 每次提交时,提交前钩子都会运行测试、类型检查和代码检查。
- 不是 Bug 查找器。
/qa会将行为 Bug 记录为 GitHub issue。 - 不是重构规划器。
/request-refactor-plan会生成 RFC 风格的重构提案。 - 不是 CI 门禁。 在任何模式下,发现的问题都仅供参考。循环模式会让这些问题持续保留,而不是将其设为合并条件——它以“每个问题都有负责人”为终点,而不是“可以安全合并”;
/closeout仍需人工确认(HITL)。 - 不是自动修复器。 循环模式会记录问题并收集证据;它不会提交任何更改,也不会作出任何决定。由操作人员处理每一行。请参阅第 5 阶段 § 为什么循环不会修复问题。
- 不是自动发布器。 审核者模式会生成供用户审核和发布的评论草稿;此技能本身不会调用
gh pr comment或gh pr review。 - 不是学习器官。 台账记录发现了什么以及后续如何处理。诊断导致缺陷漏过审查的流程问题是
/compound的职责,而台账是它的输入。
交接
- 预期输入: 已验证、可供审核并创建 PR 的实现工作(作者模式);希望审核的现有 PR 编号(审核者模式);或来自
/execute的离线交接——分支,以及它写入 PR 正文的## Review Notes(循环模式) - 产出: 一个包含沿袭信息的 PR,并标记审核所针对的头部 SHA(写入 PR 正文;如果
backend stamp返回sidecar,则写入 sidecar);此外,本地还会生成临时的.claude/.review-stamped标记,供分类钩子读取,以便将审核后修复路由到/fix-findings;以及架构审核结果(作者模式);遵循references/comment-craft.md的 PR 评论草稿(审核者模式);或同一个 PR,以及一份## Review Disposition Ledger,其中每个发现都有对应条目、负责人,以及支持或反驳该发现的证据(循环模式) - 可能转交给: 当某个发现看起来属于行为问题时转交给
/qa;当需要更深入的结构性清理时转交给/request-refactor-plan - 可能调用: 对已更改的
.ts/.tsx调用/ts-audit——仅限循环模式;作者模式和审核者模式只会提及,不会调用 - 默认后续步骤: 有值得记录的经验时,先运行
/compound——将经验记录到当前打开的 PR 上,然后返回此处,对该提交重新运行审核,让经验条目与其所解释的代码一起审核并合并,而不是未经审核就合并(如果工作严格按照预先制定的计划执行,则跳过);然后运行/closeout——确认并合并已审核的 PR,在移除工作树前切换到工作树之外,再清理已合并的分支、拉取基础分支并确认最终状态干净。没有值得记录的经验时,直接运行/closeout。仅在合并期间或之后发现的经验,仍可在合并后作为后备方案进行记录。在审核者模式下,用户审核并发布评论草稿;下一步是等待作者回应,而不是运行/closeout。在循环模式下,台账会交还给操作人员:处理所有open条目——逐一修复、提交问题、接受或丢弃;然后要么针对更新后的差异重新运行/pre-merge --loop,要么继续运行/compound,随后按上述流程重新运行/pre-merge,再运行/closeout下一步菜单。 这里是真正的分支点,因此应将下一步作为菜单提供,而不是让用户重新输入命令(参见references/next-step-menu.md)。此规则不适用于 AFK 循环模式运行——没有用户可询问,因此应打印退出摘要和交接行。在单个AskUserQuestion中,先列出推荐步骤;每个选项都应说明其结果,而不只是技能名称。在 author-mode 中,若产生了值得长期保留的经验教训:→/compoundin this PR(推荐 — 记录这条经验教训)、/closeout(合并并清理 — 没有值得记录的经验教训)、先在此分支上修复一项发现(/fix-findings <numbers>— 使用全新的修复器,然后重新运行/pre-merge,读取盖章后的增量并重新盖章)、将这些发现转为问题交接,然后记录这条经验教训。若没有产生经验教训,则优先列出 →/closeout(推荐 — 合并并清理)、先在此分支上修复一项发现(/fix-findings <numbers>— 使用全新的修复器,然后重新运行/pre-merge,读取盖章后的增量并重新盖章)、将这些发现转为问题交接,然后合并。用户选择后,将这些发现转交给/qa或/request-refactor-plan,不要在同一个选项标签中列出两个技能。当人工选择 先在此分支上修复一项发现 时,追加一个问题——要修复哪些发现——使用multiSelectAskUserQuestion,每个未解决的发现对应一个选项,标签注明其编号并用一句话概述其主张。早先一轮/fix-findings中带过来的survivedbreaker verdict 是一个独立选项,放在它所对应的发现旁边。不要预选任何选项,不要标记任何选项为推荐项,也不要在问题文本或此前任何地方建议一组编号(如“S1 到 S3?”):由模型建议一组编号、再由人工确认,相当于人工为模型的选择背书,这正是 chrislacey89/skills#190 要防止的边界。该工具限制了一个问题的选项数量(references/next-step-menu.md),因此未解决的发现超过四项时,按编号顺序提问,每个问题最多四项,每轮只问一个问题,并将所有选择一并处理。使用他们选出的准确编号调用/fix-findings,按编号顺序排列;如果他们通过“Other”作答,则按其输入顺序排列。绝不要自行补上编号,因为选择编号是人工的处置决定,不属于此技能。菜单本身的选项标签不包含发现编号,因此菜单选择不会将一组编号带入后续问题。两个菜单都保留发现处置选项,因为它与经验教训是独立的维度:某项发现是否可以暂缓,与是否产生了经验教训无关;审阅者若同时掌握两者,就必须能够明确表达这一点。如果重新运行/compound后交回来的结果中——上面的增量测试表明盖章后的增量仅包含docs/solutions/路径——即使此分支显然产生了经验教训,也应使用无经验教训菜单:经验教训已经记录在本次审阅的提交中,因此不能再次提供/compound选项,而应优先列出/closeout。 在这一组选项中,区分标准是现在还是之后,而不是严重程度——因此每个标签都应先写出用于区分的动作(修复还是交接),再以其返回的步骤收尾,而不是先写两个选项共有的步骤。在 reviewer-mode 中,选项有所不同——发布草稿评论、先线下讨论(MMG Exchange)、修改草稿评论——因为下一步取决于作者的回应,而不是/closeout。平台自带的自由文本“Other”选项就是退出通道——不要额外添加一个。
Phase 2 延后期间,应优先处理 PR。 如果本次运行延后了 Phase 2,就还没有 PR 可供 /compound 提交到其中,也没有 PR 可供 /closeout 合并。将这些选项替换为 → 打开 PR(推荐 — 使用 git push -u origin <branch> 推送分支,使其设置 upstream,并使用 Phase 2 的正文创建 PR),并保留发现处置选项。Phase 2 运行完毕后,再提供上面的菜单。
当分支尚未推送且包含已审查的修正提交时,提供折叠选项。 /fix-findings 在某个修正只针对一个分支提交时,会将其提交为 fixup! <subject>。修正提交有助于检查发现 3 是否已解决的审查者;但对下一位读者来说,这会造成干扰:他们逐个查看提交时,会先在一个提交中看到错误,过几次提交后才看到修正。当此轮为 HEAD 加盖标记、git log --format=%s "$BASE_REF..HEAD" 中有主题以 fixup! 开头,且分支未共享(阶段 2 的测试)时,添加 将修正提交折叠到它们所修正的提交中(然后重新运行 /pre-merge,即可重新加盖标记,无需重新审查)。选择此项后,运行:
: "${BASE_REF:?derive it with the Phase 1 step 3 block before running this}"
# Rebase onto the branch's own fork point, not the base's tip: a base that moved
# since the branch forked would change the tree, and the re-run would then read
# the fold as new content.
FORK_POINT=$(git merge-base "$BASE_REF" HEAD)
TREE_BEFORE=$(git rev-parse 'HEAD^{tree}')
if ! GIT_SEQUENCE_EDITOR=: git rebase -q -i --autosquash "$FORK_POINT"; then
git rebase --abort
echo "fold aborted: a fixup conflicted with a later commit; the branch is as it was" >&2
elif [ "$(git rev-parse 'HEAD^{tree}')" = "$TREE_BEFORE" ]; then
echo "folded: tree unchanged"
else
echo "folded, but the tree changed: the re-run will review the whole branch" >&2
fi
然后重新运行 /pre-merge。阶段 1 的步骤 4 会将折叠识别为仅重写历史,并重新加盖标记。分支已有上游后,不要再提供折叠选项:这些提交已被共享,而 Pro Git(第 6 章)建议在开放的 PR 上通过新提交回应审查,而不是强制推送覆盖原有提交。折叠不会改变合并策略,/closeout 仍默认采用压缩提交。