baldart 4.2.2 → 4.5.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,51 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.5.0] - 2026-06-03
9
+
10
+ **`/new` Team Mode non revisiona più lo stesso diff fino a tre volte: la profondità della code-review ora scala col `review_profile` della card.** Lo Step D del team mode faceva passare ogni card "full" sotto code-review tre volte — `D.2` (code-reviewer di gruppo) → `D.4b` (`/codexreview` per-card, che rispawna code-reviewer + Codex + CoVe + FP-gate) → Final Review F.3 (Codex sull'intero batch) — e ogni card "light" due volte (D.2 + un D.4b che a `light` esegue solo code-reviewer + FP, puro doppione di D.2). Un nuovo step **D.1.5** calcola UNA volta il profilo effettivo per card (floor da `review_profile` + escalation-only dal detector Step-A sul diff reale) e partiziona il gruppo in `LIGHT_CARDS` / `FULL_CARDS`. **D.2** ora esegue il code-reviewer solo sull'union dei diff `LIGHT_CARDS` (skippato del tutto se non ce ne sono); **D.4b** gira `/codexreview` solo per i `FULL_CARDS`, mentre i `LIGHT_CARDS` lo saltano (la loro review adversarial è garantita dalla Final FULL gate, sempre on). Esito: ogni card riceve **una sola** code-review per-card/gruppo proporzionale al rischio + la Final batch gate — light: D.2 + Final; full: D.4b + Final (giù da 3). **D.3b Simplify** salta sulle card `review_profile == skip` (quality-only, niente diff sostanziale). Tutti gli skip sono **gate-reason enumerati e deterministici** (driven dal campo `review_profile` autorato in PRD), non skip "per time budget": una nuova clausola "REVIEW DEPTH SCALES WITH `review_profile`" in testa alla skill lo formalizza. Eliminato anche il polling `sleep N; echo "waiting..."` ad ogni barrier (i background agent re-invocano l'orchestratore automaticamente). **MINOR** (cambia il comportamento di review di `/new` team mode senza rimuovere agenti o rompere install; nessuna chiave `baldart.config.yml` nuova — si appoggia su `review_profile` già esistente nella card).
11
+
12
+ > **Why.** Il team mode parallelizza la parte economica (scrivere il codice) e serializzava la parte costosa (la coda di review): i coder partivano in parallelo, poi tutto convergeva in un imbuto sequenziale di 11 sotto-passi con la code-review ripetuta. La de-duplicazione si appoggia su due invarianti già presenti: (1) la **Final-review FULL gate** (dal v3.37.0) gira SEMPRE Codex (+ code-reviewer fallback) sull'intero diff di batch, senza riduzioni — quindi ogni riga di ogni card light riceve comunque Codex-adversarial prima del merge; (2) l'**escalation-only** del detector Step-A (`light`→`full`, mai l'inverso) garantisce che un path rischioso emerso a implementazione promuova comunque la card a `full`. Tradeoff accettato (scelta utente "aggressivo 3→2"): per i `FULL_CARDS` il rilevamento cross-card si sposta dal per-gruppo (D.2) al per-batch (Final F.3) — stessa sicurezza di merge-gate, feedback a fine batch invece che per-gruppo. `/codexreview` NON è toccato (il suo FP-gate dipende architetturalmente da `code-reviewer`, quindi la leva è l'invocazione condizionale di D.4b, non un flag interno). Il **percorso sequenziale è invariato** (non ha un code-reviewer di gruppo, quindi il suo `/codexreview` a Phase 3.7 resta l'unica code-review per-card). Allineato al principio "data-driven sopra threshold arbitrarie": nessuna soglia nuova, si onora un campo deterministico già esistente. AC-Closure (D.3a), D.4 QA e D.4a doc restano invariati.
13
+
14
+ ### Changed
15
+
16
+ - **`framework/.claude/skills/new/SKILL.md`** — (a) nuova clausola in testa "REVIEW DEPTH SCALES WITH `review_profile`" che enumera il profilo come gate-reason deterministico (distinto dallo skip "per time"); la clausola "NO PHASE SKIP" cita il profilo tra le ragioni valide e ammorbidisce "never reduce" in "never reduce for any model-invented reason". (b) Nuovo **D.1.5** (Effective per-card review profile) che calcola la partizione `LIGHT_CARDS`/`FULL_CARDS` una volta sola e la logga. (c) **D.2** code-reviewer scoped a `LIGHT_CARDS` (skip se vuoto); doc-reviewer invariato su tutto il gruppo; tradeoff cross-card→Final documentato inline. (d) **D.3b** salta su `review_profile == skip`. (e) **D.4b** condizionale: `/codexreview` solo per `FULL_CARDS` (profilo sempre `full`, niente recompute), `LIGHT_CARDS` skip con reason enumerata. (f) **Step D coverage assertion** accetta gli skip profilo-driven come entry valide documentate. (g) **Step C** direttiva anti-polling (no `sleep N; echo waiting`). (h) **D.3b Simplify** ora fa **fan-out in parallelo** su tutte le card eleggibili del gruppo (analisi read-only su diff file-disgiunti → niente più coda sequenziale per-card; i fix si applicano dopo, lint/tsc una volta sola); **D.3c E2E** valuta il gate per tutte le card insieme e parallelizza le eleggibili quando colpiscono route disgiunte (fallback sequenziale su contesa del dev-server). Percorso sequenziale, `codexreview.md`, AC-Closure D.3a (resta sequenziale per via dell'`AskUserQuestion`), D.4 QA, D.4a doc, Final Review F.3: non toccati.
17
+
18
+ > **Nota sullo scope dei tagli.** La de-dup si ferma qui di proposito. La **Final Review F.3** ri-revisiona l'intero batch (Codex + doc + perf + qa-sentinel suite completa) anche dopo il lavoro per-gruppo: è ridondante su batch a gruppo singolo MA è la rete di sicurezza incondizionata su cui ora si appoggiano le card light, e il framework ha **già tentato di ridurne lo scope in v3.35.0 e l'ha rollbackata in v3.37.0** dopo un incidente — quindi resta piena. Stessa logica per il doppio run della suite di test (D.4 per-gruppo + Final). Questi NON sono toccati.
19
+
20
+ ## [4.4.0] - 2026-06-03
21
+
22
+ **`/prd` mockup intake ora risolve le schermate di una feature in modo *strutturale* da un bundle Claude Design, invece di tirare a indovinare con lo scoring per keyword.** L'export di Claude Design non è controllabile dall'utente: si scarica solo il bundle intero del workspace multi-feature. Finora Step 1.6.4b gestiva il bundle `.zip` con un solo scoring di rilevanza su filename/`<title>` (pieno di `[CALIBRATION-NEEDED]`), e la sola modalità d'ingresso era l'archivio `.zip`. Ora un **passaggio strutturale** (Step b2) sfrutta i segnali nativi che il bundle già porta: **un canvas `*.html` per feature** (anchor sul titolo feature), **slot `id="s<N>"` = screen-ID** del § 3 del prompt di handoff (`s31` ↔ schermata `3.1`), e il manifest nativo **`.design-canvas.state.json`** (arricchimento label, mai autorità). Le schermate che vivono solo come artboard nel canvas (non esportate come PNG) vengono **renderizzate deterministicamente** via http-server + Playwright (nuova `canvas-intake-recipe.md`), risolvendo il CORS che blocca Babel su `file://`. Il trigger è allargato anche alle **cartelle già estratte** (non solo `.zip`). A valle, la routing Hybrid di Step 3 passa da fuzzy substring match a **exact join** su `slot_id`. Lo scoring per keyword resta come **fallback** (bundle generico o canvas ambiguo) e il **gate di conferma STOP umano è invariato**. **MINOR** (nuovo reference `canvas-intake-recipe.md` + nuovi campi di state-schema `mockups.archive.bundle_kind`/`canvas_ref`/`discarded[]`, `mockups.canvas_render`, `screens_in_scope[].slot_id`; trigger allargato; NON una chiave `baldart.config.yml` → nessuna cascade di schema-propagation).
23
+
24
+ > **Why.** Il mapping 1-a-1 tra schermate prodotte da Claude Design e schermate del PRD non va *imposto* nell'handoff (l'export è monolitico e fuori controllo): **esiste già** nella struttura del bundle, semplicemente il framework non la leggeva. Spostare il peso dall'handoff (dove non si può fare nulla) all'intake (dove i segnali sono tutti presenti) elimina l'archeologia manuale — CORS, http-server e Playwright re-improvvisati a ogni rientro — e la sostituisce con un resolver ripetibile. Contratto fail-safe in ogni direzione: firma assente / slot non-`s<N>` / manifest parziale / feature su 2 canvas / Playwright assente / porta occupata ricadono tutti sul comportamento attuale (scoring + STOP) — **il PRD non aborta mai sull'intake**. Allineato al principio "data-driven sopra threshold arbitrarie": lo scoring `[CALIBRATION-NEEDED]` diventa il ripiego, non la prima linea.
25
+
26
+ ### Added
27
+
28
+ - **`framework/.claude/skills/prd/references/canvas-intake-recipe.md`** — ricetta archiviata per renderizzare gli artboard canvas-only di un bundle Claude Design in PNG: preflight (no auto-install), http-server con port-loop 8080→8090, render loop Playwright su `[data-dc-section]`/`[data-dc-slot]` (attributi runtime, non statici), dedup vs pre-render del designer, tabella di degradazione. Invocata da Step 1.6.4b/A5, mai standalone.
29
+
30
+ ### Changed
31
+
32
+ - **`framework/.claude/skills/prd/references/discovery-phase.md`** — Step 1.6.4b: (a) Step a allargato per accettare una **directory** oltre al `.zip` (`mockups.archive.kind: zip | directory`, convergono su `root`); (b) nuovo **Step b2 — Claude-Design structural resolution** (A0 signature gate → A1 canvas anchor con `AskUserQuestion` su ambiguità → A2 estrazione slot `id="s<N>"` → screen-ID → A3 read manifest enrich-only → A4 sidecar jsx/PNG → A5 render via recipe → A6 discard-and-log) inserito tra enumerate e score; (c) Step c demoto a **fallback** esplicito; (d) Step d con variante tabella *screen-centric*; (e) Step e materializza anche jsx-deps + asset condivisi + PNG renderizzati e scrive `screens_in_scope[]` con `slot_id`/`screen_id`/`canvas_ref`. Appendice schema estesa coerentemente.
33
+ - **`framework/.claude/skills/prd/references/ui-design-phase.md`** — routing Hybrid (Step 2 della per-screen table): **exact-join-first** su `slot_id`/`screen_id` quando presenti (no STOP), fuzzy substring match conservato come ramo `else` con il suo STOP-and-ask invariato.
34
+ - **`framework/.claude/skills/prd/SKILL.md`** — HARD RULE 16: lead-in allargato a "`.zip` OPPURE cartella estratta di un bundle Claude Design (firma `.design-canvas.state.json` e/o `*.html` con slot `id="s<N>"`)" + frase su passaggio strutturale, scoring come fallback, STOP invariato, no-abort.
35
+ - **`framework/.claude/skills/prd/assets/state-template.md`** — `mockups.archive` estesa con `bundle_kind`/`canvas_ref`/`canvas_candidates[]`/`discarded[]`, nuovo blocco `mockups.canvas_render`, `kind` ora `zip | directory`; `screens_in_scope[]` con `slot_id`/`screen_id`/`canvas_ref`.
36
+ - **`framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md`** — nudge minimo a fine § 3 (titola il canvas col nome feature; numera gli artboard coerenti con gli screen-ID): unica richiesta realistica dato che l'export non è controllabile.
37
+
38
+ ## [4.3.0] - 2026-06-03
39
+
40
+ **`/new` skips its pre-flight cross-card Codex check when `/prd` already audited the same batch jointly and nothing drifted since.** The `/prd` Step 6.6 holistic audit (4-agent team + Codex adversarial pass, 6.6d) already reviews the full card set for `files_likely_touched` conflicts, dependency shadows, implicit ordering, and shared-state mutation — the *exact* lens of `/new` Step 3d's cross-card check. Running Step 3d unconditionally re-paid that Codex call on every multi-card batch, even when implementing the freshly-audited PRD set with zero intervening change. Now the audit phase **stamps provenance** on each card (`metadata.holistic_audit`: `audited_at` + `audited_commit` + `audited_set`), and `/new` Step 3d evaluates a four-condition skip: provenance present (S1), batch jointly audited (S2), batch ⊆ audited set (S3), and no commit touched the batch's claimed paths since the audit baseline (S4). All four hold → SKIP (logged, non-decision); any fails → RUN as before. **MINOR** (new card-template field + new conditional capability; NOT a `baldart.config.yml` key — `metadata.holistic_audit` is a backlog-card field written by the audit phase).
41
+
42
+ > **Why.** The two checks are not "different lenses" — Step 3d's `FILE_CONFLICT / IMPLICIT_DEP / ORDER_RISK / STATE_MUTATION` questions map 1:1 onto 6.6d's `attack_surface`, run over the same card set + adjacent cards. The only residual value of re-running at implementation time is the two cases the authoring-time audit *cannot* have covered: (a) the `/new` batch is not the audited set (a subset, or cards stitched from separate PRD runs), or (b) the codebase drifted on the claimed paths between audit and implementation. The gate isolates exactly those two cases via provenance + a `git log <audited_commit>..<trunk> -- <claimed_paths>` drift probe, and **degrades safely in every direction** — missing/partial provenance, mismatched sets, empty/unreachable commit, or any git error all fall through to RUN, never to a silent skip. Aligns with the standing "data-driven over arbitrary thresholds" and "no silent confirmation gates for non-decisions" principles: the skip is deterministic and auditable (the RUN/SKIP reason is logged in the tracker), not a heuristic.
43
+
44
+ ### Added
45
+
46
+ - **`framework/templates/feature-card.template.yml`** — documents the optional `metadata.holistic_audit` block (`audited_at` / `audited_commit` / `audited_set`), written by the audit phase (not at authoring), consumed by `/new` Step 3d.
47
+
48
+ ### Changed
49
+
50
+ - **`framework/.claude/skills/prd/references/audit-phase.md`** — Step 6.9 (Apply Findings to Cards) adds a "Holistic-audit provenance stamp" subsection: capture three run-level values once (`audited_at` = UTC now, `audited_commit` = `git merge-base HEAD <trunk>` fork point, `audited_set` = sorted jointly-audited card IDs) and write `metadata.holistic_audit` on each card. New per-card workflow step (was 5 steps, now 6). Fail-safe contract: the stamp is an optimization hint, never a gate — never blocks the PRD if it can't be written.
51
+ - **`framework/.claude/skills/new/SKILL.md`** — Step 3d replaces the flat "Skip if batch has only 1 card" with a provenance-aware four-condition skip decision (S1 provenance present, S2 jointly audited, S3 batch ⊆ audited set, S4 no drift on claimed paths via `git log <audited_commit>..<trunk> -- <paths>`). SKIP logs `SKIPPED (provenance) — …` and proceeds with the file-ownership map unchanged; otherwise RUN with the reason recorded. Tracker `## Cross-Card Conflicts (Codex)` placeholder updated to note the possible skip. Every ambiguity falls through to RUN.
52
+
8
53
  ## [4.2.2] - 2026-06-03
9
54
 
10
55
  **`/new` Phase 0 stops blocking on framework-owned noise: telemetry-only dirty trees and `baldart update`/subtree commits are auto-ignored.** Every batch tripped its own Phase 0 gates: (1) the dirty-tree gate fired on `${paths.metrics}/archive/batch-tracker-*.md` + `skill-runs.jsonl` + `sessions/` — telemetry that Phase 8 writes *after* the merge and never commits, so the next batch always saw a "dirty" tree and forced a stash/commit decision; (2) the divergence gate fired on `.framework/` subtree commits + the `[CHORE] baldart update …` chore, treating expected framework-management divergence as the FEAT-0006 orphan-commit pattern and forcing a push/cherry-pick decision. Both gates now **partition the working tree / the ahead-commits before prompting** and surface only genuine in-progress user work. The FEAT-0006 protection is intact — real orphan commits and real uncommitted work still gate. **PATCH** (bugfix to an existing gate's classification; no new agent/skill/template, no `baldart.config.yml` key — `paths.metrics` already exists).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.2.2
1
+ 4.5.0
@@ -13,7 +13,9 @@ description: >
13
13
 
14
14
  > **SCOPE CLOSURE DISCIPLINE (BLOCKING)**: This orchestrator NEVER unilaterally defers an acceptance criterion. Silent deferral via `implementation_notes`, commit message, or final recap is a protocol violation per `framework/agents/workflows.md § Scope Closure Discipline`. Under context-window pressure, time pressure, or unforeseen complexity, the correct action is to STOP and route the decision back to the user via `AskUserQuestion` — Auto Mode's "bias toward proceeding" applies to routine decisions, NOT to scope reduction. **Phase 2.5b (AC-Closure Gate)** enforces this at the per-card level before commit and is non-skippable.
15
15
 
16
- > **NO PHASE SKIP FOR PERCEIVED TIME, EVER (BLOCKING)**: MANDATORY phases are non-negotiable in BOTH sequential mode and team mode. The phrases `time budget`, `context budget`, `context pressure as override`, `for time`, `to save tokens`, `to save iterations`, or any model-invented constraint are NOT valid skip reasons. The only valid skip reasons are those explicitly enumerated in each phase's Gate table (e.g. `features.has_e2e_review: false`, backend-only diff, card type `docs`/`chore`/`config`). Documenting skipped phases at the end of a batch as `"saltati per time budget"` is a protocol violation per the FEAT-0006 incident — treat any such phrase in your own output as a self-correction trigger: stop, surface the situation via `AskUserQuestion`, and resume only after explicit user direction. Auto Mode does NOT override this. Team-mode orchestrators (L0/L1) propagate every MANDATORY phase per-card and never aggregate, reduce, or pre-filter the per-card pipeline.
16
+ > **NO PHASE SKIP FOR PERCEIVED TIME, EVER (BLOCKING)**: MANDATORY phases are non-negotiable in BOTH sequential mode and team mode. The phrases `time budget`, `context budget`, `context pressure as override`, `for time`, `to save tokens`, `to save iterations`, or any model-invented constraint are NOT valid skip reasons. The only valid skip reasons are those explicitly enumerated in each phase's Gate table (e.g. `features.has_e2e_review: false`, backend-only diff, card type `docs`/`chore`/`config`, **and the card's `review_profile`-driven review depth — see next paragraph**). Documenting skipped phases at the end of a batch as `"saltati per time budget"` is a protocol violation per the FEAT-0006 incident — treat any such phrase in your own output as a self-correction trigger: stop, surface the situation via `AskUserQuestion`, and resume only after explicit user direction. Auto Mode does NOT override this. Team-mode orchestrators (L0/L1) propagate every MANDATORY phase per-card and never aggregate, reduce, or pre-filter the per-card pipeline **for any model-invented reason**.
17
+
18
+ > **REVIEW DEPTH SCALES WITH `review_profile` (deterministic — NOT a time/token skip)**: the review *depth* of a card MAY scale with its `review_profile` (the PRD-authored field, escalation-only via the Step-A detector — `light`→`full` never the reverse), exactly as the QA tier (D.4) and Codex `light`/`full` depth (D.4b / Phase 3.7 Step C) already do. This is an **enumerated, deterministic gate reason**, the polar opposite of a `time budget` skip: it is driven by a field a human authored at PRD time, not by the model's perception of how long the batch is taking. In **team mode** this means a `light`/`skip` (0-trigger) card receives its single per-card code review at the **D.2 group pass** and defers Codex-adversarial to the unconditional **Final-review FULL gate** (so D.4b is skipped for it), while a `full` card receives its per-card code review at the **D.4b `/codexreview`** pass (so the D.2 group code-reviewer does not re-review it). Every card still gets ≥1 per-card/group code review **plus** the cross-batch Final FULL gate — coverage is preserved, redundancy is removed. The **sequential** per-card pipeline is unchanged (it has no D.2 group code-reviewer, so its Phase 3.7 `/codexreview` stays the single per-card code review). Skipping a gate WITHOUT an enumerated `review_profile`/Gate-table reason remains a protocol violation.
17
19
 
18
20
  ## Project Context
19
21
 
@@ -100,7 +102,7 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
100
102
  |------|--------------------------|
101
103
 
102
104
  ## Cross-Card Conflicts (Codex)
103
- (pending — runs during worktree setup for batches > 1 card)
105
+ (pending — for batches > 1 card; may be SKIPPED if the batch was already audited jointly by /prd Step 6.6 with no drift since — see Step 3d skip decision)
104
106
 
105
107
  ## Issues & Flags
106
108
  (none yet)
@@ -276,7 +278,24 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
276
278
 
277
279
  > **Why**: a non-Anthropic frontier model (Codex, via `codex-companion.mjs`) reviews the full card batch for cross-card conflicts that per-card plan-auditor checks cannot detect (each plan-auditor sees only one card).
278
280
 
279
- **Skip if**: batch has only 1 card (no cross-card conflicts possible).
281
+ **Skip decision (provenance-aware since v4.3.0).** The `/prd` Step 6.6 holistic audit (4-agent team + Codex adversarial pass) ALREADY ran this exact cross-card analysis over the cards when they were authored — its Codex prompt covers `files_likely_touched` conflicts, dependency shadows, implicit ordering, and shared-state mutation across the whole audited set. Re-running here is only worth the Codex call when **something changed between that audit and now**. Compute the skip before launching:
282
+
283
+ 1. **Always skip** if the batch has only 1 card (no cross-card conflicts possible).
284
+ 2. Otherwise, read `metadata.holistic_audit` from every card in the batch and evaluate the four SKIP conditions — **all must hold**:
285
+ - **S1 — provenance present**: every batch card has `metadata.holistic_audit` with non-empty `audited_at`, `audited_commit`, and `audited_set`. (Missing/partial on any card ⇒ condition fails.)
286
+ - **S2 — jointly audited**: all batch cards carry the **same** `audited_set` value (they were reviewed together, not stitched from separate PRD runs).
287
+ - **S3 — batch ⊆ audited set**: every batch card ID is present in that shared `audited_set`.
288
+ - **S4 — no drift on the claimed paths**: no commit touched any path the batch claims, between the audit baseline and the trunk this run grounds on. Using the shared `audited_commit` (`$AC`) and the batch's union of claimed/touched paths from the file-ownership map (step 3b, `$BATCH_PATHS`):
289
+ ```bash
290
+ # empty AC, or AC not in history → treat as drift (do NOT skip)
291
+ DRIFT="$(git -C "$MAIN" log --oneline "$AC".."$TRUNK" -- $BATCH_PATHS 2>/dev/null || echo "DRIFT")"
292
+ ```
293
+ S4 holds **iff** `$AC` is non-empty AND the command succeeds AND `$DRIFT` is empty.
294
+ 3. **If S1–S4 all hold → SKIP the Codex cross-card check.** Log in the tracker under `## Cross-Card Conflicts (Codex)`:
295
+ `SKIPPED (provenance) — batch ⊆ holistic_audit set <audited_set>, no drift since <AC short sha> (audited <audited_at>)`. Then proceed to step 4 with the file-ownership map unchanged. Do NOT present anything to the user — this is a non-decision.
296
+ 4. **Otherwise → RUN the check** (launch below). Record WHY in the same tracker section so the choice is auditable: `RUN — <reason>` where reason is one of `no/partial provenance` (S1) · `batch spans multiple audit sets` (S2) · `batch not a subset of audited set` (S3) · `drift on <first drifted path>` (S4).
297
+
298
+ `$MAIN` and `$TRUNK` are the same variables resolved in Phase 0. This gate degrades safely in every direction: any missing signal, ambiguity, or git error falls through to RUN — never to a silent skip.
280
299
 
281
300
  Launch via `Bash` with `run_in_background: true` and `timeout: 300000`:
282
301
 
@@ -2339,6 +2358,8 @@ Agent tool call:
2339
2358
 
2340
2359
  The orchestrator waits for ALL background agents in the group to complete. It will be notified automatically as each finishes (`run_in_background`).
2341
2360
 
2361
+ > **Do NOT poll with `sleep N; echo "waiting..."` loops.** Background agents (and background `Bash` commands) re-invoke the orchestrator automatically when they finish — issuing `sleep`/`echo` "waiting" turns burns orchestrator turns and tokens for zero progress. Simply end your turn after spawning the background work; you will be woken on completion. This applies to EVERY barrier in Step D (D.1 build, D.2 reviewers, D.3/D.3b agents, D.4 qa-sentinel, D.4a doc-reviewer, D.4b `/codexreview`), not just Step C.
2362
+
2342
2363
  For each completed agent:
2343
2364
  1. Read the completion report from the agent's output.
2344
2365
  2. Log to tracker: card ID, status, files changed, build/lint status.
@@ -2358,21 +2379,38 @@ After ALL agents in the group complete successfully:
2358
2379
 
2359
2380
  1. **D.1 — Build verification (group)** — Run `npm run build` in the worktree to verify combined changes compile. If build fails, identify which card's changes broke it (from `git diff` per card), spawn a targeted fix-coder for those files only.
2360
2381
 
2361
- 2. **D.2Combined static review (group)** Invoke **code-reviewer** + **doc-reviewer** IN PARALLEL (read-only) across ALL changes in the group. ONE combined pass catches cross-card issues. The doc-reviewer runs **read-only here** (it cannot write while code-reviewer reads the same diff in parallel — parallel-safety), MUST **attribute every doc finding to a specific card** (by file → ownership map), and applies the full Phase 3 mandate INCLUDING the spec/docs-drift→bug lens (since v3.35.0). D.4a consumes these per-card findings and dispatches the doc-reviewer (now alone, in write mode) to apply them — no second AUDIT spawn, but the FIXES are still owned by doc-reviewer.
2382
+ 1.5. **D.1.5Effective per-card review profile (compute ONCE; drives D.2 + D.4b)** For EACH card in the group, compute its **effective codex profile** with the SAME deterministic rule the sequential Phase 3.7 Step C uses, so the two paths never disagree:
2383
+ - **Floor**: read the card's `review_profile` field (`skip`/`light`/`balanced`/`deep`) per the QA Profile Selector (fallback-computed only for legacy cards lacking the field).
2384
+ - **Escalation-only (Step A detector)**: run the Phase 3.7 **Step A** high-risk detector (bash + grep) on THIS card's committed diff (`git diff "$TRUNK...HEAD" -- <card files from ownership map>`). A matched trigger promotes `light`→`full`; it can NEVER downgrade `full`→`light`. The card's `review_profile` is the floor; the detector is the safety net on top.
2385
+ - **Partition** the group into two lists and log them in the tracker under `## Team Mode`:
2386
+ - `LIGHT_CARDS` = cards whose effective profile is `light` or `skip` **AND** zero Step-A triggers.
2387
+ - `FULL_CARDS` = all others (effective profile `balanced`/`deep`, OR any Step-A trigger).
2388
+ - Log: `## D.1.5 Effective Profiles\n<CARD-ID>: profile=<floor> triggers=<n> → effective=<light|full> (<LIGHT_CARDS|FULL_CARDS>)` per card. This single computation is the SSOT for both D.2 (code-reviewer scoping) and D.4b (inclusion) — do NOT recompute it in either step.
2389
+
2390
+ 2. **D.2 — Combined static review (group)** — Two reviewers, **scoped by the D.1.5 partition** so each card gets exactly ONE per-card/group code review proportional to its risk (the other is the Final FULL gate):
2391
+ - **doc-reviewer — over the WHOLE group, always** (unchanged). It runs **read-only here** (it cannot write while code-reviewer reads in parallel — parallel-safety), MUST **attribute every doc finding to a specific card** (by file → ownership map), and applies the full Phase 3 mandate INCLUDING the spec/docs-drift→bug lens (since v3.35.0). D.4a consumes these per-card findings and dispatches the doc-reviewer (now alone, in write mode) to apply them — no second AUDIT spawn, but the FIXES are still owned by doc-reviewer.
2392
+ - **code-reviewer — scoped to the diff-union of `LIGHT_CARDS` ONLY** (run it in parallel with the doc-reviewer over those files). `LIGHT_CARDS` cards skip D.4b, so D.2 is their single per-card code review (the cross-batch Codex pass at Final covers them adversarially). **If `LIGHT_CARDS` is empty → do NOT spawn code-reviewer at D.2** (every card is `FULL_CARDS` and receives its deeper code review — `code-reviewer` + Codex + CoVe + FP — at D.4b). Log the deterministic gate reason: `D.2 code-reviewer: scope=LIGHT_CARDS [<ids>] | SKIPPED (no light cards — code-review owned by D.4b per-card + Final FULL gate)`.
2393
+ - **Tradeoff (documented, accepted — see top-of-file `REVIEW DEPTH SCALES` clause)**: for `FULL_CARDS`, cross-card detection moves from the per-group D.2 pass to the per-batch **Final-review FULL gate** (Codex over the entire batch diff, plus code-reviewer fallback) — same merge-gate safety, feedback arrives at batch-end rather than per-group. This is the intended de-duplication: `FULL_CARDS` are NOT re-reviewed by a group-level code-reviewer on top of their D.4b `/codexreview`.
2362
2394
 
2363
2395
  3. **D.3 — Apply fixes (group)** — If D.2 findings exist, spawn ONE fix-coder to apply all fixes in a single pass. Run build + lint after.
2364
2396
 
2365
2397
  3a. **D.3a — Phase 2.5b AC-Closure Gate (per-card, BLOCKING — non-skippable)** — For EACH card in the group, **sequentially**, invoke the full Phase 2.5b gate as documented in `### Phase 2.5b — AC-Closure Gate (BLOCKING — Scope Closure Discipline)`. This includes: build the AC Closure Ledger from the card YAML, run the rationalization scan, invoke `AskUserQuestion` one-per-deferred-AC, run the `implementation_notes` deferral audit, and persist the ledger in the tracker. Until EVERY card in the group exits PASS, do NOT proceed to D.3b. Cards exiting with `not_implemented` ACs that the user routes to "Implementa adesso" must finish their fix-coder loop and re-pass the gate before D.3b starts for the next card. Log under `## AC Closure Ledger — <CARD-ID>` per card.
2366
2398
 
2367
- 3b. **D.3b — Phase 2.55 Simplify (per-card)** — For EACH card in the group, invoke Phase 2.55 (Reuse / Quality / Efficiency parallel agents) on the per-card diff (`git diff --name-only` filtered by the card's File Ownership Map). Per-card (not group-aggregate) so findings are attributable. Apply fixes inline. After all cards: re-run `npm run lint` and `npx tsc --noEmit` on the worktree once.
2399
+ 3b. **D.3b — Phase 2.55 Simplify (per-card, FANNED OUT across the group)** — The Simplify agents are **read-only analysis on file-disjoint per-card diffs** (the orchestrator applies the fixes afterward), so there is NO reason to run them one card at a time. **Spawn the per-card Simplify analysis for ALL eligible cards in PARALLEL** — in a SINGLE message, fire each card's Phase 2.55 trio (Reuse / Quality / Efficiency) scoped to that card's diff (`git diff --name-only` filtered by the card's File Ownership Map). Per-card (not group-aggregate) so findings stay attributable. When all analyses return, **apply fixes per card** (file-disjoint no write conflict), then re-run `npm run lint` and `npx tsc --noEmit` on the worktree ONCE for the whole group. (Concurrency is capped by the platform; passing N cards is safe — excess agents queue.)
2400
+ - **Gate (enumerated, `review_profile`-driven)**: SKIP D.3b for a card whose `review_profile == skip` (trivial / non-implementable card — Simplify is quality-only, so there is no merge-gate coverage to lose, and there is no substantive diff to simplify). Log `simplify: SKIPPED (review_profile=skip)`. For `light`/`balanced`/`deep` cards D.3b runs unchanged. This is the ONLY enumerated skip — never skip D.3b "for time" on a `light`+ card. (Skipped cards are simply omitted from the parallel fan-out.)
2368
2401
 
2369
- 3c. **D.3c — Phase 2.6 E2E-Review (per-card)** — For EACH card in the group, honor the existing Gate table (skip when `features.has_e2e_review: false`, backend-only diff per the diff predicate documented in Phase 2.6, or card type in the Phase 2.6 skip set — `backend`/`api`/`db`/`infra`/`docs`/`chore`/`config`). For cards that pass the gate, invoke `/e2e-review` in programmatic mode with that card's payload. BLOCKING per-card: if `/e2e-review` returns `"blocked"` or `"error"`, surface to the user via the same `AskUserQuestion` documented in Phase 2.6 — do NOT proceed to D.4 with an unresolved card. Skips are logged with the documented gate reason, never with `"time budget"` or similar.
2402
+ 3c. **D.3c — Phase 2.6 E2E-Review (per-card)** — First, evaluate the existing Gate table for EVERY card at once (skip when `features.has_e2e_review: false`, backend-only diff per the diff predicate documented in Phase 2.6, or card type in the Phase 2.6 skip set — `backend`/`api`/`db`/`infra`/`docs`/`chore`/`config`). In practice most cards in a group skip this gate (backend/db/api), so the eligible set is usually 0–1. For the cards that PASS the gate, invoke `/e2e-review` in programmatic mode with that card's payload. Each `/e2e-review` keeps its own isolated state dir (`.baldart/e2e-review/<CARD-ID>/`), so multiple runs do not clobber each other's artifacts.
2403
+ - **Parallel-when-safe**: if **two or more** cards pass the gate, you MAY fan them out in parallel ONLY when they hit **disjoint routes/pages** — the genuinely shared resource is the worktree's single dev-server port, and concurrent Playwright sessions against one server are fine for disjoint routes. If the eligible cards touch overlapping routes (or `/e2e-review` spins its own server and a port clash is possible), run those **sequentially** to avoid a flaky cross-test. Default to parallel for the common disjoint case; fall back to sequential on any contention.
2404
+ - BLOCKING per-card: if `/e2e-review` returns `"blocked"` or `"error"`, surface to the user via the same `AskUserQuestion` documented in Phase 2.6 — do NOT proceed to D.4 with an unresolved card. Skips are logged with the documented gate reason, never with `"time budget"` or similar.
2370
2405
 
2371
2406
  4. **D.4 — QA gate (group)** — Read each card's `review_profile` field (fallback for legacy cards only: compute via the QA Profile Selector, whose criteria are the SSOT in `prd-card-writer.md § Rule C`). Select the HIGHEST profile across all cards in the group (e.g., if one card is BALANCED and another is DEEP, use DEEP) and pass it as the `QA profile` to **qa-sentinel** (invoked once for the group) using the same prompt contract as Phase 3.5 step 22 (`do NOT default to FULL`; balanced → SCOPED, deep → FULL). Note: a mixed group containing any DEEP card runs the group at FULL — the max-profile intentionally overrides the SCOPED tier the BALANCED cards would get individually, because the combined diff must be validated together.
2372
2407
 
2373
2408
  4a. **D.4a — Per-card doc gate (consumes D.2, since v3.35.0; doc-reviewer-applied since v3.40.0)** — Do NOT re-run the doc AUDIT (D.2 already produced per-card-attributed findings). If any card in the group has doc findings, invoke the **doc-reviewer once in write mode** over the group, passing the per-card-attributed findings from D.2, to APPLY all doc fixes in a single pass (it is now alone — code-reviewer is done — so the D.2 parallel-safety constraint no longer applies). Do **NOT** spawn a fix-coder for doc findings: `doc` is owned by `doc-reviewer` (see "Domain-Override Domains"); a code-oriented coder lacks the doc-invariant contract. The only exception is a doc-drift→bug finding rooted in CODE — that follows the D.4b code fix path. (Previously D.4a spawned a fix-coder per card; the audit was already collapsed to a single attributed D.2 pass. D.4b's per-card `/codexreview` also skips its doc-reviewer via the lean contract, so the doc AUDIT runs once per group while the doc FIXES run once per group via doc-reviewer.) **Telemetry** — append one row per applied doc finding to `## Fix Application Log`: `D.4a | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | card=<ID> | finding=<1-line>`. **Phase-8 producer (named counter)** — ALSO record per card the `doc_gaps: found=<N> fixed=<M>` line in that card's `## Completed Cards` entry (same named counter as sequential Phase 3 step 15), so Phase 8 reads `doc_gaps_found`/`doc_gaps_fixed` from a structured field, not a free-form row.
2374
2409
 
2375
- 4b. **D.4b — Pre-Merge Codex Review Gate (per-card, MANDATORY UNCONDITIONAL)** — Invoke `/codexreview` for EACH card in the group, one at a time, BEFORE any commit. This mirrors Phase 3.7 of the sequential path and is non-skippable regardless of file paths or perceived risk. Apply the same fix sub-loop as sequential Phase 3.7 Step C.4: if the consolidated report shows verified BLOCKER/HIGH findings, spawn a fix-coder and — **re-writing the consumed-once lean contract first** — re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (max 2 retries per card; a bare prose mention or a missing card-ID would let the retry review the wrong card). If still BLOCKER/HIGH after retries, ask the user before proceeding to D.5. The D.5 commits MUST NOT happen until every card in the group has a PASS verdict (or explicit user override via `AskUserQuestion`). Log results in the tracker under `## Pre-Merge Codex Review` per card. **Lean + profile (since v3.35.0)**: before each card's `/codexreview`, apply the same Review Profile Selector and write the same `/tmp/codexreview-lean-<CARD-ID>.json` contract as sequential Phase 3.7 Step C — `arch_baseline_path` pointing at `/tmp/arch-baseline-group-<FIRST-CARD-ID>.md`, `skip_doc_reviewer: true`, and `profile: light|full` derived from the card's `review_profile` (`skip`/`light` → `light`, `balanced`/`deep` → `full`; since v3.38.0 read from the card, not recomputed) — with the same **escalation-only** rule: a Step A high-risk trigger on the actual diff promotes `light` → `full`, never the reverse. LIGHT runs `code-reviewer` + FP-gate only — the per-card **Codex adversarial pass is dropped at `light` since v3.38.0** (prerogative of `full` + the final cross-check). The Review Profile Selector is **ACTIVE since v3.37.0** — same status as sequential Phase 3.7 Step C above: `light` is per-card early feedback, and the merge-gate safety is the post-batch **Final-review FULL gate** (a single FULL `/codexreview` over the entire batch diff, which team mode reaches via "Post-batch — same as sequential mode"). To make per-card always-full again, restore `profile = full`; the final full gate stays regardless.
2410
+ 4b. **D.4b — Pre-Merge Codex Review Gate (per-card, profile-gated via D.1.5)** — Invoke `/codexreview` for **EACH card in `FULL_CARDS`** (the partition computed at D.1.5), one at a time, BEFORE any commit. This mirrors Phase 3.7 of the sequential path.
2411
+ - **`LIGHT_CARDS` → SKIP D.4b** (enumerated `review_profile`-driven gate reason, NOT a time skip). A `light`/`skip` 0-trigger card already received its single per-card code review at the **D.2 group pass**, and its Codex-adversarial review is **guaranteed by the unconditional Final-review FULL gate** (Codex over the entire batch diff, post-batch). Running `/codexreview` here at `light` would only re-run `code-reviewer` + FP-gate (Codex is dropped at `light` since v3.38.0) — a pure duplicate of D.2. Log per card: `codex-review: SKIPPED (light, 0 triggers — D.2 group code-review covered + Codex adversarial guaranteed by Final FULL gate)`.
2412
+ - **`FULL_CARDS` → run `/codexreview` as today** (the per-card Codex adversarial + CoVe + FP-gate pass is exactly where the per-card code review for these cards lives — D.2 deliberately did NOT re-review them). The profile passed is always `full` (D.1.5 already resolved it; do NOT recompute). Keep the sequential one-at-a-time loop (avoid N concurrent `/codexreview`, each of which fans out multiple sub-agents → rate-limit risk).
2413
+ Apply the same fix sub-loop as sequential Phase 3.7 Step C.4: if the consolidated report shows verified BLOCKER/HIGH findings, spawn a fix-coder and — **re-writing the consumed-once lean contract first** — re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (max 2 retries per card; a bare prose mention or a missing card-ID would let the retry review the wrong card). If still BLOCKER/HIGH after retries, ask the user before proceeding to D.5. The D.5 commits MUST NOT happen until every card in the group has a PASS verdict (or explicit user override via `AskUserQuestion`). Log results in the tracker under `## Pre-Merge Codex Review` per card. **Lean + profile (since v3.35.0)**: before each card's `/codexreview`, apply the same Review Profile Selector and write the same `/tmp/codexreview-lean-<CARD-ID>.json` contract as sequential Phase 3.7 Step C — `arch_baseline_path` pointing at `/tmp/arch-baseline-group-<FIRST-CARD-ID>.md`, `skip_doc_reviewer: true`, and `profile: full` (every card that reaches D.4b is in `FULL_CARDS` per D.1.5 — `light`/`skip` 0-trigger cards were already partitioned out and skip D.4b entirely; do NOT recompute the profile here). `full` runs the standard pipeline (Codex adversarial + CoVe + FP-gate, minus the duplicate doc-reviewer). The merge-gate safety for the skipped `LIGHT_CARDS` is the post-batch **Final-review FULL gate** (a single FULL `/codexreview` over the entire batch diff, which team mode reaches via "Post-batch — same as sequential mode"). To make D.4b run for every card again (pre-v4.5.0 behavior), drop the D.1.5 partition and iterate over all group cards here at their per-card profile; the final full gate stays regardless.
2376
2414
 
2377
2415
  5. **D.5 — Commit** — One commit per card using explicit staging from the ownership map. **NO stash in worktrees** (stashes are globally shared via `refs/stash` — see Phase 4 WORKTREE COMMIT RULE):
2378
2416
  ```bash
@@ -2400,13 +2438,13 @@ After ALL agents in the group complete successfully:
2400
2438
  Before moving to Step E, the orchestrator MUST verify the tracker contains, for EACH card in the group, ALL of the following entries (or an explicit gate-skip log with the documented reason):
2401
2439
 
2402
2440
  - `## AC Closure Ledger — <CARD-ID>` with `ac-closure: implemented=N | user-approved deferrals=M | follow-up cards created=K`
2403
- - `simplify: <N fixes | clean — 0 fixes>` (per-card from D.3b)
2441
+ - `simplify: <N fixes | clean — 0 fixes | SKIPPED (review_profile=skip)>` (per-card from D.3b)
2404
2442
  - `e2e-review: <status>` (with documented gate-skip reason if SKIP)
2405
2443
  - `doc-review: <status>` (from D.4a)
2406
2444
  - `qa: <profile=... | verdict=...>` (group-level from D.4 — same value cited per card)
2407
- - `codex-review: <verdict>` (per-card from D.4b)
2445
+ - `codex-review: <verdict | SKIPPED (light, 0 triggers — D.2 group code-review covered + Final FULL gate)>` (per-card from D.4b — the SKIPPED form is valid ONLY for cards in `LIGHT_CARDS` per the D.1.5 partition)
2408
2446
 
2409
- A missing entry means a sub-step was skipped. If any entry is missing, do NOT proceed to Step E — return to the missing sub-step and execute it. Documenting "skipped per time budget" or similar is a protocol violation per the top-of-file rigidity clause.
2447
+ A missing entry means a sub-step was skipped. An entry whose value is a **`review_profile`-driven SKIP** with the documented enumerated reason above (D.3b `skip` profile, D.4b `LIGHT_CARDS`) is a VALID, present entry — not a violation. What remains forbidden: a missing entry, or a skip whose reason is `time budget` / `to save tokens` / any model-invented constraint. If any entry is genuinely missing, do NOT proceed to Step E — return to the missing sub-step and execute it. Documenting "skipped per time budget" or similar is a protocol violation per the top-of-file rigidity clause.
2410
2448
 
2411
2449
  #### Step E: Context purge + next group
2412
2450
 
@@ -103,15 +103,21 @@ message. You ask questions, wait for answers, and iterate.
103
103
  structure and `assets/card-template.yml` for child structure.
104
104
  16. **Mockup Intake (MANDATORY).** Subito dopo Kickoff (Step 1) e prima di entrare in
105
105
  Discovery (Step 2), esegui Step 1.6 — chiedi all'utente se ha mockup a disposizione.
106
- Se **sì**: chiedi formato (immagini in chat / path locali — singoli file **o
107
- archivi `.zip`** — / misti), **STOP**, attendi. Sull'arrivo:
106
+ Se **sì**: chiedi formato (immagini in chat / path locali — singoli file, **archivi
107
+ `.zip`**, **o una cartella già estratta di un bundle Claude Design** — / misti),
108
+ **STOP**, attendi. Sull'arrivo:
108
109
  - **chat-images** / file singoli in **local-paths**: copia in `${paths.prd_dir}/<slug>/mockups/`,
109
110
  passa subito all'analisi.
110
- - **`.zip` in local-paths** (es. export "Download as .zip" da Claude Design già
111
- sul disco): esegui Step 1.6.4b `unzip` in scratch dir sotto `mockups/_archive/`,
112
- enumera i candidati visivi, calcola uno score di rilevanza contro lo scope
113
- del Kickoff, presenta la selezione auto-proposta, **STOP**, attendi conferma,
111
+ - **`.zip` OPPURE cartella bundle Claude Design** (firma: `.design-canvas.state.json`
112
+ e/o più `*.html` con slot `id="s<N>"`; es. export "Download as .zip" o la cartella
113
+ scaricata dell'intero workspace): esegui Step 1.6.4b. Prima fa un **passaggio
114
+ strutturale** (canvas-anchor sul titolo feature estrazione slot `id="s<N>"` =
115
+ screen-ID → arricchimento dal manifest `.design-canvas.state.json` → render degli
116
+ artboard canvas-only via [canvas-intake-recipe.md](references/canvas-intake-recipe.md));
117
+ lo **scoring per keyword** resta solo come fallback (bundle generico o canvas
118
+ ambiguo). In entrambi i casi presenta la selezione, **STOP**, attendi conferma,
114
119
  poi materializza solo il sottoinsieme confermato in `${paths.prd_dir}/<slug>/mockups/`.
120
+ Il PRD non aborta mai sull'intake: ogni fallimento ricade sul fallback + STOP gate.
115
121
 
116
122
  Quindi analizza in dettaglio (vedi
117
123
  [discovery-phase.md](references/discovery-phase.md) § "Mockup analysis schema"), popola
@@ -46,6 +46,11 @@
46
46
  {{/if}}
47
47
 
48
48
  {{/each}}
49
+ > **Nota per l'export.** Dai al canvas di questa feature un titolo che contenga il
50
+ > nome feature («{{feature_title}}»), e numera gli artboard in modo coerente con gli
51
+ > ID schermata qui sopra (es. schermata 3.1 → artboard `s31`). Scaricherò il bundle
52
+ > completo del workspace: questo mi permette di ritrovare esattamente le TUE schermate
53
+ > ed evitare ambiguità con le altre feature.
49
54
 
50
55
  {{#if brand_voice}}
51
56
  ## 4. Brand & voice
@@ -88,12 +88,21 @@ mockups.format: pending # chat-images | local-paths | local-archive |
88
88
  mockups.original_paths: [] # user-provided paths, chat://image-N, or archive://<rel-path>
89
89
  mockups.canonical_paths: [] # mockups/<file> after copy
90
90
 
91
- mockups.archive: # populated only when format includes local-archive
92
- local_zip_path: pending # abs path of the .zip the user gave us
93
- kind: pending # zip
94
- extracted_dir: pending # abs path of _archive/extracted/
91
+ mockups.archive: # populated only when format includes local-archive OR a Claude-Design bundle directory
92
+ local_zip_path: pending # abs path of the .zip the user gave us (n/a when kind: directory)
93
+ kind: pending # zip | directory
94
+ extracted_dir: pending # abs path of _archive/extracted/ (zip) or the user dir (directory)
95
95
  truncated: false # true when candidate list capped at 200
96
96
  candidates: [] # see discovery-phase.md § 1.6.4b step b-c
97
+ bundle_kind: pending # zip | directory | claude-design | generic (set by step b2 A0)
98
+ canvas_ref: pending # chosen feature canvas *.html (claude-design only)
99
+ canvas_candidates: [] # [{ file, title, slot_count, score, anchor_confidence }]
100
+ discarded: [] # [{ path, reason }] reason: other-feature-canvas | support-asset | upload | unrelated
101
+
102
+ mockups.canvas_render: # populated only when step b2 A5 ran (claude-design + render)
103
+ served_port: pending # http-server port used for the render
104
+ rendered_slots: [] # [{ slot_id, png }]
105
+ render_status: pending # ok | partial | failed | skipped
97
106
 
98
107
  mockup_analysis.screens: [] # see discovery-phase.md § Mockup analysis schema
99
108
  mockup_analysis.user_flow: []
@@ -104,7 +113,8 @@ mockup_analysis.design_system_alignment:
104
113
  step_3_mode: pending # full | hybrid | skipped | mockup-only
105
114
  design.html_path: pending # path | n/a (mockup-only)
106
115
 
107
- screens_in_scope: [] # [{ name, covered_by_mockups: bool, generated_in_step_3b: bool }]
116
+ screens_in_scope: [] # [{ name, slot_id, screen_id, canvas_ref, covered_by_mockups: bool, generated_in_step_3b: bool }]
117
+ # slot_id/screen_id/canvas_ref set only for claude-design bundles (enable exact join in ui-design-phase Hybrid routing)
108
118
 
109
119
  ui_inventory: [] # populated by Step 3d
110
120
 
@@ -473,14 +473,43 @@ Applied N findings to structured fields (H high, M medium).
473
473
  Manual review needed: [list [MANUAL] items, or "none"].
474
474
  ```
475
475
 
476
+ ### Holistic-audit provenance stamp (since v4.3.0) — consumed by `/new` Step 3d
477
+
478
+ This audit (the 4-agent team + Codex adversarial pass of Step 6.6) IS the holistic cross-card review: Step 6.6d's Codex prompt already covers `files_likely_touched … conflicting across cards`, dependency shadows, implicit ordering, and shared-state mutation across the whole `${CARD_PATHS}` set. To let `/new` AVOID re-running its own pre-flight cross-card Codex check (Step 3d) when nothing has changed since this audit, stamp each audited card with a machine-readable provenance block.
479
+
480
+ **Capture three run-level values ONCE** (same for every card in this audit run):
481
+
482
+ ```bash
483
+ HOLISTIC_AUDITED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
484
+ # Grounding baseline = the trunk commit the cards were authored against.
485
+ # The PRD docs worktree was forked from trunk, so the fork point is that baseline.
486
+ HOLISTIC_AUDITED_COMMIT="$(git merge-base HEAD "${git.trunk_branch}" 2>/dev/null || git rev-parse "${git.trunk_branch}" 2>/dev/null || echo "")"
487
+ # audited_set = sorted card IDs reviewed JOINTLY in this run (the Step 6.6d ${CARD_PATHS} set).
488
+ ```
489
+
490
+ `audited_set` is the **same list for every card** — it records which cards were considered together, so `/new` can verify its batch is a subset of one coherent jointly-audited set (not a mix of cards audited in separate PRD runs).
491
+
492
+ Write under `metadata.holistic_audit` on each card (additive — never overwrite other `metadata` keys):
493
+
494
+ ```yaml
495
+ metadata:
496
+ holistic_audit:
497
+ audited_at: "<HOLISTIC_AUDITED_AT>"
498
+ audited_commit: "<HOLISTIC_AUDITED_COMMIT>" # "" if git unavailable → /new treats as drift (runs Step 3d)
499
+ audited_set: ["FEAT-XXXX-01", "FEAT-XXXX-02", ...] # sorted; the joint cross-card scope
500
+ ```
501
+
502
+ > **Fail-safe contract**: this block is an OPTIMIZATION hint, never a gate. If it is absent, partial, or has an empty `audited_commit`, `/new` Step 3d simply RUNS as before — there is no correctness risk, only a redundant Codex pass. Do NOT block the PRD if the stamp can't be written.
503
+
476
504
  ### Per-card workflow
477
505
 
478
506
  For each card:
479
507
  1. Read persisted report → collect all findings for this card ID.
480
508
  2. Read current card YAML.
481
509
  3. Apply HIGH findings first, then MEDIUM, then write audit trail.
482
- 4. Write updated card YAML.
483
- 5. Re-read to verify edits landed correctly.
510
+ 4. Write the `metadata.holistic_audit` provenance block (see "Holistic-audit provenance stamp" above) using the run-level values captured once.
511
+ 5. Write updated card YAML.
512
+ 6. Re-read to verify edits landed correctly.
484
513
 
485
514
  **Note**: No separate commit here — the validation-phase.md Step 7 handles committing all PRD artifacts together.
486
515
 
@@ -0,0 +1,113 @@
1
+ # Canvas Intake Recipe — rendering Claude-Design artboards to PNG
2
+
3
+ Deterministic recipe for capturing the screens of a Claude Design feature canvas
4
+ that exist **only** as artboards inside the canvas HTML (not exported as standalone
5
+ PNGs). Replaces the ad-hoc "spin up a server, debug CORS, script Playwright" dance
6
+ with a repeatable procedure.
7
+
8
+ ## When this runs
9
+
10
+ Invoked by [discovery-phase.md](discovery-phase.md) § "Step 1.6.4b → b2 → A5",
11
+ once per resolved `canvas_ref`, only for slots lacking a confident pre-render PNG.
12
+ **Never run standalone** — it assumes A0–A4 already chose the canvas and its slot
13
+ set. Outputs feed `mockups.canvas_render` in the state file.
14
+
15
+ ## Why a local HTTP server is mandatory
16
+
17
+ The canvas HTML loads its screens via Babel:
18
+ `<script type="text/babel" src="design-canvas.jsx">` (+ the feature's `*.jsx`
19
+ deps). Opening the file as `file://` makes the browser **block** those `src`
20
+ fetches under the CORS/`file:` origin policy → Babel never executes → the artboard
21
+ DOM is never created → `[data-dc-slot]` / `[data-dc-section]` do not exist. This is
22
+ exactly the `slots=0` failure seen when rendering from `file://`. Serving `root`
23
+ over `http://` lets the jsx load and Babel render.
24
+
25
+ > Slot attributes are **runtime-only**: the static HTML has `<DCSection id="s31">`
26
+ > JSX; the `data-dc-section` / `data-dc-slot` attributes only appear after Babel
27
+ > runs on the served page. Intake-time slot enumeration greps `id="s<N>"` (static);
28
+ > rendering selects `[data-dc-section]` / `[data-dc-slot]` (runtime).
29
+
30
+ ## Selectors
31
+
32
+ | Selector | Granularity | Source JSX |
33
+ |-----------------------|------------------------------|-------------------|
34
+ | `[data-dc-section="s3x"]` | one **screen** (3.x) | `<DCSection id>` |
35
+ | `[data-dc-slot="…"]` | one **artboard / variant** within a screen | `<DCArtboard id>` |
36
+
37
+ Capture at screen granularity (`[data-dc-section]`) by default; drop to
38
+ `[data-dc-slot]` only when a screen needs per-variant PNGs.
39
+
40
+ ## Preflight (degrade, never auto-install)
41
+
42
+ ```bash
43
+ npx playwright --version # must succeed
44
+ npx http-server --version # must succeed
45
+ ```
46
+
47
+ If either is missing: do **NOT** auto-install (this runs in consumer repos —
48
+ respect their toolchain). Emit a one-line hint
49
+ (`npm i -D playwright http-server && npx playwright install chromium`), set
50
+ `mockups.canvas_render.render_status: skipped`, and return control to A5, which
51
+ degrades to pre-render PNGs + `source: live-html`.
52
+
53
+ ## Serve
54
+
55
+ Pick a free port (loop 8080→8090, advancing on `EADDRINUSE`):
56
+
57
+ ```bash
58
+ npx http-server "$root" -p <PORT> -c-1 --silent # run in background; -c-1 disables caching
59
+ ```
60
+
61
+ Record `mockups.canvas_render.served_port: <PORT>`.
62
+
63
+ ## Render loop (Playwright)
64
+
65
+ ```js
66
+ // /tmp/canvas-shoot.mjs — node, run with the project's playwright
67
+ import { chromium } from 'playwright';
68
+ const PORT = process.env.PORT, CANVAS = process.env.CANVAS, OUT = process.env.OUT;
69
+ const SLOTS = JSON.parse(process.env.SLOTS); // [{ slot_id, screen_id }]
70
+ const browser = await chromium.launch();
71
+ const page = await browser.newPage({ deviceScaleFactor: 2 }); // ≥2x export
72
+ await page.goto(`http://127.0.0.1:${PORT}/${encodeURIComponent(CANVAS)}`);
73
+ await page.waitForSelector('[data-dc-section]', { timeout: 15000 }); // confirms Babel ran
74
+ await page.waitForLoadState('networkidle');
75
+ for (const { slot_id, screen_id } of SLOTS) {
76
+ const el = await page.$(`[data-dc-section="${slot_id}"]`) || await page.$(`[data-dc-slot="${slot_id}"]`);
77
+ if (!el) { console.log(`MISS ${slot_id}`); continue; }
78
+ await el.screenshot({ path: `${OUT}/${screen_id.replace('.', '-')}.png` }); // <slug>__3-1.png upstream
79
+ console.log(`OK ${slot_id} -> ${screen_id}`);
80
+ }
81
+ await browser.close();
82
+ ```
83
+
84
+ Name outputs `<slug>__<screen-id>.png` (e.g. `conferma-ordine__3-1.png`). Record
85
+ each in `mockups.canvas_render.rendered_slots[]`.
86
+
87
+ ## Teardown
88
+
89
+ Kill the background `http-server` (by PID). Set `render_status`:
90
+ - `ok` — every requested slot captured.
91
+ - `partial` — some slots captured, some `MISS` (record which).
92
+ - `failed` — `waitForSelector('[data-dc-section]')` timed out (Babel never ran:
93
+ offline CDN, broken jsx). Degrade as in preflight.
94
+
95
+ ## Dedup vs designer pre-renders
96
+
97
+ If a slot has BOTH a designer pre-render (A4) and a freshly rendered PNG, **prefer
98
+ the designer's pre-render** — keep our render only if it differs materially in
99
+ dimensions/content, and then name it `<screen-id>__rendered.png`. Never ship two
100
+ PNGs for the same screen state.
101
+
102
+ ## Failure → degradation summary
103
+
104
+ | Failure | Detection | Degradation |
105
+ |----------------------------------|------------------------------------|--------------------------------------------------|
106
+ | Playwright / http-server missing | preflight version check fails | `skipped` → pre-render + `live-html`, install hint |
107
+ | All ports busy | EADDRINUSE through 8090 | try an ephemeral port; else `skipped` |
108
+ | Babel never runs (CORS/offline) | `[data-dc-section]` wait times out | `failed` → pre-render + `live-html` |
109
+ | Some artboards missing | `MISS` in loop | `partial` — capture the rest, log the misses |
110
+
111
+ Global rule: **never abort the PRD**. Every failure here returns to A5 with a
112
+ degraded `render_status`; intake proceeds with whatever PNGs exist plus live-HTML
113
+ references.
@@ -242,37 +242,49 @@ When the user replies with the mockups:
242
242
  5. Update `mockups.status: provided` (or `partial` only if the user explicitly says
243
243
  "ho solo questi 2 ma servono anche altri").
244
244
 
245
- ### 1.6.4b — Archive ingestion & relevance selection (when `mockups.format` includes `local-archive`)
245
+ ### 1.6.4b — Archive ingestion & relevance selection (when `mockups.format` includes `local-archive`, OR the user gives a Claude-Design bundle directory)
246
246
 
247
247
  Triggered when the user hands a local `.zip` path (typically a Claude Design
248
- "Download as .zip" export already on disk). The flow scans every mockup inside,
249
- scores each against the feature scope from Kickoff, and asks the user to confirm
250
- the auto-selected subset before analysis.
248
+ "Download as .zip" export already on disk) **OR** a path to an already-extracted
249
+ **bundle directory** (the user downloaded the whole Claude Design workspace as a
250
+ folder). The flow resolves the subset of files belonging to THIS feature, then asks
251
+ the user to confirm before analysis.
251
252
 
252
- #### Step a Detect & unpack
253
+ Resolution is two-tier: a **structural pass** (step b2) that exploits Claude
254
+ Design's native bundle structure when present, with the **keyword scorer** (step c)
255
+ as the fallback. The STOP confirmation gate (step d) is identical in both modes.
253
256
 
254
- For each zip path provided:
257
+ #### Step a Detect & accept root
258
+
259
+ For each path provided:
255
260
 
256
261
  ```bash
257
- file "<user-zip-path>"
262
+ file "<user-path>" # only meaningful for files
258
263
  ```
259
264
 
260
- - If `file` does NOT report a Zip archive, treat the path as a regular file and
261
- go back to step 1.6.4 point 2's "Otherwise" branch — no special handling.
262
- - If it IS a zip, record in state:
265
+ - **Directory** (the path is a folder): skip unpacking. Record:
266
+ - `mockups.archive.kind: directory`
267
+ - `mockups.archive.extracted_dir: <the user dir>` (absolute)
268
+ - `mockups.archive.local_zip_path: n/a`
269
+ - **`.zip`** (`file` reports a Zip archive): record:
263
270
  - `mockups.archive.local_zip_path: <user-zip-path>` (absolute)
264
271
  - `mockups.archive.kind: zip`
265
- - Unpack into a scratch directory under the PRD's mockup folder:
272
+ - Unpack into a scratch directory under the PRD's mockup folder:
266
273
 
267
- ```bash
268
- mkdir -p "$WORKTREE_PATH/${paths.prd_dir}/<slug>/mockups/_archive"
269
- unzip -q "<user-zip-path>" \
270
- -d "$WORKTREE_PATH/${paths.prd_dir}/<slug>/mockups/_archive/extracted"
271
- ```
274
+ ```bash
275
+ mkdir -p "$WORKTREE_PATH/${paths.prd_dir}/<slug>/mockups/_archive"
276
+ unzip -q "<user-zip-path>" \
277
+ -d "$WORKTREE_PATH/${paths.prd_dir}/<slug>/mockups/_archive/extracted"
278
+ ```
279
+
280
+ - Record `mockups.archive.extracted_dir` (the `extracted/` path).
281
+ - If `unzip` fails (corrupted/encrypted): STOP, surface the exit code, ask the
282
+ user to verify the zip or provide an alternative.
283
+ - **Regular non-zip file**: treat as a single file — go back to step 1.6.4 point
284
+ 2's "Otherwise" branch, no special handling.
272
285
 
273
- - Record `mockups.archive.extracted_dir`.
274
- - If `unzip` fails (corrupted/encrypted): STOP, surface the exit code, ask the
275
- user to verify the zip or provide an alternative.
286
+ Both directory and zip branches converge on a single `root` =
287
+ `mockups.archive.extracted_dir`. Everything below operates on `root`.
276
288
 
277
289
  #### Step b — Enumerate candidates
278
290
 
@@ -309,8 +321,112 @@ top 200, recording `mockups.archive.truncated: true`.
309
321
 
310
322
  Record the full index in state under `mockups.archive.candidates[]`.
311
323
 
324
+ #### Step b2 — Claude-Design structural resolution (Feature-Subset Resolution)
325
+
326
+ A Claude Design export is **not** a flat pile of files: it carries native
327
+ structure that pins each feature's screens deterministically. When that structure
328
+ is present, resolve the subset *structurally* and skip the keyword scorer. When it
329
+ is absent (or the canvas anchor stays ambiguous), fall through to step c unchanged.
330
+
331
+ > **Global contract.** The structural resolver is an **accelerator**. Any failure
332
+ > at any step (A0–A6) falls through to the keyword scorer (step c) + the STOP gate
333
+ > (step d), which is already proven. **The PRD never aborts on intake.**
334
+
335
+ **A0 — Signature gate.** On `root`, compute:
336
+ - `has_manifest` — `[ -f "$root/.design-canvas.state.json" ]`.
337
+ - `has_canvas_jsx` — a `design-canvas.jsx` exists, or some `*.html` loads it via
338
+ `<script type="text/babel" src=…>` and references `data-dc-section`.
339
+ - `feature_html_count` — number of top-level `*.html` whose **source** contains an
340
+ `id="s<N>"` slot (see A2 — this is the static signal; `data-dc-slot` only exists
341
+ at runtime after Babel renders, so do NOT grep for it here).
342
+
343
+ Set `mockups.archive.bundle_kind`:
344
+ - `claude-design` iff `has_manifest` OR (`has_canvas_jsx` AND `feature_html_count ≥ 1`) → run A1–A6.
345
+ - otherwise `generic` → **skip to step c** (keyword scorer), unchanged.
346
+
347
+ **A1 — Canvas anchor (feature_title → ONE canvas `*.html`).**
348
+ - Candidate set = top-level `*.html` MINUS the denylist of component/system
349
+ canvases: `Design System.html`, `BottomNav*.html`, `*-bundle-src.html`,
350
+ `* · standalone.html`. **Do NOT pre-filter by slot presence** — feature canvases
351
+ in the wild use mixed slot schemes (some `id="s<N>"`, some semantic like
352
+ `id="supplier-edit-a-add"`), and an `id="s<N>"`-only allowlist would wrongly drop
353
+ a feature whose canvas doesn't use that numbering. Slot presence is a *confidence
354
+ signal* (below), not a candidacy gate.
355
+ - For each candidate read its `<title>` + filename, score against `feature_title`
356
+ + scope keywords (reuse step c's case-insensitive whole-word splitter): `<title>`
357
+ match `+50`, filename `+40`, `id="s<N>"` slot-range hint `+10` (boosts canvases
358
+ that expose clean screen-IDs, enabling A2's exact join). Record in
359
+ `mockups.archive.canvas_candidates[]`.
360
+ - **Resolution:**
361
+ - Exactly 1 candidate with a confident margin (top score ≥ 50 AND ≥ 2× the
362
+ runner-up) → `mockups.archive.canvas_ref = that file`, `anchor_confidence: high`.
363
+ - 0 clear, OR ≥ 2 within the 2× margin → **AMBIGUOUS**. Do NOT guess. Use
364
+ `AskUserQuestion` listing the candidate canvases (filename + `<title>` + slot
365
+ count): "Quale canvas è questa feature?". If the user says none/multiple →
366
+ fall through to step c over all candidates (`anchor_confidence: fallback-keyword`).
367
+ Multiple confirmed → A2 unions the slot sets and `canvas_ref` becomes a list.
368
+
369
+ **A2 — Slot extraction (canvas → screen IDs).** From `canvas_ref` source only.
370
+ Anchor on the **section** element that carries the screen title, NOT a loose
371
+ `title=` (sibling attributes like `subtitle="…"` contain the substring `title="`
372
+ and a greedy `id=…title=…` regex grabs the wrong one):
373
+ ```bash
374
+ # DCSection is the per-screen container; its `title` is the screen label.
375
+ grep -oE '<DCSection id="s[0-9]+[a-z]*" title="[^"]*"' "<canvas_ref>"
376
+ # <DCSection id="s31" title="3.1 · Wizard Step 3"
377
+ ```
378
+ → `{ slot_id, screen_id, title_raw }` per section. (If the bundle uses a different
379
+ section tag, anchor on whatever element holds the `id`+dotted-`title` pair.)
380
+ - Derive `screen_id` from the title's dotted prefix (e.g. `"3.1 · …"` → `3.1`).
381
+ Cross-check against the slot number (`s31` → `3.1`). On disagreement, **trust the
382
+ `title=` prefix** and log `slot_id_number_mismatch` (handles non-`s<N>`
383
+ numbering). If neither yields a dotted id, leave `screen_id: null` — that screen
384
+ joins downstream by the fuzzy fallback, never blocks.
385
+
386
+ **A3 — Manifest read (enrich, never gate).** If `has_manifest`, parse
387
+ `.design-canvas.state.json`. For each slot in A2:
388
+ - `sections["s31"].labels` → attach as candidate `states_visible[]` / variant labels.
389
+ - `sections["s31"].title` → prefer as the human label; else keep the A2 `title=`.
390
+ - Manifest is **partial and mixed-keyspace** (some keys are slot-ids, some are
391
+ semantic section names belonging to OTHER canvases). Keys not in this canvas's
392
+ slot set are ignored. A slot missing from the manifest is the **normal case**,
393
+ not an error. Set `screens[].source` = `manifest` (labelled) or `title` (not).
394
+
395
+ **A4 — Sidecar gather.**
396
+ - **JSX deps** — the `<script type="text/babel" src="X.jsx">` list inside
397
+ `canvas_ref` IS the authoritative dependency set (needed for A5 render). Record
398
+ for materialization in step e.
399
+ - **Pre-rendered PNGs** — scan `$root/screenshots/` (and any image dir) for files
400
+ whose name confidently matches a slot number / slug (`ap-31.png` → `s31`). On a
401
+ confident match set `screens[].prerender_png` + `source: prerender`. Naming is
402
+ ad-hoc (`modale__valido.png`) so matching is **best-effort only**; unmatched
403
+ images are parked, never force-assigned.
404
+
405
+ **A5 — Render canvas-only artboards.** Screens that exist only inside the canvas
406
+ (no confident `prerender_png`) must be rendered. Invoke
407
+ [canvas-intake-recipe.md](canvas-intake-recipe.md) (http-server + Playwright over
408
+ `[data-dc-section="s<N>"]`). Output `mockups/<slug>__<screen-id>.png`, record under
409
+ `mockups.canvas_render`. If render is impossible (Playwright/server unavailable),
410
+ **degrade**: keep pre-render PNGs, mark remaining slots `source: live-html`
411
+ referencing the canvas HTML directly, set `render_status: failed|skipped`, and
412
+ continue — never abort.
413
+
414
+ **A6 — Discard-and-log the rest.** Everything in `root` NOT in { `canvas_ref`, its
415
+ jsx deps, matched `screenshots/*`, shared assets (`*tokens*.css`, `*icons*.jsx`,
416
+ `*shell*.jsx`) } is out-of-feature: the other feature canvases, their jsx,
417
+ `frames/`, `uploads/`, `docs/`. Record in `mockups.archive.discarded[]` with a
418
+ one-word `reason`. Shown for transparency at the STOP gate (step d) but **not
419
+ copied**.
420
+
421
+ After A6, build the screen-centric selection and go to **step d** (its structural
422
+ variant). Skip step c entirely — it is the fallback path only.
423
+
312
424
  #### Step c — Score against feature scope
313
425
 
426
+ > **Fallback path.** Runs ONLY when step b2 set `bundle_kind: generic`, or the
427
+ > canvas anchor stayed unresolved (`anchor_confidence: fallback-keyword`). For a
428
+ > resolved Claude-Design bundle, skip directly to step d.
429
+
314
430
  Build the scope vector from the Kickoff state (Step 1):
315
431
  - `feature_title` (from `## Feature Description`).
316
432
  - `slug`.
@@ -340,8 +456,34 @@ Record per-candidate: `score`, `matched_keywords[]`, `selected: bool`.
340
456
 
341
457
  #### Step d — Confirmation gate (STOP)
342
458
 
343
- Present a compact table to the user — selected candidates first, then up to 10
344
- discarded with the highest scores, then a footer count for "altri N scartati":
459
+ Two table variants, same STOP/adjust/re-present loop:
460
+
461
+ **Structural variant** (step b2 resolved a `canvas_ref`). Present **screen-centric**
462
+ — one row per resolved screen, plus the discarded-canvas summary:
463
+
464
+ ```
465
+ Canvas individuato: «Approvazione Ordini.html» (feature «<feature_title>») — <K> schermate.
466
+
467
+ **Schermate risolte** (slot → screen-ID):
468
+ | # | Slot | Screen | Label | Sorgente |
469
+ |---|------|--------|------------------------------------|-----------|
470
+ | 1 | s31 | 3.1 | Wizard Step 3 · CTA approvazione | manifest |
471
+ | 2 | s32 | 3.2 | Elenco approver | rendered |
472
+
473
+
474
+ **Scartati** (altre feature / asset di supporto):
475
+ | File / dir | Motivo |
476
+ |-------------------------------|-----------------------|
477
+ | Budget Pianificazione.html | other-feature-canvas |
478
+ | frames/, uploads/ | unrelated / upload |
479
+
480
+ Procedo all'analisi delle <K> schermate? Oppure indicami modifiche
481
+ (es. "togli s37", "il canvas giusto è Budget Pianificazione").
482
+ ```
483
+
484
+ **Keyword-fallback variant** (step c ran). Present file-centric — selected
485
+ candidates first, then up to 10 discarded with the highest scores, then a footer
486
+ count for "altri N scartati":
345
487
 
346
488
  ```
347
489
  Bundle scaricato e analizzato: <N> file candidati, <K> selezionati come rilevanti.
@@ -366,22 +508,37 @@ Procedo all'analisi visiva dei <K> selezionati? Oppure indicami modifiche
366
508
  ```
367
509
 
368
510
  **STOP.** Wait for explicit confirmation or an adjustment list. On adjustment,
369
- recompute `mockups.archive.candidates[].selected`, then re-present and STOP again
370
- until the user confirms.
511
+ recompute the selection (`mockups.archive.candidates[].selected` in keyword mode,
512
+ the per-slot row set in structural mode), then re-present and STOP again until the
513
+ user confirms.
371
514
 
372
515
  #### Step e — Materialize the confirmed subset
373
516
 
374
517
  Copy ONLY the confirmed selection into `${paths.prd_dir}/<slug>/mockups/`,
375
- preserving relative directory structure under the extracted root so that
376
- relative asset references (CSS, images) keep working. On filename collision,
377
- use the `<name>-2.<ext>` rule from 1.6.4 point 2.
378
-
379
- Record `mockups.original_paths[]` as `archive://<relative-path-in-zip>` and
380
- `mockups.canonical_paths[]` as the new path under `mockups/`. The scratch
381
- `_archive/extracted/` directory is kept for the duration of the PRD session
382
- (may be needed to resolve `<link>` / `<img src>` references during analysis)
383
- and cleaned up at Step 7 (Commit & Merge) — add a `.gitignore` entry for
384
- `mockups/_archive/` if not already present.
518
+ preserving relative directory structure under `root` so that relative asset
519
+ references (CSS, images) keep working. On filename collision, use the
520
+ `<name>-2.<ext>` rule from 1.6.4 point 2.
521
+
522
+ **Structural mode also copies the render dependencies**, so the canvas stays
523
+ renderable/analyzable inside the PRD folder:
524
+ - `canvas_ref` itself + every jsx dep from A4 + shared assets (`*tokens*.css`,
525
+ `*icons*.jsx`, `*shell*.jsx`), preserving relative paths so `<script src>` /
526
+ `<link>` keep resolving.
527
+ - the rendered PNGs (`mockups.canvas_render.rendered_slots[].png`) and matched
528
+ pre-render PNGs.
529
+ - **Write `screens_in_scope[]`** here — one row per resolved screen with `name`,
530
+ `slot_id`, `screen_id`, `canvas_ref`. This is what lets the Step 3 (UI Design)
531
+ Hybrid routing do an **exact join** instead of a fuzzy substring match (see
532
+ [ui-design-phase.md](ui-design-phase.md) § "Build the per-screen routing table").
533
+
534
+ Record `mockups.original_paths[]` as `archive://<relative-path-in-zip>` (zip) or
535
+ `archive://<relative-path-in-root>` (directory) and
536
+ `mockups.canonical_paths[]` as the new path under `mockups/`. When `kind: zip`,
537
+ the scratch `_archive/extracted/` directory is kept for the duration of the PRD
538
+ session (may be needed to resolve `<link>` / `<img src>` references during
539
+ analysis) and cleaned up at Step 7 (Commit & Merge) — add a `.gitignore` entry for
540
+ `mockups/_archive/` if not already present. When `kind: directory`, `root` is the
541
+ user's own folder outside the worktree — never modify or clean it; only read.
385
542
 
386
543
  Then continue to Step 1.6.5 with the selected subset.
387
544
 
@@ -802,12 +959,23 @@ mockups:
802
959
  - <user-provided path, chat://image-N, or archive://<rel-path-in-zip>>
803
960
  canonical_paths:
804
961
  - mockups/<file>.png # only for local-paths and local-archive
805
- archive: # only when format includes local-archive
806
- local_zip_path: <abs path to the user-provided .zip>
807
- kind: zip
808
- extracted_dir: <abs path to _archive/extracted/>
962
+ archive: # only when format includes local-archive OR a Claude-Design bundle dir
963
+ local_zip_path: <abs path to the user-provided .zip> # n/a when kind: directory
964
+ kind: zip | directory
965
+ extracted_dir: <abs path to _archive/extracted/ (zip) or the user dir (directory)>
809
966
  truncated: false # true when candidate list capped at 200
810
- candidates: # full enumerated list (post-filter)
967
+ bundle_kind: zip | directory | claude-design | generic # set by step b2 A0
968
+ canvas_ref: <chosen feature canvas *.html> # claude-design only
969
+ canvas_candidates: # claude-design only
970
+ - file: <*.html basename>
971
+ title: <HTML <title>>
972
+ slot_count: <int> # count of id="s<N>" slots
973
+ score: <0-100>
974
+ anchor_confidence: high | user-resolved | fallback-keyword
975
+ discarded: # claude-design only — out-of-feature, NOT copied
976
+ - path: <rel path under root>
977
+ reason: other-feature-canvas | support-asset | upload | unrelated
978
+ candidates: # full enumerated list (post-filter) — keyword-fallback path
811
979
  - path: <rel path under extracted/>
812
980
  filename: <basename>
813
981
  ext: html | png | jpg | svg | …
@@ -817,10 +985,20 @@ mockups:
817
985
  score: <0-100>
818
986
  matched_keywords: [<keyword>, …]
819
987
  selected: <bool> # true = part of the confirmed subset
988
+ canvas_render: # only when step b2 A5 rendered canvas-only artboards
989
+ served_port: <int>
990
+ rendered_slots:
991
+ - slot_id: s31
992
+ png: mockups/<slug>__3-1.png
993
+ render_status: ok | partial | failed | skipped
820
994
  mockup_analysis:
821
995
  screens:
822
996
  - name: <inferred name, e.g. "Lista ordini">
997
+ slot_id: <s31, claude-design only — else omit>
998
+ screen_id: <"3.1" dotted, claude-design only — joins handoff prompt § 3 — else omit>
823
999
  mockup_ref: <canonical_path or chat://image-N>
1000
+ source: manifest | title | rendered | prerender # claude-design only — provenance of the label/image
1001
+ prerender_png: <mockups/<file>.png — only when a designer pre-render matched this slot>
824
1002
  purpose: <one line — what this screen lets the user do>
825
1003
  components:
826
1004
  - <component name from ${paths.design_system}/INDEX.md if matched, else free-form>
@@ -180,11 +180,19 @@ to the design system, approve, and inventory. Generate ONLY the screens not cove
180
180
  - ISA `NAVIGATION` and `DASHBOARD` touchpoints that imply landing screens.
181
181
  - The `mockup_analysis.screens[]` list itself (in case the mockups include
182
182
  screens the user stories didn't make explicit).
183
- 2. For each entry, set `covered_by_mockups: true` iff the screen matches a
184
- `mockup_analysis.screens[].name` (case-insensitive substring match is fine
185
- when ambiguous, ASK the user "questa schermata `<X>` corrisponde al mockup
186
- `<Y>`?" and STOP).
187
- 3. Persist `screens_in_scope[]` in the state file `## UI Design` section.
183
+ 2. For each entry, set `covered_by_mockups: true`. Match **exact-join-first**:
184
+ - **Exact join (Claude-Design bundles).** When the entry has a `slot_id` /
185
+ `screen_id` (populated by Step 1.6.4b structural resolution, see
186
+ [discovery-phase.md](discovery-phase.md) § "Step b2") AND a
187
+ `mockup_analysis.screens[]` entry shares the same `slot_id`/`screen_id`, join
188
+ them **deterministically** — no ambiguity, no STOP. This is the common case
189
+ for handoff bundles and skips the fuzzy guesswork entirely.
190
+ - **Fuzzy fallback.** Only when `slot_id` is absent (chat images, single files,
191
+ `generic` bundles) fall back to case-insensitive substring match on
192
+ `mockup_analysis.screens[].name` — and keep the STOP-and-ask on ambiguity:
193
+ "questa schermata `<X>` corrisponde al mockup `<Y>`?".
194
+ 3. Persist `screens_in_scope[]` (with `slot_id`/`screen_id`/`canvas_ref` when
195
+ present) in the state file `## UI Design` section.
188
196
 
189
197
  ### Per-screen execution
190
198
 
@@ -130,3 +130,13 @@ metadata:
130
130
  # Auto-computed by prd-card-writer for PRD-generated cards; set it by hand here
131
131
  # when authoring a card manually (omit to let /new fall back to runtime compute).
132
132
  review_profile: "" # skip | light | balanced | deep
133
+ # holistic_audit — provenance of the /prd Step 6.6 cross-model holistic audit
134
+ # (4-agent team + Codex adversarial pass). WRITTEN BY the audit phase, not at
135
+ # authoring — leave absent when creating a card by hand. /new reads it to decide
136
+ # whether its pre-flight cross-card check (Step 3d) can be skipped because the
137
+ # batch was already audited jointly AND nothing changed since. Fail-safe: if
138
+ # absent/partial, /new just runs Step 3d (no correctness risk).
139
+ # holistic_audit:
140
+ # audited_at: "" # ISO-8601-UTC of the audit
141
+ # audited_commit: "" # trunk commit the cards were grounded against
142
+ # audited_set: [] # card IDs audited jointly (the cross-card scope)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.2.2",
3
+ "version": "4.5.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"