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