Skip to content

Commit bcbad60

Browse files
committed
Paginate PR data fetch in /fetch-pr-comments and /resolve-pr-comments via script
1 parent 08a07f0 commit bcbad60

4 files changed

Lines changed: 311 additions & 55 deletions

File tree

skills/fetch-pr-comments/SKILL.md

Lines changed: 13 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,34 +9,23 @@ Fetch unresolved review comments and top-level review body comments from a GitHu
99

1010
## Step 1: Fetch Comments
1111

12-
Fetch review threads and top-level review body comments from the PR:
12+
Auto-detect owner, repo, and PR number from current branch if not provided. Then run `scripts/fetch-pr-data.sh`, which handles full pagination (reviews, review threads, inner comment pages for long threads) and emits a single merged JSON document:
1313

1414
```bash
15-
gh api graphql -f query='
16-
query($owner: String!, $repo: String!, $pr: Int!) {
17-
repository(owner: $owner, name: $repo) {
18-
pullRequest(number: $pr) {
19-
title url
20-
reviewThreads(first: 100) {
21-
nodes {
22-
id isResolved isOutdated
23-
comments(first: 50) {
24-
nodes { author { login } body path line originalLine diffHunk }
25-
}
26-
}
27-
}
28-
reviews(first: 50) {
29-
nodes {
30-
author { login }
31-
body state
32-
}
33-
}
34-
}
35-
}
36-
}' -f owner='{owner}' -f repo='{repo}' -F pr={pr_number}
15+
bash <skill-dir>/scripts/fetch-pr-data.sh <owner> <repo> <pr_number>
3716
```
3817

39-
Auto-detect owner, repo, and PR number from current branch if not provided. Filter review threads to unresolved only. Filter reviews to those with a non-empty body, excluding `PENDING` state (unsubmitted drafts).
18+
Output shape:
19+
20+
```jsonc
21+
{
22+
"meta": { "title", "url", "headRefName", "baseRefName" },
23+
"reviewThreads": [ { "id", "isResolved", "isOutdated", "comments": { "nodes": [ { "author", "body", "path", "line", "originalLine", "diffHunk" } ] } } ],
24+
"reviews": [ { "author", "body", "state" } ]
25+
}
26+
```
27+
28+
Filter review threads to unresolved only. Filter reviews to those with a non-empty body, excluding `PENDING` state (unsubmitted drafts).
4029

