mcpField
P
pr-review-loop

pr-review-loop

github.com/xpepper测试与质量6302 次安装3 天前更新
代码仓库

关于 pr-review-loop

pr-review-loop 是一项可续接的技能,用于逐条处理拉取请求中的未解决意见。它适合希望先审核分类结果,再修改代码、回复和解决评论的开发者。

主要功能

  1. 01收集未解决的审查线程和讨论评论
  2. 02实施前对反馈分类
  3. 03等待用户批准分类结果
  4. 04逐条处理评论并运行保护检查
  5. 05支持常规模式和交接模式

适用场景

  1. 01整理进行中的拉取请求反馈
  2. 02依次修复获准处理的审查意见
  3. 03针对含糊请求向审查者澄清

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/xpepper/pr-review-agent-skill --skill pr-review-loop

安装后在 Claude Code 中输入/pr-review-loop确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

README.md
references/
handoff-mode.md
triage-guide.md

详细介绍

PR 审查循环

目的

使用一套有主见、可恢复的工作流,逐条处理所有未解决的 PR 审查评论。适用于任何审查者(人类或机器人)的评论。

常见调用方式

用户可能会通过如下提示触发此技能:

  • “处理此 PR 上所有未解决的审查评论”
  • “逐条处理 PR #42 的代码审查反馈”
  • “修复 @alice 在此拉取请求中留下的审查评论”
  • “在 PR #123 上使用 pr-review-loop”

前置条件

  • gh CLI(首选)。如果不可用,则改用任何可与 GitHub 交互的工具。
  • 必须已在本地检出 PR 分支。

模式选择

除非用户明确通过 /pr-review-loop --handoff 或类似表述(例如“以交接模式继续审查 PR”) 请求交接模式,否则使用普通模式。

  • **交接模式:**阅读并遵循 references/handoff-mode.md,而不是以下流程。交接模式仍属实验性功能,会在一个明确命名的范围完成后主动停止,以便用户开始新的会话。
  • **普通模式:**按原样遵循以下现有流程。如果存在 .pr-review/HANDOFF.md,则提醒一次:普通模式可能会使其中的状态过时,然后忽略该文件。不要读取、更新或删除它。

绝不要仅因交接文件存在就推断用户选择了交接模式。每次新会话都必须由用户明确选择该模式。

流程

步骤 1 — 预检

检查项目中的以下文件(如果存在),以了解防护规范:

  • CLAUDE.md、AGENTS.md
  • Makefile
  • .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 指南

理由部分应体现你是在质疑反馈,而不是盲目接受:说明每条评论是接受、调整还是驳回,并给出与项目约定相关的原因。我们的共同目标是合并质量尽可能高——认真对待反馈,但如果建议与项目既定约定相冲突,不要直接照做,务必在此说明。

交付顺序——先总览,再逐行确认。

  1. 总览。 在聊天中以渲染后的 Markdown 呈现完整的分类表。
  2. 逐行确认。 如果运行环境提供交互式提问工具(Claude Code 中的 AskUserQuestion、GitHub Copilot CLI 中的 ask_user、其他环境中的 request_user_input 或等效工具),则每行提一个问题,并在一次调用中提交一批中的所有问题。MUST_FIX 行排在最前;只有在工具的单次调用有数量限制时才分多次调用(Claude Code:4 个问题)。调用前输出的文本可能不会显示(Copilot CLI 可能会将其折叠到隐藏的思考区),而且这些工具会以纯文本呈现内容,因此每个问题都必须独立成句,不使用表格、竖线、粗体或反引号:
    • 标签: 行号和简短参考,例如 #3 api.rs。
    • 问题: 评论者、位置、对评论的一句话概述、建议的分类及其理由。
    • 选项: 建议的分类,并按工具支持的方式标记为推荐选项(第一个选项标为“(推荐)”,或将该字段设为默认值),然后列出两到三个最可能的替代选项。不要添加“其他”或“讨论”选项:用户可通过工具的自由文本回答讨论某一行,或选择未列出的分类。
    • 共享消息: 如果工具支持共享消息(Copilot 表单中的 message),就在其中按行写出简短总览,每行一条。
  3. 没有提问工具: 以表格和下面这句话结束:“回复 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. 运行预检(第 1 步)
  2. 从 GitHub 重新获取所有审查讨论串和 Issue 评论(第 3 步)——已解决的讨论串不会出现;对于 Issue 评论,查找发布在原评论之后的评论,其正文引用了原评论或遵循回复模板。这只是尽力而为,可靠性本质上不如自动筛选的 isResolved 机制——如果不确定,重新阅读回复比跳过更安全
  3. 检查是否存在 .pr-review/plan-*.md 文件——如果存在,表示你正在处理该评论的修复;从第 6b 步继续
  4. 如果上次运行被中断,请先核实最新的 Issue/评论正文和讨论串状态(以捕获部分发布或格式错误的远程写入),再继续
  5. 对剩余评论进行分类并继续

分类审批关卡(第 5 步)属于对话状态,不会持久化。在新的上下文中恢复时,处理前会再次展示该关卡——重新提示是安全的,因为每项修复都会在继续之前提交并推送,因此不会丢失任何工作。

这意味着进度永远不会丢失。每项修复都会在继续之前提交并推送。

状态目录

仓库根目录中的 .pr-review/(由项目添加到 gitignore)。

  • plan-<comment-id>.md — 当前正在处理的评论的计划(解决评论后删除)

请勿

  • 未经用户批准分类结果(第 5 步)就开始处理评论
  • 将所有 PR 反馈打包成一个大型提交
  • 在一次提交中进行多项无关更改
  • 不经中间提交就一次性推送所有更改
  • 处理完评论后仍将其留为未解决状态

评论

支持 Markdown