baldart 4.3.0 → 4.6.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,46 @@ 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.6.0] - 2026-06-03
9
+
10
+ **`/new` ora ha una corsia "trivial" per le card minime: niente review di codice su un diff che non contiene codice.** Dopo il de-dup v4.5.0 (profondità scalata su `review_profile`), restava un pavimento alto: una card `skip`-profile ma implementabile — un fix di copy in un `.md`, un update di una riga di `ssot-registry`, un bump di config non-API — passava comunque per codebase-architect + plan-auditor + code-reviewer + AC-closure + QA + doc + Final. Una nuova **fast-lane** scatta quando `IS_TRIVIAL(card)` = `review_profile == skip` **AND** 0 trigger del detector Step-A **AND** diff **non-source** (check deterministico per estensione, riusando il predicato "backend-only" di Phase 2.6 — solo `.md`/`.json`/`.yml`/`.txt`/`.csv`). Una card trivial esegue solo il tail minimo sicuro: `coder → gate meccanici inline (markdownlint/lint/build) → AC-closure → doc-review → commit`, saltando **codebase-architect + plan-auditor + code-reviewer + simplify + Codex + la suite di test QA**. Vale in **entrambe le modalità** (sequential: gate in Phase 1 step 2c + 2.55/3.5/3.7; team: sotto-classe `TRIVIAL_CARDS ⊆ LIGHT_CARDS` esclusa dallo scope del code-reviewer a D.2). **MINOR** (nuova capability per-card; nessuna chiave `baldart.config.yml` — si appoggia su `review_profile` già autorato in PRD).
11
+
12
+ > **Why.** `review_profile: skip` NON è riservato agli epic: `prd-card-writer.md § Rule C` lo assegna deterministicamente a card `docs`/`chore`/`config` o puramente cosmetiche (typo/rename/copy) senza logica. È quindi un segnale autorato in PRD, persistito nello YAML, su cui ancorare la corsia **senza inventare una soglia a righe** (coerente col principio "data-driven sopra threshold arbitrarie"). Il classificatore è un AND a 3 condizioni con **guard fail-safe**: un solo file source nel diff, un trigger Step-A, o un profilo non-`skip` → `IS_TRIVIAL=false` e la card torna al percorso normale. La triviality può solo **togliere review a una card che non ha codice da revisionare**, mai il contrario — una `skip` mis-autorata su codice, o una il cui testo cita `auth`/`payment`, viene sempre ricatturata. Gli invarianti restano: **AC-closure** (scope discipline) e **doc-review** non si saltano mai, e la **Final Review F.3** copre incondizionatamente ogni card trivial nel gate cross-batch prima del merge (stessa rete su cui si appoggia già il percorso `light`). Tutti gli skip sono gate-reason enumerati deterministici, mai `time budget`.
13
+
14
+ ### Changed
15
+
16
+ - **`framework/.claude/skills/new/SKILL.md`** — (a) nuova sotto-sezione SSOT "Trivial-card fast-lane" (classificatore `IS_TRIVIAL` a 3 condizioni AND + guard + tabella di cosa salta/resta). (b) Clausola in testa: `IS_TRIVIAL` aggiunto all'elenco dei gate-reason enumerati. (c) Sequential Phase 1: nuovo step **2c** che salta codebase-architect + plan-auditor (step 3/3a/4) per card provvisoriamente trivial; il briefing del coder degrada a card-spec + target file. (d) Phase 2.55/3.5/3.7: gate `IS_TRIVIAL` ri-confermato sul diff reale → skip Simplify + QA-suite + Codex, gate meccanici inline; carve-out esplicito nell'"UNCONDITIONAL" di Phase 3.7. (e) Team D.1.5: sotto-classe `TRIVIAL_CARDS ⊆ LIGHT_CARDS`; D.2 esclude le trivial dallo scope del code-reviewer. (f) Step D coverage assertion: skip trivial come entry valide documentate. Non toccati: `codexreview.md`, `prd-card-writer.md`, AC-closure, doc-review, Final Review F.3, percorso light/full di v4.5.0.
17
+
18
+ ## [4.5.0] - 2026-06-03
19
+
20
+ **`/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).
21
+
22
+ > **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.
23
+
24
+ ### Changed
25
+
26
+ - **`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.
27
+
28
+ > **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.
29
+
30
+ ## [4.4.0] - 2026-06-03
31
+
32
+ **`/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).
33
+
34
+ > **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.
35
+
36
+ ### Added
37
+
38
+ - **`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.
39
+
40
+ ### Changed
41
+
42
+ - **`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.
43
+ - **`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.
44
+ - **`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.
45
+ - **`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`.
46
+ - **`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.
47
+
8
48
  ## [4.3.0] - 2026-06-03
