baldart 4.22.0 → 4.23.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,36 @@ 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.23.0] - 2026-06-09
9
+
10
+ **Functional Traceability Gate — no orphan UI affordances from mockups.** Handed-off mockups (Claude Design / Figma / the internal generators) routinely add interactive-looking chrome — buttons, icons, menu entries — that no requirement backed. Implementing a mockup **1:1 for fidelity** materialised that chrome into real markup + dead handlers + unused icon imports, and the cruft propagated forever. A new **unconditional** gate (independent of `features.has_design_system`) now requires every interactive or iconographic artifact to trace to a function before it becomes code: each artifact is classified **backed** (implement) / **decorative** (static-render, `aria-hidden`) / **orphan** (drop or escalate — never silent). The oracle is the PRD **UI Element Inventory `function_ref`** allowlist → card AC → orphan. Enforced at three boundaries: PRD mockup-intake (orphans become BLOCKING Discovery items), `ui-expert` implementation-time (BLOCKING pre-work), and `code-reviewer` / `/design-review` per-merge (`UI_ORPHAN_AFFORDANCE` HIGH finding). The `visual-fidelity-verifier` gains a `resolved_orphans` carve-out so a deliberately-dropped orphan is *expected-absent*, not a `component-missing` false positive. **MINOR** (additive discipline across existing UI surfaces; no new agent/skill/command and **no `baldart.config.yml` key** — the gate is registry-independent and reads its allowlist from the PRD inventory, so the schema-change propagation rule does not apply).
11
+
12
+ ### Added
13
+
14
+ - **`framework/agents/design-system-protocol.md`** — new SSOT section **"Functional Traceability Gate (BLOCKING — unconditional)"**: the rule, the oracle hierarchy (UI Element Inventory `function_ref` → card AC → orphan), the backed/decorative/orphan classification + outcome matrix, the drop-or-escalate contract for orphans, where it runs (design / implementation / review), and the canonical `UI_ORPHAN_AFFORDANCE` finding code. The Gating section gains an explicit carve-out: this is the one part of the module **not** gated on `features.has_design_system`.
15
+
16
+ ### Changed
17
+
18
+ - **`framework/.claude/agents/ui-expert.md`** — BLOCKING step `0b` (Functional Traceability Gate) added to "When Designing New Interfaces"; check `3b` added to "When Reviewing UI Code"; new red-flag category **Functional traceability (`UI_ORPHAN_AFFORDANCE`)**.
19
+ - **`framework/.claude/agents/code-reviewer.md`** — new section **"Functional Traceability (MANDATORY for UI work — unconditional)"**: live affordances tracing to nothing are `UI_ORPHAN_AFFORDANCE` HIGH findings that block merge; positioned as the per-merge net behind `ui-expert`'s implementation-time gate.
20
+ - **`framework/.claude/agents/visual-fidelity-verifier.md`** — input contract gains optional `resolved_orphans[]`; `component-missing` / `interactive-state-missing` carve-out so a dropped/decorative orphan is expected-absent rather than a fidelity finding.
21
+ - **`framework/.claude/skills/e2e-review/SKILL.md`** — populates `resolved_orphans` in the verifier payload from the card's UI Element Inventory (`function_ref: decorative` or gate-resolved drops).
22
+ - **`framework/.claude/skills/prd/references/discovery-phase.md`** — mockup-intake `mockup_analysis.screens[]` schema gains an `affordances[]` block (`label` / `traces_to` / `classification`); new **Affordance discipline (Functional Traceability)** making every `orphan` a BLOCKING Discovery item resolved via Scope Expansion / `/prd-add` / downgrade / drop.
23
+ - **`framework/.claude/skills/prd/references/ui-design-phase.md`** — Step 3d gates (Full + Hybrid) now require every interactive/iconographic inventory row to carry a `function_ref` with no unresolved `orphan`.
24
+ - **`framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md`** — § 3 gains a functional-traceability constraint (only ship controls tied to a listed function; mark decorative explicitly); § 7 gains a per-element traceability map + orphan-to-decide list requirement.
25
+ - **`framework/.claude/skills/ui-design/references/inventory.md`** — UI Element Inventory table gains a **Function ref** column + the rule binding it to the gate.
26
+ - **`framework/.claude/commands/design-review.md`** — new step 6 (Functional Traceability check) emitting `UI_ORPHAN_AFFORDANCE` HIGH→major findings.
27
+
28
+ ## [4.22.1] - 2026-06-09
29
+
30
+ **Epic-parent closure at end of `/new` + `/new2`.** When the last child of an epic is implemented, the epic/parent card now gets marked `DONE` automatically instead of being left stuck at `TODO`. The Phase 6b post-merge reconciliation gate only ever touched the **batch cards** (the children); the epic/parent card (`group.is_epic: true`) is excluded from the batch by the epic guard, so it never got reconciled — final-review only *reported* "all children done" without writing the epic's own status. **PATCH** (strengthens the existing Phase 6b reconciliation gate; no new agent/skill/command/config key — `/new2` inherits it because its merge agent follows `merge-cleanup.md` as SSOT).
31
+
32
+ ### Changed
33
+
34
+ - **`framework/.claude/skills/new/references/merge-cleanup.md`** — Phase 6b gains **step 5e (Epic-parent closure)**: for each distinct `group.parent` of the batch (plus any epic card in the batch itself), if **every** child is `DONE` (checked across the whole backlog, not just the batch subset), force-update the epic card to `status: DONE` + `completed_date` + note, folded into the same reconciliation commit. Leaves the epic untouched if any child is still open. The step-6 tracker block now records `Epics closed: …`.
35
+ - **`framework/.claude/skills/new/references/final-review.md`** — the "all cards in the epic are DONE" Next-Steps message now states the epic card itself is marked `DONE` (by Phase 6b step 5e), with a note that a still-`TODO` epic under all-DONE children is a Phase 6b failure to flag.
36
+ - **`framework/.claude/workflows/new2.js`** — the merge agent's deterministic policies gain an explicit **EPIC CLOSURE** rule (all-children-DONE gate, distinct from the F-029 forced-DONE prohibition since the epic is a tracker), `MERGE_SCHEMA` gains `epicsClosed[]`, and a new `epic-closure` ledger entry records which epics were closed.
37
+
8
38
  ## [4.22.0] - 2026-06-09
