105 lines
3.2 KiB
Bash
Executable File
105 lines
3.2 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)
|
|
# review_bodies - review submissions with non-empty body text (excludes PR author)
|
|
# 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
|
|
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
|
|
createdAt
|
|
url
|
|
}
|
|
}
|
|
reviews(first: 50) {
|
|
nodes {
|
|
id
|
|
author { login }
|
|
body
|
|
state
|
|
createdAt
|
|
url
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}' | jq '.data.repository.pullRequest as $pr |
|
|
# 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(.body | test("^\\s*$") | not)],
|
|
review_bodies: [$pr.reviews.nodes[]
|
|
| select(.body != null and .body != "")
|
|
| select(.author.login != $pr.author.login)],
|
|
cross_invocation: {
|
|
signal: (($resolved | length) > 0 and ($unresolved | length) > 0),
|
|
resolved_threads: $resolved
|
|
}
|
|
}'
|