9
49
 
10
50
  **`/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).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.3.0
1
+ 4.6.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`, **the card's `review_profile`-driven review depth — see next paragraph — and the `IS_TRIVIAL` fast-lane (`review_profile=skip` + non-source diff + 0 high-risk triggers), defined once in § "Trivial-card fast-lane"**). 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
 
@@ -386,6 +388,63 @@ disagree.
386
388
 
387
389
  ---
388
390
 
391
+ ## Trivial-card fast-lane (since v4.6.0 — SSOT; phases below CITE this, never redefine it)
392
+
393
+ Some cards are genuinely too small to justify the review machine: a `docs`/`chore`/`config`
394
+ card, a copy fix in a `.md`, a one-line `ssot-registry` update, a non-API `.yml` bump. Running
395
+ codebase-architect + plan-auditor + code-reviewer + Codex + the QA test suite on a diff that
396
+ contains **no source code** is pure waste — there is nothing for those agents to review. The
397
+ fast-lane routes such cards through the minimal safe tail. It is a **per-card** property
398
+ (applies in BOTH sequential and team mode), and it is the polar opposite of a `time budget`
399
+ skip: it is gated entirely on deterministic, PRD-authored signals.
400
+
401
+ ### The classifier — `IS_TRIVIAL(card)` (ALL three must hold — AND)
402
+
403
+ 1. **`review_profile == skip`** — read verbatim from the card YAML. Per `prd-card-writer.md § Rule C`,
404
+ `skip` is assigned to `docs`/`chore`/`config` cards, all-non-logic-file cards, and pure-cosmetic
405
+ cards (typo/rename/copy/wording/style). This is NOT the epic guard (an epic is never implemented;
406
+ a trivial card IS implemented, just with a minimal tail). **AND**
407
+ 2. **Zero Step-A high-risk triggers** — run the Phase 3.7 **Step A** detector (the same bash+grep:
408
+ high-risk module from `paths.high_risk_modules` / dead-code resurrection / reference-aliasing) on
409
+ this card's diff. Any trigger ⇒ NOT trivial. **AND**
410
+ 3. **Non-source diff** — `git diff --name-only` (working-tree pre-commit in sequential; `$TRUNK...HEAD`
411
+ post-commit in team mode) contains **NO file matching the source-extension predicate**
412
+ (reuse/extend the Phase 2.6 backend-only predicate):
413
+ ```bash
414
+ SRC_RE='\.(tsx?|jsx?|mjs|cjs|vue|svelte|css|scss|py|go|rs|java|rb|php|sql|prisma)$'
415
+ if echo "$CHANGED" | grep -qE "$SRC_RE"; then IS_TRIVIAL=0; else IS_TRIVIAL=1; fi
416
+ ```
417
+ Only `.md` / `.json` / `.yml` / `.yaml` / `.txt` / `.csv` and doc paths qualify.
418
+
419
+ **Fail-safe guard (degrade toward MORE review, never less)**: if ANY condition fails — a single
420
+ source file in the diff, one Step-A trigger, or a non-`skip` profile — `IS_TRIVIAL=false` and the
421
+ card follows its normal path (in team mode that is `LIGHT_CARDS`: D.2 code-reviewer + Final; in
422
+ sequential, the full per-card pipeline). A mis-authored `skip` on a code card, or a `skip` card
423
+ whose text mentions `auth`/`payment`/etc., is therefore always caught. The classifier can only ever
424
+ *remove* review from a card that demonstrably has no code to review.
425
+
426
+ ### What a trivial card runs vs skips
427
+
428
+ | Step | Trivial card |
429
+ |------|--------------|
430
+ | codebase-architect grounding (Phase 1 step 3) | **SKIP** (non-source, self-contained) |
431
+ | plan-auditor grounding (Phase 1 step 4) | **SKIP** (depends on architect findings) |
432
+ | coder (Phase 2) | **RUN** — briefing degrades to card-spec + target file from `files_likely_touched` (no architect section) |
433
+ | mechanical gates | **RUN INLINE** — `markdownlint` on changed `.md`, `lint` on lintable files, `build` as a sanity check (NO qa-sentinel spawn, NO test suite) |
434
+ | Phase 2.5b / D.3a AC-Closure | **RUN** (scope discipline, inline, cheap — never skipped) |
435
+ | Phase 2.55 / D.3b Simplify | **SKIP** |
436
+ | Phase 2.6 / D.3c E2E | **SKIP** (already gated by `docs`/`chore`/`config` type) |
437
+ | Phase 3 / D.4a doc-review | **RUN** (the relevant reviewer for docs/ssot) |
438
+ | Phase 3.5 / D.4 QA test suite | **SKIP** (covered by inline mechanical gates above) |
439
+ | Phase 3.7 / D.4b Codex | **SKIP** |
440
+ | commit + backlog | **RUN** |
441
+ | **Final Review F.3** | **RUNS (unconditional net)** — covers every trivial card in the batch-wide gate before merge |
442
+
443
+ Every skip above is logged with the enumerated reason `trivial: review_profile=skip + non-source
444
+ diff + 0 triggers` — a deterministic, card-authored gate reason. Never `time budget`.
445
+
446
+ ---
447
+
389
448
  ## Agent Routing (Phase 2 dispatcher)
