|
| 1 | +--- |
| 2 | +name: loop-review-pr |
| 3 | +description: > |
| 4 | + Iteratively review and fix a Pluto PR until it is "ideal" — drives the |
| 5 | + /review-pr multi-agent pipeline inside /ralph-loop, applying fixes between |
| 6 | + iterations and never posting inline comments. After the loop terminates, |
| 7 | + posts a single summary comment to the GitHub PR with everything that was |
| 8 | + resolved. Invoke as `/loop-review-pr <PR-number|PR-URL> |
| 9 | + [--max-iterations N]`. |
| 10 | +--- |
| 11 | + |
| 12 | +# Loop Review PR |
| 13 | + |
| 14 | +Orchestrate a self-improving review-and-fix loop for a Pluto PR. The |
| 15 | +[[review-pr]] skill is run repeatedly inside [[ralph-loop]]; each iteration |
| 16 | +the findings are addressed in code, then the next iteration re-reviews. **No |
| 17 | +inline comments are posted to GitHub during the loop.** After completion (or |
| 18 | +hitting the iteration cap) post one summary comment. |
| 19 | + |
| 20 | +## 0. Inputs and constants |
| 21 | + |
| 22 | +```text |
| 23 | +REPO = NethermindEth/pluto |
| 24 | +PR = <number from arg, or extracted from URL> |
| 25 | +MAX_ITERATIONS = <--max-iterations N | default 15> |
| 26 | +STATE_DIR = .claude/loop-review-state |
| 27 | +STATE = $STATE_DIR/pr-$PR.md |
| 28 | +COMPLETION_PROMISE = "PR_IDEAL" |
| 29 | +``` |
| 30 | + |
| 31 | +If the user passed a URL like `https://github.com/NethermindEth/pluto/pull/311`, |
| 32 | +extract `311`. Reject any other repo. |
| 33 | + |
| 34 | +## 1. Preflight (outer turn — before the loop starts) |
| 35 | + |
| 36 | +Run in parallel: |
| 37 | + |
| 38 | +```bash |
| 39 | +gh pr view "$PR" --repo "$REPO" --json title,body,headRefName,headRefOid,baseRefName,state,isDraft |
| 40 | +gh pr diff "$PR" --repo "$REPO" | head -5 # confirm diff is fetchable |
| 41 | +git status --short |
| 42 | +``` |
| 43 | + |
| 44 | +Then: |
| 45 | + |
| 46 | +1. Refuse if the PR is `MERGED` or `CLOSED`. |
| 47 | +2. Check out the PR branch locally: |
| 48 | + ```bash |
| 49 | + gh pr checkout "$PR" --repo "$REPO" |
| 50 | + ``` |
| 51 | + Abort if the working tree is dirty (uncommitted changes) — ask the user to |
| 52 | + stash first. Do not run destructive cleanups. |
| 53 | +3. Create `$STATE_DIR` if missing. If `$STATE` exists from a prior run, read |
| 54 | + it — it contains the running log of resolved findings; the loop will |
| 55 | + append to it. |
| 56 | +4. If `$STATE` does not exist, initialize it: |
| 57 | + |
| 58 | + ```markdown |
| 59 | + # /loop-review-pr state for PR #<PR> |
| 60 | + |
| 61 | + - Title: <PR title> |
| 62 | + - Branch: <headRefName> |
| 63 | + - Started: <ISO timestamp> |
| 64 | + - Max iterations: <MAX_ITERATIONS> |
| 65 | + |
| 66 | + ## Iteration log |
| 67 | + ``` |
| 68 | + |
| 69 | +## 2. Build the ralph-loop prompt |
| 70 | + |
| 71 | +The prompt is what ralph-loop will feed back to Claude on every iteration. |
| 72 | +Construct it as a single string. Substitute `$PR`, `$REPO`, `$STATE`, |
| 73 | +`$MAX_ITERATIONS` literally. |
| 74 | + |
| 75 | +````text |
| 76 | +You are running iteration of /loop-review-pr for $REPO PR #$PR. |
| 77 | +
|
| 78 | +Goal: keep iterating — review the PR with the same parallel-agent pipeline |
| 79 | +as /review-pr, then fix what reviewers flag — until the PR is "ideal". |
| 80 | +A PR is ideal when an internal review pass produces NO findings at |
| 81 | +severity `bug` or `major`, AND `cargo +nightly fmt --all --check`, |
| 82 | +`cargo clippy --workspace --all-targets --all-features -- -D warnings`, |
| 83 | +and `cargo test --workspace --all-features` all succeed. |
| 84 | +
|
| 85 | +State file: $STATE |
| 86 | +Read it FIRST every iteration. It is the running log of what previous |
| 87 | +iterations did. Append to it; never rewrite earlier entries. |
| 88 | +
|
| 89 | +## Per-iteration workflow |
| 90 | +
|
| 91 | +1. **Read state.** `cat $STATE`. Note the iteration number — increment by 1 |
| 92 | + for this iteration. If the prior iteration ended with "PR is ideal", |
| 93 | + verify the claim by re-running the quality gates; if still clean, output |
| 94 | + `<promise>PR_IDEAL</promise>` and stop. |
| 95 | +
|
| 96 | +2. **Sync.** `git fetch origin && git status --short && git log --oneline -5`. |
| 97 | + Make sure you are still on the PR branch. |
| 98 | +
|
| 99 | +3. **Internal review (no GitHub writes).** Spawn the same four agents in |
| 100 | + parallel as /review-pr Step 2: |
| 101 | +
|
| 102 | + | Agent | Skill | Focus | |
| 103 | + |---|---|---| |
| 104 | + | pluto-review | /pluto-review | Functional equivalence with Charon Go | |
| 105 | + | security | — | Auth, key material, DoS, exhaustion | |
| 106 | + | rust-style | /rust-style | Idiomatic Rust, error handling, naming | |
| 107 | + | code-quality | — | Concurrency, state machines, lifecycle | |
| 108 | +
|
| 109 | + Give each agent the diff (`gh pr diff $PR --repo $REPO`) and the changed |
| 110 | + files on disk. Each agent returns JSON findings as in /review-pr Step 2. |
| 111 | +
|
| 112 | + **You MUST NOT** call any of the following during this loop: |
| 113 | + - `gh pr review` |
| 114 | + - `gh pr comment` |
| 115 | + - `gh api .../pulls/.../reviews` |
| 116 | + - `gh api .../pulls/.../comments` |
| 117 | + - `gh api .../issues/.../comments` |
| 118 | + - any GraphQL `addPullRequestReview*` mutation |
| 119 | + The summary comment is posted by the OUTER turn after the loop ends — |
| 120 | + not from inside the loop. |
| 121 | +
|
| 122 | +4. **Dedupe + assess.** Merge findings; assign final severity |
| 123 | + (`bug` > `major` > `minor` > `nit`). Same rules as /review-pr Step 3. |
| 124 | +
|
| 125 | +5. **Decide.** |
| 126 | + - If there are zero `bug` and zero `major` findings → go to step 7. |
| 127 | + - Else → step 6. |
| 128 | +
|
| 129 | +6. **Fix.** Pick the highest-severity finding, fix the code (and add/update |
| 130 | + tests where the finding is about behavior). Re-run the relevant tests |
| 131 | + for the touched crate. Commit the fix with a focused message — one |
| 132 | + commit per finding is fine, batched commits per file are also fine, |
| 133 | + but DO NOT batch unrelated fixes into one commit. Then append an entry |
| 134 | + to $STATE: |
| 135 | +
|
| 136 | + ```markdown |
| 137 | + ### Iteration <N> — <ISO timestamp> |
| 138 | + - [<severity>] <title> @ <file>:<line> |
| 139 | + Fix: <one-line description of what changed> |
| 140 | + Commit: <sha> |
| 141 | + ``` |
| 142 | +
|
| 143 | + After fixing as many findings as you can in this iteration, exit. The |
| 144 | + ralph-loop Stop hook will re-invoke this prompt for the next iteration, |
| 145 | + which will re-review against the new state of the branch. |
| 146 | +
|
| 147 | +7. **Quality gates.** Run from `pluto/`: |
| 148 | + ```bash |
| 149 | + cargo +nightly fmt --all --check |
| 150 | + cargo clippy --workspace --all-targets --all-features -- -D warnings |
| 151 | + cargo test --workspace --all-features |
| 152 | + ``` |
| 153 | + If any fail, treat the failure as a `bug` finding and go back to step 6. |
| 154 | + If all pass AND step 5 found no `bug`/`major` findings, append to $STATE: |
| 155 | +
|
| 156 | + ```markdown |
| 157 | + ### Iteration <N> — <ISO timestamp> — IDEAL |
| 158 | + - Internal review: clean (only minor/nit, or none) |
| 159 | + - fmt / clippy / test: green |
| 160 | + ``` |
| 161 | +
|
| 162 | + Then output exactly: `<promise>PR_IDEAL</promise>` |
| 163 | +
|
| 164 | +## Hard rules |
| 165 | +
|
| 166 | +- One PR, one branch. Never switch branches; never rebase onto main inside |
| 167 | + the loop unless a fix explicitly requires it (and then say so in $STATE). |
| 168 | +- Do not force-push. |
| 169 | +- Do not skip git hooks (no `--no-verify`). |
| 170 | +- Do not include a `Co-Authored-By:` trailer in commits — the user has |
| 171 | + explicitly rejected it. |
| 172 | +- Do not delete work-in-progress files left by earlier iterations. |
| 173 | +- If progress stalls (two consecutive iterations with no new fixes and the |
| 174 | + same findings) — append a `### STALLED` note to $STATE explaining what is |
| 175 | + blocking, then output `<promise>PR_IDEAL</promise>` is FORBIDDEN. Instead |
| 176 | + let the iteration cap end the loop; the outer turn will summarize the |
| 177 | + stall. |
| 178 | +```` |
| 179 | + |
| 180 | +## 3. Start the loop |
| 181 | + |
| 182 | +Invoke ralph-loop with the prompt above. From the outer turn, call the |
| 183 | +ralph-loop slash command (it's the `ralph-loop:ralph-loop` plugin command): |
| 184 | + |
| 185 | +```text |
| 186 | +/ralph-loop "<the prompt from §2>" --completion-promise "PR_IDEAL" --max-iterations <MAX_ITERATIONS> |
| 187 | +``` |
| 188 | + |
| 189 | +Use the Skill tool with `skill: "ralph-loop:ralph-loop"` and pass the prompt |
| 190 | +plus flags as args. The loop runs inside the current session; the Stop hook |
| 191 | +keeps re-firing the prompt until the completion promise appears or the |
| 192 | +iteration cap is hit. |
| 193 | + |
| 194 | +## 4. After the loop ends |
| 195 | + |
| 196 | +When control returns to the outer turn (either the completion promise was |
| 197 | +emitted or `--max-iterations` was reached): |
| 198 | + |
| 199 | +1. **Verify gates one more time** from the outer turn (don't trust the loop): |
| 200 | + ```bash |
| 201 | + cd pluto |
| 202 | + cargo +nightly fmt --all --check |
| 203 | + cargo clippy --workspace --all-targets --all-features -- -D warnings |
| 204 | + cargo test --workspace --all-features |
| 205 | + ``` |
| 206 | + |
| 207 | +2. **Push** the accumulated commits to the PR branch: |
| 208 | + ```bash |
| 209 | + git push # branch already tracks the PR head; no --force |
| 210 | + ``` |
| 211 | + If the upstream rejected (someone else pushed) → stop and surface to the |
| 212 | + user; do not force. |
| 213 | + |
| 214 | +3. **Build the summary** from `$STATE`. Group resolved findings by severity |
| 215 | + and reference commits. Compute: |
| 216 | + - `iterations_run` = number of `### Iteration N` headers. |
| 217 | + - `terminated_by` = `completion_promise` | `iteration_cap` | `stall`. |
| 218 | + - `gates` = result of step 1 above. |
| 219 | + |
| 220 | +4. **Post exactly one comment** to the PR: |
| 221 | + |
| 222 | + ```bash |
| 223 | + gh pr comment "$PR" --repo "$REPO" --body-file /tmp/loop-review-summary.md |
| 224 | + ``` |
| 225 | + |
| 226 | + Body template: |
| 227 | + |
| 228 | + ```markdown |
| 229 | + ## /loop-review-pr summary |
| 230 | + |
| 231 | + Ran <iterations_run> review-and-fix iteration(s) against this PR. |
| 232 | + Terminated by: **<terminated_by>**. |
| 233 | + |
| 234 | + ### Quality gates (final) |
| 235 | + - `cargo fmt` — <pass|fail> |
| 236 | + - `cargo clippy` — <pass|fail> |
| 237 | + - `cargo test` — <pass|fail> |
| 238 | + |
| 239 | + ### Resolved during the loop |
| 240 | + |
| 241 | + **Bugs (<N>)** |
| 242 | + - <title> — `<file>:<line>` — fix in <commit-sha> |
| 243 | +
|
| 244 | + **Major (<N>)** |
| 245 | + - … |
| 246 | +
|
| 247 | + **Minor (<N>)** |
| 248 | + - … |
| 249 | +
|
| 250 | + **Nits (<N>)** |
| 251 | + - … |
| 252 | +
|
| 253 | + ### Outstanding |
| 254 | + <Any findings the loop chose not to address, or — if terminated by |
| 255 | + iteration_cap / stall — the unresolved items from $STATE, with reasons.> |
| 256 | +
|
| 257 | + ### Verdict |
| 258 | + <One of: |
| 259 | + - "PR is ideal — all bug/major findings resolved, gates green." |
| 260 | + - "Hit iteration cap (<N>) before reaching ideal state — see Outstanding." |
| 261 | + - "Stalled at iteration <N> — see Outstanding for blockers." |
| 262 | + > |
| 263 | + ``` |
| 264 | +
|
| 265 | + This is the **only** comment posted to GitHub. No inline review comments. |
| 266 | + No second comment. If the body is empty (no findings resolved, gates |
| 267 | + already green on entry), still post a single one-line "no changes were |
| 268 | + needed" comment so the run is auditable. |
| 269 | +
|
| 270 | +5. **Print to the user** the PR URL and a one-line verdict, plus the path |
| 271 | + to `$STATE` if they want to inspect the full log. |
| 272 | +
|
| 273 | +## Error & edge cases |
| 274 | +
|
| 275 | +- **No findings in iteration 1, gates green** → loop exits immediately with |
| 276 | + `PR_IDEAL`; outer turn posts the "no changes needed" comment. |
| 277 | +- **Loop emits the promise but gates fail in the outer verification** → |
| 278 | + re-enter the loop with the failing-gate output prepended; do not post a |
| 279 | + misleading "ideal" summary. |
| 280 | +- **User cancels with `/cancel-ralph`** → outer turn still runs §4 with |
| 281 | + `terminated_by: user_cancel` and posts the summary of whatever was done. |
| 282 | +- **PR has additional commits pushed by someone else mid-loop** → next |
| 283 | + iteration's `git fetch` will surface it; the loop should rebase only if |
| 284 | + necessary, and the summary should mention it. |
| 285 | +
|
| 286 | +## Output |
| 287 | +
|
| 288 | +After §4 step 4 succeeds, print: |
| 289 | +
|
| 290 | +```text |
| 291 | +Loop done: <iterations_run> iteration(s), terminated by <terminated_by>. |
| 292 | +Summary comment: <gh pr comment URL> |
| 293 | +State log: $STATE |
| 294 | +``` |
0 commit comments