---
name: e2e-review
effort: medium
description: >
  Deterministic, BLOCKING end-to-end review run after a feature is implemented.
  Combines functional E2E (Playwright spec written by `coder`, executed via
  playwright-skill) with visual fidelity diff (`visual-fidelity-verifier`
  multimodal agent), aggregates findings under a strict severity gate, runs a
  bounded self-heal loop, and either passes / blocks / accepts an explicit
  user override. Invocable manually via /e2e-review CARD-ID, and auto-called
  by /new Phase 2.6. Triggers on: /e2e-review, "verifica end-to-end", "review
  schermate", "controlla i mockup", "e2e review".
---

> **YOLO MODE**: When invoked by `/new` (programmatic mode), every spawned
> agent MUST use `mode: "bypassPermissions"`. When invoked manually, the user
> approves prompts.

## Project Context

**Reads from `baldart.config.yml`:**
`paths.backlog_dir`, `paths.app_dir`, `paths.components_primitives`,
`paths.components_root`, `paths.e2e_tests_dir`, `paths.design_system`,
`paths.ui_guidelines`, `features.has_e2e_review`,
`features.has_design_system`, `features.has_backlog`,
`features.e2e_review.fidelity_tolerance`,
`features.e2e_review.max_self_heal_iterations`,
`features.e2e_review.pixel_diff_threshold`,
`features.e2e_review.require_override_reason`.

**Gated by features:** `features.has_e2e_review` — the skill REFUSES to run
when `false`. The orchestrator (`/new`) silently skips the call when the flag
is off, preserving backwards compatibility for projects that have not yet
opted in.

**Overlay:** loads `.baldart/overlays/e2e-review.md` if present —
project-specific verification recipes (custom Gherkin step library, project
mockup conventions, override approval policy).

**On missing/empty keys:** ask the user; do not assume defaults. See
`framework/agents/project-context.md` § 3.

You are the **E2E Review Orchestrator**. Your single job is to determine,
deterministically, whether an implementation matches its specification — both
functionally (the user flow works) and visually (the screen matches the
mockup or the design-system spec). You do not implement. You do not redesign.
You orchestrate three workers (`coder`, `visual-fidelity-verifier`, the
underlying Playwright runner) and aggregate their output through a strict
severity gate.

This skill embodies the **three-layer harness pattern** for post-implementation
verification documented in
[Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents):
the implementing agent (`coder`) and the verifying agent
(`visual-fidelity-verifier`) are distinct, with rule-based aggregation in
between — no agent grades its own work.

## Effort

**Baseline:** `effort: medium` (frontmatter). **Inline override:** pass
`effort=<low|medium|high|xhigh|max>` anywhere in the invocation to scale
reasoning depth for this run — detect it once at kickoff and strip the token
before consuming user input. Level→behavior mapping, parsing contract, and
precedence caveats: `framework/agents/effort-protocol.md`.


---

## Invocation Modes

| Mode | Trigger | Output |
|---|---|---|
| **Manual** | User types `/e2e-review CARD-ID` (or just `/e2e-review` then asks for the card) | Markdown report to the user + JSON sidecar at `.baldart/e2e-review/<CARD-ID>/report.json` |
| **Programmatic** | `/new` Phase 2.6 invokes the skill with `{card_id, worktree_path, mode: "programmatic"}` | JSON only on stdout (parsed by `/new` for gating); written to `.baldart/e2e-review/<CARD-ID>/report.json` |

When invoked from `/new`, you operate inside the worktree. When invoked
manually, you operate in the user's current directory (which may be a worktree
or the main repo).

---

## Pre-flight (run once at skill entry)

1. **Gate check**: read `features.has_e2e_review`. If `false`:
   - Manual mode: tell the user "E2E review is disabled in baldart.config.yml.
     Run `npx baldart configure` to enable, or run /design-review for an
     advisory check." STOP.
   - Programmatic mode: return JSON
     `{ "status": "skipped", "reason": "feature_disabled" }` and STOP.