390
449
 
391
450
  Phase 2 reads the card's `owner_agent` field and dispatches to the matching
@@ -435,7 +494,8 @@ For each card, execute these phases in order:
435
494
  - If NOT `DONE` → HALT: log in `## Issues & Flags` and ask the user: "Card <CARD-ID> depends on <DEP-ID> which is `<status>`. Proceed anyway, or wait?" Do not start implementation until the user responds explicitly. **The card status is NOT written until this gate passes** — so a HALT leaves the card untouched (no stale `IN_PROGRESS` for the next run to mis-read).
436
495
  - If `DONE` (or the user chose "proceed anyway") → continue.
437
496
  2b. **Claim** — only now set the card status to `IN_PROGRESS` and assign yourself.
438
- 3. Invoke the **codebase-architect** agent (MUST per AGENTS.md) to understand the relevant codebase area, existing patterns, and architecture before any implementation. When `features.has_lsp_layer: true`, the architect uses LSP find-references for identifier-shaped lookups this needs NO handoff from the orchestrator: the architect reads `features.has_lsp_layer` from `baldart.config.yml` directly (the flag is ambient) per `agents/code-search-protocol.md`. The orchestrator does NOT propagate the flag. (Earlier doc versions numbered this step 4; the step that read project-status BEFORE the architect was removed because it persisted pre-analysis context see step 3a.)
497
+ 2c. **Trivial-card classification (BLOCKING gate for steps 3–4)** evaluate `IS_TRIVIAL(card)` per § "Trivial-card fast-lane". Note: condition 3 (non-source diff) cannot be fully evaluated until the coder has produced the diff, so at this point compute the **provisional** trivial flag from conditions 1+2 only (`review_profile == skip` AND no Step-A trigger sourced from the card YAML text + `files_likely_touched` extensions — if EVERY path in `files_likely_touched` is non-source, condition 3 is provisionally satisfied). If provisionally trivial → **SKIP steps 3, 3a, and 4** (architecture grounding); log `trivial: architecture grounding skipped (review_profile=skip + non-source files_likely_touched + 0 triggers)` and jump to Phase 2. Re-confirm `IS_TRIVIAL` on the ACTUAL committed diff at the review gates (Phase 2.55/3.5/3.7); if the coder unexpectedly touched a source file, the guard flips the card back onto the normal review path there. If NOT provisionally trivial run steps 3, 3a, 4 as normal.
498
+ 3. **(skip when provisionally trivial — see 2c)** Invoke the **codebase-architect** agent (MUST per AGENTS.md) to understand the relevant codebase area, existing patterns, and architecture before any implementation. When `features.has_lsp_layer: true`, the architect uses LSP find-references for identifier-shaped lookups — this needs NO handoff from the orchestrator: the architect reads `features.has_lsp_layer` from `baldart.config.yml` directly (the flag is ambient) per `agents/code-search-protocol.md`. The orchestrator does NOT propagate the flag. (Earlier doc versions numbered this step 4; the step that read project-status BEFORE the architect was removed because it persisted pre-analysis context — see step 3a.)
439
499
  3a. Update `${paths.references_dir}/project-status.md` Active Code Context (skip when the file does not exist in the project) — do this AFTER the codebase-architect run (step 3) so the "Active Code Context" reflects the architect's findings (which files are actually in scope), not just the card YAML's `files_likely_touched`. Writing it before the architect run would persist pre-analysis claims that downstream agents (e.g. a parallel card) would then read as truth.