9
39
 
10
40
  **`/prd` Obsidian back-reference.** When a PRD is kicked off from an Obsidian note (the documental starting point), the same note now receives, at the end of the run, an idempotent snippet indexing how the feature was developed and where it landed — PRD path, epic + card IDs, branch, merge commit/PR, and the PRD's Canonical Sources — so the user can later query the note ("come è stata sviluppata la feature X, dov'è finita"). **MINOR** (additive capability on the `prd` skill; no new `baldart.config.yml` key — the note path is runtime-detected and state-persisted, so the schema-change propagation rule does not apply).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.22.0
1
+ 4.23.0
@@ -90,6 +90,28 @@ When `features.has_design_system: false`, the registry reads are skipped but
90
90
  the review still flags hardcoded values and inconsistencies against
91
91
  `${paths.ui_guidelines}`, and recommends `/design-system-init` in the report.
92
92
 
93
+ ## Functional Traceability (MANDATORY for UI work — unconditional)
94
+
95
+ Independent of `features.has_design_system`. For any diff that introduces UI
96
+ affordances, enforce the gate defined in
97
+ [`design-system-protocol.md`](../../agents/design-system-protocol.md)
98
+ § "Functional Traceability Gate":
99
+
100
+ 1. For every interactive or iconographic artifact added by the diff (button,
101
+ link, menu item, tab, toggle, input, icon, badge, status dot), verify it
102
+ traces to a function — a PRD UI Element Inventory `function_ref`, a card AC,
103
+ or a real handler/endpoint reached by the code.
104
+ 2. A live affordance that traces to nothing is a **`UI_ORPHAN_AFFORDANCE`** HIGH
105
+ finding: dead/stub `onClick`, button with no effect, unused icon import kept
106
+ only to mirror a mockup, menu entry routing nowhere. It ships dead code and
107
+ breaks the minimalism contract — block merge.
108
+ 3. The fix is always one of: wire the real behaviour (requires an AC — flag for
109
+ `/prd-add` or a follow-up card), render it static/`aria-hidden` decorative,
110
+ or drop it. "Leave it, it matches the mockup" is never acceptable.
111
+
112
+ This is the per-merge net behind `ui-expert`'s own implementation-time gate; if
113
+ it trips here, the upstream gate was skipped — flag that alongside the finding.
114
+
93
115
  ## Scope Boundary (MUST — read first)
94
116
 
95
117
  Your review scope is STRICTLY limited to **changed files only**.
@@ -255,6 +255,13 @@ project context layer at invocation time:
255
255
  AND tokens-reference (no hardcoded hex/shadow/radius when
256
256
  `has_design_system: true`)
257
257
  3. Evaluate minimalism: unnecessary controls, button placement, cognitive load
258
+ 3b. **Functional Traceability check** (unconditional) — for every interactive /
259
+ iconographic artifact in the diff, verify it traces to a function (inventory
260
+ `function_ref`, an AC, or a real handler/endpoint). A live affordance that
261
+ does nothing (dead `onClick`, unused icon import, button with no effect) is a
262
+ `UI_ORPHAN_AFFORDANCE` HIGH finding per
263
+ [`design-system-protocol.md`](../../agents/design-system-protocol.md)
264
+ § "Functional Traceability Gate".
258
265
  4. **Small screen audit**: verify touch targets (44x44px min), layout at 320px, text scaling to 200%
259
266
  5. Check brand theming: color application, CSS variable usage
260
267
  6. Verify separation of concerns: presentation vs. logic boundaries
@@ -275,6 +282,16 @@ project context layer at invocation time:
275
282
  ### When Designing New Interfaces
276
283
  0. **BLOCKING pre-work** — execute the registry-first cascade above
277
284
  (`design-system-protocol.md`).
285
+ 0b. **BLOCKING — Functional Traceability Gate** (runs regardless of
286
+ `has_design_system`). When implementing from a mockup (your own or one handed
287
+ off from `/prd` / Claude Design / Figma), build the list of interactive +
288
+ iconographic artifacts and classify each against the oracle (PRD UI Element
289
+ Inventory `function_ref` → card AC → orphan) per
290
+ [`design-system-protocol.md`](../../agents/design-system-protocol.md)
291
+ § "Functional Traceability Gate". Resolve every orphan **before** writing
292
+ code — drop it, render it static-only (no stub handler, no dead icon import),
293
+ or escalate via `AskUserQuestion` (one per orphan). Never implement an
294
+ unrequested affordance "to stay 1:1 with the mockup".
278
295
  1. **Component Discovery cascade** — for every visual element the task