4130
## Step 2: Present Results
4231

Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
#!/usr/bin/env bash
2+
# Fetch all review threads, reviews, and metadata for a GitHub PR.
3+
# Emits a single merged JSON document on stdout:
4+
# {
5+
# "meta": { title, url, headRefName, baseRefName },
6+
# "reviewThreads": [ ... ],
7+
# "reviews": [ ... ]
8+
# }
9+
#
10+
# Paginates reviewThreads and reviews to avoid silent drops on long PRs.
11+
# For any thread whose inner comments exceed the initial page, walks the
12+
# remaining comments via node(id:) and merges them back into the thread.
13+
#
14+
# Usage: fetch-pr-data.sh <owner> <repo> <pr_number>
15+
16+
set -euo pipefail
17+
18+
if [ "$#" -ne 3 ]; then
19+
echo "Usage: $0 <owner> <repo> <pr_number>" >&2
20+
exit 2
21+
fi
22+
23+
owner="$1"
24+
repo="$2"
25+
pr="$3"
26+
27+
meta_query=$(cat <<'GRAPHQL'
28+
query($owner: String!, $repo: String!, $pr: Int!) {
29+
repository(owner: $owner, name: $repo) {
30+
pullRequest(number: $pr) { title url headRefName baseRefName }
31+
}
32+
}
33+
GRAPHQL
34+
)
35+
36+
threads_query=$(cat <<'GRAPHQL'
37+
query($owner: String!, $repo: String!, $pr: Int!, $endCursor: String) {
38+
repository(owner: $owner, name: $repo) {
39+
pullRequest(number: $pr) {
40+
reviewThreads(first: 100, after: $endCursor) {
41+
pageInfo { hasNextPage endCursor }
42+
nodes {
43+
id isResolved isOutdated
44+
comments(first: 100) {
45+
pageInfo { hasNextPage endCursor }
46+
nodes { author { login } body path line originalLine diffHunk }
47+
}
48+
}
49+
}
50+
}
51+
}
52+
}
53+
GRAPHQL
54+
)
55+
56+
reviews_query=$(cat <<'GRAPHQL'
57+
query($owner: String!, $repo: String!, $pr: Int!, $endCursor: String) {
58+
repository(owner: $owner, name: $repo) {
59+
pullRequest(number: $pr) {
60+
reviews(first: 100, after: $endCursor) {
61+
pageInfo { hasNextPage endCursor }
62+
nodes { author { login } body state }
63+
}
64+
}
65+
}
66+
}
67+
GRAPHQL
68+
)
69+
70+
thread_tail_query=$(cat <<'GRAPHQL'
71+
query($thread_id: ID!, $cursor: String!) {
72+
node(id: $thread_id) {
73+
... on PullRequestReviewThread {
74+
comments(first: 100, after: $cursor) {
75+
pageInfo { hasNextPage endCursor }
76+
nodes { author { login } body path line originalLine diffHunk }
77+
}
78+
}
79+
}
80+
}
81+
GRAPHQL
82+
)
83+
84+
meta=$(gh api graphql \
85+
-f query="$meta_query" \
86+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
87+
--jq '.data.repository.pullRequest')
88+
89+
threads=$(gh api graphql --paginate \
90+
-f query="$threads_query" \
91+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
92+
--jq '.data.repository.pullRequest.reviewThreads.nodes[]' \
93+
| jq -s '.')
94+
95+
threads=$(jq -c '.[]' <<<"$threads" | while IFS= read -r thread; do
96+
has_next=$(jq -r '.comments.pageInfo.hasNextPage' <<<"$thread")
97+
if [ "$has_next" != "true" ]; then
98+
printf '%s\n' "$thread"
99+
continue
100+
fi
101+
thread_id=$(jq -r '.id' <<<"$thread")
102+
cursor=$(jq -r '.comments.pageInfo.endCursor' <<<"$thread")
103+
nodes=$(jq '.comments.nodes' <<<"$thread")
104+
while [ "$has_next" = "true" ]; do
105+
page=$(gh api graphql \
106+
-f query="$thread_tail_query" \
107+
-f thread_id="$thread_id" -f cursor="$cursor" \
108+
--jq '.data.node.comments')
109+
page_nodes=$(jq '.nodes' <<<"$page")
110+
nodes=$(jq -n --argjson a "$nodes" --argjson b "$page_nodes" '$a + $b')
111+
has_next=$(jq -r '.pageInfo.hasNextPage' <<<"$page")
112+
cursor=$(jq -r '.pageInfo.endCursor // ""' <<<"$page")
113+
done
114+
jq --argjson nodes "$nodes" '
115+
.comments.nodes = $nodes
116+
| .comments.pageInfo.hasNextPage = false
117+
| .comments.pageInfo.endCursor = null
118+
' <<<"$thread"
119+
done | jq -s '.')
120+
121+
reviews=$(gh api graphql --paginate \
122+
-f query="$reviews_query" \
123+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
124+
--jq '.data.repository.pullRequest.reviews.nodes[]' \
125+
| jq -s '.')
126+
127+
jq -n \
128+
--argjson meta "$meta" \
129+
--argjson threads "$threads" \
130+
--argjson reviews "$reviews" \
131+
'{meta: $meta, reviewThreads: $threads, reviews: $reviews}'

skills/resolve-pr-comments/SKILL.md

Lines changed: 14 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -25,41 +25,24 @@ At the start, use `TaskCreate` to create a task for each step:
2525

2626
## Step 1: Fetch Comments
2727

28-
Fetch review threads, top-level review body comments, and PR commits from the PR:
28+
Auto-detect owner, repo, and PR number from current branch if not provided. Then run `scripts/fetch-pr-data.sh`, which handles full pagination (reviews, review threads, inner comment pages for long threads, commits) and emits a single merged JSON document:
2929