2. **Card resolution**: parse `CARD-ID` from the user's prompt or the
   orchestrator's payload. If missing in manual mode, ask the user which card.
   Read the card YAML from `${paths.backlog_dir}/<area>/<status>/<CARD-ID>.yml`
   (or `${paths.backlog_dir}/<CARD-ID>.yml` if the project does not use the
   per-area layout). If the card cannot be found, STOP with a clear error.
3. **Auto-skip detection** (BEFORE Phase 1): the skill is unnecessary on
   pure-backend cards. Skip when ALL these conditions hold:
   - The card has no `links.design` field (no mockup reference).
   - `git diff --name-only` (vs `main` / `develop` per `git.merge_strategy`)
     shows no file under `${paths.app_dir}` AND no file under
     `${paths.components_primitives}` AND no `*.tsx` / `*.css` / `*.scss` /
     `*.svelte` / `*.vue` anywhere.
   - The card type is `backend`, `api`, `db`, `infra`, `docs`, `chore`, or
     `config`.
   When auto-skipping: return JSON `{ "status": "skipped", "reason":
   "backend_only_card", "skipped_at": "pre-flight" }`. The orchestrator does
   NOT treat skip as failure.
4. **State directory**: create `.baldart/e2e-review/<CARD-ID>/` and
   subdirectories `screenshots/`, `mockups/`, `transcripts/`. The directory
   persists across self-heal iterations; previous-iteration artifacts are
   retained for diff and audit.

---

## Phase 1 — Verification Plan Extraction

**Goal**: turn the card's acceptance criteria + mockup references + design
system constraints into a machine-readable verification plan.

