fetch-pr-feedback について
fetch-pr-feedbackは、プルリクエストのコメントを取得し、receive-feedbackスキルで処理するスキルです。レビュー担当者ごとに整理し、既定では作者、現在のユーザー、解決済みスレッドの行コメントを除外します。
主な機能
- 01Issueコメントとレビューコメントの取得
- 02PR番号または現在のブランチのPRを指定
- 03既定で作者と現在のユーザーを除外
- 04解決済みスレッドのコメントを任意で含める
- 05レビュー担当者ごとにフィードバックを整理
活用シーン
- 01現在のブランチのPRへのフィードバック収集
- 02指定したPRのコメント確認
- 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- 解決済みのレビュー スレッドに含まれる行固有のコメントを含めます(デフォルトでは除外)
手順
ゲート(順番どおりに実行し、省略しない)
各 Pass when の条件が満たされた後にのみ、次へ進みます。
- PR コンテキスト — Pass when:
$PR_NUMBERが正の整数に設定され、対象の PR に対するgh pr view/gh apiが終了コード 0 で完了していること。または、Get PR Context で指定された失敗メッセージ(“No PR found for current branch…”)のみを出力して停止していること。 - 取得 — Pass when: 解決済みスレッドに関する GraphQL 呼び出し(
--include-resolvedの場合はスキップ)と、ページネーションを使った 2 つのgh api … | jq -s -f …実行(issue コメントとレビューコメント)が終了コード 0 で、JSON として解析されること(空の[]も有効です)。終了コードが 0 以外、または jq エラーの場合は停止し、コマンドの標準エラー出力を提示してください。コメントを捏造してはいけません。 - 整形済み成果物 — Pass when: 出力が次のいずれかであること。(a)Format Feedback Document に一致する 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). - receive-feedback の読み込み — Pass when: 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 番号を指定してください。
3. コメントを取得する
--include-author が設定されていない限り、$PR_AUTHOR と $CURRENT_USER を除外して、両方の種類のコメントを取得します。--paginate と jq -s を使用して、ページネーションされた JSON 配列を 1 つにまとめます。
--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 を超えるスレッド、または 1 つのスレッド内に 100 を超えるコメントがある場合)では、スレッドまたはコメント単位のページネーションを追加する必要があります。現時点では、これは既知の制限です。
シェルのエスケープに関する問題を防ぐため、heredoc で単一引用符の区切り文字を使用し、jq フィルターを一時ファイルに書き込みます(!=、正規表現パターン、山括弧のエスケープに関する問題を防ぎます)。
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. フィードバック文書のフォーマット
ノイズの除去 — Step 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
...
どのレビュアーからもコメントが見つからない場合は、次のように出力します。「この PR にはレビューコメントが見つかりませんでした(PR 作成者、現在のユーザー、および解決済みのスレッドを除く)。」
5. receive-feedback による評価
receive-feedback スキルを読み込みます。
その後、フォーマット済みのフィードバック文書を処理します。
- フォーマット済みの文書から、対応が必要な各項目を解析します
- 各項目を verify → evaluate → execute の順に処理します
- 構造化された応答の要約を作成します
例
# 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