@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.
- package/.github/prompts/coderabbitloop.prompt.md +72 -23
- package/.github/prompts/codexloop.prompt.md +87 -85
- package/.github/prompts/copilotloop.prompt.md +84 -88
- package/.github/prompts/geminiloop.prompt.md +85 -85
- package/.github/prompts/qodoloop.prompt.md +78 -20
- package/.kiro/steering/coderabbitloop.md +72 -23
- package/.kiro/steering/codexloop.md +87 -85
- package/.kiro/steering/copilotloop.md +84 -88
- package/.kiro/steering/geminiloop.md +85 -85
- package/.kiro/steering/qodoloop.md +78 -20
- package/.windsurf/rules/coderabbitloop.md +72 -23
- package/.windsurf/rules/codexloop.md +87 -85
- package/.windsurf/rules/copilotloop.md +84 -88
- package/.windsurf/rules/geminiloop.md +85 -85
- package/.windsurf/rules/qodoloop.md +78 -20
- package/adapters/claude/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
- package/adapters/claude/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
- package/adapters/claude/olko-github-pr/skills/codexloop/SKILL.md +90 -88
- package/adapters/claude/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
- package/adapters/claude/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
- package/adapters/claude/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
- package/adapters/claude/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
- package/adapters/claude/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/github-post.md +4 -3
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/output-format.md +5 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/pricing.json +5 -1
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-deep-codex.md +2 -2
- package/adapters/claude/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-medium-codex.md +2 -2
- package/adapters/claude/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
- package/adapters/claude/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
- package/adapters/cursor/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
- package/adapters/cursor/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
- package/adapters/cursor/olko-github-pr/skills/codexloop/SKILL.md +90 -88
- package/adapters/cursor/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
- package/adapters/cursor/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
- package/adapters/cursor/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
- package/adapters/cursor/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
- package/adapters/cursor/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
- package/adapters/cursor/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
- package/adapters/cursor/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
- package/adapters/grok/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
- package/adapters/grok/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
- package/adapters/grok/olko-github-pr/skills/codexloop/SKILL.md +90 -88
- package/adapters/grok/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
- package/adapters/grok/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
- package/adapters/grok/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
- package/adapters/grok/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
- package/adapters/grok/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
- package/adapters/grok/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
- package/adapters/grok/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
- package/package.json +1 -1
- package/plugins/olko-apple-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-creative/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-garmin-kit/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-git-tools/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-github-pr/skills/coderabbitloop/SKILL.md +73 -24
- package/plugins/olko-github-pr/skills/coderabbitloop/scripts/pr-review-state.sh +163 -0
- package/plugins/olko-github-pr/skills/codexloop/SKILL.md +90 -88
- package/plugins/olko-github-pr/skills/codexloop/scripts/pr-review-state.sh +163 -0
- package/plugins/olko-github-pr/skills/copilotloop/SKILL.md +85 -89
- package/plugins/olko-github-pr/skills/copilotloop/scripts/pr-review-state.sh +163 -0
- package/plugins/olko-github-pr/skills/geminiloop/SKILL.md +87 -87
- package/plugins/olko-github-pr/skills/geminiloop/scripts/pr-review-state.sh +163 -0
- package/plugins/olko-github-pr/skills/lekker-review/references/github-post.md +4 -3
- package/plugins/olko-github-pr/skills/lekker-review/references/output-format.md +5 -1
- package/plugins/olko-github-pr/skills/lekker-review/references/pricing.json +5 -1
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-deep-codex.md +2 -2
- package/plugins/olko-github-pr/skills/lekker-review/references/revmux/profiles/lekker-medium-codex.md +2 -2
- package/plugins/olko-github-pr/skills/qodoloop/SKILL.md +79 -21
- package/plugins/olko-github-pr/skills/qodoloop/scripts/pr-review-state.sh +163 -0
- package/plugins/olko-obsidian/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-product/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-reflection/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-release/.claude-plugin/plugin.json +1 -1
- package/plugins/olko-skill-meta/.claude-plugin/plugin.json +1 -1
- 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;
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
70
|
-
|
|
71
|
-
-
|
|
72
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
|
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
|
|
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.
|
|
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.
|
|
86
|
+
#### A. Read the PR state; wait for CodeRabbit's review of the current head
|
|
66
87
|
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
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
|
-
|
|
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
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
`
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
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
|
|
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")"
|