baldart 4.2.1 → 4.3.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,31 @@ 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.3.0] - 2026-06-03
9
+
10
+ **`/new` skips its pre-flight cross-card Codex check when `/prd` already audited the same batch jointly and nothing drifted since.** The `/prd` Step 6.6 holistic audit (4-agent team + Codex adversarial pass, 6.6d) already reviews the full card set for `files_likely_touched` conflicts, dependency shadows, implicit ordering, and shared-state mutation — the *exact* lens of `/new` Step 3d's cross-card check. Running Step 3d unconditionally re-paid that Codex call on every multi-card batch, even when implementing the freshly-audited PRD set with zero intervening change. Now the audit phase **stamps provenance** on each card (`metadata.holistic_audit`: `audited_at` + `audited_commit` + `audited_set`), and `/new` Step 3d evaluates a four-condition skip: provenance present (S1), batch jointly audited (S2), batch ⊆ audited set (S3), and no commit touched the batch's claimed paths since the audit baseline (S4). All four hold → SKIP (logged, non-decision); any fails → RUN as before. **MINOR** (new card-template field + new conditional capability; NOT a `baldart.config.yml` key — `metadata.holistic_audit` is a backlog-card field written by the audit phase).
11
+
12
+ > **Why.** The two checks are not "different lenses" — Step 3d's `FILE_CONFLICT / IMPLICIT_DEP / ORDER_RISK / STATE_MUTATION` questions map 1:1 onto 6.6d's `attack_surface`, run over the same card set + adjacent cards. The only residual value of re-running at implementation time is the two cases the authoring-time audit *cannot* have covered: (a) the `/new` batch is not the audited set (a subset, or cards stitched from separate PRD runs), or (b) the codebase drifted on the claimed paths between audit and implementation. The gate isolates exactly those two cases via provenance + a `git log <audited_commit>..<trunk> -- <claimed_paths>` drift probe, and **degrades safely in every direction** — missing/partial provenance, mismatched sets, empty/unreachable commit, or any git error all fall through to RUN, never to a silent skip. Aligns with the standing "data-driven over arbitrary thresholds" and "no silent confirmation gates for non-decisions" principles: the skip is deterministic and auditable (the RUN/SKIP reason is logged in the tracker), not a heuristic.
13
+
14
+ ### Added
15
+
16
+ - **`framework/templates/feature-card.template.yml`** — documents the optional `metadata.holistic_audit` block (`audited_at` / `audited_commit` / `audited_set`), written by the audit phase (not at authoring), consumed by `/new` Step 3d.
17
+
18
+ ### Changed
19
+
20
+ - **`framework/.claude/skills/prd/references/audit-phase.md`** — Step 6.9 (Apply Findings to Cards) adds a "Holistic-audit provenance stamp" subsection: capture three run-level values once (`audited_at` = UTC now, `audited_commit` = `git merge-base HEAD <trunk>` fork point, `audited_set` = sorted jointly-audited card IDs) and write `metadata.holistic_audit` on each card. New per-card workflow step (was 5 steps, now 6). Fail-safe contract: the stamp is an optimization hint, never a gate — never blocks the PRD if it can't be written.
21
+ - **`framework/.claude/skills/new/SKILL.md`** — Step 3d replaces the flat "Skip if batch has only 1 card" with a provenance-aware four-condition skip decision (S1 provenance present, S2 jointly audited, S3 batch ⊆ audited set, S4 no drift on claimed paths via `git log <audited_commit>..<trunk> -- <paths>`). SKIP logs `SKIPPED (provenance) — …` and proceeds with the file-ownership map unchanged; otherwise RUN with the reason recorded. Tracker `## Cross-Card Conflicts (Codex)` placeholder updated to note the possible skip. Every ambiguity falls through to RUN.
22
+
23
+ ## [4.2.2] - 2026-06-03
24
+
25
+ **`/new` Phase 0 stops blocking on framework-owned noise: telemetry-only dirty trees and `baldart update`/subtree commits are auto-ignored.** Every batch tripped its own Phase 0 gates: (1) the dirty-tree gate fired on `${paths.metrics}/archive/batch-tracker-*.md` + `skill-runs.jsonl` + `sessions/` — telemetry that Phase 8 writes *after* the merge and never commits, so the next batch always saw a "dirty" tree and forced a stash/commit decision; (2) the divergence gate fired on `.framework/` subtree commits + the `[CHORE] baldart update …` chore, treating expected framework-management divergence as the FEAT-0006 orphan-commit pattern and forcing a push/cherry-pick decision. Both gates now **partition the working tree / the ahead-commits before prompting** and surface only genuine in-progress user work. The FEAT-0006 protection is intact — real orphan commits and real uncommitted work still gate. **PATCH** (bugfix to an existing gate's classification; no new agent/skill/template, no `baldart.config.yml` key — `paths.metrics` already exists).
26
+
27
+ > **Why.** Phase 0 exists because of a real incident (FEAT-0006: an unpushed orphan commit + diverged trunk caused data loss), so the gates can't be removed. But they were classifying *framework-generated artifacts as if they were user state* — the skill's own Phase 8 telemetry and the `baldart update` subtree commits — and raising a non-decision to the user on every single `/new` run. The fix is surgical classification, not relaxation: the dirty-tree gate filters paths under `$METRICS` (resolved from `paths.metrics`) and gates only on the remainder; the divergence gate reclassifies each ahead commit (subtree merge into `.framework/`, `baldart update`/`add` chore, or `.framework/`-only diff) as framework-management and counts only genuine unpushed commits. Phase 6c's batch-close assertion gets the same reclassification so the noise doesn't re-appear at the end. `/prd` and `/nw` were already lean (read-only pre-flight, branch from `origin/$TRUNK`) and needed no change. Aligns with the standing "respect explicit user intent — no silent confirmation gates for non-decisions" principle.
28
+
29
+ ### Changed
30
+
31
+ - **`framework/.claude/skills/new/SKILL.md`** — Phase 0 step 0 now resolves `$METRICS` (`paths.metrics`, default `docs/metrics`). Step 3 (dirty-tree gate) partitions `git status --porcelain`, dropping framework telemetry under `$METRICS`; gates only on `$USER_DIRTY`, otherwise logs `Dirty: framework-telemetry-only (auto-ignored)` and proceeds with no `AskUserQuestion`. Step 4 (divergence gate) and Phase 6c step 4 (batch-close divergence assertion) reclassify ahead commits, count only `$GENUINE_AHEAD`, auto-ignore `$FW_SKIPPED` framework-management commits, and branch on `$BEHIND`/`$GENUINE_AHEAD`. Step 5 log template extended with the two new auto-ignored states.
32
+
8
33
  ## [4.2.1] - 2026-06-03
9
34
 
10
35
  **`/prd` pre-dev audit: `code-reviewer` swapped for `plan-auditor` (card grounding) in the `check-audit` team.** The Step 6 pre-development audit ran `code-reviewer` on backlog `.yml` cards — but `code-reviewer` is built around a *diff* (scope = `git diff --name-only`, diff-simulation pass, completion-report cross-check, line-range/`repro_steps` findings schema, `PASS/FAIL/NEEDS_REWORK` verdict), none of which applies before any code exists. The audit had to re-purpose it with an ad-hoc mandate and override its native output format. `plan-auditor` is the agent purpose-built for this surface (native `.yml` INPUT TYPE DETECTION + BACKLOG CARD ATTACK SURFACE + native `[Target: <field>]` tagging + project memory), so it now fills that slot. **PATCH** (refinement of an existing capability — swaps which already-shipped agent audits cards; no new agent/skill/template, no `baldart.config.yml` key).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.2.1
1
+ 4.3.0
@@ -100,7 +100,7 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
100
100
  |------|--------------------------|
101
101
 
102
102
  ## Cross-Card Conflicts (Codex)
103
- (pending — runs during worktree setup for batches > 1 card)
103
+ (pending — for batches > 1 card; may be SKIPPED if the batch was already audited jointly by /prd Step 6.6 with no drift since — see Step 3d skip decision)
104
104
 
105
105
  ## Issues & Flags
106
106
  (none yet)
@@ -141,6 +141,7 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
141
141
  0. **Feature gate + config resolution (BLOCKING)** — before anything else:
142
142
  - Read `features.has_backlog` from `baldart.config.yml`. If it is `false` (or the key is absent and the user confirms there is no backlog), this skill REFUSES to run: print "`/new` operates on backlog cards and requires `features.has_backlog: true` — run `npx baldart configure` to enable." and HALT. Do NOT proceed to step 1.
143
143
  - Resolve `$TRUNK` = `git.trunk_branch` from `baldart.config.yml`. **When the key is absent** (consumer updated to ≥4.0.0 without re-running `configure`), do NOT hard-assume `develop` — autodetect the repo's real default branch exactly as worktree-manager does, so `/new` and `nw` agree on the base: `git -C "$MAIN" symbolic-ref --quiet refs/remotes/origin/HEAD` (strip `refs/remotes/origin/`), else the first existing local branch among `develop` / `main` / `master`. A `main`-trunk repo defaulted to `develop` here would diverge from the worktree base `nw` picks and break every `git diff "$TRUNK...HEAD"` gate. Only HALT ("Trunk branch unresolved — run `npx baldart configure`") if nothing resolves. Persist the resolved value as `Trunk branch:` in the tracker `## Worktree` section. **Every later Phase 0 / Phase 6c bash snippet that references the integration trunk MUST use `$TRUNK`, never a baked-in `develop`.** Begin every later consumer with a guard: if `$TRUNK` is empty → HALT with "Trunk branch unresolved — re-read `git.trunk_branch` from the tracker".
144
+ - Resolve `$METRICS` = `paths.metrics` from `baldart.config.yml` (default `docs/metrics`). This is the framework-owned telemetry directory written by Phase 8 (tracker archive, `skill-runs.jsonl`, `sessions/`). The dirty-tree gate (step 3) reads `$METRICS` to recognise — and never surface — its own telemetry output. Persist it as `Metrics dir:` in the tracker `## Worktree` section.
144
145
 
145
146
  1. **Resolve `$MAIN`** — the absolute path of the main repo (not a worktree). If `/new` was invoked from inside a worktree, walk up to the parent repo via `git rev-parse --show-superproject-working-tree` or `git worktree list` until you find the non-worktree root. Persist as `Main repo:` in the tracker `## Worktree` section. **Write `$MAIN` to the tracker the moment it is computed** — every later consumer (Phase 6c, Phase 6b) MUST re-read it from the tracker and HALT with "`$MAIN` absent from tracker" if the field is missing or empty, never silently use an undefined `$MAIN` (it does not survive context compaction).
146
147
 
@@ -149,11 +150,20 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
149
150
  git -C "$MAIN" fetch origin --quiet
150
151
  ```
151
152
 
152
- 3. **Dirty-tree gate (BLOCKING)** — capture the working tree state of the main repo:
153
+ 3. **Dirty-tree gate (BLOCKING — framework telemetry is auto-ignored, never surfaced)** — capture the working tree state of the main repo, then **partition it** so the gate fires only on genuine in-progress user work:
153
154
  ```bash
154
- git -C "$MAIN" status --porcelain
155
- ```
156
- If the output is non-empty, invoke `AskUserQuestion`:
155
+ PORCELAIN="$(git -C "$MAIN" status --porcelain)"
156
+ # Drop framework-owned telemetry THIS skill writes in Phase 8 (tracker archive,
157
+ # skill-runs.jsonl, sessions/) and never commits. Porcelain lines are "XY path";
158
+ # paths with special chars are quoted. $METRICS resolved in step 0 (e.g. docs/metrics).
159
+ USER_DIRTY="$(printf '%s\n' "$PORCELAIN" \
160
+ | grep -vE "^.{3}\"?${METRICS}/" \
161
+ | grep -vE "^.{3}\"?${METRICS}\"?$" \
162
+ | grep -v '^[[:space:]]*$')"
163
+ ```
164
+ **Why**: Phase 8 copies the batch tracker into `$METRICS/archive/` and appends to `$METRICS/skill-runs.jsonl` *after* the merge, and never commits them. If those untracked telemetry files tripped this gate, **every batch would block on the previous batch's metrics** — a self-inflicted loop. They are not user work and the user is never asked about them.
165
+ - If `$PORCELAIN` is non-empty but `$USER_DIRTY` is **empty** (only telemetry under `$METRICS` is dirty) → do **NOT** gate. Write `Dirty: framework-telemetry-only (auto-ignored)` to the tracker and proceed to step 4. No `AskUserQuestion`.
166
+ - If `$USER_DIRTY` is **non-empty** (genuine uncommitted user work) → invoke `AskUserQuestion`:
157
167
  - Question: `"Main repo ($MAIN) ha modifiche non committate prima di partire con la batch. Come procedo?"`
158
168
  - Options (max 4):
159
169
  1. **"Stash automatico (restore in Phase 6c)"** — `$MAIN` is the main checkout (NOT a worktree), so a stash here is safe. Run `git -C "$MAIN" stash push -u -m "baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>"` and write the **stash MESSAGE label** (`baldart-new-phase0-<FIRST-CARD-ID>-<timestamp>`) to the tracker under `## Workspace Snapshot: <message-label> (created <timestamp>)`. Phase 6c resolves the stash by this message — NEVER by a positional `stash@{N}`, because other stashes pushed during the batch (on the main checkout) would shift the index. Do NOT record `stash@{0}`.
@@ -163,23 +173,40 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
163
173
 
164
174
  Do NOT silently proceed. Do NOT auto-stash without the user picking option 1.
165
175
 
166
- 4. **Divergence gate (BLOCKING)** — compare local `$TRUNK` against `origin/$TRUNK` (the trunk resolved in step 0):
176
+ 4. **Divergence gate (BLOCKING — framework-management commits do NOT count as orphan commits)** — compare local `$TRUNK` against `origin/$TRUNK` (the trunk resolved in step 0):
167
177
  ```bash
168
- git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK"
178
+ read BEHIND AHEAD <<< "$(git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK")"
169
179
  ```
170
- The output is `<behind>\t<ahead>`. Branch on the result:
171
-
172
- - **`0\t0`** synchronized. Proceed to step 5.
173
- - **`N\t0`** (only behind, N > 0) — invoke `AskUserQuestion`:
174
- - Question: `"Local $TRUNK è behind origin/$TRUNK di N commit. Come procedo?"`
180
+ `$BEHIND` / `$AHEAD` are the raw counts. Before branching, **reclassify the ahead commits**: commits produced by `baldart update`/`baldart add` (subtree merges into `.framework/`, the `[CHORE] baldart update …` commit, and the regenerated overlay files they carry) are *expected* divergence, not the FEAT-0006 orphan-commit pattern. Count only **genuine** unpushed commits:
181
+ ```bash
182
+ GENUINE_AHEAD=0; GENUINE_LIST=""; FW_SKIPPED=0
183
+ for sha in $(git -C "$MAIN" rev-list "origin/$TRUNK..$TRUNK"); do
184
+ subj="$(git -C "$MAIN" log -1 --format=%s "$sha")"
185
+ # subtree merge (e.g. "Merge commit '…' as '.framework'") or baldart update/add chore
186
+ if printf '%s' "$subj" | grep -qE "as '\.framework'" || printf '%s' "$subj" | grep -qiE "baldart (update|add)"; then
187
+ FW_SKIPPED=$((FW_SKIPPED+1)); continue
188
+ fi
189
+ # touches ONLY files under .framework/ ? (empty file list = merge commit, handled above)
190
+ files="$(git -C "$MAIN" show --pretty=format: --name-only "$sha" | grep -v '^$')"
191
+ if [ -n "$files" ] && ! printf '%s\n' "$files" | grep -qvE '^\.framework/'; then
192
+ FW_SKIPPED=$((FW_SKIPPED+1)); continue
193
+ fi
194
+ GENUINE_AHEAD=$((GENUINE_AHEAD+1)); GENUINE_LIST="$GENUINE_LIST $sha"
195
+ done
196
+ ```
197
+ `$FW_SKIPPED` framework-management commits are auto-ignored (log them — see below). Branch on `$BEHIND` and the **genuine** ahead count `$GENUINE_AHEAD` (NOT the raw `$AHEAD`):
198
+
199
+ - **`BEHIND=0` and `GENUINE_AHEAD=0`** — synchronized for `/new`'s purposes. If `$FW_SKIPPED > 0`, write `Divergence: $FW_SKIPPED framework-management commits ahead (auto-ignored — baldart update/subtree)` to the tracker. **No `AskUserQuestion`.** Proceed to step 5.
200
+ - **`BEHIND>0` and `GENUINE_AHEAD=0`** (only behind) — invoke `AskUserQuestion`:
201
+ - Question: `"Local $TRUNK è behind origin/$TRUNK di $BEHIND commit. Come procedo?"`
175
202
  - Options: `[Fast-forward pull adesso]` / `[Procedi senza pull]` / `[Abort]`.
176
203
  - On `Fast-forward pull adesso`: if `$MAIN` HEAD == `$TRUNK`, run `git -C "$MAIN" pull --ff-only origin "$TRUNK"`; otherwise log "main HEAD is `<branch>` — fetch already updated `refs/remotes/origin/$TRUNK`, local `$TRUNK` ref will be reconciled in Phase 6c".
177
- - **`0\tM`** (only ahead, M > 0 UNPUSHED LOCAL COMMITS, this is the FEAT-0006 pattern) — invoke `AskUserQuestion`:
178
- - Question: `"Local $TRUNK è ahead di origin/$TRUNK di M commit (commit locali non pushati). Cosa faccio?"`
179
- - Show the commit list inline: `git -C "$MAIN" log --oneline "origin/$TRUNK..$TRUNK"`.
204
+ - **`BEHIND=0` and `GENUINE_AHEAD>0`** (genuine UNPUSHED LOCAL COMMITS, this is the FEAT-0006 pattern) — invoke `AskUserQuestion`:
205
+ - Question: `"Local $TRUNK è ahead di origin/$TRUNK di $GENUINE_AHEAD commit locali non pushati (esclusi $FW_SKIPPED commit di gestione framework). Cosa faccio?"`
206
+ - Show **only the genuine** commits inline: `git -C "$MAIN" show -s --oneline $GENUINE_LIST`.
180
207
  - Options: `[Push adesso (git push origin $TRUNK)]` / `[Cherry-pick a parte e continua]` / `[Lascia così e procedi (lo gestisco io)]` / `[Abort]`.
181
- - **`N\tM`** (diverged both ways) — invoke `AskUserQuestion`:
182
- - Question: `"Local $TRUNK è diverged da origin/$TRUNK: N behind, M ahead. Servono entrambe le riconciliazioni. Come procedo?"`
208
+ - **`BEHIND>0` and `GENUINE_AHEAD>0`** (diverged both ways) — invoke `AskUserQuestion`:
209
+ - Question: `"Local $TRUNK è diverged da origin/$TRUNK: $BEHIND behind, $GENUINE_AHEAD genuine ahead. Servono entrambe le riconciliazioni. Come procedo?"`
183
210
  - Options: `[Rebase local $TRUNK su origin/$TRUNK]` / `[Mostrami i commit e fammi decidere]` / `[Abort]`.
184
211
 
185
212
  5. **Log and proceed** — write to the tracker:
@@ -188,8 +215,8 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
188
215
  Status: PASS
189
216
  Main repo: $MAIN
190
217
  Trunk branch: $TRUNK
191
- Dirty: <none | stashed: <message-label> | user-override>
192
- Divergence (local…origin/$TRUNK): <0\t0 | resolved: ff-pull | resolved: pushed | user-deferred>
218
+ Dirty: <none | framework-telemetry-only (auto-ignored) | stashed: <message-label> | user-override>
219
+ Divergence (local…origin/$TRUNK): <0\t0 | framework-mgmt-only (auto-ignored: $FW_SKIPPED) | resolved: ff-pull | resolved: pushed | user-deferred>
193
220
  Completed: <timestamp>
194
221
  ```
195
222
  If any step ended in HALT, set `Status: HALT` and stop — do NOT continue to `## Pre-flight (once)`.
@@ -249,7 +276,24 @@ Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
249
276
 
250
277
  > **Why**: a non-Anthropic frontier model (Codex, via `codex-companion.mjs`) reviews the full card batch for cross-card conflicts that per-card plan-auditor checks cannot detect (each plan-auditor sees only one card).
251
278
 
252
- **Skip if**: batch has only 1 card (no cross-card conflicts possible).
279
+ **Skip decision (provenance-aware since v4.3.0).** The `/prd` Step 6.6 holistic audit (4-agent team + Codex adversarial pass) ALREADY ran this exact cross-card analysis over the cards when they were authored — its Codex prompt covers `files_likely_touched` conflicts, dependency shadows, implicit ordering, and shared-state mutation across the whole audited set. Re-running here is only worth the Codex call when **something changed between that audit and now**. Compute the skip before launching:
280
+
281
+ 1. **Always skip** if the batch has only 1 card (no cross-card conflicts possible).
282
+ 2. Otherwise, read `metadata.holistic_audit` from every card in the batch and evaluate the four SKIP conditions — **all must hold**:
283
+ - **S1 — provenance present**: every batch card has `metadata.holistic_audit` with non-empty `audited_at`, `audited_commit`, and `audited_set`. (Missing/partial on any card ⇒ condition fails.)
284
+ - **S2 — jointly audited**: all batch cards carry the **same** `audited_set` value (they were reviewed together, not stitched from separate PRD runs).
285
+ - **S3 — batch ⊆ audited set**: every batch card ID is present in that shared `audited_set`.
286
+ - **S4 — no drift on the claimed paths**: no commit touched any path the batch claims, between the audit baseline and the trunk this run grounds on. Using the shared `audited_commit` (`$AC`) and the batch's union of claimed/touched paths from the file-ownership map (step 3b, `$BATCH_PATHS`):
287
+ ```bash
288
+ # empty AC, or AC not in history → treat as drift (do NOT skip)
289
+ DRIFT="$(git -C "$MAIN" log --oneline "$AC".."$TRUNK" -- $BATCH_PATHS 2>/dev/null || echo "DRIFT")"
290
+ ```
291
+ S4 holds **iff** `$AC` is non-empty AND the command succeeds AND `$DRIFT` is empty.
292
+ 3. **If S1–S4 all hold → SKIP the Codex cross-card check.** Log in the tracker under `## Cross-Card Conflicts (Codex)`:
293
+ `SKIPPED (provenance) — batch ⊆ holistic_audit set <audited_set>, no drift since <AC short sha> (audited <audited_at>)`. Then proceed to step 4 with the file-ownership map unchanged. Do NOT present anything to the user — this is a non-decision.
294
+ 4. **Otherwise → RUN the check** (launch below). Record WHY in the same tracker section so the choice is auditable: `RUN — <reason>` where reason is one of `no/partial provenance` (S1) · `batch spans multiple audit sets` (S2) · `batch not a subset of audited set` (S3) · `drift on <first drifted path>` (S4).
295
+
296
+ `$MAIN` and `$TRUNK` are the same variables resolved in Phase 0. This gate degrades safely in every direction: any missing signal, ambiguity, or git error falls through to RUN — never to a silent skip.
253
297
 
254
298
  Launch via `Bash` with `run_in_background: true` and `timeout: 300000`:
255
299
 
@@ -1868,18 +1912,19 @@ The most common failure mode is leaving cards IN_PROGRESS after merge. This crea
1868
1912
 
1869
1913
  4. **Divergence assertion (BLOCKING — this catches the FEAT-0006 orphan-commit pattern)**. `$MAIN` and `$TRUNK` are read from the tracker (`## Worktree` section); HALT if either is absent/empty.
1870
1914
  ```bash
1871
- git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK"
1915
+ read BEHIND AHEAD <<< "$(git -C "$MAIN" rev-list --left-right --count "origin/$TRUNK...$TRUNK")"
1872
1916
  ```
1873
- If the output is anything other than `0\t0`:
1874
- - **Ahead only (M > 0, unpushed commits)** invoke `AskUserQuestion`:
1875
- - Question: `"Local $TRUNK ha M commit non pushati su origin/$TRUNK al termine del batch. Lista: <git log --oneline origin/$TRUNK..$TRUNK>. Cosa faccio?"`
1917
+ Compute `$GENUINE_AHEAD` / `$GENUINE_LIST` / `$FW_SKIPPED` with the **exact same reclassification loop as Phase 0 step 4** (framework-management commits — subtree merges into `.framework/`, `baldart update`/`add` chores, `.framework/`-only commits — are auto-ignored, NOT orphan commits). Then branch on `$BEHIND` and `$GENUINE_AHEAD`:
1918
+ - **`BEHIND=0` and `GENUINE_AHEAD=0`** — synchronized. If `$FW_SKIPPED > 0`, log `Divergence: $FW_SKIPPED framework-management commits ahead (auto-ignored)` and proceed. No `AskUserQuestion`.
1919
+ - **Ahead only (`GENUINE_AHEAD > 0`, genuine unpushed commits)** invoke `AskUserQuestion`:
1920
+ - Question: `"Local $TRUNK ha $GENUINE_AHEAD commit genuini non pushati su origin/$TRUNK al termine del batch (esclusi $FW_SKIPPED di gestione framework). Lista: <git show -s --oneline $GENUINE_LIST>. Cosa faccio?"`
1876
1921
  - Options: `[Push adesso (git push origin $TRUNK)]` / `[Cherry-pick selettivo su origin/$TRUNK]` / `[Reset --hard origin/$TRUNK (richiede conferma esplicita, DISTRUTTIVO)]` / `[Halt con stato preservato]`.
1877
1922
  - On `Reset --hard origin/$TRUNK` re-ask `AskUserQuestion` to confirm: `"Confermi reset --hard? I commit <list> verranno persi se non pushati altrove."` Solo dopo conferma esplicita esegui il reset.
1878
- - **Behind only (N > 0)** — invoke `AskUserQuestion`:
1879
- - Question: `"Local $TRUNK è behind origin/$TRUNK di N commit (qualcuno ha pushato durante il batch). Faccio ff-pull adesso?"`
1923
+ - **Behind only (`BEHIND > 0`, `GENUINE_AHEAD=0`)** — invoke `AskUserQuestion`:
1924
+ - Question: `"Local $TRUNK è behind origin/$TRUNK di $BEHIND commit (qualcuno ha pushato durante il batch). Faccio ff-pull adesso?"`
1880
1925
  - Options: `[Fast-forward pull]` / `[Lascia behind (lo gestisco io)]`.
1881
- - **Diverged both ways** — invoke `AskUserQuestion`:
1882
- - Question: `"Local $TRUNK è diverged (N behind, M ahead). Servono entrambe le riconciliazioni. Come procedo?"`
1926
+ - **Diverged both ways (`BEHIND > 0` and `GENUINE_AHEAD > 0`)** — invoke `AskUserQuestion`:
1927
+ - Question: `"Local $TRUNK è diverged ($BEHIND behind, $GENUINE_AHEAD genuine ahead). Servono entrambe le riconciliazioni. Come procedo?"`
1883
1928
  - Options: `[Rebase local $TRUNK su origin/$TRUNK]` / `[Mostrami i commit locali e decidiamo]` / `[Halt]`.
1884
1929
 
1885
1930
  5. **Restore Phase 0 stash (if any)** — read the tracker for `## Workspace Snapshot: <message-label>`:
@@ -473,14 +473,43 @@ Applied N findings to structured fields (H high, M medium).
473
473
  Manual review needed: [list [MANUAL] items, or "none"].
474
474
  ```
475
475
 
476
+ ### Holistic-audit provenance stamp (since v4.3.0) — consumed by `/new` Step 3d
477
+
478
+ This audit (the 4-agent team + Codex adversarial pass of Step 6.6) IS the holistic cross-card review: Step 6.6d's Codex prompt already covers `files_likely_touched … conflicting across cards`, dependency shadows, implicit ordering, and shared-state mutation across the whole `${CARD_PATHS}` set. To let `/new` AVOID re-running its own pre-flight cross-card Codex check (Step 3d) when nothing has changed since this audit, stamp each audited card with a machine-readable provenance block.
479
+
480
+ **Capture three run-level values ONCE** (same for every card in this audit run):
481
+
482
+ ```bash
483
+ HOLISTIC_AUDITED_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
484
+ # Grounding baseline = the trunk commit the cards were authored against.
485
+ # The PRD docs worktree was forked from trunk, so the fork point is that baseline.
486
+ HOLISTIC_AUDITED_COMMIT="$(git merge-base HEAD "${git.trunk_branch}" 2>/dev/null || git rev-parse "${git.trunk_branch}" 2>/dev/null || echo "")"
487
+ # audited_set = sorted card IDs reviewed JOINTLY in this run (the Step 6.6d ${CARD_PATHS} set).
488
+ ```
489
+
490
+ `audited_set` is the **same list for every card** — it records which cards were considered together, so `/new` can verify its batch is a subset of one coherent jointly-audited set (not a mix of cards audited in separate PRD runs).
491
+
492
+ Write under `metadata.holistic_audit` on each card (additive — never overwrite other `metadata` keys):
493
+
494
+ ```yaml
495
+ metadata:
496
+ holistic_audit:
497
+ audited_at: "<HOLISTIC_AUDITED_AT>"
498
+ audited_commit: "<HOLISTIC_AUDITED_COMMIT>" # "" if git unavailable → /new treats as drift (runs Step 3d)
499
+ audited_set: ["FEAT-XXXX-01", "FEAT-XXXX-02", ...] # sorted; the joint cross-card scope
500
+ ```
501
+
502
+ > **Fail-safe contract**: this block is an OPTIMIZATION hint, never a gate. If it is absent, partial, or has an empty `audited_commit`, `/new` Step 3d simply RUNS as before — there is no correctness risk, only a redundant Codex pass. Do NOT block the PRD if the stamp can't be written.
503
+
476
504
  ### Per-card workflow
477
505
 
478
506
  For each card:
479
507
  1. Read persisted report → collect all findings for this card ID.
480
508
  2. Read current card YAML.
481
509
  3. Apply HIGH findings first, then MEDIUM, then write audit trail.
482
- 4. Write updated card YAML.
483
- 5. Re-read to verify edits landed correctly.
510
+ 4. Write the `metadata.holistic_audit` provenance block (see "Holistic-audit provenance stamp" above) using the run-level values captured once.
511
+ 5. Write updated card YAML.
512
+ 6. Re-read to verify edits landed correctly.
484
513
 
485
514
  **Note**: No separate commit here — the validation-phase.md Step 7 handles committing all PRD artifacts together.
486
515
 
@@ -130,3 +130,13 @@ metadata:
130
130
  # Auto-computed by prd-card-writer for PRD-generated cards; set it by hand here
131
131
  # when authoring a card manually (omit to let /new fall back to runtime compute).
132
132
  review_profile: "" # skip | light | balanced | deep
133
+ # holistic_audit — provenance of the /prd Step 6.6 cross-model holistic audit
134
+ # (4-agent team + Codex adversarial pass). WRITTEN BY the audit phase, not at
135
+ # authoring — leave absent when creating a card by hand. /new reads it to decide
136
+ # whether its pre-flight cross-card check (Step 3d) can be skipped because the
137
+ # batch was already audited jointly AND nothing changed since. Fail-safe: if
138
+ # absent/partial, /new just runs Step 3d (no correctness risk).
139
+ # holistic_audit:
140
+ # audited_at: "" # ISO-8601-UTC of the audit
141
+ # audited_commit: "" # trunk commit the cards were grounded against
142
+ # audited_set: [] # card IDs audited jointly (the cross-card scope)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.2.1",
3
+ "version": "4.3.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"