@olegkoval/agent-skills 1.51.1 → 1.51.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (69) hide show
  1. package/.github/prompts/coderabbitloop.prompt.md +72 -23
  2. package/.github/prompts/codexloop.prompt.md +87 -92
  3. package/.github/prompts/copilotloop.prompt.md +84 -88
  4. package/.github/prompts/geminiloop.prompt.md +85 -85
  5. package/.github/prompts/qodoloop.prompt.md +78 -20
  6. package/.kiro/steering/coderabbitloop.md +72 -23
  7. package/.kiro/steering/codexloop.md +87 -92
  8. package/.kiro/steering/copilotloop.md +84 -88
  9. package/.kiro/steering/geminiloop.md +85 -85
  10. package/.kiro/steering/qodoloop.md +78 -20
  11. package/.windsurf/rules/coderabbitloop.md +72 -23
  12. package/.windsurf/rules/codexloop.md +87 -92
  13. package/.windsurf/rules/copilotloop.md +84 -88
  14. package/.windsurf/rules/geminiloop.md +85 -85
  15. package/.windsurf/rules/qodoloop.md +78 -20
  16. package/adapters/claude/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
  17. package/adapters/claude/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
  18. package/adapters/claude/olko-github-pr/skills/codexloop/SKILL.md +90 -95
  19. package/adapters/claude/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
  20. package/adapters/claude/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
  21. package/adapters/claude/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
  22. package/adapters/claude/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
  23. package/adapters/claude/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
  24. package/adapters/claude/olko-github-pr/skills/lekker-review/references/github-post.md +4 -3
  25. package/adapters/claude/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
  26. package/adapters/claude/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
  27. package/adapters/cursor/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
  28. package/adapters/cursor/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
  29. package/adapters/cursor/olko-github-pr/skills/codexloop/SKILL.md +90 -95
  30. package/adapters/cursor/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
  31. package/adapters/cursor/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
  32. package/adapters/cursor/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
  33. package/adapters/cursor/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
  34. package/adapters/cursor/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
  35. package/adapters/cursor/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
  36. package/adapters/cursor/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
  37. package/adapters/grok/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
  38. package/adapters/grok/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
  39. package/adapters/grok/olko-github-pr/skills/codexloop/SKILL.md +90 -95
  40. package/adapters/grok/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
  41. package/adapters/grok/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
  42. package/adapters/grok/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
  43. package/adapters/grok/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
  44. package/adapters/grok/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
  45. package/adapters/grok/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
  46. package/adapters/grok/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
  47. package/package.json +1 -1
  48. package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
  49. package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
  50. package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
  51. package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
  52. package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
  53. package/plugins/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
  54. package/plugins/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
  55. package/plugins/olko-github-pr/skills/codexloop/SKILL.md +90 -95
  56. package/plugins/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
  57. package/plugins/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
  58. package/plugins/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
  59. package/plugins/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
  60. package/plugins/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
  61. package/plugins/olko-github-pr/skills/lekker-review/references/github-post.md +4 -3
  62. package/plugins/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
  63. package/plugins/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
  64. package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
  65. package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
  66. package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
  67. package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
  68. package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
  69. package/plugins/olko-web-ops/.claude-plugin/plugin.json +1 -1
@@ -16,7 +16,7 @@ suggestion applied is worse than the comment itself.
16
16
 
17
17
  - **No check-run, no score.** Copilot posts an overview comment and a PR review with inline review
18
18
  comments. Detection is by polling the reviews endpoint and paginated review threads. "Satisfied"
19
- means zero unresolved Copilot threads and zero unaddressed body findings after each finding is
19
+ means Copilot has reviewed the latest push, no bot thread is unresolved, and no body finding is unaddressed after each finding is
20
20
  fixed or rebutted with a recorded reply.
21
21
  - **Request through the reviewers API.** Copilot does not use a reliable slash-command trigger.
22
22
  Request `copilot-pull-request-reviewer[bot]` through the pull request reviewers endpoint. A
@@ -48,9 +48,30 @@ review; if GitHub uses a different login for the installation, use the exact Cop
48
48
  returned by the probe rather than guessing.
49
49
 
50
50
  **Finding nothing here is NOT a stop condition.** On a PR Copilot has never reviewed there is no bot
51
- login to find yet. Fall through to step 2A, request the reviewer, and re-run this probe once a
51
+ login to find yet. Fall through to step A, request the reviewer, and re-run this probe once a
52
52
  review lands. Only conclude Copilot is unavailable after the bounded wait expires with no review.