3030
```bash
31-
gh api graphql -f query='
32-
query($owner: String!, $repo: String!, $pr: Int!) {
33-
repository(owner: $owner, name: $repo) {
34-
pullRequest(number: $pr) {
35-
title url
36-
reviewThreads(first: 100) {
37-
nodes {
38-
id isResolved isOutdated
39-
comments(first: 50) {
40-
nodes { author { login } body path line originalLine diffHunk }
41-
}
42-
}
43-
}
44-
reviews(first: 50) {
45-
nodes {
46-
author { login }
47-
body state submittedAt
48-
}
49-
}
50-
commits(last: 50) {
51-
nodes {
52-
commit {
53-
oid abbreviatedOid message committedDate
54-
}
55-
}
56-
}
57-
}
58-
}
59-
}' -f owner='{owner}' -f repo='{repo}' -F pr={pr_number}
31+
bash <skill-dir>/scripts/fetch-pr-data.sh <owner> <repo> <pr_number>
6032
```
6133

62-
Auto-detect owner, repo, and PR number from current branch if not provided. Filter review threads to unresolved only. Filter reviews to those with a non-empty body, excluding `PENDING` state (unsubmitted drafts).
34+
Output shape:
35+
36+
```jsonc
37+
{
38+
"meta": { "title", "url", "headRefName", "baseRefName" },
39+
"reviewThreads": [ { "id", "isResolved", "isOutdated", "comments": { "nodes": [ { "author", "body", "path", "line", "originalLine", "diffHunk" } ] } } ],
40+
"reviews": [ { "author", "body", "state", "submittedAt" } ],
41+
"commits": [ { "commit": { "oid", "abbreviatedOid", "message", "committedDate" } } ]
42+
}
43+
```
44+
45+
Filter review threads to unresolved only. Filter reviews to those with a non-empty body, excluding `PENDING` state (unsubmitted drafts).
6346

