From 560116bc959ae342900b110e4a5f5402dab40df5 Mon Sep 17 00:00:00 2001 From: lishuceo Date: Sun, 24 May 2026 15:23:51 +0800 Subject: [PATCH 1/3] =?UTF-8?q?docs(pr-fixup):=20=E5=9C=A8=20Step=203=20?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=20PR=20=E9=A1=B6=E5=B1=82=20comment=20?= =?UTF-8?q?=E5=92=8C=20description=20summary=20=E6=A3=80=E6=9F=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 之前 pr-fixup 只查 reviewThreads(inline review 评论),漏了 review bot 留在 PR 主时间线或 PR description 里的整体评论,导致 nit 在下一轮 review 又会被重新提一次。现在 Step 3 分 3a/3b/3c 三个数据源拉取,并用最后一次 push 时间过滤掉历史评论。 --- .claude/skills/pr-fixup/SKILL.md | 58 +++++++++++++++++++++++++++----- 1 file changed, 49 insertions(+), 9 deletions(-) diff --git a/.claude/skills/pr-fixup/SKILL.md b/.claude/skills/pr-fixup/SKILL.md index 74c2df9..e0445c2 100644 --- a/.claude/skills/pr-fixup/SKILL.md +++ b/.claude/skills/pr-fixup/SKILL.md @@ -141,7 +141,21 @@ gh run view RUN_ID --log-failed 2>&1 | tail -100 ### Step 3: 获取未解决的 Review 评论 -通过 GraphQL 获取所有 review threads: +Review 反馈分布在三个地方,必须**都检查**,不能只看 inline review threads: + +1. **Inline review threads** — reviewer 在具体代码行上的评论(GraphQL `reviewThreads`) +2. **PR 顶层 issue comments** — review bot 经常把发现的问题汇总成一条整体评论发到 PR 主时间线(`gh pr view --json comments`) +3. **PR description** — 部分 review bot 会把 summary 写进 PR description 而非 comment + +先获取 PR 作者和最后一次 push 时间: + +```bash +gh pr view PR_NUMBER --json author,body,headRefOid -q '{author: .author.login, body: .body, sha: .headRefOid}' +# 取最后一次 push 时间用于过滤(仅处理 push 之后的新评论,避免重复响应历史评论) +LAST_PUSH=$(git log -1 --format=%cI HEAD) +``` + +**3a. Inline review threads**(GraphQL): ```bash gh api graphql -f query='{ @@ -158,6 +172,7 @@ gh api graphql -f query='{ author { login } path line + createdAt } } } @@ -167,19 +182,33 @@ gh api graphql -f query='{ }' ``` -先获取 PR 作者:`gh pr view PR_NUMBER --json author -q .author.login` → `PR_AUTHOR` - 过滤条件: - `isResolved == false`(未解决) -- 发起评论(第一条 comment)的 `author.login` **不是** PR 作者(排除自己的评论,处理所有 reviewer 的反馈,包括 bot 和人类 reviewer) +- 第一条 comment 的 `author.login` **不是** PR 作者 + +**3b. PR 顶层 issue comments**: + +```bash +gh pr view PR_NUMBER --json comments -q '.comments[] | select(.author.login != "PR_AUTHOR") | {id, body, author: .author.login, createdAt}' +``` + +过滤条件: +- `author.login` 不是 PR 作者 +- `createdAt` 在最后一次 push 之后(处理新增反馈,忽略已被旧 commit 处理的历史评论) + +**3c. PR description**: -如果没有 CI 失败(Step 2 已全部通过)且没有未解决的非作者评论 → 输出 "✅ 所有 CI checks 通过,PR review 无阻塞问题" 并结束循环。 +检查 PR body 中是否包含 review summary 关键字(如 `## Review Summary`、`### Issues Found`、`🟡`、`🔴`、`nit`、`confidence`、`Suggested Action` 等 review bot 常用标记)。如果有,把这些条目当作待处理 review feedback,与 3a/3b 一起进入 Step 4。 + +如果没有 CI 失败(Step 2 已全部通过)且 3a/3b/3c 都没有未处理的反馈 → 输出 "✅ 所有 CI checks 通过,PR review 无阻塞问题" 并结束循环。 ### Step 4: 分析并处理 Review 评论 -对于每个未解决的评论: +对于每个未解决的评论(来自 3a inline、3b issue comment、3c PR description summary): -1. **读取完整源文件**:用 Read 工具读取评论所在的 `path` 文件 +1. **读取相关源文件**: + - inline 评论:用 Read 工具读取评论所在的 `path` 文件 + - issue comment / PR description summary:从 body 中解析出涉及的文件路径(通常是 `src/foo.ts:123` 格式),逐个 Read 2. **理解评论内容**:仔细阅读 `body` 中指出的具体问题 3. **结合上下文判断**:评论是否正确? @@ -200,11 +229,18 @@ gh api graphql -f query='{ - 回复评论确认修复: ```bash +# inline review comment(来自 3a) gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_DATABASE_ID/replies \ -f body="Fixed — <简述修改内容>" + +# PR 顶层 issue comment(来自 3b)— 没有 thread,直接在 PR 主时间线新增一条 +gh pr comment PR_NUMBER --body "Fixed — <简述修改内容>(回复 #ISSUE_COMMENT_ID)" + +# PR description summary 条目(来自 3c)— 同样在 PR 主时间线回复 +gh pr comment PR_NUMBER --body "Addressed — <简述修改内容>" ``` -- Resolve 该 thread: +- Resolve 该 thread(仅 inline review thread 适用,issue comment 和 description summary 无 thread 可 resolve): ```bash gh api graphql -f query='mutation { @@ -219,11 +255,15 @@ gh api graphql -f query='mutation { 1. 回复评论说明原因: ```bash +# inline review comment(来自 3a) gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_DATABASE_ID/replies \ -f body="Not an issue — <具体解释,引用代码说明 reviewer 的判断为什么不适用于此场景>" + +# PR 顶层 issue comment / description summary(来自 3b/3c) +gh pr comment PR_NUMBER --body "Not an issue — <具体解释>" ``` -2. Resolve 该 thread: +2. Resolve 该 thread(仅 inline review thread 适用): ```bash gh api graphql -f query='mutation { From 4e1909d6ec1dacf53479a7d2f5f9eb73465b6b4c Mon Sep 17 00:00:00 2001 From: lishuceo Date: Sun, 24 May 2026 15:52:41 +0800 Subject: [PATCH 2/3] =?UTF-8?q?docs(pr-fixup):=20=E4=BF=AE=E5=A4=8D=20deep?= =?UTF-8?q?-review=20=E6=8C=87=E5=87=BA=E7=9A=84=203=20=E4=B8=AA=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - LAST_PUSH 改用 gh api 拿 head commit 的 committer date,避免本地 rebase/amend 后时间不一致 - 顶层 comment 回复链接改用完整 URL,GitHub 不会把 #COMMENT_ID 解析成 comment 跳转 - 3c PR description summary 检测改成强/弱信号双层规则,避免把普通 PR 说明里偶尔出现的 🟡 误判为 review --- .claude/skills/pr-fixup/SKILL.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/.claude/skills/pr-fixup/SKILL.md b/.claude/skills/pr-fixup/SKILL.md index e0445c2..9544a78 100644 --- a/.claude/skills/pr-fixup/SKILL.md +++ b/.claude/skills/pr-fixup/SKILL.md @@ -147,12 +147,14 @@ Review 反馈分布在三个地方,必须**都检查**,不能只看 inline r 2. **PR 顶层 issue comments** — review bot 经常把发现的问题汇总成一条整体评论发到 PR 主时间线(`gh pr view --json comments`) 3. **PR description** — 部分 review bot 会把 summary 写进 PR description 而非 comment -先获取 PR 作者和最后一次 push 时间: +先获取 PR 作者、body、head SHA,以及最后一次推送对应的 commit 时间: ```bash gh pr view PR_NUMBER --json author,body,headRefOid -q '{author: .author.login, body: .body, sha: .headRefOid}' -# 取最后一次 push 时间用于过滤(仅处理 push 之后的新评论,避免重复响应历史评论) -LAST_PUSH=$(git log -1 --format=%cI HEAD) +# 用 GitHub 上 head commit 的 committer date 作为"最后一次 push 时间"的近似值 +# 不要用本地 git log(rebase/amend 后本地时间和 GitHub 上不一致) +HEAD_SHA=$(gh pr view PR_NUMBER --json headRefOid -q .headRefOid) +LAST_PUSH=$(gh api repos/OWNER/REPO/commits/$HEAD_SHA --jq .commit.committer.date) ``` **3a. Inline review threads**(GraphQL): @@ -198,7 +200,12 @@ gh pr view PR_NUMBER --json comments -q '.comments[] | select(.author.login != " **3c. PR description**: -检查 PR body 中是否包含 review summary 关键字(如 `## Review Summary`、`### Issues Found`、`🟡`、`🔴`、`nit`、`confidence`、`Suggested Action` 等 review bot 常用标记)。如果有,把这些条目当作待处理 review feedback,与 3a/3b 一起进入 Step 4。 +检查 PR body 中是否包含 review summary。判定标准(避免把普通 PR 说明误判为 review): + +- **强信号**(出现任一即可判定):`## Review Summary`、`### Issues Found`、`## Review Notes`、`Suggested Action` +- **弱信号**(需同时出现 ≥2 个才算):`🟡`、`🔴`、`nit`、`confidence`、`severity` + +满足上述任一规则的,把 review summary 段落里的条目当作待处理 review feedback,与 3a/3b 一起进入 Step 4。 如果没有 CI 失败(Step 2 已全部通过)且 3a/3b/3c 都没有未处理的反馈 → 输出 "✅ 所有 CI checks 通过,PR review 无阻塞问题" 并结束循环。 @@ -234,7 +241,8 @@ gh api repos/OWNER/REPO/pulls/PR_NUMBER/comments/COMMENT_DATABASE_ID/replies \ -f body="Fixed — <简述修改内容>" # PR 顶层 issue comment(来自 3b)— 没有 thread,直接在 PR 主时间线新增一条 -gh pr comment PR_NUMBER --body "Fixed — <简述修改内容>(回复 #ISSUE_COMMENT_ID)" +# 回复链接用完整 URL(GitHub 不会把 #COMMENT_ID 解析成 comment 跳转) +gh pr comment PR_NUMBER --body "Fixed — <简述修改内容>(回复 [评论](https://github.com/OWNER/REPO/pull/PR_NUMBER#issuecomment-ISSUE_COMMENT_ID))" # PR description summary 条目(来自 3c)— 同样在 PR 主时间线回复 gh pr comment PR_NUMBER --body "Addressed — <简述修改内容>" From 4f0c5d568546dba2f384fafe933f6e4845a7d857 Mon Sep 17 00:00:00 2001 From: lishuceo Date: Sun, 24 May 2026 16:02:49 +0800 Subject: [PATCH 3/3] =?UTF-8?q?docs(pr-fixup):=20=E4=BF=AE=E5=A4=8D=203c?= =?UTF-8?q?=20=E5=8E=BB=E9=87=8D=E7=BC=BA=E5=A4=B1=20+=20Step=205=20?= =?UTF-8?q?=E9=80=80=E5=87=BA=E6=9D=A1=E4=BB=B6=E6=9C=AA=E8=A6=86=E7=9B=96?= =?UTF-8?q?=203b/3c?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit review bot 在 4e1909d 后再次指出: - 3c (PR description summary) 没有去重机制,PR body 静态不变,会让 同一条目在每轮循环里都被重新匹配 - Step 5 的"只有误报被 resolve"退出分支假设了 thread 存在,3b/3c 的反驳路径只 reply 不 resolve,会让循环卡在未定义状态 修复: - 3c 加 "本轮内存记录" 去重要求,处理过的条目下一轮跳过 - Step 5 退出条件改为按"已处理"判断(inline reply+resolve、 3b/3c reply 视为已处理),不再要求 resolve 动作 --- .claude/skills/pr-fixup/SKILL.md | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/.claude/skills/pr-fixup/SKILL.md b/.claude/skills/pr-fixup/SKILL.md index 9544a78..e0efd21 100644 --- a/.claude/skills/pr-fixup/SKILL.md +++ b/.claude/skills/pr-fixup/SKILL.md @@ -207,6 +207,13 @@ gh pr view PR_NUMBER --json comments -q '.comments[] | select(.author.login != " 满足上述任一规则的,把 review summary 段落里的条目当作待处理 review feedback,与 3a/3b 一起进入 Step 4。 +**去重(重要)**:3a 靠 `isResolved` 去重、3b 靠 `createdAt > LAST_PUSH` 去重,但 PR description 是静态的,Claude 修完代码 push 后 description 内容并不会变。为避免同一 `/pr-fixup` 调用内同一条 3c 条目被反复处理,必须做以下两件事之一: + +- **本轮内存记录**:在当前 `/pr-fixup` 执行流程中维护一个集合(如条目正文的前 50 字符 hash),处理过的 3c 条目下一轮直接跳过 +- **镜像到顶层 comment**:处理完 3c 条目后调用 `gh pr comment` 写一条 "Addressed (3c): <条目摘要>" 到 PR 主时间线,让后续轮次靠 3b 的 `LAST_PUSH` 过滤自动跳过 + +推荐第一种(更便宜,不污染 PR 时间线)。 + 如果没有 CI 失败(Step 2 已全部通过)且 3a/3b/3c 都没有未处理的反馈 → 输出 "✅ 所有 CI checks 通过,PR review 无阻塞问题" 并结束循环。 ### Step 4: 分析并处理 Review 评论 @@ -294,10 +301,12 @@ gh api graphql -f query='mutation { - 输出 "🔄 第 N 轮:修复 X 个 CI 问题 + Y 个 review 问题,反驳 Z 个误报,等待新一轮 checks..." - 回到 Step 1 -**如果只有误报被 resolve(无代码修复)且 CI 全部通过:** -- 输出 "✅ 第 N 轮:反驳 Y 个误报并 resolve,所有 CI checks 通过" +**如果本轮所有 review 反馈都已处理(inline 已 reply+resolve、3b/3c 已 reply)且无代码修复且 CI 全部通过:** +- 输出 "✅ 第 N 轮:处理 Y 个 review 反馈(含 Z 个反驳),所有 CI checks 通过" - 结束循环 +> 注意:3b/3c 没有 thread 可以 resolve,"已处理"的标准是已经发出 `gh pr comment` 回复。不要因为"没有 resolve 动作"就误判为未处理而陷入死循环。 + --- ## 完成汇总