279
296
  introduces or touches, walk: `${paths.design_system}/INDEX.md` →
280
297
  `${paths.components_primitives}/*` → external catalog (shadcn / 21st.dev /
@@ -377,6 +394,16 @@ Use the Task tool to launch the `motion-expert` agent with a clear brief:
377
394
  - Controls that serve edge cases but clutter the main flow
378
395
  - Inconsistent terminology in UI labels
379
396
 
397
+ ### Functional traceability (`UI_ORPHAN_AFFORDANCE`)
398
+ - An interactive artifact (button, link, menu item, toggle, tab) wired to a
399
+ dead/empty/stub handler — it looks actionable but does nothing
400
+ - An icon import kept solely to mirror the mockup, with no semantic or
401
+ functional role (should be dropped or `aria-hidden` decorative)
402
+ - A mockup affordance implemented 1:1 with no backing AC / inventory
403
+ `function_ref` — implement only what traces to a function; drop or
404
+ static-render the rest (see `design-system-protocol.md`
405
+ § "Functional Traceability Gate")
406
+
380
407
  ### Architecture & separation of concerns
381
408
  - Business logic embedded in component render functions
382
409
  - Hardcoded colors instead of theme variables in branded pages
@@ -64,13 +64,25 @@ The orchestrator (`/e2e-review`) invokes you with this JSON payload:
64
64
  "components_in_scope": ["Button","Card"] // present for compliance-only
65
65
  },
66
66
  "tolerance": "strict" | "balanced" | "lenient",
67
- "fix_hints_enabled": true
67
+ "fix_hints_enabled": true,
68
+ "resolved_orphans": [ // optional — Functional Traceability Gate
69
+ "Export CSV button", "bell icon" // mockup affordances deliberately dropped or
70
+ ] // downgraded to decorative; absence is EXPECTED
68
71
  }
69
72
  ```
70
73
 
71
74
  If `mockup_source.level == "skip"`, return immediately with
72
75
  `{ "status": "skipped", "reason": "no_mockup_available", "findings": [] }`.
73
76
 
77
+ **`resolved_orphans` carve-out (Functional Traceability Gate).** The mockup is a
78
+ fidelity target, not a requirements oracle: it may contain interactive chrome that
79
+ no requirement backed, which the implementer correctly **dropped or rendered
80
+ static**. Any element matching an entry in `resolved_orphans` that is absent (or
81
+ inert) in the rendered output is **EXPECTED — never a `component-missing` or
82
+ `interactive-state-missing` finding**. Treat the list as authoritative over the
83
+ raw mockup. See `framework/agents/design-system-protocol.md` § "Functional
84
+ Traceability Gate".
85
+
74
86
  ## Severity Taxonomy (CANONICAL — cite verbatim)
75
87
 
76
88
  This taxonomy is the SSOT consumed by `/e2e-review` and `/design-review`. Do
@@ -83,7 +95,7 @@ not invent new severity levels. Do not move categories across tiers.
83
95
  | `layout-break` | Flexbox/grid container collapsed, elements stacked when designed side-by-side, vertical rhythm broken |
84
96
  | `element-order` | DOM/visual order differs from mockup (Button placed before Title when designed after) |
85
97
  | `responsiveness-break` | Breakpoint missing — desktop layout served on 320–375px viewport, horizontal scroll on mobile |
86
- | `component-missing` | Designed component absent from rendered output (e.g. mockup has a CTA card, screen does not) |
98
+ | `component-missing` | Designed component absent from rendered output (e.g. mockup has a CTA card, screen does not). **Exception:** an element listed in `resolved_orphans` is expected-absent — do NOT flag it. |
87
99
  | `component-duplicated` | Same primitive rendered twice when designed once (e.g. accidental React key collision) |
88
100
  | `unreachable-action` | Primary CTA off-screen / behind fixed overlay / unreachable by thumb on mobile |
89
101
 
@@ -117,6 +117,7 @@ When the user invokes `/design-review`, do the following (in both modes — only
117
117
  5. **Design-System Conformance section** — primitives reused, primitives reinvented (HIGH→major), tokens bypassed (HIGH→major), spec drift (MEDIUM→minor), new primitives proposed.
118
118
  - **Mode A**: include as a top-level section in the Markdown report per `agents/design-review.md` § Report Template.
119
119
  - **Mode B**: emit as `findings[]` entries with the appropriate categories from the taxonomy above, and populate `ds_drift_codes[]` with the deduplicated set.
120
+ 6. **Functional Traceability check** (unconditional — independent of `has_design_system`) — flag every interactive or iconographic affordance on the route that traces to no function (dead/stub handler, button with no effect, unused icon, menu entry routing nowhere) as a `UI_ORPHAN_AFFORDANCE` HIGH→major finding, per `framework/agents/design-system-protocol.md` § "Functional Traceability Gate". The fix is wire-it / static-render / drop — never "matches the mockup".
120
121
 
121
122
  ## Mode-detection contract (avoid silent misdetection)
122
123
 
@@ -351,9 +351,16 @@ For each `route` in `plan.routes[]`:
351
351
  "implementation_screenshot_path": ".baldart/e2e-review/FEAT-XXXX/screenshots/merchant-dashboard.png",
352
352
  "mockup_source": { ... },
353
353
  "tolerance": "<from config>",
354
- "fix_hints_enabled": true
354
+ "fix_hints_enabled": true,
355
+ "resolved_orphans": [ "<mockup affordances dropped / downgraded to decorative>" ]
355
356
  }
356
357
  ```
