From sdd
Reviews and merges PRs from /sdd:work using reviewer-responder agent pairs that check diffs against spec acceptance criteria. Supports loop mode for autonomous operation.
npx claudepluginhub joestump/claude-plugin-sdd --plugin sddThis skill uses the workspace's default tool permissions.
<!-- Governing: ADR-0017, SPEC-0015 REQ "Conflict-Marker CI Gate" -->
Searches, retrieves, and installs Agent Skills from prompts.chat registry using MCP tools like search_skills and get_skill. Activates for finding skills, browsing catalogs, or extending Claude.
Guides code writing, review, and refactoring with Karpathy-inspired rules to avoid overcomplication, ensure simplicity, surgical changes, and verifiable success criteria.
Provides UI/UX resources: 50+ styles, color palettes, font pairings, guidelines, charts for web/mobile across React, Next.js, Vue, Svelte, Tailwind, React Native, Flutter. Aids planning, building, reviewing interfaces.
Share bugs, ideas, or general feedback.
You are reviewing PRs produced by /sdd:work using reviewer-responder agent pairs. Each pair processes PRs through exactly one review-response round: the reviewer checks the diff against spec acceptance criteria, the responder addresses feedback, and the reviewer re-evaluates. Approved PRs are merged; unresolved PRs are left with comments for human follow-up. See ADR-0010 and SPEC-0009.
Loop Mode (V1, opt-in). When invoked under
/loopwith the--loopflag (and optionally--pr <N>for single-PR watch mode), this skill enters autonomous-mode and uses the lockfile + budget primitives documented inreferences/loop-primitives.md(acquired on entry, released on exit) and the telemetry + resume contract documented inreferences/loop-telemetry.md(every iteration appends ahistory.jsonlline;--resumereconcilestracked_prs[]via SHA equality withhead_sha_at_iteration_end). The full CLI surface, the active stop conditions (#2 / #3 / #5 / #6 / #8 / #9 / #10 / #11 / #12, with #4 inactive in single-PR mode), and the review-side gates (ambiguous-criteria, budget-escalation, post-feedback-merge, force-unlock, repeated-failure — note: ADR-0010's bounded one-round invariant is preserved per iteration) are wired in story #145 (SPEC-0020). Without--loop, behavior is unchanged from the rest of this document and no.sdd/loop/artifacts are created.
Resolve artifact paths: Follow the Artifact Path Resolution pattern from references/shared-patterns.md to determine the spec directory. If $ARGUMENTS contains --module <name>, resolve paths relative to that module. The resolved spec directory is {spec-dir}.
Parse arguments: Parse $ARGUMENTS.
Target resolution:
SPEC-0003), find all open PRs whose branch names match the spec's issue branch patterns or whose bodies reference the spec number.101 102 105), fetch exactly those PRs.$ARGUMENTS is empty (ignoring flags), list available specs by globbing {spec-dir}/*/spec.md, read the title from each, and use AskUserQuestion to ask which spec's PRs to review.Flag parsing:
--pairs N: Number of reviewer-responder pairs. Default: 2 (or CLAUDE.md Review > Max Pairs).--no-merge: Approve PRs but do not merge them. Default: off.--dry-run: Preview which PRs would be reviewed without taking any action. Default: off.--module <name>: Resolve artifact paths relative to the named module. Default: none.Detect tracker: Follow the "Tracker Detection" flow in the plugin's references/shared-patterns.md, but only GitHub, GitLab, and Gitea are supported (PR/MR capability required). If the saved tracker is Beads, Jira, or Linear, inform the user that /sdd:review requires a tracker with PR support.
Discover target PRs: Search the tracker for open PRs matching the target.
gh pr list --search "SPEC-XXXX" --json number,title,headRefName,body,url --limit 50 or gh pr view {number} --json number,title,headRefName,body,url for explicit PR numbers.ToolSearch) to list pull requests.glab mr list --search "SPEC-XXXX".If no open PRs are found, inform the user and suggest running /sdd:work to create PRs from planned issues.
3a. Conflict-marker CI gate (Governing: ADR-0017, SPEC-0015 REQ "Conflict-Marker CI Gate"):
Before any review logic runs, scan ALL files in every target PR diff for unresolved merge conflict markers. This is a zero-tolerance gate — any file type, any conflict marker means instant rejection.
For each target PR:
Fetch the full diff:
gh pr diff {number}ToolSearch) to fetch the PR diff.glab mr diff.Scan every line of the diff for conflict markers: <<<<<<<, =======, >>>>>>>.
If ANY conflict markers are found:
REQUEST_CHANGES review immediately:
gh api repos/{owner}/{repo}/pulls/{number}/reviews -f event=REQUEST_CHANGES -f body="..." with the rejection message below.ToolSearch) to submit a review.glab CLI.## Conflict Markers Detected
This PR contains unresolved merge conflict markers and cannot be reviewed.
| File | Line(s) |
|------|---------|
| {file-path} | {line-numbers} |
Please resolve all conflicts and push again.
If no conflict markers are found, proceed to step 4 for this PR.
3c. Tier 4 issues sync (v5.0.0+):
Before computing the topological merge order (Step 11a) and before reviewers query for missing ADR/issue references (Step 4a), sync the {repo}-issues qmd collection from the tracker. Subject to the 5-min dedup window per references/tracker-sync.md § "Cursor Management".
Read .sdd/issues/_meta.json. If last_sync is within the last 5 minutes, skip the sync silently.
Otherwise, invoke per-tracker fetch+normalize per references/tracker-sync.md. Print: "Syncing N issues from {tracker}…".
On sync failure, surface a one-line warning per tracker-sync.md § "Failure Modes and Degradation" and proceed with live tracker queries (the pre-v5 path) for this run. Do NOT block; PR review is the user's primary intent.
Load architecture context (Governing: SPEC-0009 REQ "Architecture Context Loading"):
spec.md, design.md, and any referenced ADRs from the resolved spec directory. Validate spec pairing per references/shared-patterns.md § "Spec Pairing Validation".4a. qmd-aware missing-reference retrieval (v5.0.0+):
Reviewers MUST search {repo}-adrs and {repo}-issues to identify ADRs the PR should reference and prior issues the PR touches. The point: a PR that modifies authentication code should reference the auth ADR (ADR-0011 in the SPEC-0019 example). Without a qmd assist, this requires the reviewer to remember the entire ADR/issue corpus. With qmd, the reviewer surfaces missing references as findings.
For each PR being reviewed:
Construct a hybrid query per references/qmd-helpers.md § "Hybrid Retrieval" derived from the PR's diff:
lex: keywords from the PR title + file path basenames + named symbols touchedvec: a one-sentence summary of what the PR changesintent: "/sdd:review — find ADRs and prior issues this PR should reference"collections: ["{repo}-adrs", "{repo}-issues"]limit: 8, minScore: 0.4For each match above the threshold:
Closes #N, Fixes #N, Part of #N, or Related: #N). If NOT, surface as informational note rather than a blocking finding ("PR appears related to open issue #{N} ({title}) — consider linking").Inject the missing-reference findings into the reviewer's review (alongside the spec-acceptance-criteria findings already in Step 9). Each finding cites the qmd retrieval evidence (matched ADR/issue ID + score) so the reviewer's responder can evaluate whether the connection is real.
On qmd unreachable / timeout per qmd-helpers.md § "Error Handling", surface the error and stop. Per ADR-0024, no fallback in v5.
Read review config from CLAUDE.md: Follow the "Config Resolution" pattern in the plugin's references/shared-patterns.md. Read the #### Review subsection from the ### SDD Configuration section in CLAUDE.md. Defaults: Max Pairs=2, Merge Strategy="squash", Auto Cleanup=false. CLI flags override: --pairs N overrides Max Pairs, --no-merge prevents merging.
Dry-run gate: If --dry-run is set, output a preview table and stop:
## Dry Run: /sdd:review SPEC-0003
Would review {N} PRs using {pair-count} reviewer-responder pairs.
| # | PR | Title | Branch | CI | Pair |
|---|-----|-------|--------|-----|------|
| 1 | #101 | JWT Token Generation | feature/42-jwt-token-generation | Pass | Pair 1 |
| 2 | #102 | Token Validation | feature/43-token-validation | Pass | Pair 2 |
| 3 | #103 | Token Refresh | feature/44-token-refresh | Fail | Skipped |
No reviews submitted. No merges performed.
Create team (Governing: SPEC-0009 REQ "Team Formation"):
Adaptive pair count: If the number of PRs is less than the configured pair count, reduce to min(PR count, configured pairs) to avoid idle agents. If only 1 PR, use 1 pair.
Use TeamCreate to create a coordination team. Spawn agents:
general-purpose) — one per pairgeneral-purpose) — one per pairIf TeamCreate fails, fall back to single-agent sequential mode: for each PR, review the diff against acceptance criteria, then address any issues directly (acting as both reviewer and responder). This mode skips the response round — if issues are found, leave review comments for human follow-up instead of auto-fixing.
Distribute PRs (Governing: SPEC-0009 REQ "PR Distribution"):
Assign PRs to pairs using round-robin:
Create tasks via TaskCreate for each PR, assign to the appropriate pair.
Phase 1 — Review (Governing: SPEC-0009 REQ "Review Protocol"):
Each reviewer receives their assigned PRs and processes them sequentially:
Reviewer steps:
gh pr checks {number} or gh pr view {number} --json statusCheckRollup — ALL checks MUST pass.ToolSearch) to query commit status, or GET /repos/{owner}/{repo}/commits/{sha}/status.glab ci status.## Requirements or ## Acceptance Criteria section)gh api or MCP tools — submit review with event APPROVE, COMMENT, or REQUEST_CHANGES, including line-level comments where applicable.ToolSearch) to submit a pull request review.glab CLI to add review comments.APPROVE and skip the response round for that PR.SendMessage.Review quality rules:
Phase 2 — Response (Governing: SPEC-0009 REQ "Response Protocol"):
For PRs that received REQUEST_CHANGES, the responder addresses feedback:
Responder steps:
/sdd:work still exists at .claude/worktrees/{branch-name}. If so, reuse it (run git pull first). If not, create a new one: git worktree add .claude/worktrees/{branch-name} {branch-name}.SendMessage.Phase 3 — Re-evaluation and Merge (Governing: SPEC-0009 REQ "Re-evaluation and Merge"):
After the responder pushes fixes, the reviewer re-evaluates:
APPROVE.--no-merge is NOT set:
gh pr merge {number} --squash (or --merge / --rebase per config).ToolSearch) to merge.glab mr merge.{repo}-code (the merge changed code) AND {repo}-issues (the linked story issue closed). Use the canonical update pattern from references/qmd-helpers.md § "Update Patterns". Best-effort and silent on success. On failure of either, append a one-line warning to the run log ("Index refresh failed for {collection} after merging PR #{N} — run /sdd:index update manually") but the merge itself is reported as successful.Part of #XX or the configured Ref Keyword from CLAUDE.md PR Conventions). If no epic reference is found, skip this step.
b. Fetch the epic issue and extract its child story references. Read the PR Conventions > Ref Keyword from CLAUDE.md config (default: "Part of") and use it to find child issues:
{Ref Keyword} #{epic-number}), or list issues in the same project/milestone.ToolSearch) to list issues referencing the epic with the configured ref keyword, or query the epic's milestone for open issues.glab CLI to find open issues referencing the epic with the configured ref keyword.
c. If all child story issues are now closed (no open stories remain), close the epic issue:gh issue close {epic-number}ToolSearch) to close the issue.glab issue close {epic-number}.SendMessage.--no-merge IS set, report as "approved, pending manual merge".Cleanup and report (Governing: SPEC-0009 REQ "Reporting"):
12.1: Shut down team. Send shutdown_request to all agents via SendMessage.
12.2: Offer worktree cleanup. If CLAUDE.md Review > Auto Cleanup is true, remove worktrees for successfully-processed PRs automatically. Otherwise, preserve them.
12.3: Final report.
## Review Complete: SPEC-0003
Reviewed {N} PRs using {pair-count} reviewer-responder pairs.
### Results
| PR | Title | CI | Review | Merge | Status |
|----|-------|----|--------|-------|--------|
| #101 | JWT Token Generation | Pass | Approved | Merged (squash) | Complete |
| #102 | Token Validation | Pass | Approved | Merged (squash) | Complete |
| #103 | Token Refresh | Pass | Changes requested | — | Needs human follow-up |
### Needs Human Follow-up
- **#103 Token Refresh**: Reviewer found unresolved issue after response round. Comment left on PR: "Token expiry edge case not covered by tests."
### Skipped (CI failing)
- (none in this example)
### Epics
- Closed: #50 Implement Auth Module (all 2 stories merged)
- Still open: (none in this example)
### Worktrees
- Reused: 2
- Created: 1
- Cleaned up: 0
### Next Steps
- Review unresolved PR #103 and address remaining feedback
- Run `/sdd:check` to verify implementation alignment
- Run `/sdd:audit` for comprehensive drift analysis
| Condition | Behavior |
|---|---|
| Single PR review fails (API error) | Log failure, skip that PR, continue with remaining PRs |
| Merge conflict on merge | Report conflict, leave PR unmerged for human resolution |
TeamCreate fails | Fall back to single-agent sequential mode |
| No open PRs found | Suggest /sdd:work to create PRs |
| No tracker detected | Error: tracker with PR/MR support is required |
| Responder cannot resolve feedback | Reply explaining why, report to lead, leave for human |
| Push fails during response | Responder reports error, PR skipped for that round |
| Worktree in unexpected state | git pull and verify correct branch; if unrecoverable, create fresh worktree |
| CI checks failing or pending | Skip review for that PR; report as blocked until checks pass |
| CI checks pass after re-evaluation but code issues remain | Report as "CI green, code changes requested" — do not merge |
| Epic closure fails (API error) | Log warning, report in final summary — epic remains open for manual closure |
| Cannot determine parent epic from PR body | Skip epic closure check for that PR — no error |
| Conflict markers detected in PR diff | Reject with REQUEST_CHANGES listing file paths and line numbers; skip all further review for that PR |
ToolSearch to discover tracker MCP tools at runtime — never assume specific tools are availablereferences/shared-patterns.md to read configuration from CLAUDE.md/sdd:work when available (Governing: SPEC-0009 REQ "Response Protocol")--no-merge is setReview > Merge Strategy--dry-run MUST NOT submit reviews, push commits, or merge PRsTeamCreate fails, MUST fall back to single-agent sequential mode — never error out<<<<<<<, =======, >>>>>>>) before any review logic runs (Governing: SPEC-0015 REQ "Conflict-Marker CI Gate"){repo}-adrs and {repo}-issues for ADRs and prior issues the PR should reference; surface missing ADR refs as findings (with citation), surface missing issue refs as informational notes (Governing: ADR-0024, SPEC-0019 REQ "qmd-Smart Sprint Skills"){repo}-code AND {repo}-issues per Step 11.5a — best-effort, silent on success, one-line warnings on failure (Governing: ADR-0026, SPEC-0019 REQ "Tier 1 Mutation-Aware Updates")When /sdd:review is invoked under /loop with the --loop flag (e.g. /loop /sdd:review --loop or /loop /sdd:review --loop --pr 142), the skill enters autonomous-mode and follows the contract below on every tick. Without --loop, the skill behaves exactly as the rest of this document specifies and creates no .sdd/loop/ artifacts (per SPEC-0020 REQ "Loop Mode Opt-In").
The runtime /loop skill is unchanged. --loop is a skill-side opt-in. /loop schedules ticks; everything inside a tick is the wrapped skill's concern. ADR-0010's bounded one-round invariant is preserved verbatim: each iteration executes exactly one review-response round per PR; loop iteration MUST NOT amount to multiple review-response rounds on the same PR within one iteration (per SPEC-0020 REQ "ADR-0010 Bounded-Iteration Preservation").
When --loop is set, /sdd:review accepts the following additional flags. All shared loop flags carry the documented conservative defaults; --pr <N> is review-specific (per SPEC-0020 REQ "CLI Surface for Loop Controls"). Budgets are inclusive across the entire loop run, not per-iteration.
| Flag | Default | Purpose |
|---|---|---|
--loop | off | Opt into autonomous-mode |
--max-iterations N | 5 | Iteration ceiling across the run |
--max-prs N | 20 | Distinct-PR ceiling across the run (inactive in single-PR mode — see "Single-PR Review Loop Semantics") |
--max-minutes N | 60 | Wall-clock ceiling across the run |
--max-dollars N | 25 | Dollar-cost ceiling; 0 disables condition #12 (estimate still tracked) |
--lock={skip|wait|force} | skip | Concurrency mode on lockfile contention |
--resume | off | Recover state from the most recent history.jsonl line |
--budget-file PATH | .sdd/loop/review.budget.json | Override the budget-file location |
--pr N | none | Single-PR watch mode — see "Single-PR Review Loop Semantics" below |
The first write of budget.json records the active ceilings (per references/loop-primitives.md § First-write rule) so a later --resume cannot silently widen them.
Each tick follows this canonical flow:
.sdd/loop/review.lock per references/loop-primitives.md § Acquisition flow (skip / wait / force per --lock)..sdd/loop/review.budget.json; on first write, initialize ceilings, started_at, and rate_table_source.--pr (single-PR mode).head_sha_at_iteration_end.iterations_used, comments_pushed (top-level + replies both count, per SPEC-0020 REQ "Budget Schema — comments_pushed Definition"), merges_attempted, agents_dispatched, tokens_in/tokens_out, recompute dollars_estimate, evaluate exit-time stop conditions 3 / 5 / 12 (and #4 in multi-PR mode)..sdd/loop/review.history.jsonl (per references/loop-telemetry.md) and emit the stdout status block./loop schedule the next tick.Active stop conditions for /sdd:review --loop. Note: condition #1 (backlog empty) and #7 (dependency cycle) are work-side-only.
| # | Condition | Behavior |
|---|---|---|
| 2 | Target PR reaches a terminal state (merged, closed, or labeled with the project's configured do-not-merge label) — single-PR mode only | Halt with "PR #{N} reached terminal state: {state}"; release lock; do NOT signal another tick (SPEC-0020 REQ "Terminal-PR Stop") |
| 3 | iterations_used >= max_iterations (entry-time check) | stop_conditions_fired: ["iteration_budget"] (SPEC-0020 REQ "Iteration Budget Stop") |
| 4 | len(prs_touched) >= max_prs — inactive in single-PR mode (see "Single-PR Review Loop Semantics") | stop_conditions_fired: ["prs_touched_budget"] for multi-PR mode (SPEC-0020 REQ "PR-Touch Budget Stop") |
| 5 | minutes_elapsed >= max_minutes (clock anchored at started_at, persists across --resume) | stop_conditions_fired: ["wall_clock_budget"] (SPEC-0020 REQ "Wall-Clock Budget Stop") |
| 6 | Same PR failed twice consecutively with the same root cause | Fire the Repeated-Failure gate (does NOT silently halt) (SPEC-0020 REQ "Repeated-Failure Stop") |
| 8 | User interrupt (Ctrl-C / session close / explicit /loop stop) | Drain in-flight responder pushes, release lock, emit final report, no half-states (SPEC-0020 REQ "User Interrupt Stop") |
| 9 | Lockfile holds a live PID under --lock=skip | Emit one-line skip note; do NOT increment counters (SPEC-0020 REQ "Lockfile Contention Skip"). --lock=wait blocks bounded by max_minutes; --lock=force fires the Force-Unlock gate. |
| 10 | Any prior gates[] entry recorded answer == "stop" | "Loop already stopped at gate {name} in iteration {N}"; release lock; do NOT increment counters (SPEC-0020 REQ "Prior-Gate-Stop Honor") |
| 11 | qmd unreachable for 2 consecutive iterations | Halt with the ADR-0024 remediation message; the wrapped skill signals via stderr token qmd-unreachable OR exit code EX_QMD_UNREACHABLE=78; any successful iteration resets qmd_failures_consecutive to 0 (SPEC-0020 REQ "qmd-Unreachable Stop") |
| 12 | dollars_estimate >= max_dollars (and max_dollars > 0) | "Cost budget reached: $X / $Y"; --max-dollars 0 disables but dollars_estimate is still tracked (SPEC-0020 REQ "Cost Budget Stop") |
Identical contract as /sdd:work --loop. The wrapped skill signals qmd-unreachable using either of these signals (per SPEC-0020 REQ "qmd-Unreachable Stop"):
qmd-unreachableEX_QMD_UNREACHABLE = 78The loop reads exit status first; on non-zero, scans stderr for the sentinel as a fallback. On detection, increment qmd_failures_consecutive. On any successful iteration, reset to 0. On increment to 2, condition #11 trips.
All gates are re-evaluated on every tick (per SPEC-0020 REQ "Gates Are Not Debounced Across Iterations") — the skill MUST NOT cache or reuse a prior iteration's answer. Each invocation is captured verbatim in the iteration's gates[] array per references/loop-telemetry.md.
The review-side gate set is:
| Gate | Trigger | Prompt template | Options |
|---|---|---|---|
| Ambiguous Criteria | The PR's underlying issue lacks ### Acceptance Criteria OR contains TBD/TODO markers | "Issue #{N} has ambiguous criteria. Skip, escalate, or proceed with my best interpretation?" | skip, escalate, proceed, stop |
| Budget Escalation (80%) | One or more active budgets cross 80% on this tick. In single-PR mode, the gate evaluates only max_iterations, max_minutes, and max_dollars and MUST NOT mention prs_touched. | "Approaching {budget(s)} ({used}/{total} each). Continue, raise ceiling(s), or stop?" | continue, raise, stop |
| Post-Feedback Merge | The responder addressed human review comments (login is not an agent identity) since the previous iteration AND the loop is about to merge | "Responder addressed human feedback on PR #{N}. Merge now or hold for human re-review?" | merge, hold, stop |
| Force-Unlock | --lock=force AND lockfile is present | "Force-unlock previous iteration's lock? This may corrupt in-flight work." | yes, no, stop |
| Repeated Failure | Same PR failed in two consecutive iterations with the same root cause | "Issue/PR #{N} failed twice with: {root-cause}. Skip, retry once more, or stop the loop?" | skip, retry, stop |
| Resume-Divergence (resume only) | A tracked_prs[] entry's head_sha_at_iteration_end does not match the live remote HEAD | "PR #{N} has diverged since the prior iteration crashed — re-attach, skip, or stop the loop?" | re-attach, skip, stop |
Note: Backlog-Drift is a /sdd:work --loop gate; it does NOT fire on the review side.
When two or more budgets cross 80% in the same tick, the gate fires once with a combined message listing every tripped budget. When any budget reaches 100% in the same tick that another crosses 80%, the 100%-stop wins (conditions 3 / 5 / 12 — and 4 in multi-PR mode) and the gate is suppressed.
In single-PR mode, the gate's combined message MUST NOT mention prs_touched regardless of --max-prs (per SPEC-0020 REQ "Single-PR Review Loop Semantics").
ADR-0010 caps each /sdd:review invocation at exactly one review-response round per PR. Across loop iterations, a sequence of approve → next-iteration could approximate "infinite review rounds" if the responder kept pushing fixes. The Post-Feedback-Merge gate breaks that approximation by inserting a human-mediated checkpoint when human review comments (not sibling-agent reviewer comments) have been addressed. This preserves ADR-0010's bounded-iteration invariant under loop wrapping.
The gate's "human vs. agent" detection compares the comment author's tracker login against a list of known agent identities (configurable in the future; for V1, treat the loop's own session identity and the configured ### SDD Configuration > Agent Logins list as agent identities — everyone else is human).
/sdd:review --loop MUST NOT submit a new review on a PR whose previous-iteration responder has not yet pushed fixes (per SPEC-0020 REQ "Concurrency Invariants for /sdd:review"). The check is SHA equality alone:
recorded_sha = last_history_line.tracked_prs[#PR].head_sha_at_iteration_end
current_sha = git ls-remote origin {pr.branch} | head -1
if recorded_sha == current_sha:
defer review of this PR with the spec-quoted note:
"PR #{N}: no new commits since iteration {M} (head_sha_at_iteration_end matches remote HEAD)"
Out-of-band signals (e.g., responder commentary about fixes being incoming) MUST NOT be used. CI mid-flight is NOT a lock-contention condition; it remains under the existing per-PR CI gate (skip until green) — see Step 9.3 above.
When --pr <N> is provided, the loop watches a single PR across iterations (per SPEC-0020 REQ "Single-PR Review Loop Semantics"):
prs_touched MUST be informational and MUST remain ["#N"] for the life of the runcomments_pushed and merges_attempted MUST remain visible counters — uncapped by default, reported on every tick in the status blockmax_iterations, max_minutes, and max_dollars and MUST NOT mention prs_touchedSingle-PR mode is the canonical "watch this PR until it merges" shape. Multi-PR mode (no --pr) is the "review whatever is in the discovered PR set" shape and uses the full active-condition set.
The single most-important invariant the review-side loop preserves: each /sdd:review invocation MUST still execute exactly one review-response round per PR within the iteration (per SPEC-0020 REQ "ADR-0010 Bounded-Iteration Preservation"). The reviewer evaluates, the responder addresses (if REQUEST_CHANGES), the reviewer re-evaluates. After that round closes, the iteration moves on or stops. A second architectural round on the same PR within the same iteration is forbidden by ADR-0010 and forbidden here.
Across iterations, the Post-Feedback-Merge gate (above) is the only path that could otherwise approximate "infinite review rounds" — and it is human-mediated rather than agentic. The user explicitly approves each merge that follows a human-feedback-addressed iteration.
When the loop halts for any reason, the wrapped skill MUST emit a final report before lockfile release (per SPEC-0020 REQ "Final Report on Stop"). The report covers:
.sdd/loop/review.budget.json and .sdd/loop/review.history.jsonl for further inspection--resume recovers state from the most recent history.jsonl line per references/loop-telemetry.md § Resume Contract. Counters are restored; gate evaluations are recomputed; the lockfile is treated as stale per the PID-liveness rule; tracked_prs[] is reconciled by SHA equality (silent re-attach on match, resume-divergence gate on mismatch, skip on terminal state) — no external probing substituted.
Every iteration appends a line to .sdd/loop/review.history.jsonl and emits the stdout status block. Skipped ticks (lockfile contention) MUST also append a line with outcome: "skipped_lock" and MUST NOT increment iterations_used. Schema details in references/loop-telemetry.md.
/loop skill — re-invocation cadence is /loop's concern; the wrapped skill enforces only intra-iteration semanticsreferences/loop-primitives.md § Acquisition flow--resume cannot silently widen themprs_touched (a PR re-reviewed across iterations counts once)prs_touched == ["#N"] for the life of the run; condition #4 MUST NOT firehistory.jsonl line on every iteration including skipped ticksiterations_used for a skipped tickgates[] entry recorded answer == "stop" (condition #10)head_sha_at_iteration_end (head-SHA concurrency invariant; SHA equality is the sole verification rule)qmd-unreachable OR exit code EX_QMD_UNREACHABLE=78 (the wrapped skill emits; the loop layer detects)comments_pushed (per SPEC-0020 REQ "Budget Schema — comments_pushed Definition")merge invokes the merge API