perk /pr-review-browser — human-in-the-loop adversarial review of FOREIGN PR #{{ pr }} ({{ pr_url }}) on the plannotator browser surface: ONE `start_review_wave` launch (async) → reconcile from `collect_review_wave`'s typed reports → per-angle reviewer findings pushed into the browser session via `push_annotations` after collection → the human reviews and posts from the browser.
1. The PR head worktree is ready at `{{ worktree }}` (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 — 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 change. Exactly one source-bound `ponytail` lane is required automatic coverage and appended last, outside the 2–3 selection cap; it uses the same adversarial-reviewer model/directive/report family and 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: {{ pr }}, worktree: "{{ worktree }}" }` (plus `directive` when an operator focus is set above) — the tool attempts the selected adversarial-review lanes plus required automatic final Ponytail (fresh-context `perk.adversarial-reviewer`, non-blocking), partitions required-skill preflight, and returns the run handle plus nested `launch`: `launch.requested` is the full logical manifest, `launch.runnable` is what the workflow actually accepted after preflight, and `launch.preflightFailures` names keyed omissions. A preflight-skipped lane was not launched. Never author the fan-out yourself and never orchestrate retries; a launch soft-fail (an `error_type` in the result) is reported plainly to the human — there is no retry. If exact Ponytail package/skill validation fails, that child does not spawn or fall back: the lane remains uncovered with `skill-unavailable` and the wave is incomplete while unrelated lanes continue. The children never receive the surface handle (no browser or server details in any task); they fetch their own `perk pr review-context` — never fetch it yourself (the raw diff never enters this session) — and you never re-anchor a child's finding.
{% 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**: **union** distinct concerns and **dedupe** on the same `path`+`line`, preserving each contributor's angle/severity/confidence labels in merged text. Keep the highest severity with that contributor's confidence. Assign each anchor to the first contributing lane in `collected.covered` order, forming **disjoint final per-angle arrays**, not each lane's raw findings. Duplicate-only covered lanes get empty final arrays. Then push each covered angle's final array via ONE `push_annotations` call with **`replace: true`**, including empty final arrays — `{ angle, findings, replace: true }`, the findings passed straight through (never re-anchored, never reshaped; `line: null` findings ARE pushed — but still fold into any GitHub body). The tool owns the annotation mechanics end to end, so **never compose annotation HTTP yourself**, and re-pushing is always safe (duplicate anchors are skipped, never refused). The visible source names the owning lane; merged text retains the other valid contributors. 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 — the uncovered angle(s) and the `failures` details are 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, and they **platform-post inline comments plus an APPROVE/COMMENT verdict to GitHub directly from the UI — that is the GitHub path**; any ending (Send Feedback / Approve / a platform post / closing the tab) returns to this session as a message — one shot. Then **end your turn** — the session is free while they review in the browser.
7. When the respond arrives: **perk composes nothing by default** — ask the human what they want. Call `submit_pr_review` (`dry_run: true` first; repair any reported anchors; the same gates) ONLY for a **request-changes** verdict (the UI cannot post it) or when the human explicitly asks perk to post. Cleanup: run `perk pr review cleanup --pr {{ pr }}` via bash (idempotent, offline). Surface the terse confirmation — what the human platform-posted vs what (if anything) perk posted.
