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 +38 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/prd-card-writer.md +20 -0
- package/framework/.claude/skills/new/references/commit.md +1 -1
- package/framework/.claude/skills/new/references/implement.md +26 -3
- package/framework/.claude/skills/new/references/metrics.md +2 -2
- package/framework/.claude/skills/new/references/team-mode.md +2 -2
- package/framework/.claude/skills/new2/SKILL.md +11 -2
- package/framework/.claude/skills/prd/assets/card-template.yml +11 -0
- package/framework/.claude/skills/prd/assets/prd-template.md +4 -0
- package/framework/.claude/skills/prd/references/backlog-phase.md +11 -0
- package/framework/.claude/skills/prd/references/discovery-phase.md +24 -1
- package/framework/.claude/skills/prd/references/prd-writing-phase.md +5 -0
- package/framework/.claude/skills/prd/references/ui-design-phase.md +11 -3
- package/framework/.claude/workflows/new2.js +10 -2
- package/framework/agents/card-schema.md +38 -0
- package/package.json +1 -1
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.
|
|
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
|
-
|
|
115
|
-
|
|
116
|
-
|
|
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/*/${
|
|
157
|
-
(if `$
|
|
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` +
|
|
216
|
-
|
|
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
|