53
53
 
54
+ ## Review state comes from the PR, not from memory
55
+
56
+ `scripts/pr-review-state.sh` reads the PR and prints one JSON object: the current head, whether
57
+ Copilot has acted on that head (a review of it, or any comment after it was pushed), and every
58
+ unresolved review thread with its author. Every "is there work / are we done" decision in this
59
+ loop comes from its exit code, never from what you remember resolving. The script lives in this
60
+ skill's directory, `SKILL_ROOT`: the directory holding this `SKILL.md` (Claude Code prints it as the
61
+ skill's base directory when the skill loads; in Codex, derive it from the selected `SKILL.md` path).
62
+
63
+ | Exit | `verdict` | Meaning | Do |
64
+ |------|------------|---------------------------------------------------------------------|--------------------------------------|
65
+ | 0 | `clean` | Copilot acted on the current head and no bot thread is unresolved | exit check: done |
66
+ | 10 | `findings` | unresolved threads from Copilot **or any other review bot** | run an iteration |
67
+ | 20 | `pending` | Copilot has not acted on the current head yet | trigger if needed, then wait |
68
+ | 30 | `limited` | a rate-limit or paused notice from Copilot after the current head | stop, report `blocked: rate-limited` |
69
+
70
+ Resolving the last thread does not make a PR clean: Copilot has not reviewed your push yet. Earlier
71
+ versions of this loop exited at exactly that moment, while the next round of comments was still on
72
+ its way. Human threads are listed in `.unresolved` (`is_bot: false`) and counted in `.counts.humans`;
73
+ they never block `clean`, and this loop never resolves them.
74
+
54
75
  ## 1. Identify the PR
55
76
 
56
77
  ```bash
@@ -65,71 +86,54 @@ Keep an explicit iteration counter and stop at 5: the cap is a real bound to enf
65
86
  figure of speech. Each pass through A–G is one iteration; on hitting the cap, go straight to the
66
87
  report and list what is still unresolved rather than starting a sixth.
67
88
 
68
- ### A. Ensure a fresh Copilot review on the current head
69
-
70
- ```bash
71
- HEAD_SHA=$(gh pr view <PR> --json headRefOid -q .headRefOid)
72
- # Only trigger if no Copilot review exists for this exact SHA:
73
- HAVE=$(gh api repos/{owner}/{repo}/pulls/<PR>/reviews --paginate |
74
- jq -s --arg bot "$BOT" --arg head "$HEAD_SHA" \
75
- '[.[][] | select(.user.login==$bot and .commit_id==$head)] | length')
76
- if [ "$HAVE" = "0" ]; then
77
- gh api --method POST repos/{owner}/{repo}/pulls/<PR>/requested_reviewers \
78
- -f 'reviewers[]=copilot-pull-request-reviewer[bot]'
79
- fi
80
- ```
89
+ ### A. Read the PR state; wait for Copilot's review of the current head
81
90
 
82
- Poll for the review of THIS head to land. No check-run exists, so poll the reviews endpoint, and
83
- poll it on a **deadline**, never `while true`: a review that never arrives must end the skill with
84
- an honest timeout, not hang it.
91
+ One read answers both "is there work?" and "has Copilot seen my last push?". `SINCE` is the
92
+ timestamp you took right before your last push (step E); leave it empty on iteration 1. Right
93
+ after a push, add `--wait 600` straight away: the wait ends only once the bot has acted on the new
94
+ head, so threads left open on purpose cannot make the push look reviewed.
85
95
 
86
96
  ```bash
