关于 pr-review-loop
pr-review-loop 是一项可续接的技能,用于逐条处理拉取请求中的未解决意见。它适合希望先审核分类结果,再修改代码、回复和解决评论的开发者。
主要功能
- 01收集未解决的审查线程和讨论评论
- 02实施前对反馈分类
- 03等待用户批准分类结果
- 04逐条处理评论并运行保护检查
- 05支持常规模式和交接模式
适用场景
- 01整理进行中的拉取请求反馈
- 02依次修复获准处理的审查意见
- 03针对含糊请求向审查者澄清
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ xpepper/ pr-review-agent-skill --skill pr-review-loop安装后在 Claude Code 中输入/pr-review-loop确认已加载。
包含的文件
除 SKILL.md 外,安装时一起下载的参考资料和脚本。
README.mdreferences/handoff-mode.mdtriage-guide.md详细介绍
PR 审查循环
目的
使用一套有主见、可恢复的工作流,逐条处理所有未解决的 PR 审查评论。适用于任何审查者(人类或机器人)的评论。
常见调用方式
用户可能会通过如下提示触发此技能:
- “处理此 PR 上所有未解决的审查评论”
- “逐条处理 PR #42 的代码审查反馈”
- “修复 @alice 在此拉取请求中留下的审查评论”
- “在 PR #123 上使用 pr-review-loop”
前置条件
ghCLI(首选)。如果不可用,则改用任何可与 GitHub 交互的工具。- 必须已在本地检出 PR 分支。
模式选择
除非用户明确通过
/pr-review-loop --handoff 或类似表述(例如“以交接模式继续审查 PR”)
请求交接模式,否则使用普通模式。
- **交接模式:**阅读并遵循 references/handoff-mode.md,而不是以下流程。交接模式仍属实验性功能,会在一个明确命名的范围完成后主动停止,以便用户开始新的会话。
- **普通模式:**按原样遵循以下现有流程。如果存在
.pr-review/HANDOFF.md,则提醒一次:普通模式可能会使其中的状态过时,然后忽略该文件。不要读取、更新或删除它。
绝不要仅因交接文件存在就推断用户选择了交接模式。每次新会话都必须由用户明确选择该模式。
流程
步骤 1 — 预检
检查项目中的以下文件(如果存在),以了解防护规范:
CLAUDE.md、AGENTS.mdMakefile.github/workflows/README.md
确定所有必需的防护措施(测试、编译、代码检查、格式化等)。 全部运行。如果任何一项失败,立即停止并报告——不要在基线已损坏的情况下继续。
步骤 2 — 获取当前 PR 编号和基本信息
# Get current PR details
gh pr status
# View PR with all comments
gh pr view
步骤 3 — 收集所有未解决的 PR 反馈
GitHub 以两种不同形式提供审查者反馈——必须同时收集:
| 类型 | 所在位置 | 有“解决”操作吗? |
|---|---|---|
| 审查线程 | 锚定到文件/行的内联评论,作为审查的一部分提交 | 有 — resolveReviewThread mutation |
| Issue 评论 | PR 对话中的普通评论(例如作为顶层评论发布的摘要审查) | 无 — 回复即可表示已处理 |
获取审查线程(仅保留未解决的线程):
gh api graphql -f query='
query($owner:String!, $name:String!, $number:Int!) {
repository(owner:$owner, name:$name) {
pullRequest(number:$number) {
reviewThreads(first:100) {
nodes {
id
isResolved
path
line
comments(first:20) {
nodes {
id
databaseId
author { login }
body
createdAt
replyTo { databaseId }
}
}
}
}
}
}
}' -f owner='{owner}' -f name='{repo}' -F number={pr_number} \
| jq '.data.repository.pullRequest.reviewThreads.nodes | map(select(.isResolved == false)) | map(. + {type: "review-thread"})'
获取 Issue 评论(PR 对话中的顶层评论):
gh api repos/{owner}/{repo}/issues/{pr_number}/comments \
| jq '[.[] | {id: .id, author: .user.login, body: .body, created_at: .created_at, type: "issue-comment"}]'
开始分类前,过滤掉 PR 作者和已知机器人账号发布的评论(这些不属于审查者反馈)。转换后输出中的
author字段可帮助识别。
对两个列表中的条目进行分类。记录每个条目的类型——这会影响如何回复(步骤 6f)以及如何关闭(步骤 6g)。
步骤 4 — 分类
阅读分类指南,了解具体的分类框架和示例。如果该指南不可用,则根据自己的判断使用 MUST_FIX / SHOULD_FIX / PARK / OUT_OF_SCOPE / NEEDS_CLARIFICATION 分类(定义见下文)。
将每条未解决的评论分类为:MUST_FIX、SHOULD_FIX、PARK、OUT_OF_SCOPE 或 NEEDS_CLARIFICATION。
先对所有评论完成分类,再开始处理任何一条。
如果评论无需采取行动/不产生操作(例如确认、赞许、仅含表情符号),则分类为 OUT_OF_SCOPE。简短的确认回复及解决操作应在分类获得批准后,于处理阶段(步骤 6)执行,而不是现在。
如果评论的意图确实模棱两可——存在多种解读,且每种解读都会导致实质不同的改动——则分类为 NEEDS_CLARIFICATION,而不是自行猜测。
如果有 Perplexity 或其他研究工具可用,且分类评论需要外部知识(例如库的惯用写法、语言规范),则使用这些工具辅助判断。
步骤 5 — 呈现分类结果并等待批准
到此为止。用户批准分类结果前,不要开始处理。
将每条评论分类后,向用户呈现完整的分类结果,并等待用户批准。在用户回复前,不要修改代码、创建计划文件、发布回复或解决任何问题。
用紧凑的表格呈现,每条未解决的评论占一行:
| # | 参考 | 分类 | 理由 |
|---|---|---|---|
| 1 | @author file:line(评审线程)或简短摘录(问题评论) | MUST_FIX / SHOULD_FIX / PARK / OUT_OF_SCOPE / NEEDS_CLARIFICATION | 一行说明,依据步骤 1 中找到的代码库约定 / Agent 指南 |
理由部分应体现你是在质疑反馈,而不是盲目接受:说明每条评论是接受、调整还是驳回,并给出与项目约定相关的原因。我们的共同目标是合并质量尽可能高——认真对待反馈,但如果建议与项目既定约定相冲突,不要直接照做,务必在此说明。
交付顺序——先总览,再逐行确认。
- 总览。 在聊天中以渲染后的 Markdown 呈现完整的分类表。
- 逐行确认。 如果运行环境提供交互式提问工具(Claude Code 中的
AskUserQuestion、GitHub Copilot CLI 中的ask_user、其他环境中的request_user_input或等效工具),则每行提一个问题,并在一次调用中提交一批中的所有问题。MUST_FIX 行排在最前;只有在工具的单次调用有数量限制时才分多次调用(Claude Code:4 个问题)。调用前输出的文本可能不会显示(Copilot CLI 可能会将其折叠到隐藏的思考区),而且这些工具会以纯文本呈现内容,因此每个问题都必须独立成句,不使用表格、竖线、粗体或反引号:- 标签: 行号和简短参考,例如
#3 api.rs。 - 问题: 评论者、位置、对评论的一句话概述、建议的分类及其理由。
- 选项: 建议的分类,并按工具支持的方式标记为推荐选项(第一个选项标为“(推荐)”,或将该字段设为默认值),然后列出两到三个最可能的替代选项。不要添加“其他”或“讨论”选项:用户可通过工具的自由文本回答讨论某一行,或选择未列出的分类。
- 共享消息: 如果工具支持共享消息(Copilot 表单中的
message),就在其中按行写出简短总览,每行一条。
- 标签: 行号和简短参考,例如
- 没有提问工具: 以表格和下面这句话结束:“回复 go 批准,或指出要修改的行。”然后等待用户在聊天中回复。
已取消、拒绝或内容为空的提示都不代表批准,即使工具提示可使用默认值继续也不例外。将表格重新打印为本轮的最终输出,并等待用户在聊天中回复。
对于每一行,用户可以:
- 批准建议的分类。
- 更改分类(例如:“#5 应为 OUT_OF_SCOPE,因为约定是 X”)。应用更改后的分类。
- 讨论:用自由文本提问或提出异议,而不是给出分类。回答后,只针对该行再次提问。
只有当所有行的分类都已获批准后,才能进入步骤 6。如果用户的更改大幅影响分类结果,先重新打印更新后的表格。
步骤 6 — 每次处理一条评论
按顺序处理:先处理所有 MUST_FIX,再处理 SHOULD_FIX。
对于 OUT_OF_SCOPE 的无需处理评论(致谢、称赞、仅含表情符号):发布简短的致谢回复并解决讨论串(步骤 6f–6g),然后继续处理。暂时跳过 PARK 和真正的 OUT_OF_SCOPE 建议——这些会延后处理,并在摘要中报告。
NEEDS_CLARIFICATION 评论: 在讨论串中回复一个重点明确的问题(参见下方格式),不要解决讨论串,然后继续处理下一条评论。不要实施任何修改。
级联评论: 当多条评论构成级联关系时(例如,修改 trait 签名需要更新所有 impl、调用方和测试),将它们合并为一个提交,并引用所有评论 ID。以原子方式完整实施整个级联修改——只应用其中任意一条评论都会导致代码不一致。
对于每条评论(或每组级联评论):
6a. 评估复杂度
这是简单修改吗(例如,重命名函数、修正拼写错误、调整格式)?
- 是 → 直接修复,无需计划
- 否 → 在修改任何代码之前,在
.pr-review/plan-<comment-id>.md创建计划文件
计划文件必须说明:
- 评论要求什么
- 修复方案
- 将要修改的文件
6b. 运行安全检查
运行步骤 1 中确定的所有安全检查。在修改任何代码之前,这些检查都必须通过。 如果检查未通过,请停止并报告。
6c. 修复、暂缓或请求澄清
- 修复:实施修改
- 调整后修复:如果审查者的建议方向正确,但具体代码无法编译或因其他原因不可行,则实施最接近的可行方案。在回复中(步骤 6f)说明限制,让审查者了解实现为何与其建议不同。
- 暂缓(如果在修复过程中发现应当暂缓):说明理由,撤销任何部分修改,不提交
- 请求澄清(如果在评估过程中发现意图确实含糊不清):在讨论串中提出一个重点明确的问题(参见下方),不要解决,不提交,然后继续处理
澄清问题格式(适用于 NEEDS_CLARIFICATION 或评估过程中发现的不确定情况):
cat > /tmp/pr-review-reply-{comment_id}.md <<'EOF'
Thanks for the feedback! Before I make a change, I want to make sure I understand what you're after:
<one specific, focused question — e.g. "Did you mean to rename this everywhere, or just at the call site?" or "Would you prefer approach A (…) or approach B (…)?">
EOF
jq -n --rawfile body /tmp/pr-review-reply-{comment_id}.md '{body:$body}' > /tmp/pr-review-reply-{comment_id}.json
gh api repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies \
--input /tmp/pr-review-reply-{comment_id}.json
只提出一个问题。除非确有必要来说明问题,否则不要列出备选方案。保持讨论串未解决状态,以便审查者的答复再次提醒处理。
6d. 再次运行安全检查
运行所有安全检查。所有检查都必须通过。 如果检查未通过,请在继续之前修复回归问题——不要跳过此步骤。
6e. 提交并推送
每条评论各自提交一个聚焦的提交。在提交信息正文中注明评论作者。
git add <changed files>
git commit -m "<conventional commit message describing the fix>
Addresses PR comment from @<reviewer>."
git push
跨多条评论的提交流程示例:
# Comment 1: Add missing documentation
git commit -m "docs: add module-level documentation for MetricsRecorder
Addresses PR comment from @reviewer about missing module docs."
git push
# Comment 2: Use Duration instead of i64
git commit -m "refactor: use Duration type for timing parameters
Addresses PR comment from @reviewer - improves type safety."
git push
6f. 回复 PR 评论
发布回复,说明:
- 做了什么(修复时:注明提交)
- 暂缓的原因(适用于延后处理的项目)
- 拒绝的原因(适用于范围外的项目)
回复方式因评论类型而异:
对于审查讨论串评论(内联评论,锚定到文件/行):
cat > /tmp/pr-review-reply-{comment_id}.md <<'EOF'
<reply text>
EOF
jq -n --rawfile body /tmp/pr-review-reply-{comment_id}.md '{body:$body}' > /tmp/pr-review-reply-{comment_id}.json
# POST the reply and capture the response — do NOT run this twice
gh api repos/{owner}/{repo}/pulls/{pull_number}/comments/{comment_id}/replies \
--input /tmp/pr-review-reply-{comment_id}.json > /tmp/pr-review-reply-{comment_id}-response.json
对于 issue 评论(PR 对话中的顶层评论):
cat > /tmp/pr-review-reply-{comment_id}.md <<'EOF'
<reply text — quote or reference the original comment so context is clear>
EOF
jq -n --rawfile body /tmp/pr-review-reply-{comment_id}.md '{body:$body}' > /tmp/pr-review-reply-{comment_id}.json
gh api repos/{owner}/{repo}/issues/{pr_number}/comments \
--input /tmp/pr-review-reply-{comment_id}.json > /tmp/pr-review-reply-{comment_id}-response.json
注意:这会在 PR 对话中创建一条新的顶层评论,不会归在原评论的讨论串下——引用相关内容可以保留上下文。
6f.1 验证回复正文
立即使用 POST 响应中的 ID,通过 GET 验证已发布的内容(绝不要重新运行 POST 来验证——这会创建重复内容):
对于审查讨论串回复:
NEW_REPLY_ID=$(jq '.id' /tmp/pr-review-reply-{comment_id}-response.json)
gh api repos/{owner}/{repo}/pulls/comments/$NEW_REPLY_ID | jq '{id, body, html_url}'
对于 issue 评论回复:
NEW_COMMENT_ID=$(jq '.id' /tmp/pr-review-reply-{comment_id}-response.json)
gh api repos/{owner}/{repo}/issues/comments/$NEW_COMMENT_ID | jq '{id, body, html_url}'
6g. 解决评论
对于审查讨论串评论——在 GitHub 上将其标记为已解决。
首先,查找评论的讨论串 ID:
gh api graphql -f query='
query {
repository(owner: "{owner}", name: "{repo}") {
pullRequest(number: {pr_number}) {
reviewThreads(first: 50) {
nodes {
id
isResolved
comments(first: 1) { nodes { body } }
}
}
}
}
}'
然后解决讨论串:
gh api graphql -f query='
mutation {
resolveReviewThread(input: {threadId: "{thread_id}"}) {
thread { id isResolved }
}
}'
验证是否已解决:
gh api graphql -f query='
query {
node(id: "{thread_id}") {
... on PullRequestReviewThread {
id
isResolved
}
}
}'
对于 issue 评论——没有“解决”机制。步骤 6f 中发布的回复即为结束信号。在步骤 9 的摘要中注明。
6h. 删除计划文件
如果创建了计划文件,请将其删除:
rm .pr-review/plan-<comment-id>.md
第 7 步 — 处理循环的停止条件
当不再有 MUST_FIX 或 SHOULD_FIX 评论时停止。
如果你希望一次性解决所有讨论串,而不是逐个处理,可以在这里操作:
gh api graphql -f query='
query {
repository(owner: "{owner}", name: "{repo}") {
pullRequest(number: {pr_number}) {
reviewThreads(first: 50) {
nodes { id isResolved comments(first: 1) { nodes { body } } }
}
}
}
}' | jq -r '.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved == false) | .id' \
| while read thread_id; do
gh api graphql -f query="mutation { resolveReviewThread(input: {threadId: \"$thread_id\"}) { thread { id } } }"
done
第 8 步 — 检查 PR 正文是否偏离
发布摘要前,检查 PR 的提交是否已导致其范围偏离正文所述内容。请与 PR 的基础分支比较,而不是与本次运行的起点比较——正文应描述整个 PR;在新的上下文中恢复时,可以重新获取基础分支。
# The PR's base branch (e.g. main)
base=$(gh pr view {pr_number} --json baseRefName --jq '.baseRefName')
# All commits the PR adds on top of its base
git log "origin/$base..HEAD" --oneline
# The current PR body
gh pr view {pr_number} --json body --jq '.body'
偏离 = 正文会误导读者,使其对 PR 包含的内容产生错误认识:行为有所新增或移除、方案有所改变,或正文未提及的范围。
不算偏离 = 不改变 PR 所述意图的纯 Bug 修复调整。不要无谓地反复修改正文。
如果发现偏离,请更新 PR 正文,并保留仍然准确的部分:
cat > /tmp/pr-body.md <<'EOF'
<updated PR body>
EOF
gh pr edit {pr_number} --body-file /tmp/pr-body.md
在第 9 步的摘要中记录此次更新(添加一行“已更新 PR 正文以反映……”)。第 5 步的分类审批已批准此操作,且摘要中会说明,因此无需另行确认。
第 9 步 — 摘要
在 PR 上发布最终评论,总结:
## PR Review Loop — Summary
### Fixed
- [commit abc1234] Renamed `foo` to `bar` (comment by @alice)
- ...
### Parked
- Refactor of X module deferred — tracked in #<issue> (comment by @bob)
- ...
### Rejected
- Suggestion to use Y rejected: project convention is Z (comment by @carol)
- ...
### Awaiting Clarification
- Asked @alice: "Did you mean to rename this everywhere, or just at the call site?" — thread left open
- ...
### PR Body
- Updated PR body to reflect <what changed> (or omit this section if no drift)
没有条目的部分请省略。
使用正文文件以避免 shell 插值:
cat > /tmp/pr-review-summary.md <<'EOF'
<summary text>
EOF
gh pr comment {pr_number} --body-file /tmp/pr-review-summary.md
然后核实已发布的摘要正文:
gh pr view {pr_number} --comments
可恢复性
此技能旨在支持随时中断,并在新的上下文中重新启动。
启动时:
- 运行预检(第 1 步)
- 从 GitHub 重新获取所有审查讨论串和 Issue 评论(第 3 步)——已解决的讨论串不会出现;对于 Issue 评论,查找发布在原评论之后的评论,其正文引用了原评论或遵循回复模板。这只是尽力而为,可靠性本质上不如自动筛选的
isResolved机制——如果不确定,重新阅读回复比跳过更安全 - 检查是否存在
.pr-review/plan-*.md文件——如果存在,表示你正在处理该评论的修复;从第 6b 步继续 - 如果上次运行被中断,请先核实最新的 Issue/评论正文和讨论串状态(以捕获部分发布或格式错误的远程写入),再继续
- 对剩余评论进行分类并继续
分类审批关卡(第 5 步)属于对话状态,不会持久化。在新的上下文中恢复时,处理前会再次展示该关卡——重新提示是安全的,因为每项修复都会在继续之前提交并推送,因此不会丢失任何工作。
这意味着进度永远不会丢失。每项修复都会在继续之前提交并推送。
状态目录
仓库根目录中的 .pr-review/(由项目添加到 gitignore)。
plan-<comment-id>.md— 当前正在处理的评论的计划(解决评论后删除)
请勿
- 未经用户批准分类结果(第 5 步)就开始处理评论
- 将所有 PR 反馈打包成一个大型提交
- 在一次提交中进行多项无关更改
- 不经中间提交就一次性推送所有更改
- 处理完评论后仍将其留为未解决状态