baldart 4.71.0 → 4.73.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,44 @@ 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.73.0] - 2026-06-25
9
+
10
+ **Mockup→code fidelity: let the implementer actually SEE the mockup, and carry the intent a flat list loses.** A recurring complaint: a detailed mockup (Claude Design / Figma) is handed to `/prd`, then `/new` ships something that is not 1:1 with the design. The obvious diagnosis — "the PRD's visual mapping is too weak, add a mockup-understanding agent" — was **refuted** by an adversarial pass: the visual data largely already exists (`mockup_analysis` captures affordances + traceability + states + DS violations; `component_bindings` propagates region→component to the implementer), and a parallel machine-readable `design-spec` artifact would be a second SSOT that drifts from the mockup and would feed the verifier a textual proxy instead of the pixels (a bias `visual-fidelity-verifier` explicitly forbids). The **actual driver**: the `/new` UI briefing passes the mockup as a *path* and says "Read the design.html file" — but for a binary PNG/Figma mockup nothing ever instructs the (multimodal) `ui-expert` to **load the image into its context**. The implementer was building from text, never looking at the design.
11
+
12
+ **MINOR** — adds capability; **no new `baldart.config.yml` key** (rides on the existing `features.has_design_system`), so the schema-change propagation rule does NOT apply. The new `component_bindings[].props` is an **optional** sub-field — legacy cards omit it and the validator (which keys on the `C`-profile top-level field) is unaffected. On a non-multimodal tool (Codex) the image-load instruction degrades to a harmless no-op and the structured fields carry the intent.
13
+
14
+ ### Changed
15
+
16
+ - **The `/new` UI briefing loads the mockup IMAGE multimodally** (`framework/.claude/skills/new/references/implement.md` § "Design Reference"). The instruction now branches on the file type of `links.design`: an IMAGE (`.png`/`.jpg`/`.jpeg`/`.webp`/`.pdf`, or a `mockups/*.png` canonical path) is **`Read` directly** so it renders into the agent's context (pixels = fidelity target); an `.html` keeps the existing read-as-text behaviour. The briefing also now surfaces `component_bindings[].props`/`variant` (exact variant intent) and the card's responsive ACs (breakpoint behaviour a single-viewport image cannot show) as the structured complement to the image.
17
+ - **`mockup_analysis` schema enriched in-place with the four dimensions a flat affordance list + a single static image lose** (`framework/.claude/skills/prd/references/discovery-phase.md` § "Mockup analysis schema"). Each `screens[]` entry gains four OPTIONAL fields: `layout` (compact nesting sketch — the hierarchy), `component_props` (key props/variant legible per component — the reuse-vs-`reuse-variant` signal), `states` (richer `states_visible` mapping each state→the AC that mandates it; an untraced state is a Discovery gap by the same rule as an untraced affordance), and `responsive` (breakpoint behaviour). "Omit rather than guess" discipline extended to all four.
18
+ - **Component Reconciliation consumes the enrichment and persists it** (`framework/.claude/skills/prd/references/ui-design-phase.md` § "Component Reconciliation"). `REUSE+VARIANT` detection now compares `mockup_analysis.component_props` against the component's spec HEAD (props the spec doesn't cover ⇒ variant), and the persist step carries resolved props into `component_bindings[].props` and lifts `responsive` notes into the UI card's `acceptance_criteria` as testable ACs.
19
+
20
+ ### Added
21
+
22
+ - **`component_bindings[].props` card sub-field** (`framework/agents/card-schema.md` § `component_bindings`) — the optional resolved props/variant each region must use, the exact-variant intent a static image leaves ambiguous; `ui-expert` implements it verbatim. Sub-field of the existing `C`-profile `component_bindings`, so no validator/matrix change.
23
+
24
+ ## [4.72.0] - 2026-06-25
25
+
26
+ **Session-id provenance on PRDs and cards — trace an implementation bug back to the chat history that produced it.** When `/new` ships a card that turns out wrong, there was no link from the card back to *which Claude Code session* planned it (`/prd`) or implemented it (`/new`/`/new2`) — so you couldn't reopen the transcript to see the steps the session took (or skipped). Now every card carries an optional `provenance` block recording the session UUID for each lifecycle stage, and the PRD frontmatter records its authoring session. Resolve the chat history at `~/.claude/projects/*/<session-id>.jsonl` (the UUID is globally unique — no project slug needed). This turns "this feature is broken, why?" into a concrete pointer at the planning/implementation conversation, for improving the framework from real runs.
27
+
28
+ The enabling fact (verified against a live `env`): the session id IS available at runtime as **`CLAUDE_CODE_SESSION_ID`** — NOT `CLAUDE_SESSION_ID` (which does not exist). The capture discipline matters: the value is read by the **orchestrator** (`/prd`, `/new`, the `new2` skill) in its own context and passed down — never read inside a writer/commit subagent, whose own session id differs (`CLAUDE_CODE_CHILD_SESSION=1`).
29
+
30
+ **MINOR** — adds a traceability capability; **NOT a `baldart.config.yml` key** (it is a card-schema field + a native env var), so the schema-change propagation rule does NOT apply. The `provenance` field is **optional (`C`)** in every card profile — the validator ignores it and legacy cards never break (verified by running `validate-card-baseline.js` on cards with and without the block). Sub-fields are omitted when the env var is unset; an EPIC card (a tracker) carries `planning_session` only.
31
+
32
+ ### Added
33
+
34
+ - **`provenance` card field** (`framework/agents/card-schema.md` — the schema SSOT) — `{ planning_session, implementation_session }`, both optional session UUIDs, added to the field-state matrix as `C`/`C`/`C` with a dedicated `§ provenance` section defining ownership-by-lifecycle and the `CLAUDE_CODE_SESSION_ID` resolution rule. Mirrored in the card template (`framework/.claude/skills/prd/assets/card-template.yml`) and a `planning_session` frontmatter line in the PRD template (`framework/.claude/skills/prd/assets/prd-template.md`).
35
+ - **`session_id` on the `/new` telemetry row** (`framework/.claude/skills/new/references/metrics.md`) — `skill-runs.jsonl` records now carry the run's session UUID so the metrics row also resolves to its transcript.
36
+
37
+ ### Changed
38
+
39
+ - **`/prd` stamps the planning session** — `prd-card-writer` (`framework/.claude/agents/prd-card-writer.md`) sets `provenance.planning_session` from a new `PLANNING_SESSION_ID` field the `/prd` orchestrator passes (captured in the orchestrator's own context, `backlog-phase.md`), and `/prd` writes the same id into the PRD frontmatter (`prd-writing-phase.md`). The writer never reads the env var itself (subagent session ≠ conversation session).
40
+ - **`/new` + `/new2` stamp the implementation session** when a card reaches `status: DONE` — `/new` at `commit.md` Step 28 (`framework/.claude/skills/new/references/commit.md`); `/new2` via a new `sessionId` workflow arg captured by the skill and stamped by `new2.js` at the per-card commit step, the cross-card integration commit, and the skill's post-run deferred-card reconciliation — always merging into the existing `provenance` block, never overwriting `planning_session`.
41
+
42
+ ### Fixed
43
+
44
+ - **`team-mode.md` used the non-existent `CLAUDE_SESSION_ID`** (`framework/.claude/skills/new/references/team-mode.md`) — the subagent-transcript locator (`ls ~/.claude/projects/*/${CLAUDE_SESSION_ID:-*}/subagents`) always fell through to the wildcard fallback because the variable name was wrong. Corrected to `CLAUDE_CODE_SESSION_ID` so it resolves the live session's `subagents/` dir directly.
45
+
8
46
  ## [4.71.0] - 2026-06-25
9
47
 
10
48
  **Responsive-awareness in UI review — catch a viewport-scoped edit that regresses the *other* viewport.** When an agent works on one viewport (fix the mobile header, tweak the desktop table), nothing today catches it silently shifting the viewport it was *not* editing. The obvious fix — cloning the i18n ESLint gate into a `responsive-gate` that bans raw `matchMedia`/breakpoint literals — was **refuted** by an adversarial pass: that gate is blind to where responsiveness actually lives (Tailwind `md:` classes, CSS, CSS-in-JS — not `window.matchMedia`), so it is mayo-specific opinion, not a distributable mechanism. The real driver is served by **reactivating machinery that already exists and was inert**: `visual-fidelity-verifier` has had a `responsiveness-break` finding all along, but `/e2e-review` only ever screenshotted **one** viewport (1440px), so it could never fire.
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.71.0
1
+ 4.73.0
@@ -42,6 +42,26 @@ predating v3.22.0): fall back to the consumer root (whatever Bash `pwd`
42
42
  returns). Log a `[WORKING-DIR-FALLBACK]` warning in your response so the