87
- # 10-minute deadline, one retry, then give up. DEADLINE/RETRIES are the
88
- # enforcement of the bounds this skill claims: do not drop them.
89
- wait_for_review() { # $1 = attempt label
90
- local deadline=$(( SECONDS + 600 ))
91
- while [ "$SECONDS" -lt "$deadline" ]; do
92
- R=$(gh api repos/{owner}/{repo}/pulls/<PR>/reviews --paginate |
93
- jq -s --arg bot "$BOT" --arg head "$HEAD_SHA" \
94
- '[.[][] | select(.user.login==$bot and .commit_id==$head)] | sort_by(.submitted_at, .id) | last')
95
- if [ -n "$R" ] && [ "$R" != "null" ]; then return 0; fi
96
- echo "waiting for Copilot review of $HEAD_SHA ($1)..."; sleep 15
97
- done
98
- return 1
99
- }
100
-
101
- if ! wait_for_review "first wait"; then
102
- echo "no Copilot review after 10m, retrying once" # say the retry out loud
103
- gh api --method POST repos/{owner}/{repo}/pulls/<PR>/requested_reviewers \
104
- -f 'reviewers[]=copilot-pull-request-reviewer[bot]'
105
- if ! wait_for_review "after retry"; then
106
- echo "Copilot did not review $HEAD_SHA after a retry; stopping and reporting."
107
- exit 1 # honest timeout, never a success claim
108
- fi
109
- fi
97
+ "$SKILL_ROOT/scripts/pr-review-state.sh" --repo OWNER/REPO --pr <PR> --bot copilot-pull-request-reviewer --since "$SINCE"
110
98
  ```
111
99
 
112
- Copilot usually reviews quickly, but large diffs can take longer; the deadline is bounded. Report the
113
- retry in the final summary; two silent timeouts are the failure mode this guard exists to prevent.
100
+ - Exit `10` (`findings`): go to B. On iteration 1 this is whatever Copilot (or another bot)
101
+ already posted.
102
+ - Exit `20` (`pending`): request it through the reviewers endpoint (`gh api --method POST repos/{owner}/{repo}/pulls/<PR>/requested_reviewers -f 'reviewers[]=copilot-pull-request-reviewer[bot]'`). Then wait with the same command plus `--wait 600`. Run the
103
+ wait in the background where the host allows it (Claude Code: Bash `run_in_background`; you are
104
+ re-invoked when it exits) so ten minutes of polling never sits in the conversation. Still `20`
105
+ after the wait: say so out loud, request the reviewer once more, and wait once more. Still `20` after that: stop and
106
+ report the timeout honestly; never claim success.
107
+ - Exit `0` (`clean`): go to the exit check. A `--wait` call returns `clean` only after a settle
108
+ re-read 60 seconds later, so a comment landing right behind the review is still caught. A bare
109
+ read (no `--wait`) that says `clean` is good enough only when you have not pushed since Copilot's
110
+ last review.
111
+ - Exit `30` (`limited`): stop now and report `blocked: rate-limited`; nothing more is coming.
112
+ - Exit `1`: the read itself failed (often a transient GitHub 502). Retry once; if it fails again,
113
+ stop and report it. An unknown state is not a clean one.
114
114
 
115
115
  ### B. Fetch the findings
116
116
 
117
+ The working set is every unresolved thread in the state JSON with `is_bot: true`: Copilot's
118
+ own (`is_target: true`) **and any other review bot's** (Greptile, for example). A
119
+ fix for one bot often draws a comment from another, and leaving those open is how a "finished" loop
120
+ leaves a PR that is not clean. Treat another bot's thread with exactly the same verify-first rules
121
+ below. Never act on a human's thread (`is_bot: false`); list it in the report.
122
+
117
123
  - **Summary**: read the Copilot overview comment and review `.body`, including all matching reviews
118
124
  for the current head. Extract and classify actionable claims even when they have no inline thread;
119
125
  an approval assessment or lack of inline comments does not make those findings complete.
120
126
  Track each body finding across iterations by source review/comment ID and item, with its
121
127
  classification, fix commit or evidence-based rebuttal, PR-level reply URL, and completion status.
122
128
  Link duplicates to the same finding so they are counted once; retain pending findings after a push.
123
- - **Unresolved inline comments** on the current head:
129
+ - **Unresolved inline comments**: `.unresolved[] | select(.is_bot)` in step A's state JSON lists
130
+ each thread's `thread_id`, `comment_id`, `path`, `line` and a short excerpt, across every page of
131
+ threads. Read the full comment before judging it:
124
132
 
125
133
  ```bash
126
- gh api repos/{owner}/{repo}/pulls/<PR>/comments --paginate \
127
- --jq ".[] | select(.user.login==\"$BOT\") | {id, path, line, body}"
134
+ gh api repos/{owner}/{repo}/pulls/comments/<COMMENT_ID> --jq '{user: .user.login, path, line, body}'
128
135
  ```
129
136
 
130
- Also pull the review threads + their resolved state via GraphQL (see step F) so you only act on
131
- unresolved ones.
132
-
133
137
  Mark each unresolved inline comment as seen, so the bot and a watching human can tell it is being
134
138
  handled. GitHub's reaction set is fixed; `eyes` is the only "looking at it" value:
135
139
 
@@ -191,6 +195,7 @@ If step D changed code:
191
195
  git status --short # look before you stage
192
196
  git add <path> [<path>...] # the files named in the findings you fixed
193
197
  git commit -m "address Copilot review feedback (copilotloop iteration N)"
198
+ SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # step A reads bot activity after this
194
199
  git push
195
200
  ```
