<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary
Implements the tasks/proposed/ AI-160 draft spec (AI-476): a bounded, dispatch-tick-driven sweep that auto-recovers draft drone PRs whose latest posted reviewer-drone review still has unanswered findings — the gap where AI-250 correctly withholds Mercy/ready-flip from an unadjudicated review, but nothing previously re-checked the PR afterward.
## Why it's needed
AI-250 (review-loop-adjudication.ts) correctly refuses to start Mercy or flip a PR ready while a posted reviewer-drone review has unanswered findings, and AI-258 correctly refuses to spawn a second addresser on a refused Agent.send (agent_busy). Both are working fail-closed behaviours. The gap: nothing ever re-checks that PR afterward — the dispatch tick moves on, the PR is invisible to dashboards filtering to non-draft, and the Mercy watcher explicitly declines to start. The only previously-known recovery was a human running drones address --pr <n> --review-id <id> --unaddressed-only then gh pr ready <n> by hand.
## Changes
- src/address-turn.ts (new) — extracts the fresh-Agent.create + addressFindings send/parse/dispose core out of cli/address.ts's command action (runAddressTurn), so the standalone drones address CLI and the new sweep share one implementation instead of a second hand-rolled copy (per the spec's explicit anti-duplication instruction). cli/address.ts now calls this core; behaviour is unchanged.
- src/stranded-review-recovery.ts (new) — the AI-476 sweep engine. Lists open, draft, cursor/-branch drone PRs per registered repo; resolves the latest reviewer-drone review; computes unanswered findings purely from GitHub-observable state (fetchPostedReview + extractExpectedFindings + fetchReviewThreadMap + filterUnaddressedFindings — the same predicate --unaddressed-only already uses); fires the recovery through the existing (repoUrl, prNumber, reviewId) addresser lock (acquireAddressLock); flips the PR ready via the idempotent markPrReadyForReview once the round reaches adjudication (addressPhaseAdjudicated); bounds retries per (repo, pr, reviewId, headSha) and parks loudly after the cap.
- src/dispatcher.ts — wires the sweep into the scheduled tick alongside the existing AI-222 auto-resolve sweep; adds a DispatchStrandedReviewSection receipt shape so every attempt (recovered / still-unaddressed / skipped-lock-held / parked / etc.) is visible on the receipt without reading a host log.
- src/cli/dispatch.ts — new --no-stranded-review-recovery flag; drones dispatch passes strandedReviewRecovery: true by default.
- Docs — ARCHITECTURE.md entries for both new modules; a decision-log entry explaining the two design forks the spec left open (how the sweep invokes the addresser turn; GitHub-observable-only detection) and why the runDispatch engine's default is OFF (opposite of AI-222) while the CLI's default is ON.
### Contract surface affected
- DispatchReceipt gains an additive optional strandedReviewRecovery field (no schema-version bump, same convention as autoResolve).
- RunDispatchInput gains strandedReviewRecovery (+ a handful of strandedReview* test/tuning seams), defaulting to false at the pure-engine layer — see the decision log for why this deliberately diverges from AI-222's default-on precedent (this sweep independently gh pr list --drafts every registered repo every tick, with no passive openPRs-style data source to lean on for test hermeticity).
## Breaking changes
None. All new fields are additive/optional; cli/address.ts's external behaviour (flags, exit codes, output) is unchanged.
## Test plan
- pnpm typecheck → clean (tsc --noEmit, no errors).
- pnpm test → 128 test files / 4229 vitest tests passed, 620 Python tests passed (6 skipped), exit code 0.
- New coverage:
- src/address-turn.test.ts (4 tests) — agent lifecycle, callback wiring, disposal-on-throw.
- src/stranded-review-recovery.test.ts (25 tests) — the spec's four named acceptance scenarios (recovered / already-answered / concurrent-addresser / repeated-failure-park) plus provenance gate, cross-repository, dry-run, kill-switch, head-moved, no-review-found, still-unaddressed-after-firing, per-tick budget/candidate caps, and prior-receipt-ledger loading.
- src/dispatcher.test.ts (+4 tests) — default-off at the engine layer, receipt/tick-summary wiring, a non-fatal per-PR parked outcome, and a whole-sweep-throw escalating to exitCode 1.
- Updated existing tests to match the extraction: src/mcp-call-sites.test.ts (the createCloudAgent call site inventory entry moved to address-turn.ts), src/receipt-outbox.test.ts (address's "real work" call is now runAddressTurn(), help-parity snapshots (UPDATE_HELP_PARITY=1) for the new --no-stranded-review-recovery flag.
- Verified empirically that defaulting the sweep ON at the runDispatch engine layer (mirroring AI-222) would have made the previously-hermetic 182-test dispatcher.test.ts suite issue real, authenticated gh pr list --draft calls against every registry-referenced repo (including this very repo) on nearly every test — 101s vs 0.6s, with a real cloud-fire risk if a matching draft PR ever existed. Fixed by flipping the engine default to opt-in and enabling it explicitly at the drones dispatch CLI boundary instead (see the decision log).
## Verification artifact
Terminal output from the final full-suite run on this branch:
$ pnpm typecheck> tsc --noEmit
(clean, no output)
$ pnpm test
Test Files 128 passed (128)
Tests 4229 passed (4229)
...
Ran 620 tests in 60.910s
OK (skipped=6)
No manual/GUI testing applies — this is a backend dispatch-tick sweep with no UI surface; CURSOR_API_KEY is unavailable in this environment so firing a real drone (the only way to manually exercise the cloud-agent recovery path end-to-end) is out of scope here, consistent with this repo's own "Cursor Cloud specific instructions" (firing is billable + side-effecting on a target repo).
<!-- CURSOR_AGENT_PR_BODY_END -->
<div><a href="https://cursor.com/agents/bc-db5c21dd-a1f9-4679-b94d-90a85ec57476?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-web-light.png"><img alt="Open in Web" width="114" height="28" src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a> <a href="https://cursor.com/background-agent?bcId=bc-db5c21dd-a1f9-4679-b94d-90a85ec57476&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source media="(prefers-color-scheme: dark)" srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source media="(prefers-color-scheme: light)" srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img alt="Open in Cursor" width="131" height="28" src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a> </div>