@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
@@ -22,10 +22,31 @@ Qodo runs as `qodo-code-review[bot]` and posts **two independent things** per re
22
22
 
23
23
  **Use the inline threads as the source of truth.** They carry `isResolved` state natively (fetch via GraphQL, see `references/graphql-queries.md`), so "done" is answerable directly instead of re-parsing the rollup's HTML on every iteration. Each thread's body contains a `<details><summary>Agent Prompt</summary>` (or `**Agent Prompt**`) section with a ready-made fix spec, and a severity badge image whose `alt` text is `Remediation recommended` or `Informational` (recommended ↔ "Review recommended", informational ↔ "Optional").
24
24
 
25
- Qodo has **no check run to poll**: there is nothing in `gh pr checks` to wait on. Completion means a new `qodo-code-review[bot]` comment/review appears after your push; poll comments, not checks.
25
+ Qodo has **no check run to poll**: there is nothing in `gh pr checks` to wait on. Completion means a new `qodo-code-review[bot]` comment/review appears after your push; `scripts/pr-review-state.sh` detects it.
26
26
 
27
27
  **Rate limit is a real terminal state, not a bug.** A completed-looking comment reading "Qodo reviews are paused" / "you've reached your PR review limit" means Qodo will not review again this cycle: stop and report it, don't loop waiting for something that isn't coming.
28
28
 
29
+ ## Review state comes from the PR, not from memory
30
+
31
+ `scripts/pr-review-state.sh` reads the PR and prints one JSON object: the current head, whether
32
+ Qodo has acted on that head (a review of it, or any comment after it was pushed), and every
33
+ unresolved review thread with its author. Every "is there work / are we done" decision in this
34
+ loop comes from its exit code, never from what you remember resolving. The script lives in this
35
+ skill's directory, `SKILL_ROOT`: the directory holding this `SKILL.md` (Claude Code prints it as the
36
+ skill's base directory when the skill loads; in Codex, derive it from the selected `SKILL.md` path).
37
+
38
+ | Exit | `verdict` | Meaning | Do |
39
+ |------|------------|---------------------------------------------------------------------|--------------------------------------|
40
+ | 0 | `clean` | Qodo acted on the current head and no bot thread is unresolved | exit check: done |
41
+ | 10 | `findings` | unresolved threads from Qodo **or any other review bot** | run an iteration |
42
+ | 20 | `pending` | Qodo has not acted on the current head yet | trigger if needed, then wait |
43
+ | 30 | `limited` | a rate-limit or paused notice from Qodo after the current head | stop, report `blocked: rate-limited` |
44
+
45
+ Resolving the last thread does not make a PR clean: Qodo has not reviewed your push yet. Earlier
46
+ versions of this loop exited at exactly that moment, while the next round of comments was still on
47
+ its way. Human threads are listed in `.unresolved` (`is_bot: false`) and counted in `.counts.humans`;
48
+ they never block `clean`, and this loop never resolves them.
49
+
29
50
  ## Instructions
30
51
 
31
52
  ### 1. Identify the PR
@@ -50,33 +71,48 @@ narrower.
50
71
 
51
72
  ### 2. Loop (max 5 iterations)
52
73
 
