mcpField
F
fetch-pr-feedback

fetch-pr-feedback

github.com/existential-birds测试与质量8568 次安装11 周前更新
代码仓库

关于 fetch-pr-feedback

fetch-pr-feedback 是用于收集拉取请求评论并交由 receive-feedback 技能处理的技能。它按审阅者整理反馈,默认排除 PR 作者、当前用户及已解决讨论串中的行内评论。

主要功能

  1. 01获取问题评论与审查评论
  2. 02指定 PR 编号或当前分支的 PR
  3. 03默认排除作者和当前用户
  4. 04可选择纳入已解决讨论串评论
  5. 05按审阅者整理反馈

适用场景

  1. 01收集当前分支 PR 的反馈
  2. 02查看指定 PR 的评论
  3. 03选择纳入作者或已解决讨论串评论

安装

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

$npx skills add https://github.com/existential-birds/beagle --skill fetch-pr-feedback

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

详细介绍

获取 PR 反馈

获取当前 PR 中所有评审者的评论,对其进行格式化,并使用 receive-feedback 技能进行评估。默认排除 PR 作者和当前用户。默认也排除属于已解决评审讨论串的行级评论。

用法

调用 fetch-pr-feedback 技能,可选传入以下标志:

fetch-pr-feedback [--pr <number>] [--include-author] [--include-resolved]

标志:

  • --pr <number> - 目标 PR 编号(默认:当前分支的 PR)
  • --include-author - 包含 PR 作者自己的评论(默认:排除)
  • --include-resolved - 包含来自已解决评审讨论串的行级评论(默认:排除)

说明

门槛(按顺序执行;不得跳过)