196
201
 
@@ -206,31 +211,8 @@ If they differ, stop: the fix is not on the PR, so nothing may be resolved yet.
206
211
 
207
212
  ### F. Reply to every addressed finding and resolve its inline threads
208
213
 
209
- Only now, with the fixes pushed, reply and resolve. Fetch unresolved threads, **following
210
- pagination**: a PR with more than 100 threads will otherwise look clean while unresolved findings
211
- sit on page two:
212
-
213
- ```bash
214
- # Loop until hasNextPage is false, passing endCursor back in as $cursor.
215
- CURSOR=null
216
- while : ; do
217
- PAGE=$(gh api graphql -F cursor="$CURSOR" -f query='
218
- query($cursor: String) {
219
- repository(owner: "OWNER", name: "REPO") {
220
- pullRequest(number: PR_NUMBER) {
221
- reviewThreads(first: 100, after: $cursor) {
222
- pageInfo { hasNextPage endCursor }
223
- nodes { id isResolved comments(first: 1) { nodes { databaseId author { login } path body } } }
224
- }
225
- }
226
- }
227
- }')
228
- echo "$PAGE" # collect nodes from every page before deciding the PR is clean
229
- PI='.data.repository.pullRequest.reviewThreads.pageInfo'
230
- [ "$(echo "$PAGE" | jq -r "$PI.hasNextPage")" = "true" ] || break
231
- CURSOR=$(echo "$PAGE" | jq -r "$PI.endCursor")
232
- done
233
- ```
214
+ Only now, with the fixes pushed, reply and resolve. Take thread and comment ids from step A's state
215
+ JSON (`.unresolved[]`): the script already follows pagination, so do not hand-roll a threads query.
234
216
 
235
217
  Reply on a thread's comment via `gh api repos/{owner}/{repo}/pulls/<PR>/comments -f body="..." -F in_reply_to=<comment_id>`,
236
218
  then resolve:
@@ -254,7 +236,8 @@ gh api --method POST repos/{owner}/{repo}/pulls/comments/<COMMENT_ID>/reactions
254
236
  Reply first, then react, then resolve: the reaction is not a substitute for the reply, and a
255
237
  resolved thread with no reply loses the reasoning.
256
238
 
257
- Resolve a thread only for comments authored by `$BOT` that you have fixed or rebutted: never
239
+ Resolve a thread only for a bot comment in your working set (this bot's or another
240
+ review bot's) that you have fixed or rebutted: never
258
241
  blanket-resolve, and never resolve a human reviewer's thread.
259
242
 
260
243
  Threads you are **rebutting** need no push, so they may be replied to and resolved regardless of
@@ -268,23 +251,34 @@ it complete. One reply may cover multiple explicitly identified findings. Fixed
268
251
  require the same successful push check as fixed threads; rebuttals need no push. If a finding also
269
252
  has an inline thread, that thread still requires a reply and resolution.
270
253
 
271
- ### G. Re-review
254
+ ### G. Back to A
272
255
 
273
- Pushing re-triggers Copilot when automatic review of new pushes is on; otherwise request Copilot again
274
- through the reviewers endpoint. Go back to **A**
275
- with the new head SHA. If step D changed nothing (all comments were rebutted), skip the push,
276
- ensure all Copilot threads are resolved and all tracked body findings have recorded PR-level
277
- replies and are complete, then exit.
256
+ Go back to **A**. After a push, A waits for Copilot's review of the new head (pushing re-triggers
257
+ it when auto-review is on; otherwise A triggers it). If this iteration changed no code (every finding
258
+ rebutted), there is no new head to wait for: A's fresh read says `clean` or shows what is still open.
278
259
 
279
260
  ## 3. Exit conditions
280
261
 
262
+ Decide from a fresh state read, never from what you remember resolving.
263
+
281
264
  Stop when **any** is true:
