reviewit — post-push AI review cycle
You are orchestrating the post-push AI review cycle for an open pull request.
Lean mode (default): Two reviewers are fired at the same iteration watermark — Gemini Flash and GitHub Copilot — but handling is staggered. Act on Gemini as soon as it returns, push those fixes, then collect Copilot when it finishes before starting the next iteration. Cap is 2 iterations.
Deep mode (deep arg): Same two reviewers as lean — Gemini Flash + Copilot, same staggered fast/delayed handling. No in-skill /review. Three behavioral changes vs lean:
- 4-iter cap with early-exit on no-fix iters. If an iteration produced no
fixresolutions across either the Gemini fast pass or the Copilot delayed pass (everything was deferred or dismissed, or there were no findings), exit the loop — re-firing reviewers on an unchanged HEAD just re-posts the same findings. - Mid-loop cost-shift checkpoint between iter 2 → 3 and iter 3 → 4. If the iteration that just completed produced fixes but findings still aren't converging (any critical, or critical+suggestion+nitpick ≥ 5 post-dedup), pause and ask the user before spending another iteration's worth of paid-reviewer budget. Three exits: continue the chain, bail early to the final
/deepgrill(skipping the remaining paid iters), or stop and merge as-is (skipping/deepgrilltoo). The trigger only fires when fix-resolutions are still being produced — the no-fix early-exit above already handles the other non-convergence mode. - Final
/deepgrillon the PR. After the loop exits for any reason except merge-as-is (clean, early-exit, cost-shift bail-out, OR iter cap), invokeSkill(skill="deepgrill"). This runs/refactorpass+/grill deep(the full Claude-side agent matrix) as a separate sub-skill — fresh agents looking at the PR's current state. This replaces the older deep-mode/reviewfire that ran inside Phase 1 and routinely caused the orchestrator to drop out of the polling loop early.
In both modes, the /grill pre-push pass already covered Claude-side adversarial agents on the pre-push tree — so post-push the orchestrator delegates Claude-side review to a single post-loop /deepgrill (deep mode only) rather than re-running anything inline.
This replaces the older /review-cycle skill. Auto-trigger of Gemini and Copilot is intentionally disabled — /reviewit is the only path that fires AI review.
Mode resolution
$ARGUMENTS is whitespace-tokenized. The first token is the PR number; if a second token exists and equals deep (case-insensitive), set MODE=deep and MAX_ITERS=4. Otherwise MODE=lean and MAX_ITERS=2. Surface the resolved mode in the Phase 6 summary.
Core principles
- Full auto by default: once the PR number is provided, do not ask for confirmation between phases. Fix everything fixable, defer what isn't, dismiss false positives. Present the summary at the end.
- Reviewers are complementary: each catches things the other misses. Unique findings are the primary value. Overlap is acknowledged in replies but not dwelt on.
- Deduplicate before acting: don't fix the same thing twice. For staggered Copilot, dedupe against both reviewer findings and the code already changed by the Gemini fix commit.
- Reply to every comment after the push has produced the real commit SHA: for fixes, post the commit SHA; for deferrals, link the tracking issue; for dismissals, record the rationale. Replies happen in Phase 4 step 4, not in Phase 3 — Phase 3 only records resolutions to
/tmp. The reply step is the most-skipped step in this skill; do not fold it into "commit + push" or treat it as optional. - Cap at
MAX_ITERSreview iterations: each iteration is(fire reviewers → handle Gemini fast pass → push/reply → handle Copilot → push/reply → loop check). AfterMAX_ITERS, stop the loop and proceed to Phase 5.5 (deep) or Phase 6 (lean). The reply step is part of an iteration's completion criteria — an iteration that pushes fixes but doesn't post replies is incomplete. If a PR needs more than the cap, it's signaling something deeper (scope too large, or repeated regressions). - Deep-mode cost-shift checkpoint: in deep mode, after iterations 2 and 3, if findings still aren't converging (any critical, or critical+suggestion+nitpick ≥ 5 post-dedup), Phase 5 pauses and asks the user before firing the next iteration. Before pausing, Phase 5 scans the branch's pre-push commit history for
/refactorpassand/grillsignatures and adapts the prompt: pre-push detected → frame as genuine non-convergence and recommend the final/deepgrillas the deep-variant escalation; no signatures visible → frame as "pre-push appears skipped" and recommend running/deepgrillto catch up. Either way the user chooses: continue, bail to the final/deepgrillearly, or merge as-is. Lean mode is unaffected (cap=2 means it stops at the same point anyway). - No per-iter
/refactorpass: review-fix commits push directly. The base was refactor-passed pre-push, and re-running/simplifyon small surgical fixes has been validated to add negligible value.
Phase 0: Initialization
Argument: $ARGUMENTS — first token is the PR number; optional second token deep enables deep mode (see "Mode resolution" above).
Validate PR number (numeric, > 0). If missing, ask the user for it. Resolve
MODEandMAX_ITERSper the rule above.Fetch PR details:
gh pr view <pr-number> --json number,title,headRefName,baseRefName,state,files,mergeableCheck PR is open. If closed/merged/draft-without-explicit-confirmation, notify and exit.
Confirm the head ref is checked out locally (
git rev-parse --abbrev-ref HEADmatchesheadRefName). If not, the skill cannot push fixes — surface and exit.Triviality detection — prompt to skip the chain on docs/config-only PRs. Inspect the PR's changed files and classify by extension (same heuristic as
/refactorpassand/grillPhase 0):gh pr view <pr-number> --json files --jq '.files[].path'Classify each path. If only docs/config files (
.md,.txt,.yml,.yaml,.json,.toml,.gitignore,.gitattributes,LICENSE,CHANGELOG,README, files underdocs/,*.fixture.*, snapshot files), prompt the user before spending any reviewer budget:This PR looks docs/config-only — N files, no source code changes. Gemini Flash costs $0.05–$0.20 even on near-empty diffs. How to proceed? [C] Run the full chain anyway. Pick this if you specifically want Gemini's eyes on the doc content. [S] Skip everything — just merge.- C: proceed to Phase 1 normally.
- S: exit cleanly. Print a summary noting nothing was run.
For mixed changesets (some source, some docs), run the full chain (Phase 1 onward) without prompting — source files justify the spend.
TodoWrite: create tasks per iteration for "fire reviewers", "parse + dedup (Gemini fast pass)", "address findings (record resolutions)", "commit + push fixes", "post replies with real SHA", "poll Copilot", "parse + dedup (Copilot delayed pass)", "address + push + reply", "loop check". In deep mode, add a "cost-shift checkpoint" task at iterations 2 and 3 (the loop-check step ends with a possible AskUserQuestion in those iterations), and a final "run /deepgrill on the PR" task. The "post replies" tasks are their own line items — don't fold them into "commit + push" or they get skipped.
Phase 1: Fire reviewers and handle the fast pass
This phase fires once at the start of each iteration.
Pre-checks
Capture the current HEAD SHA and iteration start timestamp before firing the reviewers. These are the watermarks used in Phase 1's polling step to distinguish "review of the current commit" from "stale review from a prior iteration":
ITERATION_HEAD=$(gh pr view <pr-number> --json headRefOid --jq '.headRefOid')
ITERATION_STARTED_AT=$(date -u +%Y-%m-%dT%H:%M:%SZ)
Capture the current state of comments / reviews so polling can detect new posts (not pre-existing ones from earlier iterations or runs):
# Fetch existing comments to detect already-posted reviews
gh api --paginate repos/{owner}/{repo}/pulls/<pr-number>/comments \
> /tmp/pr-<pr-number>-comments.json
gh api --paginate repos/{owner}/{repo}/issues/<pr-number>/comments \
> /tmp/pr-<pr-number>-issue-comments.json
gh api repos/{owner}/{repo}/pulls/<pr-number>/reviews \
> /tmp/pr-<pr-number>-reviews.json
Fire Gemini (Flash by default)
gh workflow run "Gemini Code Review" \
--repo <owner>/<repo> \
-F pr_number=<pr-number> \
-F tier=flash
Pass -F tier=flash explicitly. The workflow defaults tier to pro when omitted (intentional for UI clickers, unintended for CLI/API callers). Pro is $1–$8 per review; Flash is $0.05–$0.20. Only override to pro if the user has explicitly asked for a deep review on a high-stakes PR (security/auth, schema migrations, large refactors) AND confirmed the cost.
Fire Copilot
Copilot is a Bot, not a User — gh pr edit --add-reviewer and REST requested_reviewers don't work. Use the GraphQL requestReviews mutation's botIds field:
PR_NODE=$(gh pr view <pr-number> --json id --jq '.id')
gh api graphql \
-f query='mutation($prId:ID!,$botIds:[ID!]){requestReviews(input:{pullRequestId:$prId,botIds:$botIds,union:true}){pullRequest{id}}}' \
-f prId="$PR_NODE" \
-f botIds='BOT_kgDOCnlnWA'
Copilot bot node id is BOT_kgDOCnlnWA (constant). Verify with gh api repos/{owner}/{repo}/pulls/<n>/requested_reviewers --jq '.users[].login' → expected Copilot. The mutation is idempotent — safe to call across iterations. If Copilot already reviewed the current HEAD (commit_id == ITERATION_HEAD from the polling step below), skip re-requesting.
Wait for Gemini (the fast reviewer)
Do not wait for Copilot before acting on Gemini. Copilot is usually much slower, so blocking the entire iteration on it leaves fast Gemini findings idle. The efficient sequence is:
- Fire Gemini and Copilot at the same
ITERATION_HEAD. - Wait for Gemini.
- Deduplicate, fix, push, and reply to Gemini findings.
- Then poll Copilot for the original
ITERATION_HEAD, dedupe against already-fixed issues, and handle any remaining findings. - Only after Copilot is handled or explicitly timed out should the next iteration begin.
Each reviewer has a different completion signal. The polling check must validate that the review pertains to ITERATION_HEAD (current HEAD), not a stale review from a prior iteration on the same PR.
Gemini: posts an issue comment with
<!-- GEMINI_REVIEW -->marker, plus inline comments with<!-- GEMINI_INLINE -->marker. Poll every 30s, timeout 10 min:# Re-fetch then filter: a Gemini comment whose `updated_at` is at-or-after # ITERATION_STARTED_AT — this is "newly posted/updated for this iteration" # rather than a leftover from a previous run. gh api --paginate repos/{owner}/{repo}/issues/<pr-number>/comments \ > /tmp/pr-<pr-number>-issue-comments.json jq --arg t "$ITERATION_STARTED_AT" \ '[.[] | select((.body | contains("<!-- GEMINI_REVIEW -->")) and (.updated_at >= $t))] | length' \ /tmp/pr-<pr-number>-issue-comments.jsonLength ≥ 1 → Gemini has posted for this iteration.
If Gemini times out (no finding at ITERATION_HEAD within the 10 min budget), stop cleanly with a resume command rather than waiting on Copilot first. The cycle expects Gemini to be the fast lane; if Gemini didn't fire at all (workflow misconfiguration, missing API key, etc.), surface that to the user before sinking time into Copilot polling.
Poll Copilot after the fast-pass push
After the Gemini fix commit is pushed and replies are posted, poll Copilot for the original ITERATION_HEAD. It is expected that Copilot may be reviewing the pre-fix head; use that output as delayed feedback for the same iteration, not as permission to skip it.
Copilot: posts findings via one of three modes — check both the reviews endpoint and the inline-comments endpoint, treat either signal as completion. Poll every 30s, timeout 10 min when
--waitis in effect; otherwise poll briefly and stop with a resume command if it is still pending:Mode pulls/<n>/reviewsrow?pulls/<n>/commentsrows?Review with findings yes ( state: COMMENTED)yes (one per finding) Findings without review no yes Approved with no findings yes ( state: APPROVED)no Mode 2 is real and observed in production. Polling only the reviews endpoint times out in this case while Copilot has already posted findings inline.
# Re-fetch BOTH endpoints — review row OR inline comments at ITERATION_HEAD # are independent signals that Copilot has finished. gh api repos/{owner}/{repo}/pulls/<pr-number>/reviews \ > /tmp/pr-<pr-number>-reviews.json gh api --paginate repos/{owner}/{repo}/pulls/<pr-number>/comments \ > /tmp/pr-<pr-number>-comments.json # Signal 1: top-level Copilot review at ITERATION_HEAD. COPILOT_REVIEW=$(jq --arg sha "$ITERATION_HEAD" \ '[.[] | select((.user.login | test("copilot"; "i")) and (.commit_id == $sha))] | length' \ /tmp/pr-<pr-number>-reviews.json) # Signal 2: Copilot inline comments at ITERATION_HEAD created since the # iteration started (filters out leftovers from earlier rounds on the # same PR). The `commit_id` field on review comments matches the head # SHA the comment was posted against, so the watermark is the same as # for reviews. COPILOT_INLINE=$(jq --arg sha "$ITERATION_HEAD" --arg t "$ITERATION_STARTED_AT" \ '[.[] | select((.user.login | test("copilot"; "i")) and (.commit_id == $sha) and (.created_at >= $t))] | length' \ /tmp/pr-<pr-number>-comments.json) if [ "$COPILOT_REVIEW" -ge 1 ] || [ "$COPILOT_INLINE" -ge 1 ]; then # Copilot is done for this iteration — proceed to delayed Copilot parsing. : fiEither signal ≥ 1 → Copilot has finished for this iteration. Do not count reviews or comments of prior commits as completion — those are stale.
Note: 0 inline comments ≠ missing review. A Copilot review on the current commit with
state: APPROVEDand no inline comments is a clean pass — surface the review body so the user sees it.
If Copilot is still pending after the non---wait poll budget, stop cleanly and report the resume command. Do not start the next iteration while Copilot for the previous ITERATION_HEAD is still pending unless the user explicitly tells you to abandon it. If Copilot times out in --wait mode, log it and proceed with the already-handled fast-pass findings. Note the missing reviewer in the final summary.
Phase 2: Parse, categorize, deduplicate
Run this phase twice per iteration:
- Gemini fast pass: parse Gemini findings (inline + summary), dedupe within them, then proceed through fixes/replies.
- Delayed Copilot pass: after the fast-pass push, parse Copilot findings for the original
ITERATION_HEAD, dedupe them against the Gemini findings and current code, then fix/reply only what remains.
For Copilot findings already fixed by the Gemini commit, do not edit code again. Reply with the Gemini commit SHA and record the comment id as handled.
Refresh comment fixtures
gh api --paginate repos/{owner}/{repo}/pulls/<pr-number>/comments \
> /tmp/pr-<pr-number>-comments.json
gh api --paginate repos/{owner}/{repo}/issues/<pr-number>/comments \
> /tmp/pr-<pr-number>-issue-comments.json
Parse Copilot findings
jq '[.[] | select((.user.login | test("copilot"; "i"))
and (.in_reply_to_id == null or .in_reply_to_id == 0))
| {id, path, line, body}]' \
/tmp/pr-<pr-number>-comments.json
For each: classify severity (critical / suggestion / nitpick / question), category (architecture / correctness / security / performance / maintainability / testing), record file path, summarize.
Parse Gemini findings
Gemini posts in TWO places:
Summary issue comment —
<!-- GEMINI_REVIEW -->marker, full list with counts. The canonical list for dedup math.jq -r '.[] | select(.body | contains("<!-- GEMINI_REVIEW -->")) | .body' \ /tmp/pr-<pr-number>-issue-comments.jsonInline review comments —
<!-- GEMINI_INLINE -->marker on each, posted bygithub-actions[bot]. The reply targets for per-line findings.jq '[.[] | select((.body | contains("<!-- GEMINI_INLINE -->")) and (.in_reply_to_id == null)) | {id, path, line, body}]' \ /tmp/pr-<pr-number>-comments.json
Severity emoji markers in Gemini bodies: 🔴 critical, 🟡 suggestion, 🟢 nitpick, 💡 question.
Deduplicate across reviewers
Two findings are duplicates if they:
- Reference the same file AND same line range (±5 lines), OR
- Reference the same file AND describe the same issue (semantic match)
For each group, classify as pair_overlap (Gemini + Copilot both caught it) or unique (only one reviewer).
In the staggered flow, Gemini's findings are dedupe-classified during the Gemini fast pass; Copilot's findings are dedupe-classified during the delayed pass against the already-handled Gemini set (so an "overlap" in the Copilot pass means "Gemini already addressed this — just reply with the Gemini fix SHA").
Present comparison
## Review Comparison — PR #<number>, iteration <N>
| Severity | Gemini | Copilot | Pair | Unique |
|-----------------|--------|---------|------|--------|
| Critical | | | | |
| Suggestions | | | | |
| Nitpicks | | | | |
| Questions | | | | |
| **Total** | | | | |
### Unique to Gemini
1. [severity] file:line — summary
### Unique to Copilot
...
### Overlapping
...
Proceed immediately to address findings — no confirmation needed.
Phase 3: Address findings (no replies yet)
For each deduplicated finding in the active pass, ordered by severity (critical first):
Read the file at the referenced path.
Classify resolution:
- Fix: apply the code change.
- Defer: create a GitHub issue (label:
from-ai-review). Capture the issue URL. - Dismiss: false positive — write down the rationale.
Execute resolution — Edit / Write /
gh issue createas needed.Record the resolution to
/tmp/pr-<pr-number>-iter-<N>-<pass>-resolutions.jsonas one row per reply target (= one row per original reviewer comment). Usegeminifor the Gemini fast pass andcopilotfor the delayed Copilot pass. Phase 4 step 5 reads this file and posts exactly one reply per row. Do not post any reply yet — the SHA isn't known until after the push.File format: a single JSON array
[{...}, {...}, ...]. Build the full array in-memory while iterating findings, then callWrite(file_path, content=<json-array>)once at the end of Phase 3. Don't try to "append" rows to a JSON file in place — naïve append produces invalid JSON. Phase 4 reads withjq -c '.[]'and iterates row-by-row.For cross-reviewer overlapping findings (the same underlying issue caught by multiple reviewers), write one row per reviewer, all sharing the same
overlap_group_id,resolution, andexplanation— but each with its ownfinding_id/reviewerand analso_flagged_bylisting the OTHER reviewers in the group. That way every original commenter gets a reply, and each reply correctly attributes the others.Each row must include enough context to post the right kind of reply at the right endpoint. The schema below uses
<angle-bracket placeholders>for illustration — the on-disk file contains real values (numbers, strings,nulls) and is valid JSON:{ "finding_id": <numeric comment id from GitHub, or null for gemini-summary findings>, "reviewer": "copilot" | "gemini-inline" | "gemini-summary", "path": "<file path>", "line": <line number or null>, "resolution": "fix" | "defer" | "dismiss", "explanation": "<one-line description of what changed / why dismissed>", "defer_issue_url": "<gh issue url, only for defer; null otherwise>", "also_flagged_by": ["<other reviewer names in this overlap group>"], "overlap_group_id": "<stable id shared by all rows in the same dedup group, or null for unique findings>", "finding_text": "<for gemini-summary rows: the exact bullet/paragraph from the Gemini summary that this row replies to. Captured at Phase 3 write time so Phase 4 can quote it without re-parsing the summary. null for inline rows.>" }Reviewer values:
copilot— Copilot inline review comment (user.loginmatchescopilot)gemini-inline—github-actions[bot]comment with<!-- GEMINI_INLINE -->markergemini-summary— finding present only in the Gemini summary<!-- GEMINI_REVIEW -->issue comment, no inline equivalent.finding_idis null;finding_textis required (used to quote the original finding in the new top-level issue comment Phase 4 will post).
Phase 4: Commit, push, and post replies
Run this phase after each pass that produced code changes or reply-worthy resolutions: once for the Gemini fast pass, and again for the delayed Copilot pass if it has remaining findings. If a pass has only dismissals or already-fixed findings (or no findings at all), skip the commit when there are no file changes, but still post the replies — with the prior Gemini-commit SHA for "already fixed" cases, or no SHA for dismissals.
Stage and commit:
git add <changed-files> git commit -m "fix: address AI review feedback (iteration <N>) on PR #<pr-number>"Push:
git push PUSHED_SHA=$(git rev-parse HEAD) PUSHED_SHA_SHORT=$(git rev-parse --short=8 HEAD)Verify the push landed on the PR head before posting replies — otherwise replies will reference a SHA that's not in the PR's commit history. GitHub's PR API is eventually consistent, so the headRefOid can lag the actual ref by a few seconds after a push. Use a short retry loop with backoff, and use string equality (not
grepregex) to avoid quoting/anchor surprises:verify_pr_head() { local attempt for attempt in 1 2 3 4; do local pr_head pr_head=$(gh pr view <pr-number> --json headRefOid --jq '.headRefOid') if [[ "$pr_head" == "$PUSHED_SHA" ]]; then return 0 fi sleep $(( attempt * 2 )) # 2s, 4s, 6s, 8s — total ~20s ceiling done echo "PR head ($pr_head) does not match pushed SHA ($PUSHED_SHA) after retries — investigate before replying" >&2 return 1 } verify_pr_head || exit 1Post replies now that the real SHA is in hand. Read the current pass's
/tmp/pr-<pr-number>-iter-<N>-<pass>-resolutions.jsonand post one reply per row. Do not skip this step — fixes without replies leave reviewer threads orphaned. Build the body with${PUSHED_SHA_SHORT}substituted inline.Reply body templates:
Fix (single reviewer):
Fixed in `${PUSHED_SHA_SHORT}`. <explanation from resolutions.json>Fix (overlapping —
also_flagged_bynon-empty):Fixed in `${PUSHED_SHA_SHORT}`. <explanation> Note: also flagged by <comma-joined also_flagged_by>.Defer:
Deferred — tracking in <defer_issue_url>. <explanation: why this is being deferred rather than fixed in this PR>Dismiss:
Dismissing — false positive. <explanation: why the reviewer's reasoning doesn't apply>
Endpoints (route by
reviewerfield):copilot,gemini-inline(whenfinding_idis non-null):gh api -X POST repos/{owner}/{repo}/pulls/<pr-number>/comments/<finding_id>/replies \ -f body="<assembled body>"gemini-summary(no inline equivalent, post a top-level issue comment quoting the finding):gh api -X POST repos/{owner}/{repo}/issues/<pr-number>/comments \ -f body="> <quoted summary finding text, prefixed with > on each line> <assembled body>"
Iterate the resolutions file row-by-row. If any single reply POST fails, log the failure and continue with the rest — partial reply coverage is better than no replies. Surface the count of failed-reply POSTs in the Phase 6 summary so the user can manually follow up.
Phase 5: Loop control
After each iteration (both the Gemini fast pass and the Copilot delayed pass complete), evaluate the cascade below in order. The first matching branch wins:
Iteration count ==
MAX_ITERS(2 in lean, 4 in deep) → exit the loop. Lean → Phase 6 with the iteration-cap message. Deep → Phase 5.5 (the final/deepgrillstill runs on the cap-state PR).All reviewers came back clean for this iteration (no new findings on the post-fix HEAD) → success. Lean → Phase 6. Deep → Phase 5.5.
Deep-mode no-fix early-exit — applies only when
MODE == deepAND this iteration produced zerofixresolutions across both the Gemini fast pass and the Copilot delayed pass (everything was deferred or dismissed). Re-firing reviewers on an unchanged HEAD just re-posts the same findings. → exit the loop, proceed to Phase 5.5. If you want a second opinion on a defer/dismiss decision, that's what the final/deepgrillis for. Lean mode falls through to step 5 in this case — the lean loop continues until reviewers come back clean or the cap is hit, sinceMAX_ITERS=2gives lean far less wiggle room to burn on unchanged-HEAD re-fires.Deep-mode cost-shift checkpoint — applies only when all of the following hold:
MODE == deep, AND- The iteration that just completed is iteration 2 OR iteration 3 (i.e., we're about to fire iteration 3 or iteration 4), AND
- This iteration produced ≥1
fixresolution (otherwise step 3 already fired), AND - The iteration's findings meet the significance threshold below.
Significance threshold — trip if either:
- Any critical-severity finding surfaced in the iteration just completed (across both reviewers, post-dedup), OR
- The total of critical + suggestion + nitpick findings ≥ 5 in the iteration just completed (post-dedup, questions excluded).
Pull the counts from the Phase 2 dedup table that was emitted at the start of this iteration. If the threshold is not met, fall through to step 5.
Re-prompt suppression — if the user already picked C (continue) at an earlier checkpoint in this PR's chain, do not prompt again. Track this with a sentinel file (survives session restarts so the rule holds even if
/reviewitis re-invoked mid-chain):BYPASS_FILE=/tmp/pr-<pr-number>-cost-shift-bypass if [[ -f "$BYPASS_FILE" ]]; then # User already chose to continue past the cost-shift checkpoint # on a previous iteration in this PR's chain. Fall through to step 5. : fiIf the sentinel file exists, skip the rest of step 4 and fall through to step 5. Otherwise continue.
If the threshold is met, the cost-benefit of continuing has shifted: the next iteration will spend additional Gemini Flash ($0.05–$0.20) + Copilot budget, and two consecutive non-converging iterations is a weak signal that paid reviewers will close the residuals on the next pass. The final
/deepgrill(Phase 5.5) uses local Claude agents at no per-call cost and catches most of what Gemini/Copilot would find on a non-converging diff — so bailing to it early can be cheaper than running another paid iteration first.Verify pre-push status before pausing. Whether the framing should be "non-convergence" or "skipped pre-push catching up" depends on whether
/refactorpass+/grillran before the PR was opened. Detect by scanning the branch's pre-PR commit history for the documented commit signatures:# Find the merge-base with the PR's base ref, then list the branch's commits. BASE_REF=$(gh pr view <pr-number> --json baseRefName --jq '.baseRefName') MERGE_BASE=$(git merge-base "origin/$BASE_REF" HEAD 2>/dev/null || true) if [[ -z "$MERGE_BASE" ]]; then # origin/<base> isn't fetched, or git merge-base failed for any other reason. # Do NOT fall back to `git log HEAD` — on a non-shallow clone that walks the # entire history and produces false-positive detection counts. Treat as # detection-skipped and proceed to the PREPUSH_DETECTED=false branch with # an explicit "(detection failed — origin/<base> not available)" note in # the prompt body so the user knows the framing is best-effort. PREPUSH_DETECTED=false DETECTION_FAILED=true else # Exclude post-push review-fix commits — only pre-push commits matter for # this detection. The `fix: address AI review feedback (iteration N) on PR #M` # commits are produced by /reviewit itself in Phase 4. BRANCH_SUBJECTS=$(git log --pretty=format:"%s" "$MERGE_BASE"..HEAD \ | grep -vE "^fix: address AI review feedback \(iteration ") REFACTORPASS_COMMITS=$(echo "$BRANCH_SUBJECTS" | grep -cE "^refactor: /simplify pass" || true) GRILL_FIX_COMMITS=$(echo "$BRANCH_SUBJECTS" | grep -cE "^fix: address /grill finding" || true) fiInterpret (when detection ran successfully):
REFACTORPASS_COMMITS >= 1→/refactorpassdefinitively ran. (Absence is inconclusive:/refactorpassskips silently when/simplifymade no changes, so 0 commits ≠ skipped.)GRILL_FIX_COMMITS >= 1→/grilldefinitively ran and produced at least one fix. (Absence is inconclusive:/grillproduces no commits when all findings were ignored or deferred, or when it ran clean.)- Pre-push appears to have run (
PREPUSH_DETECTED=true):REFACTORPASS_COMMITS >= 1ORGRILL_FIX_COMMITS >= 1. - Pre-push not detected (
PREPUSH_DETECTED=false): both counts are 0. Treat as "likely skipped" but acknowledge in the prompt that this is a heuristic — the user may have run the chain with no resulting commits (clean refactorpass + all-ignore/all-defer grill).
Known limitation: this detection assumes the PR's base ref is a normal branch name (alphanumerics,
/,.,-,_). Base refs containing git revision metacharacters (..,:,^,~) can be misinterpreted bygit merge-baseas revision ranges rather than ref names, producing detection failures or wrongMERGE_BASEvalues. This case is rare in practice (typical base refs aremain,develop, or stacked-PR feature branches) and is not actively sanitized — if it fires,MERGE_BASEwill likely be empty and the detection-failed branch above handles it gracefully.Pause and ask the user via
AskUserQuestion. The question body adapts based onPREPUSH_DETECTED. Construct it with the actual counts from the iteration that just completed. Remember in deep mode the final/deepgrill(Phase 5.5) runs after the loop exits regardless of[C]vs[L]—[L]just gets there earlier without spending another paid iteration first.When
PREPUSH_DETECTED=true:Iteration <N> in deep mode still surfaced <T> significant findings (<C> critical, <S> suggestion, <K> nitpick) despite /refactorpass + /grill having run pre-push (detected: <R> refactorpass commit(s), <G> grill-fix commit(s)). The remaining <MAX_ITERS - N> iteration(s) of paid reviewers would fire Gemini Flash ($0.05–$0.20) + Copilot again. Two iterations of non-convergence is a signal that paid reviewers aren't the right next tool — the final /deepgrill (Phase 5.5) is the deep-variant escalation and runs locally at no per-call cost. How to proceed? [C] Continue — fire iteration <N+1> of /reviewit deep as planned. The final /deepgrill runs after the loop ends, as usual. [L] Bail out of the paid-reviewer loop now and skip ahead to the final /deepgrill on the current PR state. Saves the remaining <MAX_ITERS - N> paid iteration(s). [M] Stop and merge as-is — accept the residual findings AND skip the final /deepgrill. The PR merges with whatever the loop landed so far.When
PREPUSH_DETECTED=false(orDETECTION_FAILED=true, in which case prepend the detection-failed sentence to the body):[If DETECTION_FAILED=true, prepend this line:] (Pre-push detection failed — origin/<base-ref> not available locally; the framing below assumes the skipped-pre-push case but may be wrong.) Iteration <N> in deep mode surfaced <T> significant findings (<C> critical, <S> suggestion, <K> nitpick) and no /refactorpass or /grill commits are visible in this branch's pre-push history. The residuals are likely the skipped pre-push chain catching up post-push, which is expensive on Gemini ($0.05–$0.20/iter) + Copilot. The final /deepgrill (Phase 5.5) runs the full pre-push chain that appears to have been skipped — almost certainly the right next step. (If you did run /refactorpass + /grill but they produced no commits — clean simplify pass and all-ignore/all-defer grill — treat this prompt as a false-positive and continue.) How to proceed? [C] Continue — fire iteration <N+1> of /reviewit deep as planned. The final /deepgrill runs after the loop ends, as usual. [L] Bail out of the paid-reviewer loop now and skip ahead to the final /deepgrill on the current PR state. Saves the remaining <MAX_ITERS - N> paid iteration(s). [M] Stop and merge as-is — accept the residual findings AND skip the final /deepgrill. The PR merges with whatever the loop landed so far.Route on the answer:
C (continue) → write the bypass sentinel so the checkpoint does not fire again on the next iteration in this PR's chain, then fall through to step 5. Fire the next iteration.
touch "/tmp/pr-<pr-number>-cost-shift-bypass"L (bail to final
/deepgrill) → exit the loop. Proceed to Phase 5.5 withBAIL_REASON=cost-shift; Phase 6'sFinal statebecomesstopped-at-iter-<N> (deep cost-shift bail-out). The summary must include the explicit recommended next steps: (1)/deepgrillran on the converged PR state — review its output, (2) push any local fix commits, (3) optionally re-invoke/reviewit <pr>(lean) for one more paid pass on the cleaned-up diff.M (merge as-is) → exit the loop AND skip Phase 5.5. Proceed directly to Phase 6 with
Final state: stopped-at-iter-<N> (merge-as-is). The summary should list residual findings so the user can review before merging.
Otherwise (below cap, new findings present, deep-mode early-exit and cost-shift checkpoint passed or not applicable) → start the next iteration. Re-fire Phase 1.
Phase 5.5: Final /deepgrill (deep mode only)
Deep mode only. Lean mode skips this step and goes straight to Phase 6.
Also skip when the user picked [M] merge as-is at the Phase 5 cost-shift checkpoint — that exit means "stop everything, merge with current state", which includes skipping the final fresh-agent pass. Set the Phase 6 deepgrill status to skipped (cost-shift merge-as-is) and proceed directly to Phase 6. All other Phase 5 exits (clean, no-fix early-exit, [C] continue-then-cap-hit, [L] cost-shift bail-out, plain cap-hit) run this phase normally.
Invoke via the Skill tool: Skill(skill="deepgrill").
/deepgrill runs /refactorpass + /grill deep — the full agent matrix (code-reviewer, silent-failure-hunter, type-design-analyzer, comment-analyzer, pr-test-analyzer, security-review). Fresh agents in a separate sub-skill context look at the PR's current state on top of whatever the Gemini+Copilot loop landed. This is the deep-mode replacement for the older Phase 1 /review fire — moving the fresh-agent pass outside the polling loop avoids the orchestration trap where /review's self-contained prompt caused control to exit the polling loop early.
⚠️ Do not stop after
/deepgrillreturns. When control returns from the Skill tool, treat the output as Phase 5.5's deliverable and immediately proceed to Phase 6. Do not summarize, do not hand back to the user mid-skill, do not assume the workflow is done./reviewitowns the final summary.
If /deepgrill surfaces findings the developer wants to act on, they're handled interactively per /grill's standard Phase 4 verification flow — fix locally, then push. Those follow-up commits are not automatically re-routed back through Gemini+Copilot; the developer can re-run /reviewit <pr> (lean) on the follow-up commits if they want bot eyes on them.
Capture for the Phase 6 summary:
- whether
/deepgrillran (one ofran|failed: <reason>|skipped (lean)|skipped (cost-shift merge-as-is)), - whether
/refactorpassproduced a commit, - the count of
/grill deepfindings the user chosefix/defer/ignoreon, if the sub-skill returned that summary.
If /deepgrill fails or is interrupted, record the failure in the Phase 6 summary and continue — the Gemini+Copilot loop result is still valid. Do not auto-retry.
Phase 6: Summary
✅ /reviewit complete on PR #<pr-number> (mode: <lean | deep>)
Iterations: <N> of <MAX_ITERS> max
Final state: <clean | early-exit (no fixes in last iter) | iteration-cap-reached | reviewer-timeout | stopped-at-iter-<N> (deep cost-shift bail-out) | stopped-at-iter-<N> (merge-as-is)>
Findings addressed:
- Fixed: <count>
- Deferred (issues): <count> — links: ...
- Dismissed: <count>
Replies posted: <count> of <total reply targets>
- Reply targets = inline rows (copilot / gemini-inline) + gemini-summary rows.
- Failed to post: <count> — manual follow-up required on: <comma-joined finding ids or summary-row indices>
Reviewer breakdown:
- Gemini Flash: <total findings, X unique>
- Copilot: <total findings, X unique>
Commits pushed: <list>
Final /deepgrill (deep mode only):
- Status: <ran | failed: reason | skipped (lean) | skipped (cost-shift merge-as-is)>
- /refactorpass commit: <sha or "none">
- /grill deep findings: <fix count> fixed, <defer count> deferred, <ignore count> ignored
Next: review the diff and merge when ready.
If iteration cap was hit in lean mode:
⚠️ Hit 2-iteration lean cap. Residual findings remain — review the latest
reviewer comments and either fix manually, merge as-is, or escalate with
/reviewit <pr> deep for the 4-iter chain + final /deepgrill.
If iteration cap was hit in deep mode:
⚠️ Hit 4-iteration deep cap. Residual findings remain — the final /deepgrill
still ran on the cap-state PR, so review its output alongside the open
Gemini/Copilot threads before merging.
If deep mode early-exited on no-fixes:
ℹ️ Deep loop early-exited after iteration <N> (no fix resolutions in last
iter — all findings were defer/dismiss). Final /deepgrill ran on the
converged PR state.
If the deep-mode cost-shift checkpoint bailed out to the final /deepgrill:
⚠️ Stopped at iteration <N> of 4 (deep cost-shift bail-out).
Iteration <N> surfaced <T> significant findings
(<C> critical, <S> suggestion, <K> nitpick) — the remaini
…(truncated)