仅当满足每项 通过条件 后,才能继续。

  1. PR 上下文 — 通过条件: $PR_NUMBER 已设置为正整数,且针对该 PR 的 gh pr view / gh api 执行完成,退出代码为 0;或者在 获取 PR 上下文 步骤中停止,并且只显示该步骤中给出的失败信息(“当前分支未找到 PR…”)。
  2. 获取评论 — 通过条件: 已解决讨论串的 GraphQL 调用(若指定 --include-resolved 则跳过)以及两次分页的 gh api … | jq -s -f … 执行(Issue 评论 + 评审评论)均以退出代码 0 结束,且解析为 JSON(空数组 [] 也有效)。若退出代码非零或 jq 出错,则停止;显示命令的 stderr,不得编造评论。
  3. 格式化结果 — 通过条件: 输出必须是以下两种情况之一:(a) 符合 格式化反馈文档 的 Markdown(标题为 # PR #$PR_NUMBER Review Feedback,每位评审者均有 ## Reviewer: …,并包含 Summary / Line-Specific 小节);或 (b) 完全等于:No review comments found on this PR (excluding PR author, current user, and resolved threads).
  4. 加载 receive-feedback — 通过条件: 已加载 receive-feedback 技能;只有此时才能针对该格式化文档运行该技能的 verify → evaluate → execute 循环。
1. 解析参数

从 $ARGUMENTS 提取标志:

  • --pr <number> 或从当前分支检测
  • --include-author 标志(布尔值,默认 false)
  • --include-resolved 标志(布尔值,默认 false)
2. 获取 PR 上下文
# If --pr was specified, use that number directly
# Otherwise, get PR for current branch:
gh pr view --json number,headRefName,url,author --jq '{number, headRefName, url, author: .author.login}'

# Get repo owner/name
gh repo view --json owner,name --jq '{owner: .owner.login, name: .name}'

# Get current authenticated user
gh api user --jq '.login'

保存为 $PR_NUMBER、$PR_AUTHOR、$OWNER、$REPO、$CURRENT_USER。

注意: $OWNER、$REPO 等是占位符。请使用前面步骤中的实际值替换。

如果当前分支不存在 PR,则失败并显示:“当前分支未找到 PR。使用 --pr 指定 PR 编号。”

3. 获取评论

获取这两类评论,并排除 $PR_AUTHOR 和 $CURRENT_USER(除非设置了 --include-author)。使用 --paginate 和 jq -s 将分页后的 JSON 数组合并为一个数组。

除非设置了 --include-resolved,否则先获取属于已解决评审讨论串的每条评审评论的 databaseId。Issue 评论(摘要/演练说明)不属于评审讨论串,因此这只会影响行级评审评论。

已解决讨论串的评论 ID(如果设置了 --include-resolved,则完全跳过此块;改为设置 RESOLVED_IDS='[]'):

RESOLVED_IDS=$(gh api graphql \
  -F owner="$OWNER" -F repo="$REPO" -F pr="$PR_NUMBER" \
  -f query='
    query($owner: String!, $repo: String!, $pr: Int!) {
      repository(owner: $owner, name: $repo) {
        pullRequest(number: $pr) {
          reviewThreads(first: 100) {
            nodes {
              isResolved
              comments(first: 100) { nodes { databaseId } }
            }
          }
        }
      }
    }
  ' \
  --jq '[.data.repository.pullRequest.reviewThreads.nodes[]
         | select(.isResolved)
         | .comments.nodes[].databaseId
         | select(. != null)]')

first: 100 的限制足以覆盖一般 PR。对于非常大的 PR(超过 100 个讨论串,或单个讨论串中超过 100 条评论),需要添加讨论串/评论级分页;目前这是已知限制。

使用单引号分隔符的 heredoc 将 jq 过滤器写入临时文件(避免 !=、正则表达式模式和尖括号导致的 shell 转义问题):

Issue 评论(摘要/演练说明):

cat > /tmp/issue_comments.jq << 'JQEOF'
def clean_body:
  gsub(".*?"; ""; "s")
  | gsub(""; ""; "s")
  | gsub("<details>\\s*<summary>\\s*🧩 Analysis chain[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>\\s*🤖 Prompt for AI Agents[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>\\s*📝 Committable suggestion[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>Past reviewee.*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>Recent review details[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>\\s*Tips\\b.*?</details>"; ""; "s")
  | gsub("\\n?\\n---\\n+(?=\\s*(?:✨\\s*Finishing Touches|🪧\\s*Tips?|🤖\\s*Generated|<sub>|_Generated by|Generated by Claude|Generated with))[\\s\\S]*$"; ""; "s")
  | gsub("^\\s+|\\s+$"; "")
  | if length > 4000 then .[:4000] + "\n\n[comment truncated]" else . end
;
[(add // []) | .[] | select(
  .user.login != $pr_author and
  .user.login != $current_user
)] |
map({id, user: .user.login, body: (.body | clean_body), created_at})
JQEOF

gh api --paginate "repos/$OWNER/$REPO/issues/$PR_NUMBER/comments" | \
  jq -s --arg pr_author "$PR_AUTHOR" --arg current_user "$CURRENT_USER" \
  -f /tmp/issue_comments.jq

评审评论(行级):

cat > /tmp/review_comments.jq << 'JQEOF'
def clean_body:
  gsub(".*?"; ""; "s")
  | gsub(""; ""; "s")
  | gsub("<details>\\s*<summary>\\s*🧩 Analysis chain[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>\\s*🤖 Prompt for AI Agents[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>\\s*📝 Committable suggestion[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>Past reviewee.*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>Recent review details[\\s\\S]*?</details>"; ""; "s")
  | gsub("<details>\\s*<summary>\\s*Tips\\b.*?</details>"; ""; "s")
  | gsub("\\n?\\n---\\n+(?=\\s*(?:✨\\s*Finishing Touches|🪧\\s*Tips?|🤖\\s*Generated|<sub>|_Generated by|Generated by Claude|Generated with))[\\s\\S]*$"; ""; "s")
  | gsub("^\\s+|\\s+$"; "")
  | if length > 4000 then .[:4000] + "\n\n[comment truncated]" else . end
;
[(add // []) | .[] | select(
  .user.login != $pr_author and
  .user.login != $current_user and
  (.id as $comment_id | ($resolved_ids | index($comment_id) | not))
)] |
map({
  id,
  user: .user.login,
  path,
  line_display: (
    .line as $end | .start_line as $start |
    if $start and $start != $end then "\($start)-\($end)"
    else "\($end // .original_line)" end
  ),
  body: (.body | clean_body),
  created_at
})
JQEOF

gh api --paginate "repos/$OWNER/$REPO/pulls/$PR_NUMBER/comments" | \
  jq -s --arg pr_author "$PR_AUTHOR" --arg current_user "$CURRENT_USER" \
  --argjson resolved_ids "$RESOLVED_IDS" \
  -f /tmp/review_comments.jq

如果设置了 --include-author,则从这两个 jq 过滤器文件中省略 --arg pr_author 参数和 .user.login != $pr_author 条件。无论哪种情况,都保留 $current_user 排除条件。当 RESOLVED_IDS='[]'(即 --include-resolved 路径)时,$resolved_ids 过滤器自然不起作用,因此保留即可。

4. 格式化反馈文档

剔除噪声——由第 3 步中的 clean_body jq 函数处理。顺序很重要:首先移除 ... 代码块,然后移除剩余的 HTML 注释,接着移除已知的噪声 <details> 块(Analysis chain、Prompt for AI Agents、Committable suggestion、Past reviewee、Recent review details、Tips),最后移除 --- 页脚模板。必须在 --- 页脚模式之前剔除 <details> 块,因为机器人分析链中包含 --- 分隔符,否则会截断实际的问题。实质性的 <details> 块(例如“Suggested fix”“Proposed fix”)会保留。剔除后超过 4000 个字符的评论会被截断,并添加 [comment truncated] 标记。

按审阅者分组——按审阅者的用户名整理格式化输出:

# PR #$PR_NUMBER Review Feedback

## Reviewer: coderabbitai[bot]

### Summary Comments
[Issue comments from this reviewer, each separated by ---]

### Line-Specific Comments
[Review comments from this reviewer, each formatted as:]

**File: `path/to/file.ts:42`**
[cleaned comment body]

---

## Reviewer: another-reviewer

### Summary Comments
...

### Line-Specific Comments
...

如果没有找到任何审阅者的评论,则输出:“No review comments found on this PR (excluding PR author, current user, and resolved threads).”

5. 使用 receive-feedback 评估

加载 receive-feedback 技能。

然后处理格式化后的反馈文档:

  1. 从格式化后的文档中解析每个可执行项
  2. 对每个项依次执行 verify → evaluate → execute
  3. 生成结构化的响应摘要

示例

# Fetch unresolved reviewer comments on current branch's PR (default)
fetch-pr-feedback

# Fetch from a specific PR
fetch-pr-feedback --pr 123

# Include PR author's own comments
fetch-pr-feedback --include-author

# Include line-specific comments from resolved review threads
fetch-pr-feedback --include-resolved

# Combined
fetch-pr-feedback --pr 456 --include-author --include-resolved

评论

支持 Markdown