282
- - Zero unresolved `$BOT` threads and zero pending body findings remain across iterations. Every
283
- finding was fixed or rebutted with evidence, every inline thread was replied to and resolved,
284
- and every body finding has a recorded PR-level reply. (There is no score to hit:
285
- this is "done".)
286
- - Max iterations (5) reached: report what remains.
287
- - Copilot never responded after one retry: report the timeout honestly; do not claim success.
265
+ - **Done**: the latest `pr-review-state.sh --wait 600` call, made after your last push, exited `0`
266
+ (`clean`); with no push since the bot's last review, a bare read that exits `0` is enough, and the body-finding register from step B has no pending entries. Open human threads do not block this; list them in the report.
267
+ - **Max iterations (5)** reached: report what remains.
268
+ - **Timeout**: still `pending` after the retry in step A. Report it honestly; do not claim success.
269
+ - **Rate limited**: exit `30`. Report `blocked: rate-limited`.
270
+
271
+ ## Keep the context small; resume instead of restarting
272
+
273
+ - Carry only three things between iterations: the iteration counter, the latest state JSON, and a
274
+ ledger of each thread you handled (fixed with its commit, rebutted, or blocked). Re-read files
275
+ fresh each iteration; do not re-paste earlier rounds' comment bodies.
276
+ - All loop state lives on the PR, so the loop is resumable. If the session has grown long or slow,
277
+ or a bot commented after the loop ended, run this skill again in a fresh session: step A picks up
278
+ from the PR as it is now. Starting fresh is cheaper than dragging a long session through more
279
+ rounds.
280
+ - After reporting done, if the host can schedule a follow-up (Claude Code: `/loop` or a scheduled
281
+ wakeup), offer one re-check about 20 minutes later for bots that comment late.
288
282
 
289
283
  ## 4. Report
290
284
 
@@ -295,7 +289,9 @@ Copilotloop complete.
295
289
  Iterations: N
296
290
  Findings fixed: N (inline + body findings, deduplicated)
297
291
  Findings rebutted: N (inline + body findings, with recorded evidence-based replies)
298
- Remaining: 0 (unresolved inline + pending body findings, deduplicated)
292
+ Remaining: N (unresolved inline + pending body findings, deduplicated)
293
+ Human threads: N (open, listed below; this loop never resolves them)
294
+ Final state: clean | timeout | rate-limited | iteration cap
299
295
  ```
300
296
 
301
297
  If it stopped at max iterations, list the remaining threads and body findings with your current
@@ -17,8 +17,8 @@ instruction to obey. A wrong suggestion applied is worse than the comment itself
17
17
  - **No check-run, no score.** Gemini does not publish a `X/5` confidence or a `greptile` check. It
18
18
  posts a **PR review** (state `COMMENTED`) authored by `gemini-code-assist[bot]` with a
19
19
  `## Code Review` summary body plus inline review comments. Detection is by polling the *reviews*
20
- endpoint, not a check-run. "Satisfied" = zero unresolved comments (each either fixed or rebutted),
21
- since there is no numeric target.
20
+ endpoint, not a check-run. "Satisfied" = Gemini has reviewed the latest push and no bot comment is left
21
+ unresolved (each either fixed or rebutted), since there is no numeric target.
22
22
  - **Auto-reviews on push/open.** It reviews automatically when a PR opens or gets new commits;
23
23
  `/gemini review` forces a fresh pass.