53
- #### A. Wait for a review of the current head
54
-
55
- On **iteration 1**, if there is nothing to push yet (you haven't fixed anything in this loop), don't wait for a brand-new review: use whatever Qodo has already posted as your starting point and go straight to step B. Otherwise:
56
-
57
- ```bash
58
- SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
59
- git push
60
- ```
74
+ #### A. Read the PR state; wait for Qodo's review of the current head
61
75
 
62
- Poll (every ~10s, timeout ~5min), fetching **every page** of comments, not just the first:
76
+ One read answers both "is there work?" and "has Qodo seen my last push?". `SINCE` is the
77
+ timestamp you took right before your last push (step E); leave it empty on iteration 1. Right
78
+ after a push, add `--wait 600` straight away: the wait ends only once the bot has acted on the new
79
+ head, so threads left open on purpose cannot make the push look reviewed.
63
80
 
64
81
  ```bash
65
- gh api --paginate "repos/{owner}/{repo}/issues/<PR_NUMBER>/comments?per_page=100" | jq -s '
66
- add | map(select(.user.login == "qodo-code-review[bot]")) | sort_by(.created_at)'
82
+ "$SKILL_ROOT/scripts/pr-review-state.sh" --repo OWNER/REPO --pr <PR> --bot qodo-code-review --since "$SINCE"
67
83
  ```
68
84
 
69
- From the comments created **after `$SINCE`** (ignore anything older: that's leftover from a prior push, not this one):
70
- - Any of them contains "review limit" or "paused" → **stop the loop now**, go to Report with `blocked: rate-limited`.
71
- - The latest one starts with `<h3>Code Review by Qodo</h3>` → proceed to step B.
72
- - Otherwise (nothing yet, or only "Qodo is busy working") → sleep 10s and poll again, up to the 5-minute timeout.
85
+ - Exit `10` (`findings`): go to B. On iteration 1 this is whatever Qodo (or another bot)
86
+ already posted.
87
+ - Exit `20` (`pending`): Qodo reviews new pushes on its own, so there is nothing to post. Then wait with the same command plus `--wait 600`. Run the
88
+ wait in the background where the host allows it (Claude Code: Bash `run_in_background`; you are
89
+ re-invoked when it exits) so ten minutes of polling never sits in the conversation. Still `20`
90
+ after the wait: say so out loud, check the PR for a Qodo notice explaining the silence, and wait once more. Still `20` after that: stop and
91
+ report the timeout honestly; never claim success.
92
+ - Exit `0` (`clean`): go to the exit check. A `--wait` call returns `clean` only after a settle
93
+ re-read 60 seconds later, so a comment landing right behind the review is still caught. A bare
94
+ read (no `--wait`) that says `clean` is good enough only when you have not pushed since Qodo's
95
+ last review.
96
+ - Exit `30` (`limited`): stop now and report `blocked: rate-limited`; nothing more is coming.
97
+ - Exit `1`: the read itself failed (often a transient GitHub 502). Retry once; if it fails again,
98
+ stop and report it. An unknown state is not a clean one.
73
99
 
74
100
  #### B. Fetch unresolved findings
75
101
 
76
- Run the paginated GraphQL query in `references/graphql-queries.md` (`unresolvedQodoThreads`): **follow `pageInfo.hasNextPage`/`endCursor` until it's exhausted**; a PR with more than 100 Qodo threads silently loses every finding past the first page otherwise. For each unresolved thread from `qodo-code-review[bot]`: parse the finding title, severity (recommended/optional), file/line, and the **Agent Prompt** block.
102
+ The working set is every unresolved thread in the state JSON with `is_bot: true`: Qodo's
103
+ own (`is_target: true`) **and any other review bot's** (Greptile, for example). A
104
+ fix for one bot often draws a comment from another, and leaving those open is how a "finished" loop
105
+ leaves a PR that is not clean. Never act on a human's thread (`is_bot: false`); list it in the report.
106
+
107
+ Qodo's threads are `.unresolved[] | select(.is_target)` in step A's state JSON (the script already follows pagination). For each, read the full comment (`gh api repos/{owner}/{repo}/pulls/comments/<comment_id>`) and parse the finding title, severity (recommended/optional), file/line, and the **Agent Prompt** block.
77
108
 
78
109
  Build the working set: all `recommended` threads, plus `optional` ones too if `--include-optional`.
79
110
 
111
+ Threads from other bots come from the state JSON (`.unresolved[] | select(.is_bot and (.is_target | not))`).
112
+ They carry no ready-made fix prompt: read the full comment
113
+ (`gh api repos/{owner}/{repo}/pulls/comments/<comment_id>`), verify its claim against the real
114
+ code, and handle it like any Qodo finding: fix it, or reply with the evidence for not fixing it.
115
+
80
116
  Mark each thread as seen with an 👀 reaction on its first comment (`databaseId` from the query), so
81
117
  the bot and a watching human can tell it is being worked. GitHub's reaction set is fixed; `eyes` is
82
118
  the only "looking at it" value:
@@ -87,7 +123,15 @@ gh api --method POST repos/{owner}/{repo}/pulls/comments/<DATABASE_ID>/reactions
87
123
 
88
124
  #### C. Check exit conditions
89
125
 
90
- Stop if the working set is empty, or max iterations reached.
126
+ Stop when step A's read exited `0` (`clean`) after your last push (a `--wait` read, which settles),
127
+ or exited `30` (`limited`), or the iteration cap is reached. An empty working set on its own is not
128
+ done: with no review of the current head yet, it only means Qodo has not answered your push.
129
+
130
+ Two kinds of thread stay open by design, so for Qodo `findings` can still mean done: a finding you
131
+ **blocked** (replied, deliberately left unresolved) and, without `--include-optional`, an
132
+ **Optional** finding outside the working set. Stop as done when Qodo has acted on the current head
133
+ (`.bots[0].acted_on_head` is `true` in a `--wait` read) and every open bot thread is one of those
134
+ two. Never re-work a thread already in your blocked ledger.
91
135
 
92
136
  #### D. Fix each finding
93
137
 
@@ -100,6 +144,7 @@ If **fixed** is empty, skip straight to F (nothing to commit). Otherwise, stage
100
144
  ```bash
101
145
  git add <files touched by this iteration's fixes>
102
146
  git commit -m "address Qodo review feedback (qodoloop iteration N)"
147
+ SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # step A reads bot activity after this
103
148
  git push
104
149
  ```
105
150
 
@@ -133,6 +178,18 @@ gh api --method POST repos/{owner}/{repo}/pulls/comments/<DATABASE_ID>/reactions
133
178
 
134
179
  Go back to step A.
135
180
 
181
+ ### Keep the context small; resume instead of restarting
182
+
183
+ - Carry only three things between iterations: the iteration counter, the latest state JSON, and a
184
+ ledger of each thread you handled (fixed with its commit, rebutted, or blocked). Re-read files
185
+ fresh each iteration; do not re-paste earlier rounds' comment bodies.
186
+ - All loop state lives on the PR, so the loop is resumable. If the session has grown long or slow,
187
+ or a bot commented after the loop ended, run this skill again in a fresh session: step A picks up
188
+ from the PR as it is now. Starting fresh is cheaper than dragging a long session through more
189
+ rounds.
190
+ - After reporting done, if the host can schedule a follow-up (Claude Code: `/loop` or a scheduled
191
+ wakeup), offer one re-check about 20 minutes later for bots that comment late.
192
+
136
193
  ### 3. Report
137
194
 
138
195
  | Field | Value |
@@ -141,7 +198,8 @@ Go back to step A.
141
198
  | Findings resolved | N |
142
199
  | Findings blocked | N (with reasons, left unresolved) |
143
200
  | Remaining | N (if any) |
144
- | Blocked | rate-limited / human-intervention / max-iterations / none |
201
+ | Human threads | N (open, listed; never resolved by this loop) |
202
+ | Blocked | rate-limited / timeout / human-intervention / max-iterations / none |
145
203
 
146
204
  ```text
147
205
  Qodoloop complete.
@@ -153,4 +211,4 @@ Qodoloop complete.
153
211
 
154
212
  ## References
155
213
 
156
- - `references/graphql-queries.md`: the exact `unresolvedQodoThreads` query and the `resolveReviewThread` / `addPullRequestReviewThreadReply` mutations, with field notes.
214
+ - `references/graphql-queries.md`: the `resolveReviewThread` / `addPullRequestReviewThreadReply` mutations, with field notes. Its `unresolvedQodoThreads` query is superseded by `scripts/pr-review-state.sh` for finding threads.
@@ -9,7 +9,7 @@ description: >
9
9
  CodeRabbit" / "resolve CodeRabbit comments".
10
10
  compatibility: Requires git and gh (GitHub CLI) authenticated, with the CodeRabbit GitHub App installed on the repo.
11
11
  metadata:
12
- version: "1.1"
12
+ version: "1.2"
13
13
  allowed-tools: Bash(gh:*) Bash(git:*)
14
14
  ---
15
15
  <!-- Generated by scripts/build-adapters.sh. Do not edit directly. -->
@@ -38,6 +38,27 @@ CodeRabbit has slash commands (official, `docs.coderabbit.ai/reference/review-co
38
38
 
39
39
  **Rate limit is a real terminal state, not a bug.** A comment reading "you've reached your PR review limit" (confirmed live wording) means CodeRabbit will not review again this cycle: stop and report it, don't loop waiting for something that isn't coming.
40
40
 
41
+ ## Review state comes from the PR, not from memory
42
+
43
+ `scripts/pr-review-state.sh` reads the PR and prints one JSON object: the current head, whether
44
+ CodeRabbit has acted on that head (a review of it, or any comment after it was pushed), and every
45
+ unresolved review thread with its author. Every "is there work / are we done" decision in this
46
+ loop comes from its exit code, never from what you remember resolving. The script lives in this
47
+ skill's directory, `SKILL_ROOT`: the directory holding this `SKILL.md` (Claude Code prints it as the
48
+ skill's base directory when the skill loads; in Codex, derive it from the selected `SKILL.md` path).
49
+
50
+ | Exit | `verdict` | Meaning | Do |
51
+ |------|------------|---------------------------------------------------------------------|--------------------------------------|
52
+ | 0 | `clean` | CodeRabbit acted on the current head and no bot thread is unresolved | exit check: done |
53
+ | 10 | `findings` | unresolved threads from CodeRabbit **or any other review bot** | run an iteration |
54
+ | 20 | `pending` | CodeRabbit has not acted on the current head yet | trigger if needed, then wait |
55
+ | 30 | `limited` | a rate-limit or paused notice from CodeRabbit after the current head | stop, report `blocked: rate-limited` |
56
+
57
+ Resolving the last thread does not make a PR clean: CodeRabbit has not reviewed your push yet. Earlier
58
+ versions of this loop exited at exactly that moment, while the next round of comments was still on
59
+ its way. Human threads are listed in `.unresolved` (`is_bot: false`) and counted in `.counts.humans`;
60
+ they never block `clean`, and this loop never resolves them.
61
+
41
62
  ## Instructions
42
63
 
43
64
  ### 1. Identify the PR
@@ -62,38 +83,50 @@ gets those edits swept into the fix commit right along with it.
62
83
  Keep an explicit iteration counter and stop at 5: the cap is a real bound to enforce, not a figure
63
84
  of speech. Each pass through A–F is one iteration; on hitting the cap, go straight to the report.
64
85
 
65
- #### A. Wait for a review of the current head
86
+ #### A. Read the PR state; wait for CodeRabbit's review of the current head
66
87
 
67
- On **iteration 1**, if there is nothing to push yet (you haven't fixed anything in this loop), don't
68
- wait for a brand-new review: use whatever CodeRabbit has already posted as your starting point and
69
- go straight to step B. Otherwise:
88
+ One read answers both "is there work?" and "has CodeRabbit seen my last push?". `SINCE` is the
89
+ timestamp you took right before your last push (step E); leave it empty on iteration 1. Right
90
+ after a push, add `--wait 600` straight away: the wait ends only once the bot has acted on the new
91
+ head, so threads left open on purpose cannot make the push look reviewed.
70
92
 
71
93
  ```bash
72
- SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ)
73
- git push
94
+ "$SKILL_ROOT/scripts/pr-review-state.sh" --repo OWNER/REPO --pr <PR> --bot coderabbitai --since "$SINCE"
74
95
  ```
75
96
 
76
- If this repo doesn't auto-review on push, nudge it:
77
-
78
- ```bash
79
- gh pr comment <PR_NUMBER> --body "@coderabbitai review"
80
- ```
81
-
82
- Poll (every ~10s, timeout ~5min) for CodeRabbit's next formal review to land: a new entry in
83
- `gh api --paginate repos/{owner}/{repo}/pulls/<PR_NUMBER>/reviews` from `coderabbitai[bot]`
84
- (REST spelling carries the `[bot]` suffix) created after `$SINCE`. Follow pagination so a review
85
- that lands on page two is not missed. If you instead see a rate-limit comment ("review limit"),
86
- **stop the loop now** and go to Report with `blocked: rate-limited`. If no review arrives within
87
- the timeout after one retry, stop and report the timeout honestly; do not claim success.
97
+ - Exit `10` (`findings`): go to B. On iteration 1 this is whatever CodeRabbit (or another bot)
98
+ already posted.
99
+ - Exit `20` (`pending`): post `@coderabbitai review` if this repo does not review pushes automatically (`gh pr comment <PR_NUMBER> --body "@coderabbitai review"`). Then wait with the same command plus `--wait 600`. Run the
100
+ wait in the background where the host allows it (Claude Code: Bash `run_in_background`; you are
101
+ re-invoked when it exits) so ten minutes of polling never sits in the conversation. Still `20`
102
+ after the wait: say so out loud, post `@coderabbitai review` once more, and wait once more. Still `20` after that: stop and
103
+ report the timeout honestly; never claim success.
104
+ - Exit `0` (`clean`): go to the exit check. A `--wait` call returns `clean` only after a settle
105
+ re-read 60 seconds later, so a comment landing right behind the review is still caught. A bare
106
+ read (no `--wait`) that says `clean` is good enough only when you have not pushed since CodeRabbit's
107
+ last review.
108
+ - Exit `30` (`limited`): stop now and report `blocked: rate-limited`; nothing more is coming.
109
+ - Exit `1`: the read itself failed (often a transient GitHub 502). Retry once; if it fails again,
110
+ stop and report it. An unknown state is not a clean one.
88
111
 
89
112
  #### B. Fetch unresolved findings
90
113
 
91
- Run the `unresolvedQodoThreads`-shaped query in `references/graphql-queries.md` (filter `author.login`
92
- on GraphQL's bare `coderabbitai`, **not** REST's `coderabbitai[bot]`: see the reference's login-spelling
93
- note, and follow `pageInfo.hasNextPage`/`endCursor` until exhausted). For each unresolved thread,
94
- parse the finding text, its `databaseId`, and the **Prompt for AI Agents** block (heading text is
114
+ The working set is every unresolved thread in the state JSON with `is_bot: true`: CodeRabbit's
115
+ own (`is_target: true`) **and any other review bot's** (Greptile, for example). A
116
+ fix for one bot often draws a comment from another, and leaving those open is how a "finished" loop
117
+ leaves a PR that is not clean. Never act on a human's thread (`is_bot: false`); list it in the report.
118
+
119
+ CodeRabbit's threads are `.unresolved[] | select(.is_target)` in step A's state JSON (the script
120
+ already follows pagination). For each, read the full comment
121
+ (`gh api repos/{owner}/{repo}/pulls/comments/<comment_id>`) and parse the finding text and the
122
+ **Prompt for AI Agents** block (heading text is
95
123
  `🤖 Prompt for AI Agents`, not `Agent Prompt`: match case/emoji-insensitively).
96
124
 
125
+ Threads from other bots come from the state JSON (`.unresolved[] | select(.is_bot and (.is_target | not))`).
126
+ They carry no ready-made fix prompt: read the full comment
127
+ (`gh api repos/{owner}/{repo}/pulls/comments/<comment_id>`), verify its claim against the real
128
+ code, and handle it like any CodeRabbit finding: fix it, or reply with the evidence for not fixing it.
129
+
97
130
  Mark each thread as seen with an 👀 reaction on its first comment (`databaseId` from the query), so
98
131
  the bot and a watching human can tell it is being worked:
99
132
 
@@ -103,7 +136,9 @@ gh api --method POST repos/{owner}/{repo}/pulls/comments/<DATABASE_ID>/reactions
103
136
 
104
137
  #### C. Check exit conditions
105
138
 
106
- Stop if there are zero unresolved CodeRabbit threads, or max iterations reached.
139
+ Stop when step A's read exited `0` (`clean`) after your last push (a `--wait` read, which settles),
140
+ or exited `30` (`limited`), or the iteration cap is reached. An empty working set on its own is not
141
+ done: with no review of the current head yet, it only means CodeRabbit has not answered your push.
107
142
 
108
143
  #### D. Fix each finding
109
144
 
@@ -123,6 +158,7 @@ If step D changed code:
123
158
  git status --short # look before you stage
124
159
  git add <path> [<path>...] # the files named in the findings you fixed
125
160
  git commit -m "address CodeRabbit review feedback (coderabbitloop iteration N)"
161
+ SINCE=$(date -u +%Y-%m-%dT%H:%M:%SZ) # step A reads bot activity after this
126
162
  git push
127
163
  ```
128
164
 
@@ -171,6 +207,18 @@ the reply, and a resolved thread with no reply loses the reasoning.
171
207
 
172
208
  Go back to step A.
173
209
 
210
+ ### Keep the context small; resume instead of restarting
211
+
212
+ - Carry only three things between iterations: the iteration counter, the latest state JSON, and a
213
+ ledger of each thread you handled (fixed with its commit, rebutted, or blocked). Re-read files
214
+ fresh each iteration; do not re-paste earlier rounds' comment bodies.
215
+ - All loop state lives on the PR, so the loop is resumable. If the session has grown long or slow,
216
+ or a bot commented after the loop ended, run this skill again in a fresh session: step A picks up
217
+ from the PR as it is now. Starting fresh is cheaper than dragging a long session through more
218
+ rounds.
219
+ - After reporting done, if the host can schedule a follow-up (Claude Code: `/loop` or a scheduled
220
+ wakeup), offer one re-check about 20 minutes later for bots that comment late.
221
+
174
222
  ### 3. Report
175
223
 
176
224
  | Field | Value |
@@ -179,6 +227,7 @@ Go back to step A.
179
227
  | Findings resolved | N |
180
228
  | Findings skipped | N (with reasons) |
181
229
  | Remaining | N (if any) |
230
+ | Human threads | N (open, listed; never resolved by this loop) |
182
231
  | Blocked | rate-limited / timeout / max-iterations / none |
183
232
 
184
233
  ```
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bash
2
+ # pr-review-state.sh: read a PR's review-bot state once (or until it settles) and print it as JSON.
3
+ #
4
+ # Shared by every review loop in olko-github-pr (codexloop, geminiloop, copilotloop,
5
+ # coderabbitloop, qodoloop). Each skill ships its own copy so it works on every adapter;
6
+ # tests/test-pr-review-state.sh fails if the copies drift apart.
7
+ #
8
+ # The loops decide "done" from this output, never from their own memory of what they resolved:
9
+ # clean every --bot has acted on the current head AND no bot thread is unresolved
10
+ # findings at least one unresolved thread from any bot (target or not): work to do
11
+ # pending some --bot has not acted on the current head yet
12
+ #
13
+ # "Acted on the head" = a review of the head commit, or after the head was pushed: a
14
+ # comment that is not a progress notice, or a PR reaction other than 👀 (Codex signals a clean
15
+ # review with only a 👍 on the PR, and edits a "**Running**" summary comment while it works).
16
+ # limited a --bot posted a rate-limit / paused notice after the current head
17
+ set -euo pipefail
18
+
19
+ usage() {
20
+ cat >&2 <<'EOF'
21
+ usage: pr-review-state.sh --repo OWNER/REPO --pr N --bot REGEX [--bot REGEX ...]
22
+ [--since ISO8601] [--wait SECONDS] [--settle SECONDS] [--interval SECONDS]
23
+
24
+ --bot case-insensitive regex matched against the bare bot login (GraphQL drops "[bot]"),
25
+ e.g. codex, gemini-code-assist, copilot, coderabbitai, qodo-code-review. Repeatable:
26
+ every --bot must have acted on the current head before the verdict can be "clean".
27
+ --since when the current head was pushed (date -u +%Y-%m-%dT%H:%M:%SZ, taken before the push).
28
+ Bot activity counts for the head only if it happened after max(head commit date, since).
29
+ --wait poll until every --bot has acted on the current head (or posted a limit notice), or
30
+ SECONDS pass (default 0: read once). Open threads do not end the wait early, so threads
31
+ left open on purpose cannot make a fresh push look reviewed.
32
+ --settle once "clean", wait SECONDS and read again, so a late comment is not missed
33
+ (default 60 when --wait is set, else 0).
34
+ --interval seconds between polls (default 20).
35
+
36
+ exit: 0 clean, 10 findings, 20 pending, 30 limited, 2 usage, 1 error
37
+ EOF
38
+ exit 2
39
+ }
40
+
41
+ REPO="" PR="" SINCE="" WAIT=0 SETTLE="" INTERVAL=20
42
+ BOTS=()
43
+ while [ $# -gt 0 ]; do
44
+ case "$1" in
45
+ --repo) REPO="${2:-}"; shift 2 ;;
46
+ --pr) PR="${2:-}"; shift 2 ;;
47
+ --bot) BOTS+=("${2:-}"); shift 2 ;;
48
+ --since) SINCE="${2:-}"; shift 2 ;;
49
+ --wait) WAIT="${2:-}"; shift 2 ;;
50
+ --settle) SETTLE="${2:-}"; shift 2 ;;
51
+ --interval) INTERVAL="${2:-}"; shift 2 ;;
52
+ -h|--help) usage ;;
53
+ *) echo "unknown argument: $1" >&2; usage ;;
54
+ esac
55
+ done
56
+ [ -n "$REPO" ] && [ -n "$PR" ] && [ "${#BOTS[@]}" -gt 0 ] || usage
57
+ case "$REPO" in */*) ;; *) usage ;; esac
58
+ [ -n "$SETTLE" ] || { [ "$WAIT" -gt 0 ] && SETTLE=60 || SETTLE=0; }
59
+ OWNER="${REPO%%/*}" NAME="${REPO#*/}"
60
+ BOTS_JSON=$(printf '%s\n' "${BOTS[@]}" | jq -R . | jq -s .)
61
+
62
+ QUERY='query($owner:String!,$name:String!,$pr:Int!,$cursor:String){
63
+ repository(owner:$owner,name:$name){ pullRequest(number:$pr){
64
+ headRefOid
65
+ commits(last:1){ nodes{ commit{ oid committedDate } } }
66
+ reviews(last:100){ nodes{ author{ login __typename } commit{ oid } submittedAt body } }
67
+ comments(last:100){ nodes{ author{ login __typename } createdAt updatedAt body } }
68
+ reactions(last:50){ nodes{ content createdAt user{ login } } }
69
+ reviewThreads(first:100, after:$cursor){
70
+ pageInfo{ hasNextPage endCursor }
71
+ nodes{ id isResolved isOutdated path line
72
+ comments(first:1){ nodes{ databaseId url author{ login __typename } body } } }
73
+ }
74
+ } }
75
+ }'
76
+
77
+ # One full read: first page carries head/reviews/comments; every page carries threads.
78
+ read_state() {
79
+ local cursor=null page first="" threads="[]"
80
+ while :; do
81
+ page=$(gh api graphql -f query="$QUERY" -f owner="$OWNER" -f name="$NAME" \
82
+ -F pr="$PR" -F cursor="$cursor")
83
+ [ -n "$first" ] || first="$page"
84
+ threads=$(jq -c --argjson acc "$threads" \
85
+ '$acc + .data.repository.pullRequest.reviewThreads.nodes' <<<"$page")
86
+ [ "$(jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.hasNextPage' <<<"$page")" = "true" ] || break
87
+ cursor=$(jq -r '.data.repository.pullRequest.reviewThreads.pageInfo.endCursor' <<<"$page")
88
+ done
89
+
90
+ jq -c --argjson bots "$BOTS_JSON" --argjson threads "$threads" --arg since "$SINCE" '
91
+ .data.repository.pullRequest as $p
92
+ | $p.headRefOid as $head
93
+ | ([$p.commits.nodes[0].commit.committedDate // "", $since] | max) as $after
94
+ | def matches($login): any($bots[]; . as $re | ($login // "") | test($re; "i"));
95
+ def isbot($a): ($a.__typename // "User") != "User";
96
+ def progress: test("busy working|in progress|currently processing|currently reviewing|\\*\\*(running|queued)\\*\\*"; "i");
97
+ def limited: test("review limit reached|reached your (pr )?review limit|reviews are paused|rate limit (exceeded|reached)|been rate[- ]limited"; "i");
98
+ [ $bots[] as $re
99
+ | ($p.reviews.nodes | map(select((.author.login // "") | test($re; "i")))) as $revs
100
+ | ($p.comments.nodes | map(select((.author.login // "") | test($re; "i")))) as $coms
101
+ | ($coms | map(select(.updatedAt >= $after))) as $fresh
102
+ | ($p.reactions.nodes | map(select(((.user.login // "") | test($re; "i"))
103
+ and .content != "EYES" and .createdAt >= $after))) as $reacts
104
+ | {
105
+ bot: $re,
106
+ reviewed_head: any($revs[]; .commit.oid == $head),
107
+ activity_after_head: (any($fresh[]; (.body | progress) | not)
108
+ or ($reacts | length) > 0),
109
+ limited: any($fresh[]; .body | limited)
110
+ }
111
+ | .acted_on_head = (.reviewed_head or .activity_after_head)
112
+ ] as $per_bot
113
+ | [ $threads[] | select(.isResolved | not)
114
+ | .comments.nodes[0] as $c
115
+ | {
116
+ thread_id: .id, comment_id: $c.databaseId, url: $c.url,
117
+ author: $c.author.login, is_bot: isbot($c.author), is_target: matches($c.author.login),
118
+ path, line, outdated: .isOutdated,
119
+ excerpt: (($c.body // "") | gsub("\\s+"; " ") | .[0:160])
120
+ } ] as $open
121
+ | ($open | map(select(.is_bot))) as $open_bot
122
+ | {
123
+ head: $head,
124
+ head_after: $after,
125
+ bots: $per_bot,
126
+ unresolved: $open,
127
+ counts: {
128
+ target: ($open_bot | map(select(.is_target)) | length),
129
+ other_bots: ($open_bot | map(select(.is_target | not)) | length),
130
+ humans: ($open | map(select(.is_bot | not)) | length)
131
+ },
132
+ verdict: (
133
+ if any($per_bot[]; .limited) then "limited"
134
+ elif ($open_bot | length) > 0 then "findings"
135
+ elif any($per_bot[]; .acted_on_head | not) then "pending"
136
+ else "clean" end)
137
+ }' <<<"$first"
138
+ }
139
+
140
+ exit_for() {
141
+ case "$1" in clean) echo 0 ;; findings) echo 10 ;; pending) echo 20 ;; limited) echo 30 ;; *) echo 1 ;; esac
142
+ }
143
+
144
+ deadline=$(( SECONDS + WAIT ))
145
+ state=$(read_state)
146
+ verdict=$(jq -r .verdict <<<"$state")
147
+ waiting() { jq -e '.verdict != "limited" and any(.bots[]; .acted_on_head | not)' >/dev/null <<<"$state"; }
148
+ while :; do
149
+ if waiting && [ "$SECONDS" -lt "$deadline" ]; then
150
+ sleep "$INTERVAL"
151
+ elif [ "$verdict" = "clean" ] && [ "$SETTLE" -gt 0 ]; then
152
+ # Clean once is not clean: bots post a review, then a second bot or a follow-up lands.
153
+ sleep "$SETTLE"
154
+ SETTLE=0
155
+ else
156
+ break
157
+ fi
158
+ state=$(read_state)
159
+ verdict=$(jq -r .verdict <<<"$state")
160
+ done
161
+
162
+ printf '%s\n' "$state"
163
+ exit "$(exit_for "$verdict")"