358
+ Populate `resolved_orphans` from the card's UI Element Inventory: any
359
+ interactive/iconographic row whose `function_ref` is `decorative` or that was
360
+ resolved as dropped by the Functional Traceability Gate. This prevents the
361
+ verifier from flagging a deliberately-removed orphan affordance as
362
+ `component-missing`. Omit the field (or pass `[]`) when the inventory has no
363
+ resolved orphans.
357
364
  3. **Parse the agent's JSON response** strictly. If the response is malformed
358
365
  or contains prose outside the JSON, log
359
366
  `verifier_protocol_violation` in `gaps[]` and treat the route's findings
@@ -303,10 +303,16 @@ deterministic script outside this orchestrator's context window.
303
303
  ```
304
304
  ## Prossimi Passi
305
305
 
306
- Tutte le card dell'epic **FEAT-XXXX** sono state completate.
306
+ Tutte le card dell'epic **FEAT-XXXX** sono state completate, e la card epic stessa è
307
+ stata marcata `DONE` (chiusura automatica in Phase 6b, step 5e).
307
308
  Pronto per il deploy: `/deploy` o `git push origin <trunk branch>` (il `git.trunk_branch` configurato)
308
309
  ```
309
310
 
311
+ > Nota: la chiusura della card epic è eseguita dal gate Phase 6b (epic-parent closure),
312
+ > non da questo step di reporting — qui la si riporta soltanto. Se l'epic risultasse
313
+ > ancora `TODO`/`IN_PROGRESS` mentre tutti i figli sono `DONE`, è un fallimento di
314
+ > Phase 6b step 5e da segnalare in `## Issues & Flags`.
315
+
310
316
  5. **Proceed to Phase 7** (production readiness checklist).
311
317
 
312
318
  ---
@@ -74,12 +74,39 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
74
74
  - If doc-freshness requires ssot-registry.md → update its `last_verified_from_code` date, stage it, and retry.
75
75
  - If COMMIT_LOCK → re-run step 5a and retry.
76
76
  - Do NOT retry more than 2 times. Log the failure and move on.
77
+ 5e. **Epic-parent closure (MANDATORY — the epic card is NOT in the batch)**: the batch
78
+ implements children; the epic/parent card (`group.is_epic: true`, id usually ending
79
+ `-00` / filename `*-epic.yml` / `review_profile: skip`) is never implemented and is
80
+ excluded from the per-card loop in step 2 — so it stays at its old status (almost
81
+ always `TODO`) even when its **last** child just went DONE. Close it here, deterministically:
82
+
83
+ For each **distinct parent epic** referenced by the batch — the set of `group.parent`
84
+ values across the batch cards, PLUS any card in the batch that is itself an epic
85
+ (`group.is_epic: true`) — do:
86
+ a. Read the epic parent card `${paths.backlog_dir}/<EPIC-ID>.yml`. If `status: DONE`
87
+ already → skip (idempotent).
88
+ b. Check that **every** child is DONE (not just the batch's subset — children
89
+ implemented in earlier sessions count too):
90
+ ```bash
91
+ # Lists children of <EPIC-ID> that are NOT yet DONE. Empty output ⇒ all DONE.
92
+ grep -l "parent: <EPIC-ID>" ${paths.backlog_dir}/*.yml | xargs grep -L "status: DONE"
93
+ ```
94
+ c. **Only if** that command prints nothing (all children DONE) → force-update the epic
95
+ parent: set `status: DONE`, add `completed_date: <today>`, add note
96
+ `"Marked DONE by epic-closure gate (Phase 6b) — all children DONE"`. Stage it and
97
+ **fold into the SAME reconciliation commit** as step 5 (or, if step 5 didn't run
98
+ because all children were already DONE, make a dedicated
99
+ `[BATCH] Close epic <EPIC-ID> — all children DONE` commit, honoring the same
100
+ COMMIT_LOCK / doc-freshness precautions as step 5a–5d).
101
+ d. **If any child is still open** → leave the epic untouched: it is genuinely still in
102
+ progress, and step-4b's "Card rimanenti nell'epic" table already reports the gap.
77
103
  6. **Update tracker** with reconciliation results:
78
104
  ```
79
105
  ## Phase 6b — Status Reconciliation
80
106
  Cards checked: N
81
107
  Already DONE: M
82
108
  Force-updated to DONE: K [list card IDs]
109
+ Epics closed: J [list epic IDs] (or "none — children still open" / "n/a — no epic in batch")
83
110
  Reconciliation commit: <hash> (or "not needed")
84
111
  ```