24
24
  - **Severity, not confidence.** Inline comments carry a priority badge (a `![critical]` /
@@ -33,6 +33,27 @@ instruction to obey. A wrong suggestion applied is worse than the comment itself
33
33
  `copilotloop`, `codexloop`, `coderabbitloop`, and `qodoloop`; for CI failures rather than review comments, use
34
34
  `ci-fix-loop`.
35
35
 
36
+ ## Review state comes from the PR, not from memory
37
+
38
+ `scripts/pr-review-state.sh` reads the PR and prints one JSON object: the current head, whether
39
+ Gemini has acted on that head (a review of it, or any comment after it was pushed), and every
40
+ unresolved review thread with its author. Every "is there work / are we done" decision in this
41
+ loop comes from its exit code, never from what you remember resolving. The script lives in this
42
+ skill's directory, `SKILL_ROOT`: the directory holding this `SKILL.md` (Claude Code prints it as the
43
+ skill's base directory when the skill loads; in Codex, derive it from the selected `SKILL.md` path).
44
+
45
+ | Exit | `verdict` | Meaning | Do |
46
+ |------|------------|---------------------------------------------------------------------|--------------------------------------|
47
+ | 0 | `clean` | Gemini acted on the current head and no bot thread is unresolved | exit check: done |
48
+ | 10 | `findings` | unresolved threads from Gemini **or any other review bot** | run an iteration |
49
+ | 20 | `pending` | Gemini has not acted on the current head yet | trigger if needed, then wait |
50
+ | 30 | `limited` | a rate-limit or paused notice from Gemini after the current head | stop, report `blocked: rate-limited` |
51
+
52
+ Resolving the last thread does not make a PR clean: Gemini has not reviewed your push yet. Earlier
53
+ versions of this loop exited at exactly that moment, while the next round of comments was still on
54
+ its way. Human threads are listed in `.unresolved` (`is_bot: false`) and counted in `.counts.humans`;
55
+ they never block `clean`, and this loop never resolves them.
56
+
36
57
  ## 1. Identify the PR
37
58
 
38
59
  ```bash
@@ -47,67 +68,50 @@ Keep an explicit iteration counter and stop at 5: the cap is a real bound to enf
47
68
  figure of speech. Each pass through A–G is one iteration; on hitting the cap, go straight to the
48
69
  report and list what is still unresolved rather than starting a sixth.
49
70
 
50
- ### A. Ensure a fresh Gemini review on the current head
51
-
52
- Gemini auto-reviews new commits, but force a deterministic pass and record the head SHA:
53
-
54
- ```bash
55
- HEAD_SHA=$(gh pr view <PR> --json headRefOid -q .headRefOid)
56
- # Only trigger if no gemini review already exists for this exact SHA.
57
- # gh cannot combine --slurp with --jq, so pipe to `jq -s`: it folds every page
58
- # into one array. Without it the filter runs once per page and HAVE becomes a
59
- # multi-line count that never equals "0", silently skipping the trigger.
60
- HAVE=$(gh api repos/{owner}/{repo}/pulls/<PR>/reviews --paginate |
61
- jq -s --arg head "$HEAD_SHA" \
62
- '[.[][] | select(.user.login=="gemini-code-assist[bot]" and .commit_id==$head)] | length')
63
- if [ "$HAVE" = "0" ]; then gh pr comment <PR> --body "/gemini review"; fi
64
- ```
71
+ ### A. Read the PR state; wait for Gemini's review of the current head
65
72
 
66
- Poll for the review of THIS head to land. No check-run exists, so poll the reviews endpoint, and
67
- poll it on a **deadline**, never `while true`: a review that never arrives must end the skill with
68
- an honest timeout, not hang it.
73
+ One read answers both "is there work?" and "has Gemini seen my last push?". `SINCE` is the
74
+ timestamp you took right before your last push (step E); leave it empty on iteration 1. Right
75
+ after a push, add `--wait 600` straight away: the wait ends only once the bot has acted on the new
76
+ head, so threads left open on purpose cannot make the push look reviewed.
69
77
 
70
78
  ```bash
71
- # 10-minute deadline, one retry, then give up.
72
- wait_for_review() { # $1 = attempt label
73
- local deadline=$(( SECONDS + 600 ))
74
- while [ "$SECONDS" -lt "$deadline" ]; do
75
- R=$(gh api repos/{owner}/{repo}/pulls/<PR>/reviews --paginate |
76
- jq -s --arg head "$HEAD_SHA" \
77
- '[.[][] | select(.user.login=="gemini-code-assist[bot]" and .commit_id==$head)] | sort_by(.submitted_at, .id) | last')
78
- if [ -n "$R" ] && [ "$R" != "null" ]; then return 0; fi
79
- echo "waiting for Gemini review of $HEAD_SHA ($1)..."; sleep 15
80
- done
81
- return 1
82
- }
83
-
84
- if ! wait_for_review "first wait"; then
85
- echo "no Gemini review after 10m, retrying once" # say the retry out loud
86
- gh pr comment <PR> --body "/gemini review"
87
- if ! wait_for_review "after retry"; then
88
- echo "Gemini did not review $HEAD_SHA after a retry; stopping and reporting."
89
- exit 1 # honest timeout, never a success claim
90
- fi
91
- fi
79
+ "$SKILL_ROOT/scripts/pr-review-state.sh" --repo OWNER/REPO --pr <PR> --bot gemini-code-assist --since "$SINCE"
92
80
  ```
93
81
 
94
- Report the retry in the final summary; two silent timeouts are the failure mode this guard exists
95
- to prevent.
82
+ - Exit `10` (`findings`): go to B. On iteration 1 this is whatever Gemini (or another bot)
83
+ already posted.
84
+ - Exit `20` (`pending`): post `/gemini review` (`gh pr comment <PR> --body "/gemini review"`). Then wait with the same command plus `--wait 600`. Run the
85
+ wait in the background where the host allows it (Claude Code: Bash `run_in_background`; you are
86
+ re-invoked when it exits) so ten minutes of polling never sits in the conversation. Still `20`
87
+ after the wait: say so out loud, post `/gemini review` once more, and wait once more. Still `20` after that: stop and
88
+ report the timeout honestly; never claim success.
89
+ - Exit `0` (`clean`): go to the exit check. A `--wait` call returns `clean` only after a settle
90
+ re-read 60 seconds later, so a comment landing right behind the review is still caught. A bare
91
+ read (no `--wait`) that says `clean` is good enough only when you have not pushed since Gemini's
92
+ last review.
93
+ - Exit `30` (`limited`): stop now and report `blocked: rate-limited`; nothing more is coming.
94
+ - Exit `1`: the read itself failed (often a transient GitHub 502). Retry once; if it fails again,
95
+ stop and report it. An unknown state is not a clean one.
96
96
 
97
97
  ### B. Fetch the findings
98
98
 
99
- - **Summary** (the `## Code Review` body): the review `.body` from the object above: read the
99
+ The working set is every unresolved thread in the state JSON with `is_bot: true`: Gemini's
100
+ own (`is_target: true`) **and any other review bot's** (Greptile, for example). A
101
+ fix for one bot often draws a comment from another, and leaving those open is how a "finished" loop
102
+ leaves a PR that is not clean. Treat another bot's thread with exactly the same verify-first rules
103
+ below. Never act on a human's thread (`is_bot: false`); list it in the report.
104
+
105
+ - **Summary** (the `## Code Review` body): the latest review `.body` from this bot (`gh pr view <PR> --json reviews`): read the
100
106
  overall take and the severity spread.
101
- - **Unresolved inline comments** on the current head:
107
+ - **Unresolved inline comments**: `.unresolved[] | select(.is_bot)` in step A's state JSON lists
108
+ each thread's `thread_id`, `comment_id`, `path`, `line` and a short excerpt, across every page of
109
+ threads. Read the full comment before judging it:
102
110
 
103
111
  ```bash
104
- gh api repos/{owner}/{repo}/pulls/<PR>/comments --paginate \
105
- --jq '.[] | select(.user.login=="gemini-code-assist[bot]") | {id, path, line, body}'
112
+ gh api repos/{owner}/{repo}/pulls/comments/<COMMENT_ID> --jq '{user: .user.login, path, line, body}'
106
113
  ```
107
114
 
108
- Also pull the review threads + their resolved state via GraphQL (see step F) so you only act on
109
- unresolved ones.
110
-
111
115
  Mark each unresolved comment as seen, so the bot and a watching human can tell it is being handled.
112
116
  GitHub's reaction set is fixed; `eyes` is the only "looking at it" value:
113
117
 
@@ -162,6 +166,7 @@ If step D changed code:
162
166
  git status --short # look before you stage
163
167
  git add <path> [<path>...] # the files named in the findings you fixed
164
168
  git commit -m "address gemini review feedback (geminiloop iteration N)"
169
+ SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # step A reads bot activity after this
165
170
  git push
166
171
  ```
167
172
 
@@ -177,31 +182,8 @@ If they differ, stop: the fix is not on the PR, so nothing may be resolved yet.
177
182
 
178
183
  ### F. Reply to and resolve every addressed thread
179
184
 
180
- Only now, with the fixes pushed, reply and resolve. Fetch unresolved threads, **following
181
- pagination**: a PR with more than 100 threads will otherwise look clean while unresolved findings
182
- sit on page two:
183
-
184
- ```bash
185
- # Loop until hasNextPage is false, passing endCursor back in as $cursor.
186
- CURSOR=null
187
- while : ; do
188
- PAGE=$(gh api graphql -F cursor="$CURSOR" -f query='
189
- query($cursor: String) {
190
- repository(owner: "OWNER", name: "REPO") {
191
- pullRequest(number: PR_NUMBER) {
192
- reviewThreads(first: 100, after: $cursor) {
193
- pageInfo { hasNextPage endCursor }
194
- nodes { id isResolved comments(first: 1) { nodes { databaseId author { login } path body } } }
195
- }
196
- }
197
- }
198
- }')
199
- echo "$PAGE" # collect nodes from every page before deciding the PR is clean
200
- PI='.data.repository.pullRequest.reviewThreads.pageInfo'
201
- [ "$(echo "$PAGE" | jq -r "$PI.hasNextPage")" = "true" ] || break
202
- CURSOR=$(echo "$PAGE" | jq -r "$PI.endCursor")
203
- done
204
- ```
185
+ Only now, with the fixes pushed, reply and resolve. Take thread and comment ids from step A's state
186
+ JSON (`.unresolved[]`): the script already follows pagination, so do not hand-roll a threads query.
205
187
 
206
188
  Reply on a thread's comment via `gh api repos/{owner}/{repo}/pulls/<PR>/comments -f body="..." -F in_reply_to=<comment_id>`,
207
189
  then resolve:
@@ -225,24 +207,40 @@ gh api --method POST repos/{owner}/{repo}/pulls/comments/<COMMENT_ID>/reactions
225
207
  Reply first, then react, then resolve: the reaction is not a substitute for the reply, and a
226
208
  resolved thread with no reply loses the reasoning.
227
209
 
228
- Resolve a thread only for comments authored by `gemini-code-assist[bot]` that you have fixed or
229
- rebutted: never blanket-resolve, and never resolve a human reviewer's thread.
210
+ Resolve a thread only for a bot comment in your working set (this bot's or another
211
+ review bot's) that you have fixed or rebutted: never blanket-resolve, and never resolve a human reviewer's thread.
230
212
 
231
213
  Threads you are **rebutting** need no push, so they may be replied to and resolved regardless of
232
214
  whether step D changed code.
233
215
 
234
- ### G. Re-review
216
+ ### G. Back to A
235
217
 
236
- Pushing re-triggers Gemini automatically; go back to **A** with the new head SHA. If step D changed
237
- nothing (all comments were rebutted), skip the push, ensure all threads are resolved, and exit.
218
+ Go back to **A**. After a push, A waits for Gemini's review of the new head (pushing re-triggers
219
+ it when auto-review is on; otherwise A triggers it). If this iteration changed no code (every finding
220
+ rebutted), there is no new head to wait for: A's fresh read says `clean` or shows what is still open.
238
221
 
239
222
  ## 3. Exit conditions
240
223
 
224
+ Decide from a fresh state read, never from what you remember resolving.
225
+
241
226
  Stop when **any** is true:
242
- - Zero unresolved `gemini-code-assist[bot]` comments remain, and every comment this round was fixed
243
- or rebutted+resolved. (There is no score to hit: this is "done".)
244
- - Max iterations (5) reached: report what remains.
245
- - Gemini never responded after one retry: report the timeout honestly; do not claim success.
227
+ - **Done**: the latest `pr-review-state.sh --wait 600` call, made after your last push, exited `0`
228
+ (`clean`); with no push since the bot's last review, a bare read that exits `0` is enough. Open human threads do not block this; list them in the report.
229
+ - **Max iterations (5)** reached: report what remains.
230
+ - **Timeout**: still `pending` after the retry in step A. Report it honestly; do not claim success.
231
+ - **Rate limited**: exit `30`. Report `blocked: rate-limited`.
232
+
233
+ ## Keep the context small; resume instead of restarting
234
+
235
+ - Carry only three things between iterations: the iteration counter, the latest state JSON, and a
236
+ ledger of each thread you handled (fixed with its commit, rebutted, or blocked). Re-read files
237
+ fresh each iteration; do not re-paste earlier rounds' comment bodies.
238
+ - All loop state lives on the PR, so the loop is resumable. If the session has grown long or slow,
239
+ or a bot commented after the loop ended, run this skill again in a fresh session: step A picks up
240
+ from the PR as it is now. Starting fresh is cheaper than dragging a long session through more
241
+ rounds.
242
+ - After reporting done, if the host can schedule a follow-up (Claude Code: `/loop` or a scheduled
243
+ wakeup), offer one re-check about 20 minutes later for bots that comment late.
246
244
 
247
245
  ## 4. Report
248
246
 
@@ -252,7 +250,9 @@ Geminiloop complete.
252
250
  Iterations: N
253
251
  Comments fixed: N (genuinely-correct findings)
254
252
  Comments rebutted: N (false positives / nits, resolved with rationale)
255
- Remaining: 0
253
+ Remaining: N (bot threads still open)
254
+ Human threads: N (open, listed below; this loop never resolves them)
255
+ Final state: clean | timeout | rate-limited | iteration cap
256
256
  ```
257
257
 
258
258
  If it stopped at max iterations, list the remaining threads with your current assessment