43
43
  caller can detect it and migrate.
44
44
 
45
+ ## Session provenance (the caller passes `PLANNING_SESSION_ID`)
46
+
47
+ When invoked by `/prd` Step 5 the caller ALSO passes a **`PLANNING_SESSION_ID`**
48
+ field — the Claude Code session UUID of the `/prd` run (the orchestrator's
49
+ `$CLAUDE_CODE_SESSION_ID`). On EVERY card you write, set:
50
+
51
+ ```yaml
52
+ provenance:
53
+ planning_session: "<PLANNING_SESSION_ID>"
54
+ ```
55
+
56
+ This makes a card traceable back to the chat history that authored it
57
+ (`~/.claude/projects/*/<id>.jsonl`). **Do NOT read `CLAUDE_CODE_SESSION_ID`
58
+ yourself** — you run as a subagent, so your own session id differs from the
59
+ `/prd` conversation the user sees; only the value the caller passed is correct.
60
+ If the caller did NOT pass `PLANNING_SESSION_ID` (legacy call site, or the env
61
+ var was unset), **omit the `provenance` block entirely** — never write a
62
+ placeholder. Do NOT set `implementation_session` — `/new`/`/new2` add that when
63
+ the card reaches `status: DONE`.
64
+
45
65
  ---
46
66
 
47
67
  ## Field Grounding Rule (HARD RULE — zero tolerance)
@@ -30,7 +30,7 @@
30
30
  3. **Retry cap + escalation** — retry the commit at most **2 times**. If it still fails (e.g. a pre-commit / doc-freshness rule that cannot be satisfied without human input), log the failure in `## Issues & Flags` as `[COMMIT-BLOCKED] <error>` and invoke `AskUserQuestion`: (a) fix the blocking condition manually and let me retry, (b) hand off the worktree intact, (c) abandon this card and continue the batch. Do NOT loop indefinitely and do NOT silently proceed to step 28 with an uncommitted card. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a hook rejection needs human input) → materialize a follow-up card carrying the blocking condition and leave the worktree intact (NEVER force-commit, NEVER mark the card DONE).**
31
31
  - The Claude Code pre-commit hook automatically skips for worktree commits (Husky handles checks natively in the worktree).