85
112
  7. **HALT condition**: if a card cannot be updated (e.g., file read error, YAML parse error), log it in `## Issues & Flags` and continue with the remaining cards. Report the failure in the final summary.
@@ -34,6 +34,15 @@
34
34
 
35
35
  ## 3. Schermate in scope
36
36
 
37
+ > **Vincolo di tracciabilità funzionale.** Inserisci SOLO controlli (bottoni,
38
+ > icone-azione, voci di menu, tab, toggle) legati a una funzione elencata qui
39
+ > sotto. Non aggiungere chrome interattivo "per completezza visiva": ogni
40
+ > affordance senza funzione diventa codice morto in implementazione. Se proponi
41
+ > un elemento interattivo non presente nelle azioni primarie, segnalalo in
42
+ > consegna come *proposta da validare*, non come requisito. Gli elementi
43
+ > puramente decorativi (icone di sezione, marchi) marcali esplicitamente come
44
+ > **decorativi**.
45
+
37
46
  {{#each screens_in_scope}}
38
47
  ### {{this.name}}
39
48
 
@@ -108,7 +117,14 @@ Per ogni schermata elencata in § 3:
108
117
 
109
118
  1. **PNG export** ad alta risoluzione (≥ 2x) di OGNI stato richiesto (es. `lista-ordini__empty.png`, `lista-ordini__populated.png`).
110
119
  2. {{output_format_preference_block}} <!-- "Codice HTML/CSS in un file unico" | "Codice React (.tsx) con import Tailwind" | "Link Figma alla frame" -->
111
- 3. **Note inline** su:
120
+ 3. **Mappa di tracciabilità** — per OGNI elemento interattivo o iconografico
121
+ della schermata, una riga: `elemento → funzione che lo motiva` (l'azione
122
+ primaria / il dato di § 3 che serve), oppure marcalo **decorativo** se non ha
123
+ comportamento. Qualunque elemento che non riesci a tracciare a una funzione,
124
+ elencalo separatamente come *orphan da decidere* — così posso scegliere se
125
+ dargli una funzione, renderlo decorativo o rimuoverlo, invece di
126
+ implementarlo a vuoto.
127
+ 4. **Note inline** su:
112
128
  - Componenti nuovi (non presenti nel registry sopra) che hai introdotto e perché.
113
129
  - Token deviation: qualunque colore/spacing/radius fuori dalla palette sopra, con motivazione.
114
130
  - Decisioni di layout non ovvie (es. priority sort, hierarchy visiva, density).
@@ -1095,6 +1095,10 @@ mockup_analysis:
1095
1095
  purpose: <one line — what this screen lets the user do>
1096
1096
  components:
1097
1097
  - <component name from ${paths.design_system}/INDEX.md if matched, else free-form>
1098
+ affordances: # interactive + iconographic artifacts visible in the mockup
1099
+ - label: <e.g. "Export CSV button", "bell icon", "Filters tab">
1100
+ traces_to: <US-id / AC-id / discovery dimension this artifact serves — or "" if none yet>
1101
+ classification: backed | decorative | orphan
1098
1102
  states_visible: [empty, loading, error, success, default]
1099
1103
  copy_excerpts:
1100
1104
  - <verbatim text visible in the mockup, max ~10 strings per screen>
@@ -1122,3 +1126,25 @@ mockup_analysis:
1122
1126
  or an explicit non-question (recorded in Discovery Log as "skipped — outside
1123
1127
  mockup scope"). Gaps that remain unresolved by the end of Discovery block PRD
1124
1128
  writing in Step 4.
1129
+
1130
+ **Affordance discipline (Functional Traceability):** classify every interactive
1131
+ or iconographic artifact the mockup shows. The mockup is a fidelity target, not
1132
+ a requirements oracle — handed-off designs (Claude Design / Figma) routinely add
1133
+ buttons, icons, and menu entries no requirement asked for, and implementing them
1134
+ 1:1 ships dead code. So:
1135
+ - `backed` — the artifact serves a user story / AC already in scope. Record
1136
+ `traces_to`.
1137
+ - `decorative` — legitimately present but carries no behaviour (section icon,
1138
+ brand mark). It will render static/`aria-hidden` at implementation, never as a
1139
+ live control.
1140
+ - `orphan` — interactive-looking but traces to nothing in scope. Each orphan is
1141
+ a **BLOCKING Discovery item**: ask the user (one per turn, like a gap) whether
1142
+ it should (a) gain a real function → routes through the Scope Expansion Check /
1143
+ `/prd-add`, (b) be downgraded to decorative, or (c) be dropped from the design.
1144
+ Never carry an unresolved `orphan` past Discovery — it would silently become
1145
+ dead UI. Record the resolution in the Discovery Log.
1146
+
1147
+ This classification is the seed of the UI Element Inventory `function_ref`
1148
+ column (Step 3d) — the allowlist `ui-expert` and `code-reviewer` enforce at
1149
+ implementation/review via the Functional Traceability Gate
1150
+ (`framework/agents/design-system-protocol.md`).
@@ -157,7 +157,10 @@ This prevents designing new components when 450+ reusable ones already exist.
157
157
  **STOP.** Wait for user to choose.
158
158
  - **After Step 3c:** explicit user approval ("confermo", "va bene", "ok").
159
159
  - **After Step 3d:** `${paths.prd_dir}/<slug>/design.html` exists (inside `$WORKTREE_PATH`) AND `## UI Element Inventory`
160
- is populated in the state file with at least 3 elements.
160
+ is populated in the state file with at least 3 elements, AND every interactive
161
+ or iconographic row carries a `function_ref` (an US/AC/endpoint or `decorative`) —
162
+ no unresolved `orphan` (Functional Traceability Gate, see
163
+ `framework/agents/design-system-protocol.md`).
161
164
  - **After Step 3e (BLOCKING when `features.has_design_system: true`):** ui-design Step H
162
165
  (Post-Intervention Coherence Check) must complete with no unresolved `DS_INDEX_DRIFT` /
163
166
  `DS_COMPONENT_STALE` / `DS_TOKENS_DRIFT` findings (or every finding explicitly waived
@@ -252,7 +255,9 @@ to the design system, approve, and inventory. Generate ONLY the screens not cove
252
255
  `design_system_alignment.violations[]` is either resolved (token/component
253
256
  swapped in the inventory) or explicitly waived by the user (logged as such).
254
257
  - **After 3d:** UI Element Inventory populated with at least one row per screen in
255
- `screens_in_scope[]`. `design.html_path` set OR `mockup-only` flag recorded.
258
+ `screens_in_scope[]`, every interactive/iconographic row carrying a `function_ref`
259
+ (no unresolved `orphan` — Functional Traceability Gate).
260
+ `design.html_path` set OR `mockup-only` flag recorded.
256
261
  - **After 3e — Registry Coherence (BLOCKING when `features.has_design_system: true`):**
257
262
  ui-design Step H (Post-Intervention Coherence Check) must complete with no
258
263
  unresolved `DS_INDEX_DRIFT` / `DS_COMPONENT_STALE` / `DS_TOKENS_DRIFT` findings
@@ -24,10 +24,10 @@ Aggiungi al state file del PRD sotto `## UI Element Inventory`:
24
24
 
25
25
  Source: ${paths.prd_dir}/<slug>/design.html (option <X>, approved <date>)
26
26
 
27
- | # | Element | Type | Purpose | Inputs/Outputs | Behavior |
28
- |---|---------|------|---------|----------------|----------|
29
- | 1 | <name> | page / section / component / modal / form / button / endpoint | <what it does> | <fields, props, API params> | <interactions, states, validation> |
30
- | 2 | ... | ... | ... | ... | ... |
27
+ | # | Element | Type | Purpose | Inputs/Outputs | Behavior | Function ref |
28
+ |---|---------|------|---------|----------------|----------|--------------|
29
+ | 1 | <name> | page / section / component / modal / form / button / endpoint | <what it does> | <fields, props, API params> | <interactions, states, validation> | <US-id / AC-id / endpoint — or `decorative`> |
30
+ | 2 | ... | ... | ... | ... | ... | ... |
31
31
 
32
32
  ### New components to create
33
33
  - <ComponentName> — <purpose> (NOT existing in codebase)
@@ -45,6 +45,16 @@ Source: ${paths.prd_dir}/<slug>/design.html (option <X>, approved <date>)
45
45
  - Sii specifico: "Save button that calls POST /api/v1/foo/bar" non "a button" (cita l'endpoint reale).
46
46
  - Includi error states, loading states, e empty states visibili nel design.
47
47
  - Distingui chiaramente tra componenti da creare e componenti da riusare.
48
+ - **Function ref (Functional Traceability):** ogni riga interattiva o iconografica
49
+ DEVE citare la funzione che la giustifica — uno US-id, un AC-id, o l'endpoint
50
+ reale che invoca. Un elemento puramente visivo si marca `decorative` (verrà reso
51
+ statico/`aria-hidden`, mai come controllo vivo). Se un artefatto del design non
52
+ traccia a nulla ed è interattivo (un **orphan**), NON inventargli uno scopo:
53
+ era un orphan già emerso nell'intake del mockup (Discovery) e va risolto lì
54
+ (funzione reale via `/prd-add`, downgrade a `decorative`, o rimozione dal design)
55
+ prima di scrivere l'inventario. Questa colonna è l'allowlist che `ui-expert` e
56
+ `code-reviewer` fanno rispettare con il Functional Traceability Gate
57
+ (`framework/agents/design-system-protocol.md`).
48
58
 
49
59
  ## Cosa NON includere
50
60
 
@@ -163,6 +163,7 @@ const MERGE_SCHEMA = {
163
163
  mergeTs: { type: 'string' },
164
164
  reconciliation: { type: 'string' },
165
165
  forcedDone: { type: 'array', items: { type: 'string' }, description: 'MUST be empty — false-DONE is forbidden (F-029)' },
166
+ epicsClosed: { type: 'array', items: { type: 'string' }, description: 'Epic/parent cards marked DONE by Phase 6b step 5e (all children DONE) — NOT a forcedDone violation' },
166
167
  uncommittedLeft: { type: 'boolean', description: 'true if dirty code was left (NOT committed) + reported (F-030)' },
167
168
  note: { type: 'string' },
168
169
  },
@@ -634,6 +635,7 @@ if (!committed.length) {
634
635
  `• G24 → auto-merge via merge_strategy.\n` +
635
636
  `• F-030 HARD RULE: NEVER \`git add\`/commit code that did not pass the per-card gates. If the worktree is dirty with uncommitted code → DO NOT commit it; leave it, set uncommittedLeft:true, and report. NO "safety commit". Security/migration code is NEVER swept in.\n` +
636
637
  `• F-029 HARD RULE: Phase 6b reconciliation marks a card DONE ONLY if it has a real commit in ${TRUNK}..HEAD AND its gates are green. NEVER force a non-implemented card to DONE. Return forcedDone:[] (must be empty).\n` +
638
+ `• EPIC CLOSURE (Phase 6b step 5e): the epic/parent card (group.is_epic:true) is NOT in the batch and stays TODO unless closed here. For each distinct group.parent of the batch cards (and any epic card in the batch itself): if EVERY child of that epic — \`grep -l "parent: <EPIC-ID>" backlog/*.yml | xargs grep -L "status: DONE"\` prints nothing — set the epic card status:DONE + completed_date + note "epic-closure gate — all children DONE" and fold into the reconciliation commit. If any child is still open → leave the epic untouched. This is NOT a forcedDone violation (the epic is a tracker, gated on all-children-DONE, not on its own commit). Return epicsClosed:[<EPIC-IDs marked DONE>].\n` +
637
639
  `• G19 sync-deferred → HEAD==${TRUNK} ff-pull, else leave+report. G20 → leave+report. G21 post-batch dirty → partition-ignore framework artifacts; leave the rest + report (do NOT commit). G22 divergence → behind: ff-pull; ahead/both: leave+report; NEVER reset --hard/force-push. G23 stash restore conflict → leave intact + report.\n\n` +
638
640
  `Return: { merged, mergeCommit, mergeTs, reconciliation, forcedDone:[], uncommittedLeft, note }`,
639
641
  { label: 'merge', phase: 'Merge', agentType: 'general-purpose', schema: MERGE_SCHEMA }
@@ -641,6 +643,7 @@ if (!committed.length) {
641
643
  } catch (e) { if (e && e.transientExhausted) noteDegraded('outage'); mergeResult = null }
642
644
  if (mergeResult && (mergeResult.forcedDone || []).length) { noteDegraded('false_done'); ledger(firstCard, 'F029-guard', 'VIOLATION', `forcedDone: ${mergeResult.forcedDone.join(' ')}`) }
643
645
  if (mergeResult && mergeResult.uncommittedLeft) ledger(firstCard, 'F030-guard', 'LEFT-UNCOMMITTED', 'dirty code left (not swept) + reported')
646
+ if (mergeResult && (mergeResult.epicsClosed || []).length) ledger(firstCard, 'epic-closure', 'CLOSED', `epics marked DONE (all children DONE): ${mergeResult.epicsClosed.join(' ')}`)
644
647
  ledger(firstCard, 'G24-merge', (mergeResult && mergeResult.merged) ? 'MERGED' : 'INCOMPLETE', (mergeResult && (mergeResult.mergeCommit || mergeResult.note)) || '')
645
648
  if (mergeResult && mergeResult.reconciliation) ledger(firstCard, 'G19-23-reconcile', 'AUTO', mergeResult.reconciliation)
646
649
  }
@@ -36,6 +36,12 @@ in step 1 below are SKIPPED — agents/skills still respect
36
36
  When the flag is `true`, the registry reads are **BLOCKING**: skipping them is
37
37
  a protocol violation and the agent must refuse to proceed.
38
38
 
39
+ **One exception — the Functional Traceability Gate is unconditional.** The
40
+ gate defined in § "Functional Traceability Gate" below is the only part of this
41
+ module that is **not** gated on `features.has_design_system`. It concerns
42
+ *function provenance* (does each artifact in a mockup do something?), not the
43
+ visual registry, so it applies to every UI surface — design-system or not.
44
+
39
45
  To bootstrap a registry on a project that does not yet have one, invoke the
40
46
  `design-system-init` skill (`/design-system-init`).
41
47
 
@@ -259,6 +265,86 @@ agent has two choices:
259
265
  Never silently introduce a new component that overlaps with an existing
260
266
  registry primitive — that is the exact failure mode this protocol prevents.
261
267
 
268
+ ## Functional Traceability Gate (BLOCKING — unconditional)
269
+
270
+ A mockup is a fidelity target, **not** a requirements oracle. Design tools
271
+ (Claude Design, Figma, the `ui-design`/`frontend-design` generators) routinely
272
+ add interactive-looking chrome — buttons, icons, menu entries, toggles, badges —
273
+ that no requirement asked for. Implementing a mockup **1:1 for fidelity**
274
+ materialises that chrome into real markup + dead handlers + unused icon imports,
275
+ and the cruft then propagates forever. This gate stops it at the boundary where
276
+ the leak happens: **before** any artifact becomes code.
277
+
278
+ **Gating: unconditional.** Unlike the registry cascade above, this gate runs on
279
+ every UI surface regardless of `features.has_design_system`. It is about whether
280
+ each artifact *does something*, not about tokens or the registry.
281
+
282
+ ### The rule
283
+
284
+ > No interactive or iconographic artifact is implemented unless it traces to a
285
+ > function. Orphans are resolved — never implemented "to stay faithful to the
286
+ > mockup".
287
+
288
+ "Interactive or iconographic artifact" = any element a user could plausibly
289
+ expect to act on or read as a signal: buttons, links, menu items, tabs,
290
+ toggles, inputs, icons, badges, status dots, action affordances. Pure layout
291
+ and decorative-by-construction visuals (spacers, dividers, background art,
292
+ hero illustrations) are out of scope — they carry no implied behaviour.
293
+
294
+ ### The oracle (where "a function" comes from), in order
295
+
296
+ 1. **PRD UI Element Inventory** — the `function_ref` column (added by `/prd`
297
+ Step 3d / the `ui-design` inventory). When present, this is the authoritative
298
+ allowlist: each row names the AC / user story / handler / endpoint it serves,
299
+ or is explicitly marked `decorative`.
300
+ 2. **Backlog card spec / acceptance criteria** — when no inventory row exists,
301
+ the artifact must map to an AC or an explicit behaviour in the card.
302
+ 3. **Neither** — the artifact is an **orphan**. Do not guess a purpose for it.
303
+
304
+ ### Classification + outcome (per artifact)
305
+
306
+ | Classification | Definition | Outcome |
307
+ |---|---|---|
308
+ | **Backed** | Traces to an inventory `function_ref`, an AC, or a real handler/endpoint | **Implement** — wire the real behaviour |
309
+ | **Decorative** | Legitimately present but carries no behaviour (e.g. a section icon, a brand mark) | **Static-only** — render it inert: no `onClick`, no stub handler, no route, no disabled-button placeholder. If it imports an icon purely for decoration, keep it minimal and labelled `aria-hidden` |
310
+ | **Orphan** | Interactive-looking, traces to nothing, not deliberately decorative | **Drop or escalate** — see below |
311
+
312
+ For every **orphan**, never decide silently (consistent with the framework's
313
+ `always-ask, never-assume` and "no silent AC deferral" contracts):
314
+
315
+ - **Drop** when the artifact is clearly unrequested chrome and removing it does
316
+ not change any in-scope flow — the default for obvious leftover decoration
317
+ rendered as a button.
318
+ - **Escalate** (one `AskUserQuestion` per orphan, or a single grouped report
319
+ when run inside an autonomous workflow) when it is plausible the artifact
320
+ *should* have a function that was simply never specified — i.e. dropping it
321
+ might silently discard intended scope. The three offered resolutions are:
322
+ *implement (needs an AC → routes to `/prd-add` or a follow-up card)*,
323
+ *static-only*, or *drop*.
324
+
325
+ ### Where it runs
326
+
327
+ 1. **Design / generation time** (`ui-design`, `frontend-design`, `ui-expert`
328
+ designing) — when producing a mockup, do not invent interactive chrome with
329
+ no backing requirement; mark genuinely decorative elements as such in the
330
+ inventory so the downstream gate is deterministic, not a guess.
331
+ 2. **Implementation time** (`ui-expert` implementing) — **BLOCKING pre-work**:
332
+ build the artifact list from the mockup, classify each row against the oracle,
333
+ and resolve every orphan (drop / static-only / escalate) **before** writing
334
+ code. Implementing an orphan as a live affordance is the exact failure mode
335
+ this gate prevents.
336
+ 3. **Review time** (`code-reviewer`, `/design-review`) — verify the shipped diff
337
+ introduces no live affordance that traces to nothing. Each one is a
338
+ `UI_ORPHAN_AFFORDANCE` HIGH finding (dead handler / unused icon import / button
339
+ with no effect).
340
+
341
+ ### Finding code
342
+
343
+ `UI_ORPHAN_AFFORDANCE` — an interactive or iconographic artifact that was
344
+ implemented (or is about to be) without a traceable function. HIGH severity:
345
+ it ships dead code and erodes the minimalism contract. The fix is always one of
346
+ the three outcomes above, never "leave it, it matches the mockup".
347
+
262
348
  ## Post-Intervention Coherence Check (BLOCKING completion gate)
263
349
 
264
350
  The BLOCKING pre-work above enforces *reads* before any UI change. This
@@ -351,6 +437,18 @@ The following files reference this module instead of duplicating its rules:
351
437
  - `framework/.claude/commands/design-review.md`
352
438
  - `framework/agents/design-review.md`
353
439
 
440
+ Additional consumers of the **Functional Traceability Gate** specifically
441
+ (unconditional, registry-independent):
442
+
443
+ - `framework/.claude/agents/visual-fidelity-verifier.md` (`resolved_orphans`
444
+ carve-out — a dropped orphan is expected-absent, not `component-missing`)
445
+ - `framework/.claude/skills/e2e-review/SKILL.md` (passes `resolved_orphans`)
446
+ - `framework/.claude/skills/prd/references/discovery-phase.md` (mockup-intake
447
+ affordance classification — the oracle's origin)
448
+ - `framework/.claude/skills/prd/references/ui-design-phase.md` +
449
+ `framework/.claude/skills/ui-design/references/inventory.md` (the
450
+ `function_ref` allowlist the gate enforces)
451
+
354
452
  When changing the BLOCKING cascade, change it here — not in the consumers.
355
453
 
356
454
  ## See also
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.22.0",
3
+ "version": "4.23.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"