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