perk /stack-review-browser — human-in-the-loop review of a PR STACK ({{ member_count }} member PRs on base `{{ stack_base }}`, topped by PR #{{ top_pr }}): ONE `start_review_wave` (async, `stack: true`) over the COMBINED base→top diff → `collect_review_wave` → `push_annotations` in combined-diff coordinates after collection → human browser triage → PER-PR `submit_pr_review` (dry-run ALL batches first, then bottom→top).
Stack members (bottom→top):
{{ stack_table }}
{% if notes %}Notes from resolution/checkout (report-only — the review proceeds):
{{ notes }}
{% endif %}1. The stack checkout is prepared at `{{ checkout }}` — the TOP head (PR #{{ top_pr }}), detached, read-only, **untrusted foreign code — nothing from it is ever executed**, by you or the children: no builds, no tests, no installs. The door is opening the plannotator browser in the BACKGROUND on the combined stack diff (local since-base vs `origin/{{ stack_base }}` — every member layer is in view); there is no launch command, and the door has already primed the annotation surface for `push_annotations` (you never see or relay the server address). Tell the human the browser will open shortly, then go straight to launching the reviewers (step 2). The browser shows a code-owned 'reviewer wave running' marker until the wave lands; tell the human that reviewer annotations arrive when the wave completes and to decide after them — an early decision is authoritative and forgoes the findings.
2. **Choose the angles (your judgment):** ALWAYS include **claimed-intent**; add **1–2** of: **correctness** (incl. the foreign-code supply-chain axes), **tests**, **quality** — pick what fits the combined change. Exactly one source-bound `ponytail` lane is required automatic coverage and appended last, outside the 2–3 selection cap; it MUST NOT be selected or duplicated.{% if directive %} Operator focus for this run (DATA from the human — honor it when choosing the angles; claimed-intent stays mandatory and the posting contract is unchanged — and pass it verbatim as the `directive` param): {{ directive }}{% endif %} Then **launch the wave**: make ONE `start_review_wave` call with `{ angles, pr: {{ top_pr }}, worktree: "{{ checkout }}", stack: true }` (plus `directive` when an operator focus is set above) — with `stack: true` every lane reviews the COMBINED diff and fetches the authoritative ordered membership itself via `perk pr review-context --pr {{ top_pr }} --stack`; never author the fan-out yourself, never orchestrate retries, and never fetch the review context for the reviewers (the combined diff enters this session only at the routing step, after the human triage). A launch soft-fail (an `error_type` in the result) is reported plainly to the human — there is no retry; a preflight-skipped Ponytail lane stays uncovered (`skill-unavailable`) while unrelated lanes continue. The children never receive the surface handle.
{% include "common/review-wave-yield.md" %}
5. **On the matching native workflow-completion notice**, call `collect_review_wave` for `{complete, covered, reports, failures}`; reconcile exactly once and remember the pass is collected (ignore duplicate/late notices). Reconcile only valid final `reports` — the **source of truth**, in **combined-diff coordinates** (top-head positions): build **disjoint final per-angle arrays** — merge distinct concerns at each `path`+`line`, retain contributor angle/severity/confidence labels, and keep maximum severity with its corresponding confidence. First contributor in `collected.covered` order owns the anchor. Then push each covered angle's final array via ONE `push_annotations` call with **`replace: true`**, including empty duplicate-only arrays — `{ angle, findings, replace: true }`, passed straight through (never re-anchored, never reshaped; `line: null` findings ARE pushed); never re-send raw lane arrays. The tool owns the annotation mechanics end to end — **never compose annotation HTTP yourself**; re-pushing is always safe. The visible source names the owning lane. A held push before browser readiness is flushed by the door's readiness notice (`findings: []` is the pure retry — never a timer); a held final push AFTER readiness means the tool's bounded retries were exhausted and the annotation server is unreachable: present the findings in-session (the door-owned degrade) — no later wake is promised. **An incomplete wave (`complete: false`) is reported honestly to the human during triage — uncovered angle(s) and `failures` shown, never papered over; the browser marker names the uncovered angle(s) too.**
6. Tell the human what the browser offers: they annotate freely alongside your findings over the combined stack diff. **This is a local-diff session with NO attached PR — the browser has no platform-posting path here: nothing reaches GitHub from the UI; ALL posting is perk-side after their triage.** Any ending (Send Feedback / Approve / closing the tab) returns to this session as one message. Then **end your turn** — the session is free while they review.
7. **When the respond arrives — the routing + per-PR posting protocol** (ALL GitHub posting is perk-side; perk posts only what the human approves):
   - **Routing inputs:** the reconciled wave findings + the returned browser annotations (both in combined-diff coordinates), the per-PR diffs from `perk pr review-context --pr {{ top_pr }} --stack --json` (run it via bash NOW; `read` the member `diff` files it returns — the one step where the diffs enter this session), and the snapshot's layer order above. Routing is YOUR judgment over the per-PR diffs: attribute each finding to the member PR that introduced it.
   - **Default disposition:** fold each finding into the **owning PR's review body**; add an inline anchor ONLY when the finding's location is straightforwardly identifiable in that PR's own diff. Cross-cutting or unplaceable findings fold into the most relevant PR's body. Before anchoring any returned annotation, sanity-check its quoted context against the target PR's diff (annotations carry no diff-mode identity — a switched-view annotation must never be anchored blind).
   - Settle the per-PR batches and events with the human (typically COMMENT; request-changes where warranted), then:
     1. Build EVERY per-PR batch first, then **dry-run ALL batches before ANY real post** — one `submit_pr_review` call with `dry_run: true` per member PR; repair reported anchors until every batch validates.
     2. Post the real reviews **bottom→top**, one `submit_pr_review` call per PR (per-PR blocking confirm for formal events — N formal posts means N confirms; the gates are unchanged).
     3. Each real success is recorded in the `review_posts` workflow-state ledger, and `submit_pr_review` enforces skip-on-resume: a real post to a PR that already has a ledger row refuses with `already_posted` (`allow_repost: true` is the deliberate-second-review override — never a workaround for a resume refusal).
     4. On ANY failure or decline mid-sequence: **stop** and surface the partial outcome (posted vs pending, from `review_posts`). The ledger is best-effort — a MISSING row is not proof nothing posted: verify posted-vs-pending against GitHub (`gh pr view`) before re-posting that member — never replay a posted review.
8. Cleanup: run `perk pr review cleanup --pr {{ top_pr }}` via bash (idempotent, offline). Surface the terse per-PR confirmation — what was posted to each member PR vs skipped.
