baldart 4.71.0 → 4.72.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,28 @@ 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.72.0] - 2026-06-25
9
+
10
+ **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.
11
+
12
+ 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`).
13
+
14
+ **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.
15
+
16
+ ### Added
17
+
18
+ - **`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`).
19
+ - **`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.
20
+
21
+ ### Changed
22
+
23
+ - **`/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).
24
+ - **`/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`.
25
+
26
+ ### Fixed
27
+
28
+ - **`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.
29
+
8
30
  ## [4.71.0] - 2026-06-25
9
31
 
10
32
  **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.72.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.
@@ -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:
@@ -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
 
@@ -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
 
@@ -158,6 +159,34 @@ that finds a `component_bindings` entry MUST honor it over any contrary reading
158
159
  the mockup; a UI card that went through `/prd` with `features.has_design_system:
159
160
  true` but lacks this field is under-specified (the reconciliation did not run).
160
161
 
162
+ ### `provenance` (session-id traceability — optional, skill-written)
163
+
164
+ A small block that records **which Claude Code session produced the card**, so a card
165
+ that later turns out wrong is traceable back to the chat history that planned or
166
+ implemented it (read the transcript at `~/.claude/projects/*/<session-id>.jsonl`).
167
+
168
+ ```yaml
169
+ provenance:
170
+ planning_session: "fd8a0411-8946-45b6-a671-c7f747ca67e6" # /prd — the session that AUTHORED this card
171
+ implementation_session: "9c2e17b0-1a3d-4f88-bb20-7e0c4a9d51fe" # /new|/new2 — the session that took it to status: DONE
172
+ ```
173
+
174
+ Rules:
175
+ - **Optional (`C`)** in every profile — legacy cards lack it and never HALT; the validator
176
+ ignores it. It is **never** authored by a human; the skills fill it.
177
+ - The value is the canonical session UUID from the env var **`CLAUDE_CODE_SESSION_ID`**
178
+ (NOT `CLAUDE_SESSION_ID`, which does not exist). Resolve a transcript with
179
+ `ls ~/.claude/projects/*/<uuid>.jsonl` — the UUID is globally unique, so the project slug
180
+ is not needed.
181
+ - **Ownership is split by lifecycle**: `prd-card-writer` writes `planning_session` at creation
182
+ (the value is **passed in by the `/prd` orchestrator** as `PLANNING_SESSION_ID` — never read
183
+ from inside the writer subagent, whose own session id differs). `/new` (`commit.md` Step 28)
184
+ and `/new2` (`new2.js` commit step) add `implementation_session` when they set `status: DONE`.
185
+ - An **EPIC** card is a tracker, never implemented → it carries `planning_session` only, never
186
+ `implementation_session`.
187
+ - Omit any sub-field whose session id is unavailable (env var unset) rather than writing a
188
+ placeholder.
189
+
161
190
  ## Writer contract
162
191
 
163
192
  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.72.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"