440
500
  4. **Plan-auditor grounding check** — invoke the **plan-auditor** agent in **QUICK mode** (pass `mode: QUICK`; its contract runs only the grounding checks, and because the codebase-architect findings are passed in below it does NOT re-spawn codebase-architect for this narrow check):
441
501
  ```
@@ -1088,6 +1148,8 @@ Domains NOT listed here remain governed by the per-phase rules of the correspond
1088
1148
 
1089
1149
  ### Phase 2.55 — Simplify (code cleanup before review)
1090
1150
 
1151
+ > **Trivial fast-lane gate**: re-confirm `IS_TRIVIAL` on the ACTUAL committed diff (§ "Trivial-card fast-lane" — all 3 conditions, now including the real non-source diff check). If trivial → **SKIP this phase** AND Phase 3.5 QA + Phase 3.7 Codex; instead run the **inline mechanical gates** (`markdownlint` on changed `.md`, `lint` on lintable changed files, `build` as a sanity check — no qa-sentinel, no test suite), then proceed to Phase 3 (doc review, which DOES run) and Phase 4 (commit). Log `simplify/qa/codex: SKIPPED (trivial — non-source diff)`. If the actual diff turned out to contain a source file → NOT trivial → run this phase and the normal review path. AC-Closure (Phase 2.5b) already ran and is never skipped.
1152
+
1091
1153
  After completeness is verified, clean up the implementation before it reaches reviewers and E2E tests. This phase launches three parallel agents on the card's diff, then applies fixes directly.
1092
1154
 
1093
1155
  **Step-by-step**:
@@ -1326,7 +1388,9 @@ skill's Phase 1 falls back to deriving Gherkin scenarios from
1326
1388
 
1327
1389
  ### Phase 3.7 — Pre-Merge Codex Review Gate (MANDATORY — UNCONDITIONAL)
1328
1390
 
1329
- **This gate is NON-SKIPPABLE and runs for EVERY card before the Phase 4 commit, no matter what.** A per-card `/codexreview` MUST run BEFORE the Phase 4 commit, regardless of file paths, card type, or perceived risk. The historical conditional High-Risk Path detector is preserved below — but only as a **signal-logging step** to record *which* triggers matched in the tracker. It NEVER suppresses the `/codexreview` invocation. Even if zero triggers match, `/codexreview` runs.
1391
+ **This gate runs for EVERY card with code to review before the Phase 4 commit.** A per-card `/codexreview` MUST run BEFORE the Phase 4 commit, regardless of file paths or perceived risk. The historical conditional High-Risk Path detector is preserved below — but only as a **signal-logging step** to record *which* triggers matched in the tracker. It NEVER suppresses the `/codexreview` invocation. Even if zero triggers match, `/codexreview` runs.
1392
+
1393
+ > **Single carve-out — `IS_TRIVIAL` fast-lane (since v4.6.0)**: a card that is `IS_TRIVIAL` on its ACTUAL committed diff (`review_profile=skip` + non-source diff + 0 Step-A triggers — § "Trivial-card fast-lane") **skips this gate**, because there is no code for Codex to review. Its adversarial coverage is carried by the SAME unconditional **Final-review FULL gate** the `light` path already relies on (it runs Codex over the entire batch diff, including every trivial card's files, before merge). This is NOT a `time budget` skip — it is the deterministic `IS_TRIVIAL` gate reason, and the guard means a single source file in the diff flips the card back onto this gate. Log `codex-review: SKIPPED (trivial — non-source diff; covered by Final FULL gate)`.
1330
1394
 
1331
1395
  **Rationale**: leaving the gate conditional caused it to be silently skipped on the majority of cards, defeating the AGENTS.md "MUST run BEFORE merge" requirement. The gate itself is unconditional — what varies is its DEPTH (`light` vs `full`). The protection against a missed blocker on a "low-risk" path (BUG-0530-class regressions) is carried by two mechanisms, not by an always-on per-card Codex pass: (1) the Step A detector escalates any risky diff to `full` per-card, and (2) the unconditional **Final-review FULL gate** runs Codex adversarial over the entire batch diff before merge. So `light` can safely run `code-reviewer` + FP-gate only (since v3.38.0) without weakening the merge gate.
1332
1396
 
@@ -2356,6 +2420,8 @@ Agent tool call:
2356
2420
 
2357
2421
  The orchestrator waits for ALL background agents in the group to complete. It will be notified automatically as each finishes (`run_in_background`).
2358
2422
 
2423
+ > **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.
2424
+
2359
2425
  For each completed agent:
2360
2426
  1. Read the completion report from the agent's output.
2361
2427
  2. Log to tracker: card ID, status, files changed, build/lint status.
@@ -2375,21 +2441,39 @@ After ALL agents in the group complete successfully:
2375
2441
 
2376
2442
  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.
2377
2443
 
2378
- 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.
2444
+ 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:
2445
+ - **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).
2446
+ - **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.
2447
+ - **Partition** the group into two lists and log them in the tracker under `## Team Mode`:
2448
+ - `LIGHT_CARDS` = cards whose effective profile is `light` or `skip` **AND** zero Step-A triggers.
2449
+ - `FULL_CARDS` = all others (effective profile `balanced`/`deep`, OR any Step-A trigger).
2450
+ - **Sub-classify `TRIVIAL_CARDS` ⊆ `LIGHT_CARDS`** = cards that are `IS_TRIVIAL` on the committed diff (§ "Trivial-card fast-lane": `review_profile == skip` AND 0 Step-A triggers AND **non-source diff**). These are the LIGHT cards that have nothing for `code-reviewer` to review. (A `skip` card whose diff DID touch a source file is in `LIGHT_CARDS` but NOT `TRIVIAL_CARDS` — the guard keeps it on the code-review path.)
2451
+ - Log: `## D.1.5 Effective Profiles\n<CARD-ID>: profile=<floor> triggers=<n> diff=<source|non-source> → effective=<light|full> (<LIGHT_CARDS|FULL_CARDS>)<, TRIVIAL if applicable>` per card. This single computation is the SSOT for D.2 (code-reviewer scoping), D.3b/D.3c (already skipped for trivial), and D.4b (inclusion) — do NOT recompute it downstream.
2452
+
2453
+ 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):
2454
+ - **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.
2455
+ - **code-reviewer — scoped to the diff-union of `LIGHT_CARDS` MINUS `TRIVIAL_CARDS`** (run it in parallel with the doc-reviewer over those files). These light non-trivial cards skip D.4b, so D.2 is their single per-card code review (the cross-batch Codex pass at Final covers them adversarially). **`TRIVIAL_CARDS` are excluded from the code-reviewer scope** — their diff is non-source, so there is nothing to code-review (doc-reviewer still covers them via the whole-group pass above; the Final FULL gate covers them adversarially). **If `LIGHT_CARDS \ TRIVIAL_CARDS` is empty → do NOT spawn code-reviewer at D.2** (every remaining card is either `FULL_CARDS` — deeper review at D.4b — or `TRIVIAL_CARDS` — nothing to review). Log the deterministic gate reason: `D.2 code-reviewer: scope=[<ids>] | excluded-trivial=[<ids>] | SKIPPED (no code-bearing light cards)`.
2456
+ - **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`.
2379
2457
 
2380
2458
  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.
2381
2459
 
2382
2460
  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.
2383
2461
 
2384
- 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.
2462
+ 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.)
2463
+ - **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.)
2385
2464
 
2386
- 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.
2465
+ 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.
2466
+ - **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.
2467
+ - 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.
2387
2468
 
2388
2469
  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.
2389
2470
 
2390
2471
  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.
2391
2472
 
2392
- 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.
2473
+ 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.
2474
+ - **`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)`.
2475
+ - **`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).
2476
+ 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.
2393
2477
 