32
32
  28. **Mark card DONE (MANDATORY — do NOT skip)**:
33
- a. Edit the backlog YAML (`${paths.backlog_dir}/<CARD-ID>.yml`): set `status: DONE`, add `completed_date: <today>`.
33
+ a. Edit the backlog YAML (`${paths.backlog_dir}/<CARD-ID>.yml`): set `status: DONE`, add `completed_date: <today>`, and add `provenance.implementation_session: <SESSION-ID>` (this run's session UUID — get it with `Bash(echo "${CLAUDE_CODE_SESSION_ID:-}")`; if empty, omit the field). Merge into any existing `provenance:` block written by `/prd` — do NOT overwrite `planning_session`. This makes an implementation bug traceable back to the session that produced it (`~/.claude/projects/*/<id>.jsonl`).
34
34
  b. Add implementation notes summarizing what was built.
35
35
  c. **Update `${paths.references_dir}/ssot-registry.md`** — add/update the entry for this card's feature area. The pre-commit doc-freshness hook BLOCKS commits that touch `${paths.backlog_dir}/` without a corresponding ssot-registry update. Always include ssot-registry.md in the same commit as the backlog YAML.
36
36
  d. **Verify the write**: re-read the YAML file and confirm `status: DONE` is present. If not, retry the edit.
@@ -111,9 +111,32 @@
111
111
 
112
112
  ### Design Reference (UI cards only — include if card has links.design)
113
113
  Design file: [path from card's links.design field]
114
- Read the design.html file and use it as the **visual reference** for your
115
- implementation (layout, spacing, hierarchy, copy). The design was approved by
116
- the user your implementation MUST match its appearance.
114
+
115
+ **Load the mockup into your context branch on the file type:**
116
+ - If `links.design` resolves to an IMAGE (`.png` / `.jpg` / `.jpeg` /
117
+ `.webp` / `.pdf`, or the card points at a `mockups/*.png` canonical path):
118
+ you are multimodal — **`Read` the mockup image file path directly**. `Read`
119
+ renders the image visually into your context (the native multimodal
120
+ mechanism). The mockup's PIXELS are the fidelity target: study layout,
121
+ spacing, hierarchy, density, color, and the relative position of every
122
+ region. Do NOT implement from the filename or a textual guess — you MUST
123
+ actually load and look at the image. (On a non-multimodal tool the `Read`
124
+ degrades to no-op text; fall back to the structured fields below.)
125
+ - If `links.design` is an `.html` file: **Read the design.html file** and use
126
+ it as the visual reference.
127
+
128
+ Use it as the **visual reference** for your implementation (layout, spacing,
129
+ hierarchy, copy). The design was approved by the user — your implementation
130
+ MUST match its appearance.
131
+
132
+ **Structured design intent (when present on the card).** The image shows the
133
+ static look at one viewport; these fields cover what a single image cannot:
134
+ - `component_bindings[].props` / `variant` — the exact props/variant each
135
+ region must use (e.g. `SectionHeader {depth: 0}`, `DataTable {density:
136
+ comfortable}`). Implement them verbatim; do not eyeball a variant.
137
+ - the card's responsive acceptance criteria — breakpoint behaviour the
138
+ single-viewport mockup does not show (e.g. "≤640px: table collapses to
139
+ card-list"). These are testable ACs, not optional polish.
117
140
 
118
141
  **The mockup is a visual reference, NOT a license to re-create components.**
119
142
  When the card carries `component_bindings` (the `/prd` Component Reconciliation
@@ -41,10 +41,10 @@ has no blocking consequence the honest label is "monitoring signal".
41
41
  3. **Assemble the full JSONL record in memory** (single atomic append — JSONL is append-only, so the row must be COMPLETE before it touches the file). If `STATS=true` (step 6), the `"cost"` key is part of THIS record — do NOT write a baseline row now and rewrite it later (a mid-file rewrite corrupts the JSONL; there is no `sed -i`/`jq` rewrite step in this skill). When `STATS=true`, defer the single append to step 6 after the cost object is computed; when `STATS=false`, append now. The record shape:
42
42
 
43
43
  ```json
44
- {"ts":"<ISO-8601-UTC>","skill":"new","run_id":"batch-<FIRST-CARD-ID>","cards":["FEAT-XXX"],"total_cards":N,"first_attempt_success_rate":0.0,"mean_fix_cycles":0.0,"qa_profiles":{"skip":0,"light":0,"balanced":0,"deep":0},"qa_pass_first_attempt_rate":0.0,"findings_total":0,"findings_verified":0,"actionability_rate":0.0,"severity_p0_pct":0.0,"doc_gaps_found":0,"doc_gaps_fixed":0,"cycle_time_mins":0,"worktree_branch":"","merge_commit":""}
44
+ {"ts":"<ISO-8601-UTC>","skill":"new","run_id":"batch-<FIRST-CARD-ID>","session_id":"<$CLAUDE_CODE_SESSION_ID, '' if unset>","cards":["FEAT-XXX"],"total_cards":N,"first_attempt_success_rate":0.0,"mean_fix_cycles":0.0,"qa_profiles":{"skip":0,"light":0,"balanced":0,"deep":0},"qa_pass_first_attempt_rate":0.0,"findings_total":0,"findings_verified":0,"actionability_rate":0.0,"severity_p0_pct":0.0,"doc_gaps_found":0,"doc_gaps_fixed":0,"cycle_time_mins":0,"worktree_branch":"","merge_commit":""}
45
45
  ```
46
46
 
47
- Use `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. Append the assembled line ONCE with `echo '<full-record>' >> "$METRICS/skill-runs.jsonl"` via Bash — never append a partial row then mutate it.
47
+ Use `date -u +%Y-%m-%dT%H:%M:%SZ` for the timestamp. Fill `session_id` with `echo "${CLAUDE_CODE_SESSION_ID:-}"` (this run's session UUID — the same one stamped as `provenance.implementation_session` on each card; `""` if unset) so the telemetry row resolves to the chat transcript at `~/.claude/projects/*/<id>.jsonl`. Append the assembled line ONCE with `echo '<full-record>' >> "$METRICS/skill-runs.jsonl"` via Bash — never append a partial row then mutate it.
48
48
 
49
49
  4. Copy the batch tracker to archive:
50
50
 
@@ -153,8 +153,8 @@ For each completed agent:
153
153
  - A genuinely legitimate no-op card (its AC turned out already satisfied) is the rare exception: confirm it from the report's per-requirement `evidence` pointing at PRE-EXISTING code, and log it explicitly as `no-op (AC pre-satisfied)` — never assume zero-diff means success.
154
154
  **Classify the CAUSE before re-spawning — you MUST read the rested agent's LAST event, never infer the cause from "no report" alone.** A background teammate killed mid-flight dies WITHOUT delivering anything to you, so its failure signature lives ONLY in its on-disk transcript — "no report + no diff" is the SAME observable for a rate-limit death and a genuine empty-result, and they need OPPOSITE remedies. Before choosing one, locate and tail that transcript:
155
155
  - Subagent transcripts live at `<session-subagents-dir>/agent-*.jsonl`, each with a sibling `agent-*.meta.json` whose `agentType` equals the teammate label. Resolve the file and read its tail, e.g.:
156
- `D="$(ls -dt ~/.claude/projects/*/${CLAUDE_SESSION_ID:-*}/subagents 2>/dev/null | head -1)"; f="$(grep -l '"<teammate-label>"' "$D"/*.meta.json 2>/dev/null | head -1 | sed 's/\.meta\.json$/.jsonl/')"; tail -c 4000 "$f"`
157
- (if `$CLAUDE_SESSION_ID` is unset, the most recent `subagents/` dir under this project is the live session's.) If the transcript truly cannot be located, fall back to whatever the `Agent` tool result surfaced — but do NOT default to "genuine empty-result" without having looked. **State the classification you reached + the evidence in your narration** (e.g. "03 last event = `Rate limited` → transient").
156
+ `D="$(ls -dt ~/.claude/projects/*/${CLAUDE_CODE_SESSION_ID:-*}/subagents 2>/dev/null | head -1)"; f="$(grep -l '"<teammate-label>"' "$D"/*.meta.json 2>/dev/null | head -1 | sed 's/\.meta\.json$/.jsonl/')"; tail -c 4000 "$f"`
157
+ (if `$CLAUDE_CODE_SESSION_ID` is unset, the most recent `subagents/` dir under this project is the live session's.) If the transcript truly cannot be located, fall back to whatever the `Agent` tool result surfaced — but do NOT default to "genuine empty-result" without having looked. **State the classification you reached + the evidence in your narration** (e.g. "03 last event = `Rate limited` → transient").
158
158
  - **Transient infra failure (the common cause of a silent empty rest in a WIDE parallel wave).** The agent's last event is an API error — signatures: `isApiErrorMessage:true`, `Rate limited`, `Server is temporarily limiting requests`, `overload`, `(not your usage limit)` — i.e. the background teammate was **killed mid-flight by API rate-limiting** (it had done real work — reads, a plan — then died), NOT a model fabrication or "decided nothing to do". The runtime does NOT auto-resume a dead teammate, so it rests with no report + no diff. **Re-spawn it, but STAGGERED with backoff — do NOT re-fire several transient-failed agents in the same parallel burst** (that re-saturates the API and kills them again). Space them out (one at a time, or after a brief pause). This still costs a fresh run (the killed agent's reads are lost — the runtime cannot resume it), so **prevention beats recovery**: if a wave repeatedly hits rate limits, NARROW the parallel fan-out width for that wave (spawn fewer coders at once) rather than re-firing the full wave. A transient failure does NOT consume the Step-B genuine-failure budget. **(Workflow twin, since v4.66.0:** inside the `new-card-review` / `new-final-review` dynamic workflows the same transient-vs-terminal discipline is enforced in code — `agentSafe` retries a transient-killed reviewer in-workflow, and a reviewer that stays dead is recorded in `summary.degradedReviewers` rather than silently dropped by `filter(Boolean)`; a dead **qa-sentinel** specifically injects a synthetic `FAIL` gate so the merge is blocked, never passed on UNKNOWN mechanical gates. Same SSOT, two surfaces: this prose for orchestrator-spawned teammates, the workflow JS for in-workflow fan-out.)**
159
159
  - **Genuine empty-result / fabrication.** The agent rested CLEANLY (no error in its last event), with no completion report and no diff. THIS is the model-fault case: take the one Step-B re-spawn below (same cap), re-briefing the coder with an explicit "write your ownership files and confirm them on disk before reporting done" mandate. A second empty result → `AskUserQuestion` (skip/abandon) — do not re-spawn a third time. **When AUTONOMOUS, apply § AUTONOMOUS RESOLUTION RULE (SKILL.md): category=blocker; recommended=none safe (a card whose coder produced nothing twice has no implementation) → skip the card AND materialize a follow-up card carrying its spec (NEVER mark it DONE / no-op).** (Classify the cause first — a transient rate-limit death is re-spawned staggered, not counted against this budget.)
160
160
 
@@ -152,6 +152,7 @@ Workflow({ name: 'new2', args: {
152
152
  refModulesBase, // .claude/skills/new/references (semantic SSOT)
153
153
  config, // the parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
154
154
  ts, // ISO timestamp NOW — the workflow has no clock (Date.now() unavailable there)
155
+ sessionId, // this run's CLAUDE_CODE_SESSION_ID — stamped as provenance.implementation_session on each DONE card
155
156
  migration, // Step-3.5 manifest: { status:'none'|'applied'|'skipped'|'degraded', modality?, summary?, artifacts?, affects_cards? }
156
157
  flags: { stats, effort, full }
157
158
  }})
@@ -160,6 +161,12 @@ Workflow({ name: 'new2', args: {
160
161
  Record the kickoff time (`ts`) before the call — Step 5 uses it to stamp the
161
162
  real wall-clock duration (the workflow cannot read a clock).
162
163
 
164
+ Capture `sessionId` the same way, in THIS skill's own context (not a subagent):
165
+ `Bash(echo "${CLAUDE_CODE_SESSION_ID:-}")`. The workflow stamps it as
166
+ `provenance.implementation_session` on each card it marks DONE, so a batch is
167
+ traceable back to the chat history that ran it (`~/.claude/projects/*/<id>.jsonl`).
168
+ If the echo is empty, pass `sessionId: ''` and the workflow omits the field.
169
+
163
170
  The workflow runs the entire batch in the background. **Do not poll it** — it
164
171
  returns when the batch is done. It returns:
165
172
 
@@ -212,8 +219,10 @@ returns when the batch is done. It returns:
212
219
  - **Every class ∈ {`owner-gated`, `not-a-code-defect`, `policy-deferred-ac`}** → the card's own
213
220
  code is complete; the residual is an external/infra step. Now that step 1 guaranteed its
214
221
  deferral's follow-up exists on disk in the main repo, set the card `status: DONE` +
215
- `completed_date` + an implementation_note (`"DONE post-run (new2) AC deferred to follow-up
216
- <id>"`) in `${paths.backlog_dir}/<card>.yml`, and fold all of them into ONE reconciliation
222
+ `completed_date` + `provenance.implementation_session: <sessionId>` (the same id captured
223
+ for the workflow args merge into any existing provenance block, do NOT touch
224
+ `planning_session`; omit if empty) + an implementation_note (`"DONE post-run (new2) — AC
225
+ deferred to follow-up <id>"`) in `${paths.backlog_dir}/<card>.yml`, and fold all of them into ONE reconciliation
217
226
  commit in the MAIN repo.
218
227
  - **ANY class ∈ {`unresolved`, `out-of-ownership`, `outage`} (or missing)** → the card's DoD is
219
228
  genuinely NOT met (an AC the workflow tried and failed to implement). Leave it **IN_PROGRESS**
@@ -284,3 +284,14 @@ assumptions:
284
284
  unknowns: []
285
285
 
286
286
  notes: []
287
+
288
+ # --- Provenance (OPTIONAL — SKILL-written, never authored by hand) ---
289
+ # Records which Claude Code session produced this card, so an implementation error
290
+ # is traceable back to the chat history that planned/implemented it.
291
+ # Resolve the transcript at ~/.claude/projects/*/<session-id>.jsonl (UUID is globally unique).
292
+ # planning_session: written by prd-card-writer (value passed in by the /prd orchestrator as
293
+ # PLANNING_SESSION_ID — the orchestrator's CLAUDE_CODE_SESSION_ID, NOT the writer subagent's).
294
+ # implementation_session: added by /new (commit.md Step 28) or /new2 when status → DONE.
295
+ # Omit a sub-field when its session id is unavailable; an EPIC card has planning_session only.
296
+ provenance:
297
+ planning_session: "{{CLAUDE_CODE_SESSION_ID of the /prd run — omit if unset}}"
@@ -5,6 +5,10 @@ canonicality: canonical
5
5
  owner: prd
6
6
  status: draft
7
7
  freshness_status: fresh
8
+ # Session that authored this PRD — resolve the chat history at
9
+ # ~/.claude/projects/*/<id>.jsonl. Value = $CLAUDE_CODE_SESSION_ID of the /prd run.
10
+ # Omit the line if the env var is unset.
11
+ planning_session: {{CLAUDE_CODE_SESSION_ID of the /prd run}}
8
12
  ---
9
13
 
10
14
  # {{Feature Name}} PRD
@@ -194,6 +194,10 @@ Agent(
194
194
  directory, NOT into the consumer's main repo. See your "Working Directory"
195
195
  section for the rule and sanity check.)
196
196
 
197
+ PLANNING_SESSION_ID: <$PLANNING_SESSION_ID>
198
+ (The /prd run's session UUID — set provenance.planning_session on every card.
199
+ See your "Session provenance" section. Omit the field if this value is empty.)
200
+
197
201
  PRD path: <$WORKTREE_PATH>/<prd_path>
198
202
  State file path: <$WORKTREE_PATH>/<state_file_path>
199
203
  Card template: <$WORKTREE_PATH>/.claude/skills/prd/assets/card-template.yml
@@ -223,6 +227,13 @@ in the state file's `## Worktree` section BEFORE invoking the agent. Do NOT
223
227
  pass the literal string `$WORKTREE_PATH` — the subagent has no shell context
224
228
  to expand it.
225
229
 
230
+ The same applies to `<$PLANNING_SESSION_ID>`: BEFORE invoking the agent, the
231
+ `/prd` skill captures its OWN session id with `Bash(echo "${CLAUDE_CODE_SESSION_ID:-}")`
232
+ and substitutes the value (a UUID) into the prompt. The capture MUST run in the
233
+ orchestrator's own context (this skill), NOT inside the writer subagent — a
234
+ subagent's `CLAUDE_CODE_SESSION_ID` is its own, not the `/prd` conversation's.
235
+ If the echo prints empty, pass an empty value and the writer omits the field.
236
+
226
237
  ### What the agent handles
227
238
 
228
239
  The `prd-card-writer` agent owns the entire card writing pipeline:
@@ -1122,11 +1122,24 @@ mockup_analysis:
1122
1122
  purpose: <one line — what this screen lets the user do>
1123
1123
  components:
1124
1124
  - <component name from ${paths.design_system}/INDEX.md if matched, else free-form>
1125
+ layout: # OPTIONAL — compact nesting sketch (the hierarchy a flat list loses)
1126
+ - region: <e.g. "page-header">
1127
+ component: <component or free-form for this region>
1128
+ contains: [<child region/component>, …] # omit for leaf regions
1129
+ component_props: # OPTIONAL — key props/variants legible from the mockup, per component
1130
+ - component: <e.g. "SectionHeader">
1131
+ props: {<prop>: <value>} # e.g. {depth: 0} · {density: comfortable, selectable: true}
1125
1132
  affordances: # interactive + iconographic artifacts visible in the mockup
1126
1133
  - label: <e.g. "Export CSV button", "bell icon", "Filters tab">
1127
1134
  traces_to: <US-id / AC-id / discovery dimension this artifact serves — or "" if none yet>
1128
1135
  classification: backed | decorative | orphan
1129
1136
  states_visible: [empty, loading, error, success, default]
1137
+ states: # OPTIONAL — richer form: map each visible state to the AC that mandates it
1138
+ - state: <loading | empty | error | success | default>
1139
+ traces_to: <AC-id — or "" if no AC mandates it yet (a Discovery gap, see below)>
1140
+ representation: <one line, e.g. "skeleton rows ×5", "EmptyState, copy 'Nessun ordine'">
1141
+ responsive: # OPTIONAL — breakpoint behaviour a single-viewport mockup cannot show
1142
+ - <e.g. "≤640px: DataTable collapses to card-list; header shows back button">
1130
1143
  copy_excerpts:
1131
1144
  - <verbatim text visible in the mockup, max ~10 strings per screen>
1132
1145
  gaps:
@@ -1148,11 +1161,21 @@ mockup_analysis:
1148
1161
  - Omit `copy_excerpts` for screens that have no readable text (icon-only).
1149
1162
  - Omit `states_visible` for screens that show only the default state — do not
1150
1163
  invent states the mockup doesn't show.
1164
+ - `layout`, `component_props`, `states`, and `responsive` are OPTIONAL
1165
+ enrichments. Fill them when the mockup makes them legible — they carry exactly
1166
+ the intent a flat affordance list and a single static image lose (nesting
1167
+ hierarchy, the exact component variant, state→AC mapping, breakpoint
1168
+ behaviour). Omit a field rather than guess; never invent a breakpoint,
1169
+ prop, or nesting the mockup doesn't actually show (same rule as `states_visible`).
1170
+ These feed the Component Reconciliation (reuse vs `reuse-variant`) and the
1171
+ `/new` implementer briefing — they are how visual intent survives to code.
1151
1172
 
1152
1173
  **Gap discipline:** every entry in `gaps[]` MUST become either a Discovery question
1153
1174
  or an explicit non-question (recorded in Discovery Log as "skipped — outside
1154
1175
  mockup scope"). Gaps that remain unresolved by the end of Discovery block PRD
1155
- writing in Step 4.
1176
+ writing in Step 4. A `states[]` entry with `traces_to: ""` is a gap by the same
1177
+ rule — a visible state no AC mandates must be resolved in Discovery (gain an AC,
1178
+ or be confirmed cosmetic), exactly like an untraced affordance.
1156
1179
 
1157
1180
  **Affordance discipline (Functional Traceability):** classify every interactive
1158
1181
  or iconographic artifact the mockup shows. The mockup is a fidelity target, not
@@ -97,6 +97,11 @@ Mark task 3 as `in_progress`.
97
97
  - Include a "Research-Informed Decisions" subsection listing decisions influenced
98
98
  by research, with brief rationale and source reference.
99
99
  5. Write `${paths.prd_dir}/<slug>/PRD.md` using the template from `assets/prd-template.md`.
100
+ Fill the frontmatter `planning_session` with this run's session id —
101
+ `Bash(echo "${CLAUDE_CODE_SESSION_ID:-}")` (run it in this skill's own context,
102
+ not a subagent). If the echo is empty, drop the `planning_session:` line. This is
103
+ the same UUID passed to `prd-card-writer` as `PLANNING_SESSION_ID`, so the PRD and
104
+ its cards share one traceable session pointer.
100
105
 
101
106
  ### PRD sections checklist
102
107
 
@@ -158,7 +158,10 @@ Before invoking `ui-design`:
158
158
  - **REUSE** — an existing component (or a closed-family canonical member) covers
159
159
  it. Record the exact component. *No question asked — this is the silent path.*
160
160
  - **REUSE+VARIANT** — an existing component covers it but the mockup needs a
161
- prop/variant not yet in its spec.
161
+ prop/variant not yet in its spec. The `mockup_analysis.component_props` for
162
+ this component is the signal: compare the legible props against the
163
+ component's spec HEAD — props the spec doesn't cover ⇒ this is a VARIANT,
164
+ not a plain REUSE.
162
165
  - **NEW** — genuinely no match.
163
166
  5. **Ask ONLY on ambiguity** (`REUSE+VARIANT` and `NEW` — never on plain REUSE).
164
167
  One consolidated `AskUserQuestion` per ambiguous element, in the
@@ -179,7 +182,12 @@ Before invoking `ui-design`:
179
182
  - Into each affected card → the `component_bindings` block (see
180
183
  `agents/card-schema.md`): `region → component (reuse | new) [+ variant]`. This
181
184
  is the authority `ui-expert` implements against (the mockup becomes a visual
182
- reference, not a license to re-create).
185
+ reference, not a license to re-create). **Carry the resolved props** from
186
+ `mockup_analysis.component_props` into `component_bindings[].props` so the
187
+ exact-variant intent reaches the implementer (a single static image leaves it
188
+ ambiguous). Likewise, lift any `mockup_analysis.screens[].responsive` notes
189
+ into the UI card's `acceptance_criteria` as testable responsive ACs — the
190
+ mockup cannot show breakpoint behaviour at one viewport.
183
191
  - Into the manifest → when the user confirms a new standard / closure, the
184
192
  component's `canonical_for` / `use_when` / `selection_closed` are written (via
185
193
  `/design-system-init` enrich or `doc-reviewer`), so `baldart ds-gate` enforces
@@ -222,7 +230,7 @@ Pass the resolved inventory to `ui-design`:
222
230
  ```
223
231
  ### Component Reconciliation (resolved)
224
232
  REUSE — bind region → existing component:
225
- - [region] → [Component] — [path] — [variant if any]
233
+ - [region] → [Component] — [path] — [variant if any] — [props if any]
226
234
 
227
235
  NEW (governed) — confirmed with user:
228
236
  - [region] → [NewComponent] — role: [family@context] — standard: [yes/no] — closed: [yes/no]
@@ -29,6 +29,8 @@ export const meta = {
29
29
  // affected cards' apply-AC is NOT owner-gated-deferred (F-016 exception).
30
30
  // flags { stats, effort, full }
31
31
  // ts string ISO timestamp injected by the skill (Date.now() is unavailable here)
32
+ // sessionId string this run's CLAUDE_CODE_SESSION_ID (skill-captured) — stamped as
33
+ // provenance.implementation_session on each card marked DONE ('' → omit)
32
34
  //
33
35
  // return (consumed by the skill at Step 5):
34
36
  // { report, perCardResults, gateLedger, residualFollowups, telemetry, degraded, degradationReasons, residuals }
@@ -52,6 +54,12 @@ const METRICS = a.metricsDir || 'docs/metrics'
52
54
  const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
53
55
  const FLAGS = a.flags || {}
54
56
  const TS = a.ts || ''
57
+ // Session provenance — stamped on each DONE card so an implementation bug is traceable to the
58
+ // chat history that produced it. '' (env var unset) → the commit agent omits the field.
59
+ const SESSION_ID = a.sessionId || ''
60
+ const provLine = SESSION_ID
61
+ ? ` Also set provenance.implementation_session: "${SESSION_ID}" (merge into any existing provenance block from /prd — do NOT touch planning_session).`
62
+ : ''
55
63
  // Migration Gate manifest (skill Step 3.5 — front-loaded BEFORE this workflow ran). When
56
64
  // status:'applied', the declared schema is LIVE on the active DB, so its apply-AC must NOT be
57
65
  // re-classified as an owner-gated policy-deferred AC (F-016) — the downstream cards verify for real.
@@ -775,7 +783,7 @@ async function runCard(cardId, cardPath) {
775
783
  // main repo (so a card is never DONE with a silently-dropped requirement — F-029).
776
784
  const doneStep = deferredOpen
777
785
  ? `(4) DO NOT mark the card DONE: it has an OPEN owner-gated/policy-deferred AC. Keep status IN_PROGRESS and add an implementation_note "deferred — DONE pending follow-up (new2 skill reconciles post-run)". STILL add the ssot-registry row for the committed code.`
778
- : `(4) mark the card DONE in its YAML + add the ssot-registry row.`
786
+ : `(4) mark the card DONE in its YAML + add the ssot-registry row.${provLine}`
779
787
  let commitRes
780
788
  try {
781
789
  commitRes = await agentSafe(
@@ -968,7 +976,7 @@ async function integrateCrossCard() {
968
976
  try {
969
977
  await agentSafe(
970
978
  `Commit the cross-card integration fixes in worktree ${sharedCtx.worktreePath}. MECHANICAL — do NOT modify source beyond what is already changed on disk. Steps:\n` +
971
- `(1) \`git status --porcelain\`; (2) stage ONLY dirty files within the batch union ${JSON.stringify(batchUnion)} — NEVER \`git add -A\`, NEVER \`git stash\`; (3) for each fully-freed card YAML [${fullyFreed.join(', ') || '(none)'}] set status to DONE and add implementation_note "cross-card integration: deferred AC resolved with batch-union ownership (new2 v4.30.0)"; (4) commit message \`[integrate] cross-card residual fixes${fullyFreed.length ? ' (' + fullyFreed.join(', ') + ')' : ''}\`; (5) 'nothing to commit' = the resolve already committed (record HEAD).\n` +
979
+ `(1) \`git status --porcelain\`; (2) stage ONLY dirty files within the batch union ${JSON.stringify(batchUnion)} — NEVER \`git add -A\`, NEVER \`git stash\`; (3) for each fully-freed card YAML [${fullyFreed.join(', ') || '(none)'}] set status to DONE and add implementation_note "cross-card integration: deferred AC resolved with batch-union ownership (new2 v4.30.0)"${SESSION_ID ? ' and set provenance.implementation_session: "' + SESSION_ID + '" (do NOT touch planning_session)' : ''}; (4) commit message \`[integrate] cross-card residual fixes${fullyFreed.length ? ' (' + fullyFreed.join(', ') + ')' : ''}\`; (5) 'nothing to commit' = the resolve already committed (record HEAD).\n` +
972
980
  `Return: { committed, commit, filesChanged }`,
973
981
  { label: 'integrate:commit', phase: 'Integrate', agentType: 'general-purpose', model: 'haiku',
974
982
  schema: { type: 'object', required: ['committed'], additionalProperties: true, properties: { committed: { type: 'boolean' }, commit: { type: 'string' }, filesChanged: { type: 'array', items: { type: 'string' } } } } }
@@ -74,6 +74,7 @@ Legend: **R** = required, present **and non-empty** · **E** = required key, val
74
74
  | `db_indexes` | — | C | C | only if it adds a compound query |
75
75
  | `test_plan` · `integration_points` · `existing_patterns` · `anti_patterns` · `reuse_analysis` · `input_output_examples` · `error_handling` | C | C | C | populate when applicable (see `prd-card-writer.md` Required Fields) |
76
76
  | `assumptions` · `unknowns` · `notes` | C | C | C | |
77
+ | `provenance` | C | C | C | session-id traceability — written by the SKILL, never the user; see § `provenance` below |
77
78
 
78
79
  ## Consumer contract — HALT / BACK-FILL / WARN
79
80
 
@@ -147,6 +148,7 @@ component_bindings:
147
148
  action: reuse # reuse | reuse-variant | new
148
149
  variant: "" # when action: reuse-variant
149
150
  role: "page-header@depth-0" # when it maps a Selection-Policy role
151
+ props: {depth: 0} # optional — exact props the region must use (from mockup_props)
150
152
  - region: "row actions"
151
153
  component: IconButton
152
154
  action: reuse
@@ -158,6 +160,42 @@ that finds a `component_bindings` entry MUST honor it over any contrary reading
158
160
  the mockup; a UI card that went through `/prd` with `features.has_design_system:
159
161
  true` but lacks this field is under-specified (the reconciliation did not run).
160
162
 
163
+ `props` is **optional** — the resolved key props/variant the region must use,
164
+ lifted from the mockup analysis `component_props` during reconciliation (e.g.
165
+ `{depth: 0}`, `{density: comfortable}`). It carries the exact-variant intent a
166
+ single static image leaves ambiguous; `ui-expert` implements it verbatim. Omit it
167
+ when the mockup exposes no distinguishing props (the component's defaults apply).
168
+ Breakpoint behaviour the mockup cannot show at a single viewport lives in the
169
+ card's `acceptance_criteria` as testable responsive ACs, not here.
170
+
171
+ ### `provenance` (session-id traceability — optional, skill-written)
172
+
173
+ A small block that records **which Claude Code session produced the card**, so a card
174
+ that later turns out wrong is traceable back to the chat history that planned or
175
+ implemented it (read the transcript at `~/.claude/projects/*/<session-id>.jsonl`).
176
+
177
+ ```yaml
178
+ provenance:
179
+ planning_session: "fd8a0411-8946-45b6-a671-c7f747ca67e6" # /prd — the session that AUTHORED this card
180
+ implementation_session: "9c2e17b0-1a3d-4f88-bb20-7e0c4a9d51fe" # /new|/new2 — the session that took it to status: DONE
181
+ ```
182
+
183
+ Rules:
184
+ - **Optional (`C`)** in every profile — legacy cards lack it and never HALT; the validator
185
+ ignores it. It is **never** authored by a human; the skills fill it.
186
+ - The value is the canonical session UUID from the env var **`CLAUDE_CODE_SESSION_ID`**
187
+ (NOT `CLAUDE_SESSION_ID`, which does not exist). Resolve a transcript with
188
+ `ls ~/.claude/projects/*/<uuid>.jsonl` — the UUID is globally unique, so the project slug
189
+ is not needed.
190
+ - **Ownership is split by lifecycle**: `prd-card-writer` writes `planning_session` at creation
191
+ (the value is **passed in by the `/prd` orchestrator** as `PLANNING_SESSION_ID` — never read
192
+ from inside the writer subagent, whose own session id differs). `/new` (`commit.md` Step 28)
193
+ and `/new2` (`new2.js` commit step) add `implementation_session` when they set `status: DONE`.
194
+ - An **EPIC** card is a tracker, never implemented → it carries `planning_session` only, never
195
+ `implementation_session`.
196
+ - Omit any sub-field whose session id is unavailable (env var unset) rather than writing a
197
+ placeholder.
198
+
161
199
  ## Writer contract
162
200
 
163
201
  The canonical writer is the `prd-card-writer` agent. It emits the full baseline for the
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.71.0",
3
+ "version": "4.73.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"