1. **Read card sources** in this order:
   - `card.acceptance_criteria[]` — primary source of "what must work".
   - `card.test_plan` — if present (written by `/prd`), it contains
     pre-planned Gherkin scenarios, test credentials, data prerequisites. The
     contract between `/prd` and this skill is documented in
     `framework/.claude/skills/prd/references/test-plan-contract.md` (see the
     `prd` skill's test-plan output).
   - `card.requirements[]` — additional context for what was implemented.
   - `card.links.design[]` — mockup references (Figma URL, local PNG/JPG/PDF
     path).
   - `card.scope.routes[]` — explicit routes/screens in scope. If absent,
     fall back to deriving them from the diff (see Phase 2 below).
2. **Derive routes/screens in scope** when not explicit:
   - Run `git diff --name-only` (vs the branch base).
   - Match changed files under `${paths.app_dir}` to route patterns
     (e.g. Next.js `app/.../page.tsx` → route, Remix `routes/...` →
     route, SvelteKit `routes/...` → route). If the framework is unknown,
     ask the user.
   - For each changed component under `${paths.components_primitives}`,
     identify the routes that import it via the `codebase-architect` agent
     (fallback to grep when LSP is unavailable).
3. **Synthesize verification plan** as Gherkin scenarios. Format:

   ```gherkin
   Feature: <card.title>
     Background:
       Given the user is <persona from test_plan.test_credentials.persona>
       And the system is seeded with <data_prerequisites>

     Scenario: <AC-1 short label>
       Given <pre-state>
       When <user action>
       Then <expected outcome>
       And <screen <route> matches mockup <mockup_ref>>
   ```

   When the card has a `test_plan.test_scenarios[]`, copy those scenarios
   verbatim — do not rephrase them. When the card has only
   `acceptance_criteria[]`, derive one scenario per AC using best-effort
   translation. Flag ACs that resist Gherkin translation (e.g. purely
   non-functional) under a `## Non-Functional Checks` section so they are
   not silently dropped.
4. **Persist the plan** to `.baldart/e2e-review/<CARD-ID>/plan.json`:

   ```json
   {
     "card_id": "FEAT-XXXX",
     "title": "...",
     "routes": ["/dashboard", "/dashboard/edit"],
     "personas": [{"name": "merchant", "auth": "password", "creds": {...}}],
     "data_prerequisites": ["seed merchant 'demo'", "create one paid booking"],
     "scenarios": [
       {
         "id": "AC-1",
         "name": "Merchant can update store hours",
         "gherkin": "Feature: ...",
         "routes_touched": ["/settings/hours"]
       }
     ],
     "non_functional_checks": ["loads in < 2s on slow 3G"],
     "mockup_refs": [{"route": "/dashboard", "level": "local", "path": "mockups/dashboard.png"}]
   }
   ```

---

## Phase 2 — Mockup Source Cascade (4 levels)

**Goal**: for each route in scope, locate the ground-truth visual reference.
This is graceful by design — never hard-fail because Figma is not configured.

For each `route` in the plan, walk the cascade in order and STOP at the first
hit:

### Level (a) — Figma MCP

If `card.links.design[]` contains a Figma URL AND the Figma MCP server is
available (probe `get_design_context` / `get_screenshot` tools at runtime):

- Call `get_design_context` for the Figma node to extract tokens / variables /
  primitives referenced in the design.
- Call `get_screenshot` to fetch the rendered Figma frame at 1440px width;
  save to `.baldart/e2e-review/<CARD-ID>/mockups/<route-slug>.figma.png`.
- Record `mockup_source.level = "figma"` and the node ID for the audit trail.

If the MCP tools error or time out, fall through silently to level (b) — do
not surface the failure as a blocker.

### Level (b) — Local image

If `card.links.design[]` contains a local file path (PNG / JPG / PDF) OR the
card has a `mockups/` directory alongside its YAML:

- Resolve the path. For PDFs, extract the first page as PNG (use `pdftoppm`
  or equivalent — degrade gracefully if unavailable).
- Copy to `.baldart/e2e-review/<CARD-ID>/mockups/<route-slug>.local.png`.
- Record `mockup_source.level = "local"` and the source path.

### Level (c) — Compliance-only (registry)

If no mockup image is available BUT `features.has_design_system: true`:

- The verifier will operate in compliance-only mode against
  `${paths.design_system}/INDEX.md` + `tokens-reference.md` + per-component
  specs (cascade detailed in
  [`framework/agents/design-system-protocol.md`](../../../agents/design-system-protocol.md)).
- Identify which primitives the route uses via the `codebase-architect`
  agent (or grep on imports under `${paths.components_primitives}`).
- Record `mockup_source.level = "compliance-only"` and the list of components
  in scope.

### Level (d) — Skip visual

If none of (a)–(c) apply (no mockup, no design system):

- Record `mockup_source.level = "skip"` and log a warning to the report's
  `gaps[]` field: `"no_mockup_or_design_system — visual fidelity check
  skipped for route <route>"`.
- The functional E2E (Phase 3) still runs.

The cascade output is appended to the plan under `mockup_refs[]`.

---

## Phase 3 — Functional E2E (browser-based)

**Goal**: execute the Gherkin scenarios from Phase 1 in a real browser and
capture the result + screenshots per route.

1. **Pre-conditions**:
   - The project must have a runnable dev server (read `scripts/dev` from
     `package.json` or the equivalent). If the dev server is not already
     running, start it in the background and wait for the health-check URL
     to respond (max 60s). Record the port for cleanup.
   - The project must have `@playwright/test` installed under
     `${paths.e2e_tests_dir}`. If missing, the skill cannot run — log
     `"playwright_not_installed"` in `gaps[]` and surface a setup hint to
     the user (`npm i -D @playwright/test && npx playwright install
     chromium`).
2. **Spec generation**: spawn a `coder` agent with this contract:

   ```
   Generate a Playwright .spec.ts file for card <CARD-ID> at:
     ${paths.e2e_tests_dir}/<CARD-ID-slug>.spec.ts

   Source of truth: .baldart/e2e-review/<CARD-ID>/plan.json
   (read it — it contains every scenario, persona, prerequisite, and route).

   ## Rules
   - One `test.describe` per scenario.
   - Use `page.getByRole()`, `page.getByText()`, `page.getByTestId()` —
     never CSS selectors that depend on Tailwind class strings.
   - Use `expect(locator)` with auto-waiting.
   - For each route in the scenario, call:
       await page.goto(route);
       await page.waitForLoadState("networkidle");
       await page.screenshot({
         path: ".baldart/e2e-review/<CARD-ID>/screenshots/<route-slug>.png",
         fullPage: true
       });
     The screenshot path is consumed by the visual-fidelity-verifier in Phase 4 — do not change it.
   - For OTP-driven personas (test_plan.test_credentials.auth_method == "otp"),
     use `await page.pause()` between phone-entry and code-entry. Document
     the manual step in a test.step block.
   - Do NOT use chromium.launch() — let Playwright config manage the browser.
   - Use the project's baseURL from playwright.config.ts.

   ## Out of scope
   - Do not modify any file outside ${paths.e2e_tests_dir}/.
   - Do not refactor existing tests.
   - Do not edit playwright.config.ts.
   ```

   This agent runs in `bypassPermissions` mode when called by `/new`.
3. **Execution**: run the generated spec via Bash:

   ```bash
   npx playwright test ${paths.e2e_tests_dir}/<CARD-ID-slug>.spec.ts \
     --reporter=json \
     --output=.baldart/e2e-review/<CARD-ID>/playwright-results \
     2>&1 | tee .baldart/e2e-review/<CARD-ID>/transcripts/playwright.log
   ```

   Headed mode is reserved for OTP flows in manual invocation; programmatic
   invocation from `/new` always runs headless.
4. **Result capture**: parse the Playwright JSON output. Record per scenario:
   - `status`: `passed` | `failed` | `flaky` | `skipped`
   - `duration_ms`
   - `failure_reason` (when failed, the first assertion error + stack hint)
   - `screenshot_paths[]` (one per route in the scenario)
5. **Functional findings**: every `failed` or `flaky` scenario becomes a
   functional finding in the aggregate report (Phase 5). Severity:
   - Auth / payments / data-mutation scenario failed → `critical`
   - Display-only scenario failed → `major`
   - Flaky (passed on retry) → `minor` with `confidence: low`

---

## Phase 4 — Visual Fidelity (multimodal agent)

**Goal**: for each route with a mockup reference at level (a)–(c), invoke the
`visual-fidelity-verifier` agent and collect its severity-tagged findings.

For each `route` in `plan.routes[]`:

1. **Pre-filter with pixel-diff** (optimization to control Vision cost):
   - When `mockup_source.level in {figma, local}`, run a pixel-level diff
     (any cheap library: `pixelmatch`, `looks-same`, or even ImageMagick's
     `compare -metric AE`) between the implementation screenshot and the
     mockup. Pass `pixel_diff_threshold` from config (default `0.02` = 2%).
   - If the diff is **below** the threshold, skip the Vision call for this
     route and record an empty findings array with
     `pre_filter: "pixel_diff_below_threshold"`. This is the primary
     latency / cost saver — most routes pass pixel diff cleanly.
   - When `mockup_source.level == "compliance-only"`, skip pre-filter (no
     image to compare against) and proceed directly to step 2.
   - When `mockup_source.level == "skip"`, skip this route entirely.
2. **Invoke `visual-fidelity-verifier`** with the input contract documented
   in [`visual-fidelity-verifier.md`](../../agents/visual-fidelity-verifier.md):

   ```
   subagent_type: "visual-fidelity-verifier"
   mode: "bypassPermissions"   // when called from /new
   prompt: |
     Compare the implementation against the mockup and return JSON per your
     output schema.

     Input payload:
     {
       "card_id": "FEAT-XXXX",
       "route": "/dashboard",
       "viewport": { "width": 1440, "height": 900 },
       "implementation_screenshot_path": ".baldart/e2e-review/FEAT-XXXX/screenshots/merchant-dashboard.png",
       "mockup_source": { ... },
       "tolerance": "<from config>",
       "fix_hints_enabled": true,
       "resolved_orphans": [ "<mockup affordances dropped / downgraded to decorative>" ]
     }
   ```
   Populate `resolved_orphans` from the card's UI Element Inventory: any
   interactive/iconographic row whose `function_ref` is `decorative` or that was
   resolved as dropped by the Functional Traceability Gate. This prevents the
   verifier from flagging a deliberately-removed orphan affordance as
   `component-missing`. Omit the field (or pass `[]`) when the inventory has no
   resolved orphans.
3. **Parse the agent's JSON response** strictly. If the response is malformed
   or contains prose outside the JSON, log
   `verifier_protocol_violation` in `gaps[]` and treat the route's findings
   as empty (do not invent findings from prose).
4. **Aggregate** visual findings across all routes into a single
   `visual_findings[]` array, deduplicating by `(route, category, region)`.

---

## Phase 5 — Aggregation + Severity Gate (STRICT default)

**Goal**: combine functional + visual findings into a single verdict using
the canonical severity taxonomy from `visual-fidelity-verifier.md`.

1. **Compose** the `findings[]` array:
   - Functional findings from Phase 3 (`source: "functional"`).
   - Visual findings from Phase 4 (`source: "visual"`).
   - Each finding carries: `severity`, `category`, `source`, `route`,
     `description`, `expected`, `actual`, `fix_hint`, `evidence`,
     `ds_drift_code`, `confidence`.
2. **Apply tolerance filter** based on
   `features.e2e_review.fidelity_tolerance`:
   - `strict` (DEFAULT): Critical + Major + Minor all gate. Additionally,
     auto-demote `Minor` findings with `confidence: low` to advisory (they
     remain in the report but do not block) — this is the documented
     escape hatch against font-rendering / anti-aliasing false positives.
   - `balanced`: Critical + Major gate. Minor advisory.
   - `lenient`: Critical gates. Major + Minor advisory.
3. **Compute gate decision**:
   - At least one **gating** finding → `verdict: "blocked"`.
   - No gating findings → `verdict: "passed"`.
4. **DO NOT report yet** — first run the self-heal loop (Phase 5b).

### Phase 5b — Self-heal loop (bounded)

Read `features.e2e_review.max_self_heal_iterations` (default `2`). The loop
applies only when `verdict == "blocked"` AND `iteration < max`.

For each iteration:

1. **Spawn a `coder` agent** with this contract:

   ```
   The /e2e-review skill found the following gating findings on card
   <CARD-ID>. Implement minimal, surgical fixes — do not refactor.

   ## Gating findings
   [paste the JSON findings filtered to gating severity]

   ## Rules
   - File ownership: stay within the card's allowed file set (see
     /tmp/batch-tracker-<FIRST-CARD-ID>.md ## File Ownership Map).
   - One fix per finding when possible. Reuse existing tokens / primitives
     before inventing new ones (cite ${paths.design_system}/INDEX.md +
     tokens-reference.md).
   - After the fixes, re-run lint and typecheck. Both must pass.
   - Do NOT modify Playwright specs or this skill's state files.

   ## Out of scope
   - Visual polish beyond the findings listed.
   - New features or scope expansion.
   ```

2. **Re-run Phase 3 (functional) and Phase 4 (visual)** with fresh
   screenshots. Increment `iteration`.
3. **Re-aggregate and re-decide** (back to Phase 5 step 1).
4. **STOP** when `verdict == "passed"` OR `iteration == max`.

When the loop exhausts iterations and findings remain, transition to the
**override path** below.

### Override path (when self-heal fails)

When `iteration == max` AND `verdict == "blocked"`:

- **Manual mode**: show the user the remaining findings table and ask:
  "Override the gate and proceed anyway? If yes, provide a reason
  (mandatory when `features.e2e_review.require_override_reason: true`)."
  If the user provides a reason → set `verdict: "overridden"` and record
  `override_reason` in the report. If the user declines → keep
  `verdict: "blocked"`.
- **Programmatic mode (from `/new`)**: do NOT prompt. Return
  `verdict: "blocked"` with the findings. `/new` decides whether to escalate
  to the user.

---

## Phase 6 — Return Protocol

Write the final report to `.baldart/e2e-review/<CARD-ID>/report.json`:

```json
{
  "status": "passed" | "blocked" | "overridden" | "skipped" | "error",
  "card_id": "FEAT-XXXX",
  "timestamp": "2026-05-24T12:34:56Z",
  "iterations": 2,
  "tolerance": "strict",
  "mockup_source_summary": {
    "figma": 0,
    "local": 2,
    "compliance-only": 1,
    "skip": 0
  },
  "functional": {
    "scenarios_total": 5,
    "passed": 5,
    "failed": 0,
    "flaky": 0,
    "skipped": 0
  },
  "visual": {
    "routes_checked": 3,
    "routes_pre_filtered": 1,
    "compliance_score_avg": 0.94
  },
  "findings": [ ... ],
  "ds_drift_codes": ["DS_TOKENS_DRIFT"],
  "gaps": [ ... ],
  "override_reason": null,
  "transcript_paths": [
    ".baldart/e2e-review/FEAT-XXXX/transcripts/playwright.log",
    ".baldart/e2e-review/FEAT-XXXX/transcripts/visual-verifier.md"
  ]
}
```

Then:

- **Manual mode**: render a concise Markdown summary to the user with the
  verdict, per-route findings table, and links to the full JSON + transcripts.
  Use boxed UI sparingly — this is conversational output.
- **Programmatic mode**: emit the JSON object as the final message (no prose
  preamble) so `/new` can parse it directly.

---

## Re-run Trigger (when called from `/new`)

When invoked by `/new`, the skill exposes a re-run hook: if `/new` Phase 3.7
(`/codexreview` gate) modifies a file under `${paths.app_dir}`,
`${paths.components_primitives}`, or any `*.css` / `*.scss` after the initial
`/e2e-review` run, the orchestrator MUST re-invoke `/e2e-review` before the
final commit. This protects against code-review changes that silently
introduce visual drift.

The re-run reuses the same `.baldart/e2e-review/<CARD-ID>/` state directory
and starts from Phase 3 (skipping plan extraction and mockup cascade).

---

## Interaction with Other Skills / Agents

- **`/new` Phase 2.6** — primary caller. See
  [`framework/.claude/skills/new/SKILL.md`](../new/SKILL.md) Phase 2.6 for the
  invocation contract.
- **`/prd` test-plan output** — primary input to Phase 1. The skill expects
  the schema documented in
  [`framework/.claude/skills/prd/references/test-plan-contract.md`](../prd/references/test-plan-contract.md).
- **`/design-review` command** — when invoked from this skill with
  `mode: "programmatic"`, returns JSON instead of Markdown for gating.
  See [`framework/.claude/commands/design-review.md`](../../commands/design-review.md).
- **`playwright-skill`** — tooling layer for spec execution. See its
  "E2E Review Integration" section.
- **`webapp-testing`** — used in compliance-only mode for runtime DOM
  inspection (read computed styles, focus order). See its "E2E Review
  Integration" section.
- **`ds-drift` routine** — weekly safety net. This skill is the per-card
  first line; the routine catches what slips through (e.g. direct human
  edits that bypass `/new`). The two are complementary, not redundant.

---

## What This Skill Is NOT

- **Not a code reviewer** — that is `/codexreview` + `code-reviewer`.
- **Not a design generator** — that is `ui-design` / `frontend-design`.
- **Not a mockup creator** — when no mockup exists, the skill degrades to
  compliance-only or skip; it does not invent one.
- **Not a unit-test runner** — that is `qa-sentinel`.
- **Not an exploratory tester** — the verification plan is bounded by the
  card's acceptance criteria; the skill does not invent new scenarios.

---

## Failure Modes & Diagnostics

| Symptom | Likely cause | Action |
|---|---|---|
| Skill refuses to run | `features.has_e2e_review: false` | Run `npx baldart configure` and enable it |
| Pixel-diff always 100% | Implementation screenshot dimensions ≠ mockup dimensions | Confirm viewport in Phase 3 matches the mockup's target width; resize mockup if needed |
| Visual findings dominated by `font-rendering-variance` | Anti-aliasing / subpixel differences across OS | Tolerance is doing its job — these auto-demote in strict mode when `confidence: low` |
| Self-heal loop never converges | `coder` introducing new regressions while fixing | After max iterations, override with reason and open a follow-up backlog card |
| `verifier_protocol_violation` in gaps | `visual-fidelity-verifier` returned prose mixed with JSON | Re-spawn the verifier once; if persistent, surface to user (likely a model regression) |

---

## Return Protocol

The skill's final message is one of:

- **Manual mode**: a short Markdown summary followed by a fenced JSON code
  block with the full report. The JSON is the source of truth; the Markdown
  is for human readability.
- **Programmatic mode**: a single JSON object matching the schema in Phase 6.
  No prose preamble.
