Files
claude-engineering-plugin/plugins/compound-engineering/skills/ce-resolve-pr-feedback/scripts/get-pr-comments
Trevin Chow b35de99788
Some checks failed
CI / pr-title (push) Has been cancelled
CI / test (push) Has been cancelled
Release PR / release-pr (push) Has been cancelled
Release PR / publish-cli (push) Has been cancelled
feat(ce-resolve-pr-feedback): drop bot noise, centralize test runs (#610)
2026-04-20 00:33:03 -07:00

117 lines
4.3 KiB
Bash
Executable File

#!/usr/bin/env bash
set -e
if [ $# -lt 1 ]; then
echo "Usage: get-pr-comments PR_NUMBER [OWNER/REPO]"
echo "Example: get-pr-comments 123"
echo "Example: get-pr-comments 123 EveryInc/cora"
exit 1
fi
PR_NUMBER=$1
if [ -n "$2" ]; then
OWNER=$(echo "$2" | cut -d/ -f1)
REPO=$(echo "$2" | cut -d/ -f2)
else
OWNER=$(gh repo view --json owner -q .owner.login 2>/dev/null)
REPO=$(gh repo view --json name -q .name 2>/dev/null)
fi
if [ -z "$OWNER" ] || [ -z "$REPO" ]; then
echo "Error: Could not detect repository. Pass OWNER/REPO as second argument."
exit 1
fi
# Fetch review threads, regular PR comments, and review bodies in one query.
# Output is a JSON object with four keys:
# review_threads - unresolved, non-outdated inline code review threads
# pr_comments - top-level PR conversation comments (excludes PR author and known review bots)
# review_bodies - review submissions with non-empty body text (excludes PR author and known review bots)
# cross_invocation - cross-invocation awareness envelope:
# signal: true when both resolved and unresolved threads exist (multi-round review)
# resolved_threads: last N resolved threads by recency, for cluster analysis input
#
# Bot filtering: the bots listed below fall into two categories:
# - AI review bots (coderabbitai, codex, gemini, copilot): put actionable
# feedback in inline review comments (review_threads). Their top-level PR
# comments and review submission bodies are wrappers/summaries with no
# actionable asks on their own.
# - CI/status bots (codecov): post coverage, build, or deploy summaries. Never
# actionable on their own; any follow-up belongs in human review comments.
# Filtering them at the source keeps the agent focused on real feedback and
# avoids narrating "ignoring bot wrapper" on every run. Add new bot logins here
# as they're observed; prefer exact login match over pattern matching to avoid
# dropping actionable content from general-purpose bots.
gh api graphql -f owner="$OWNER" -f repo="$REPO" -F pr="$PR_NUMBER" -f query='
query FetchPRFeedback($owner: String!, $repo: String!, $pr: Int!) {
repository(owner: $owner, name: $repo) {
pullRequest(number: $pr) {
author { login }
reviewThreads(first: 50) {
edges {
node {
id
isResolved
isOutdated
path
line
comments(first: 10) {
nodes {
id
author { login }
body
createdAt
url
}
}
}
}
}
comments(first: 100) {
nodes {
id
author { login }
body
}
}
reviews(first: 50) {
nodes {
id
author { login }
body
state
}
}
}
}
}' | jq '.data.repository.pullRequest as $pr |
# Bots whose top-level comments and review bodies are wrappers or CI summaries, not actionable.
["coderabbitai", "chatgpt-codex-connector", "gemini-code-assist", "copilot-pull-request-reviewer", "codecov"] as $review_bot_logins |
# Unresolved threads (existing behavior, unchanged)
[$pr.reviewThreads.edges[]
| select(.node.isResolved == false and .node.isOutdated == false)] as $unresolved |
# Resolved threads for cross-invocation awareness (last 10 by most recent comment)
[$pr.reviewThreads.edges[]
| select(.node.isResolved == true)
| { thread_id: .node.id, path: .node.path, line: .node.line,
first_comment_body: .node.comments.nodes[0].body,
last_comment_at: ([.node.comments.nodes[].createdAt] | sort | last) }]
| sort_by(.last_comment_at) | .[-10:] | reverse as $resolved |
{
review_threads: $unresolved,
pr_comments: [$pr.comments.nodes[]
| select(.author.login != $pr.author.login)
| select(.author.login as $l | $review_bot_logins | index($l) | not)
| select(.body | test("^\\s*$") | not)],
review_bodies: [$pr.reviews.nodes[]
| select(.body != null and .body != "")
| select(.author.login != $pr.author.login)
| select(.author.login as $l | $review_bot_logins | index($l) | not)],
cross_invocation: {
signal: (($resolved | length) > 0 and ($unresolved | length) > 0),
resolved_threads: $resolved
}
}'