处理PR评论
用于在收到CodeRabbit、Cursor或人类评审员的审查反馈后,系统化回应每个评论线程,确保适当的归因和线程解决。
何时激活
当以下任一条件为真时激活此技能:
- 用户要求“处理PR评论”或“处理审查反馈”
- 用户提及CodeRabbit、Cursor机器人或评审员评论
- 用户正在处理PR审查中请求的修复
- 用户要求“检查PR评论”或“回应评审员”
- 在做出修复以解决审查反馈后
关键:完整工作流
大多数开发者忘记步骤4-6。此技能确保它们发生。
阶段1:发现和过滤评论
首先,运行gtg以快速获取PR状态和可操作的评论概述:
# 获取PR状态和可操作的评论
OWNER=$(gh repo view --json owner -q .owner.login)
REPO=$(gh repo view --json name -q .name)
gtg "$PR_NUMBER" --repo "$OWNER/$REPO" --format json > /tmp/gtg-status.json
# 显示状态
echo "Status: $(jq -r '.status' /tmp/gtg-status.json)"
echo "Actionable comments: $(jq -r '.actionable_comments | length' /tmp/gtg-status.json)"
echo "Unresolved threads: $(jq -r '.threads.unresolved' /tmp/gtg-status.json)"
# 列出操作项
echo "Action items:"
jq -r '.action_items[]' /tmp/gtg-status.json
为了详细过滤和分类,同时运行过滤脚本:
# 过滤可操作与非可操作评论
bin/pr-comments-filter.sh <PR_NUMBER>
此脚本:
- 过滤掉非可操作评论(确认、感谢、指纹识别)
- 按优先级(关键 → 低)分类可操作评论
- 显示用于处理的评论ID和详细信息
阶段2:分类可操作评论
过滤脚本按优先级分类:
| 优先级 | 标记 | 行动 |
|---|---|---|
| 🔴 关键 | _⚠️ Potential issue_ | _🔴 Critical_ |
立即修复 |
| 🟠 高 | _⚠️ Potential issue_ | _🟠 Major_ |
在合并前修复 |
| 🟡 中 | _🟡 Minor_ 或 _🛠️ Refactor suggestion_ | _🟠 Major_ |
应该修复 |
| 🔵 低 | _🔵 Trivial_ / _🧹 Nitpick_ |
如果快速则修复 |
| 👤 人类 | 非机器人评论 | 总是处理 |
对于每个可操作评论,进一步分类为:
- Bug/问题 - 必须修复
- 增强 - 应该修复
- 挑剔 - 最好修复
- 问题 - 需要澄清
- 有意为之 - 用解释拒绝
- 超出范围 - 见阶段2b
阶段2b:从审查主体中提取“超出差异范围”评论(关键)
⚠️ 常被遗漏:CodeRabbit在审查主体中发布“超出差异范围”评论,而不是作为内联线程。这些是必须处理的可操作反馈。
# 从审查主体中提取超出差异范围评论
PR_NUMBER=<number>
OWNER=$(gh repo view --json owner -q .owner.login)
REPO_NAME=$(gh repo view --json name -q .name)
echo "=== 超出差异范围评论 ==="
gh api "repos/$OWNER/$REPO_NAME/pulls/$PR_NUMBER/reviews" --paginate | \
jq -r '.[] | select(.body | test("Outside diff range"; "i")) |
"Review ID: \(.id)
\(.body)
---"'
这些评论不在线程中 - 无法内联回复。您必须:
- 从审查主体中解析文件路径和行号
- 在代码中处理反馈
- 提交并推送
- 留下一般PR评论确认已处理
阶段2c:处理其他超出范围评论
运行超出范围检测脚本:
# 检测超出范围评论
bin/pr-comments-out-of-scope.sh <PR_NUMBER>
此脚本检测以下评论:
- 引用不在PR差异中的行
- 被GitHub标记为“已过时”(GraphQL
isOutdated标志) - 是一般PR讨论评论
重要:默认将超出范围评论视为范围内。
评估过程
使用ultrathink评估每个超出范围评论:
ultrathink: 分析这个超出范围审查评论:
- 评审员要求什么?
- 这个更改有多复杂?(代码行数、受影响的文件)
- 是否需要重构其他系统?
- 我能在30分钟内完成吗?
- 有任何风险或依赖吗?
推荐:FIX_NOW 或 CREATE_ISSUE
决策矩阵
| 标准 | 行动 |
|---|---|
| 简单修复(< 30 分钟,< 3 文件,无重构) | FIX_NOW - 立即进行更改 |
| 中等复杂性(范围不明确) | ASK_USER - 呈现选项 |
| 重大重构(多个系统、有风险) | CREATE_ISSUE - 记录以便后续跟进 |
对于 FIX_NOW
- 进行修复
- 提交带有描述性消息
- 推送到PR分支
- 回复:“在提交 <hash> 中修复。这超出了原始PR范围,但易于处理。”
- 解决线程
对于 CREATE_ISSUE
- 创建带有上下文的GitHub问题
- 回复:“已创建问题 #<number> 以跟踪此事项。”
- 解决线程
阶段3:进行修复
修复实际代码问题。提交并推送。
阶段4:回应每个线程(常被遗忘!)
推送修复后,单独回应每个评论线程:
PR_NUMBER=<number>
OWNER=$(gh repo view --json owner -q .owner.login)
REPO_NAME=$(gh repo view --json name -q .name)
CURRENT_USER=$(gh api user -q '.login')
COMMENT_ID=<id-from-filter-script>
gh api "/repos/$OWNER/$REPO_NAME/pulls/$PR_NUMBER/comments/$COMMENT_ID/replies" \
-X POST \
-f body="Fixed in commit $(git rev-parse --short HEAD).
*(Response by Claude on behalf of @$CURRENT_USER)*"
阶段5:解决所有线程
回应后,每个线程都必须被解决。 使用GraphQL解决:
THREAD_ID="PRRT_kwDOK-xA485..." # 来自GraphQL查询
gh api graphql -f query='mutation {
resolveReviewThread(input: {threadId: "'"$THREAD_ID"'"}) {
thread { id isResolved }
}
}'
阶段6:处理无法解决的线程
- 询问评论作者请求具体跟进
- 不要留下未解决 - 在回应后解决,或请求澄清
- 如果等待作者回应,标记为需要用户输入
阶段7:推送后迭代检查(强制)
⚠️ 工作流失败的首要原因:在阶段5-6后停止而不检查新评论。
自动化评审员(CodeRabbit、Cursor)分析您推送的每个提交。他们在检查运行期间或之后发布新评论。
# 步骤1:等待所有CI/CD检查完成
PR_NUMBER=<number>
echo "⏳ 等待CI/CD检查完成..."
gh pr checks $PR_NUMBER --watch
# 步骤2:检查自上次回应以来的新评论
OWNER=$(gh repo view --json owner -q .owner.login)
REPO_NAME=$(gh repo view --json name -q .name)
CURRENT_USER=$(gh api user -q '.login')
# 获取您上次回复的时间戳
LAST_REPLY=$(gh api "repos/$OWNER/$REPO_NAME/pulls/$PR_NUMBER/comments" --paginate | \
jq -r "[.[] | select(.user.login == \"$CURRENT_USER\") | select(.in_reply_to_id)] | sort_by(.created_at) | last | .created_at")
# 处理用户无先前回复的情况
if [ -z "$LAST_REPLY" ] || [ "$LAST_REPLY" = "null" ]; then
echo "⚠️ 未找到先前回复 - 将所有评论视为新评论"
LAST_REPLY="1970-01-01T00:00:00Z" # Unix纪元 - 将所有评论视为新评论
fi
# 检查该时间之后的新评论
NEW_COUNT=$(gh api "repos/$OWNER/$REPO_NAME/pulls/$PR_NUMBER/comments" --paginate | \
jq -r --arg time "$LAST_REPLY" '[.[] | select(.in_reply_to_id == null) | select(.created_at > $time)] | length')
# 步骤3:同时检查审查主体中的新“超出差异范围”评论
NEW_REVIEWS=$(gh api "repos/$OWNER/$REPO_NAME/pulls/$PR_NUMBER/reviews" --paginate | \
jq -r --arg time "$LAST_REPLY" '[.[] | select(.submitted_at > $time) | select(.body | test("Outside diff range"; "i"))] | length')
TOTAL_NEW=$((NEW_COUNT + NEW_REVIEWS))
if [ "$TOTAL_NEW" -gt 0 ]; then
echo "🚨 检测到 $NEW_COUNT 个新内联评论 + $NEW_REVIEWS 个新审查主体评论"
echo "行动:返回阶段1并迭代"
else
echo "✅ 无新评论 - 安全进行验证"
fi
如果发现新评论:返回阶段1。不要进行验证。
迭代循环:
重复:
阶段1:发现评论
阶段2:分类
阶段3:修复
阶段4:回应
阶段5:解决线程
阶段6:处理不清晰线程
阶段7:推送后检查新评论
如果发现新评论 → 转到阶段1
如果无新评论 → 进行验证
回应模板
对于已修复
在提交 <hash> 中修复。
*(由Claude代表 @username 回应)*
对于已确认的挑剔
已确认 - 这是一个有效建议。推迟到未来的清理PR以保持此PR专注。
*(由Claude代表 @username 回应)*
对于有意决策
这是有意为之,因为[原因]。[事物]设计为[解释]。
*(由Claude代表 @username 回应)*
强制完成前检查
⚠️ 阻塞性:您必须在宣布任何PR准备就绪前运行此脚本并显示其输出:
bin/pr-comments-check.sh <PR_NUMBER>
此脚本:
- 如果所有评论已处理,返回退出码0
- 如果存在任何未处理评论,返回退出码1
- 显示每个评论的✅/❌状态
如果脚本显示任何❌,您尚未完成。 处理每个未处理评论:
- 如果可操作 → 修复并回复确认修复
- 如果超出范围 → 回复解释推迟(如果需要则创建问题)
- 如果不同意 → 回复理由
- 绝不静默忽略
您必须在回应中显示脚本输出作为所有评论已处理的证据。示例:
🔍 检查PR #908的未处理评论...
=== 内联代码审查评论 ===
✅ 评论 123 由 cursor[bot] - 1 回复
✅ 评论 456 由 coderabbitai[bot] - 1 回复
=== 一般PR讨论评论 ===
ℹ️ coderabbitai[bot]: <!-- 摘要 -->...
✅ 所有内联审查评论已处理
直到此脚本返回✅,PR才准备就绪。
验证清单
在宣布PR评论已处理前:
- [ ] 运行
bin/pr-comments-filter.sh <PR>识别可操作评论 - [ ] ⚠️ 关键:从审查主体中提取“超出差异范围”评论(阶段2b)
- [ ] 运行
bin/pr-comments-out-of-scope.sh <PR>查找其他超出范围反馈 - [ ] 代码修复已进行并推送
- [ ] 每个评论线程已发布回应
- [ ] “超出差异范围”评论已通过一般PR评论处理
- [ ] ⚠️ 推送后检查:等待CI/CD完成,检查新评论
- [ ] 推送后检查后未发现新评论(如果发现则迭代)
- [ ] 所有线程已解决(无未解决线程剩余)
- [ ] 所有回应包括适当归因
- [ ] 超出范围评论已修复或已创建GitHub问题
⚠️ 不要跳过“超出差异范围”检查(阶段2b) - 这是PR处理不完整的第二大原因。
参考
有关所有边缘案例和故障排除的完整详细工作流,见:
.claude/commands/handle-pr-comments.md