6447
## Step 2: Triage Review Body Comments
6548

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env bash
2+
# Fetch all review threads, reviews, commits, and metadata for a GitHub PR.
3+
# Emits a single merged JSON document on stdout:
4+
# {
5+
# "meta": { title, url, headRefName, baseRefName },
6+
# "reviewThreads": [ ... ],
7+
# "reviews": [ ... ], # includes submittedAt
8+
# "commits": [ ... ] # oid, abbreviatedOid, message, committedDate
9+
# }
10+
#
11+
# Paginates reviewThreads, reviews, and commits to avoid silent drops on long
12+
# PRs. For any thread whose inner comments exceed the initial page, walks the
13+
# remaining comments via node(id:) and merges them back into the thread.
14+
#
15+
# Usage: fetch-pr-data.sh <owner> <repo> <pr_number>
16+
17+
set -euo pipefail
18+
19+
if [ "$#" -ne 3 ]; then
20+
echo "Usage: $0 <owner> <repo> <pr_number>" >&2
21+
exit 2
22+
fi
23+
24+
owner="$1"
25+
repo="$2"
26+
pr="$3"
27+
28+
meta_query=$(cat <<'GRAPHQL'
29+
query($owner: String!, $repo: String!, $pr: Int!) {
30+
repository(owner: $owner, name: $repo) {
31+
pullRequest(number: $pr) { title url headRefName baseRefName }
32+
}
33+
}
34+
GRAPHQL
35+
)
36+
37+
threads_query=$(cat <<'GRAPHQL'
38+
query($owner: String!, $repo: String!, $pr: Int!, $endCursor: String) {
39+
repository(owner: $owner, name: $repo) {
40+
pullRequest(number: $pr) {
41+
reviewThreads(first: 100, after: $endCursor) {
42+
pageInfo { hasNextPage endCursor }
43+
nodes {
44+
id isResolved isOutdated
45+
comments(first: 100) {
46+
pageInfo { hasNextPage endCursor }
47+
nodes { author { login } body path line originalLine diffHunk }
48+
}
49+
}
50+
}
51+
}
52+
}
53+
}
54+
GRAPHQL
55+
)
56+
57+
reviews_query=$(cat <<'GRAPHQL'
58+
query($owner: String!, $repo: String!, $pr: Int!, $endCursor: String) {
59+
repository(owner: $owner, name: $repo) {
60+
pullRequest(number: $pr) {
61+
reviews(first: 100, after: $endCursor) {
62+
pageInfo { hasNextPage endCursor }
63+
nodes { author { login } body state submittedAt }
64+
}
65+
}
66+
}
67+
}
68+
GRAPHQL
69+
)
70+
71+
commits_query=$(cat <<'GRAPHQL'
72+
query($owner: String!, $repo: String!, $pr: Int!, $endCursor: String) {
73+
repository(owner: $owner, name: $repo) {
74+
pullRequest(number: $pr) {
75+
commits(first: 100, after: $endCursor) {
76+
pageInfo { hasNextPage endCursor }
77+
nodes { commit { oid abbreviatedOid message committedDate } }
78+
}
79+
}
80+
}
81+
}
82+
GRAPHQL
83+
)
84+
85+
thread_tail_query=$(cat <<'GRAPHQL'
86+
query($thread_id: ID!, $cursor: String!) {
87+
node(id: $thread_id) {
88+
... on PullRequestReviewThread {
89+
comments(first: 100, after: $cursor) {
90+
pageInfo { hasNextPage endCursor }
91+
nodes { author { login } body path line originalLine diffHunk }
92+
}
93+
}
94+
}
95+
}
96+
GRAPHQL
97+
)
98+
99+
meta=$(gh api graphql \
100+
-f query="$meta_query" \
101+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
102+
--jq '.data.repository.pullRequest')
103+
104+
threads=$(gh api graphql --paginate \
105+
-f query="$threads_query" \
106+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
107+
--jq '.data.repository.pullRequest.reviewThreads.nodes[]' \
108+
| jq -s '.')
109+
110+
threads=$(jq -c '.[]' <<<"$threads" | while IFS= read -r thread; do
111+
has_next=$(jq -r '.comments.pageInfo.hasNextPage' <<<"$thread")
112+
if [ "$has_next" != "true" ]; then
113+
printf '%s\n' "$thread"
114+
continue
115+
fi
116+
thread_id=$(jq -r '.id' <<<"$thread")
117+
cursor=$(jq -r '.comments.pageInfo.endCursor' <<<"$thread")
118+
nodes=$(jq '.comments.nodes' <<<"$thread")
119+
while [ "$has_next" = "true" ]; do
120+
page=$(gh api graphql \
121+
-f query="$thread_tail_query" \
122+
-f thread_id="$thread_id" -f cursor="$cursor" \
123+
--jq '.data.node.comments')
124+
page_nodes=$(jq '.nodes' <<<"$page")
125+
nodes=$(jq -n --argjson a "$nodes" --argjson b "$page_nodes" '$a + $b')
126+
has_next=$(jq -r '.pageInfo.hasNextPage' <<<"$page")
127+
cursor=$(jq -r '.pageInfo.endCursor // ""' <<<"$page")
128+
done
129+
jq --argjson nodes "$nodes" '
130+
.comments.nodes = $nodes
131+
| .comments.pageInfo.hasNextPage = false
132+
| .comments.pageInfo.endCursor = null
133+
' <<<"$thread"
134+
done | jq -s '.')
135+
136+
reviews=$(gh api graphql --paginate \
137+
-f query="$reviews_query" \
138+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
139+
--jq '.data.repository.pullRequest.reviews.nodes[]' \
140+
| jq -s '.')
141+
142+
commits=$(gh api graphql --paginate \
143+
-f query="$commits_query" \
144+
-f owner="$owner" -f repo="$repo" -F pr="$pr" \
145+
--jq '.data.repository.pullRequest.commits.nodes[]' \
146+
| jq -s '.')
147+
148+
jq -n \
149+
--argjson meta "$meta" \
150+
--argjson threads "$threads" \
151+
--argjson reviews "$reviews" \
152+
--argjson commits "$commits" \
153+
'{meta: $meta, reviewThreads: $threads, reviews: $reviews, commits: $commits}'

0 commit comments

Comments
 (0)