2394
2478
  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):
2395
2479
  ```bash
@@ -2416,14 +2500,15 @@ After ALL agents in the group complete successfully:
2416
2500
 
2417
2501
  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):
2418
2502
 
2419
- - `## AC Closure Ledger — <CARD-ID>` with `ac-closure: implemented=N | user-approved deferrals=M | follow-up cards created=K`
2420
- - `simplify: <N fixes | clean — 0 fixes>` (per-card from D.3b)
2503
+ - `## AC Closure Ledger — <CARD-ID>` with `ac-closure: implemented=N | user-approved deferrals=M | follow-up cards created=K` (NEVER skipped — applies to trivial cards too)
2504
+ - `simplify: <N fixes | clean — 0 fixes | SKIPPED (review_profile=skip)>` (per-card from D.3b)
2421
2505
  - `e2e-review: <status>` (with documented gate-skip reason if SKIP)
2422
- - `doc-review: <status>` (from D.4a)
2423
- - `qa: <profile=... | verdict=...>` (group-level from D.4 same value cited per card)
2424
- - `codex-review: <verdict>` (per-card from D.4b)
2506
+ - `doc-review: <status>` (from D.4a — runs for trivial cards too)
2507
+ - `qa: <profile=... | verdict=... | mechanical-only (trivial)>` (group-level from D.4; for a `TRIVIAL_CARDS`-only ride-along the trivial card cites the group value or `mechanical-only`)
2508
+ - `code-review: <covered at D.2 | covered at D.4b | SKIPPED (trivial — non-source diff)>` (per-card the trivial SKIP is valid ONLY for `TRIVIAL_CARDS` per the D.1.5 partition)
2509
+ - `codex-review: <verdict | SKIPPED (light, 0 triggers — D.2 group code-review covered + Final FULL gate) | SKIPPED (trivial — non-source diff; covered by Final FULL gate)>` (per-card from D.4b — the SKIPPED forms are valid ONLY for `LIGHT_CARDS` / `TRIVIAL_CARDS` respectively)
2425
2510
 
2426
- 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.
2511
+ A missing entry means a sub-step was skipped. An entry whose value is a **`review_profile`-driven SKIP** or **`IS_TRIVIAL` SKIP** with the documented enumerated reason above (D.3b `skip` profile, D.4b `LIGHT_CARDS`, code/codex `TRIVIAL_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.
2427
2512
 
2428
2513
  #### Step E: Context purge + next group
2429
2514
 
@@ -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
 
@@ -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
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.3.0",
3
+ "version": "4.6.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"