Apply the ARDI loop (ARD + iterate) to every open PR/MR in the repo, driving
each to a clean review verdict in series.
Triage and local patch preparation may run in parallel first; every action that
mutates a PR stays serial.
-
List the open PRs/MRs and decide which are in scope.
gh pr list --state open --limit 100 \
--json number,title,headRefName,baseRefName,isDraft,author,reviewDecision
On GitLab, use glab api "projects/:id/merge_requests?state=opened&per_page=100"
and look for source_branch (≡ headRefName) and target_branch (≡ baseRefName)
in the JSON — glab mr list alone does not expose these fields.
State the scope rules when you report, so the user can
correct:
-
Include drafts (isDraft: true) unless another agent is actively driving one.
A draft is the corpus's own in-flight claim signal ---
pr-on-claim opens one from an empty start: scaffold commit before any code exists ---
so read each draft's state rather than sorting by the flag:
- Skip a driven draft.
Any of these marks one:
the head commit is still the
start: scaffold (the implementer is mid-flight),
a still-live claim comment stands (claims expire after 2 hours with no push or comment --- claim-pr),
another actor pushed recently,
or the draft is deliberately held as a merge-order gate (CLAUDE.md's "Surface merge-order constraints", surface 3).
- Include a parked draft.
Real implementation on the branch,
no live claim,
and no recent activity by another actor.
The review bot skips drafts in most repos,
so once its content passes the repo's checks,
mark it ready for review --- a clean verdict is unreachable while it stays draft.
Name each draft's disposition, and the signal that decided it, in the scope report,
so the user can veto before the loop touches it.
-
Only iterate PRs the user owns / is responsible for by default. In a
shared repo, don't start review loops (which push commits) on other
people's PRs unless told to. If unsure who owns what, ask first.
-
A green PR with no review check run is parked, not finished.
On a repo whose review workflow is workflow_dispatch-only, nothing
fires on push, so a PR nobody ever reviewed presents exactly like one
that passed: every check green, nothing pending.
The tell is an absence, so no check state carries it, and the
sweep's own triage is where that absence has to be caught.
Read the review workflow's on: block once per repo, then treat "zero
review check runs on the head" as its own triage outcome.
pull_request_read get_check_runs answers it per PR.
pr-on-claim's dispatch-only
section covers the single-PR case; the increment here is that a sweep
classifying many PRs at once will otherwise sort these into the
nothing-to-do pile.
- Do: name such a PR's verdict as missing rather than clean, and
dispatch a review for it --- pricing that round first, since a
dispatch is a real spend and several of them are several spends.
- Don't: read green checks with nothing pending as evidence a
review passed; on a dispatch-only repo that is the steady state.
(Morrison-Lab/ai-config, 2026-08-16: an ardia sweep found #1500 and
#1509 parked 4h and 2h19m, both all-green with no verdict, because that
repo's claude-review.yml carries no pull_request trigger.
Both had never been reviewed; the dispatched rounds then returned
"Needs more work" on each, with a blocking correctness bug in #1509.)
-
If the list is empty, say so and stop — nothing to do.
Detect and sort stacked PRs. Check each PR's baseRefName. If any PR's
baseRefName matches another open PR's headRefName, they are stacked.
Sort the list so base PRs come before the PRs stacked on them —
process bases first so derived PRs always sit on a clean, reviewed base. Note
any stack in the scope report:
Stacked PRs detected: #A → #B (process #A first)
If a circular stack is found (impossible in practice but check anyway),
surface it to the user and skip those PRs.
Tie-break with infrastructure-first. Among PRs with no stacking
relationship, when otherwise equally pressing, process internal
infrastructure PRs (shared tooling, CI workflows, reusable actions,
templates) slightly ahead of feature PRs — see
pr-prioritization. This
never overrides the stacking order above.
Report the in-scope list (with bare PR URLs) before you start, so the
user can veto any before the loop pushes commits.
-
Optionally fan out read-only triage and local preparation.
Independent PRs may be inspected concurrently, one worker per PR, each in its
own worktree.
A worker may read the latest review and CI logs, trace a finding to its
cause, run the repo's local checks, and prepare a focused patch.
It must not claim a PR, post a comment, commit, push, request review, or
otherwise mutate shared forge state --- every one of those belongs to the
serial loop below.
Check for supersession before preparing a patch for an idle, non-clean PR.
Grep origin/main for the PR's distinctive added phrases; if they are all
already there, the PR is Superseded (see step 3's terminal states) and its
patch prep is wasted work --- flag it for closure instead.
Run that grep over main's whole Markdown corpus rather than over the PR's
own file paths, for the reason step 3's terminal state gives.
The patch has to leave the worktree as an artifact, not sit in it as a
dirty tree.
A worker's worktree is not durable: isolation: 'worktree' has reclaimed one
mid-run before, which is the incident
incidents-dont-repeal-decisions
is written about.
A dirty tree is also the one form the orchestrator cannot inspect, diff, or
apply without re-entering that worktree, so it fails whether or not the
worktree survives.
Have each worker end by emitting a patch to an orchestrator-owned path, and
return that path plus the SHAs below:
git diff > "$ARTIFACT_DIR/pr-<N>.patch"
That keeps the no-mutation rule exactly as stated --- a patch file is not a
commit --- while making the preparation survive the worker.
Skip the fan-out for stacked PRs, for PRs whose likely file footprints
overlap, and whenever independence is uncertain.
Consolidate the prepared findings and patches before step 3 begins.
A prepared patch is a snapshot, not a decision.
Record the base and head SHAs the patch was prepared against, and re-check
all four signals when the serial loop reaches that PR: those two refs,
the latest review, and CI state.
Re-derive the patch if any has moved.
The two refs are the half that is easy to omit and the half that actually
goes stale.
A main advance or a new PR-head commit need not produce a new review or a
new CI run, so a review-and-CI check alone returns "unchanged" for exactly
the case that invalidates a patch --- which is the shape
warns about, where the
pass path and the stale path print the same thing.
-
For each PR/MR, in series, run ARDI (the full single-PR loop --- see the
ardi skill): claim → sync main → read latest review → ARD every finding →
push → post summary → re-request review → repeat until fully clean. Don't
reimplement that loop here; follow it per PR.
For stacked PRs: the ideal flow is to merge the base PR before starting
ARDI on the derived PR. If the base isn't mergeable yet (pending CI, open
review findings), complete ARDI on the base first to drive it to clean and
merge it, then start the derived PR. Never run ARDI on a derived PR while its
base is still open and unclean — you'd be reviewing against a moving target.
A PR reaching clean-but-unmerged is that PR's terminal state, not a
reason to pause the sweep: merging is human-gated (you don't self-merge), but
that gates only the merge — move straight to the next PR rather than waiting
for a human to merge first. See
stack-dont-pause, and use
stack-prs for the branch/PR mechanics when the
next item needs to stack on a clean-but-unmerged PR.
Cascading the stack is part of ARDIA, not separate side work. Every time
a base advances — it merges into main, or its own head moves (a review
fix, a main-sync commit) — every PR stacked above it goes BEHIND/DIRTY
and must be re-synced: merge the base's new head into the child, resolve
conflicts (keeping both the base's changes and the child's own — e.g. a
rename in the base and a new parameter in the child both survive), re-verify
(run the repo's own checks — build/lint/tests, plus any doc regeneration or
character check), bump the child's version above the base where the repo
requires it, and push. This ripples:
a single review-fix commit to a mid-stack PR puts every descendant behind,
so one ARDIA pass may sync the same branch more than once as fixes land
below it. When the user says "cascade" or "keep driving all these to clean,"
that includes this conflict-resolution/re-sync loop up the whole stack —
don't treat "resolve merge conflicts" or "sync the stack" as out-of-scope.
Process bottom-up: sync the lowest BEHIND/DIRTY PR first, then its
children, since each sync advances a head the next child needs.
Drive each to a terminal state:
-
Clean — zero flagged items under any heading; post the unclaim
comment, record the round count.
-
Escalated --- every remaining is deadlocked and waiting on a human ruling.
Record which findings, move to the next PR, and return when the human rules.
Escalating findings is not this: keep driving the PR on everything else.
A round count is never a terminal state at all --- see 's "Stopping conditions".
-
Report a summary table at the end, with clickable links:
| MR/PR | Rounds | Final status |
|---|
| #25 | 3 | ✅ Clean |
| #26 | 4 | ⏸️ Escalated --- awaiting human on: … |
| #27 | 1 | ⛔ Blocked --- needs human decision on … |
| #28 | 0 | 🔁 Superseded --- content on main via #N; recommend closing |
For any PR not driven to clean, list its remaining open items so triage
is one glance, not a re-investigation. Don't merge anything — opening merges
is the user's call.
Separately from the Workflow-based survey fan-out above, a single PR's ARDI
round (see ardi) can delegate sidecar work directly via the Agent tool ---
verifying a disputed factual claim, investigating an unclear CI failure, or
researching how a prior PR handled the same pattern --- while the main thread
keeps driving that round forward. This is a lighter-weight call than the Workflow
tool covers above and needs no opt-in gate. Give the subagent a stronger
model (e.g. model: 'opus' on the Agent tool call) for judgment-heavy
sidecar work, and symmetrically a cheaper/faster tier (model: 'fable' or
'haiku') for a mechanical one --- see
select-model's decision tree for both
directions. For a heavy fan-out survey/verify pass, prefer a
separately-billed provider (e.g. the codex CLI) first when available ---
see delegate-to-codex.
If asked to keep the queue clean on an interval, drive this skill from a
recurring runner (e.g. the loop skill) rather than busy-waiting inside one
invocation. Each tick re-enumerates open PRs (new ones appear, merged ones drop
off) and runs the series loop over the current set.