# Changelog

All notable changes to BALDART will be documented in this file.

The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [4.35.0] - 2026-06-13

**Card-baseline standardization — every backlog card, any prefix/origin, conforms to one profile-aware SSOT; `/new` normalizes foreign cards at ingestion.** A real `CHORE-0007` (consumer repo `mayo`) reached `/new` **without `review_profile`** (and without `scope`/`scope_boundaries`/`canonical_docs`): it was hand/ad-hoc authored after a graph-align finding, never by the canonical writer. `/new` and `/new2` consume cards **type-blind** — they scale per-card review depth on `review_profile` and run the same pipeline regardless of prefix — so an off-baseline card silently degrades the pipeline. Root cause: the baseline was scattered across `card-template.yml` + `prd-card-writer`'s Required-Fields + Rule C with **no single SSOT and no validator**; `prd-card-writer` only documented the `/prd` epic+children flow (its standalone single-card mode, used by `new2-resolve`, was undocumented); and three writers diverged — `/prd`/`new2`/`new2-resolve` emit the full baseline, but the `/new` AC-deferral stub (`completeness.md`) and `/issue-review` (`issue-review.md`) wrote partial cards.

The fix is **profile-aware** by design (an adversarial review caught that a flat "every field required, non-empty" rule would wrongly reject valid cards — an epic legitimately has `owner_agent: ""` and `files_likely_touched: []`; a standalone CHORE legitimately omits `scope_boundaries`/`links.prd`). A new SSOT module defines per-profile (epic/child/standalone) field states (REQUIRED / may-be-empty / conditional). Every creation path now delegates to `prd-card-writer` (the canonical writer), and `/new`/`/new2` ingestion **normalizes** a non-conformant card: HALT only on truly non-derivable fields (`scope`/`requirements`/`acceptance_criteria`/`files_likely_touched`), **back-fill + persist** the derivable ones (`review_profile` via Rule C, `owner_agent`→`coder`) to the card on disk in the **main repo** (check-and-skip idempotent; committed with the `merge-cleanup.md` Phase 6b COMMIT_LOCK discipline), WARN on the rest. A shipped validator (profile-aware) + CI gate would have failed `CHORE-0007` at authoring time. **MINOR** (new protocol module + new agent capability + new validator/CI gate; backwards-compatible — conformant cards still validate, back-fill only *adds* fields; `review_profile`/baseline are not `baldart.config.yml` keys ⇒ schema-propagation rule N/A).

### Added

- **`framework/agents/card-schema.md`** — Atomic Card Baseline Schema: the universal, profile-aware (epic/child/standalone) field-state matrix every backlog card satisfies, plus the consumer HALT/BACK-FILL/WARN contract. Cites Rule A (`owner_agent` enum, REGISTRY.md) and Rule C (`review_profile` criteria) — never copies them. This is the missing peer of the `agents/index.md` module manifest ("if you create/mutate a card, read card-schema.md"). 23rd protocol module.
- **`framework/scripts/validate-card-baseline.js`** — profile-aware validator. Parses the field-state matrix from `card-schema.md` and the enums from `REGISTRY.md` (no embedded copy → no drift), detects the card profile, and asserts each field's state + enum validity. Exit 1 + per-field errors. Module API (`validateCard`/`detectProfile`/`loadSchema`/`loadEnums`) for reuse.
- **`scripts/check-card-baseline.js`** + new step in **`.github/workflows/check-reference-integrity.yml`** — CI gate: self-tests the validator against fixtures (valid epic/child/standalone + a broken CHORE-0007 shape + invalid enums) and asserts `card-template.yml` ↔ `card-schema.md` do not drift.
- **`framework/templates/ci/check-card-baseline.yml`** — opt-in consumer CI template: fails a PR when a hand-authored `backlog/*.yml` drifts off the baseline (catches manual cards at PR time, not only at `/new` time).

### Changed

- **`framework/.claude/agents/prd-card-writer.md`** — new § "Standalone Single-Card Mode" (documents the no-epic single-card path used by `/new`/`new2`/`new2-resolve`/`/issue-review`: relaxes epic+children and conditional fields, but still emits the full STANDALONE baseline — *standalone ≠ minimal*). "Required Fields Per Card" now cites `card-schema.md` instead of being the implicit SSOT; `description:` frontmatter acknowledges the standalone invocation.
- **`framework/.claude/skills/new/references/setup.md`** — pre-flight step 1b restructured into **1b-i** (HALT on non-derivable fields, incl. `scope`; `scope_boundaries` is NOT a HALT field), **1b-ii** (back-fill `review_profile`/`owner_agent`, persist to `$MAIN`, check-and-skip, dedicated `[BACKFILL]` commit), **1b-iii** (run the profile-aware validator).
- **`framework/.claude/skills/new/SKILL.md`** — QA Profile Selector simplified to a pure read (the compute-from-Rule-C fallback is consolidated into setup.md 1b-ii, which persists the value — no second copy of the criteria).
- **`framework/.claude/skills/new/references/completeness.md`** — AC-deferral follow-up now delegates to `prd-card-writer` Standalone Mode (full baseline) instead of writing a 3-field stub; minimal stub only on total agent outage (self-heals on first ingestion).
- **`framework/.claude/commands/issue-review.md`** — backlog-card creation delegates to `prd-card-writer` (standalone for one-off issues, epic+children for new features) instead of hand-filling the template.
- **`framework/.claude/skills/new2/SKILL.md`** — offline reconciliation cites `card-schema.md` + the standalone mode; notes the crisis stub self-heals via the `/new` back-fill.
- **`framework/agents/index.md`**, **`framework/.claude/skills/prd/assets/card-template.yml`** — routing rule + template header pointer to `card-schema.md` as the field SSOT.

## [4.34.2] - 2026-06-13

**`/new` workflow-delegation gates hardened to BINARY/imperative — close the "ran inline by judgment" escape hatch.** A real `/new` run (mayo, single-card CHORE-0007, BALDART 4.34.1 with both workflows linked + the `Workflow` tool available) ran **both** review-delegation gates inline instead of delegating: the orchestrator invented escape criteria absent from the gate ("single-card → marginal context-economy benefit", "memory note on workflow degeneration") and mis-cited the `feedback_dynamic_workflows_fit` guidance, which actually names the Final Review fan-out as the canonical workflow fit. The degeneration reports it conflated concern the `new2`/`new2-resolve` whole-batch host — a different workflow. Net effect: the run validated only the inline fallback and gave **zero** signal on the `new-card-review` (v4.34.0) path. Both gates were already worded as `IF tool+script → delegate / ELSE inline`, but the prose left enough softness for the model to insert discretion. **PATCH** (gate-prose hardening; no behavior change to the install layout or the workflows themselves — the delegated path was always the intended one).

### Changed

- **`framework/.claude/skills/new/references/review-cycle.md`** — Phase 2.5x branch reframed as a "MECHANICAL, BINARY decision" with an explicit **no-discretion** rule: the soft factors (N=1, small diff, marginal benefit, "a workflow once degenerated", "inline is safer") are named and declared **NOT inputs to the gate**; the `new2` degeneration conflation is corrected inline; running inline while the conditions held is now defined as a **gate violation** with a mandated telemetry log line.
- **`framework/.claude/skills/new/references/final-review.md`** — Step F.1.5 branch given the same imperative treatment (MUST-delegate, no-discretion rule, `new2`-conflation correction, gate-violation log line).

## [4.34.1] - 2026-06-12

**`reference-integrity` CI gate: recognize the plumbing carve-out.** v4.33.2 introduced a legitimate `subagent_type: general-purpose` dispatch for the mechanical file-revert agent (the REGISTRY.md plumbing carve-out — mechanical git/file ops, never code authoring), but the CI `reference-integrity` gate's R11 anti-fallback rule banned `general-purpose` **unconditionally**, so `main` went red on the v4.33.2 / v4.34.0 tags (the npm publish workflow is independent and succeeded — both versions are live). The gate now allows `subagent_type: general-purpose` **only** on a dispatch line explicitly marked `plumbing carve-out`; every other `general-purpose` dispatch stays banned (R11 intact, intentionally narrow so it can't be abused as a generic fallback). **PATCH** (CI gate fix; no behavior change to `/new`).

### Changed

- **`scripts/check-reference-integrity.js`** — Check A allows `subagent_type: general-purpose` when the dispatch line carries the literal `plumbing carve-out` marker; otherwise the R11 ban stands (with an error message pointing to the marker + REGISTRY.md).

## [4.34.0] - 2026-06-12

**`/new` per-card review cluster extracted into the `new-card-review` dynamic workflow — kills the dominant context-cost driver on long epics.** On a long epic (10-12 cards) `/new` accumulated context monotonically: every card ran the full review fan-out (Simplify ×3 + Codex/code-review + qa + the verify/FP pass) *inside the orchestrator*, and each step re-paid the whole growing prefix (cumulative `cache_read`). The driver is **turn-count × prefix**, not subagent output volume. Following the validated `new-final-review` pattern (read-only fan-out hosted in a workflow whose context is discarded), the review cluster now runs OUTSIDE the orchestrator and returns only a compact result.

A **single workflow parametrized on the number of cards** covers both modes: sequential delegates `cards:[1]`; team-mode delegates `cards:[the whole wave]`, so it runs **once per wave, not once per card** — the key win, since the team-mode per-card Codex (D.4b) + Simplify (D.3b) loops were the chattiest sub-steps. The workflow fans out the finders per card (Simplify + cross-model Codex with `code-reviewer` fallback + qa-sentinel at group-max tier + security-reviewer on high-risk only), each specialist FP-checking its own findings; then **one `coder` applies all VERIFIED code/perf/security/simplify findings in a single pass** (files disjoint by ownership) and re-verifies lint/tsc/build. Only `{perCard:{fixesApplied,residual}, gateTable, summary}` re-enters the orchestrator — the minority `residual` (doc, needs-manual, scope-expanding, unconverged) is resolved by the skill with the right specialist or a user gate.

**Boundaries (by design):** E2E (Phase 2.6 / D.3c — human-gated + nests a skill) and doc-review (Phase 3 / D.2+D.4a — write-mode, must see final code) stay in the skill; doc runs **after E2E on final code** in the delegated path. `/codexreview` (Phase 3.7 / D.4b — a skill that nests sub-agents) cannot run inside a workflow, so the workflow launches the Codex **binary** directly via an agent (haiku preflight + background poll), exactly like `new-final-review`; the **Final FULL gate** remains the cross-card depth net. api-perf stays deferred to the Final. **Opt-in + additive**: delegates only when the `Workflow` tool is available AND the script is linked, else the inline prose (SSOT) runs unchanged — Codex/cross-tool consumers are unaffected. **MINOR** (new capability, fallback inline, no breaking change; no `baldart.config.yml` key ⇒ schema-propagation rule N/A). Validation deferred to telemetry on the first real run (per `/new` convention).

### Added

- **`framework/.claude/workflows/new-card-review.js`** — the per-wave review+fix workflow (1..N cards). Modeled on `new-final-review.js` (shared schemas, deterministic Codex preflight haiku + background poll, specialist-owned FP-check) + Simplify/security finders + the single-coder batch-fix pass.

### Changed

- **`framework/.claude/skills/new/references/review-cycle.md`** — new "Phase 2.5x — Review-cluster workflow delegation gate" (replica of final-review.md F.1.5): delegates Phase 2.55+3.5+3.7 to `new-card-review` (`cards:[this card]`), consumes the typed `residual`, then proceeds E2E→Doc→Commit; else inline SSOT. `IS_TRIVIAL` never delegates.
- **`framework/.claude/skills/new/references/team-mode.md`** — new "D.1.6 — Review-cluster workflow delegation gate": delegates the group's D.3b+D.4+D.4b in ONE call per wave (`cards:[the wave]`), runs D.3a (AC-closure) before it and D.3c (E2E) + post-E2E doc + D.5/D.6 after; else inline.
- **`framework/.claude/skills/new/references/codex-gate.md`** — Phase 3.7 carve-out: SKIP when the cluster was delegated (the workflow already ran the Codex pass).
- **`framework/.claude/skills/new/SKILL.md`** — routing table notes the `new-card-review` delegation for the 2.55+3.5+3.7 cluster (parity with the final-review row).
- **`framework/docs/WORKFLOWS.md`** — documents the `new-card-review` workflow.

## [4.33.2] - 2026-06-12

**`/new` runs its two purely-mechanical spawns on haiku instead of opus.** Audit of every agent `/new` spawns (not `new2`) found that the spawns inherit each agent's frontmatter default and none ever override `model:` — correct for the reasoning agents (`coder`/`ui-expert` = opus; the review/analysis agents = sonnet), but the **background worktree-setup subagent** that runs `/nw` is a `general-purpose` agent with no `model:`, so it inherited the **session model (opus)** to do pure git plumbing (create worktree, install deps, allocate port, write registry). Same shape for the **file-scoped revert agent** (was spawned as `coder`/opus to do a mechanical revert). Both now run on **haiku** — cheaper and faster on the background barrier, with no quality loss because neither does any reasoning or code authoring. `qa-sentinel` deliberately stays on sonnet (failure interpretation + test-tier selection). `/mw` is invoked inline by the orchestrator (not a subagent) so it is unaffected. No `baldart.config.yml` key involved (model selection is native) ⇒ schema-change propagation rule does not apply. **PATCH** (cost/perf, no new capability, no breaking change).

### Changed

- **`framework/.claude/skills/new/references/setup.md`** — the §4b background worktree-setup subagent (`general-purpose`, runs `/nw`) now spawns with `model: "haiku"`, with a rationale noting it is deterministic plumbing.
- **`framework/.claude/skills/new/references/implement.md`** — the Phase 2.4 unauthorized-file revert is now a `general-purpose` + `model: "haiku"` agent with an explicit ROLE BOUNDARY (mechanical file-scoped revert, never code authoring), replacing the prior `coder`/opus revert spawn.
- **`framework/.claude/agents/REGISTRY.md`** — the Model Selection Matrix "never haiku" rule is scoped to **named specialist agents**, with an explicit **plumbing carve-out** sanctioning `model: "haiku"` for mechanical `general-purpose` plumbing (the two `/new` uses above); `qa-sentinel`'s stay-on-sonnet rationale made explicit.

## [4.33.1] - 2026-06-12

**Discoverability follow-up to v4.33.0** — the new `stack.schema_deploy_from_trunk_only` gate guard was enforced in the `/new` reference modules + `new2.js`, but the **"Reads from `baldart.config.yml`"** manifest line at the top of each skill body still omitted it. Surface it so the key is discoverable from the skill's config-reads declaration (parity with `stack.database` / `stack.deployment`). Manifest-only — **no behavior change**. **PATCH**.

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — add `stack.schema_deploy_from_trunk_only` to the "Reads from" gate-guards list.
- **`framework/.claude/skills/new2/SKILL.md`** — make the `stack.*` read explicit about `stack.schema_deploy_from_trunk_only` (the trunk-only schema-deploy gate guard).

## [4.33.0] - 2026-06-12

**New opt-in `stack.schema_deploy_from_trunk_only` safety invariant — `/new` + `new2` never push a remote schema/RLS/index/migration deploy from a non-trunk branch or worktree.** Before this release, the Production Readiness checklist (`/new` Phase 7 / `new2` Production phase) and the front-loaded Migration Gate would auto-execute a remote schema deploy (`supabase db push`, `prisma migrate deploy`, `firebase deploy --only firestore:rules|firestore:indexes|storage`, CDK/Terraform apply, any SQL migrator) gated only on `stack.database`/`stack.deployment` — with no guard against running it from an unmerged feature branch / git worktree. On projects that develop multiple cards in parallel worktrees against a single shared remote datastore, that desyncs migration history and produces "code-ahead-of-schema" outages (a real consumer hit a `42703 undefined_column` production outage this way).

The new key is **stack-agnostic and opt-in**, a no-op for projects that don't set it:
- `false` (default) / unset → current behavior preserved exactly (unset → the skill asks; `baldart configure` defaults it to `true` only when it autodetects parallel worktree usage).
- `true` → a remote schema/RLS/index/migration deploy is auto-executed **only** when `git rev-parse --abbrev-ref HEAD == git.trunk_branch`. From any non-trunk branch / worktree it is **never** auto-run: the migration stays committed and the remote deploy is deferred ("happens from trunk after merge") and routed to the existing **`db-migration-deploy`** residual class (dedup-collapsed onto one external action).

**The guard enforces only the branch invariant** — the actual deploy command, env loader, and gate UX stay delegated to the project overlay (`.baldart/overlays/new.md`); core does not move the command in. Datastore-agnostic by design (any persistence layer, not Supabase-specific). The principle is documented as a MUST-rule in `production-readiness.md` and the generic `deployment-protocol.md`. **MINOR** (new opt-in config key + capability; default false ⇒ zero behavior change for existing consumers). Schema-change propagation rule applied end-to-end (template + configure prompt/autodetect + update detector + skills + docs + CHANGELOG).

### Added

- **`framework/templates/baldart.config.template.yml`** — new `stack.schema_deploy_from_trunk_only: false` key with documentation.
- **`src/commands/configure.js`** — `detectWorktreeUsage()` probe (`git worktree list`); the autodetected default and an interactive confirm prompt for the new key (pre-checked when parallel worktrees are detected).

### Changed

- **`framework/.claude/skills/new/references/production-readiness.md`** — new "Schema-deploy-from-trunk-only guard" subsection + a MUST-rule in § Rules: when the flag is on, gate every remote schema/RLS/index/migration auto-deploy on `current branch == git.trunk_branch`, else defer as a `db-migration-deploy` residual.
- **`framework/.claude/skills/new/references/setup.md`** — Migration Gate (Phase 0 step 1b) honors the same guard: a command modality (front-loaded apply) runs only when `$MAIN` is on trunk; otherwise it degrades to the end-of-batch owner-gated deferral.
- **`framework/.claude/workflows/new2.js`** — reads `stack.schema_deploy_from_trunk_only`; the Production phase agent gates schema deploys on `HEAD == TRUNK` and returns `schemaDeploysDeferred`, which the workflow converts into `db-migration-deploy` residuals (owner-gated, collapsed by `ownerGatedActionKey`).
- **`framework/.claude/skills/new2/SKILL.md`** — Migration Gate Step 3.5 step 6 honors the off-trunk guard (degrade to deferred when `$MAIN` is not on trunk).
- **`src/commands/update.js`** — the `missingStack` schema-drift detector now surfaces boolean scalar `stack.*` keys (not just strings), so `schema_deploy_from_trunk_only` is offered to pre-v4.33.0 consumers on update.
- **`framework/agents/deployment-protocol.md`** — new datastore-agnostic "Schema deploys run from trunk only" best-practice subsection.
- **`framework/docs/PROJECT-CONFIGURATION.md`** — documents the new `stack.schema_deploy_from_trunk_only` key in § 4.4.

## [4.32.0] - 2026-06-11

**`new2`: `deep` is now relevance-gated too — it no longer fires all 5 reviewers on every card regardless of content.** Diagnosing a real batch (the FEAT-0023 supplier-associated-users epic — permissions/RLS/migrations, so 7 of 11 cards are legitimately `review_profile: deep` per `prd-card-writer` Rule C) exposed an **asymmetry**, not a misclassification: the per-card review matrix relevance-gated `balanced` (a specialist runs only if its domain is evidenced by `scopeFiles ∪ MAY-EDIT`) but left `deep` on an **unconditional 5-way fan-out** (`FULL_FANOUT` = code-reviewer + doc-reviewer + qa-sentinel + api-perf-cost-auditor + security-reviewer). So a `deep` card with no doc surface still paid for a doc-reviewer, and one with no API/data surface still paid for api-perf-cost-auditor — every time. This is exactly the "every card gets a full review regardless of what's inside" symptom.

The fix unifies the review MODEL: **the profile controls review DEPTH, the surface controls review BREADTH.**
- `deep` and `balanced` now share the same relevance-gated reviewer SET (extracted into `relevanceGated()`): `touchesCode` → code-reviewer + qa-sentinel; `touchesDocs` → doc-reviewer (the core finder on a doc-only card); `touchesApiData` → api-perf-cost-auditor; `securityRelevant` → security-reviewer.
- `deep`'s extra DEPTH is unchanged and is carried downstream, NOT by the fan-out set: the full QA suite + Codex-full posture reach each spawned reviewer via `cardBrief`'s "Review profile" line, and the Phase-1 architect audit still keys on `reviewProfile === 'deep'` (`needAudit`). So the reviewers that DO run review at full depth — only the ones whose domain the card never touches are dropped.
- **Fail-safe preserved**: `noEvidence` (empty `scopeFiles ∪ MAY-EDIT`) still falls back to `FULL_FANOUT` for BOTH balanced and deep — absence of evidence ≠ evidence of absence. `light`/`skip` unchanged. Batch-level coverage is unchanged: the Final Review's batch-wide doc + api/perf passes remain the safety nets, and `securityRelevant` (which a `deep` card almost always trips) keeps security-reviewer on genuinely sensitive cards.

Not touched: `prd-card-writer` Rule C (it classifies correctly — `deep` for migration/RLS/permission/schema/HIGH-integration cards, `light` for pure-UI, `skip` for epic/doc — verified against the FEAT-0023 cards) and `/new`'s interactive prose path (which already scales per-card by profile + relevance — api-perf is deferred to the Final Review F.3, qa deferred at balanced, doc deferred at light-no-doc — so the unconditional-deep fan-out was unique to `new2.js`). **MINOR** (review-behavior change on the EXPERIMENTAL `new2` surface only; no config key — the schema-change propagation rule does not apply; no change to `/new`).

### Changed

- **`framework/.claude/workflows/new2.js`** — per-card review matrix (B7): the relevance gate now applies to `deep` as well as `balanced` via a shared `relevanceGated()` helper; the `reviewProfile === 'deep'` arm of the `FULL_FANOUT` branch is removed, leaving only `noEvidence` as the conservative full-fan-out fail-safe. The `review-matrix` ledger row's `→conservative-full` annotation now fires for any `noEvidence` profile (was `balanced`-only). Comment block updated to document the depth-vs-breadth model.

## [4.31.1] - 2026-06-11

**`new2`: fix v4.31.0's dedup coverage + drop empty residuals + honest A/B cost — verified against the real FEAT-0022 telemetry/report (not the narrative).** Reading the actual run's `skill-runs.jsonl` + workflow report (instead of the prior turn's recollection) exposed three things v4.31.0 missed:
- **The owner-gated dedup was too narrow** — it filtered by `deferralClass ∈ {owner-gated, not-a-code-defect}`, but the run's FIVE identical `db:push` residuals on card -01 carried kinds `ac-unmet`/`blocker`/`policy-deferred-ac`; the two `policy-deferred-ac` (an F-016 class that is ALWAYS an external/infra action) slipped through. Fix: dedup on `EXTERNAL_DEFERRAL = {owner-gated, not-a-code-defect, policy-deferred-ac}`. A second defect caught by a self-test before publishing: the action key split those 5 across `db-migration-deploy` and `migration:<file>` (the filename branch ran first), collapsing to 2 not 1 — **one `db:push` pushes all pending migrations, so the deploy intent must win over a bare filename**; reordered the key so every db-migration-deploy residual maps to one key. Verified on the real residuals: 7 db:push residuals (5 on -01, 1 on -02, 1 batch-level `F001`) → 2 (one per real card; `F001` dropped), matching the hand reconciliation.
- **Empty out-of-scope residuals** — the run emitted two `out-of-scope` residuals with no file/line/evidence (`":"`), which would mint contentless follow-up cards. `resolve()` now skips an out-of-scope finding whose composed evidence is empty after stripping `:`/space separators.
- **A/B cost telemetry was ~8× under** — the workflow reported `total_tokens=476k` (`budget.spent()`, output-only) / `agent_count=30` while the harness saw ~4.15M subagent tokens / 64 agents, because the workflow counters exclude the subagents spawned inside the nested workflows (`new2-resolve`, `new-final-review`). Since `total_tokens` was non-null, the skill's "backfill only if null" rule accepted the wrong figure — defeating new2's whole purpose (A/B on context economy). SKILL.md Step 5.5 now mandates **always** reading the real transcript `usage` via the `-stats` script as the headline cost, labelling the workflow figures as partial.

**PATCH** (corrects v4.31.0's dedup coverage + noise filter + telemetry honesty on the EXPERIMENTAL `new2` surface; no new capability, no config key). Meta: same lesson as the feature itself — the v4.31.0 dedup was written from the prior turn's recollection; reading the raw telemetry/report found the gap. Validate fixes against the data, not the memory of the data.

### Changed

- **`framework/.claude/workflows/new2.js`** — `dedupOwnerGatedResiduals` now keys on `EXTERNAL_DEFERRAL` (adds `policy-deferred-ac`); `ownerGatedActionKey` reorders the db-migration-deploy branch ahead of the filename branch (one key per deploy action); `resolve()` skips empty `out-of-scope` residuals.
- **`framework/.claude/skills/new2/SKILL.md`** — Step 5.5 cost recording: always backfill real transcript `usage` (the workflow's `total_tokens`/`agent_count` are partial — output-only + exclude nested workflows); keep them labelled `*_workflow`.

## [4.31.0] - 2026-06-11

**`new2`: the residual ledger self-corrects before returning — no more duplicate-of-done follow-ups, no more N defers for one external action.** A real `new2` run (FEAT-0022 epic, 3 cards) surfaced two over-report classes in the offline-safe residual ledger that the skill was absorbing **by hand** every run: (1) **4 of 8 follow-ups were false-open** — scope-expansion residuals deferred early in the batch but satisfied LATER by another card's commit / a final-review fix, which only `integrateCrossCard()` retracts; a residual closed by any *other* in-batch path stayed falsely-open (and left a best-effort, uncommitted follow-up YAML in the worktree). (2) **3 follow-ups for ONE physical action** — one migration's remote `db:push` re-raised per-card AND batch-wide by the final review → three near-identical owner-gated cards. Both were caught only by the skill's manual per-residual disk grep + consolidation (load-bearing, repeated every run). This release moves that work into the workflow, once, deterministically:
- **Ledger self-correction (`reconcileLedgerAgainstHead`)** — before returning, re-verify every still-pending code/doc residual (`scope-expansion`/`out-of-scope`/`unresolved`/`file-diff-violation`/`merge-artifact-skipped`/`out-of-ownership`, `materialized` true OR false) against the worktree HEAD via one read-only agent, and retract only the ones it can back with a `file:line` proof. **Conservative by design** — default KEEP: a false-open residual is recoverable downstream (the skill re-checks disk), but a wrong retract silently drops real work (F-029). Owner-gated / not-a-code-defect / policy-deferred are EXTERNAL actions a commit cannot close → never auto-retracted here. Telemetry `ledger_reconciled`.
- **Owner-gated dedup (`dedupOwnerGatedResiduals`)** — collapse owner-gated / not-a-code-defect residuals that share one action key (migration filename · `db:push`/`db:check-sync` · deploy · secret · DNS), keeping **one per distinct real card** (the skill marks each card DONE only after ITS follow-up exists) and dropping only batch-level duplicates (residual `card` is a finding id → no DONE-linkage); a batch-level residual with no matching per-card entry is a genuinely-new action and is kept. Telemetry `owner_gated_deduped`.

The skill's Step 5.1 disk reconciliation **still runs** — it is now a safety net over a pre-cleaned ledger (the self-correction is conservative; F-040 worktree-not-merged still applies), not the sole defence. Same recurring shape as prior `new2` fixes: the splice existed in ONE location (`integrateCrossCard`), the resolution-detection was needed in ALL paths that close a residual. **MINOR** (additive capability + observability on the EXPERIMENTAL `new2` surface; no behavior regression — the guards/policies are unchanged and the retract is proof-gated + conservative; no `baldart.config.yml` key, so the schema-change propagation rule does not apply; no change to `/new`).

### Added

- **`framework/.claude/workflows/new2.js`** — `reconcileLedgerAgainstHead()` (new `Reconcile` phase, conservative proof-gated retract of already-satisfied residuals) + `dedupOwnerGatedResiduals()` (collapse duplicate external actions), both run right before `finalReturn`. New telemetry fields `ledger_reconciled` + `owner_gated_deduped`; new counters wired through `buildTelemetry`.
- **`framework/.claude/skills/new2/SKILL.md`** — documents that `residuals[]` now arrives pre-cleaned (Step 5.1 reframed as a safety net) and records the two new telemetry fields in the A/B step.

## [4.30.1] - 2026-06-11

**`new2`: stop spending opus on mechanical ops steps — explicit per-step model overrides.** Three `general-purpose` agents had no `model:` override, so they inherited the session's main-loop model (opus) for work that needs none. The Merge step is a deterministic OPS/GIT executor (git merge + YAML status reconciliation + grep-based epic closure + leave-and-report hygiene gates) whose correctness-critical checks (F-029 forcedDone guard, F-040 deferred guard) are enforced in JS AFTER it returns — independent of the agent's reasoning → **sonnet**. The per-card Codex review agent is a pure DRIVER (runs the companion, strips `[codex]` traces, maps findings) — the review intelligence is Codex, run externally → **haiku**. The post-merge Production Readiness checklist is non-blocking report-not-execute → **sonnet**. The Pre-flight agent (DAG + ownership map + idempotency — it grounds the whole batch) intentionally **stays opus**. **PATCH** (cost optimization on the EXPERIMENTAL `new2` surface; no behavior change — the deterministic guards/policies are unchanged; no config key).

### Changed

- **`framework/.claude/workflows/new2.js`** — `merge` → `model: 'sonnet'`; `review:<card>:codex` driver → `model: 'haiku'`; `production-readiness` → `model: 'sonnet'`. `preflight` unchanged (opus).

## [4.30.0] - 2026-06-11

**`new2`: Cross-Card Integration Pass — implement residuals deferred by a per-card ownership artifact in-batch, instead of leaving them as follow-ups to manage.** A residual deferred `out-of-ownership` is often a *process* artifact: during the per-card pipeline each card may only touch its own MAY-EDIT, so a fix that needs another card's files is deferred. But `new2` is strictly sequential — once every card is committed that boundary no longer binds. A new pass, run BEFORE the final review, re-runs `resolve()` for those residuals with ownership widened to `card MAY-EDIT ∪ remedy files` (domain-routed — never a raw coder), but ONLY when the remedy lands inside the batch union (`within()`); a remedy outside the batch is a future card's work and stays a follow-up. Transient `outage` residuals are retried with the card's own ownership. Everything else (`owner-gated` / `not-a-code-defect` / `baseline-not-reached` / new-AC `scope-expansion`) still defers — a coder physically cannot do those, or it would be silent scope creep. Integrated fixes are committed (one `[integrate]` commit) so the final review covers them, and a fully-freed card is marked DONE. **MINOR** (additive capability on the EXPERIMENTAL `new2` surface; scope per the user decision = out-of-ownership-within-batch + outage only; no `baldart.config.yml` key, so the schema-change propagation rule does not apply; no change to `/new`).

### Added

- **`framework/.claude/workflows/new2.js`** — `integrateCrossCard()` (new `Integrate` phase between `Implement` and `Final`): collects `out-of-ownership`/`outage` residuals, batch-union `within()` gate on remedy files, surgical ownership widening, domain-routed `resolve()` re-run, one integration commit + YAML DONE-marking for fully-freed cards, ledger cleanup. New telemetry `cross_card_integrated`; the residual ledger now carries `domain` + `remedyFiles`, and `cardMayEdit` records per-card MAY-EDIT for the union.
- **`framework/.claude/workflows/new2-resolve.js`** — `noFollowup` arg: when the integration pass re-runs an already-tracked residual, a failed retry returns the verdict WITHOUT minting a duplicate follow-up card (the original residual stays tracked). `materialiseFollowup()` now propagates `remedyFiles` so the caller can decide whether a remedy is in-batch.
- **`framework/.claude/skills/new2/SKILL.md`** — the A/B record step keeps `cross_card_integrated` alongside `deferral_breakdown` (how many deferrals were genuinely undeferrable vs absorbed in-batch).

## [4.29.1] - 2026-06-11

**`new2`: `deferral_breakdown` telemetry — see WHY residuals became follow-ups, per class.** Follow-up cards in `new2` are not "useless deferred work": an in-scope fixable anomaly is fixed directly by `resolve()`'s domain fixer in-batch; a follow-up is created ONLY for a residual that is structurally undeferrable (`out-of-ownership` would edit another card's files · `owner-gated`/`not-a-code-defect`/`baseline-not-reached` aren't code a coder can apply · `unresolved` already failed fixer+judge+tier-2 · `scope-expansion` with a new AC needs a PRD decision · `outage`). To diagnose a run with *many* follow-ups, the telemetry now counts residuals per class so a skewed breakdown points at the real root cause (many `out-of-ownership` → PRD MAY-EDIT too narrow · many `unresolved` → fixes genuinely hard · many `scope-expansion` → cards under-specified upstream) — data first, before any change to the deferral logic. **PATCH** (observability only on the EXPERIMENTAL `new2` surface; diagnosis-only — nothing is auto-implemented from a residual; no behavior change, no config key).

### Added

- **`framework/.claude/workflows/new2.js`** — `telemetry.deferral_breakdown` (per-class residual counts, derived from the `deferralClass`/`kind` already carried on each residual) + a "Ripartizione per classe" line in the human report's Residui section.
- **`framework/.claude/skills/new2/SKILL.md`** — the A/B record step now keeps `deferral_breakdown` and names it the data to consult BEFORE proposing any deferral-logic change.

## [4.29.0] - 2026-06-11

**Final review (F.4): each domain specialist owns its lane — no cross-domain `code-reviewer` re-judge, no self-judge.** The verification step classified findings by spawning `code-reviewer` over EVERY low-confidence finding. That was wrong on two counts: (a) a `doc` finding from `doc-reviewer` (or an `api`/`perf` finding from `api-perf-cost-auditor`) was re-validated by `code-reviewer` — the WRONG specialist judging another domain (a `code-reviewer` judging prose); (b) when Codex is unavailable the `code-reviewer` *fallback* produces the findings and `code-reviewer` then re-judged its OWN findings — self-judging with no model diversity (the same waste removed from the resolve pass in v4.27.1–2). Now every domain specialist FP-checks its OWN findings in the finding pass (doc-reviewer, api-perf-cost-auditor, and the Codex/`code-reviewer`-fallback code engine), so surviving findings arrive already validated; the residual `confidence < 80` path is routed to the finding's DOMAIN specialist (doc→doc-reviewer, api/perf→api-perf-cost-auditor, security/migration→security-reviewer, test→qa-sentinel, else code-reviewer), and when that specialist is the originating finder the finding is surfaced as `NEEDS_MANUAL_CONFIRMATION` rather than re-judged. Applies to BOTH `/new` (inline F.4 prose, the SSOT) and `new2` (the `new-final-review` workflow). **MINOR** (behavioral refinement of the final-review classification rule; the returned `{findings, classification, summary}` contract is unchanged; no `baldart.config.yml` key, so the schema-change propagation rule does not apply).

### Changed

- **`framework/.claude/skills/new/references/final-review.md` Step F.4 step 9** (SSOT) — replaced "Claude agent findings with confidence < 80 → cross-validate by spawning code-reviewer" with the specialist-ownership rule: specialists self-FP-check their own domain, residual unresolved findings route to the domain specialist, finder===verifier ⇒ `NEEDS_MANUAL_CONFIRMATION`.
- **`framework/.claude/workflows/new-final-review.js`** — `doc-reviewer`/`api-perf-cost-auditor` prompts now mandate an in-domain false-positive check; their findings (and the `code-reviewer` fallback's) are marked `preValidated` so they skip the re-judge; `verifyFinding()` uses a new `domainVerifier()` router (never a hardcoded `code-reviewer`) and short-circuits a finder===verifier case to `NEEDS_MANUAL_CONFIRMATION`. `meta.description` + Verify phase detail updated.

## [4.28.1] - 2026-06-11

**Doc: list the `new.migration-example.md` overlay example in the overlays README.** Completes the v4.28.0 Migration Gate docs — the example overlay file shipped in v4.28.0 was not yet listed in the `framework/templates/overlays/README.md` example table. **PATCH** (doc-only).

### Changed

- **`framework/templates/overlays/README.md`** — added the `new.migration-example.md` row (Migration-Gate apply modalities for `/new` Phase 0 step 1b · `new2` Step 3.5).

## [4.28.0] - 2026-06-11

**`new2`: front-loaded Migration Gate — declared DB migrations are applied BEFORE the batch, so downstream cards verify against a live schema.** The workflow runs autonomously and cannot apply an owner-gated DB migration mid-run, so a declared migration was deferred to end-of-batch — and every downstream card was built/verified against a schema not yet live (`validation_commands` / QA / E2E / DB-generated `tsc` types fail falsely → those cards cascade into deferral). The new gate runs in the **skill** (the main loop, which can interact — the workflow's zero-ask contract is untouched), mirroring `/new` Phase 0 step 1b: it finds the batch epic's `migration_plan` (`required: true`), verifies artifacts on disk, assembles apply modalities (epic `apply_modalities` + `.baldart/overlays/new.md` `## Migration modalities` + project memory + built-in `Già applicata`/`Abort`), asks ONE pre-launch question, executes the chosen modality in `$MAIN`, and passes a `migration` manifest into the workflow. **MINOR** (additive capability on the EXPERIMENTAL `new2` surface; the gate is a silent no-op when no migration is declared and degrades to today's end-of-batch deferral when artifacts are missing — no regression; the `migration` arg is the skill→workflow contract only, NOT a `baldart.config.yml` key, so the schema-change propagation rule does not apply).

### Added

- **`framework/.claude/skills/new2/SKILL.md` Step 3.5 — Migration Gate.** BLOCKING only when a migration is declared; otherwise a silent no-op. Emits the `migration = { status: 'none'|'applied'|'skipped'|'degraded', modality?, summary?, artifacts?, affects_cards? }` manifest passed to the workflow (Step 4 args block). The final-record step also logs `migration_gate: <status>` so the A/B comparison stays honest about when a migration was front-loaded (a pre-launch interaction, NOT a mid-batch question — the zero-ask-during-batch invariant holds).
- **`framework/.claude/workflows/new2.js` — migration manifest consumption.** Reads `a.migration` (args contract documented), and when `status === 'applied'` injects an EXCEPTION to the F-016 owner-gated rule in the preflight (the schema-apply / db-push AC of the affected cards must NOT be classified as a policy-deferred AC — it is satisfied) and a `MIGRATION LIVE` note into each affected card's brief (run REAL validation against the live schema, do not defer or build against an absent schema). A migration NOT covered by the manifest still follows the existing end-of-batch owner-gated deferral.

## [4.27.2] - 2026-06-11

**`new2` resolve: kill the second self-judging adversarial pass for `security` too.** v4.27.1 removed the wasteful adversarial doc review (doc-reviewer judging doc-reviewer). An audit of every fixer→judge pair in the resolution pass found `security` is the exact same structural case: the fixer is `security-reviewer` (protected domain) and so is the judge, so the mandatory cross-check was `security-reviewer` judging `security-reviewer` — no cross-model diversity, the same waste. The skip is now driven by a structural guard, `selfJudges = (fixerAgent === judgeAgent)`, instead of a per-domain allowlist, so it covers doc + security today and can't silently regress if the fixer/judge map changes. Every domain where fixer ≠ judge (ui/perf/test/code/migration) keeps the mandatory adversarial cross-check unchanged. **PATCH** (cost/latency optimization on the EXPERIMENTAL `new2` surface; no config key, no change to `/new`).

### Changed

- **`framework/.claude/workflows/new2-resolve.js`** — introduced `selfJudges`; both `judgeVerify()` and the terminal-judge ratification branch short-circuit when `selfJudges` is true (was `domain === 'doc'`). `meta.description` updated to describe the structural fixer===judge rule.

## [4.27.1] - 2026-06-11

**`new2` resolve: never run an adversarial doc review.** When a resolution pass fixed a `doc`-domain finding, the fixer was `doc-reviewer` (which applies *and* self-verifies the fix in one pass) — but `judgeVerify()` then spawned `doc-reviewer` *again* as the mandatory adversarial judge, plus the terminal-judge ratification used it a third time. That is `doc-reviewer` judging `doc-reviewer`: zero cross-model diversity, pure waste of tokens and time. The doc domain now trusts the single reviewer-writer pass — the adversarial judge and the terminal-judge ratification are both skipped for `doc` only (every other domain keeps the mandatory cross-check unchanged, since their fixer and judge are genuinely independent specialists). **PATCH** (cost/latency optimization on the EXPERIMENTAL `new2` surface; no config key, no change to `/new`, no behavior change for code/ui/security/perf/test resolutions).

### Changed

- **`framework/.claude/workflows/new2-resolve.js`** — `judgeVerify()` short-circuits for `domain === 'doc'` (accepts the first verified attempt without re-spawning), and the terminal-judge branch ratifies a doc terminal verdict directly instead of re-running `doc-reviewer`. `meta.description` updated to document the doc exception.

## [4.27.0] - 2026-06-11

**`/prd`: the Obsidian back-reference is now two-phase — the spec note is linked the moment the PRD starts, not only at merge.** The end-of-run snippet (v4.22.0) is gated on a successful merge, so a PRD that is paused or abandoned mid-flow (e.g. parked at the UI-design step, worktree never merged) left its origin note empty — even though detection already happened at Step 1. Observed in the wild: a `realtime-order-collaboration` PRD stuck at `ui-design` never wrote back, while a completed sibling PRD did. **MINOR** (additive capability on `/prd`; reuses the existing slug-keyed markers + the snippet `status:` lifecycle field; no `baldart.config.yml` key, so the schema-change propagation rule does not apply).

### Added

- **`framework/.claude/skills/prd/references/obsidian-backref.md`** — new shared SSOT for the back-reference: the marker contract (`<!-- BALDART:prd:<slug> START/END -->`), the idempotent read-replace-or-append write procedure, the skip conditions (no note / `path: none` / unwritable → log, never fatal), and the two write modes. Both call sites cite it, so the snippet shape can't drift between them.
- **Provisional write at detection (discovery-phase Step 1 point 2).** Right after the Obsidian note is detected, `/prd` writes a lightweight `status: drafting` block (slug + deterministic branch `prd/<slug>` + planned PRD path + trunk) into the note and sets state `## Obsidian Spec Note / status: provisional-written`. The note reflects "a PRD was started here" immediately.

### Changed

- **Final write upgrades the provisional block in place (validation-phase Step 7.6).** Same slug-keyed markers, so the post-merge full payload (epic + card IDs, merge SHA/PR, `resources:`, `status: planned`) replaces the `drafting` block — no duplicates. Step 7.6 is now a lean delegation to `obsidian-backref.md § Mode: FINAL` instead of an inlined copy of the snippet.
- **Snippet `status:` enum tightened to `drafting | planned | done`** (was `planned | in-progress | done`) — a clean ladder: PRD in stesura → mergiato/pianificato → in produzione.
- **State `## Obsidian Spec Note / status` enum** gains `provisional-written`: `none → detected → provisional-written → back-reference-written`.
- **Coupling fix:** `prd-writing-phase.md` Step 4 point 2 now adds the PRD Canonical Sources row when the note status is anything other than `none` (was hard-coded to `== detected`), so the provisional-write status change doesn't suppress the traceability row.
- **`SKILL.md` HARD RULE 19** rewritten for the two-phase lifecycle; flow-table gains a `1.2` row and annotates `7.6` as the FINAL upgrade.

## [4.26.1] - 2026-06-11

**`new2`: specialization integrity — a full audit of every agent spawn; no role mixing anywhere.** User principle: code is written ONLY by `coder`, UI only by `ui-expert`, each agent does one thing. The audit of all 16 spawn sites found two genuine violations and three under-specified plumbing roles. **PATCH** (role-integrity fixes on the EXPERIMENTAL `new2` surface; no config key, no change to `/new`).

### Fixed

- **`new2.js` E2 — the ops pre-flight agent no longer repairs the baseline.** "baseline FAILS → fix once" had a general-purpose git agent editing source code. Now: the pre-flight returns `baseline:'fail'` + an actionable log (explicit role boundary: it never edits source/doc files), and the WORKFLOW spawns the **coder** specialist for one bounded repair attempt — verified by deterministically re-running the baseline gates (no claim to trust), `E2-baseline: FIXED-BY-CODER` ledger row; still failing → batch-fatal as before. Zero extra spawns in the happy path.
- **`new2.js` — the Codex driver never reviews.** Its runtime fallback was "perform the review yourself with the code-reviewer lens" — a general-purpose agent doing code review. Now the driver returns `note:'codex-unavailable'` with empty findings, and the workflow spawns the REAL `code-reviewer` (same `stdReview` call as the matrix), ledgered as `review-codex: FALLBACK`.
- **`new2-resolve.js` — judge map completed per domain.** `doc` fixes are now judged by **doc-reviewer** (code-reviewer judging prose was cross-domain) and `test` fixes by **qa-sentinel**; `security`/`migration` → security-reviewer and `perf` → api-perf-cost-auditor unchanged; `ui`/`code` stay with code-reviewer (the judge verifies a CODE change — its charter, incl. DS rule 8 for UI). Fixer and judge of the same type remain two independent adversarial instances.

### Changed

- **`new2.js` — explicit ROLE BOUNDARY lines on every plumbing agent** (pre-flight, commit, merge, production-readiness): they never edit source/doc files; commit/merge touch ONLY card YAML status fields + registry rows; a content-level merge conflict is leave+report, never hand-resolved; production-readiness executes stack-matched commands but reports (never edits) code/config changes. The full writer map is now: code/perf/migration/test fixes → `coder`; UI → `ui-expert`; security fixes → `security-reviewer`; docs → `doc-reviewer`; backlog YAML → `prd-card-writer`; bookkeeping (status/registry) → mechanical commit/merge agents.

## [4.26.0] - 2026-06-11

**`new2`: Phase 1 decomposed into specialist agents — the owner implements, it no longer explores.** The old per-card pipeline gave the owner agent one mega-prompt ("you ARE claim + architect + plan-auditor + owner"), so the owner absorbed the whole codebase exploration into its own context and reached the actual coding with a degraded window. Phase 1 now runs as dedicated specialists with file handoff (`/tmp`), per the "ognuno fa una cosa" principle. Verified premise: nested subagent spawning does NOT exist in Claude Code (official docs: "Subagents cannot spawn other subagents" + 2 empirical probes), so the decomposition lives at the WORKFLOW level — the JS is the orchestrator, exactly what dynamic workflows are for. **MINOR** (pipeline capability on the EXPERIMENTAL `new2` surface only; no config key, no change to `/new`).

### Changed

- **`framework/.claude/workflows/new2.js` — per-card `codebase-architect` specialist (B7).** Context retrieval is now a dedicated `codebase-architect` spawn that writes the COMPLETE untruncated baseline to `/tmp/arch-baseline-<CARD>.md` (the owner and reviewers Read the file; the structured return stays minimal). Bonus over the old pre-flight snapshot: the per-card architect sees prior in-batch commits — card N's baseline reflects what cards 1..N-1 changed. The pre-flight no longer writes baselines (generalist relieved of specialist work; `archBaselinePaths` removed from its contract). **Fail-safe**: architect crash/not-ok degrades to the old inline behavior (the owner explores itself) — never blocks the card; transient outage re-queues it.
- **`new2.js` — `plan-auditor` demoted to a deterministic DRIFT gate.** `/prd` already validates every card at creation time, so an unconditional re-audit per card was duplicate work. plan-auditor now runs ONLY when execution-time drift is plausible: (a) a prior in-batch commit touched the card's declared surface (`filesLikelyTouched` ∩ prior `filesChanged`, computed in JS), (b) the architect found declared paths missing (stale card — factual `missingPaths` check, no judgement), or (c) `review_profile: deep` (Rule C escalation). Its corrections amend the implementation BRIEFING (never the backlog YAML). A fresh single-card batch with no drift evidence skips it at zero cost. Crash → non-blocking skip, ledgered.
- **`new2.js` — epic guard moved to JS (zero spawns for trackers).** The pre-flight returns `cardGraph[].isEpic` (implement.md §6b rule); an epic card now short-circuits in JS before ANY spawn — the old path burned a full owner-agent spawn just to learn the card was a tracker. The impl agent's own epic flag stays as backstop.
- **`new2.js` telemetry — `per_card[].phase1`** records `{architect: done|inline-fallback, audit: pass|fixes|skipped-no-drift|skipped-error|skipped-no-baseline}` plus `phase1-architect`/`phase1-audit` ledger rows, so the A/B accounting can price the decomposition.

## [4.25.0] - 2026-06-11

**`new2`: deterministic per-card review matrix — `balanced` no longer spawns every specialist on every card.** The old fan-out ran code-reviewer + doc-reviewer + qa-sentinel + api-perf-cost-auditor (+ security-reviewer) unconditionally at `balanced`: an api-perf pass on a doc-only card, a doc pass on a pure-code card — 1–3 wasted spawns per card. Each specialist now runs IFF its domain is evidenced by the card's actual surface (`scopeFiles ∪ MAY-EDIT`), computed deterministically in JS (no agent judgement) and audited via a `review-matrix` ledger row per card. **MINOR** (review-behavior capability on the EXPERIMENTAL `new2` surface only; no config key, no change to `/new` — its interactive profiles are untouched; the schema-change propagation rule does not apply).

### Changed

- **`framework/.claude/workflows/new2.js` — relevance-gated `balanced` fan-out.** `touchesCode` → code-reviewer + qa-sentinel; `touchesDocs` (`.md`/`.mdx` or under `paths.docs_dir|references_dir|wiki_dir|prd_dir|design_system`) → doc-reviewer (on a doc-ONLY card it IS the core finder — code-reviewer and qa-sentinel are skipped); `touchesApiData` (superset of the final review's `hasApiDataFiles` regex) → api-perf-cost-auditor; the v4.24.2 `securityRelevant` gate → security-reviewer. Surface = `scopeFiles ∪ MAY-EDIT`, so a DoD-mandated doc the implementer FORGOT to edit still triggers doc-reviewer (it's in MAY-EDIT). **Fail-safes**: `deep` keeps the unconditional full fan-out (Rule C escalation is respected); an empty surface (no evidence) falls back to the full fan-out — absence of evidence is not evidence of absence. `light`/`skip` unchanged.
- **`new2.js` Phase Final — the F-041 `singleCard`/slim skip is now coverage-gated.** The final review's doc pass is skipped for a single-card batch ONLY when doc-reviewer ACTUALLY ran per-card (`reviewersRun`); otherwise it runs as the missing-doc-update safety net — so gating a per-card specialist off never leaves its domain unreviewed at batch level. This also closes a **latent F-041 hole**: under `light`, doc-reviewer never ran per-card, yet the slim skip still suppressed the final doc pass for single-card batches.
- **`new2.js` telemetry — `per_card[].reviewers`** records the gated matrix per card, so the A/B spawn accounting can measure exactly what the gating saves.

## [4.24.3] - 2026-06-10

**`new2`: two follow-ups from the v4.24.2 logic review — epic closure no longer misses deferred cards, and the owner-gated classification reaches the final review.** Both are "parallel location" gaps of earlier fixes (the v4.17.2 meta-lesson): v4.22.1's epic-closure and v4.24.1's owner-gated deferral each missed one site. **PATCH** (bug-fix to the EXPERIMENTAL `new2` surface only; no config key, no change to `/new`).

### Fixed

- **`new2/SKILL.md` (Step 5.2) — epic-closure re-check after deferred-DONE.** The merge agent's epic-closure (Phase 6b step 5e) runs BEFORE the skill marks deferred cards DONE post-run — so an epic whose last open child was a deferred card stayed TODO forever (nothing re-checked it). The skill now re-runs the all-children-DONE check for the parents of the cards it just marked DONE, closing the epic in the same reconciliation commit.
- **`new2.js` (Phase Final) — owner-gated deferrals no longer block the merge at final review.** The `merge-blocker`/`qa-fail` resolves checked only `.status`: a finding whose sole remedy is an external/infra step (e.g. the same pending remote `db:push` a reviewer re-raises batch-wide) set `mergeBlocked` and stranded a complete batch. The `deferralClass` check from the per-card loops (v4.24.1/v4.24.2) now applies here too: `owner-gated`/`not-a-code-defect` → follow-up tracked + `DEFERRED-OWNER-GATED` ledger row, merge proceeds; a genuine unresolved code defect still blocks.

## [4.24.2] - 2026-06-10

**`new2`: holistic logic review — deterministic owner_agent routing, a merge gate that no longer strands the batch, `deferralClass` end-to-end, and −N agent spawns per run.** A full logic review of `new2.js`/`new2-resolve.js`/`SKILL.md` against `/new`'s reference modules found four correctness defects and five sources of wasted spawns. The headline routing bug: the pre-flight's `ownerAgent` was passed RAW as `agentType` — the G25 "unknown→coder" rule lived only in the prompt, so any freeform value (`claude`, `backend`, a typo) was a PERMANENT spawn error → card `failed` → (combined with the merge-gate bug) the whole batch unmerged. **PATCH** (bug-fix/hardening of the EXPERIMENTAL `new2` surface only; **no `baldart.config.yml` key**, **no change to `/new`** — the schema-change propagation rule does not apply).

### Fixed

- **`new2.js` (A1) — deterministic owner_agent clamp in JS, not prompt.** After the pre-flight, `cardGraph[].ownerAgent` is clamped through the exact `/new` router table (`implement.md` §6b): `coder`/`ui-expert` pass through; `plan`/`visual-designer`/`motion-expert`/unknown/missing degrade to `coder`, each with a `[ROUTER]` ledger row (audit trail). The RAW value is kept for the security-relevance heuristic. Fixes the "wrong agent for the card" failure class at the code level.
- **`new2.js` (A2) — the merge integrity gate matches its own comment.** `followup`/`blocked` cards (rolled back + tracked in the offline-safe residual ledger) no longer count as "incomplete": one failed card used to strand every committed card in an orphaned worktree with no resume path (the run wasn't `degraded`, so the skill never resumed). `failed` (crash) counts as complete ONLY when its cleanup verified the worktree clean.
- **`new2.js` + `new2/SKILL.md` (A3) — `deferralClass` end-to-end (v4.24.1 covered only the review-blocks loop).** The ac-unmet loop now records WHY each deferral happened; `residuals[]` carry `deferralClass` and committed cards carry `deferredClasses[]`. The skill marks a deferred card DONE post-run ONLY when every class is `owner-gated`/`not-a-code-defect`/`policy-deferred-ac`; an `unresolved` class (an AC the workflow tried and failed to implement) keeps the card IN_PROGRESS and is surfaced explicitly — a genuinely unmet DoD can no longer be auto-DONE'd with a follow-up as a fig leaf (F-029).
- **`new2.js` (A4) — crashed cards clean up after themselves.** `crashResult` now runs the rollback (whole-worktree scope — safe: all committed work lives in HEAD), so a crashed card's dirty files no longer poison the next card's E4 reconcile with a misleading `out-of-ownership` revert.
- **`new2.js` (A5) — `${paths.backlog_dir}` instead of a hardcoded `backlog/*.yml`** in the epic-closure merge prompt (consumer portability).

### Changed

- **`new2.js` (B1) — per-card idempotency probe removed (−N Haiku spawns/run).** `cardGraph[].alreadyCommitted` is now probed once by the pre-flight (git-authoritative: commit in `trunk..HEAD` + validations green + no open follow-up); on a fresh worktree it costs nothing, and resume stays covered by the journal cache.
- **`new2.js` (B2) — `security-reviewer` fan-out only when the card's files intersect `high_risk_modules`** (the bare `highRisk.length` fired it on every card of every project that configures the list); the owner-agent and brief-token triggers are unchanged.
- **`new2.js` (B3) — review blocks and scope-expansion findings batched per kind+domain → ONE `resolve()` per group** via the existing `findings[]` contract (the same F-007 batching the final review already used). One-by-one routing was the dominant per-card cost driver (each resolve = fixer + judge + possible Tier-2 fan-out).
- **`new2.js` (B4) — `executionMode`/`groups` removed from the pre-flight contract**: the DAG scheduler is strictly sequential (single worktree) and nothing ever read them; telemetry now reports the honest constant. Real parallelism is a future release, after A/B data.
- **`new2.js` (B5/C) — dead code removed**: the never-populated `lessons` mechanism (every cardBrief promised batch lessons that were always `(none)`), the unreachable `resolve()` `'fatal'` branch (`new2-resolve` never returns it — v4.17.2 G1 rule), the always-empty per-card `telemetry` stub (per_card now reports `deferred`/`deferredClasses`/gate count), and the now-unused `batchFatal` flag.

## [4.24.1] - 2026-06-10

**`new2`: an owner-gated gate no longer destroys a completed card (silent work-loss → commit + defer).** A real `/new2` run on a schema-change card produced **zero output** despite 52 min of work: the card's only obstacle was an *owner-gated* step (`db:check-sync` needs an approved remote `db:push`). That step was correctly `policy-deferred` up front — but the **same** condition was *also* re-raised by a reviewer as a fresh `MIGRATION_NOT_DEPLOYED` blocker, which (via the review-block branch's `s !== 'resolved' → cardBlocked`) triggered `rollbackCard`'s `git clean -fd` and **erased the completed migration**. Compounding it: the `E4-file-diff` gate logged `AUTO-REVERTED` while reverting *nothing* (leaving the card's DoD-mandated ADR/ER-doc edits orphaned in the worktree — its MAY-EDIT map was narrower than the DoD), and the residual follow-up was written *inside the worktree* and marked `materialized:true` without disk proof, so it vanished when the batch didn't merge. Root cause confirmed via the gate ledger + on-disk state + two rounds of adversarial review (the obvious "E4 reverted it" diagnosis was **wrong** — E4 was a no-op; `rollbackCard` was the eraser). **PATCH** (bug-fix to the EXPERIMENTAL `new2` skill + its workflows; **no `baldart.config.yml` key** and **no change to shared `/new` prose** — the DONE-deferral is handled entirely inside `new2`'s own merge prompt + skill, so `/new` interactive is provably unaffected; the schema-change propagation rule does not apply).

### Fixed

- **`framework/.claude/workflows/new2.js` + `new2-resolve.js` — classification-based card-block (the primary fix, F-040).** `resolve()` now propagates a structured `deferralClass` from `new2-resolve`'s terminal-judge. A review-block that resolves to an **owner-gated / not-a-code-defect** deferral no longer sets `cardBlocked` → the card's *complete* code is committed (it is **not** rolled back); only a genuine unresolved **code** defect (or `out-of-ownership`/`baseline`/`outage`) still blocks + rolls back. A deliberately-broken migration (`db:reset` failing) is still classified code-defect → blocks, so this is not an over-match escape hatch.
- **`new2.js` — `E4-file-diff` is honest.** The old `AUTO-REVERTED` log reverted nothing. The owner agent now reconciles out-of-ownership edits itself (per `implement.md §11b`) and reports `revertedOutOfOwnership`; the gate logs `REVERTED` / `FLAGGED` to match reality, and an unresolved violation becomes a tracked residual (never silent, never orphaned).
- **`new2.js` pre-flight — ownership map ⊇ DoD (root cause of the E4 false positive).** A card's MAY-EDIT now = `files_likely_touched` ∪ paths **named explicitly** in its `acceptance_criteria`/`definition_of_done` (the ADR/data-model/ER doc a schema-change must touch), so editing a DoD-mandated doc is no longer a file-diff violation.
- **`new2.js` + `new2/SKILL.md` — DONE deferred to the skill, gated on the follow-up existing on disk (closes the F-029 false-DONE the review surfaced).** A card carrying an open owner-gated/policy-deferred AC commits its code but stays **NON-DONE**; the merge agent is told to leave those cards non-DONE; the **skill** marks them DONE post-run **only after** verifying the deferral's follow-up exists on disk in the main repo (fail-loud otherwise — never DONE with a dropped requirement).
- **`new2-resolve.js` + `new2/SKILL.md` — follow-ups are reliable.** Follow-up materialisation is best-effort inside the workflow (it rides the merge if the batch merges); the **skill is the SSOT**, verifying every residual against the **main-repo** disk and creating any missing follow-up there — so a non-merged batch never loses one. `materialized` is now advisory only.
- **`new2.js` (Fix G) — AC-deferral dedup is text-drift-proof.** The policy-deferred-AC key is now scoped to the AC *number* (`acSig`), so a deferred AC is no longer re-routed to `resolve()` a second time when the pre-flight and implement agents word the AC text slightly differently.
- **`new2/SKILL.md` — telemetry reconciled against disk.** Before recording, the skill verifies each `committed` card actually has a commit on trunk and never presents progress the disk does not show; adds `cards_deferred_done_pending` so the A/B record distinguishes "code landed" from "DONE".

## [4.24.0] - 2026-06-10

**Atomic backlog-ID allocator — no FEAT/BUG collisions across parallel worktrees.** When several `/prd` (or `/new`/`new2` follow-up) sessions run in parallel on sibling worktrees, each branched from the same trunk, the old `max(^id: FEAT-) + 1` scan made them all land on the **same next integer**: the other session's card was in flight on an unmerged sibling branch, invisible to both the local backlog and the trunk merge-base — so two epics both became `FEAT-0024` and conflicted at rebase/merge. The `git fetch` + merge-base scan only ever covered *already-merged* IDs, never in-flight ones. A new allocator anchors a lock + per-prefix high-water mark in `$MAIN/.worktrees/` (the shared coordination point every worktree already reaches via `git rev-parse --git-common-dir`, gitignored like `registry.json`), so a reservation is atomic across every worktree **on the same machine**. The high-water mark bumped under the lock is the correctness anchor; `max()` against the real backlog + sibling-worktree backlogs + reservations log + trunk merge-base makes it **self-healing**. **MINOR** (additive capability on the `worktree-manager` skill; opt-in — callers fall back to the inline merge-base scan + `[ID-RACE-RISK]` note when the script is absent, so older installs and cross-machine cloud agents are unaffected. **No `baldart.config.yml` key** — the allocator reuses `paths.backlog_dir` + the gitignored `.worktrees/` convention, so the schema-change propagation rule does not apply).

### Added

- **`framework/.claude/skills/worktree-manager/scripts/allocate-id.sh`** — prefix-parametric (`FEAT`/`BUG`/`UI`/`DOC`/`PERF`/…) atomic ID allocator. `reserve <PREFIX> <slug>` prints the next free integer zero-padded to 4 digits; `release <worktree-path>` prunes a finished worktree's reservations. Cross-process mutex via atomic `mkdir` (stale-stolen after 30s via the lock dir's own mtime — a directory, not a git ref, so it never touches the shared `refs/stash` that `git stash` in worktrees is forbidden over). The slow `git fetch` runs **before** the lock, keeping the critical section local-FS-only (sub-second). Reads `paths.backlog_dir` + `git.trunk_branch` from `baldart.config.yml` and resolves `$MAIN` at runtime — no hardcoded project facts (passes the framework-edit-gate). Same-machine scope; the trunk merge-base scan is the best-effort cross-machine guard.

### Changed

- **`framework/.claude/skills/worktree-manager/SKILL.md`** — new section **"ID Allocation"** documenting the allocator, the shared `.id-alloc.lock/` / `.id-hwm-<PREFIX>` / `id-reservations.jsonl` files, the same-machine scope + opt-in fallback contract, and the gap-tolerant monotonic-counter design. `release` is wired into the cleanup paths of `/mw` (step 7), `mw-docs` (step 6), and `/cw` (step 4).
- **`framework/.claude/agents/prd-card-writer.md`** — § "FEAT-XXXX numbering" + Pre-Generation Checklist item 1 now reserve the integer via the allocator (primary path), with the trunk fetch + merge-base scan + `[ID-RACE-RISK]` note as the documented fallback when the script is absent. Applies to any prefix the writer mints.
- **`framework/.claude/agents/prd.md`** — § 4.1 NAMING CONVENTIONS prefers the allocator (any prefix) over the plain backlog scan, with the same documented fallback.

## [4.23.0] - 2026-06-09

**Functional Traceability Gate — no orphan UI affordances from mockups.** Handed-off mockups (Claude Design / Figma / the internal generators) routinely add interactive-looking chrome — buttons, icons, menu entries — that no requirement backed. Implementing a mockup **1:1 for fidelity** materialised that chrome into real markup + dead handlers + unused icon imports, and the cruft propagated forever. A new **unconditional** gate (independent of `features.has_design_system`) now requires every interactive or iconographic artifact to trace to a function before it becomes code: each artifact is classified **backed** (implement) / **decorative** (static-render, `aria-hidden`) / **orphan** (drop or escalate — never silent). The oracle is the PRD **UI Element Inventory `function_ref`** allowlist → card AC → orphan. Enforced at three boundaries: PRD mockup-intake (orphans become BLOCKING Discovery items), `ui-expert` implementation-time (BLOCKING pre-work), and `code-reviewer` / `/design-review` per-merge (`UI_ORPHAN_AFFORDANCE` HIGH finding). The `visual-fidelity-verifier` gains a `resolved_orphans` carve-out so a deliberately-dropped orphan is *expected-absent*, not a `component-missing` false positive. **MINOR** (additive discipline across existing UI surfaces; no new agent/skill/command and **no `baldart.config.yml` key** — the gate is registry-independent and reads its allowlist from the PRD inventory, so the schema-change propagation rule does not apply).

### Added

- **`framework/agents/design-system-protocol.md`** — new SSOT section **"Functional Traceability Gate (BLOCKING — unconditional)"**: the rule, the oracle hierarchy (UI Element Inventory `function_ref` → card AC → orphan), the backed/decorative/orphan classification + outcome matrix, the drop-or-escalate contract for orphans, where it runs (design / implementation / review), and the canonical `UI_ORPHAN_AFFORDANCE` finding code. The Gating section gains an explicit carve-out: this is the one part of the module **not** gated on `features.has_design_system`.

### Changed

- **`framework/.claude/agents/ui-expert.md`** — BLOCKING step `0b` (Functional Traceability Gate) added to "When Designing New Interfaces"; check `3b` added to "When Reviewing UI Code"; new red-flag category **Functional traceability (`UI_ORPHAN_AFFORDANCE`)**.
- **`framework/.claude/agents/code-reviewer.md`** — new section **"Functional Traceability (MANDATORY for UI work — unconditional)"**: live affordances tracing to nothing are `UI_ORPHAN_AFFORDANCE` HIGH findings that block merge; positioned as the per-merge net behind `ui-expert`'s implementation-time gate.
- **`framework/.claude/agents/visual-fidelity-verifier.md`** — input contract gains optional `resolved_orphans[]`; `component-missing` / `interactive-state-missing` carve-out so a dropped/decorative orphan is expected-absent rather than a fidelity finding.
- **`framework/.claude/skills/e2e-review/SKILL.md`** — populates `resolved_orphans` in the verifier payload from the card's UI Element Inventory (`function_ref: decorative` or gate-resolved drops).
- **`framework/.claude/skills/prd/references/discovery-phase.md`** — mockup-intake `mockup_analysis.screens[]` schema gains an `affordances[]` block (`label` / `traces_to` / `classification`); new **Affordance discipline (Functional Traceability)** making every `orphan` a BLOCKING Discovery item resolved via Scope Expansion / `/prd-add` / downgrade / drop.
- **`framework/.claude/skills/prd/references/ui-design-phase.md`** — Step 3d gates (Full + Hybrid) now require every interactive/iconographic inventory row to carry a `function_ref` with no unresolved `orphan`.
- **`framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md`** — § 3 gains a functional-traceability constraint (only ship controls tied to a listed function; mark decorative explicitly); § 7 gains a per-element traceability map + orphan-to-decide list requirement.
- **`framework/.claude/skills/ui-design/references/inventory.md`** — UI Element Inventory table gains a **Function ref** column + the rule binding it to the gate.
- **`framework/.claude/commands/design-review.md`** — new step 6 (Functional Traceability check) emitting `UI_ORPHAN_AFFORDANCE` HIGH→major findings.

## [4.22.1] - 2026-06-09

**Epic-parent closure at end of `/new` + `/new2`.** When the last child of an epic is implemented, the epic/parent card now gets marked `DONE` automatically instead of being left stuck at `TODO`. The Phase 6b post-merge reconciliation gate only ever touched the **batch cards** (the children); the epic/parent card (`group.is_epic: true`) is excluded from the batch by the epic guard, so it never got reconciled — final-review only *reported* "all children done" without writing the epic's own status. **PATCH** (strengthens the existing Phase 6b reconciliation gate; no new agent/skill/command/config key — `/new2` inherits it because its merge agent follows `merge-cleanup.md` as SSOT).

### Changed

- **`framework/.claude/skills/new/references/merge-cleanup.md`** — Phase 6b gains **step 5e (Epic-parent closure)**: for each distinct `group.parent` of the batch (plus any epic card in the batch itself), if **every** child is `DONE` (checked across the whole backlog, not just the batch subset), force-update the epic card to `status: DONE` + `completed_date` + note, folded into the same reconciliation commit. Leaves the epic untouched if any child is still open. The step-6 tracker block now records `Epics closed: …`.
- **`framework/.claude/skills/new/references/final-review.md`** — the "all cards in the epic are DONE" Next-Steps message now states the epic card itself is marked `DONE` (by Phase 6b step 5e), with a note that a still-`TODO` epic under all-DONE children is a Phase 6b failure to flag.
- **`framework/.claude/workflows/new2.js`** — the merge agent's deterministic policies gain an explicit **EPIC CLOSURE** rule (all-children-DONE gate, distinct from the F-029 forced-DONE prohibition since the epic is a tracker), `MERGE_SCHEMA` gains `epicsClosed[]`, and a new `epic-closure` ledger entry records which epics were closed.

## [4.22.0] - 2026-06-09

**`/prd` Obsidian back-reference.** When a PRD is kicked off from an Obsidian note (the documental starting point), the same note now receives, at the end of the run, an idempotent snippet indexing how the feature was developed and where it landed — PRD path, epic + card IDs, branch, merge commit/PR, and the PRD's Canonical Sources — so the user can later query the note ("come è stata sviluppata la feature X, dov'è finita"). **MINOR** (additive capability on the `prd` skill; no new `baldart.config.yml` key — the note path is runtime-detected and state-persisted, so the schema-change propagation rule does not apply).

### Added

- **`framework/.claude/skills/prd/references/validation-phase.md`** — new **Step 7.6 — Obsidian back-reference** (runs after the merge, when the canonical paths/IDs/SHA are final). Writes a marker-delimited section (`<!-- BALDART:prd:<slug> START/END -->`) holding a fenced `yaml` payload + human-readable links back into the spec note. **Idempotent** (re-runs / `/prd-add` replace the block in place), **non-blocking** (no note, unresolved vault path, or unwritable file → skip with a log line). The note lives in the user's vault, outside the worktree and outside git — a plain filesystem write, never staged.

### Changed

- **`framework/.claude/skills/prd/references/discovery-phase.md`** — Step 1 point 2 Obsidian detection broadened beyond `[[wikilink]]` to also accept `obsidian://open?…` URIs and pasted `.md` paths, and now resolves + persists the note's **absolute `path:`** (vault root from `paths.obsidian_vault` / memory / one-time ask) so Step 7.6 can write back without re-deriving.
- **`framework/.claude/skills/prd/assets/state-template.md`** — new structured `## Obsidian Spec Note` section (`wikilink:` / `path:` / `status:`).
- **`framework/.claude/skills/prd/SKILL.md`** — new **HARD RULE 19** (Obsidian back-reference) + Step 7.6 row in the flow table.
- **`framework/.claude/skills/prd/references/prd-writing-phase.md`** — Canonical Sources row now reads the structured `wikilink:` field and cross-links Step 7.6.

## [4.21.1] - 2026-06-09

**Nightly reconciliation of the canonical API registries (errors.md / schemas.md).** Makes the nightly `doc-review` run explicitly keep `${paths.api_errors}` and `${paths.api_schemas}` in sync with the day's code changes, instead of relying on the generic doc↔code drift step. **PATCH** (strengthens existing routine/agent behaviour; no new agent/skill/command/config key).

### Changed

- **`framework/routines/doc-review.routine.yml`** — new step 3b: when `features.has_api_docs: true`, reconcile the canonical `errors.md` (stable error codes added/renamed/removed → apply or flag `DOC_GAP`) and `schemas.md` (Zod/TS shape changes → update or flag `SCHEMA_DRIFT`) against the last-24h commits. Skips silently when the paths are unset.
- **`framework/.claude/agents/doc-reviewer.md`** — new "Canonical API registries — reconciliation (nightly-owned)" section: the agent now explicitly OWNS the code→doc direction for the two registries (the `doc-writing-for-rag` skill owns the doc-authoring direction). Ties together the existing `SCHEMA_DRIFT` + error-code checks.

## [4.21.0] - 2026-06-09

**Code knowledge graph layer (Graphify) + Karpathy wiki rebind + nightly doc-alignment command.** Introduce [Graphify](https://github.com/safishamsi/graphify) as the new retrieval engine replacing the removed RAG (v4.20.0): a single language-agnostic tool (tree-sitter, 28 langs, local/offline, native Leiden communities; pip `graphifyy`, CLI `graphify` + `graphify-mcp`). Opt-in, runtime-detected, silent fallback to LSP→Grep→Git. **MINOR** (additive; new agent + 2 skills + 1 routine + 1 protocol module; new config key `features.has_code_graph` + `graph.*` propagated end-to-end per the schema-change rule).

### Added

- **`features.has_code_graph` + `graph:` block** (`framework/templates/baldart.config.template.yml`) — `graph.{output_dir, auto_rebuild_hook, register_mcp}` (scalars only). Propagated: configure autodetect + prompt + interactive install (`src/commands/configure.js`), update schema-drift detector **extended to diff nested top-level blocks** like `graph:` (`src/commands/update.js`), doctor diagnostic + actions (`src/commands/doctor.js`).
- **`src/utils/graphify-installer.js`** — thin wrapper over Graphify's NATIVE commands (no per-language adapter registry): `detect/install/verify/buildGraph/installGitHook/installAlwaysOn/registerMcp/isStale`. Composes `pipx install graphifyy`, `graphify update` (offline build), `graphify hook install` (auto-rebuild), `graphify <tool> install` (always-on), `graphify-mcp` (MCP).
- **`framework/agents/code-graph-protocol.md`** — new protocol module: when to prefer the graph (structural/relational) vs LSP (symbol) vs Grep (text) vs Git; CLI-first, MCP optional; budget; silent fallback. Wired into `code-search-protocol.md`, `index.md`, `codebase-architect`, and the exploration skills (`context-primer`, `bug`, `prd`, `new`, `simplify`) — all gated on `has_code_graph`.
- **Karpathy wiki rebind** — the LLM-wiki auto-learning loop (removed with RAG in v4.20.0) is re-activated on Graphify's native `GRAPH_REPORT.md` (god nodes / Leiden communities / suggested questions → synthesis candidates), offline. Updated `llm-wiki-methodology.md`, `wiki-curator.md`, `wiki-review.routine.yml`, `capture/SKILL.md`. Gated; dormant when `has_code_graph: false` (no regression).
- **Nightly doc-alignment command** — new agent `doc-graph-aligner` (rebuild graph → cross-check graph vs docs: uncovered core code, stale docs, registry drift → report + synthesis candidates; propose-only), routine `doc-graph-align.routine.yml` (+ `routines/index.yml`, `optional: true`), interactive skill `/graph-align`, and install skill `/graphify-bootstrap`.
- **`framework/docs/CODE-GRAPH-LAYER.md`** — operator guide (lifecycle, schema, native commands, invariants, CI behavior). MCP-INTEGRATION.md + PROJECT-CONFIGURATION.md updated.

### Notes

- **CI install is deliberate**: a Python tool is never installed silently in `--yes`/non-interactive runs — `configure` writes the flag, `baldart doctor` (`graph-install`/`graph-rebuild`/`graph-hook`) and `/graphify-bootstrap` backfill.
- **`graphify-out/`** added to `.gitignore` (generated artifact). **`package.json`** version is unrelated drift (tracked separately from `VERSION`).

## [4.20.0] - 2026-06-09

**Rimozione del motore RAG (strato A); wiki overlay preservato dormiente.** Il layer RAG (`doc-rag` MCP, `search_docs`, loop auto-learning con tap-point `wiki_log`) non era attivo in nessun consumer ed era un costo di istruzioni morto in ~20 file tra agenti, skill e moduli protocollo. Rimosso completamente; il retrieval del codice si riduce a **LSP → Grep → Git**. Il wiki overlay (loop di Karpathy: `/capture`, `wiki-curator`, `paths.wiki_dir`, `has_wiki_overlay`) resta nel framework ma **dormiente**, scollegato dal motore — engine-agnostic e riutilizzabile in futuro su un nuovo motore di retrieval. **MINOR** (nessun agente/skill/comando rimosso, nessun cambio di layout d'install; rimossa la sola chiave config `paths.wiki_log`, non usata da alcun consumer).

### Removed

- **Motore RAG (strato A) da tutti gli agenti, skill e moduli protocollo.** `mcp__doc-rag__search_docs` / `search_docs` / `mode:"hybrid"`, i verdetti `rag_telemetry` (`useful/weak/empty/fallback_degraded`), il tier "RAG hybrid" in `code-search-protocol.md`, l'Investigation Protocol "RAG-first" di `codebase-architect`, il tap-point di instrumentazione `wiki_log`, e la community detection GraphRAG (`search_synthesis`, `mode:"drift"`). File toccati: `framework/agents/{code-search-protocol,llm-wiki-methodology,index}.md`; `framework/.claude/agents/{codebase-architect,doc-reviewer,wiki-curator,code-reviewer,security-reviewer,senior-researcher,plan-auditor,qa-sentinel,coder,api-perf-cost-auditor,prd-card-writer}.md`; `framework/.claude/skills/{context-primer,bug,capture,prd}/` + `prd/references/discovery-phase.md`; `framework/.claude/skills/issue-review/SKILL.md`; `framework/.claude/commands/codexreview.md`.
- **Chiave config `paths.wiki_log`** da `framework/templates/baldart.config.template.yml` + autodetect in `src/commands/configure.js`. `paths.wiki_dir` e `features.has_wiki_overlay` restano (infrastruttura wiki viva).
- **Sezione "Documentation retrieval (RAG)"** da `framework/docs/MCP-INTEGRATION.md`; riferimenti doc-rag Required/optional aggiornati.

### Changed

- **Retrieval hierarchy → LSP → Grep → Git** (era RAG → LSP → Grep → Git) in `code-search-protocol.md` e nell'Investigation Protocol di `codebase-architect` (ora "Canonical router FIRST" via `ssot-registry.md`).
- **`wiki-curator` ridotto** a drift / anchor / frontmatter validation + synthesis-candidate da ADR/PRD (engine-agnostic); rimosse § "RAG Instrumentation" e § "Graph Community Synthesis".
- **Routine `wiki-review`** ripulita dallo scan dei verdetti RAG; resta safety-net strutturale. `routines/index.yml` e README riformulati ("maintenance" invece di "auto-learning loop").
- **`/capture`** scrive ancora la synthesis page; il logging passa da helper `wiki_log` a bullet markdown manuale in `${paths.wiki_dir}/log.md`.

### Backup

- **`docs/archive/rag-layer/`** — backup integrale recuperabile: `README.md` (cos'era / perché / come ripristinare), `snapshot/` (copie pre-modifica dei file toccati), `rebind-to-graphify.md` (come ricablare il loop di Karpathy su un code knowledge graph con community detection nativa). `doc-writing-for-rag` (skill di densità documentale) **non** rimossa — è engine-agnostic.

## [4.19.0] - 2026-06-08

**`/prd`: cross-check di completezza della discovery con Codex (shift-left).** Aggiunge un singolo passaggio cross-model **al termine della discovery**, prima del design — il punto a più alto ritorno per Codex in `/prd`. **MINOR** (additivo/opt-in: scatta solo se il companion Codex è risolto a runtime; nessuna chiave `baldart.config.yml` → schema-propagation N/A).

### Added

- **Discovery Completeness Cross-Check (Codex) — `framework/.claude/skills/prd/references/discovery-phase.md`.** Dopo che i gate di uscita esistenti passano (comprehension ≥99% OR user-proceed, mockup-gap risolti, research risolta) e PRIMA di flippare lo status a `ui-design`, un passaggio Codex (`--wait`, pattern di 6.6d) rilegge lo state file di discovery (dimensioni + risposte + Discovery Log) + il brief di kickoff e cerca i **buchi che la formula 99% non può vedere**: domande mai poste, ambiguità latenti, NFR/error-path/edge mancanti, touchpoint d'integrazione. Razionale: la formula misura la copertura delle dimensioni *note*; un modello diverso intercetta i blind spot del questionatore, e catturare un gap di requisiti qui (pre-design) costa molto meno che a Step 6.6d (post-PRD/post-card). Per ogni gap: o diventa la prossima domanda di discovery (one-per-turn) o uno skip esplicito nel Discovery Log (stessa disciplina dei mockup-gap). **Un solo passaggio** (niente loop). **Rispetta l'intent esplicito**: se l'utente ha forzato l'uscita ("proceed"), il check è advisory-only. **Fallback**: Codex assente → skip con log (gate additivo; nessun fallback same-model Claude — annullerebbe la diversità di modello che è l'intero valore del check). Menzione nella flow table di `SKILL.md`.

## [4.18.0] - 2026-06-08

**Codex come finder a profilo `light` (`/new` + `new2`) + engine Codex opt-in nelle routine cron.** Sposta il carico di review da Claude alla licenza Codex dove la review è read-only e genera finding, mantenendo la diversità di modello (Codex trova, l'agent Claude verifica). **MINOR** (additivo/opt-in con fallback; nessuna rottura install, **nessuna chiave `baldart.config.yml`** → schema-propagation N/A — `review_engine` è schema routine-payload).

### Changed

- **Profilo `light` → Codex è il finder per-card (inverte la scelta v3.38.0).** Prima `light` faceva `code-reviewer` (finder) + FP-gate e deferiva Codex al solo Final FULL gate. Ora `/codexreview` a `light` lancia **Codex come unico finder** + lo Step 3 FP-gate (`code-reviewer` + `codebase-architect` validano): la lettura full-diff esce da Claude, Claude conserva solo la validazione. `full` invariato (entrambi i finder). **Fallback obbligatorio**: Codex non disponibile → `light` degrada a `code-reviewer` finder (comportamento pre-v4.18.0), la review non salta mai. L'invariante BUG-0530 ne esce rafforzata (Codex per-card su ogni card non-triviale). File: `framework/.claude/commands/codexreview.md` (Step -0.5/Step 2/invariant/Method-log), `framework/.claude/skills/new/references/codex-gate.md` (razionale Phase 3.7).
- **Team-mode allineato: i `LIGHT_CARDS` non skippano più D.4b.** La partizione LIGHT/FULL esisteva per tenere Codex fuori da `light`; ora ogni card non-triviale gira `/codexreview` a D.4b (light → Codex-light, full → Codex-full). La D.2 group pass diventa **doc-reviewer only** (il code-reviewer di gruppo sui light cards è rimosso — la loro review si sposta a D.4b). Coverage-assertion e clausola "REVIEW DEPTH SCALES" aggiornate. File: `framework/.claude/skills/new/references/team-mode.md`, `framework/.claude/skills/new/SKILL.md`, `framework/.claude/agents/prd-card-writer.md` (Rule C — semantica Codex-`light`).
- **`new2.js`: mirror del Codex finder a `light`.** Il companion Codex è ora risolto in pre-flight per **ogni** batch (prima solo multi-card per il cross-card), con il path propagato in `sharedCtx`. A `light` il fan-out per-card usa un reviewer **`codex`** (un agent general-purpose che lancia `codex-companion.mjs --wait` sul diff) quando il companion è risolto, con fallback `code-reviewer`. Il FP-gate è preservato a valle dal judge avversariale di `new2-resolve` (F-015). Nuovo campo schema `codexScriptPath`; ledger `codex-companion`; telemetria `codex_resolved` ora probed per ogni batch.

### Added

- **`review_engine: codex` opt-in nelle routine di review (solo backend cron).** `code-review.routine.yml` accetta `review_engine: claude|codex` (default `claude`) + un `prompt_codex:` per la review olistica single-pass. L'adapter `cron` risolve il companion Codex e lo invoca (`--wait`), con **guard `CODEX_NOT_FOUND` → fallback a `claude --agent`**; il check `ANTHROPIC_API_KEY` è ammorbidito a warning sul ramo Codex (Codex usa `~/.codex`). I backend `github-actions` e `claude-code-cloud` **non** supportano Codex (no plugin/auth in CI, schema RemoteTrigger ignoto) → emettono l'artefatto Claude + un warning esplicito. Codex non spawna specialist → la variante Codex perde la profondità security-reviewer/api-perf (documentato; usa `claude` per quella). File: `framework/routines/code-review.routine.yml`, `src/utils/routine-adapters/{cron,github-actions,claude-code-cloud}.js`, README.

## [4.17.2] - 2026-06-08

**`new2` hardening: 7 lacune latenti chiuse da una revisione adversariale dell'intero workflow.** Dopo i fix di v4.17.1 (nati da run reali), una revisione olistica di `new2` (orchestratore + resolve + final-review + skill) ha isolato 7 lacune non ancora emerse nei run ma reali alla lettura del codice — validate di persona scartando i falsi positivi (l'integrity gate di merge è corretto; lo skill "prose-only" è by-design). **PATCH** (hardening dei workflow `new2`/`new2-resolve` + skill/doc; nessuna nuova capability/layout, **nessuna chiave `baldart.config.yml`** → schema-propagation N/A).

### Fixed

- **G1 — `new2-resolve.js`: rimossi gli handler self-healing orfani (`agent-crash`, `baseline-fail`).** `new2.js` non vi instradava MAI (un crash → `crashResult` marca `failed` + residual; un baseline fail → `batchFatal`). Erano codice morto irraggiungibile: `runCard` va in crash PRIMA della fase commit, quindi un eventuale `resolved` non potrebbe comunque committare/mergiare. Il path residual (lo skill materializza la follow-up) è la SSOT; commento esplicativo in `crashResult`.
- **G2 — `new2.js`: detection Codex G3 cross-card deterministica + skip mono-card.** Il check Codex del pre-flight usava ancora il **soft self-report** che v4.17.1 aveva corretto solo nella final review (stesso falso negativo, per batch multi-card). Ora il bullet G3 risolve PRIMA il glob del companion (esistenza decisa dal glob, non dal giudizio dell'agent), gira Codex in **background con poll**, e vieta un `CODEX_NOT_FOUND` prematuro; su batch **mono-card** il check è saltato del tutto (niente cross-card). Nuovo `codexResolved` nello schema pre-flight + ledger `G3-codex` + campo telemetria `codex_resolved` → un falso negativo è ora visibile.
- **G3 — `new2.js`: niente silent drop dei finding merge-artifact.** Il filtro F-035 scartava i finding "esiste già nel worktree" con solo un ledger SKIP; un regex lasco poteva far sparire un BLOCKER reale. Ora ogni finding scartato diventa anche un `residuals` (`merge-artifact-skipped`, `materialized:false`) → tracciato, nulla perso.
- **G4 — `new2/SKILL.md`: materializzazione follow-up offline-safe via `prd-card-writer`.** Il fallback dello skill scriveva uno stub minimale, incoerente col fix #2 (F-039, workflow → prd-card-writer). Ora lo Step 5.1 delega a `prd-card-writer` (card-template, Rule C, owner_agent, traceability), con stub minimale solo come ultima risorsa in outage totale.
- **G5 — `new2.js`: routing `security-reviewer` non solo da `scopeFiles`.** Una card di sicurezza con file non-matchanti saltava la security review quando `high_risk_modules` non è configurato. Ora il trigger considera anche l'`owner_agent` della card e il suo brief (titolo/requirements), oltre allo scope.
- **G6 — classificatore di dominio doc allineato.** `isDocDomain` (new2.js) e la branch `doc` di `normDomain` (new2-resolve.js) ora portano un commento di sincronizzazione reciproca (stessi token `doc|wiki|ssot|readme`) per prevenire drift.
- **G7 — sync documentazione.** `WORKFLOWS.md` (righe `new-final-review`/`new2`/`new2-resolve`) e `new2/SKILL.md` allineati al comportamento v4.17.1/v4.17.2 (doc routing, prd-card-writer followup, Codex deterministico, final review snella mono-card, rimozione handler orfani).

## [4.17.1] - 2026-06-08

**`new2` post-refactor: tre falle di routing/detection emerse al secondo run reale (card FEAT-0019-followup).** Dopo l'hardening v4.17.0, gli agent principali sono correttamente specializzati, ma il *resolve* e il *final review* mostravano tre errori di instradamento. **PATCH** (bugfix di orchestrazione nei workflow `new2-resolve`/`new-final-review`; nessuna nuova capability, nessun cambio layout, **nessuna chiave `baldart.config.yml`** → schema-propagation N/A).

> **Why.** Tutti e tre i bug condividono la stessa radice: una decisione che dovrebbe essere **deterministica** (quale agent per quale dominio / esiste Codex) era affidata o a una mappa incompleta o al self-report soft di un subagent. Il dominio freeform `documentation` non matchava la chiave `doc`, il companion Codex (installato e funzionante) veniva dichiarato assente per un timeout di una Bash-call sincrona, e le follow-up card nascevano da un Haiku general-purpose anziché dal loro owner.

### Fixed

- **`framework/.claude/workflows/new2-resolve.js` — routing per dominio robusto (F-038/F-024).** Le review agent emettono `domain` come **stringa libera** (`documentation`, `frontend`, …); la mappa fixer keyava solo su `doc`/`ui`, così `documentation` cadeva sul costoso `coder` (Opus) per un finding di pura documentazione. Aggiunto `normDomain()` che normalizza ai bucket di routing (`doc|ui|security|migration|perf|test|code`) **prima** di scegliere fixer e judge, e mappa fixer estesa (`doc→doc-reviewer`, `ui→ui-expert`, `security→security-reviewer`, resto→`coder`) — reviewer-owns-its-domain anche in fase di *apply*, non solo di *judge*.
- **`framework/.claude/workflows/new2-resolve.js` — follow-up card via `prd-card-writer` (F-039).** La materializzazione della follow-up usava `general-purpose` + Haiku scrivendo uno stub YAML minimale; ora delega all'agent **`prd-card-writer`** (owner del `card-template`, Rule C per `review_profile`, `owner_agent`, traceability) — le card di follow-up rispettano le stesse regole SSOT di quelle native.
- **`framework/.claude/workflows/new-final-review.js` — detection Codex deterministica + background (F-040).** Codex risultava "non disponibile" pur essendo installato e funzionante: il workflow delegava l'intera invocazione a un subagent generico che la eseguiva **in modo sincrono** dentro una Bash-call e auto-dichiarava `codexAvailable` — un run reale di Codex (minuti) superava il timeout della call → falso negativo → fallback a `code-reviewer`. Ora un **pre-flight deterministico** (un Haiku risolve SOLO il glob del companion e ritorna il path: la decisione di esistenza è tolta al review agent), poi Codex gira in **background con poll su file** e finestra 10 min (allineato a `references/final-review.md` F.3/F.4); `codexAvailable:false` è ammesso solo per `CODEX_NOT_FOUND` reale o file vuoto a fine timeout. Un **unico** fallback deterministico a `code-reviewer` copre sia "companion assente" sia "run non completato".
- **`new2.js` + `new-final-review.js` — final review snella per batch mono-card (F-041).** Un batch da 1 sola card eseguiva la batch final review **per intero** (`doc-reviewer` + `api-perf-cost-auditor` + `qa-sentinel`), **duplicando** la review per-card (Phase 3) che aveva già girato gli stessi agent sugli stessi file — e senza alcun conflitto cross-card da trovare (~300k token ripetuti su un run reale da 1,28M). Ora `new2.js` passa `singleCard: committed.length === 1`; con quel flag la final review tiene **solo** il passaggio cross-model Codex (valore unico: modello diverso → bug diversi) + i gate `qa-sentinel` (letti dall'integrity gate di merge), e salta i duplicati Claude. Batch multi-card invariati.
- **`new2.js` + `new2-resolve.js` — fan-out Tier-2 inutile su finding doc (F-038/F-033/F-036).** Un singolo merge-blocker doc-domain già **risolto al Tier-1 e confermato dal judge** (`best:1`) faceva comunque partire **3 `doc-reviewer` paralleli (~150k token)** — con rischio di follow-up spuria per un problema già chiuso. Tre cause composte, tutte corrette: **(A)** la resolve riceveva come MAY-EDIT i file di **codice** della card (`reviewScopeFiles`), così il fixer doc editava `docs/` "fuori ownership" — ora `domainMayEdit()` dà ai finding doc la loro territory reale (`docs_dir`/`references_dir`/`wiki_dir`/`prd_dir`); **(B)** la guardia anti-fabbricazione del judge usava `.every()` (rigetto se *anche un solo* file confermato cade fuori MAY-EDIT) — ma il judge lista naturalmente i file adiacenti (il codice che una doc descrive); ora `someInScope()` (`.some()`) rigetta solo un verdetto che non conferma **nessun** file di proprietà (phantom puro); **(C)** il fan-out a 3 angoli (`root-cause`/`call-site`/`reuse-utility`) è code-bug-shaped → per domini deterministici non-code (doc/test) ora un **singolo** retry mirato invece di 3.

## [4.17.0] - 2026-06-07

**`new2` hardening single-wave: 36 finding dal primo run reale risolti in un colpo.** Il primo run reale di `new2` (batch FEAT-0021, 9 card) sotto un outage 529 prolungato è costato **7.4M token / 100 agent / 5h08m producendo solo 6/9 card reali**, con 2 stati `DONE` falsi nel backlog, codice security RLS-bypass non revisionato finito su `develop` via un "safety commit", e 1 follow-up persa. Una raccolta diagnostica end-to-end (36 finding verificati su codice + telemetria reale) ha isolato cause **strutturali**; questo rilascio le risolve tutte in un'unica wave, riportando il costo atteso a ~1.5-2.5M e — prioritario — azzerando le violazioni di integrità/sicurezza sotto degradazione. **MINOR** (cambio sostanziale di capacità del workflow esperimentale `new2`; nessuna rimozione/cambio layout d'install; **nessuna nuova chiave `baldart.config.yml`** → schema-propagation N/A).

> **Why.** `new2` è un A/B di `/new` per misurare l'economia di contesto del workflow-hosting. Il run reale ha mostrato che, oltre al costo, l'autonomia zero-ask degradava in modo **non sicuro** sotto outage (DONE falsi, codice non revisionato su trunk, follow-up persa) — esattamente il recovery che `/new` (gated, main-loop) avrebbe fermato. Decisioni utente vincolanti: *Decision-auto, infra-deferred* (niente `BALDART_AUTONOMOUS=1`; azioni infra owner-gated sempre policy-deferred) e *agenti specializzati come `/new`* (no general-purpose dove conta la qualità).

### Changed
- **`framework/.claude/workflows/new2.js` — scheduler a DAG + resilienza + integrità.** Il for-loop sequenziale e il ramo team-mode sono sostituiti da uno **scheduler dependency-gated** (semantica single-worktree): una card parte solo quando TUTTE le dipendenze in-batch sono `committed`; i dipendenti di una dep fallita vanno `blocked` (niente resolve doomed, F-017/F-021); fallimento transitorio → `pending` + re-invocazione (cap); outage sostenuto → **degrada pulito** (`degraded:true` + worktree clean + return-as-ledger, F-012/F-019/F-020). Implement usa l'**owner_agent** della card; review è un **fan-out specializzato** sfrondato dal `review_profile` (F-024/F-025); commit è **Haiku** con `git status` reconcile, mai `git add -A` (F-023). Skip-completed idempotente su commit-sha+receipt+no-open-followup, NON sul flag `DONE` (F-026). Merge **integrity-gated**: mai force-DONE (F-029), mai commit di codice non revisionato (F-030), mai merge di batch incompleto/degradato (F-031). Telemetria veritiera: `total_tokens` via `budget.spent()`, `agent_count`, `degraded`, `cards_real_done` vs `cards_force_done`=0 (F-032). Parse-guard `args` (F-001).
- **`framework/.claude/workflows/new2-resolve.js` — short-circuit + anti-fabbricazione + batching.** Verdetto **terminale** che salta il multi-attempt quando il problema è impossibile-by-definition (`out-of-ownership` verificato in JS contro MAY-EDIT; altri reason ratificati dal judge → niente escape-hatch, F-008). **Judge avversariale OBBLIGATORIO** su ogni claim `verified`, con **cross-check JS** dei file confermati indipendentemente dal judge (defeats fabricated success, beccata 2× nel run reale — F-015/F-033). Lista **`findings` batchata** (1 resolve per fix-area, F-007/F-035/F-037). Fixer/judge **per dominio** (doc→doc-reviewer, ui→ui-expert, security→security-reviewer judge, perf→api-perf-cost-auditor judge — F-024). `outOfScopeFindings` non più persi (F-022). Follow-up su Haiku, **offline-safe** (deferita al skill se nessun agente può scrivere, F-020). Parse-guard `args` (F-004).
- **`framework/.claude/skills/new2/SKILL.md` — reconcile + resume + telemetria reale.** Step 4 passa `ts` (il workflow non ha clock). Step 5: materializza le follow-up dei `residuals` non ancora su disco (offline-safe — il skill ha il filesystem → chiude "nothing dropped" sotto outage, F-020), **rilancia** con `resumeFromRunId` se `degraded` (idempotente via skip-completed), registra telemetria veritiera (`wall_clock_s`, `followups_on_disk` contando i file — F-032).
- **`framework/.claude/agents/prd-card-writer.md` — Rule A.1 (AC↔ownership consistency).** Fix a monte del difetto ricorrente "AC fuori dal MAY-EDIT" (F-016): ogni AC dev'essere soddisfabile editando i file della propria card.
- **`framework/docs/WORKFLOWS.md`** — righe `new2`/`new2-resolve` aggiornate al comportamento v4.17.0.

## [4.16.2] - 2026-06-06

**`baldart update` non si blocca più all'infinito su un edit `.framework/` ormai assorbito a monte.** Un edit locale a un file framework (es. `agents/ui-expert.md`) committato con un subject che *non* contiene la parola "baldart" sfuggiva alla classificazione `chore-wrapper` (rumore) e veniva marcato `custom-overlay-able`. Quel commit resta per sempre ancestor di `HEAD` ma mai di `FETCH_HEAD`, quindi `git log FETCH_HEAD..HEAD -- .framework` lo ripesca a **ogni rilascio successivo** — e il flusso seamless si fermava sul gate `divergence-capture-incomplete` perché l'overlay (additivo) non riproduceva byte-a-byte il file `.framework`, anche quando il contenuto era già identico all'upstream e la personalizzazione viveva correttamente nell'overlay. Risultato: lo stesso commit "fantasma" rifaceva muro a ogni `update`, senza via d'uscita via overlay (riconciliare un overlay additivo non può mai far passare il round-trip stretto). **PATCH** (bugfix del detector di divergenza + reset-safety degli overlay additivi; nessun cambio di capability, nessuna chiave `baldart.config.yml`).

> **Why.** Il check di fedeltà capture+verify pretendeva `merge(base_upstream, overlay) == file_.framework`. Per un overlay `extend` (additivo) questo è **strutturalmente falso** quando `.framework` è pristine-upstream: il merge aggiunge sezioni, il file no → "stale" eterno. E il classifier non distingueva un edit *vivo* (da preservare) da uno *assorbito* (contenuto già a monte → un re-sync non perde nulla). Due correzioni complementari chiudono la classe di falso positivo per tutti i consumer.

### Fixed

- **`src/utils/git.js` — categoria `absorbed` nel classifier di divergenza.** `classifyDivergence` ora demota a rumore (`absorbed`) ogni commit `custom-*` i cui file `.framework/` toccati sono **byte-identici all'upstream (`FETCH_HEAD`)** — confronto di blob-SHA, prefisso `.framework/` strippato per mappare il path upstream; conservativo (file fuori da `.framework/`, mancante a monte, o SHA diverso → resta drift reale). `aggregateDivergenceClass` include `absorbed` tra le `noiseCategories`, così un edit ormai assorbito non spinge più la classe verso `overlay-able`/`mixed`: la batch torna `all-noise` → auto-resolve pulito.
- **`src/utils/overlay-capture.js` — reset-safety per overlay additivi (`captureAndVerify`).** Quando esiste già un overlay agent/command, il file `.framework` viene riconosciuto come **reset-safe** (nessun edit inline da perdere) se è pristine: (a) byte-identico alla base upstream verso cui si farebbe reset, **oppure** (b) corrisponde alla base con cui l'overlay è stato autorato (`base_file_sha` in frontmatter) — pristine anche dopo che l'upstream è andato avanti. In quel caso è `captured` (non più `stale`), invece di pretendere il round-trip stretto che un overlay additivo non può mai soddisfare. Il caso *genuinamente* stale (edit inline reale che l'overlay non riproduce) resta correttamente un blocker.
- **Test.** `classify-divergence.test.js`: 3 fixture per `absorbed` (da solo / con merge plumbing → `all-noise`; con overlay-able genuino → `overlay-able`). `overlay-capture.test.js`: 2 fixture per la reset-safety (overlay additivo + `.framework` == base → `captured`; upstream spostato ma `.framework` pristine vs `base_file_sha` → `captured`).

## [4.16.1] - 2026-06-05

**Review di allineamento `new` ↔ `new2`: 9 fix di fedeltà sui workflow `new2`/`new2-resolve` emersi da una review adversariale a 3 reviewer paralleli.** La v4.16.0 ha introdotto `new2` come variante workflow-hosted di `/new`; una review di allineamento (gate-coverage · child-workflow wiring · control-flow) ha refutato l'implementazione su difetti reali — di semantica/allineamento, non di esecuzione (il wiring tra i 3 workflow era contract-fedele). **PATCH** (bugfix di allineamento del nuovo `new2`, nessun cambio di capability, nessuna chiave `baldart.config.yml`).

> **Why.** `new2` deve essere un A/B equo di `/new`: ogni gate va mappato con policy *fedele* alla semantica di `/new` (meno l'AskUserQuestion), e tutti i path/child-workflow vanno cablati. La review ha trovato punti dove la policy deterministica divergeva silenziosamente dalla SSOT o dove un segnale veniva letto ma non agito.

### Fixed

- **`framework/.claude/workflows/new2.js`**:
  - **Final review `NEEDS_MANUAL_CONFIRMATION` non più ignorato** — il filtro blocker includeva solo `VERIFIED` mentre il commento dichiarava "treat as VERIFIED": ora include anche `NEEDS_MANUAL_CONFIRMATION` (tentato il fix, mai scartato in silenzio → ripristina l'invariante zero-drop di `final-review.md` F.4).
  - **`status:'fatal'` di `new2-resolve` ora onorato** — `resolve()` setta `batchFatal` e sospende final review + merge, invece di degradarlo a `followup`.
  - **Phase 7 Production Readiness aggiunta** (post-merge, non-bloccante) — ripristina la full-parity dichiarata (auto-esegue i deploy stack-matched, riporta gli item manuali).
  - **Card crashata non più persa** — il catch del driver passa da `resolve('agent-crash')` (retry specialista, mai bypass su security/migration) e garantisce un follow-up tracciato (path `agent-crash` prima irraggiungibile).
  - **G3 cross-card / G5 depends-on rafforzati** — il prompt pre-flight ora *applica* le azioni cross-card (force-sequential/warning in executionMode/groups), non le logga soltanto; G5 garantisce l'esclusione **transitiva** dei dipendenti.
  - **Minori**: `fileDiffViolation` (E4) ora loggato nel gate ledger; `hasApiDataFiles` non più sempre-true (rimosso il disgiunto che rendeva irraggiungibile lo skip dell'api-perf-auditor); i resolve di `merge-blocker`/`qa-fail` passano `mayEditPaths` (confine ownership sui fix batch-wide).
- **`framework/.claude/workflows/new2-resolve.js`**: obbligo esplicito di **re-run E2E (Phase 2.6)** quando un fix tocca file UI (app/components/`*.css`/`*.scss`), allineato a `codex-gate.md` step 6.

## [4.16.0] - 2026-06-05

**Nuova skill sperimentale `new2` + i workflow `new2`/`new2-resolve`: l'INTERA batch di `/new` può girare come un singolo dynamic workflow autonomo in background, per fare A/B testing sul context economy.** `/new` è una skill che gira nel main loop, quindi l'output di ogni subagent (architect, coder, reviewer, Codex, QA, E2E) gonfia il contesto dell'orchestrator — il driver di costo che si sposta d'asse a ogni fix (v4.12→v4.15). I dynamic workflow spostano l'orchestrazione in un runtime **isolato**: i risultati intermedi vivono in variabili di script e nel main loop torna **solo il report finale**. `new2` è la versione workflow-hosted di `/new`, **parallela e additiva** (NON sostituisce `/new`), pensata per misurare il delta di `usage` reale a parità di semantica. **MINOR** (nuova skill + 2 workflow, opt-in, Claude-only; nessun breaking change, nessuna chiave `baldart.config.yml` — il gating è runtime via presenza del tool `Workflow`, come v4.14.0).

> **Il nodo: i workflow non possono chiedere input mid-run** ("For sign-off between stages, run each stage as its own workflow"). `/new` ha ~25 gate `AskUserQuestion` bloccanti. Invece di spezzare in stage interattivi, `new2` **rende deterministico ogni gate**: mappati tutti → zero `AskUserQuestion` → la batch intera è un solo workflow autonomo. Ogni gate collassa in due archetipi — **(A) auto-risolvi col default seamless** (stash/ff-pull/retry/revert) o **(B) fail → self-healing `new2-resolve`**. Le operazioni distruttive/outward (`reset --hard`, force-push, drop stash) non si auto-eseguono MAI → degradano a "lascia intatto + segnala". Decisioni utente recepite in design: **auto-merge completo** (G24, l'unica azione outward autonoma), **card in fail → si auto-risolve senza lasciare nulla indietro** (non skip, non halt), **E2E `error` di tooling → procedi con flag**, **full parity** (sequential + team-mode coder paralleli via `parallel()`).

> **`new2-resolve` — "nulla lasciato indietro".** Su decisione esplicita dell'utente, un gate bloccante non salta né ferma la batch: innesca un workflow di risoluzione self-healing con 8 `kind` su 3 assi — **fail/blocco** (`ac-unmet`/`blocker`/`qa-fail`/`e2e-blocked`/`merge-blocker`), **scope-expansion** (un finding legittimo che allarga lo scope oltre la AC: confine deterministico ancorato a file-ownership map + Domain-Override, niente soglie arbitrarie → integra-entro-confine-sicuro **o** follow-up), e **edge trasversali** (`agent-crash` re-instradato al giusto specialista mai bypassando security/migration; `baseline-fail` = unico terminale batch-fatale). Strategia a tier: fix mirato → multi-attempt giudicato (`parallel()` + judge avversariale) → re-verifica → **terminale = follow-up card auto-materializzata** (l'opzione sancita dalla Scope Closure Discipline, NON un deferral silenzioso). La decisione umana si sposta da ~25 interruzioni mid-run a una sola review post-run della coda follow-up.

> **A/B validity.** Per attribuire il delta SOLO al cambio di host (skill main-loop → workflow background) e non a drift semantico, `new2` riusa gli **stessi agenti** con gli **stessi briefing**: i prompt-agente del workflow **citano i moduli reference di `/new`** (`args.refModulesBase`) come SSOT semantica, e `new2.js` codifica solo la *shape di orchestrazione* + le policy di gate. La telemetria Phase-8 esce con discriminatore `variant: "new2"` su `skill-runs.jsonl`, confrontabile con i run `/new`. Limiti documentati (è un esperimento): Claude-only, recovery solo intra-sessione, ogni op git/bash diventa azione di un agent (lo script non ha fs/shell). Distribuzione: i `.js` sono raccolti automaticamente dal per-item symlink esistente (`MERGE_KINDS.workflow`, overlay:false, Codex `workflowsDir()=null`) — nessuna modifica all'installer; lo `framework-edit-gate` li scansiona by-path → tutti i fact via `args`.

### Added

- **`framework/.claude/skills/new2/SKILL.md`** — skill sottile di ingresso (`effort: high`): degradation gate (`Workflow` tool assente / `.claude/workflows/new2.js` non linkato → "usa /new" + HALT), parse args (stessa grammatica di `/new`: range, `-full`, `-stats`, `effort=`), feature gate (`has_backlog`), raccolta fact da `baldart.config.yml`, `Workflow({ name:'new2', args })`, e presentazione del report + telemetria `variant=new2`. Resta ~1 schermata nel main loop → costo context trascurabile.
- **`framework/.claude/workflows/new2.js`** — orchestratore della batch: Pre-flight (un ops-agent esegue Phase 0 + worktree + cross-card con le policy deterministiche G1–G5/E2), pipeline per-card sequential **o** team-mode (`parallel()` per wave), Final review (delega a `new-final-review`), auto-merge (G24 + riconciliazione 6b/6c conservativa G19–G23). Ogni gate bloccante / finding di scope → `workflow('new2-resolve', …)`. Gate ledger + report + telemetria nel return.
- **`framework/.claude/workflows/new2-resolve.js`** — workflow di risoluzione self-healing (8 `kind`, 3 assi; tier fix→judge→verify→follow-up). `agent()`/`parallel()` only (figlio, nessun nesting).

### Changed

- **`framework/docs/WORKFLOWS.md`** — tabella "What ships today": aggiunte le righe `new2` e `new2-resolve` + nota "esperimento, non rimpiazzo".
- **`README.md`** — conteggio skill 29 → 30; `new2` aggiunto alla categoria Workflow con nota EXPERIMENTAL/Claude-only.

## [4.15.0] - 2026-06-04

**Il pre-flight di `/new` smette di gonfiare il contesto dell'orchestrator: il setup worktree + baseline build e il check Codex cross-card girano in background fuori dal prefisso cached, e le scritture del tracker di pre-flight si fondono in un singolo flush.** Forensics su una run reale (`/new FEAT-0020 -full`, 8 card, team mode) — estrazione del campo `usage` per turno dal transcript — ha mostrato **~150k token di occupazione al solo pre-flight, prima di toccare una card**, con un `cache_read` cumulativo di ~10M token (il prefisso ri-letto a ogni turno × ~70 turni). La diagnosi ovvia («è il bloat della prosa della skill») è stata **refutata dai numeri**: dopo lo split di v4.14.0, la prosa skill-controllata in quei 150k è solo ~18%; il floor statico ambiente (schemi tool MCP + CLAUDE.md + memoria) è ~42% e non è toccabile da `/new`; il vero driver residuo è il **numero di turni dell'orchestrator nel pre-flight** (~70) — ognuno ri-bolla il prefisso — alimentato da tre sprechi concreti. **MINOR** (cambio di orchestrazione capability-preserving di `/new`: stessi gate, stessi output, stesso install-contract; nessuna chiave `baldart.config.yml`, nessuna modifica CLI).

> **Why.** L'orchestrator di `/new` è long-lived: tutto ciò che entra nel suo contesto ci resta per l'intera run e viene ri-letto (a tariffa cache-read) a ogni turno. Tre cose lo gonfiavano al pre-flight senza alcun bisogno di restare nell'orchestrator: (1) l'invocazione inline di `/nw` caricava il corpo da **~1200 righe** di `worktree-manager` nel prefisso cached + camminava `npm install`/`tsc`/`lint`/`build` per ~15-20 turni foreground; (2) il trace `[codex]` del check cross-card rientrava nell'orchestrator perché il result-handling faceva `tail`/`cat` del file grezzo (pieno di righe di comando-trace) invece del solo verdict; (3) il tracker veniva creato e poi editato ~5 volte in modo incrementale durante un pre-flight che è idempotente e non ha bisogno di persistenza mid-flight. La cura sposta (1) e (2) in **contesti usa-e-getta** (subagente background + Bash background) di cui l'orchestrator trattiene solo il verdict strutturato, e collassa (3) in **un solo flush**. Il pattern è additivo agli invarianti già presenti (§ "Context economy" del core, regola sul canale inline) e non tocca nessun gate BLOCKING né il contratto di recovery per-fase dell'esecuzione card. Lezione metodologica confermata: dopo ogni fix il driver dominante può spostarsi d'asse — qui da "prosa nel prefisso" (v4.13/4.14) a "turni di pre-flight × prefisso crescente".

> **Onestà sulla magnitudine (post code-review + DUE giri di adversarial review).** Il risparmio vero è il **sollievo del prefisso dell'orchestrator** (corpo di worktree-manager fuori dal prefisso cached + ~15-30 turni foreground collassati in background ops): i token del build NON spariscono — vengono spesi nel subagente, sullo stesso pool di sessione — ma smettono di moltiplicarsi contro il prefisso crescente dell'orchestrator a ogni turno. La review avversariale ha **refutato** quattro punti rivelatisi difetti reali (poi fixati): (1) l'assunzione "un subagente può invocare una Skill" era **non provata e senza fallback** → **fallback inline a `/nw`** se il subagente non ritorna il blocco; (2) la barriera a due op poteva **risvegliarsi sul primo completamento** e leggere un `$AUDIT_FILE` a metà → **wait-for-ALL esplicito**; (3) la delega worktree apriva un **recovery-gap** (un worktree di codice NON è ri-creabile — `/nw` fa fail-loud su collisione). Il primo rimedio (pre-check su `registry.json`) è stato **a sua volta refutato dal secondo giro**: worktree-manager scrive l'entry registry SOLO a fine `/nw`, *dopo* il build, quindi per tutta la finestra di barriera il worktree esiste su disco ma NON è in registry → il check era cieco proprio nello scenario da coprire. Fix corretto: **rilevamento git-autoritativo** (`git worktree list` sul branch deterministico) con resume-se-completo / reset-e-ricrea-se-orfano (a pre-flight non c'è lavoro card da perdere); (4) il flush proponeva un `Write`-da-memoria che dopo una compaction avrebbe **droppato i campi Phase-0** (`$MAIN`/`$TRUNK`) → ora solo `Edit` chirurgici, mai overwrite; più una clausola nel **Context recovery protocol** del core che codifica il rientro in Pre-flight step 4. Per contro l'adversarial è stata essa stessa **refutata dai dati** sul filtro `[codex]`: lo static-grep del `.mjs` diceva "prefisso mai emesso", ma il file audit reale ha **63 righe `[codex]` su 73** (il wrapper Codex di Claude Code le emette nel redirect) → il filtro è corretto e tenuto.

### Changed

- **`framework/.claude/skills/new/references/setup.md`** — Pre-flight (once) step 4-6 riscritti:
  - **Step 4 (Lever #1)**: il setup worktree non è più invocato inline; l'orchestrator spawna **un subagente background** (`general-purpose`, **`mode: bypassPermissions`**, `run_in_background`, `name: worktree-setup-<FIRST-CARD-ID>`) che invoca `/nw` programmatic e ritorna SOLO un blocco strutturato (`worktree_path`/`branch`/`port`/`created_at`/`baseline`/`baseline_log`). Il corpo da ~1200 righe di `worktree-manager` + i log install/build vivono e muoiono nel contesto del subagente. **Guardie (post-review, 2 giri)**: step **4a2** pre-check **git-autoritativo** (`git worktree list` sul branch deterministico — NON il registry, che è scritto solo a fine `/nw` ed è cieco durante il build) → worktree completo: resume; orfano mid-build: reset (`worktree remove --force` + `branch -D`) e ricrea; step **4d** fallback inline a `/nw` se il subagente non ritorna il blocco (capability Skill-da-subagente non assunta, mai strandare la barriera); campo `created_at` nel blocco (e letto dal registry sul path di resume) per non rompere `cycle_time_mins`; flush con soli `Edit` chirurgici (mai `Write`-da-memoria, che dopo compaction droppa i campi Phase-0).
  - **Step 5 (barrier)**: l'orchestrator lancia 3d (Codex) + 4 (worktree subagent) insieme e **chiude il turno** su una barriera **wait-for-ALL** (attende OGNI op lanciata, non la prima — altrimenti leggerebbe un `$AUDIT_FILE` a metà; compaction mid-barrier rientra in pre-flight, resa sicura dal 4a2) — niente più ~30 turni foreground di walk-through (no `sleep`/`echo`, ri-invocazione automatica a completamento).
  - **Step 6 (Lever #3)**: al resume, gate sul `baseline: fail` (STOP), verdict Codex distillato, e **un singolo Edit** che scrive l'intero blocco di pre-flight (`## File Ownership Map` + `## Execution Mode` + `## Worktree` + `## Cross-Card Conflicts (Codex)`) — niente Edit incrementali per sub-step (pre-flight idempotente; la persistenza incrementale resta per l'esecuzione card).
  - **Step 3d "Result handling" (Lever #2)**: aggiunta la **verdict-extraction discipline** — il file `$AUDIT_FILE` contiene il trace `[codex]` davanti al verdict; vietato `cat`/`tail`/`head` grezzo, si legge solo via filtro `grep -vE '^\[codex\]' "$AUDIT_FILE" | tail -n 60` e si logga il finding distillato, mai il trace.
- **`framework/.claude/skills/new/references/team-mode.md`** — Team Mode Pre-flight: le sezioni `## Team Mode` + `## Parallel Groups` confluiscono nel **flush unico** di setup.md step 6c (niente Edit separato); il wave layout è risolto prima del lancio dei background ops, così è in-context per il flush.
- **`framework/.claude/skills/new/SKILL.md`** — § "Context economy": nuovo **punto 6** (delega il setup body-heavy a un contesto usa-e-getta — background subagent/Bash, l'orchestrator tiene solo il verdict; batch delle scritture tracker di pre-flight) + chiusura della regola estesa a «what the orchestrator chooses to retain».

## [4.14.1] - 2026-06-04

**Il gate Simplify del Team Mode di `/new` (D.3b) ora skippa solo le card davvero trivial (`TRIVIAL_CARDS`), non tutte quelle con `review_profile == skip` — chiude una divergenza col path sequential (Phase 2.55 `IS_TRIVIAL`) e con la stessa D.1.5 di team-mode.** La pipeline per-card di `/new` vive in due modalità (sequential e team) che ri-enunciano alcuni gate; due predicati gemelli erano davvero divergenti. **Drift 1 (bug, fixato)**: il sequential Phase 2.55 ri-conferma `IS_TRIVIAL` sul diff ACTUAL committato (tutte e 3 le condizioni, incl. il check non-source) → una card `review_profile=skip` con un file SORGENTE nel diff reale NON è trivial → ESEGUE Simplify; il team D.3b invece skippava sul solo `review_profile == skip`, ignorando il diff reale → la saltava. Aggravante: team-mode era incoerente con sé stesso — a D.1.5 calcola GIÀ `TRIVIAL_CARDS` col check non-source completo (e la sua nota SSOT dichiara «D.3b already skipped for trivial»), ma il gate D.3b gateava sul set più lasco. Blast radius: solo qualità (la card prendeva comunque D.2 code-review + il Final FULL gate), ma in team mode si perdeva la difesa contro la deviazione del coder che il sequential ha. **Drift 2 (intenzionale, documentato)**: lo scope per-GRUPPO della QA gate (D.4) vs per-CARD del sequential (21b) — stesso predicato (`deep`/risk → FULL, altrimenti defer al Final), granularità per necessità del modello a wave (qa-sentinel gira una volta sul diff combinato del gruppo); è il lato deliberatamente più conservativo (una card rischiosa → tutto il gruppo FULL), nessun gap di correttezza → comportamento invariato, aggiunta solo una frase di chiarimento. **PATCH** (bugfix di un falso-skip in team mode; nessuna capability, nessuna chiave `baldart.config.yml`).

> **Why.** I gate ri-enunciati nei due path di `/new` devono restare predicato-identici, o le due modalità divergono silenziosamente. Qui il fix è un puro allineamento: D.3b passa dal set lasco (`review_profile == skip`) al set già calcolato a monte a D.1.5 (`TRIVIAL_CARDS` = `review_profile==skip` AND 0 Step-A triggers AND non-source diff), che è esattamente l'`IS_TRIVIAL` del sequential 2.55. Nessuna nuova logica, nessun ricalcolo: si consuma il set che D.1.5 dichiarava già di pilotare. Il Drift 2 si lascia perché la granularità per-gruppo è una necessità del modello a wave (un solo qa-sentinel sul diff combinato), non una svista, ed è il lato più sicuro — ma ora è documentato come intenzionale per non farlo rifixare in futuro.

### Changed

- **`framework/.claude/skills/new/references/team-mode.md`** — gate D.3b (Simplify) riscritto da «SKIP per `review_profile == skip`» a «SKIP per card in `TRIVIAL_CARDS`» (il set già computato a D.1.5), con log-line allineata al sequential (`simplify: SKIPPED (trivial — non-source diff)`); una card `skip` con file sorgente nel diff ora esegue Simplify come nel sequential 2.55. D.4 (QA gate) — aggiunta una frase che documenta lo scope per-gruppo come INTENZIONALE (stesso predicato del sequential 21b, valutato a livello di gruppo perché qa-sentinel gira una volta sul diff combinato; lato deliberatamente più conservativo). Nessun cambio di logica su D.4.

## [4.14.0] - 2026-06-04

**Nuovo tipo di payload `.claude/workflows/` (dynamic workflow di Claude Code) + il primo workflow `new-final-review`: la Final Review F.2–F.4 di `/new` può girare come script di orchestrazione deterministico, opt-in e con fallback inline.** I dynamic workflow (research-preview di Claude Code) spostano l'orchestrazione fan-out fuori dal context-window dell'orchestrator, nel codice. Una review avversariale ha **refutato** l'idea di farne il motore *per-card* di `/new` (rompe la portabilità Codex — sono Claude-only; dipende da una preview come breaking change; duplica la logica di gate col team-mode; introduce attrito-permessi e regressione di recovery proprio dove `/new` è più intrecciata con worktree/git/stato). Il bersaglio scelto è invece la parte dove i rischi spariscono e il valore resta: la **Final Review F.1–F.6** è **read-only** (gli agenti leggono solo il diff committato), ha **definizione SSOT unica** condivisa da sequential e team mode (→ nessun twin), e ha l'**adversarial-verify già nativo** (FP-check di Codex in F.3, cross-validation `code-reviewer` a confidence<80 in F.4). **MINOR** (nuova capability additiva: nuovo payload type + un workflow; nessun breaking change, nessuna chiave `baldart.config.yml` — il gating è runtime via presenza del tool `Workflow`; i consumer senza workflow o Codex-only eseguono la Final Review inline come prima).

> **Why.** La Final Review è l'unica fase di `/new` con una definizione singola condivisa da entrambe le modalità e senza ownership di stato git o gate-utente: estrarla in un workflow non duplica nulla e non regredisce nulla. Il confine è netto — il workflow incapsula **F.2 (baseline) + F.3 (fan-out Codex ‖ doc-reviewer ‖ api-perf-cost-auditor ‖ qa-sentinel) + F.4 (consolidamento + verifica)** e ritorna i finding già classificati (`VERIFIED`/`NEEDS_MANUAL_CONFIRMATION`, `FALSE_POSITIVE` scartati) + la gate table; **F.1 (scope), F.5 (apply fix + build + `AskUserQuestion`) e F.6 (wrap-up) restano nella skill**, perché un workflow non può eseguire git né chiedere all'utente. La prosa di `final-review.md` resta SSOT e fallback sempre-disponibile; il workflow ne mirrora solo la *forma di orchestrazione* (i suoi agent-brief citano il modulo per la *semantica* di review). Distribuzione: symlink per-item come skill/agent/command, ma **senza overlay** (l'overlay-merger è per `.md`; un `.js` non ha sezioni markdown) e **solo per Claude** (`codex.js` ritorna `workflowsDir()=null` → mai linkato in un albero Codex).

### Added

- **`framework/.claude/workflows/new-final-review.js`** (nuovo) — workflow `meta.name: 'new-final-review'`, phases `Baseline`/`Review`/`Verify`. Fan-out F.2–F.4 con fallback Codex→`code-reviewer` come branch JS, schema sui finding (`VERIFIED`/`FALSE_POSITIVE`/`NEEDS_MANUAL_CONFIRMATION`), `api-perf-cost-auditor` saltato via branch quando non ci sono file API/data. Tutto da `args` (portabile, anti-contaminazione).
- **Payload type `.claude/workflows/`** — `src/utils/tool-adapters/claude.js` espone `workflowsDir()='.claude/workflows'`; `codex.js` `workflowsDir()=null` (Claude-only). `src/utils/symlinks.js`: `_mergeBulkDir` generalizzato con mappa per-kind (`MERGE_KINDS`: estensione + overlay on/off), nuovo `mergeWorkflows()`, chiamato in `createAllSymlinks()` e nel reconcile di `update.js`; `verifySymlinks()` esteso a `workflows`. `BALDART_MANAGED_PATTERNS` (`update.js`) include `workflows`. `doctor.js`: probe + repair advisory dei workflow non linkati (Claude-only, non bloccante).
- **`framework/docs/WORKFLOWS.md`** (nuovo) — cos'è il payload `.claude/workflows/`, il modello opt-in + degradazione, il perché Claude-only e no-overlay.

### Changed

- **`framework/.claude/skills/new/references/final-review.md`** — nuovo **Step F.1.5 (workflow delegation gate)**: se il tool `Workflow` è disponibile e `new-final-review.js` è linkato → delega F.2–F.4 al workflow e porta i finding/gate-table a F.5; altrimenti inline come prima (prosa = SSOT/fallback). Drift-note per i maintainer. `SKILL.md` § Routing annota la delega.

**Il SKILL.md di `/new` scende da 61k a ~10k token (−82%) — il dettaglio passo-passo di ogni fase è uscito dal corpo skill (prefisso di sistema, ri-letto a ogni turno) in moduli `references/*.md` caricati on-demand.** Diagnosi data-driven su una run reale (`/new` su ~4 card → 480k token di contesto): forensics sul transcript dell'orchestrator → il **floor statico è 152k token PRIMA che parta una card**, e il colpevole #1 è il SKILL.md monolitico da **2766 righe / 61k token**, residente nel prefisso di sistema cached e ri-validato a ogni turno (362 turni nella run osservata). Una prima ipotesi ovvia (comprimere la *prosa* degli agent, stile "caveman") è stata **misurata e scartata**: la prosa è ~8k = 2% del totale. La cura replica 1:1 il pattern già in prod su `/prd` (core snello + reference modules + routing table): il core tiene solo gli invarianti cross-fase (Context Tracking, Progress Visibility, § Context economy, Agent Routing, QA Profile, Trivial fast-lane, Risk-signal detector, Fix Application Log) + una **§ Routing** che mappa fase→modulo→skip; ogni fase carica il suo modulo via Read quando ci entra. Contenuto spostato **verbatim** (zero cambi comportamentali; le stesse fasi/gate, nello stesso ordine). **MINOR** (refactor capability-neutral + nuovo contratto interno di module-loading on-demand; install-contract e invocazione invariati, consumer auto-aggiornati via directory-symlink; nessuna chiave `baldart.config.yml`, nessuna modifica CLI).

> **Why.** Il corpo di una skill vive nel prefisso di sistema: a ogni turno l'harness lo ri-legge dalla cache, quindi un SKILL.md da 61k si paga 362 volte su una run lunga e occupa il tetto-contesto per l'intera durata. Spostare il dettaglio per-fase nella conversazione (via Read on-demand) lo rende **compaction-eligible** (il system prompt non lo è) e fa sì che le card trivial/light non carichino mai i moduli delle fasi che skippano. Caveat onesto: nel caso peggiore (no compaction, ogni fase eseguita una volta) il risparmio è il solo shrink del prefisso (~50k × ogni turno); nel caso tipico è maggiore. La disciplina è preservata da una HARD RULE ("leggi il modulo PRIMA di eseguire la fase") + un campo tracker `phase_module_loaded:` che la § Context recovery ri-legge dopo una compaction. Il Level 2 (card-runner sub-agent per collassare i ~90 turni/card in contesto isolato) resta valutato a parte — i docs ufficiali confermano l'isolamento del contesto subagent, superando la refutazione v4.12.0, ma è fuori da questa release.

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — riscritto come **core ~543 righe / ~10k token** (da 2766 / 61k). Tiene: frontmatter + meta-rules BLOCKING, § Context Tracking (+ nuovo campo `phase_module_loaded:`), § Progress Visibility, § Context economy, **nuova § Routing** (mappa fase→modulo→skip + HARD RULE "Read il modulo prima della fase"), § Parallelism rules, § Context recovery protocol (+ step di ri-Read del modulo post-compaction), § Agent Routing, § QA Profile Selector, § Trivial-card fast-lane, **nuova § Risk-signal detector** (il vecchio Phase 3.7 Step A, rilocato nel core perché usato sia dal Trivial-classifier a Phase 1 sia da Phase 3.7), § Fix Application Log + Domain-Override.

### Added

- **`framework/.claude/skills/new/references/*.md`** (11 moduli on-demand, contenuto verbatim dal SKILL.md originale): `setup.md` (Phase 0 + Pre-flight), `implement.md` (Phase 1-2), `completeness.md` (Phase 2.5/2.5b), `review-cycle.md` (Phase 2.55/2.6/3/3.5), `codex-gate.md` (Phase 3.7, con Step A che ora rimanda al § Risk-signal detector del core), `commit.md` (Phase 4-5), `final-review.md` (Final F.1-F.6), `merge-cleanup.md` (Phase 6/6b/6c), `production-readiness.md` (Phase 7), `team-mode.md` (Team Mode + Step D), `metrics.md` (Phase 8). Arrivano ai consumer (Claude **e** Codex) via il directory-symlink esistente, senza modifiche CLI.

## [4.12.1] - 2026-06-04

**I gate Phase 0 di `/new` (e il finalizer `/mw`) non scattano più sugli artefatti BALDART-managed lasciati dirty da una reinstall/update — `.baldart/generated/`, `.baldart/state.json`, `.baldart/skill-conflicts.json`.** Sintomo riportato dall'utente: *ogni* volta dopo un aggiornamento, `/new` Phase 0 segnalava «stato non pulito» (N file dirty `.baldart/generated` + `state.json`) e «commit ahead di origin», girando all'utente la domanda su come gestire lo stato della main repo. La verifica del sorgente ha confermato la radice: i due gate Phase 0 sapevano auto-ignorare **solo** la telemetria sotto `${paths.metrics}` (dirty-tree gate) e i commit sotto `.framework/` o con subject `baldart update/add` (divergence gate) — ma `baldart update`/`add`/`push` scrivono una **terza** classe di rumore sotto `.baldart/` (i file reali del merge overlay in `generated/`, il ledger `state.json`, il log `skill-conflicts.json`) che nessuno dei due riconosceva, così finiva in `$USER_DIRTY` / `GENUINE_AHEAD` e bloccava. Stessa classe di bug del fix v4.2.2 (telemetria), per gli artefatti `.baldart/` che quel fix non copriva. Cura: **classificazione chirurgica, non rilassamento** — `.baldart/overlays/` resta deliberatamente FUORI dal set ignorato (un overlay editato è lavoro genuino e *deve* gateare). **PATCH** (bugfix di un falso positivo nei gate; nessuna nuova capability, nessuna chiave `baldart.config.yml`).

> **Why.** Un gate di igiene workspace deve distinguere il *lavoro feature in corso* dall'*output del framework stesso*. La v4.2.2 aveva insegnato ai gate a ignorare la telemetria che `/new` scrive di suo; questa fix estende la stessa logica all'output che `baldart update`/`add` lascia dirty per costruzione (rigenera `generated/*` e bumpa `state.json` a ogni sync, committati solo al reconcile successivo). La distinzione critica è `generated|state|skill-conflicts` (framework-managed → ignora) **vs** `overlays/` (user-owned → gatea): un partizionamento di pattern, mai un rilassamento del gate. Verificato con bash reale che le tre forme (porcelain partition, file-list reclassification, `case` glob del finalizer) trattino l'overlay edit come genuino in ogni punto.

### Fixed

- **`framework/.claude/skills/new/SKILL.md`** — Phase 0 step 3 (dirty-tree gate) e Phase 6c step 3 (clean-tree assertion) droppano da `$USER_DIRTY`/`$POST_DIRTY` anche `.baldart/(generated/|state.json|skill-conflicts.json)`; Phase 0 step 4 (divergence reclassification loop) accetta come framework-management i commit che toccano *solo* `.framework/` **e/o** quei `.baldart/` artifacts. Label tracker `framework-telemetry-only` → `framework-managed-only`.
- **`framework/.claude/skills/worktree-manager/SKILL.md`** — il finalizer `/mw` (sync main repo) classifica gli stessi `.baldart/` artifacts come OWNED (auto-reconcile lossless, come il metrics log) invece che FOREIGN, evitando il falso `[SYNC-NEEDS-DECISION]` che `/new` Phase 6c convertirebbe in un gate utente.

## [4.12.0] - 2026-06-04

**`/new` non gonfia più il context dell'orchestrator su long-run — il bulk content (log dei gate, `git diff`, file interi, baseline) non scorre più nel canale inline dell'orchestrator: vive su disco e viaggia per *path*.** Sintomo riportato dall'utente: su batch lunghi il consumo si impennava per il context bloat. Una prima ipotesi (i *finding* delle review che tornano inline + un card-runner sub-agent isolato + un "context purge" da rinforzare) è stata **sottoposta a review avversariale e refutata**: (1) l'orchestrator DEVE leggere il contenuto dei finding ai punti-decisione (domain-override partition, AC-Closure, `NEEDS_MANUAL_CONFIRMATION`, merge/dedup del Final gate) → un contratto "verdict-only" che li nasconde romperebbe la skill e i consumer condivisi (`/prd`, `/codexreview`); (2) un card-runner sub-agent non può ospitare i gate utente né garantire il nested-spawn dei reviewer; (3) un "compaction-lossless checkpoint" non regge (`/tmp` è single-point-of-failure, l'auto-compact non è controllabile da una skill). La verifica sul codice ha trovato che **il vero driver del bloat è il bulk content reso inline nei tool-result dell'orchestrator stesso**, non i finding: output dei gate `lint`/`tsc`/`test`/`build` (resi inline da `| tee`, ~40-50%), `git diff` completo passato inline ai simplify agent (~25-30%), Read di file interi nel completeness check, e baseline concatenati nel Final F.3. **MINOR** (cambia il comportamento di esecuzione di `/new`; nessuna nuova capability, nessun nuovo agente, nessuna chiave `baldart.config.yml`, nessun contratto di return degli agent toccato — la schema-propagation rule non si applica).

> **Why.** L'orchestrator di `/new` è un unico contesto long-lived su tutto il batch: ogni byte che rende inline (Bash tool-result, prompt che compone, file che Read-a) resta in finestra per l'intero run, e nessun "purge" via prompt recupera token. La fix attacca la radice — **tenere il bulk fuori dal canale inline** — senza toccare ciò che l'orchestrator può leggere ai punti-decisione (i finding restano file-resident e leggibili on-demand). Onestà sui limiti: questo riduce il *rate di accumulo* (stima ~40-50% sul footprint dominante), non azzera la crescita del context; il bound vero richiederebbe isolamento per-card a livello di harness, fuori dalla portata di una skill.

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — nuova sezione SSOT **`## Context economy (MANDATORY)`** (HARD RULE in 5 punti: gate-output discipline, diff-to-disk, findings file-resident, pass-paths-not-concatenations, targeted Reads). Applicata ai siti che la violavano:
  - **Gate bash (Phase 2 step 8)** — `cmd 2>&1 | tee /tmp/x.txt` → `cmd > /tmp/x.txt 2>&1; echo "<gate>:$?"`: l'output completo resta su disco, inline solo l'exit code; su fallimento si legge un estratto *bounded* (`tail -n 30`), mai il log intero. Step 9 passa al fix agent il **path** del log, non il blob.
  - **Tutti i re-run di gate foreground** (gap-fix Phase 2.5, Phase 2.55 step 5, Phase 3 step 16, pre-commit Phase 4, Final F.5, team D.1/D.3b) — pointer alla disciplina (redirect a disco, mai inline).
  - **`git diff` (Phase 2.55 step 2-3)** — `git diff > /tmp/diff-<CARD>.txt`; i 3 simplify agent ricevono il **path** e leggono il diff da soli, niente «full diff» nei prompt. Team D.3b allineato.
  - **Blocchi Codex (Phase 0 cross-card audit, Final F.3)** — `2>&1 | tee "$FILE"` → `> "$FILE" 2>&1` (niente copia inline; il file si riempie comunque in streaming per il polling F.4, commento aggiornato).
  - **Final F.3 baseline** — `${ARCH_BASELINE}` (contenuti concatenati) → `${ARCH_BASELINE_PATHS}` (lista di path che Codex Read-a on-demand); F.2 non concatena più i per-card baseline.
  - **Completeness check (Phase 2.5 step 4)** — Read *mirati* (grep con `offset`/`limit` stretti attorno al `file:line` del completion report) invece di Read di file interi.
  - **Progress Bar** — blocco markdown completo solo alle boundary pesanti (card/wave/gate-decision/STOP); il movimento di fase intra-card si mostra via Task spine sub-state, non ri-emettendo la tabella a ogni fase.
  - **Phase 5 step 31** — il "CONTEXT PURGE" è ri-etichettato come *reference-hygiene* (non token-reclamation, che un prompt non può ottenere); il leverage reale è upstream (la § Context economy tiene il bulk fuori dal context fin dall'inizio).

## [4.11.0] - 2026-06-04

**Gli overlay non costringono più a riconciliare a ogni `baldart update` — la VERSION non è più incisa nell'identità dei file generati né usata come segnale di drift quando il base file non è cambiato.** Sintomo riportato dall'utente: ogni update richiedeva una riconciliazione. La verifica del sorgente ha trovato **due trigger indipendenti con la stessa radice** — la VERSION del framework veniva trattata come parte dell'identità di overlay/file-generati, ma viene bumpata a *ogni* release a prescindere dal fatto che lo specifico base file sia cambiato. **(A) churn dei file agent/command generati:** `buildMarker()` stampava `base_version=<VERSION>` nel commento `<!-- baldart-generated -->`; a ogni update la VERSION cambiava → la riga del marker cambiava anche su contenuto byte-identico → git vedeva un diff → scattava il commit `post-update reconcile` *a ogni update*. Il campo era puramente informativo (`readMarker` lo leggeva ma nessuno gate-ava la rigenerazione su di esso). **(B) falso-drift degli skill overlay:** `doctor` e `status` confrontavano il pin `base_skill_version` *direttamente* con la VERSION installata (ignorando `base_file_sha`), così ogni bump faceva apparire ogni overlay come "drifted" anche con base SKILL.md identico — mentre `overlay drift` lo faceva già correttamente SHA-first. **MINOR** (cambia il formato del marker generato; nessuna chiave `baldart.config.yml` — la fix riusa `base_file_sha` già presente).

> **Why.** Il drift deve segnalare *modifiche reali di contenuto*, non bump di versione. La radice comune era usare la VERSION come proxy di "il base è cambiato": un proxy sempre falso-positivo dato che VERSION sale a ogni release. La fix rende ogni layer **content-addressed** (SHA) e relega la VERSION a metadato informativo: (A) il marker porta solo `base_sha` + `overlay_sha` — entrambi cambiano *solo* su modifica reale → i file generati tornano byte-stabili tra update di sola-versione (il commit di reconcile scatta solo quando base o overlay cambiano davvero); (B) `doctor`/`status` diventano SHA-first come `overlay drift`, col confronto di versione come fallback solo quando manca `base_file_sha`. In più il pin `base_<kind>_version` viene rinfrescato al merge **solo quando lo SHA combacia** (base invariato) — così resta veritiero senza mai mascherare drift reale: se il base è cambiato lo SHA differisce e il pin stale resta un segnale legittimo da riconciliare. Retrocompatibile in lettura: `readMarker` tollera ancora i marker legacy con `base_version=` (i consumer pre-4.11 subiscono un diff one-shot al primo update, poi stabilità).

### Changed

- **`src/utils/overlay-merger.js`** — `buildMarker()` non stampa più `base_version` (identità content-addressed: solo `base_sha` + `overlay_sha`); `readMarker()` rende `base_version` opzionale nella regex (legge sia il formato nuovo sia il legacy, `baseVersion: null` quando assente); call-site di `mergeOverlay` semplificato. Nuovo helper `refreshOverlayVersionPin(overlayPath, kind, frameworkVersion, currentBaseSha)` — rinfresca il pin di versione **solo** quando `base_file_sha` combacia col base corrente (no-op su mismatch → drift preservato; no-op su already-current; fail-safe su I/O). `baseVersionTargeted` ora usa `frameworkVersion` come fallback significativo invece di `'unknown'`.
- **`src/commands/doctor.js`** — `overlayDrift` SHA-first: confronta `base_file_sha` con lo SHA del base SKILL.md corrente; fallback al confronto di versione solo quando `base_file_sha` manca (o il base è illeggibile). Esclude i file `*.example.md`.
- **`src/commands/status.js`** — display drift SHA-first allineato al contratto di `overlay drift` (mostra `base changed: sha X → Y` su drift reale, `base unchanged` quando lo SHA combacia; fallback di versione solo senza `base_file_sha`).
- **`src/utils/symlinks.js`** — `_generateOverlayedFile` calcola `baseSha` una volta e, dopo il backfill di `base_file_sha`, chiama `refreshOverlayVersionPin` per tenere truthful il pin informativo quando il base è invariato.

### Added

- **`src/utils/__tests__/overlay-merger.test.js`** — 7 test: stabilità byte-identica del merge tra bump di sola-versione (fix Trigger A), assenza di `base_version` nel marker generato, lettura back-compat del marker legacy, e i tre rami di `refreshOverlayVersionPin` (refresh su sha-match, no-op su sha-mismatch con drift preservato, no-op su already-current).

### Fixed

- **`framework/.claude/skills/new/SKILL.md`** (Phase 3.7 Step A, card-scoped diff) — risoluzione della backlog card per nome file resa **case-insensitive** (`find -name` → `-iname`). Era l'unico punto in tutte le skill che risolve una card by-name, e `-name` è case-sensitive: i file su disco hanno prefisso maiuscolo (`FEAT-`/`BUG-`/`CHORE-`), ma se `CARD_ID` arriva con case diverso (es. `/new feat-0019`) `find` tornava **vuoto in silenzio** → `CARD_FILE=""` → la card veniva trattata come inesistente (stessa famiglia del case-collision docs/PRD noto). Stesso blocco: il placeholder `${paths.backlog_dir:-../../backlog}` non era bash valido (il punto nel nome variabile → `bad substitution` se eseguito verbatim) — ora emesso già risolto in una variabile `BACKLOG_DIR` (fallback `backlog`).
- **`framework/.claude/skills/worktree-manager/SKILL.md`** (`/nw` step 3b, sync card non-tracciate) — stesso fix di normalizzazione: il token `${paths.backlog_dir:-backlog}` letterale (bash non valido) sostituito da una variabile `BACKLOG_DIR` risolta, allineando il fallback (`backlog`) tra le due skill.

## [4.10.0] - 2026-06-04

**`/new` ora ha una todo list persistente e una progress bar — durante un batch sai sempre quale fase gira, quale wave del team mode è in volo, e quale gate è risolto o skippato (con motivo).** Finora `/new` era un orchestratore autonomo cieco verso l'utente: l'unica traccia di stato era il tracker file interno `/tmp/batch-tracker-<FIRST-CARD-ID>.md`, che l'utente **non vede**. Risultato: in un run lungo (15 fasi per card, 50+ gate, sequential o team mode a wave) non avevi modo di capire dove fosse il processo. La fix replica il doppio meccanismo che dà a `/prd` la sua todo list amata, adattato alla struttura `wave → card → fase/gate`: (1) un **Task spine nativo** (TaskCreate/TaskUpdate) — il pannello todo sempre visibile della UI — tenuto *coarse*, un task per card etichettato per wave (`Wave 1 · FEAT-0502 — <title>`) più i framing task `Pre-flight`/`Final review`/`Merge & cleanup`; (2) una **Progress Bar markdown** emessa **alle transizioni** (cambio fase/wave/card, decisione gate, ogni `AskUserQuestion`/STOP — non a ogni messaggio, per non fare rumore nei tratti autonomi) con tabella card×wave + un **Gate ledger** che mostra ogni gate `✅ risolto` / `⏭️ skippato (+motivo)`. **MINOR** (capability aggiunta alla skill `/new`; nessuna chiave `baldart.config.yml` — usa solo i Task tool nativi e l'`execution_strategy` già esistente).

> **Why.** Il valore è la *visibilità*, ma il vincolo è non trasformare il ledger in una licenza di skip. Per costruzione: ogni entry `⏭️` deve citare un motivo **verbatim** dalle skip-reason già enumerate nelle Gate table (`IS_TRIVIAL`, `review_profile=light`, `backend-only diff`, `features.has_e2e_review:false`, `balanced→Final`, `holistic_audit provenance`); un `⏭️ (time budget)` / `(to save tokens)` è esso stesso una violazione di protocollo per la clausola top-of-file "NO PHASE SKIP FOR PERCEIVED TIME". Il ledger rende i skip **auditabili**, non li autorizza. Il tracker interno resta la SSOT di recovery; il Task spine + Progress Bar sono il suo specchio live — esattamente il pattern `/prd` state-file ↔ progress bar.

### Added

- **`framework/.claude/skills/new/SKILL.md`** — nuova sezione autoritativa **"Progress Visibility (MANDATORY)"** (subito dopo "Context Tracking"): definisce una volta sola (A) il Task spine nativo per-card etichettato per wave con la tabella delle transizioni di stato (`Pre-flight`/card/`Final review`/`Merge & cleanup`), (B) il format e i trigger della Progress Bar a transizione con il Gate ledger e la regola dei motivi-verbatim, (C) il mapping fase→cella del ledger (2.5b/2.55/2.6/3/3.5/3.7 + i gate di setup). Reminder a una riga `→ Visibility` nei punti di transizione già esistenti — Pre-flight (creazione spine dopo la risoluzione di modalità/wave), Phase 1 step 2b (claim → card `in_progress`), Phase 4 step 29 (DONE → card `completed`), team mode Step B/D.6/E (confini di wave) e Step F.1/Phase 6 (transizioni di batch) — senza duplicare logica in ogni fase. Aggiunta una "Update rule" del tracker che lega ogni transizione interna ai due surface visibili.

## [4.9.0] - 2026-06-04

**`prd-card-writer` Rule C apre `light` alle card UI-presentazionali — la metà `-ui` di una coppia split non paga più una review Codex piena su un diff di pixel.** Finora il pavimento era assoluto: *ogni* card `feature`/`enhancement` era `balanced` minimo, mai `light`. La regola nasceva giusta (non sotto-revisionare feature che *sembrano* piccole ma nascondono logica), ma non distingueva una UI card che *contiene* logica da quella genuinamente presentazionale — tipicamente la metà `-ui` di una coppia `logic/ui` già splittata dalla atomicity rule (es. `…-submit-ui`, la cui logica vive nella card sorella `…-submit-logic`). Risultato: su un epic come FEAT-0019, card puramente compositive venivano marcate `balanced` → review Codex-`full` per-card su un diff di soli componenti. Nuovo **ramo LIGHT (b)**, recintato e deterministico: scatta SOLO se TUTTE valgono — card pure-UI (`areas == [ui]`, o `owner_agent: ui-expert` quando `areas` è assente) · ≤3 file, tutti componenti/stili (zero data-fetching / state / API / server) · **nessun path sotto `paths.components_primitives`** (compone primitive esistenti, non ne crea/modifica) · nessun trigger DEEP. **MINOR** (nuova capability di routing in Rule C; nessuna chiave `baldart.config.yml` — si appoggia su `areas`/`owner_agent`/`paths.components_primitives` già esistenti).

> **Why.** Il taglio è sicuro perché **la rete DS non dipende dal `review_profile`**: una card `light` mantiene la sua code-review per-card/group con `code-reviewer` rule 8 + la cascade registry-first (gated su `features.has_design_system`), ed è coperta dalla **Final FULL gate** cross-batch — si rimanda solo il Codex-*adversarial* per-card. Quindi la DS-drift (hex/shadow/radius hardcoded = HIGH) viene comunque beccata. Il ramo è **stretto per costruzione**: ogni indizio di logica/state/data-binding, un primitive nuovo/modificato, o `areas` oltre `[ui]` → la card resta `balanced` (la guida resta "in dubbio, `deep`"). Coerente col principio "data-driven sopra threshold arbitrarie": niente soglia a righe, solo segnali strutturali autorati in PRD. Validato su FEAT-0019: dei 3 card `-ui`, solo `05b` (2 file componenti, logica su 05a, compone `BottomActionBar`) qualifica; `06b` (4 file + validazione UI/View Transitions) e `07` (5 file, tocca i view-model + render permission-gated) restano correttamente `balanced`.

### Changed

- **`framework/.claude/agents/prd-card-writer.md`** — Rule C: riga LIGHT estesa col ramo (b) UI-presentational (predicato recintato a 4 condizioni AND); paragrafo "Critical" riscritto — il pavimento `balanced` per `feature`/`enhancement` ora ha **l'unica eccezione** del ramo (b), col razionale (rete DS profilo-indipendente) e i disqualifier espliciti. SSOT invariato altrove: la fallback table del `/new` continua a puntare a Rule C, il routing escalation-only `light`→`full` resta intatto (una UI card `light` è sempre promuovibile da un trigger Step-A).

## [4.8.0] - 2026-06-03

**`baldart update` diventa seamless: un solo comando, zero iterazioni — aggiorna e preserva gli overlay.** Un transcript reale (`v4.2.1 → v4.3.0`) ha richiesto **3 invocazioni fallite + git surgery manuale** per un'operazione il cui lavoro è sempre lo stesso. Le cause erano bug di design del CLI, ora rimossi: (1) **asimmetria stash** — `--yes` auto-stasciava il tree sporco ma il path `--reset` lo *rifiutava*, buttando via l'autorizzazione dell'utente e costringendo a stash/drop/mv-a-/tmp/pop a mano; (2) **hard-stop su divergenza già sicura** — il CLI calcolava `overlayCoversTouched` (sa che gli edit sono già negli overlay, lo scrive: "--reset is safe") ma sotto `--yes` *rifiutava* invece di procedere; (3) **flag-maze** — per sbloccarsi serviva `--reset --yes --i-know`, combo da indovinare; (4) **finalizer assente sul path reset** — i generated-agents + `state.json` restavano uncommitted; (5) **preview morta** — `git log HEAD..FETCH_HEAD -- .framework/` è sempre vuoto col subtree-squash. Ora `npx baldart update --yes` fa tutto in un colpo: auto-stash (motore unico, `git stash push -u` senza pathspec → niente più fallimenti su dir `.gitignore`d), auto-risoluzione della divergenza (overlay-covered → reinstall pulito che riapplica gli overlay; **uncovered → auto-cattura in overlay popolati dal diff + VERIFICA che il merge riproduca l'edit** prima del reinstall), riconcilia + committa gli artefatti BALDART-managed (finalizer anche sul path reset), e ripristina lo stash. **MINOR** (capability aggiunta, comandi invariati e retrocompatibili; nessuna chiave `baldart.config.yml`).

> **Why.** L'intent dell'utente è vincolante: *"voglio che tutto il processo sia seamless, niente iterazioni — le cose da fare sono sempre le stesse: aggiornare e preservare gli overlay"* (coerente col principio "rispetta l'intent esplicito vs advisor"). L'unica collisione possibile tra "seamless" e "non perdere dati" è un edit a un file framework **non** coperto da overlay: la scelta dell'utente è **auto-cattura + verifica** — il CLI genera l'overlay dal diff (modello section-marker di `overlay-merger.js`) e ri-merge contro il base upstream fresco; se il risultato riproduce l'edit a livello di sezioni+preamble procede muto, altrimenti **si ferma una sola volta** (`divergence-capture-incomplete`) elencando i blocker (section deletion, cambio frontmatter/preamble, file `src/`) — mai un'azione distruttiva silenziosa. La verifica è la rete di sicurezza che rende la cattura sicura a prescindere dalla fedeltà dell'algoritmo. Gli escape hatch espliciti (`--on-divergence pull|scaffold-overlays|abort`, `--reset --yes --i-know`) restano per CI/power-user, ma non sono più il path che l'utente deve scoprire: il default È quello seamless.

### Added

- **`src/utils/overlay-capture.js`** — nuovo modulo `captureAndVerify`: verifica che OGNI edit overlay-able divergente sia preservato da un overlay, esistente o catturato. Per agents/commands è **verificabile** — ri-merge l'overlay (esistente o generato dal diff via `[OVERRIDE]`/H2 plain) contro il base upstream fresco (`FETCH_HEAD`) e confronta sezioni ordinate + preamble; un overlay **esistente ma stale** (non riproduce l'edit `.framework` corrente) diventa un **blocker**, non una perdita silenziosa al reset. Per le skill (runtime-concat, non mergiabili dal CLI) un overlay esistente è fidato, l'assenza è un blocker. Path non mappabili → blocker. Il chiamante passa l'INTERO set di file toccati (mai un sottoinsieme pre-filtrato): un pre-filtro lascerebbe il reset cancellare un edit senza overlay che lo preservi. Esporta helper unit-testabili (12+ test).

### Changed

- **`src/commands/update.js`** — (a) **motore di stash unico** `autoStashNonFramework` (blanket `git stash push -u`, niente pathspec) riusato sia dalla normale flow sia da `runReset` (che prima *rifiutava* il tree sporco). (b) **`runReset` finalizer**: re-apply dello stash + `postUpdateAutoCommit` + emissione JSON (`action: 'reset'`) → tree pulito su tutti i path. (c) **divergenza overlay-able**: il default sotto `--yes` non rifiuta più — `resolveOverlayDivergenceSeamlessly` cattura+verifica gli uncovered (committandoli **prima** del reset così sopravvivono allo stash e alimentano il merge), poi reset-and-reapply; stop singolo solo su blocker. (d) **divergenza mixed/real-custom**: cattura il sottoinsieme overlay-able e si ferma una volta (i path `src/`/CHANGELOG non sono auto-risolvibili). (e) **preview reale**: `changelogSinceInstalled` mostra le entry del CHANGELOG upstream tra versione installata e remota (rimpiazza e **rimuove** il dead-code `git.diffWithRemote`, diff opt-in sempre-vuoto col subtree-squash). `--i-know` resta esplicito solo per il `--reset` standalone (implicito nel path seamless).
- **`src/utils/git.js`** — rimosso il metodo morto `diffWithRemote` (nessun chiamante dopo la nuova preview).
- **`src/commands/overlay.js`** — esporta `buildFrontmatter` (riusato da `overlay-capture.js` per scrivere overlay con frontmatter valido senza re-implementare la logica).
- **`framework/.claude/skills/baldart-update/SKILL.md`** — riscritta per il flusso seamless: rimossa la replica chat dei 2 decision-point (preview + stash) e lo step di preview morto; la skill ora lancia `npx baldart update --yes` e riporta, sorvegliando l'unica pausa legittima (`divergence-capture-incomplete`). Description frontmatter, Hard rule #2, tabella decision-point, workflow (Step 0/1/2) e DRIFT-CHECK (`update_js_prompts: 5`, `last_verified: v4.8.0`) allineati.



**`/new` smette di rifare per-card 4 review che la Final FULL gate già ripete sull'intero batch.** La Final Review F.3 gira sempre, incondizionata, su tutto il diff di batch: Codex + **doc-reviewer** + **api-perf-cost-auditor** + **qa-sentinel** (suite completa + build + audit). Quindi le passate *per-card* di quegli stessi reviewer sono, per la sicurezza del merge-gate, già coperte dalla rete finale — il loro unico valore residuo è il fail-fast/attribuzione. Quattro pruning, tutti deterministici e con la Final come rete: (1) **plan-auditor grounding** saltato (sequential) quando `/prd` ha già fatto l'holistic audit (`metadata.holistic_audit`) e non c'è drift sui path della card — mirror esatto del gate v4.3.0 Step 3d; (2) **doc-review per-card** deferita alla Final per le card `light` il cui diff non tocca file di documentazione; (3) **qa-sentinel per-card** che gira solo per `deep` (o `balanced` escalata da risk-drift) — `balanced` non-rischiose deferiscono la suite alla Final; (4) **api-perf-cost-auditor per-card** droppato via nuovo flag lean-contract `skip_api_perf_auditor`, deferito alla Final. **MINOR** (comportamento `/new` più lean; nessuna chiave `baldart.config.yml` — si appoggia su `review_profile` + `metadata.holistic_audit` già esistenti).

> **Why.** Il criterio è netto: si potano solo le review che la **Final FULL gate rifà** (doc, api-perf, qa, codex). Ciò che la Final NON copre — **AC-Closure** (scope discipline), **Simplify** (quality/reuse), **codebase-architect grounding**, **E2E** — NON si tocca: pruneare quelli sarebbe perdere copertura, non togliere ridondanza. Ogni skip è un gate-reason **enumerato e deterministico** (provenance `/prd`, oppure `review_profile`), mai un `time budget`. ⚠️ **Tradeoff #3 (esplicito)**: la maggior parte delle card `feature`/`enhancement` è `balanced` per Rule C, quindi un batch tutto-balanced esegue il primo run della suite di test solo alla Final gate — si perde il fail-fast/attribuzione per-card; il merge-gate resta salvo (Final = suite completa sull'intero batch). Reversibile in 1 riga (ripristina `balanced → SCOPED`). Il percorso sequenziale e quello team restano allineati (stessa logica di defer). `/codexreview` riceve un campo lean-contract simmetrico a `skip_doc_reviewer` (api-perf è un breadth-agent indipendente, NON un validatore del FP-gate → safe).

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — (1) Phase 1 step 4: skip provenance-based del plan-auditor (P1 holistic_audit presente + P2 no-drift `git log <audited_commit>..<trunk> -- <card paths>`; fail-safe → RUN). (2) Phase 3 + team D.1.5/D.2/D.4a: nuova sotto-classe `DOC_DEFER_CARDS` (light + diff senza doc) esclusa dal doc-reviewer, deferita alla Final. (3) Phase 3.5 step 21b + team D.4: `balanced` deferisce qa-sentinel alla Final (eccetto risk-escalation → FULL per-card); solo `deep`/escalated gira per-card. (4) lean-contract a Phase 3.7 Step C + D.4b: `skip_api_perf_auditor: true`. + Step D coverage assertion (accetta i nuovi defer come entry valide) + clausola in testa (enumera i nuovi gate-reason).
- **`framework/.claude/commands/codexreview.md`** — Step -0.5 + Step 2 lean-agent note: nuovo campo `skip_api_perf_auditor` che omette agent #2 (`api-perf-cost-auditor`) anche a `full`. Non tocca il FP-gate (dipende da `code-reviewer`, non da api-perf).

## [4.6.0] - 2026-06-03

**`/new` ora ha una corsia "trivial" per le card minime: niente review di codice su un diff che non contiene codice.** Dopo il de-dup v4.5.0 (profondità scalata su `review_profile`), restava un pavimento alto: una card `skip`-profile ma implementabile — un fix di copy in un `.md`, un update di una riga di `ssot-registry`, un bump di config non-API — passava comunque per codebase-architect + plan-auditor + code-reviewer + AC-closure + QA + doc + Final. Una nuova **fast-lane** scatta quando `IS_TRIVIAL(card)` = `review_profile == skip` **AND** 0 trigger del detector Step-A **AND** diff **non-source** (check deterministico per estensione, riusando il predicato "backend-only" di Phase 2.6 — solo `.md`/`.json`/`.yml`/`.txt`/`.csv`). Una card trivial esegue solo il tail minimo sicuro: `coder → gate meccanici inline (markdownlint/lint/build) → AC-closure → doc-review → commit`, saltando **codebase-architect + plan-auditor + code-reviewer + simplify + Codex + la suite di test QA**. Vale in **entrambe le modalità** (sequential: gate in Phase 1 step 2c + 2.55/3.5/3.7; team: sotto-classe `TRIVIAL_CARDS ⊆ LIGHT_CARDS` esclusa dallo scope del code-reviewer a D.2). **MINOR** (nuova capability per-card; nessuna chiave `baldart.config.yml` — si appoggia su `review_profile` già autorato in PRD).

> **Why.** `review_profile: skip` NON è riservato agli epic: `prd-card-writer.md § Rule C` lo assegna deterministicamente a card `docs`/`chore`/`config` o puramente cosmetiche (typo/rename/copy) senza logica. È quindi un segnale autorato in PRD, persistito nello YAML, su cui ancorare la corsia **senza inventare una soglia a righe** (coerente col principio "data-driven sopra threshold arbitrarie"). Il classificatore è un AND a 3 condizioni con **guard fail-safe**: un solo file source nel diff, un trigger Step-A, o un profilo non-`skip` → `IS_TRIVIAL=false` e la card torna al percorso normale. La triviality può solo **togliere review a una card che non ha codice da revisionare**, mai il contrario — una `skip` mis-autorata su codice, o una il cui testo cita `auth`/`payment`, viene sempre ricatturata. Gli invarianti restano: **AC-closure** (scope discipline) e **doc-review** non si saltano mai, e la **Final Review F.3** copre incondizionatamente ogni card trivial nel gate cross-batch prima del merge (stessa rete su cui si appoggia già il percorso `light`). Tutti gli skip sono gate-reason enumerati deterministici, mai `time budget`.

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — (a) nuova sotto-sezione SSOT "Trivial-card fast-lane" (classificatore `IS_TRIVIAL` a 3 condizioni AND + guard + tabella di cosa salta/resta). (b) Clausola in testa: `IS_TRIVIAL` aggiunto all'elenco dei gate-reason enumerati. (c) Sequential Phase 1: nuovo step **2c** che salta codebase-architect + plan-auditor (step 3/3a/4) per card provvisoriamente trivial; il briefing del coder degrada a card-spec + target file. (d) Phase 2.55/3.5/3.7: gate `IS_TRIVIAL` ri-confermato sul diff reale → skip Simplify + QA-suite + Codex, gate meccanici inline; carve-out esplicito nell'"UNCONDITIONAL" di Phase 3.7. (e) Team D.1.5: sotto-classe `TRIVIAL_CARDS ⊆ LIGHT_CARDS`; D.2 esclude le trivial dallo scope del code-reviewer. (f) Step D coverage assertion: skip trivial come entry valide documentate. Non toccati: `codexreview.md`, `prd-card-writer.md`, AC-closure, doc-review, Final Review F.3, percorso light/full di v4.5.0.

## [4.5.0] - 2026-06-03

**`/new` Team Mode non revisiona più lo stesso diff fino a tre volte: la profondità della code-review ora scala col `review_profile` della card.** Lo Step D del team mode faceva passare ogni card "full" sotto code-review tre volte — `D.2` (code-reviewer di gruppo) → `D.4b` (`/codexreview` per-card, che rispawna code-reviewer + Codex + CoVe + FP-gate) → Final Review F.3 (Codex sull'intero batch) — e ogni card "light" due volte (D.2 + un D.4b che a `light` esegue solo code-reviewer + FP, puro doppione di D.2). Un nuovo step **D.1.5** calcola UNA volta il profilo effettivo per card (floor da `review_profile` + escalation-only dal detector Step-A sul diff reale) e partiziona il gruppo in `LIGHT_CARDS` / `FULL_CARDS`. **D.2** ora esegue il code-reviewer solo sull'union dei diff `LIGHT_CARDS` (skippato del tutto se non ce ne sono); **D.4b** gira `/codexreview` solo per i `FULL_CARDS`, mentre i `LIGHT_CARDS` lo saltano (la loro review adversarial è garantita dalla Final FULL gate, sempre on). Esito: ogni card riceve **una sola** code-review per-card/gruppo proporzionale al rischio + la Final batch gate — light: D.2 + Final; full: D.4b + Final (giù da 3). **D.3b Simplify** salta sulle card `review_profile == skip` (quality-only, niente diff sostanziale). Tutti gli skip sono **gate-reason enumerati e deterministici** (driven dal campo `review_profile` autorato in PRD), non skip "per time budget": una nuova clausola "REVIEW DEPTH SCALES WITH `review_profile`" in testa alla skill lo formalizza. Eliminato anche il polling `sleep N; echo "waiting..."` ad ogni barrier (i background agent re-invocano l'orchestratore automaticamente). **MINOR** (cambia il comportamento di review di `/new` team mode senza rimuovere agenti o rompere install; nessuna chiave `baldart.config.yml` nuova — si appoggia su `review_profile` già esistente nella card).

> **Why.** Il team mode parallelizza la parte economica (scrivere il codice) e serializzava la parte costosa (la coda di review): i coder partivano in parallelo, poi tutto convergeva in un imbuto sequenziale di 11 sotto-passi con la code-review ripetuta. La de-duplicazione si appoggia su due invarianti già presenti: (1) la **Final-review FULL gate** (dal v3.37.0) gira SEMPRE Codex (+ code-reviewer fallback) sull'intero diff di batch, senza riduzioni — quindi ogni riga di ogni card light riceve comunque Codex-adversarial prima del merge; (2) l'**escalation-only** del detector Step-A (`light`→`full`, mai l'inverso) garantisce che un path rischioso emerso a implementazione promuova comunque la card a `full`. Tradeoff accettato (scelta utente "aggressivo 3→2"): per i `FULL_CARDS` il rilevamento cross-card si sposta dal per-gruppo (D.2) al per-batch (Final F.3) — stessa sicurezza di merge-gate, feedback a fine batch invece che per-gruppo. `/codexreview` NON è toccato (il suo FP-gate dipende architetturalmente da `code-reviewer`, quindi la leva è l'invocazione condizionale di D.4b, non un flag interno). Il **percorso sequenziale è invariato** (non ha un code-reviewer di gruppo, quindi il suo `/codexreview` a Phase 3.7 resta l'unica code-review per-card). Allineato al principio "data-driven sopra threshold arbitrarie": nessuna soglia nuova, si onora un campo deterministico già esistente. AC-Closure (D.3a), D.4 QA e D.4a doc restano invariati.

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — (a) nuova clausola in testa "REVIEW DEPTH SCALES WITH `review_profile`" che enumera il profilo come gate-reason deterministico (distinto dallo skip "per time"); la clausola "NO PHASE SKIP" cita il profilo tra le ragioni valide e ammorbidisce "never reduce" in "never reduce for any model-invented reason". (b) Nuovo **D.1.5** (Effective per-card review profile) che calcola la partizione `LIGHT_CARDS`/`FULL_CARDS` una volta sola e la logga. (c) **D.2** code-reviewer scoped a `LIGHT_CARDS` (skip se vuoto); doc-reviewer invariato su tutto il gruppo; tradeoff cross-card→Final documentato inline. (d) **D.3b** salta su `review_profile == skip`. (e) **D.4b** condizionale: `/codexreview` solo per `FULL_CARDS` (profilo sempre `full`, niente recompute), `LIGHT_CARDS` skip con reason enumerata. (f) **Step D coverage assertion** accetta gli skip profilo-driven come entry valide documentate. (g) **Step C** direttiva anti-polling (no `sleep N; echo waiting`). (h) **D.3b Simplify** ora fa **fan-out in parallelo** su tutte le card eleggibili del gruppo (analisi read-only su diff file-disgiunti → niente più coda sequenziale per-card; i fix si applicano dopo, lint/tsc una volta sola); **D.3c E2E** valuta il gate per tutte le card insieme e parallelizza le eleggibili quando colpiscono route disgiunte (fallback sequenziale su contesa del dev-server). Percorso sequenziale, `codexreview.md`, AC-Closure D.3a (resta sequenziale per via dell'`AskUserQuestion`), D.4 QA, D.4a doc, Final Review F.3: non toccati.

> **Nota sullo scope dei tagli.** La de-dup si ferma qui di proposito. La **Final Review F.3** ri-revisiona l'intero batch (Codex + doc + perf + qa-sentinel suite completa) anche dopo il lavoro per-gruppo: è ridondante su batch a gruppo singolo MA è la rete di sicurezza incondizionata su cui ora si appoggiano le card light, e il framework ha **già tentato di ridurne lo scope in v3.35.0 e l'ha rollbackata in v3.37.0** dopo un incidente — quindi resta piena. Stessa logica per il doppio run della suite di test (D.4 per-gruppo + Final). Questi NON sono toccati.

## [4.4.0] - 2026-06-03

**`/prd` mockup intake ora risolve le schermate di una feature in modo *strutturale* da un bundle Claude Design, invece di tirare a indovinare con lo scoring per keyword.** L'export di Claude Design non è controllabile dall'utente: si scarica solo il bundle intero del workspace multi-feature. Finora Step 1.6.4b gestiva il bundle `.zip` con un solo scoring di rilevanza su filename/`<title>` (pieno di `[CALIBRATION-NEEDED]`), e la sola modalità d'ingresso era l'archivio `.zip`. Ora un **passaggio strutturale** (Step b2) sfrutta i segnali nativi che il bundle già porta: **un canvas `*.html` per feature** (anchor sul titolo feature), **slot `id="s<N>"` = screen-ID** del § 3 del prompt di handoff (`s31` ↔ schermata `3.1`), e il manifest nativo **`.design-canvas.state.json`** (arricchimento label, mai autorità). Le schermate che vivono solo come artboard nel canvas (non esportate come PNG) vengono **renderizzate deterministicamente** via http-server + Playwright (nuova `canvas-intake-recipe.md`), risolvendo il CORS che blocca Babel su `file://`. Il trigger è allargato anche alle **cartelle già estratte** (non solo `.zip`). A valle, la routing Hybrid di Step 3 passa da fuzzy substring match a **exact join** su `slot_id`. Lo scoring per keyword resta come **fallback** (bundle generico o canvas ambiguo) e il **gate di conferma STOP umano è invariato**. **MINOR** (nuovo reference `canvas-intake-recipe.md` + nuovi campi di state-schema `mockups.archive.bundle_kind`/`canvas_ref`/`discarded[]`, `mockups.canvas_render`, `screens_in_scope[].slot_id`; trigger allargato; NON una chiave `baldart.config.yml` → nessuna cascade di schema-propagation).

> **Why.** Il mapping 1-a-1 tra schermate prodotte da Claude Design e schermate del PRD non va *imposto* nell'handoff (l'export è monolitico e fuori controllo): **esiste già** nella struttura del bundle, semplicemente il framework non la leggeva. Spostare il peso dall'handoff (dove non si può fare nulla) all'intake (dove i segnali sono tutti presenti) elimina l'archeologia manuale — CORS, http-server e Playwright re-improvvisati a ogni rientro — e la sostituisce con un resolver ripetibile. Contratto fail-safe in ogni direzione: firma assente / slot non-`s<N>` / manifest parziale / feature su 2 canvas / Playwright assente / porta occupata ricadono tutti sul comportamento attuale (scoring + STOP) — **il PRD non aborta mai sull'intake**. Allineato al principio "data-driven sopra threshold arbitrarie": lo scoring `[CALIBRATION-NEEDED]` diventa il ripiego, non la prima linea.

### Added

- **`framework/.claude/skills/prd/references/canvas-intake-recipe.md`** — ricetta archiviata per renderizzare gli artboard canvas-only di un bundle Claude Design in PNG: preflight (no auto-install), http-server con port-loop 8080→8090, render loop Playwright su `[data-dc-section]`/`[data-dc-slot]` (attributi runtime, non statici), dedup vs pre-render del designer, tabella di degradazione. Invocata da Step 1.6.4b/A5, mai standalone.

### Changed

- **`framework/.claude/skills/prd/references/discovery-phase.md`** — Step 1.6.4b: (a) Step a allargato per accettare una **directory** oltre al `.zip` (`mockups.archive.kind: zip | directory`, convergono su `root`); (b) nuovo **Step b2 — Claude-Design structural resolution** (A0 signature gate → A1 canvas anchor con `AskUserQuestion` su ambiguità → A2 estrazione slot `id="s<N>"` → screen-ID → A3 read manifest enrich-only → A4 sidecar jsx/PNG → A5 render via recipe → A6 discard-and-log) inserito tra enumerate e score; (c) Step c demoto a **fallback** esplicito; (d) Step d con variante tabella *screen-centric*; (e) Step e materializza anche jsx-deps + asset condivisi + PNG renderizzati e scrive `screens_in_scope[]` con `slot_id`/`screen_id`/`canvas_ref`. Appendice schema estesa coerentemente.
- **`framework/.claude/skills/prd/references/ui-design-phase.md`** — routing Hybrid (Step 2 della per-screen table): **exact-join-first** su `slot_id`/`screen_id` quando presenti (no STOP), fuzzy substring match conservato come ramo `else` con il suo STOP-and-ask invariato.
- **`framework/.claude/skills/prd/SKILL.md`** — HARD RULE 16: lead-in allargato a "`.zip` OPPURE cartella estratta di un bundle Claude Design (firma `.design-canvas.state.json` e/o `*.html` con slot `id="s<N>"`)" + frase su passaggio strutturale, scoring come fallback, STOP invariato, no-abort.
- **`framework/.claude/skills/prd/assets/state-template.md`** — `mockups.archive` estesa con `bundle_kind`/`canvas_ref`/`canvas_candidates[]`/`discarded[]`, nuovo blocco `mockups.canvas_render`, `kind` ora `zip | directory`; `screens_in_scope[]` con `slot_id`/`screen_id`/`canvas_ref`.
- **`framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md`** — nudge minimo a fine § 3 (titola il canvas col nome feature; numera gli artboard coerenti con gli screen-ID): unica richiesta realistica dato che l'export non è controllabile.

## [4.3.0] - 2026-06-03

**`/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).

> **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.

### Added

- **`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.

### Changed

- **`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.
- **`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.

## [4.2.2] - 2026-06-03

**`/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).

> **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.

### Changed

- **`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.

## [4.2.1] - 2026-06-03

**`/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).

> **Why.** Step 6.6d already replaced `plan-auditor`'s *plan-logic* role with a cross-model Codex adversarial audit, and kept `code-reviewer` as the Claude-side reviewer. But `code-reviewer`'s repurposed mandate (conflicts with existing patterns, reuse gaps, adjacent-card file conflicts) overlapped ~90% with the Codex `attack_surface` plus the Step 6.4 adjacent-card retrieval already fed to every agent. The only non-overlapping sliver — project memory of recurring code-level pitfalls — is covered *better* by `plan-auditor` (which has its own `.claude/agent-memory/plan-auditor/`). The swap aligns fit without losing coverage; the Maintenance Note's ablation rubric flagged `code-reviewer`-on-cards as the prime removal candidate. `code-reviewer` is unchanged and remains the agent for post-dev review (`/new` Phase 3, `/codexreview`).

### Changed

- **`framework/.claude/skills/prd/references/audit-phase.md`** — Step 6.6c agent-type mapping swaps the `code-reviewer` row for `plan-auditor` (card grounding, Always). New 6.6e agent-specific block runs `plan-auditor` with **full card-attack-surface coverage but team-aware spawn suppression**: no `codebase-architect` re-spawn (`/prd` already primed context at kickoff), no specialist auto-spawn (`security-reviewer` / `api-perf-cost-auditor` run as first-class teammates in the same team), and an output-format override to the teammate contract (`### [CARD-ID] — Plan Audit Findings` + `## FINDINGS` via `TaskUpdate`) — `plan-auditor`'s never-suppress list (`git_strategy: TBD`, missing-auth, `claimed_path_collision`, `adr_required_missing`, injection) still binds.
- **Codex fallback dedup (Step 6.6d)** — the Codex-unavailable fallback now spawns a **FULL-mode** `plan-auditor` and explicitly SKIPS the card-grounding `plan-auditor` teammate, preventing two `plan-auditor` instances on the same cards.
- **Consolidated report + Audit Engine Summary (Step 6.7)** — `### Code Review Findings` → `### Plan Audit Findings (card grounding — Claude-side)`; the engine summary now distinguishes the cross-model Codex pass from the Claude-side card-grounding pass.

## [4.2.0] - 2026-06-03

**Per-skill reasoning effort: every shipped skill now declares an `effort:` baseline + honors an inline `effort=<level>` override.** Different skills should reason at different depths — routine narration shouldn't pay for deep deliberation, and structured planning shouldn't be starved of it. Each of the 29 skills gets a frontmatter `effort:` baseline (`prd` → `high`, the other 28 → `medium`) and a `## Effort` body block letting the user dial reasoning depth per-run, e.g. `/prd effort=high <content>`. **MINOR** (new protocol module + new template = capability added; no `baldart.config.yml` key, so the schema-change propagation rule does not apply).

> **Why.** Claude Code's native `effort:` frontmatter is the right knob for the *baseline*, but its precedence puts frontmatter **above** the session `/effort` level — only the global `CLAUDE_CODE_EFFORT_LEVEL` env var beats it, and a running skill cannot re-set the knob itself ([anthropics/claude-code#47275](https://github.com/anthropics/claude-code/issues/47275)). So a naive "default + inline flag that overrides" is impossible with the native knob alone. The inline override is therefore implemented as a **body-level reasoning escalation**: the skill reads `effort=<level>` from its arguments and engages the matching thinking directive (`think hard` → `ultrathink`), which the harness interprets from the text. Fully portable — no env var, no consumer config. Escalation upward is reliable; de-escalation below the frontmatter baseline is best-effort (the body can skip optional deliberation but can't lower the native budget).

### Added

- **`framework/agents/effort-protocol.md`** (new protocol module, #21) — the textual SSOT: native-knob precedence chain, tiering rubric for choosing a baseline, the `effort=<level>` inline-override parsing contract (detect once at kickoff, strip the token before consuming user input — modeled on the existing `-stats` flag in `prd`), and the level→behavior mapping (`low` → no extended thinking; `high`/`xhigh`/`max` → `think hard`/`think harder`/`ultrathink`). Routed from `framework/agents/index.md`.
- **`framework/templates/skill-effort.snippet.md`** (new template) — copy-paste starter for the `effort:` frontmatter key + the `## Effort` body block, twin of `skill-project-context.snippet.md`.
- **`effort:` frontmatter + `## Effort` body block on all 29 skills** — `prd` is `high`; `new`, `bug`, `simplify`, `frontend-design`, `ui-design`, `design-system-init`, `e2e-review`, `issue-review`, and the remaining 19 are `medium`. Any `medium` skill can be pushed to `high`/`xhigh`/`max` per-run via the inline override.

### Changed

- **`skill-creator`** now instructs authors to set the `effort:` baseline (per the tiering rubric) and to paste the `## Effort` block for every new skill — the previous "do not include any other fields in YAML frontmatter" guidance is updated to permit `effort`.

### Out of scope (future)

- **Agents** (`framework/.claude/agents/*.md`) also support `effort:` in frontmatter, but they receive no inline arguments (they are spawned by orchestrators via the Task tool). Their override story — static baseline per agent + orchestrator passing the desired level in the Task prompt — is deferred to a separate release to avoid mixing two override models.

## [4.1.1] - 2026-06-02

**Fix: `/prd` (and `/new`) silently stopped creating their worktree after updating to ≥4.0.0.** Regression introduced in v4.0.0: `worktree-manager` was rewritten to resolve the worktree base from the **new `git.trunk_branch` config key** and to **hard-`exit 1`** when it is absent ("never assume develop"). Because `baldart update` never overwrites `baldart.config.yml`, every consumer that updated without re-running `configure` had no `git.trunk_branch` → the `nw-docs` / `nw` pre-flight aborted → no worktree. v4.0.0 also downgraded the `/prd` Step 1 worktree gate from BLOCKING to a non-blocking "MONITORING SIGNAL", so the abort no longer surfaced as a hard STOP — the session drifted into Discovery on the main checkout instead. **PATCH** (regression fix, no new capability, no schema change).

> **Why.** Before v4.0.0 the worktree base was hardcoded `origin/develop`, so creation always worked. Making it config-driven was correct, but the missing-key path should degrade to autodetection, not a hard failure — and the safety gate that would have caught the failure was removed in the same release. The fix resolves the trunk the same way `configure.js` already does (`origin/HEAD` → local `develop`/`main`/`master`), which is *not* "assume develop": it resolves the branch the repo actually uses.

### Fixed

- **`worktree-manager` `nw-docs` + `nw` pre-flight** — when `git.trunk_branch` is absent from `baldart.config.yml`, autodetect the real default branch (`git symbolic-ref refs/remotes/origin/HEAD` → first existing local `develop`/`main`/`master`) and warn, instead of `exit 1`. Hard-abort only when even autodetection finds nothing (bare repo / no branches). This unblocks `/prd` and `/new` worktree creation on consumers that updated without re-running `configure`.
- **`/prd` Step 1 worktree gate** (`prd/references/discovery-phase.md`) — restored from "MONITORING SIGNAL (non-blocking)" back to a **BLOCKING gate**: the kickoff "Ho creato la sessione PRD … in un worktree dedicato" message is emitted only after the worktree is verified on disk; if `nw-docs` aborted, HALT and surface the error verbatim instead of proceeding to Discovery on the main checkout (HARD RULE 17).
- **`/new` Phase 0 trunk resolution** (`new/SKILL.md`) — when `git.trunk_branch` is absent, `/new` now autodetects the trunk with the **same** `origin/HEAD` → `develop`/`main`/`master` logic as `worktree-manager`, instead of hard-assuming the `develop` literal. Keeps `/new`'s `$TRUNK` (used by the divergence gate and every `git diff "$TRUNK...HEAD"` gate) in lockstep with the branch `nw` actually bases the worktree on — a `main`-trunk repo no longer diverges between the two.
- **`update.js` schema-drift notice** — when the user declines `configure` and `git.trunk_branch` is among the missing keys, the CLI now states that `/prd` and `/new` will autodetect the trunk and warn until the key is set, replacing the previous (inaccurate for this key) "skills will prompt for the missing keys on first use" reassurance.

## [4.1.0] - 2026-06-02

**Portability: framework-wide path-literal de-contamination (`docs/…`, `backlog/`, `src/components/…` → `${paths.*}`) + new `paths.docs_dir` config key.** Closes the open follow-up forward-referenced in v4.0.3: a dedicated, per-occurrence judgment pass over the path-literals `contamination.js` flags across `framework/`. The scanner's `autofixable` count drops **219 → 49**; the 49 survivors are legitimate and intentionally kept. The new `paths.docs_dir` config key makes this **MINOR**.

> **Why.** A hardcoded path in a shipped agent/skill is a frozen spot — a consumer whose docs live at `documentation/` (not `docs/`) gets a wrong directive. But not every literal is contamination: `${paths.*}` is resolved *at runtime* by the skill/agent that reads it, so a token only belongs where the file is an operative instruction. A literal correctly stays literal when it is a `# e.g.` config-default comment (converting is circular), a clickable markdown link (a token breaks the link), a concrete dated worked-example (`ADR-20260115-…` — the literal *is* the teaching), a negative instruction (`do not assume src/components/shared/`), or an overlay example (overlays are verbatim, never runtime-resolved). The judgment was made per-occurrence by a workflow (one judge per file → an adversarial bidirectional verifier) and applied deterministically through the `contamination.js` rule SSOT scoped to approved lines. The adversarial verifier also surfaced contamination the per-file judges missed — including the identity leak below.

### Added

- **`paths.docs_dir`** — umbrella docs-search root, used by the `rg`-fallback in `code-reviewer`, `senior-researcher`, and `worktree-manager` (where bare `docs/` had no config key). Propagated end-to-end per the schema-change rule: `baldart.config.template.yml` + `configure.js` (autodetection + prompt) + `project-context.md` autodetection table + `PROJECT-CONFIGURATION.md` key table. `update.js` detects it automatically via the generic `missingPaths` diff, so existing consumers are offered `configure` on their next update.

### Changed — path-literals → `${paths.*}` across the framework payload (38 files changed, +162/−151)

- **Agents** — `REGISTRY.md`, `codebase-architect`, `coder`, `doc-reviewer`, `prd`, `prd-card-writer`, `api-perf-cost-auditor`, `code-reviewer`, `plan-auditor`, `qa-sentinel`, `security-reviewer`, `senior-researcher`, `skill-improver`, `visual-designer`, `motion-expert`, `wiki-curator`, `hybrid-ml-architect`, `legal-counsel-gdpr`: operative read/write/search paths now resolve `${paths.design_system | references_dir | prd_dir | adrs_dir | wiki_dir | backlog_dir | components_* | docs_dir}`.
- **Skills / commands** — `new`, `prd`, `bug`, `simplify`, `worktree-manager`, `check`, `codexreview`, `qa`, prd reference phases, and the prd asset templates (`card-template.yml`, `epic-template.yml`, `prd-template.md` — resolved by `prd-card-writer` at fill time).
- **Routines** — `ds-drift`, `wiki-review`.
- **`bug/SKILL.md`** — Project Context header now declares `paths.wiki_dir` (companion to the converted Phase-0 doc-rag fallback, keeping declared config deps in sync with the body).

### Fixed

- **Identity leak** — `prd-template.md` hardcoded `**Owner**: Antonio Baldassarre`, shipped into every consumer's PRD document; now the `{{Owner}}` placeholder.

### Kept literal (intentional)

- **`breaking-change-checklist.md`** — a human-read user-edit template (not agent-resolved), where a `${paths.*}` token would be inert text; left literal by design.
- **Opt-out marker** (`<!-- contamination-scan: skip -->`) added to 3 wall-to-wall pedagogical files so the scanner stops re-flagging intentional teaching literals: `doc-writing-for-rag/references/line-count-targets.md`, `schemas-and-errors.md`, `templates/overlays/commands/codexreview.example.md`.
- Config-default `# e.g.` comments (`baldart.config.template.yml`), worked examples, clickable links, and documented defaults across `design-system-init`, `prd/SKILL`, `design-review`, and others.

## [4.0.4] - 2026-06-02

**Portability fix (visual identity): genericize the last fidelity-app visual-identity flavor in doc-RAG worked examples.** Confirms the graphic dimension is clean: `Neo-Brutalism` (plus the `Press Start 2P` font and `amber accents`) appeared as bare styling labels in the `doc-writing-for-rag` before/after examples — now generalized to "the project's design system (per `identity.design_philosophy`)". No behaviour change → **PATCH**.

> **Why / scope confirmation.** After this, project-specific visual identity exists in the shipped payload ONLY where it should: as a config-driven example value (`identity.design_philosophy` e.g. "Neo-Brutalism" / "Minimalist" / "Glassmorphism"), in the config documentation (`PROJECT-CONFIGURATION.md`, template comments, the contamination scanner's own meta-docs), and in the intentional `templates/overlays/*.fidelity-example.md` starter overlays (named and shipped precisely so a consumer copies/adapts/deletes them). Zero project-specific design-philosophy, chart-wrapper, font, or brand-colour literal remains baked into portable skill/agent logic. (Hard-coded `${paths.*}` literals remain a separate, larger open cleanup — see v4.0.3 note.)

## [4.0.3] - 2026-06-02

**Portability fix (round 3 — final): remove fidelity-app DOMAIN example-flavor from shipped framework files.** Closes the T13 portability sweep started in v4.0.1/4.0.2 by genericizing the consumer-domain vocabulary (`merchant`/`booking`/`reservation`/`merchant-theming`, `/merchant/dashboard` routes, `customer-facing vs merchant-facing`, `FEAT-0127-merchant-points-guard`, `area:customer|merchant|admin`) that survived as illustrative example flavor. No behaviour change → **PATCH**.

> **Why.** Even in prose examples, a consumer-domain noun teaches the wrong vocabulary and trips the `contamination.js` `requires-decision` rules. The fix distinguishes genuine fidelity-domain flavor (generalized) from legitimate generic English (`customer` in marketing/security "multi-tenant … customers"), the scanner's own meta-references (`framework-edit-gate.js`, `contamination.js`), and `project-context.md`'s explanation that these opinionated tokens belong in the overlay — all of which are correct and untouched.

### Changed — genericized domain example flavor

- **Example queries/routes** → placeholders: `wiki-curator.md` / `doc-reviewer.md` search examples (`<feature-X>` / `<entity>`); `visual-fidelity-verifier.md`, `commands/design-review.md`, `e2e-review/SKILL.md` route envelopes (`/dashboard` instead of `/merchant/dashboard`); `impact-analysis.md` worked-CR endpoint.
- **`prd.md`** — example card filename and `area:` labels reference `identity.audience_segments` instead of literal `merchant`/`customer`/`admin`.
- **`prd-card-writer.md`** — "split by audience segment (per `identity.audience_segments`)" instead of "customer-facing vs merchant-facing"; generic subsystem example.
- **`doc-writing-for-rag` reference files** (`before-after-examples.md`, `compact-templates.md`, `schemas-and-errors.md`) — added an "illustrative sample domain" disclaimer (the worked before/after corpus uses a sample API; only the *technique* is normative), rather than rewriting 290+ lines of self-contained illustration.

> **Note — out of scope, surfaced for a future pass.** Running `contamination.js` over the whole `framework/` payload also flags ~200 hard-coded **path literals** (`backlog/`, `docs/design-system/`, `src/components/…`) that should be `${paths.*}` config keys. A meaningful fraction are legitimate (config-template defaults, `PROJECT-CONFIGURATION.md` docs, canonical protocol examples), but the rest are a genuine, larger portability cleanup deserving its own dedicated pass — not bundled here.

## [4.0.2] - 2026-06-02

**Portability fix (round 2): remove hard-coded auth/deployment project identifiers from shipped framework files.** Companion to v4.0.1 — extends the de-contamination from the database dimension to the auth and deployment dimensions, where fidelity-app-specific identifiers (`withAuth`, `withAuthNoParams`, `src/lib/auth/middleware.ts`, `src/app/api/v1/...`, `BookingTable`, `ADMIN + MERCHANT`, "Vercel Functions") leaked into self-verification examples, detection-signal lists, and template fills. No behaviour change → **PATCH**.

> **Why.** A project-specific symbol baked into a shipped detection list or CoVe example is the same frozen-spot defect as a hard-coded datastore: the consumer's auth wrapper, role names, and deploy platform are `stack.*` / overlay facts. Genuinely platform-named integrations were left untouched because they are correct: the `vercel:deploy` skill documentation (that skill *is* Vercel-specific), the multi-platform deploy tables in `/new` ("firebase: … vercel: … aws: …"), the per-platform timeout/limit tables in `api-perf-gate.md`, and the config-driven `stack_signature` example in `research-phase.md` (which already shows both a Firestore and a Supabase example).

### Changed — generalized auth/deployment identifiers to placeholders / config keys

- **`code-reviewer.md`** + **`plan-auditor.md`** — the chain-of-verification example findings now use `<auth-wrapper>` / `<route-file>` / `<auth-module>` placeholders (resolved from `${paths.high_risk_modules}`) instead of `withAuth` + literal `src/app/api/v1/...` / `src/lib/auth/middleware.ts` paths.
- **`audit-phase.md`** — the auth-change detection signal cites "the project's auth-guard wrapper / permission helper (e.g. a `withAuth*`-style wrapper)" rather than the literal `withAuth` / `checkPermission` symbols.
- **`doc-writing-for-rag`** (SKILL + `compact-templates.md`) — endpoint template fills use `<authWrapper>` / `<ROLE_A>` / `<ROLE_B>` instead of `withAuthNoParams` / `ADMIN + MERCHANT`.
- **`api-perf-gate.md`** — the upload red-flag cites "the platform's request-body limit (e.g. 4.5MB on Vercel Functions — per `stack.deployment`)".
- **`validation-phase.md`** — the logic-marker grep list covers datastore-access patterns per `stack.database` (not only `prisma` / `firestore.collection`).
- **`wiki-curator.md`**, **`doc-reviewer.md`** — illustrative example queries/paths use `<authWrapper>` / `<DomainType>` / `src/lib/<module>.ts#<symbol>` placeholders.

## [4.0.1] - 2026-06-02

**Portability fix: remove hard-coded `Firestore` assumptions from shipped framework files (T13 follow-up).** The v4.0.0 portability pass guarded most stack-specific checks behind `stack.database`, but a residual set of agent checklists and methodology blocks still *assumed* Firestore as the datastore — contamination that is dead-false (or misleading) on any non-Firestore consumer. No behaviour change for correctly-configured projects → **PATCH**.

> **Why.** A hard-coded datastore literal inside portable framework logic is a frozen-spot where the framework requires a hot-spot (Pree's taxonomy): the datastore is a per-consumer `stack.database` fact that belongs in config/overlay, not baked into a shipped checklist. The genuinely multi-DB surfaces were already correct and are untouched: the gated per-DB blocks in `plan-auditor.md` (§ Persistence-Specific) and `api-perf-gate.md` (§ Stack-specific addenda + per-DB pricing snapshots), the per-`stack.database` switch arms, and the `/new` Production-Readiness example (which already carries an explicit "adapt to your stack" note + multi-DB equivalents).

### Changed — generalized assumed-Firestore checklists to per-`stack.database`

- **`plan-auditor.md`** — 8 general-checklist items (dependency enumeration, transaction/batch concurrency, quota/rate-limiting, N+1, state-machine, async-propagation clock, the never-suppress exception list, common-missing-indexes) now say "datastore … per `stack.database`" instead of naming Firestore. The gated § Persistence-Specific per-DB block is unchanged.
- **`code-reviewer.md`**, **`api-design-principles`**, **`doc-reviewer.md`**, **`prd-card-writer.md`**, **`prd-template.md`** — review/cost/invariant checklist lines and template placeholders generalized to "datastore … per `stack.database`".
- **`api-perf-cost-auditor.md`** — the MANDATORY Load-Simulation methodology ("count exact reads/sec", quota/hot-partition ceilings, tail-latency, never-demote anti-patterns) is now datastore-neutral with Firestore kept only as an inline example; the Firestore-specific hot-doc 1-write/s limit is generalized to "hot-partition / hot-document write limits".
- **`bug` skill** + **`logging-patterns.md`** — the data-bug triage row, the data-debug tooling block, and the "Datastore-Specific Debugging" section (formerly "Firestore-Specific") are now gated on `stack.database` with Firestore + the Firebase MCP shown as the example, and the equivalent primitives for Postgres/Supabase/Mongo named.

## [4.0.0] - 2026-06-01

**Framework-wide architectural alignment of `/prd` and `/new` (the two most-used skills) and their child agents/skills, driven by a full coherence audit benchmarked against the scientific literature.** A multi-agent audit mapped every phase of `/prd` and `/new` one-by-one and found **439 findings** (23 critical, 145 high) concentrated in **6 systemic fault lines** — the canonical one being exactly the bug that triggered this work: the `qa-sentinel` agent was *referenced everywhere but architecturally orphaned*, invoked by callers for capabilities its own system prompt forbids. A research team then benchmarked the 14 architectural themes against the peer-reviewed literature (multi-agent orchestration, automated/LLM code review, CI quality gates, self-repair loops, requirements traceability, observability), producing 8 north-star principles. This release aligns the framework to them. **MAJOR** because it narrows agent capability contracts, demotes a legacy entry point (`commands/new.md`) to a redirect stub, and adds config keys.

> **Why.** The single meta-finding across 8 of 14 themes: BALDART repeatedly enforced critical invariants by **social contract** (an LLM or maintainer is trusted to remember, sync, or self-police) where the literature unanimously requires a **technical contract** (a build-time check, a tool-grant boundary, a tracker precondition, an append-only ledger, or a config-key indirection that fails loudly). The fix extends patterns BALDART already ships correctly (the `framework-edit-gate` hook, `contamination.js`, the worktree stash ban, the AC-Closure Gate, the schema-change-propagation rule — all *validated as correct* by the research) to the surfaces that bypassed them. The research also **refuted three assumptions** the framework held: (1) a cheap per-card "light" review is *not* recovered by an expensive full-batch backstop (Lost-in-the-Middle, TACL 2024 — large batches miss centrally-positioned findings); (2) single-model self-FP-checking and same-vendor "model diversity" are *not* independent validation (Kim et al., ICML 2025 — two LLMs agree 60% of the time *when both err*); (3) LLM diff-simulation is *not* a high-leverage runtime-invariant detector (Chen et al., ICSE 2025 — 44% on intermediate state). Fabricated citations that had become load-bearing rationale (`Arora 2023`, `Devin 2025`, an internal "Anthropic Harness Design" doc, a non-existent `GPT-5.4` model id) were stripped framework-wide.

### BREAKING

- **Agent capability contracts reconciled (the qa-sentinel class).** `qa-sentinel` is now consistently a mechanical gate-runner (PASS/FAIL, no source reads, no severity taxonomy, no test-writing, no e2e) across **every** caller and routing doc — `REGISTRY.md`, `/qa`, `/codexreview` (it is no longer the false-positive "Behavior Validator", which it structurally cannot perform — a code-aware reviewer is), and `/new` Phase 3.5 / Phase 7. The same reconciliation hits `plan-auditor` (a real `QUICK` mode now exists and callers pass `mode: QUICK`), `code-reviewer` (capability row vs `bypassPermissions` reconciled to one truth), `ui-expert` (`model: opus` + `Can Edit Code: Yes` now match the REGISTRY and its real implementer role), and `security-reviewer` / `api-perf-cost-auditor` (YAML-mergeable teammate output schema; never spawned via the prohibited `general-purpose` type). Consumers with overlays on these agents should re-check their overlays.
- **`commands/new.md` is now a thin redirect stub.** The legacy slash-command copy of the `/new` pipeline (which silently omitted the BLOCKING Phase 0 hygiene, AC-Closure, Simplify, E2E, Pre-Merge Codex, post-merge hygiene, and Metrics phases, ran `code-reviewer` where the skill forbids it, and pointed at a non-existent `/qa/README.md`) is removed. `/new` now has exactly one source of truth: the `new` skill. `prd-add-phase.md` is likewise a redirect stub to the worktree-aware `prd-add` skill (the `/prd` auto-trigger now targets the canonical skill, not the stub).
- **New `baldart.config.yml` keys** (schema-change-propagation: template + `configure` autodetect + `update` detector): `git.trunk_branch` (replaces every hard-coded `develop`/`main` integration-trunk literal in the worktree flow; autodetected from `origin/HEAD`), `paths.high_risk_modules` (config-driven risk detectors — no project paths baked into shipped gate logic), `paths.metrics`, `paths.wiki_log`. Existing consumers are prompted to populate them on next `update`.

### Changed — single source of truth (no more divergent twins)

- **`review_profile`** decision criteria now live ONLY in `prd-card-writer.md` § Rule C (the SSOT); `/new`, `backlog-phase.md`, `card-template.yml`, `validation-phase.md`, `testing.md`, `qa.md` carry a pointer, not a duplicate table. The drifted `>15-file → DEEP` trigger is reconciled into the SSOT.
- **`owner_agent` enum** and a new **card `status` enum** are machine-readable SSOT blocks in `REGISTRY.md`; the inline copies and the `prd.md:538`-line-number citations are replaced with pointers. `execution_strategy` is uniformly `groups[]/level`; `git_strategy` is uniformly an object `{branch, base, target}` (a string broke `/new`'s worktree-slug derivation); the duplicate `impact-analysis.md` was deleted (one canonical copy under `prd/references/`).

### Changed — gates made real, worktree hygiene fixed (P0)

- Abolished "MANDATORY NON-BLOCKING" labels (relabelled to MONITORING SIGNAL or given an executable consequence); fixed vacuous post-commit `git diff --name-only HEAD` (now `HEAD~1..HEAD`); added `tsc --noEmit` as its own gate (guarded by `stack.language`); re-run static checks after code-mutating phases; Final Review BLOCKER findings now block the merge.
- Removed every `git stash` that ran **inside** a worktree (`refs/stash` is globally shared — the skill's own Safety Rule, now honoured everywhere); externalized `$MAIN`/`$TRUNK`/slug to the tracker with presence guards (consistent variable name + resolution across both `/prd` and `/new`); task-scoped scratch filenames (no date-only collisions).

### Changed — P1 hardening (Wave 6)

- Retry loops are explicit three-branch state machines (SUCCESS / STUCK-ESCALATE / ABORT) with error-fingerprint stuck-detection, a single cap (3), `git status --porcelain` before commit, and TIMED_OUT branches for background tasks. `codebase-architect` is deduplicated (reuse per-card baselines instead of re-spawning for the batch); fan-out has explicit completion barriers; `run_in_background` is never combined with `--wait`.
- Telemetry is instrument-at-source: every Phase-8 metric field has a named producer (no post-hoc prose parsing); JSONL is a single atomic append (no append-then-rewrite); `cycle_time_mins` is anchored to a strategy-independent event. Thresholds carry provenance tags; `COMPREHENSION_DIMENSION_COUNT` is a single named constant so the research-launch trigger fires at a stable point.

### Added

- **`scripts/check-reference-integrity.js`** — a build-time reference-integrity gate over the shipped `framework/` payload: every `subagent_type` dispatch must resolve to a real agent, the `general-purpose` fallback is banned, and a denylist regression-guards the dead references this release removed (fabricated citations, `/qa/README.md`, `obsidian-sync`). Runs in CI on every push/PR (`.github/workflows/check-reference-integrity.yml`).
- A new `agents/coding-standards.md § Reference-Aliasing Mutation Patterns` section (the SSOT that `coder.md` / `code-reviewer.md` / `new` already cited but that did not exist), and graceful-degradation for optional/unshipped dependencies (`huashu-design`, `doc-reviewer-support`) instead of MANDATORY reads of missing files.

> **Process note.** The audit, literature benchmark, alignment, a correctness review, the P1 pass, and a holistic `/prd`+`/new` review were each run as multi-agent workflows; every change was adversarially re-reviewed before landing (the holistic review caught a stale card-contract twin in `agents/prd.md` that the per-file passes missed). Two genuine security findings surfaced and were fixed as P0: an `xargs … git show HEAD:{}` injection vector and a hard-coded vulnerability baseline that created false conformance.

## [3.41.0] - 2026-06-01

**`qa-sentinel` is now profile-driven: `balanced` cards run SCOPED tests (only the tests related to the touched files), and the FULL suite is reserved for `deep` cards (or a `balanced` change whose diff reveals undeclared risk).** This is a **behavioral change to QA depth** — the default for an ordinary feature/refactor changes from "run the whole suite" to "run the related tests" — not an additive feature. **No new `baldart.config.yml` keys** — it reuses the existing `review_profile` field; scoped/smoke test selection is auto-probed at runtime from the project's test runner.

> **Why.** The `review_profile` (skip/light/balanced/deep) is computed deterministically upstream by `prd-card-writer` (Rule C) and passed to `qa-sentinel` by `/new` — but the agent **ignored it**. Its prompt literally said `Run FULL VALIDATION MODE` while the line below passed `QA profile: balanced`, and the agent re-decided QUICK-vs-FULL from a crude `>5 files` heuristic. Net effect: a 6-file UI-only `balanced` card triggered the entire regression suite. The fix makes the profile **authoritative**: it selects the tier; file *count* no longer escalates (the framework already treats file count as advisory-only — `prd-card-writer.md`). The only upgrade path is **risk drift** — the diff itself touching auth/permission/payment/schema/migration/API-contract — which catches a card the author under-classified. An adversarial review of the first design rejected three over-reaches kept out of this release: putting the executable tier table in `agents/testing.md` (the agent never reads it), a ghost `SMOKE` tier mapped to `light` (which never invokes the agent), and a `>15 files` escalation (re-introducing the very full-too-often problem).

### Changed — `qa-sentinel` honors `review_profile` (scoped-by-default)

- **[framework/.claude/agents/qa-sentinel.md](framework/.claude/agents/qa-sentinel.md)** — Step 0 + `Operating Modes`: tier is now selected from the `QA profile` in the prompt, not file count. MODE 1 is **SCOPED VALIDATION** (profile `balanced`): a stack-aware test-impact cascade (`jest --findRelatedTests` → `vitest related` → `pytest <paths>` → `--testPathPattern`, monorepo-workspace-aware) that runs only related tests and **never** falls through to the full suite. MODE 2 is **FULL VALIDATION** (profile `deep`, or `balanced` escalated by risk drift). Confidence rule added: a SCOPED run that finds no related tests for a *logic* change records `Tests: SKIP — no related tests` and caps confidence at ≤70% instead of reporting PASS 95%. The H2 headings are unchanged (preserves consumer agent overlays); only the section bodies were rewritten.
- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** — Phase 3.5 qa-sentinel prompt no longer says "FULL VALIDATION MODE"; it passes the tier contract (`balanced → SCOPED`, `deep → FULL`, risk-drift-only escalation) and derives the E2E depth from the profile (`deep` full+blocking, `balanced` `--grep` advisory). Duplicate step "22" renumbered. Team-mode **D.4** documents that a mixed group with any `deep` card runs FULL (max-profile intentionally overrides the per-card SCOPED tier).
- **[framework/.claude/commands/qa.md](framework/.claude/commands/qa.md)** — `/qa` now passes `QA profile` to qa-sentinel (so the agent never defaults to absent), and the LIGHT path no longer falls back to the full suite when the test pattern is unclear.

### Changed — documentation aligned

- **[framework/agents/testing.md](framework/agents/testing.md)** — new stack-neutral **Scope-aware Test Selection** section documenting the principle (balanced→scoped / deep→full / risk-drift→escalate), explicitly pointing at `qa-sentinel` as the executable SSOT and fencing TIA as heuristic (no coverage maps / AST / ML). Runner-specific commands deliberately kept out (Codex / non-Node consumers read this module).
- **[framework/agents/index.md](framework/agents/index.md)** + **[README.md](README.md)** — routing pointer and the qa-sentinel one-liner updated (`Quick/Full/Deep profiles` → `profile-driven: scoped-by-default, full on deep`).

## [3.40.0] - 2026-06-01

**Doc fixes surfaced by `/new`'s doc-review are now applied by the `doc-reviewer` itself, not delegated to `coder`.** The `doc` fix-domain owner changes from `coder` to `doc-reviewer` (write mode) across every `/new` site that previously spawned a coder for documentation: sequential Phase 3, team-mode D.4a, and the post-batch Final review. **No new `baldart.config.yml` keys** — this is a fix-routing change internal to `/new`.

> **Why.** When `/new`'s post-implementation doc-review found stale/missing docs, the remediation was routed to `coder` — the wrong agent on two counts. (1) It's *overkill*: the `doc-reviewer` already ran the audit and has the full doc context in-context; a coder spawn re-derives it from scratch. (2) It's *wrong*: the doc invariants the orchestrator must not break (freshness markers, linking protocol, frontmatter standard, tabular formatting, SSOT/registry coverage, dependency-topological order, SCIP/code refs) are encoded in the **`doc-reviewer`** system prompt, NOT the coder's — so doc fixes were going to the agent *least* equipped for them. The `doc-reviewer.md` Constraints already mandate "WRITE missing docs directly … do not defer to other agents"; `/new` Phase 3 contradicted that by invoking it read-only and handing the fix to `coder`. The v3.28.3 Domain-Override decision was framed as "orchestrator-inline **vs** coder" and simply never put "doc-reviewer writes" on the menu. This release closes that gap. (`/prd` is unaffected — it *produces* docs directly via `prd-card-writer`/the writing phases and has no read-only-audit→remediation loop.)

### Changed — `doc` fix-domain owner is now `doc-reviewer` (not `coder`)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** — Domain-Override Domains table: the `doc` domain now carries an explicit **Owning agent = `doc-reviewer` (write mode)** column, with a rationale block ("Why `doc` is owned by `doc-reviewer`, not `coder`"). `security` and `migration` stay with `coder`. The mechanical `CHANGELOG.md`/`ssot-registry.md` append edge case now goes through `doc-reviewer`, never `coder`.
- **Sequential Phase 3** collapses from two spawns (doc-reviewer read-only audit + coder apply) to **one** — the doc-reviewer audits AND applies in a single invocation (it runs alone; code-review moved to Phase 3.7, so the old read-only/parallel-safety constraint no longer applies). The only output it does NOT fix itself is a doc-drift→bug finding rooted in CODE, which follows the code fix path.
- **Team-mode D.2/D.4a**: D.2 keeps the doc-reviewer **read-only** (it runs in parallel with `code-reviewer` — parallel-safety preserved); D.4a now re-invokes the **doc-reviewer in write mode** over the group to apply the per-card-attributed findings, instead of spawning a fix-coder.
- **Final review F.5**: verified `>= MEDIUM` findings are now partitioned by domain — `doc`-domain findings → `doc-reviewer`, all other findings → `coder`.

### Changed — Fix Application telemetry recognises the new owner

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** Fix Application Log Schema: `decision` and `applied_by` gain the `doc-reviewer` value; `phase` gains `D.4a`. (The analyzer regex in `framework/scripts/analyze-fix-application.js` already accepts arbitrary `[\w-]+` tokens — no code change needed.)
- **[framework/docs/FIX-APPLICATION-TELEMETRY.md](framework/docs/FIX-APPLICATION-TELEMETRY.md)**: violation/healthy pattern tables updated — `applied_by=coder` on a Phase 3 `doc` row is now itself a violation; the healthy doc pattern is `decision=doc-reviewer | applied_by=doc-reviewer`.

## [3.39.0] - 2026-06-01

**A `/prd` or `/new` run now CONCLUDES clean — it never ends by handing the user a list of "azioni tue, non bloccanti" (an uncommitted file blocking the local `develop` fast-forward, a merged remote branch left undeleted).** Every workspace-hygiene leftover is either auto-resolved by the finalizer or put behind ONE explicit `AskUserQuestion` gate. No passive manual TODO ever survives into the final summary. **No new `baldart.config.yml` keys.**

> **Why.** A `/prd` finalization closed with: *"il develop locale non si è fast-forwardato per una modifica non committata pre-esistente a `docs/metrics/skill-runs.jsonl` — gestisci quel file e poi `git pull --ff-only`"* and *"il branch remoto non è stato eliminato — puoi cancellarlo a mano"*. The orchestrator declared the run "completato" while leaving the workspace half-finished. Root cause: `worktree-manager mw-docs` printed `⚠️ … Leaving as-is` on a blocked fast-forward, and `/prd` (unlike `/new` Phase 6c) had no workspace-hygiene phase to consume that — it forwarded the warning to the user verbatim.

### Changed — the merge finalizer auto-resolves instead of giving up

- **[framework/.claude/skills/worktree-manager/SKILL.md](framework/.claude/skills/worktree-manager/SKILL.md)** (Common — sync local develop): the blocked-ff path no longer prints `Leaving as-is`. It partitions the dirty tree: the framework-owned append-only telemetry log (`docs/metrics/skill-runs.jsonl`) is reconciled autonomously and losslessly (commit + `pull --rebase` + best-effort push) so the ff completes; ANY foreign file emits a new `[SYNC-NEEDS-DECISION]` marker the orchestrator MUST convert into one explicit gate. Work this run does not own is never auto-committed.
- **[framework/.claude/skills/worktree-manager/SKILL.md](framework/.claude/skills/worktree-manager/SKILL.md)** (Cleanup step 6.3 + report): merged remote-branch deletion is now an explicit, reported step (was `2>/dev/null || true` silent) — a denial is retried, never degraded into a "puoi cancellarlo a mano" note.

### Added — `/prd` Step 7.5 Workspace Hygiene & finalization (BLOCKING)

- **[framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md)**: new Step 7.5 mirrors `/new` Phase 6c — consumes the `[SYNC-NEEDS-DECISION]` / `[SYNC-DEFERRED]` markers, raises ONE `AskUserQuestion` per unresolved residue, ensures the merged remote branch is gone. New **HARD RULE** in Final output: the summary MUST NOT contain a "azioni tue / note non bloccanti" section — a run that prints "gestisci tu il file e poi pulla" has FAILED Step 7.5.

### Changed — `/new` Phase 6c parses the new marker

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** (Phase 6c step 2): now also parses `[SYNC-NEEDS-DECISION]` (foreign-file ff block / rebase conflict) as a blocking gate, alongside the existing `[SYNC-DEFERRED]`.

### Changed — AGENTS.md scopes branch-deletion approval to unmerged branches

- **[framework/AGENTS.md](framework/AGENTS.md)**: owner approval is required before deleting an **unmerged** branch; deleting an **already-merged** feature branch is routine cleanup the merge skill performs automatically (no separate approval). The "prune remote branches only when instructed" rule is clarified to exclude a feature branch's own ref on successful merge.

### Revert

- Restore the `git -C "$MAIN" pull --ff-only origin develop || echo "⚠️ … Leaving as-is."` one-liner in `worktree-manager` and drop `/prd` Step 7.5.

## [3.38.0] - 2026-06-01

**The per-card review depth (QA + Codex) is now decided deterministically in the backlog card via a new `review_profile` field, computed once at PRD authoring time — instead of being re-derived heuristically by `/new` at runtime.** This removes the implementing agent's interpretation latitude: the card carries `skip|light|balanced|deep`, `/new` READS it, and only falls back to runtime computation for legacy cards that predate the field. **No new `baldart.config.yml` keys** — this is a backlog-card schema change, propagated end-to-end across templates + card-writer + validation + `/new` in the same release.

> **Why.** Previously the QA Profile Selector (Phase 3.5) and the Codex Review Profile Selector (Phase 3.7) both recomputed the profile inside `/new` from soft signals (title keywords, the self-admittedly-unreliable `files_likely_touched`), so the same card could be reviewed differently depending on the orchestrator's reading. The card already encodes every hard signal needed (`owner_agent`, type, `areas`, `data_fields`, `db_indexes`, `acceptance_criteria`, `estimated_complexity`) — so the decision is now made once, in the card, by the deterministic rules in `prd-card-writer.md § Rule C` (the SSOT).

### Added — `review_profile` card field (deterministic review depth)

- **[framework/.claude/skills/prd/assets/card-template.yml](framework/.claude/skills/prd/assets/card-template.yml)** + **[framework/templates/feature-card.template.yml](framework/templates/feature-card.template.yml)** + **[framework/.claude/skills/prd/assets/epic-template.yml](framework/.claude/skills/prd/assets/epic-template.yml)**: new `review_profile: skip|light|balanced|deep` field. Epics are always `skip` (trackers, no code work). Authors MAY override the computed value by hand.
- **[framework/.claude/agents/prd-card-writer.md](framework/.claude/agents/prd-card-writer.md)**: new **Rule C** — the canonical mapping table (metadata → profile) and the SSOT for the decision. Added `review_profile` to the required-fields list. `feature`/`enhancement` cards are floored at `balanced` (never `light`).
- **[framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md)**: Step 6.0 now BLOCKS on a missing/out-of-enum `review_profile` (mirrors the `owner_agent` gate), with a soft WARN+normalize for `feature`-card-`light` violations and epic non-`skip` values.
- **[framework/.claude/skills/prd/references/backlog-phase.md](framework/.claude/skills/prd/references/backlog-phase.md)**: documents that `prd-card-writer` populates `review_profile`.

### Changed — `/new` reads the field instead of recomputing it

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** (QA Profile Selector): primary path now READS `review_profile` from the card verbatim; the existing rule table is demoted to a **legacy-card fallback** with an explicit "keep in sync with `prd-card-writer.md § Rule C` (SSOT)" note.
- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** (Phase 3.7 Step C.0): the Codex profile derives from the card's `review_profile` (`skip`/`light` → Codex `light`, `balanced`/`deep` → Codex `full`). New **escalation-only invariant**: the Step A high-risk diff detector can only promote `light` → `full` (a risk surfaced at implementation time the authored profile didn't anticipate), never downgrade `full` → `light`. The card's `review_profile` is the deterministic floor; the diff detector is the safety net on top.
- **[framework/.claude/commands/new.md](framework/.claude/commands/new.md)**: thin mirror updated to the read-from-card primary path with compute-fallback.

### Changed — `light` profile no longer runs the per-card Codex adversarial pass

- **[framework/.claude/commands/codexreview.md](framework/.claude/commands/codexreview.md)** (Step -0.5) + **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** (Phase 3.7 Step C.0, Team D.4b): at `profile: light`, `/codexreview` now omits agent #5 (Codex adversarial) in addition to qa-sentinel + api-perf-cost-auditor + Step 3.5 CoVe. **`light` per-card = `code-reviewer` + the false-positive gate only.** The Codex adversarial review becomes the prerogative of `full` and of the final cross-check.
- **Why it's safe (BUG-0530 invariant relocated, not dropped)**: the "never miss a blocker on a low-risk path" guarantee is now carried by (1) the **escalation-only** Step A detector — any high-risk path in the actual diff promotes `light` → `full`, so risky code still gets per-card Codex adversarial immediately; and (2) the unconditional **Final-review FULL gate** (v3.37.0), which runs Codex adversarial over the entire batch diff before merge. So every `light` card's code is still Codex-reviewed at least once pre-merge — the pass is paid once at the batch gate instead of N times per-card. Trade-off: a Codex-class finding on a 0-trigger `light` card surfaces at the final gate rather than per-card (later fix-cycle, still pre-merge).
- **Revert**: re-add agent #5 to the `light` branch of `/codexreview` Step -0.5.

### Backward compatibility

- **Non-breaking**: backlogs authored before v3.38.0 have no `review_profile`. `/new` silently falls back to the runtime rule table (logging `computed — review_profile absent`), so existing cards run exactly as before. New PRD-generated cards get the field automatically.

## [3.37.1] - 2026-06-01

**`/prd` Step 7 merge is fully seamless — auto merge + worktree cleanup with zero end-of-session questions; the user is asked ONLY for conflicts `mw-docs` genuinely cannot auto-resolve.** Fixes a drift between the `/prd` `validation-phase.md` and the real `worktree-manager mw-docs` behaviour that made the skill over-ask. No new `baldart.config.yml` keys.

### Fixed — `/prd` no longer over-asks at merge time

- **[framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md)**: the cross-PRD-collision note and the Step 7 conflict-resolution table claimed structured registries (`project-status.md`, `ssot-registry.md`, …) "abort the rebase and ask for manual resolution". That contradicted `worktree-manager` `mw-docs`, which actually strips markers + runs a structural validation and only aborts when the validation fails (the rare same-row collision). Aligned both spots to the real behaviour: append-only registries auto-resolve and merge with zero questions; escalation happens **only** on a genuinely unresolvable conflict (code/test/fixture, or a structured registry whose strip+validate failed).
- **[framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md)** (Step 7 step 9): reinforced "no confirmation prompt" — explicitly forbids the "vuoi che mergi e pulisca il worktree?" pre-emptive gate. Merge **and** cleanup run automatically; the model lets `mw-docs` run and only surfaces what it actually reports as unresolvable, instead of asking "just in case".

## [3.37.0] - 2026-05-30

**Two-tier review for `/new`: fast `light` per-card during the loop, one guaranteed FULL pass over the whole batch before merge.** This activates the dormant v3.35.0 Review Profile Selector AND re-introduces an unconditional final full review — together they cut tokens/wall-clock on low-risk cards *without* weakening the merge gate. **No new `baldart.config.yml` keys** — both are changes inside the skill, so the schema-propagation rule does not apply.

> **The design.** `light` (per-card Phase 3.7) is now an **early-feedback optimization**, not the safety gate. The safety gate is the **Final review**, which since this release ALWAYS runs a single FULL `/codexreview` (full agent set) over the **entire batch diff** before merge — no N=1 skip, no cross-card scope reduction. So every line of every card, including any reviewed at `light`, gets a full-depth Codex review at least once before merge. This is what makes activating `light` safe: it shifts the breadth passes (qa-sentinel, api-perf-cost-auditor, doc-reviewer) + CoVe from the per-card loop to the single final pass, rather than dropping them from the merge gate. (The v3.35.0 data gate — which had kept the selector hard-coded to `full` pending consumer telemetry — is lifted by explicit maintainer decision; an adversarial review showed that gate's intended post-hoc telemetry could never have validated it anyway, since the Fix Application Log is blind by construction to blockers `light` *misses*. The final full gate makes that validation question moot.)

### Changed — Review Profile Selector live (per-card early feedback)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: Phase 3.7 Step C (sequential) and Team Mode D.4b no longer hard-code `profile = full`. The selector runs as designed — `light` ⟺ Step A matched **0** high-risk triggers **AND** the card's QA profile ∈ {`skip`, `light`}; `full` otherwise. **BUG-0530 invariant preserved**: `light` ≠ skip — Codex adversarial + `code-reviewer` + false-positive gate ALWAYS run per-card; `light` drops only the breadth passes + CoVe.

### Changed — Final review is now an unconditional FULL batch-diff gate (supersedes v3.35.0 scope-reduction)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** (Final review / Step F.1): the v3.35.0 final-review scope gate (N=1 → skip F.1–F.4; N>1 → reduce to the cross-card surface) is **removed**. The final review now runs Steps F.1–F.5 for **every batch including N=1**, with `review_scope_files` = the **full union** of all touched files, never reduced. F.3 reviews the entire batch diff at full depth (Codex + doc-reviewer + api-perf-cost-auditor + qa-sentinel); F.5 runs the final build. Team mode reaches the same gate via "Post-batch — same as sequential mode" (its prior "cross-card-only scope" note is updated accordingly).
- **Rationale**: v3.35.0 de-duplicated the final full pass on the assumption that Phase 3.7 had already full-reviewed every card. Selecting `light` breaks that assumption, so the post-batch full pass is re-introduced as the guaranteed merge gate.

### Cost & trade-offs (documented)

- **Net cost**: one full `/codexreview` per batch is re-introduced (the deliberate price of the per-card `light` speed-up). The saving comes from cheaper per-card reviews during the loop; the final pass is paid once per batch, not per card.
- **Later feedback on `light` cards**: a defect on a `light`-reviewed card may now surface at the final pass rather than at the per-card step — possibly a later fix-cycle. Acceptable: it is still caught before merge.
- **Revert**: to make Phase 3.7 always-full again, restore `profile = full` (no config kill-switch by design). The final full gate is independent and stays regardless.

## [3.36.0] - 2026-05-30

Adds **opt-in session telemetry** to `/new` and `/prd`: append `-stats` (or `--stats`) to a run and, at the end, the skill writes a per-**agent-role** breakdown of REAL token consumption and wall-clock time. This finally answers "what took the most time and tokens in a `/new` run" — and feeds the v3.35.0 Review Profile Selector **data gate** (still pending precisely because `docs/metrics/` carried no per-role cost data). **No new `baldart.config.yml` keys** — the opt-in is a flag, not a config feature, so the schema-propagation rule does not apply.

> **Zero model-token overhead.** The measurement is a **post-hoc Node script**, not a hook and not LLM self-reporting (the model has no reliable token counter). It reads the Claude Code session transcripts — the orchestrator's `<session>.jsonl` plus every `subagents/agent-*.jsonl` — which already carry `message.usage` (the four token counters) and per-message `timestamp`. On a real `/new` run the bulk of cost lives in the subagents (measured ~53M cumulative subagent tokens vs ~47k orchestrator output), and it is all captured. Cache-read tokens are kept **separate** from fresh input (they bill ~10×cheaper) so the breakdown doesn't mislead.

### Added — `-stats` / `--stats` opt-in session telemetry

- **[framework/scripts/analyze-session-tokens.js](framework/scripts/analyze-session-tokens.js)** (new): post-hoc aggregator. Locates the live transcripts from `$CLAUDE_CODE_SESSION_ID` + the cwd slug, sums the four `usage` counters (`fresh_input` / `cache_read` / `cache_creation` / `output`) **kept separate**, computes per-file wall-clock spans, and maps each subagent file → its role by matching the subagent's first prompt to the `Agent` tool_use `subagent_type` in the main transcript (verified 20/20 on a real `/new` batch; unmatched files bucket as `unknown`). Emits a human-readable `docs/metrics/sessions/<run_id>.md` (per-role table sorted by a weighted `cost_units` figure + totals + caveats) and a machine-readable `TELEMETRY_JSON=` line. **Fail-safe**: any error prints `stats: SKIPPED (<reason>)` and exits 0 — it can never abort a run.
- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: arg parser strips `-stats` / `--stats` like `-full` and sets an internal `STATS` flag (composes with `-full`). **Phase 8 — Metrics Log** gains a `STATS`-gated step that invokes the script and enriches the same `skill-runs.jsonl` row with a `"cost"` object (`by_role` + `totals` + `run_wall_ms`). Without `-stats`, Phase 8 is byte-for-byte unchanged.
- **[framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md)**: new **HARD RULE 18** — since `/prd` is conversational (no flag parser), the opt-in is detected once at **Step 1 (Kickoff)** from `$ARGUMENTS` and persisted as `stats_enabled` in the session state file (survives context compression / Step 0 resume). The **Step 7 Metrics Log** reads it and runs the same script (`--skill prd`), enriching its JSONL row identically.
- **[framework/.claude/skills/prd/assets/state-template.md](framework/.claude/skills/prd/assets/state-template.md)**: new `stats_enabled` header field.

### Notes & caveats

- **Accurate in sequential mode.** Team-mode L0/L1 orchestrators can spawn background / nested sub-orchestrators in *separate* session dirs; their subagents are not under the top-level `<session>/subagents/`. The script detects the gap (Agent spawns in main with no local subagent file) and prints an explicit caveat with the excluded count rather than silently undercounting.
- The per-role `wall(sum)` reflects active compute time; the headline run wall-clock is the orchestrator span and **includes human idle gaps** — both are labelled in the report.

## [3.35.2] - 2026-05-30

Resolves the **3.35.1 known follow-up**: a flag introduced in a newer release (e.g. `--on-divergence`, v3.29.0) was rejected by an older global CLI with a cryptic `error: unknown option` **before** the `npx baldart@latest` auto-relaunch could self-upgrade — commander validates options during `program.parse()`, which runs *before* the action handler where the relaunch lives. The `update` command now parses **permissively**, so an unknown flag is captured instead of crashing the parser; the action then transparently relaunches under `@latest` (forwarding the raw argv so the unrecognized flag survives) or, when that isn't possible, emits an **actionable** error instead of commander's cryptic one. **No new `baldart.config.yml` keys** — the schema-propagation rule does not apply.

> **Forward-looking by nature.** This cannot retroactively fix CLIs *already* installed at a pre-3.35.2 version — their commander still throws before any code we control runs. The fix rescues flags introduced *after* this release for anyone on 3.35.2+. For older installs the only path remains `npm i -g baldart@latest` (now surfaced by the helpful error + the update notifier).

### Fixed — self-upgrade survives flags newer than the installed CLI

- **[bin/baldart.js](bin/baldart.js)**: the `update` command gains `.allowUnknownOption()` + `.allowExcessArguments()` (scoped to `update` only — every other command keeps strict validation). Unknown tokens now land in `command.args` instead of throwing `commander.unknownOption` during parse; they are forwarded to `update(options, command.args)`.
- **[src/commands/update.js](src/commands/update.js)** (`update`): an early unknown-flag block runs **before** any work. When unknown tokens are present it relaunches under `npx baldart@latest`, forwarding `process.argv.slice(3)` (raw flags, with the leading `update` subcommand token stripped so the child isn't spawned as `update update …`). The unrecognized flag never enters `options`, so raw-argv forwarding is the only faithful path.
- **[src/commands/update.js](src/commands/update.js)** (`maybeRelaunchUnderLatest`): accepts `extra.rawArgs` (forward verbatim instead of reconstructing from parsed `options`) and `extra.unknown` (skip the interactive "proceed with current CLI" prompt — pointless when the CLI literally can't parse the flag; relaunch whenever a newer version exists). The normal stale-CLI relaunch path is unchanged (still reconstructs known flags, still forwards `--on-divergence` / `--json`).
- **[src/commands/update.js](src/commands/update.js)** (`failOnUnknownArgs`): new helpful terminal error replacing commander's cryptic message — hedges between "recently-added flag → `npm i -g baldart@latest`" and "typo", and **suppresses the upgrade suggestion** when `BALDART_RELAUNCHED=1` (we are already the `@latest` child, so a typo is the only explanation). `--json`-aware: emits a `usage-error` JSON object on stdout.
- **[src/commands/update.js](src/commands/update.js)**: `--json` mode is now **armed before** the unknown-flag block so the relaunch notice / helpful error route to STDERR — preserving the single-object STDOUT contract for `update --json --yes --<new-flag>` (the agent/CI scenario this machinery exists for).

## [3.35.1] - 2026-05-30

Fixes a **HIGH-severity** bug where `baldart update --reset` could leave a consumer **committed-without-`.framework/`** (broken install, manual recovery only). The reset path wiped `.framework/`, committed the deletion, then handed off to `add()` **programmatically with no repository** — the bin-layer default (`antbald/BALDART`) only applies on the CLI path, so `repo` arrived `undefined`, crashing at `repo.startsWith(...)` ("Cannot read properties of undefined") *after* the wipe was already committed. Three layers now make this unreachable: (1) repo resolved **before** any destructive step, (2) reinstall failures roll back to the pre-reset backup tag, (3) defensive guards + legible errors. **No new `baldart.config.yml` keys** — the schema-propagation rule does not apply.

### Fixed — `update --reset` never leaves the consumer without `.framework/`

- **[src/commands/update.js](src/commands/update.js)** (`runReset`): the upstream repo is now resolved via `State.resolveRepo()` **before** the backup tag / `rm -rf` / reset commit, then threaded **explicitly** into `add()` (was `addCmd(undefined, { yes: true })` — the root cause). The stale v3.28.1 comment claiming "undefined → use default from package.json" (which never existed in `add.js`) is corrected.
- **[src/commands/update.js](src/commands/update.js)**: the reinstall is wrapped in a `try/catch` that, on **any** failure (undefined repo, network, ignored path, missing hooks), auto-runs `git reset --hard <backupTag>` to restore the pre-reset `.framework/` and prints an explicit recovery command. Safe because reset is gated on a clean working tree — the backup tag holds everything but the reset commit.
- **[src/commands/add.js](src/commands/add.js)**: new `options.throwOnError` makes `add()` **re-throw** instead of `process.exit(1)` at every reachable failure point (download catch, gitignore-still-ignored, hooks-missing post-flight), so a programmatic caller can recover. CLI behavior is unchanged (flag absent → historical exit-1). Also resolves a falsy `repo` from the ledger as a last resort and fails fast **before** download with a recovery hint.
- **[src/utils/git.js](src/utils/git.js)** (`normalizeRepoUrl`): guards a falsy/non-string `repo` with a legible `Error` ("repository non specificato …") instead of the cryptic `Cannot read properties of undefined (reading 'startsWith')`.

### Changed — shared repo resolver (de-dup)

- **[src/utils/state.js](src/utils/state.js)**: new exported `resolveRepo(cwd)` (cascade `state.framework_repo → DEFAULT_REPO`, never `undefined`, preserves a consumer's fork) + exported `DEFAULT_REPO` constant. `recordInstall` now uses the constant.
- **[src/commands/version.js](src/commands/version.js)**: drops its local `REPO_DEFAULT` duplicate in favor of the shared `State.DEFAULT_REPO`.

### Known follow-up (separate fix)

- `--on-divergence` (v3.29.0) is rejected by an **older global CLI** with "unknown option" *before* the `npx baldart@latest` auto-relaunch can self-upgrade (commander validates options first). Tracked separately — workaround: `npm i -g baldart@latest` before using new flags.

## [3.35.0] - 2026-05-30

De-duplicates the review chain in `/new` and adds a **Review Profile Selector** so the per-card pre-merge gate scales its *depth* by risk instead of running the full multi-agent `/codexreview` on every card — including trivial ones. The per-card gate stays **unconditional** (it always runs); only its breadth varies, and the safety-critical Codex adversarial pass + false-positive gate run on every card regardless of profile. Copertura invariata: every line is still code-reviewed once, every doc doc-reviewed once. **No new `baldart.config.yml` keys** — the profile is computed at runtime from signals already present (the Phase 3.7 high-risk-trigger detector + the QA profile), and lean mode is an internal caller→skill file contract, so the schema-propagation rule does not apply.

### Changed — `/new` review chain (de-dup, coverage-invariant)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** + **[framework/.claude/commands/codexreview.md](framework/.claude/commands/codexreview.md)**: per-card Phase 3.7 (and Team Mode D.4b) now hand `/codexreview` a lean contract file (`/tmp/codexreview-lean-<CARD-ID>.json`) so it **reuses the Phase-1 architecture baseline** (`/tmp/arch-baseline-<CARD-ID>.md`) instead of re-spawning `codebase-architect`, and **skips the duplicate `doc-reviewer`** (Phase 3 already audits the same diff). `/codexreview` gains a backward-compatible **Step -0.5 (lean-mode detection)** with consume-once semantics; standalone `/codexreview <CARD-ID>` (no contract file) behaves exactly as before.
- **doc-reviewer lens-merge**: Phase 3's `doc-reviewer` mandate now also carries the *spec/docs-drift → bug* lens that was previously exclusive to `/codexreview` agent #4 — so dropping the duplicate loses no coverage.
- **Final review de-dup**: single-card batches short-circuit F.1–F.4 (the lone card already passed Phase 3.7 unchanged) while still running the F.5 final build; multi-card batches scope the cross-card review to a **computed** surface (files touched by ≥2 cards ∪ `depends_on`-linked pairs), never a model-decided skip.
- **Team Mode doc-reviewer collapse**: `doc-reviewer` went from ~3× per card (D.2 + D.4a + D.4b) to ~1× per group — D.2 attributes findings per-card, D.4a consumes them (no re-spawn), D.4b skips its copy via the lean contract.

### Added — Review Profile Selector (data-gated)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: Phase 3.7 Step C selects `light` ⟺ (0 high-risk triggers AND QA profile ∈ {skip, light}), else `full`. `light` drops the breadth agents (qa-sentinel, api-perf-cost-auditor, doc-reviewer) + Step 3.5 CoVe but ALWAYS keeps `code-reviewer` + Codex adversarial + the false-positive gate (BUG-0530 invariant preserved — `light` ≠ skip). Mirrors the existing QA Profile Selector pattern. Ships behind a **data gate**: the mapping stays hard-coded to `full` until consumer telemetry (`docs/metrics/` Fix Application Log) confirms `light`-eligible cards have not historically produced verified BLOCKER/HIGH at full depth.

## [3.34.0] - 2026-05-30

Adds an **assert-active git-hooks health check** to `baldart doctor` and the `worktree-manager` verify step. A consumer had `core.hooksPath` pointing at `.git/hooks` (empty) while the repo's versioned hooks lived in `scripts/git-hooks/` — the pre-commit schema guard and pre-push were **silently inactive for days**. The existing worktree-manager verify (`git config core.hooksPath || true`) only *read* the value without asserting anything, so it never caught the drift. Same class as the LSP bug: the framework "verifies" by reading a proxy instead of asserting the real state. This moves it from read-only to assert-active. **WARN-only** — the check never mutates `core.hooksPath` (a wrong heuristic + auto-fix would be worse than the original bug); it surfaces the exact remediation command and lets the user apply it. **No new `baldart.config.yml` keys** — it's a universal, always-on, filesystem-autodetected check, so the schema-propagation rule does not apply.

### Added — git-hooks health check (assert-active)

- **[src/utils/githooks.js](src/utils/githooks.js)**: new, self-contained, fail-safe utility (distinct from `src/utils/hooks.js`, which manages `.claude/settings.json` hooks). `getStatus(cwd)` detects the repo's versioned-hooks directory (`.husky`, `.githooks`, `scripts/git-hooks`, `githooks` — first that exists and contains a standard-named hook), resolves the **active** hooks dir explicitly (`core.hooksPath` → relative resolved against `git rev-parse --show-toplevel`, absolute verbatim; unset → `<git-dir>/hooks`, handling the worktree `.git`-is-a-file case), and asserts the active dir serves the versioned hooks. **`git rev-parse --git-path hooks` is deliberately NOT used** — empirically it returns the configured value relative/unresolved and historically some git versions ignored `core.hooksPath` for it. Returns `checked:false` (silent, `ok:true`) when no managed hooks exist or on any error, so the check never produces a false WARN.
- **[src/utils/githooks.js](src/utils/githooks.js)**: pure exported `isActiveServing(activeAbs, expectedAbs)` — identical-or-nested decision (covers husky v9, which sets `core.hooksPath=.husky/_` with user hooks in `.husky/`). Paths are canonicalized (symlink-resolved, handling non-existent leaves) before comparison so a symlinked ancestor (macOS `/var`→`/private/var`, symlinked repo/home) never yields a false INACTIVE. Executability (`-x`) is asserted only for raw dirs git execs directly; husky user hooks (not required `+x` in v9) skip the assert.
- **[src/commands/doctor.js](src/commands/doctor.js)**: probe wired into `detectState` (`state.gitHooks`), a `Git hooks` line in `renderDiagnostic` (silent when `checked:false`), and an **advisory print-only** action in `planActions` (`githooks-health`) whose `run()` prints the WARN + fix command and **never mutates** `core.hooksPath` (safe under `--auto`).
- **[framework/.claude/skills/worktree-manager/SKILL.md](framework/.claude/skills/worktree-manager/SKILL.md)**: step 4 (isolation setup) replaces the silent `git config core.hooksPath || true` read with a lean **assert** — detects the versioned-hooks dir, resolves the active dir, and echoes a loud `⚠️ WARNING` + the `git config core.hooksPath <dir>` fix on mismatch (matches husky v9 `.husky/_` and `<dir>/*` as active). The worktree shares `core.hooksPath` via `.git/commondir`, so the same resolution applies.

### Tests

- **[src/utils/__tests__/githooks-active.test.js](src/utils/__tests__/githooks-active.test.js)**: new — 8 pure fixtures for `isActiveServing` (the mayo bug, husky v9 nesting, identical dirs, prefix-not-a-boundary, trailing-sep normalization).
- **[scripts/test-githooks.sh](scripts/test-githooks.sh)**: new — drives **real** git repos: misconfig (`hooksPath=.git/hooks`, hooks in `scripts/git-hooks`) → inactive + fix; correctly wired → ok; non-executable raw hook → ok=false + chmod fix; husky v9 → active, `-x` assert skipped; no managed hooks → `checked:false` (silent).

## [3.33.1] - 2026-05-30

Fixes a **false-positive in the LSP layer's verify step** that let `/lsp-bootstrap` (and `baldart configure` / `doctor`) report a language server as `verified` while every real LSP call failed with `Executable not found in $PATH` — a dead layer masked by `codebase-architect`'s silent Grep fallback. Root cause was a **resolution mismatch**: verify used `npx --no-install typescript-language-server --version` (resolves from `node_modules/.bin`), but the consumer — Claude Code's LSP tool — spawns the server **by name from `$PATH`**. The npm-dev install (`npm install --save-dev`) put the binary in `node_modules/.bin`, which is *not* on `$PATH`, so verify passed and spawn failed. The npm-managed adapters now install **globally** so the binary lands on `$PATH`, and verify checks `$PATH` reachability the *same way the consumer spawns it*. **No new `baldart.config.yml` keys** — reachability is computed, not configured, so the schema-propagation rule does not apply.

### Fixed — LSP verify no longer reports unreachable servers as `verified`

- **[src/utils/lsp-installer.js](src/utils/lsp-installer.js)**: `verifyServers()` rewritten around a 3-state taxonomy. It first probes `$PATH` reachability via `command -v <binary>` (new `_isOnPath` helper — mirrors the consumer's by-name spawn exactly), then runs the functional `verifyCommand()`. Results carry `status` ∈ `verified` (on `$PATH`, probe passed) / `installed-but-unreachable` (present in `node_modules` but not on `$PATH` — new `_isInstalledLocally` helper fingerprints the legacy devDep install; `fix` = global-install command) / `unreachable` / `not-installed` / `unknown-adapter`. Never throws.
- **[src/utils/lsp-adapters/typescript.js](src/utils/lsp-adapters/typescript.js)** + **[src/utils/lsp-adapters/python.js](src/utils/lsp-adapters/python.js)**: `installMode` `npm-dev` → `npm-global`; `installCommand()` → `npm install -g …`; `verifyCommand()` → `<binary> --version` (resolved from `$PATH`, mirroring the consumer) instead of `npx --no-install …`. System adapters (go/rust/ruby) were already PATH-correct and are unchanged.

### Changed — `configure` records unreachable servers; `doctor` fix is now real

- **[src/commands/configure.js](src/commands/configure.js)**: **every** known-adapter result that isn't cleanly `verified` is now recorded in `lsp.installed_servers` (like system-mode pending installs) instead of being dropped by the old `filter(v => v.ok)` — otherwise re-runs reinstall blindly and `doctor` never sees the mismatch. This covers both `installed-but-unreachable` and the npm-global happy-path trap where `npm install -g` exits 0 but npm's global bin dir isn't on the current `$PATH` (→ status `not-installed`, since `npx` can't see the global prefix either) — without this, the primary configure path would have silently reintroduced the dead layer. The fix command (+ `npm prefix -g` PATH hint) is surfaced inline for each.
- **[src/commands/doctor.js](src/commands/doctor.js)**: the `lsp-fix` remediation now reinstalls globally (a real fix that puts the binary on `$PATH`), where before it re-ran `npm install --save-dev` — a no-op for the reachability problem. The `why` text explains the `$PATH` contract, and a post-reinstall re-verify surfaces the `npm prefix -g` PATH escape when a server stays unreachable (so a misconfigured global bin dir doesn't trap the user in a reinstall loop).

### Docs

- **[framework/.claude/skills/lsp-bootstrap/SKILL.md](framework/.claude/skills/lsp-bootstrap/SKILL.md)**: Output Contract redefines `verified` as `$PATH`-reachable, adds the `installed-but-unreachable` status, and documents the **cold-start caveat** (first query may under-report references until indexing completes).
- **[framework/agents/code-search-protocol.md](framework/agents/code-search-protocol.md)**: new fallback rule for cold-start under-reporting — re-issue the first query before trusting a suspiciously-low reference count; never declare a helper unused off one cold find-references.
- **[framework/docs/LSP-LAYER.md](framework/docs/LSP-LAYER.md)** + **[framework/docs/PROJECT-CONFIGURATION.md](framework/docs/PROJECT-CONFIGURATION.md)**: adapter-contract table, install-mode descriptions, `verifyServers` contract, and troubleshooting §6.3/§6.4 updated for global install + `$PATH` reachability + the PATH-mismatch cause.

## [3.33.0] - 2026-05-30

Closes a **structural silent-partial-install** failure mode (observed on the `mayo` consumer, 3.28→3.31, still live in 3.32.0; same class as the historic v3.27.1 bug): `add` was **non-atomic** — the hook registration ran *after* optional blocking prompts (git aliases, configure). In a non-TTY context (Claude's Bash tool never has a TTY on stdin) or on any interruption, those `inquirer` prompts throw/hang, so `.framework/` + `state.json` landed on the new version while `Hooks.registerAll` never ran — a half-install that `baldart status` still reported as "Valid". The fix makes install atomic (correctness-critical mutations run **before** every optional prompt), adds a **post-flight hook assert** that refuses to declare success with any active hook missing, and finally exposes `add --yes` so agents/CI can install unattended. Two additive, non-breaking follow-ups ship alongside. **No new `baldart.config.yml` keys** — `--yes` is a CLI flag, so the schema-propagation rule does not apply.

### Fixed — `add` is now atomic + never declares a half-install successful

- **[src/commands/add.js](src/commands/add.js)**: `Hooks.registerAll` is **reordered ahead of** the optional git-aliases and configure prompts. Hook registration determines install correctness and must never sit behind a blocking prompt — if `add` is interrupted at an optional prompt, the hooks are already in place. The drift callback now passes `{ autoYes: nonInteractive }` (parity with `update.js`), so a drifted hook in non-TTY no longer throws.
- **[src/utils/hooks.js](src/utils/hooks.js)**: new exported `verifyAll(cwd)` — a pure read over `getStatus` returning `{ ok, malformed, missing, path }`. It iterates only the **active** `HOOK_REGISTRY` (commented-out entries like `capture-detect` excluded for free). A malformed `settings.json` counts as a failure (registration was skipped).
- **[src/commands/add.js](src/commands/add.js)**: **post-flight assert** before the success summary — re-reads `settings.json`, attempts a single best-effort re-registration if anything is missing, then exits non-zero ("Install is NOT complete") if a hook is still absent. Covers `update --reset` too, since that path delegates to `add(undefined, { yes: true })`.

### Added — `add --yes` (non-interactive install for CI / agents)

- **[bin/baldart.js](bin/baldart.js)**: registers `-y, --yes` on the `add` subcommand (the underlying `add(repo, options)` already honored `options.yes`, it was just unreachable from the CLI). With `--yes`, `add` auto-confirms optional prompts and passes `{ nonInteractive: true }` to `configure` so its wizard writes autodetected values without blocking.
- **[src/commands/add.js](src/commands/add.js)**: `add --yes` on a repo that **already has** `.framework/` now fails fast with a clear pointer to `npx baldart update --reset --yes --i-know` instead of hitting the deliberately-interactive destructive "Remove and reinstall?" prompt (which would throw under non-TTY and surface as a generic failure). Clean installs only; reinstall goes through `--reset`.

### Added — divergence classifier flags overlay-covered edits (`--reset` is safe)

- **[src/utils/git.js](src/utils/git.js)**: `classifyDivergence` now tags each `custom-overlay-able` commit with `overlayCovered` — true when **every** framework file it touches already has a matching overlay in `.baldart/overlays/`. New pure static `overlayRelForFrameworkFile(path)` mirrors the `overlay.js` path conventions (unit-tested).
- **[src/commands/update.js](src/commands/update.js)**: in the overlay-able divergence branch, when all commits are `overlayCovered` the UI/JSON now recommend `--reset --yes --i-know` as the **safe** resolution (intent already preserved in the overlays) and note that `scaffold-overlays` would create redundant skeletons. New `overlay_already_covered` field in the `--json` output.

### Added — deterministic `overlay drift` via base-sha backfill

- **[src/utils/overlay-merger.js](src/utils/overlay-merger.js)**: new `ensureBaseFileShaInOverlay(overlayPath, baseSha)` — **additive-only, idempotent, fail-safe**: stamps `base_file_sha` into an overlay's frontmatter when absent (never overwrites, never fabricates frontmatter). Called from the merger in **[src/utils/symlinks.js](src/utils/symlinks.js)** `_generateOverlayedFile` at `add`/`update` time, so `baldart overlay drift` becomes deterministic for **every** overlay — not just freshly-scaffolded ones (hand-written / pre-v3.19.0 overlays were "unknown" to drift).

### Tests

- **[scripts/test-add-postflight.sh](scripts/test-add-postflight.sh)**: new — encodes the post-flight **invariant** (a `settings.json` missing an active hook → `verifyAll().ok === false`; the dropped hook in the test is literally `baldart-overlay-telemetry`, the mayo failure). Fails on pre-v3.33.0 code (no `verifyAll`).
- **[scripts/test-overlay-backfill.sh](scripts/test-overlay-backfill.sh)**: new — backfill is additive, idempotent, never overwrites an existing sha, never fabricates frontmatter, fail-safe.
- **[src/utils/__tests__/classify-divergence.test.js](src/utils/__tests__/classify-divergence.test.js)**: extended with overlay-path-mapping fixtures (38 assertions, all green).

### Changed — `/baldart-update` skill

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: post-flight report (step 4) documents the new CLI hook assert in `add`/`--reset`; `last_verified` v3.32.0 → v3.33.0 (`hook_registry_entries`/`update_js_prompts` unchanged).

## [3.32.0] - 2026-05-30

Makes the CLI **agent-native at the output layer**: `npx baldart version --json` and `npx baldart update --json --yes` now emit a single **machine-readable JSON object** on stdout (schemas `baldart.version/1` / `baldart.update/1`), with every human line routed to stderr. This closes the last fragile seam in the autonomous-update path — the `/baldart-update` skill previously had to **regex-scrape human-readable prose** (the v3.26.0 wording change had already forced a dual-format tolerant parser in Step 0). An agent now reads a field instead of matching a sentence. Borrowed from the [CLI-Anything](https://github.com/HKUDS/CLI-Anything) philosophy (`--json` on every command) — but BALDART is already a headless CLI, so this is a thin, hand-rolled output layer, not a generated wrapper. **No new `baldart.config.yml` keys** — `--json` is a CLI flag, not a config key, so the schema-propagation rule does not apply.

### Added — `--json` structured output

- **[src/commands/version.js](src/commands/version.js)**: `--json` emits `{schema:"baldart.version/1", installed, installed_version, remote_version, aligned, offline, fetched, uncommitted_files, commits_ahead/behind, cli_version, cli_latest, cli_update_available, repository, last_update_date, last_pushed_version, last_push_date}` — exactly the facts the human box shows. Framework-not-installed and error paths emit `{installed:false|null, error, …}` so the agent always gets parseable stdout.
- **[src/commands/update.js](src/commands/update.js)**: `--json` emits a single `{schema:"baldart.update/1", ok, action, …}` result at **every** terminal point through one `emitUpdateJson` chokepoint (all-or-nothing: a missed exit would leave stdout empty). `ok:true` ⟺ the framework is now at the remote version (`action` `updated` / `already-current`); every refusal/failure is `ok:false` with `action` (`divergence-refused`, `divergence-scaffolded`, `aborted`, `failed`, `framework-missing`, `usage-error`), `reason`, and a `next_command` the human can re-run. `--json` **requires `--yes`** (so no prompt can block) and **rejects `--reset`** (the nuclear path stays an interactive human escape hatch).
- **[src/utils/ui.js](src/utils/ui.js)**: new `UI.setJsonMode(on)` redirects every `console.log` the UI helpers emit to **stderr** (reversible), keeping stdout clean for the JSON. `ora` spinners already write to stderr, so they need no change. The JSON itself is written with `process.stdout.write`, never `console.log`, so it survives the redirect.

### Changed — autonomous `/baldart-update` reads JSON, not prose

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: Step 0 now **prefers** `version --json` (field reads) and **falls back** to the dual-format text parser when the global CLI predates v3.32.0 (`--json` unknown / non-JSON stdout) — the same "new skill + old global CLI" transition window the text parser already guards. Autonomous mode runs `update --json --yes`, parses the single result object (`ok` / `action` / `installed_after` / `backup_tag` / `next_command`), and keeps the **exit code as the safety backstop**. The post-flight assert cross-checks `installed_after` against on-disk `.framework/VERSION` and the Step 0 `remote_version`. `last_verified` v3.31.0 → v3.32.0; added a **JSON-OUTPUT NOTE** recording that `--json` is a flag, not a config key.

### Fixed — self-relaunch flag forwarding

- **[src/commands/update.js](src/commands/update.js)** `maybeRelaunchUnderLatest`: the stale-CLI self-relaunch now forwards `--json` **and** `--on-divergence <strategy>` to the `npx baldart@latest` child. The latter was a pre-existing latent drop (an agent's non-interactive divergence strategy would silently vanish on relaunch); both are fixed together.
- **[bin/baldart.js](bin/baldart.js)**: the CLI self-update notifier's stdout `announce` is suppressed under `--json` so it cannot pollute the single JSON object.

## [3.31.0] - 2026-05-30

Gives `/baldart-update` an **autonomous mode** so agents and scheduled routines can bring the framework up-to-date with **zero human interaction** — closing the gap that the skill, as shipped, was *purely* human-in-the-loop (it replicated every CLI decision point as a chat confirmation, which an unattended agent cannot answer). The new mode is **auto-detected** from a positive automation signal and keeps every one of the CLI's safety gates: it drives `npx baldart update --yes` and **stops hard** (never forces a merge or reset) the moment the CLI refuses on custom divergence or a conflict — exactly the calls a human should own. **No new `baldart.config.yml` keys** — `BALDART_AUTONOMOUS` is a runtime env var, not a config flag, so the schema-propagation rule does not apply.

### Added — autonomous (unattended) mode for `/baldart-update`

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: new **Mode detection** first step (`printenv | grep -qE '^(BALDART_AUTONOMOUS|CI|GITHUB_ACTIONS)='`) branches the skill into INTERACTIVE (a human is present → existing chat-confirmation flow, unchanged) or AUTONOMOUS (no human → new flow). Detection is **positive-signal-only**: absence of every signal defaults to INTERACTIVE so a human never silently loses control. Documents *why* the TTY test (`[ -t 0 ]`) cannot be used — Claude's Bash tool never has a TTY on stdin, even with a human present, so it can't tell the two situations apart.
- **`## Autonomous mode`** section: pre-flight (shared Step 0 + CLI version drift gate) → `npx baldart update --yes` (bare, `timeout: 600000`) → exit-code interpretation → shared post-flight assert → structured log-friendly report. **Hard guardrail**: on a non-zero exit (custom-overlay-able / real-custom divergence, subtree-pull conflict, stale-CLI halt) the skill STOPS and reports the exact flag a human would re-run with — it **never** retries with `--on-divergence pull|scaffold-overlays` or `--reset` to force past a refusal. A refusal is a *correct* outcome that needs a human.

### Added — automation marker injected by the routine-adapters

- **[src/utils/routine-adapters/github-actions.js](src/utils/routine-adapters/github-actions.js)**: the generated workflow's run step now sets `BALDART_AUTONOMOUS: '1'` in its `env:` block (alongside `ANTHROPIC_API_KEY`), inherited by the `claude` process and every child Bash it spawns.
- **[src/utils/routine-adapters/cron.js](src/utils/routine-adapters/cron.js)**: the generated shell wrapper `export`s `BALDART_AUTONOMOUS=1` before invoking `claude`.
- **[src/utils/routine-adapters/claude-code-cloud.js](src/utils/routine-adapters/claude-code-cloud.js)**: the generated routine config now carries `env: { BALDART_AUTONOMOUS: '1' }` (best-effort — relies on the RemoteTrigger surfacing `env` into the run environment; github-actions / cron inject it directly).

### Changed — drift-check counter + honest framing

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: `last_verified` v3.26.0 → v3.31.0; added an **AUTONOMY NOTE** recording that `BALDART_AUTONOMOUS` is a runtime env var (not a config key) so the schema-propagation rule does not apply. Hard rules gain a rule #0 (run mode detection first; default-on-absence is INTERACTIVE); rule #2 now qualifies the chat-replication mandate as INTERACTIVE-only. The closing "both keep the user in the loop" claim is corrected to reflect that autonomous mode drops confirmations while keeping the safety gates.

## [3.30.0] - 2026-05-29

Hardens the **overlay** system — the layer that turns generic framework skills into project-specific knowledge. An audit surfaced a structural asymmetry: agent/command overlays are *compile-time merged* (the overlay is materially fused into the generated file → guaranteed delivery), but skill overlays use *runtime concatenation* (a line of prose tells the agent to read `.baldart/overlays/<skill>.md` → best-effort, nothing verifies it loaded). The most important overlays for knowledge transfer had the weakest guarantee. This release ships the high-certainty authoring/DX fixes now and adds **observation-only telemetry** to gather real data on whether skill overlays actually load — before deciding whether a stronger enforcement mechanism is worth building. **No new `baldart.config.yml` keys** — the telemetry hook is always-on (like `framework-edit-gate` / `agent-discovery-gate`), so the schema-propagation rule does not apply.

### Added — overlay-load telemetry (observation only)

- **[framework/.claude/hooks/overlay-telemetry.sh](framework/.claude/hooks/overlay-telemetry.sh)**: a fail-safe `PostToolUse` hook (matcher `Read`) that appends one JSON line to `.baldart/telemetry/overlay-loads.jsonl` every time a `.baldart/overlays/*.md` file is read. Objective, not self-reported — the failure mode being measured is "the agent forgot the overlay", which a self-report would corrupt in exactly that case. Written in **bash, not node**, on purpose: it fires on every `Read` (the hottest tool) in the critical path, so a per-read node cold-start (~50ms) would tax every file read — a bash fork (~5ms) keeps the hot path cheap. **Numerator-only by design**: skill invocation is prompt-expansion, not a tool call, so the denominator (skill runs that *should* have loaded an overlay) is not hook-observable (claude-code [#43630](https://github.com/anthropics/claude-code/issues/43630), [#22655](https://github.com/anthropics/claude-code/issues/22655)). Registered in `HOOK_REGISTRY` (`baldart-overlay-telemetry`) → auto-registered by `add`, backfilled by `update`, reported by `doctor` — no call-site changes (registry-driven).
- **[framework/scripts/analyze-overlay-loads.js](framework/scripts/analyze-overlay-loads.js)**: zero-dependency aggregator that cross-references the read log against the overlays that exist on disk. Headline signal: a **skill** overlay that exists but is read ~never (`NEVER-LOADED`). Agent/command overlays (compile-time merged) are deliberately not flagged on zero reads.
- **[framework/docs/OVERLAY-TELEMETRY.md](framework/docs/OVERLAY-TELEMETRY.md)**: full reading guide, with an explicit "conservative proxy — UNDER-counts" section (in-context reads and prompt-expansion are invisible to the hook; a low count is a reason to investigate, not proof of a miss).

### Changed — overlay authoring/DX parity for skills

- **[src/commands/overlay.js](src/commands/overlay.js)** `validate` (skill path): now renders a **merge dry-run** for skill overlays — section counts, concatenated size, and a per-marker check flagging `[OVERRIDE]/[APPEND]/[PREPEND]` headings that don't anchor to any base H2. Previously it printed only "valid, concat model", so a skill-overlay author never saw what the merge would look like (agent/command overlays already got this). Powered by a new pure, unit-tested `analyzeSkillConcat()` helper.
- **[src/commands/overlay.js](src/commands/overlay.js)** `drift` (skill path): when the base file changed, now lists *which* overlay markers were orphaned by the change (heading renamed/removed upstream) — the same actionable feedback agent/command overlays get from the merger, instead of a bare sha-mismatch warning.
- **[src/commands/overlay.js](src/commands/overlay.js)** `scaffoldBody` (skill path): the `overlay scaffold skill/<x>` skeleton is now a guided template — explains the runtime-concat model honestly, enumerates the typical project-fact sections (canonical paths, domain vocabulary, BLOCKING rules, mandated stack), and shows a ready-to-edit `[OVERRIDE]` block. Improves authoring for all skills, not just the two that ship an example overlay.
- **[framework/agents/project-context.md](framework/agents/project-context.md)** §5 precedence rules: corrected the overclaim that a skill overlay's `## [OVERRIDE] <topic>` "replaces" the base section. For skills nothing parses the markers — they are textual conventions the executing agent honours (best-effort), unlike agent/command overlays which are materially fused (§5.bis). The doc now matches `scaffoldBody`'s honest framing instead of the middle.

### Changed — drift-check counter

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: `hook_registry_entries` 3 → 4 (the new telemetry hook). The `add`/`update`/`doctor` integration is registry-driven, so no narration changes were needed.

### Added — tests

- **[src/utils/__tests__/analyze-skill-concat.test.js](src/utils/__tests__/analyze-skill-concat.test.js)**: 5 cases covering marker matching, orphan detection, plain (new) sections, line counts, and the empty-overlay edge case.

## [3.29.0] - 2026-05-29

Closes a two-part process gap in `npx baldart update` reported from a non-TTY agent context on a shared consumer repo: (1) plumbing/collaboration merge commits were mislabeled `custom-other`, downgrading an otherwise overlay-able divergence set into `mixed`; (2) an automated agent had **no** non-destructive path to complete an update when the consumer carried local `.framework/` commits — `--yes` refused, the interactive prompt needed a TTY, and `--reset` is gated on a clean working tree. **No new `baldart.config.yml` keys** — `--on-divergence` is a CLI flag, not a config flag, so the schema-propagation rule does not apply.

### Fixed — divergence classifier no longer poisoned by merge commits

- **[src/utils/git.js](src/utils/git.js)** `classifyDivergence()`: merge commits are now detected **structurally by parent count** (`%P` in the `git log` format → `parents.length >= 2`) instead of relying solely on the brittle subject regex `^Merge … into <branch>$`. That regex missed real `git subtree pull --squash` merges that present as a bare `Merge commit '<sha>'` (git omits the `into <branch>` suffix when the merge lands on `master`) and shared-repo collaboration merges shaped like `Merge branch 'x' of <url> into <branch>` / `Merge remote-tracking branch '…'`. Such merges fell through to the path classifier, which returns `custom-other` for the empty diff-tree of a merge — collapsing an `overlay-able` set into `mixed` and hiding the overlay-migration option. Merges are now labeled `subtree-merge` (noise) and excluded before the aggregate decision.
- **[src/utils/git.js](src/utils/git.js)**: aggregation extracted to a pure, unit-testable static `GitUtils.aggregateDivergenceClass(commits)`. Each divergence commit record now also carries `parents` and `touched` (the latter reused by the new overlay-scaffold flow below).
- **[src/utils/__tests__/classify-divergence.test.js](src/utils/__tests__/classify-divergence.test.js)**: +9 fixtures — bare-merge / `of <url>` / remote-tracking subjects (documenting the regex blind spot now covered by parent count) and an `aggregateDivergenceClass` suite asserting `overlay-able + plumbing-merge → overlay-able` (not `mixed`).

### Added — `--on-divergence` non-interactive escape hatch for agents / CI

- **[src/commands/update.js](src/commands/update.js)** + **[bin/baldart.js](bin/baldart.js)**: `npx baldart update --on-divergence <strategy>` resolves a divergent update without a TTY:
  - `scaffold-overlays` → auto-creates overlay skeleton(s) for the overlay-able framework files touched by the divergent commits (reusing `overlay.scaffoldFile`), then stops so the edits can be filled in and the update re-run. For `mixed`, scaffolds the overlay-able subset and refuses to pull while `custom-other` commits remain (would be destructive).
  - `pull` → keeps the commits and lets the subtree pull create a merge (non-destructive; conflicts are surfaced, never auto-resolved). Works for `overlay-able`, `mixed`, and `real-custom`.
  - `abort` → explicit no-op.
- Bare `--yes` on a divergent class still refuses (nothing destructive by accident) but now prints the exact flag to re-run with instead of dead-ending.

### Changed — interactive `overlay` choice now actually scaffolds

- **[src/commands/update.js](src/commands/update.js)**: the interactive "migrate to overlay" option previously printed *"run `/overlay` then re-run"* and exited (a no-op). It now scaffolds the overlay skeleton(s) in-place via `overlay.scaffoldFile` and points the user to `/overlay` for guided filling.
- **[src/commands/overlay.js](src/commands/overlay.js)**: extracted a non-exiting `scaffoldFile(cwd, target, options)` (returns a structured result) from the `process.exit`-driven `scaffold()` CLI wrapper, so it can be called in a loop from `update` without a single missing/existing overlay terminating the run. Exported alongside `parseTarget`.

### Changed — `/baldart-update` skill narration synced

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: documents the parent-count merge detection and the new `--on-divergence` strategies; corrects the stale "real user-custom commits ABORT in --yes mode" claim.

## [3.28.3] - 2026-05-28

Release B of the 3-release plan on `/new` fix-application authority. Closes two inline-apply violation patterns observed in real `/new` runs (doc fixes applied by the orchestrator instead of delegated to coder; Codex HIGH security findings written inline) and shuts the structural loophole in `SKILL.md`'s sub-agent failure protocol that was being used to bypass delegation even when no agent had crashed. Telemetry from v3.28.2 will measure the effectiveness of these changes; Release C (conditional threshold rule) waits for that data.

### Added — Domain-Override Domains sub-section

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: new sub-section under "Fix Application Log Schema" enumerates tassativamente the domains where inline orchestrator apply is **never** safe, regardless of patch size: `doc` (any `*.md` under references/, prd/, root CHANGELOG, ssot-registry), `security` (Phase 3.7 detector Triggers #2/#3 + SQL RLS policy mutations), `migration` (`supabase/migrations/*.sql` or `${paths.migrations_dir}`). The coder agent's system prompt + project overlay enforces invariants (freshness, tabular formatting, RLS structure) that orchestrator inline edits routinely break. Edge case explicit: mechanical CHANGELOG / ssot-registry append-a-row stays `doc` (uniformity > spawn cost).

### Changed — Phase 3 doc delegation reinforced

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** Phase 3 step 15: explicit chiosa "Doc fixes are NEVER applied inline by the orchestrator, regardless of size or perceived triviality" added below the existing coder-spawn instruction. The rule already existed (line 1116, pre-v3.28.2: "invoke the coder agent once") but was being bypassed in practice — the chiosa makes the rule non-discretionary.

### Changed — Phase 3.7 coder applies Codex patches verbatim

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** Phase 3.7 step 4 sub-bullet "spawn coder": coder now receives the report path + list of VERIFIED bugs **plus the patches Codex suggested inline in the report**, and applies them verbatim. The coder does NOT re-do the analysis, does NOT re-grep, does NOT re-read files extensively. Codex has already produced both the diagnosis and the patch; the coder's value-add is the right system prompt (project conventions, naming, testing patterns), not redoing security/correctness review. Saves an estimated 5-10k tokens per Codex-driven coder spawn.

### Changed — Sub-agent failure protocol (loophole closed)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** "Sub-agent failure protocol" section: replaced single bullet "attempt the work yourself directly" with a 4-step protocol. (1) log failure with full trace, (2) retry once (transient errors are common), (3) **if domain-override (doc / security / migration) and retry still fails: STOP and AskUserQuestion — never inline-fallback for these domains**, (4) non-override domains may fall back inline but MUST log `applied_by=orchestrator-fallback` for telemetry visibility. Razionale: the pre-v3.28.3 wording was being used as a shortcut to bypass delegation rules even when no agent had crashed; closing that defeats the violation pattern at the structural level.

### Rationale

Release B addresses 4 of the 17 critiques raised in the adversarial review of the original threshold-rule plan (#4-5 domain override definition, #7 Codex re-analysis ridondante, #10 escape valve aperta). The remaining critiques are either addressed by Release A telemetry, deferred to conditional Release C, or accepted as design trade-offs documented in the plan file. **No new `baldart.config.yml` keys** — domain-override is enforced by content/path matching, not by config flag (KISS).

## [3.28.2] - 2026-05-28

Telemetry-only release. Adds structured logging for fix-application decisions in `/new` Phases 2.55 / 3 / 3.5 / 3.7. **Zero behavior change** — the orchestrator's decisions remain identical to v3.28.1. The goal is to measure, with real data, whether the per-phase delegation rules need to change in a future release.

### Added — `## Fix Application Log` section in tracker

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: new section **"Fix Application Log Schema (telemetry)"** inserted before Phase 2.55. Defines a pipe-separated row format (`<phase> | <domain> | est_lines=<n> | decision=<...> | applied_by=<...> | <key=val>...`) appended to the tracker's `## Fix Application Log` section. Phases 2.55 / 3 / 3.5 / 3.7 each instructed to log one row per finding processed (including skipped). Cost: ~1-2 KB per card, negligible context impact.
- Per-phase logging instructions added to Phase 2.55 step 4 (simplify reuse/quality/efficiency), Phase 3 step 15 (doc), Phase 3.5 step 23 (qa-sentinel blockers), Phase 3.7 step 4 (codex BLOCKER/HIGH + false-positive-filtered). Each phase classifies finding `domain` consistently with the schema enum.

### Added — Aggregator script

- **[framework/scripts/analyze-fix-application.js](framework/scripts/analyze-fix-application.js)**: zero-dependency Node script. Reads N tracker files (shell-expanded glob), parses rows in the `## Fix Application Log` section, aggregates by `phase × domain × decision × applied_by`, outputs a tabular summary plus an automatic violation footer flagging inline-apply on doc / security / migration domains. Usage: `node framework/scripts/analyze-fix-application.js docs/references/trackers/*.md`.

### Added — Telemetry reading guide

- **[framework/docs/FIX-APPLICATION-TELEMETRY.md](framework/docs/FIX-APPLICATION-TELEMETRY.md)**: new doc. Explains why telemetry exists, the row schema (cross-references SKILL.md as SSOT), how to read the analyzer output, violation patterns vs delegation candidates vs healthy patterns, and the trigger conditions for the future Release C decision (threshold rule vs Phase 2.55→3 merge).

### Rationale

This release is the first of a 3-release sequence (A: telemetry → B: loophole + 2 known violations → C: conditional threshold rule). The decision to instrument *before* changing rules avoids inventing arbitrary thresholds without data — see plan file at `/Users/antoniobaldassarre/.claude/plans/sto-notando-una-cosa-misty-lightning.md` for full reasoning. Schema-change propagation rule: **no new `baldart.config.yml` keys** in this release.

## [3.28.1] - 2026-05-28

Patch su tre drift segnalati dal curator dopo v3.28.0 install in `mayo`:
(1) `--yes` non si propaga da `update --reset` al child `baldart add`,
(2) errore "Configuring alias is not permitted without enabling allowUnsafeAlias" stampato come install-failed mentre l'install era OK,
(3) due template `ui-guidelines.template.md` + `brand-guidelines.md` lasciati untracked senza istruzioni chiare all'utente. Tutti e tre cosmetici/UX, nessuno affecta la correctness del framework installato — ma confondono l'utente durante il post-install.

### Fixed — `baldart add` honors `--yes` from parent caller

- **[src/commands/add.js](src/commands/add.js)** + **[src/commands/update.js:237](src/commands/update.js)**: `add(repo, options)` ora legge `options.yes` e auto-conferma i prompt non-distruttivi (`Install framework?`, `Configure git aliases?`, `Configure project context now?`). Il prompt distruttivo `Remove and reinstall?` resta interattivo anche con `--yes` (safety). `update --reset` ora invoca correttamente `addCmd(undefined, { yes: true })` — prima passava `{ yes: true }` come **primo argomento** (cioè `repo`), facendo `options` undefined e ogni prompt scattava.

### Fixed — Git alias config failure non più mascherato da install-failure

- **[src/commands/add.js](src/commands/add.js)**: la configurazione degli alias `fw-version`/`fw-update`/`fw-push` passa da `git.git.addConfig()` (simple-git, che rifiuta i bang-alias senza `allowUnsafeAlias`) a `spawnSync('git', ['config', key, value])` diretto. Il bypass del check simple-git è esplicito qui: l'utente sta opt-in agli alias durante l'install. Failure isolato per alias (non bubble-up al catch globale), reported come WARNING se non-zero count, install continua. Il messaggio "✗ Installation failed: Configuring alias is not permitted…" non appare più.

### Fixed — Template editable post-install: istruzioni chiare

- **[src/commands/add.js](src/commands/add.js)** NEXT STEPS box: i due template `docs/references/ui-guidelines.template.md` e `docs/references/brand-guidelines.md`, copiati incondizionatamente durante `copyCustomizableFiles()`, ora sono elencati esplicitamente con cosa farne (rename / edit / delete) e con il punto chiave: "Commit them to track your project's design language, or add them to .gitignore if you don't need them." Evita la confusione del curator durante v3.28.0 install ("non chiari se debbano essere scelti/scartati dall'utente o committati. Non c'è prompt che lo chieda").

## [3.28.0] - 2026-05-28

Tre additions indipendenti accumulate nel working tree e pronte per il rilascio: convenzione testuale per le dipendenze MCP, supporto `.zip` per il mockup intake del PRD, e nuovo hook `capture-detect` come parte del proactive mode dello skill `/capture` (registrazione opt-in, non auto-attivata). Tre temi distinti ma raggruppati in un'unica release di accumulo perché tutti additive e zero-schema-change.

### Added — Convenzione MCP integration

- **[framework/docs/MCP-INTEGRATION.md](framework/docs/MCP-INTEGRATION.md)**: nuovo documento centralizzato. Spiega quando un agent/skill deve appoggiarsi a un MCP server, fornisce un catalogo di MCP raccomandati per i casi d'uso comuni (doc-rag, playwright, firestore/db, LSP, …), e definisce la convenzione testuale "MCP dependencies" per dichiarare le dipendenze nel body dell'agent/skill. Zero nuove chiavi config in `baldart.config.yml` — la convenzione resta testuale finché una skill concreta non dovrà gate-arne il comportamento (regola di schema-change propagation: niente layer drift).
- **[framework/.claude/agents/codebase-architect.md](framework/.claude/agents/codebase-architect.md)**: aggiunta sezione `## MCP dependencies` con classificazione Required (`doc-rag` come primo step dell'Investigation Protocol) vs Optional (LSP find-references, DB MCP per inspect runtime). Documentato il degrade path quando l'MCP non è disponibile.
- **[framework/.claude/skills/bug/SKILL.md](framework/.claude/skills/bug/SKILL.md)**: aggiunta sezione `## MCP dependencies` con Required (`playwright` per Phase 1 reproduction di UI/Client bug — senza è impossibile catturare stato deterministico) e Optional (`doc-rag` per known patterns, DB MCP per Data bugs, `codebase-architect` agent).

### Added — PRD mockup intake da archivio `.zip`

- **[framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md)** Step 1.6: nuovo formato `local-archive` accettato accanto a `chat-images` / `local-paths` / `mixed`. Caso d'uso: export "Download as .zip" da Claude Design (o qualsiasi altro tool che produce bundle di mockup) già scaricato sul disco dell'utente.
- **[framework/.claude/skills/prd/references/discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md)** § Mockup Intake: Step 1.6.4b descrive l'intero flow per `.zip` — `unzip` in scratch dir `mockups/_archive/`, enumerazione candidati visivi, scoring di rilevanza contro lo scope del Kickoff, selezione auto-proposta, STOP per conferma, materializzazione del sottoinsieme confermato in `${paths.prd_dir}/<slug>/mockups/`. Evita di copiare PNG irrilevanti nell'asset directory definitiva.
- **[framework/.claude/skills/prd/assets/state-template.md](framework/.claude/skills/prd/assets/state-template.md)**: aggiornato `mockups.format` enum per includere `local-archive`.

### Added — Hook `capture-detect` (proactive mode, opt-in)

- **[framework/.claude/hooks/capture-detect.sh](framework/.claude/hooks/capture-detect.sh)**: nuovo Stop hook che drop una candidate JSON in `.claude/capture-queue/` quando rileva ragionamento cross-document nella sessione appena chiusa. La distillazione vera in synthesis page wiki resta sempre una human-approved manual step via lo skill `/capture`. Failure-isolated (exit 0 sempre, non blocca lo Stop event).
- **Registrazione opt-in (NOT auto-attivata)**: la entry corrispondente in [src/utils/hooks.js](src/utils/hooks.js) resta commentata (linee 90-97). Lo skill `/capture` funziona già in modalità manual senza il hook; il proactive mode è una feature opzionale. Per attivarlo nel proprio consumer: scommentare la entry e re-installare via `npx baldart update`. Una futura release potrà rendere il proactive il default.

### Added — Smoke test del hooks registry

- **[scripts/test-hooks.sh](scripts/test-hooks.sh)**: nuovo smoke test per `src/utils/hooks.js` (multi-hook registry introdotto v3.18.0). Verifica register/unregister/isRegistered/merge idempotente con fixture in tmpdir. Dev tooling, non user-visible.

## [3.27.1] - 2026-05-28

Patch follow-up alla v3.27.0 dopo l'incidente reale in `mayo`: l'utente aveva CLI globale v3.24.0 e ha eseguito `/baldart-update` per aggiornarsi a v3.27.0. La skill ha rilanciato `npx baldart update`, che ha aggiornato il framework payload da v3.24 a v3.27, ma `update.js` in esecuzione era il binario v3.24.0 — quello che precede sia il meccanismo di auto-relaunch (introdotto v3.26.0) sia la logica di symlink-indirection (v3.27.0). Risultato: i 6 agent overlay-merged sono stati rigenerati con la **vecchia logica** (file regolari direttamente in `.claude/agents/`), e quindi il fix v3.27.0 NON si è attivato — `npx baldart doctor` ha riportato "tutto healthy" ma in realtà gli agent erano ancora invisibili a Claude Code (bug #20931). Chicken-and-egg: il CLI vecchio non sa rilanciare il nuovo, il nuovo non è ancora in esecuzione.

### Fixed — /baldart-update skill: CLI version drift gate

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)** Step 0: la skill ora rileva esplicitamente il CLI version drift e HALTA pre-update quando il CLI globale è < v3.26.0 (no auto-relaunch) e il payload sta per attraversare il boundary v3.27.0 (migration breaking). Decision logic articolata: se CLI è già >= v3.26.0 lascia che l'auto-relaunch faccia il suo lavoro; altrimenti istruisce `npm i -g baldart@latest` PRIMA di ri-eseguire la skill. Evita il silent-fail dove il CLI vecchio applica una pseudo-migration con codice obsoleto.

### Note al curator

Questo è un edge case strutturale che si manifesta SOLO quando un consumer attraversa il boundary v3.26.0 / v3.27.0 partendo da un CLI globale precedente alla v3.26.0. I consumer già su CLI v3.26.0+ hanno l'auto-relaunch (`npx baldart@latest`) e non sono affected. Per i consumer pre-v3.26.0 il fix in skill aiuta solo le sessioni future (skill caricata da framework payload >= v3.27.1); chi aveva già la skill vecchia in sessione al momento dell'incidente — come `mayo` ieri — può solo riparare manualmente: `npm i -g baldart@latest` + restart sessione CC + `/baldart-update`.

## [3.27.0] - 2026-05-28

Fix critico al meccanismo di overlay-merge degli agent. Sintomo riproducibile osservato in `mayo` durante `/new FEAT-0009 -full` (team mode, claude-opus-4-7): gli agent overlay-merged (`coder`, `ui-expert`, `code-reviewer`, `doc-reviewer`, `qa-sentinel`, `security-reviewer` quando hanno un overlay) **non comparivano nella lista "Available agent types"** del Claude Code session reminder. Sequenza concreta: `codebase-architect` (symlink) parte e completa; i 4 background agent (`coder×2` + `ui-expert×2`) lanciati subito dopo falliscono con `InputValidationError: subagent_type not recognized`; l'orchestratore halta con un AskUserQuestion fuori-protocollo. Repro: `ls -la .claude/agents/` mostra 21 symlink + 6 file regolari (i 6 overlay-merged); CC discoverizza i 21 symlink e omette i 6 file regolari. Confermato su sessioni indipendenti del 2026-05-27 e 2026-05-28 → bug strutturale, non transiente. Workflow `/new` in team mode, `/check`, `/qa`, `/codexreview` e skill `prd` erano **bloccati end-to-end** su qualsiasi consumer con overlay agent.

### Root cause — Claude Code upstream bug #20931

Il session-start discovery scanner di Claude Code che costruisce la registry di `subagent_type` validi **skippa silenziosamente i file regolari** in `.claude/agents/` (solo i symlink vengono caricati). Il bug è già documentato in `framework/.claude/hooks/agent-discovery-gate.js:10-15`:

> #20931 — file-based discovery of `.claude/agents/*.md` is documented as BROKEN in some scenarios (files ignored at session start)

Dalla v3.8.0 in poi BALDART ha generato i file overlay-merged come **file regolari** direttamente in `.claude/agents/<name>.md`, intersecando esattamente la zona di rottura di CC. Tutti gli overlay agent attivi nei consumer sono stati silenziosamente invisibili per ~3 mesi, e il sintomo emergeva solo quando una skill provava ad invocare lo `subagent_type` (es. team-mode `/new`).

Diagnosi precedente erroneamente attribuita a "HTML comment prima del frontmatter rompe il parser YAML" è stata rivista: il riordino del marker post-frontmatter resta una pulizia di formato corretta ma **non era la causa primaria** del sintomo.

### Fixed — Symlink indirection per agent/command overlay-merged (root cause fix)

- **[src/utils/symlinks.js](src/utils/symlinks.js)** `_generateOverlayedFile()`: il content merged ora vive in `.baldart/generated/<kind>s/<name>.md` (file regolare), e `.claude/agents/<name>.md` (o `.claude/commands/<name>.md`) viene creato come **symlink relativo** verso quel file. Claude Code vede il path consumer come un symlink come tutti gli altri e lo discoverizza correttamente. Stesso pattern per Codex (`.agents/skills/`) tramite il tool adapter.
- **[src/utils/symlinks.js](src/utils/symlinks.js)** `_installPerItemSymlink()`: quando `hasOverlay` viene rimosso e il path consumer è un symlink che punta in `.baldart/generated/`, il CLI relinka al framework base e cancella lo stale generated file. Coerenza forte tra "overlay esiste" e "esiste un generated file".
- **[src/utils/symlinks.js](src/utils/symlinks.js)**: migrazione automatica del legacy layout. Se il consumer ha un file regolare baldart-generated direttamente in `.claude/agents/<name>.md` (pre-v3.27.0), la prossima `npx baldart update` lo riconosce dal marker, sposta il content in `.baldart/generated/agents/<name>.md` e crea il symlink. Nessun intervento manuale richiesto. User-fork (file regolare senza marker baldart) restano protetti.

### Added — Safety net SessionStart hook

- **[framework/.claude/hooks/agent-discovery-info.sh](framework/.claude/hooks/agent-discovery-info.sh)**: oltre a rilevare gli agent **mancanti** (file assente), il hook ora rileva anche gli agent in **stato degradato** — ovvero file regolari con marker `<!-- baldart-generated: -->` presenti in `.claude/agents/` (pre-v3.27.0 layout). Quando rilevati, emette `additionalContext` esplicito al modello con il nome degli agent affected, la causa (upstream bug #20931) e l'azione richiesta (`npx baldart update`). Difesa-in-profondità: il `mergeAgents` migra automaticamente e questo hook copre l'edge case in cui un consumer non lanci `update` subito dopo l'upgrade.

### Fixed — Formato file generato (cleanup secondario, non root cause)

- **[src/utils/overlay-merger.js](src/utils/overlay-merger.js)**: i file generati hanno il frontmatter YAML su riga 1 e il marker `<!-- baldart-generated: -->` subito dopo il `---` di chiusura (era prima invertito). Allinea il formato alla convenzione standard CommonMark + YAML frontmatter, indipendentemente dal fatto che CC sia o meno strict sul parser.
- **[src/utils/overlay-merger.js](src/utils/overlay-merger.js)**: nuovo export `isGeneratedFile(content)` posizione-agnostico. `readMarker()` cerca il marker in tutto il file (cap 4KB) per leggere sia file v3.27.0+ sia legacy pre-v3.27.0 (retrocompat fondamentale per la migration).
- **[framework/.claude/hooks/framework-edit-gate.js](framework/.claude/hooks/framework-edit-gate.js)**: il PreToolUse hook controlla il marker via `head.includes` su slice 5KB, così intercetta edit ai file generati sia che il marker sia su riga 1 (legacy) sia dopo il frontmatter (v3.27.0+).

### Migration path per i consumer

Su un consumer con overlay agent in layout legacy (es. `mayo`):

1. `npx baldart update` rileva la nuova VERSION e lancia `mergeAgents`.
2. Per ogni file regolare baldart-generated in `.claude/agents/`: marker letto → content spostato in `.baldart/generated/agents/<name>.md` → simlink creato.
3. `git status` mostra: `.claude/agents/<name>.md` modificato (file → symlink), `.baldart/generated/agents/<name>.md` nuovo. La `_autoCommitFn` del CLI committa con `chore(baldart): post-update reconcile to v3.27.0`.
4. **Restart della Claude Code session** richiesto perché la registry di `subagent_type` viene letta al session-start.
5. Verifica: `Available agent types` ora include tutti i nomi (sia symlink che generated-symlink). `/new` in team mode funziona.

Nessuna perdita di overlay: i file in `.baldart/overlays/agents/*.md` sono consumer-owned e non vengono toccati. Lo skill `/baldart-update` cita esplicitamente nel report post-flight gli agent migrati e ricorda di restartare la sessione CC.

## [3.26.0] - 2026-05-27

Fix definitivo del processo `baldart update` dopo che un'esecuzione reale su un consumer (`mayo`) ha esposto un workflow strutturalmente rotto. Sintomi: `update --yes` stampava "Already up to date!" mentre `version`/`doctor` riportavano correttamente "Remote: 51 commit ahead"; CLI globale (v3.18.1) silenzioso vs npm latest (v3.24.0); divergenza subtree (19 commit locali + 51 upstream) mai menzionata; output sporcato da `ExperimentalWarning` di npm a ogni invocazione; skill `/baldart-update` che narrava il workflow rotto senza accorgersene. Root cause: tre superfici (`update`, `version`, `doctor`) implementavano TRE check diversi per la stessa domanda, e nessuna era giusta — `update.js:193` usava `hasChangesToPush()` (commit LOCALI non pushati al CONSUMER's origin, niente a che vedere con BALDART upstream); `version`/`doctor` usavano `HEAD...FETCH_HEAD` full-repo che include sempre subtree-merge noise → falso positivo perenne "52 commit ahead".

### Fixed — Bug "Already up to date" su consumer con update disponibili

- **[src/utils/git.js](src/utils/git.js)** + **[src/commands/update.js](src/commands/update.js)**: nuova `getUpdateStatus(repo, branch)` SSOT basata su **VERSION compare** (`git show FETCH_HEAD:VERSION` vs `.framework/VERSION`), non sul commit count (che è strutturalmente rumoroso sul subtree). `update.js` Step 2 ora usa `status.isAligned` come segnale autoritativo. Eliminata la chiamata a `git.hasChangesToPush()` (rimane per compatibilità di `push.js`).

### Fixed — Falsi positivi "52 commit ahead / 20 unpushed" perenni

- **[src/commands/version.js](src/commands/version.js)** + **[src/commands/doctor.js](src/commands/doctor.js)**: messaging riscritto su `isAligned`. `Remote: v3.26.0 (aligned)` quando OK, `Remote: v3.26.0 — update available (installed v3.22.1)` altrimenti. Il commit count migra dietro `version --verbose` (etichettato "subtree commit history — includes auto-generated merges"). Doctor `Local changes` mostra `unpushed commit(s)` SOLO se non-aligned (altrimenti tutto subtree-noise → riga `clean`).

### Added — Classifier divergenza subtree (Fix 2)

- **[src/utils/git.js](src/utils/git.js)**: nuova `classifyDivergence()` che ispeziona i commit consumer non in upstream e li categorizza branch-agnostic:
  - `subtree-merge` (`Merge commit '*' into <any-branch>`) → auto-resolve silente.
  - `subtree-squash` (`Squashed '.framework/' changes from *`) → auto-resolve silente.
  - `chore-wrapper` (`[CHORE]` / `chore(baldart):` con `baldart` keyword nel subject o scope) → auto-resolve silente.
  - `custom-overlay-able` (modifiche SOLO a file in `.framework/.claude/{agents,skills,commands}/`) → prompt: "Migra a overlay (raccomandato)".
  - `custom-other` → prompt 3-way (push first / pull anyway / abort).
- Aggregate class `all-noise | overlay-able | real-custom | mixed | unknown`. Default conservativo `custom-other` per commit non riconosciuti — mai data loss silente.
- Test fixture-driven: **[src/utils/__tests__/classify-divergence.test.js](src/utils/__tests__/classify-divergence.test.js)** con 24 fixture inclusi i pattern commit reali osservati nel debug di `mayo` (`[CHORE] reconcile baldart state ledger v3.22.1 -> v3.25.0`, `[CHORE] register baldart agent-discovery hooks`, ecc.).

### Added — CLI self-upgrade auto-relaunch (Fix 4)

- **[src/commands/update.js](src/commands/update.js)** `maybeRelaunchUnderLatest()`: se il CLI globale è dietro npm latest, `update` rilancia trasparentemente `npx baldart@<latest> update <flags>` con `stdio: inherit` (preserva prompt). Loop guard via env `BALDART_RELAUNCHED=1` (impedisce ricorsione se npm cache serve ancora un vecchio). Fallback al CLI corrente se npx fallisce. Opt-out: `BALDART_NO_RELAUNCH=1`. Razionale rispetto a `update-notifier.js`: l'avvertenza "no auto-update globale" si riferisce a `npm i -g` silente; `npx@latest` è per-invocation, esplicito, opt-out → policy intatta.

### Added — `--reset` nuclear option (Fix 5)

- **[bin/baldart.js](bin/baldart.js)** + **[src/commands/update.js](src/commands/update.js)** `runReset()`: `npx baldart update --reset` rimuove `.framework/` e reinstalla pulito, preservando `baldart.config.yml` + `.baldart/` (overlays, state.json) + `.claude/settings.json` + custom agents/skills/commands non-symlink. Safety gate obbligatorio: working tree clean (refusal se dirty), prompt esplicito per file untracked/ignored in `.framework/` (in `--yes` richiede ALSO `--i-know`), backup tag pre-rm. Post-restore sanity check: se un file user-owned è scomparso, refuse + suggest `git reset --hard <backup-tag>`.

### Added — Soppressione `ExperimentalWarning` (Fix 6)

- **[bin/baldart.js](bin/baldart.js)**: `process.env.NODE_NO_WARNINGS = '1'` impostato ALL'INIZIO (prima di ogni `require`). Propagato a tutti i child process spawnati dal CLI (git, npx, ecc.). Verificato su Node 23.3.0: silenzia il warning. Escape hatch: `BALDART_VERBOSE_WARNINGS=1` lo riabilita. **Limite noto**: non silenzia il warning emesso da `npx` PRIMA che il nostro codice carichi — l'unico workaround è `npm i -g baldart` (modalità raccomandata) + invocazione diretta `baldart`.

### Changed — Auto-backfill hook missing senza prompt (Fix 7)

- **[src/commands/update.js](src/commands/update.js)** logging: hook missing aggregati in una singola linea (`Auto-registered N missing hook(s): a, b, c`) invece di una per hook. La logica `Hooks.registerAll` era già corretta (auto-create silenzioso per missing, prompt solo per drift) — il cambio è cosmetico ma riduce noise post-update.

### Changed — Skill `/baldart-update` parser tollerante + post-flight assert (Fix 9)

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: Step 0 parser tollerante con 5 pattern in ordine (nuovo format `Remote: vX.Y.Z (aligned)`, format intermedio `update available`, legacy `N commit(s) ahead` / `up to date`, offline) — gestisce la finestra di transizione skill v3.26.0 + CLI globale v3.18.1. Se nessun pattern matcha → istruisce `npm i -g baldart@latest`. Eliminato Step 3 (hooks drift — ora auto-backfill in CLI) e Step 4 (auto-commit prompt — sempre yes in `--yes`). Step 6 ora include **post-flight assert obbligatorio**: confronta `.framework/VERSION` pre/post, FAIL LOUD se exit 0 ma VERSION invariata (safety net contro regression del Fix 1).

### Changed — Doctor planner usa version-compare authority

- **[src/commands/doctor.js](src/commands/doctor.js)** `planActions()`: `remoteAhead = state.remote.fetched && state.remote.isAligned === false`. Label dell'action `update` ora `Update framework (vX.Y.Z → vA.B.C)` invece di `Pull N commit(s) from upstream`. Local-work detection sopprime commit count quando aligned.

### Internal

- Nessuna nuova chiave in `baldart.config.yml`. Schema-change propagation rule non applicabile.
- `getRemoteVersion()` esistente non più consumato — VERSION compare ora dentro `getUpdateStatus()` via `git show FETCH_HEAD:VERSION`. La funzione resta per back-compat (status.js la chiama).
- `hasChangesToPush()` resta in `git.js` per `push.js` che la usa correttamente (consumer's origin commits to push upstream).

## [3.25.0] - 2026-05-27

Strict-phase-gating + workspace-hygiene gates per `/new`. Risolve l'incident **FEAT-0006**: epic team-mode (4-layer L0→L1×5→L2×2→L3×2, 10 sub-card) che ha saltato per ogni card le fasi MANDATORY 2.5b (AC-Closure), 2.55 (Simplify), 2.6 (E2E-Review), 3 (Doc-Review), 3.5 (QA-Sentinel), 3.7 (Codexreview) — giustificazione documentata come "time budget", parola **inventata dal modello a runtime**, non esistente nella skill. In aggiunta, AC-IMG-3 "deferred dal coder 09" senza passare dal gate, e main repo lasciato con orphan commit non pushato + local `develop` diverged. Tre root cause radicate: (1) Phase 2.5b non era propagata in team-mode Step D, (2) coder `completion-report` accettava `status: partial`/`blocked` come terminale, (3) zero gate di workspace hygiene su `$MAIN`.

### Added — Phase 0: Workspace Hygiene Pre-flight (BLOCKING)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: nuova `## Phase 0` prima di `## Pre-flight (once)`. Su `$MAIN`: `fetch origin`, dirty-tree gate (`git status --porcelain` → `AskUserQuestion` con opzioni `[stash auto] / [commit ora] / [procedi senza stash] / [abort]`), divergence gate (`rev-list --left-right --count` → `AskUserQuestion` per ahead/behind/diverged). Stash ref salvato nel tracker sotto `## Workspace Snapshot` per il restore di Phase 6c. Anti-bypass guard finale che rifiuta di proseguire senza `## Phase 0: PASS` nel tracker. Auto Mode NON override.

### Added — Phase 6c: Workspace Hygiene Post-merge (BLOCKING)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: nuova `### Phase 6c` dopo Phase 6b. Parse del marker `[SYNC-DEFERRED]` emesso da `/mw`, clean-tree assertion, divergence assertion (cattura il pattern FEAT-0006 orphan commit con `AskUserQuestion` opzioni `[push adesso] / [cherry-pick] / [reset --hard con conferma esplicita] / [halt]`), restore dello stash di Phase 0 (con `AskUserQuestion` su conflict, mai silent drop). Anti-bypass guard come in Phase 0.

### Added — Team-mode Step D rewrite (FEAT-0006 anti-regression)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** righe ~1988: nuovo Step D con clausola di apertura esplicita ("La lean Step D è ora FORBIDDEN") + sub-step MANDATORY per-card propagati identici al sequential mode:
  - **D.3a** — Phase 2.5b AC-Closure Gate per-card (sequenziale), bloccante.
  - **D.3b** — Phase 2.55 Simplify per-card (diff per-card per attribuibilità).
  - **D.3c** — Phase 2.6 E2E-Review per-card (honora il Gate table esistente — skip backend-only/docs/chore con motivazione documentata, mai con "time budget").
  - **D.4a** — Phase 3 Doc Review per-card (oltre al D.2 combined).
- **Coverage assertion** end-of-group MANDATORY: prima di Step E, il tracker deve contenere per OGNI card le entry `AC Closure Ledger`, `simplify`, `e2e-review`, `doc-review`, `qa`, `codex-review`. Entry mancante → ritorno alla sub-step mancante. Documentare "skipped per time budget" è violazione esplicita.

### Added — Top-of-file rigidity clause + tracker header

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: nuovo blockquote `> **NO PHASE SKIP FOR PERCEIVED TIME, EVER (BLOCKING)**` subito sotto SCOPE CLOSURE DISCIPLINE. Enumera esplicitamente le phrase forbidden (`time budget`, `context budget`, `context pressure as override`, `for time`, `to save tokens`, `to save iterations`). Self-correction trigger: se il modello produce una di queste phrase nel proprio output, deve fermarsi e routare via `AskUserQuestion`.
- **Tracker template** (~riga 50): HTML comment header che sopravvive alle compaction e ricorda la regola dopo ogni context recovery.

### Changed — `coder` agent completion report (semantica binaria)

- **[framework/.claude/agents/coder.md](framework/.claude/agents/coder.md)**: card-level `status:` enum ridotto a `done | failed` (era `done | partial | failed`). Per-requirement e per-AC `status:` ridotto a `done | not_implemented` (era `done | partial | blocked`). Le legacy values `partial`/`blocked` erano usate come canali di silent deferral. Aggiunta a `Forbidden Actions`: "Don't defer acceptance criteria" + "Don't invent skip reasons". Retro-compatibile: coder che emettono ancora `partial` faranno triggerare il gate Phase 2.5b invece di passare in silenzio.

### Changed — `framework/agents/workflows.md` Scope Closure Discipline

- Nuovo `### Coder authority (no defer)` in coda alla sezione: chiarisce che coder e orchestratori L0/L1 relay-ano i signal nel gate, non li aggregano/filtrano. Citable da skill e agent.

### Changed — `/mw` `[SYNC-DEFERRED]` structured marker

- **[framework/.claude/skills/worktree-manager/SKILL.md](framework/.claude/skills/worktree-manager/SKILL.md)** righe ~835–855: quando `$MAIN` HEAD ≠ develop, oltre al `fetch` esistente emette ora `[SYNC-DEFERRED] main repo HEAD=<branch>, ...` su stdout. `/mw` standalone preserva l'isolamento terminale (no auto-checkout); orchestratori upstream (Phase 6c di `/new`) parsano il marker e routano la riconciliazione via user gate.

### Why MINOR (not MAJOR)

Tutte le fasi MANDATORY esistevano già — vengono solo propagate (team-mode Step D) e rese non-bypassabili. Le nuove Phase 0/6c sono additive bloccanti, non rimuovono né rinominano capability. La semantica del `completion-report` cambia ma è retro-compatibile: coder pre-3.25 che emettono `partial`/`blocked` triggerano il gate invece di passare silently, e il gate ora esiste in team-mode. Per la decision tree in MAINTAINING.md "Tightened existing MANDATORY gates without removing capabilities → MINOR". L'incident FEAT-0006 era un fallimento di propagazione, non di design — la fix riallinea l'implementazione all'intent originale di v3.24.0.

### Why no schema-change propagation

Modifiche testuali a 4 file framework. Zero chiavi nuove in `baldart.config.yml`, zero modifiche CLI (`src/commands/*`), zero hook nuovi, zero adapter, zero template. Considerato e scartato `policy.strict_phase_gating: true`: sarebbe 5 punti di propagazione (template + configure + update + skill + CHANGELOG) per zero beneficio tunable dall'utente — gli overlay `.baldart/overlays/new.md` restano la via per personalizzazioni progettuali. Verifica: `grep -r "policy.strict_phase_gating\|features.strict_phase_gating" framework/ src/` deve restituire vuoto.

## [3.24.0] - 2026-05-27

Nuovo gate **AC-Closure** (Phase 2.5b) in `/new` + regola **Scope Closure Discipline** in `framework/agents/workflows.md`. Risolve il pattern ricorrente in cui l'orchestratore — sotto pressione di context-window o complessità inattesa — defer-iva unilateralmente acceptance criteria numerate marcandole `deferred` negli `implementation_notes` YAML con razionalizzazione post-hoc ("ApproveSheet già copre il confirm"). Caso reale che ha innescato la fix: sessione `/new` su FEAT-0005 (2026-05-27) dove 5 AC su 22 sono state silenziosamente deferite con razionalizzazione poi rivelata falsa alla verifica funzionale. Le decisioni di deferral sono ora **sempre routate all'utente via `AskUserQuestion`** una-per-AC.

### Added — Phase 2.5b AC-Closure Gate (BLOCKING)

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: nuova Phase 2.5b inserita tra Phase 2.5 (Completeness Check) e Phase 2.55 (Simplify). 6 step:
  1. **AC Closure Ledger** — tabella esplicita una-riga-per-AC con status enum binario `implemented` | `deferred` (nessuna terza categoria — l'AC è nel diff o sta venendo deferita), evidence `file:line`, campo `User-approved?`.
  2. **Rationalization scan** — per ogni AC `deferred`, se la giustificazione matcha pattern `covered by X` / `already handles` / `redundant with Y` / `out of scope`, l'orchestratore DEVE leggere `X/Y` e produrre una scope-mapping line-per-line nel tracker sotto `## Rationalization Verification`. Il pattern senza verifica NON auto-approva il deferral.
  3. **User gate** — `AskUserQuestion` BLOCKING uno-per-AC (mai batched), con testo AC verbatim + 4 opzioni: implementa adesso (spawna fix coder scoped al ownership map) / approva deferral (registra `[USER-APPROVED DEFERRAL] <date>: <reason>` in `implementation_notes`) / sposta su follow-up card (crea `<CARD-ID>-followup-AC<N>.yml` con `status: TODO`) / ferma il batch.
  4. **`implementation_notes` audit** — grep finale per phrase `deferred` / `out of scope` / `covered by` / `skipped` etc.; ogni match deve carry il prefisso `[USER-APPROVED DEFERRAL]` o tornare allo Step 3.
  5. **Context-pressure exception** — directive esplicita: sotto context pressure si ferma e si chiede, NON si compensa skippando AC. Auto Mode's "bias toward proceeding" non override questa regola.
- **Top-level directive** in `/new` SKILL.md (riga 13, subito dopo YOLO MODE banner): riassume la Scope Closure Discipline e linka workflows.md.

### Added — Scope Closure Discipline rule (citable protocol)

- **[framework/agents/workflows.md](framework/agents/workflows.md)**: nuova sezione `## Scope Closure Discipline (MUST)` tra "Status Tracking" e "Deploys and Publications". 3 violazioni esplicite (silent deferral, rationalization-without-verification, scope reduction under context pressure) + protocollo di enforcement via `AskUserQuestion`. Citable da skill/agent come autorità — un finding "silent AC deferral" è BLOCKING in code review.

### Why MINOR (not PATCH)

Aggiunta di capability: nuovo gate obbligatorio in `/new` che cambia comportamento osservabile per i consumer (Phase 2.55 non parte più finché Phase 2.5b non passa). Nessun breaking change — card senza AC-deferral passano il gate transparently in <1s con `ac-closure: implemented=N | deferrals=0 | follow-ups=0`. Per la decision tree in MAINTAINING.md "Added an agent / command / skill / routine / template (or capability)? → MINOR".

### Why no schema-change propagation

Modifiche testuali a due file framework. Zero chiavi nuove in `baldart.config.yml`, zero modifiche CLI (`src/commands/*`), zero hook, zero adapter, zero template. Il gate sfrutta strutture YAML già esistenti (`acceptance_criteria`, `implementation_notes`) e `${paths.backlog_dir}` già nella config schema da v3.0.0.

## [3.23.0] - 2026-05-27

Aggiunto il flag `-full` / `--full` a `/new` per espandere un epic ai suoi figli in un colpo solo — `/new FEAT-005 -full` lancia automaticamente `FEAT-005-1`, `FEAT-005-2`, … senza domande di grouping né di branch. Risolve il pattern ricorrente "ho un epic con 5 child card, le voglio tutte ora": prima richiedeva di listarle a mano (`/new FEAT-005-1 FEAT-005-2 FEAT-005-3 …`) o di usare l'hyphen-range solo se gli ID erano numericamente contigui.

### Added — `/new` epic expansion flag

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)**: nuova regola di parsing `Epic expansion (-full / --full)` nella sezione args. Discovery dei figli via union di due rules: (1) filename in `${paths.backlog_dir}` con prefisso `<PARENT-ID>-` (separator letterale, quindi `FEAT-005-1.yml` matcha ma `FEAT-005.yml` e `FEAT-0050.yml` no); (2) cards con `group.parent: <PARENT-ID>`. Il parent stesso è escluso (override esplicito: `/new FEAT-005 FEAT-005 -full`). Ordinamento per `group.sequence` ASC, fallback lessicografico. Batch-scoped: `/new FEAT-005 FEAT-008 -full` espande entrambi gli epic. Zero figli trovati → HALT esplicito (no silent no-op). Mixed batches supportati: `/new FEAT-005 -full FEAT-007` accoda i figli di 005 + FEAT-007 standalone.
- **[framework/.claude/commands/new.md](framework/.claude/commands/new.md)**: versione condensata della stessa regola, allineata al SKILL canonico.

### Why MINOR (not PATCH)

Capability addition pura: nuovo flag, nessuna modifica al comportamento esistente. Tutti gli invocation pattern pre-3.23 continuano a funzionare identici. Per la decision tree in MAINTAINING.md "Did you add new functionality? → YES → MINOR".

### Why no schema-change propagation

Modifica testuale a due file di skill/command. Zero chiavi nuove in `baldart.config.yml`, zero modifiche CLI (`src/commands/*`), zero hook, zero adapter. La discovery dei figli sfrutta `${paths.backlog_dir}` già presente nella config schema da v3.0.0.

## [3.22.1] - 2026-05-27

Rimosso il gate `AskUserQuestion` di conferma merge introdotto in v3.22.0 Step 7 — era friction inutile contro il "seamless" che è il punto della release. Quando l'utente lancia `/prd` ha già implicitamente accettato la pipeline completa (discovery → design → cards → commit → merge in develop). Chiedere "Procedo con merge?" a fine sessione equivale a chiedere "sei sicuro di voler completare quello che mi hai chiesto di fare?".

### Changed — `/prd` Step 7 merge è ora full-seamless

- **[framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md)**: Step 7 point 9 non è più un `AskUserQuestion` ma una nota che chiarisce il razionale del no-confirmation flow + escape hatch user-initiated ("non mergiare" / "lascia il worktree" honora l'override e ferma dopo il commit).
- **[framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md)** HARD RULE 17 lifecycle: la descrizione di Step 7 cita "Seamless by default, no confirmation gate" + l'escape hatch.
- **[framework/.claude/skills/prd/references/discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md)**: il messaggio di kickoff visibile all'utente promette esplicitamente "commit + merge automatico in develop + cleanup worktree, seamless" — niente più sorprese a fine sessione.

### Why PATCH and not MINOR

Rimozione di un comportamento (gate di conferma) introdotto solo 0 minuti fa nella stessa giornata. Nessuna nuova capability, nessun breaking change per consumer: il gate non era una feature documentata né attesa dagli utenti pre-v3.22.0, era una sovra-cautela introdotta da me contro la richiesta esplicita ("seamless"). La decision tree di MAINTAINING.md classifica "bugfix di behaviour non desiderato senza breaking change" come PATCH.

### Why no schema-change propagation

Modifica puramente comportamentale dentro un singolo step di una singola skill. Zero chiavi nuove, zero modifiche CLI, zero hook.

## [3.22.0] - 2026-05-27

Le sessioni `/prd` ora vivono in un **docs worktree** dedicato, esattamente come fa `/new` per le card di codice. Il problema risolto: con 5-10 sessioni Claude in parallelo, ogni PRD inquinava il main checkout con `docs/prd/<slug>/PRD.md`, lo state file, le card YAML, l'eventuale `design.html`, e gli update a `ssot-registry.md` / `project-status.md`. `git status` diventava un campo minato, le sessioni `/new` rischiavano di stashare/committare file PRD non loro, e la review manuale era pressoché impossibile. Soluzione: estendere `worktree-manager` con una **modalità docs lean** (no `npm install`, no porta, no `.env` copy, no build verify, no lint/tsc gates) e integrarla nel ciclo di vita del PRD — creazione automatica al kickoff, merge automatico (previa conferma utente) al termine, cleanup seamless.

### Added — `worktree-manager` programmatic docs mode

- **[framework/.claude/skills/worktree-manager/SKILL.md](framework/.claude/skills/worktree-manager/SKILL.md)**: nuova sezione "Programmatic API — docs mode" con due funzioni:
  - **`nw-docs`** — input `{ slug, branchPrefix? }`, output `{ path, branch, kind: "docs" }`. Esegue solo `git fetch origin develop` + `git worktree add .worktrees/prd-<slug> -b prd/<slug> origin/develop` + entry nel registry con `kind: "docs"`, `port: null`, `buildVerified: null`. Target di costo: < 5 secondi (vs minuti per `/nw` standard a causa di `npm install`).
  - **`mw-docs`** — input `{ worktreePath, commitMessage? }`, output `{ merged, mergeCommit, prNumber, strategy, worktreeRemoved }`. Esegue safety commit + rebase su `origin/develop` (con il protocollo conflict-resolution doc-friendly esistente in `/mw` step 4b) + merge via `git.merge_strategy` configurato (`pr` o `local-push`) + cleanup worktree/branch. **Salta** i gate inapplicabili a un worktree di docs: `npm run build`, `npx eslint`, `npx tsc`, `npm run test`, post-merge build verify.
- Forme **FORBIDDEN** documentate esplicitamente: niente `npm install`/port scan/`.env*` copy/build in `nw-docs`; niente lint/tsc/build gates in `mw-docs`. Il senso della modalità lean è proprio non eseguirli.
- Registry condiviso con i code worktrees (`.worktrees/registry.json`) — `/lw` e `/cw` mostrano docs worktrees accanto ai code worktrees, distinti dal campo `kind`.

### Changed — `worktree-manager`: rebase conflict protocol hardened

- **[framework/.claude/skills/worktree-manager/SKILL.md](framework/.claude/skills/worktree-manager/SKILL.md)** — la tabella di conflict-resolution dello step 4b di `/mw` (riusata verbatim da `mw-docs`) è stata estesa con due categorie nuove **prima** della catch-all "Docs/config":
  - **Structured registries** (`project-status.md`, `ssot-registry.md`, `field-registry.*`, `traceability-matrix.md`, `REGISTRY.md`) → **STOP**: abort rebase, report all'utente. Senza questa riga, due PRD paralleli che toccano `project-status.md` farebbero auto-strip dei marker di conflitto producendo file con sezioni duplicate e tabelle malformate. Il fix vale anche per `/mw` su code worktrees — qualsiasi card che modifica una di queste registry ora chiede risoluzione manuale invece di corromperle silenziosamente.
  - **Append-only logs** (`**/*.jsonl` sotto `docs/metrics/`) → auto-resolve "keep both sides", che per JSONL è semanticamente corretto (file line-oriented additivo). Questo permette al Metrics Log del PRD (vedi sotto) di sopravvivere a merge concorrenti senza intervento.

### Changed — `/prd` skill: HARD RULE 17 (worktree isolation)

- **[framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md)**: nuova HARD RULE 17 obbligatoria — ogni sessione PRD apre un docs worktree allo Step 1 prima di scrivere qualunque file, e committa/mergia tutto allo Step 7 prima di terminare. La regola include:
  - **Path resolution explicit rule**: tutti i `Write` / `Edit` / `Read` di Claude Code DEVONO prefissare i path con `$WORKTREE_PATH` (Claude Code richiede path assoluti — senza prefisso esplicito i file finirebbero nel main repo, vanificando l'isolamento). Esempio `right vs wrong` nel testo della regola.
  - **Forbidden operations**: nessun `git checkout`/`switch`/`branch` sul main, nessun `git stash` (refs/stash è condiviso fra worktree), nessuna scrittura diretta sul main repo durante la sessione.
  - **Failure handling**: se `nw-docs` fallisce (e.g. `.worktrees/` non in `.gitignore`), la skill DEVE fermarsi e segnalare — niente fallback silenzioso al main repo.
- **HARD RULE 9 aggiornata**: il session-start update di `project-status.md` viene **skippato** quando la sessione gira in un docs worktree (sarebbe invisibile alle altre sessioni). L'update at-commit resta attivo, eseguito nel worktree come ultima scrittura prima del rebase + merge.
- **Step 1 (Kickoff)** in [discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md): aggiunto step 3 che chiama `worktree-manager nw-docs` subito dopo aver derivato lo `<slug>`, prima di creare lo state file. Output kickoff aggiornato per mostrare path/branch del worktree all'utente.
- **Step 0 (Context Recovery)** in [discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md): scan iniziale di `.worktrees/registry.json` per entries con `kind: "docs"` che matchano lo slug — se trovate, `cd` nel worktree e riprendi. Gestito anche il caso "state file legacy nel main da pre-v3.22.0" (chiede all'utente se migrare o continuare in legacy mode).
- **Step 7 (Resolution, Commit & Merge)** in [validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md) — rinominato da "Resolution and Commit" e riscritto end-to-end:
  - Update di `ssot-registry.md` / `project-status.md` avviene nel worktree (skip se file inesistente).
  - Commit con `git -C "$WORKTREE_PATH" commit` (file espliciti, mai `git add .`).
  - **Gate di conferma esplicito** (`AskUserQuestion`) prima del merge: "Procedo con merge automatico in `develop` e cleanup del worktree?". Opzione "lascia il worktree" per review manuale o test.
  - Sulla conferma: invocazione `worktree-manager mw-docs` che rebase + merge + cleanup. Failure handling: il worktree resta su disco, niente retry automatico.
- **[framework/.claude/skills/prd/assets/state-template.md](framework/.claude/skills/prd/assets/state-template.md)**: nuova sezione `## Worktree` (path, branch, kind, created_at, merge_commit).
- **Metrics Log riposizionato dentro il worktree.** La sezione "Metrics Log" di SKILL.md scriveva precedentemente in `docs/metrics/skill-runs.jsonl` con risoluzione del path rispetto al main repo root — incompatibile con HARD RULE 17 e con isolamento parallelo. Ora scrive in `$WORKTREE_PATH/docs/metrics/skill-runs.jsonl` come ultimo append PRIMA del commit di Step 7; il file è incluso nella lista di stage esplicita e il conflict protocol di `mw-docs` lo riconosce come append-only (vedi categoria "Append-only logs" sopra). Risultato: due PRD paralleli che terminano insieme producono un append di entrambe le righe nel file su `develop`, senza intervento manuale.

### Why MINOR and not PATCH

Nuova capability portata da una skill esistente (`worktree-manager` guadagna l'API docs mode) + cambio di workflow per `/prd` (gira sempre in worktree). Nessun breaking change per i consumer: le sessioni PRD esistenti pre-3.22.0 sono detettate in Step 0 e gestite con prompt di migrazione (legacy mode resta supportato). La decision tree di `MAINTAINING.md` classifica "added capability" come MINOR.

### Why no schema-change propagation

Zero nuove chiavi in `baldart.config.yml`. La modalità docs riusa:
- `git.merge_strategy` (già esistente da v3.9.0) per il merge — `pr` o `local-push`, rispetta la scelta del progetto.
- `paths.prd_dir`, `paths.backlog_dir`, `paths.references_dir` (esistenti) — risolti relativamente al worktree (HARD RULE 17).
- `features.has_prd_workflow` (esistente) — gating della skill.

Nota CHANGELOG-only (non normativa): per i PRD molti consumer preferiscono `merge_strategy: local-push` (no PR per documentazione). Chi vuole differenziare la strategia per skill può farlo via overlay `.baldart/overlays/prd.md` — la skill non impone scelte.

### Pre-release code review fixes (within v3.22.0)

Un `/code-review` high-effort eseguito prima del tag ha individuato 10 bug — tutti corretti in questa stessa release prima del rilascio. I più gravi:

- **`prd-card-writer` agent + `backlog-phase.md` invocation**: il subagent scriveva le card a `backlog/` consumer-root-relative senza alcuna `$WORKTREE_PATH` awareness — Step 5 del PRD spruzzava le card nel main repo, vanificando l'isolamento. Fix: nuova sezione "Working Directory (MANDATORY)" nel file dell'agent + il prompt da `backlog-phase.md` ora passa `WORKING_DIRECTORY` esplicito (regola di substitution documentata).
- **`ui-design/SKILL.md` Step G**: copia `design.html` a `${paths.prd_dir}/<slug>/design.html` consumer-root-relative. Fix identico: sezione "Working Directory" + `ui-design-phase.md` ora passa `WORKING_DIRECTORY=$WORKTREE_PATH` come arg.
- **`/prd-add` Step 0**: listava `${paths.prd_dir}/sessions/` sul main repo, zero scan del registry. Fix: Step 0 riscritto — scansiona `.worktrees/registry.json` per `kind: "docs"` prima di guardare il main; gestisce sia worktree-mode che legacy; documenta gli effetti collaterali downstream (delta commit nel worktree, sub-agents ri-invocati con `WORKING_DIRECTORY`, no auto-merge per CR).
- **Regressione `/new` introdotta dal conflict protocol hardening**: la prima versione della tabella step 4b in `worktree-manager` abortiva blanket il rebase su `ssot-registry.md` / `project-status.md`, ma `/new` AGGIORNA `ssot-registry.md` per ogni card (line 1131 di `framework/.claude/skills/new/SKILL.md`, hook pre-commit che blocca commit a `backlog/` senza ssot). Risultato: ogni batch multi-card di `/new` sarebbe fallito al rebase. Fix: sostituito il blanket abort con **structural validation** (`validate_structured_md` helper) — strip markers + check struttura (no marker residui, no header table duplicati, no frontmatter doppio). Se valido → accept (caso routine: append additivo); se invalido → abort (caso raro: due card hanno modificato la stessa riga). Restituisce a `/new` il comportamento additivo originale, mantiene la protezione contro corruzione reale.
- **`git stash` in `mw-docs`**: la prima versione riusava verbatim `/mw` step 4b che fa `git stash push --include-untracked` prima del rebase. Violazione diretta della Safety Rule "NEVER use git stash in worktrees" (refs/stash condiviso fra worktree — incidente FEAT-0522). Fix: la sezione `mw-docs` ora esplicita "NO stash" — il safety commit dello step 1 garantisce working tree clean, qualunque dirty post-safety è un errore che richiede STOP, mai uno stash di recovery.
- **`nw-docs` mancante `.gitignore` check**: la Safety Rule generale dice "Always verify `.worktrees/` is in `.gitignore` before creating", ma `nw-docs` la omette. Fix: step 0 di `nw-docs` è ora un pre-flight `.gitignore` check che fallisce con errore esplicito se mancante.
- **`nw-docs` pseudo-bash relativo invece che assoluto**: il path ritornato come `path` doveva essere assoluto (per HARD RULE 17), ma lo pseudo-code lo settava relativo. Fix: ancorato a `$MAIN_ROOT` esplicito via `git rev-parse --show-toplevel` + `WORKTREE_PATH="$MAIN_ROOT/$WORKTREE_REL"`.
- **Esempio HARD RULE 17 fuorviante**: l'esempio "RIGHT" mostrava `Write file_path = "$WORKTREE_PATH/..."` come stringa literal. Claude Code NON espande shell variables → o errore "path must be absolute" o creazione di una dir letterale `$WORKTREE_PATH/`. Fix: esempio riscritto con 3 forme "wrong" (main repo, literal var, relative) + 1 "right" (substitution textuale dell'absolute path).
- **Off-by-one cross-ref Metrics Log**: "Add the file to Step 7 point 5" ma il staging è point 6 (point 5 è il Metrics Log step stesso, self-referential). Fix: cross-ref corretto a point 6 con nota di disambiguazione.
- **Case pattern `*.jsonl` non raggiungibile per il secondo arm + auto-strip non desiderato per fixture**: `*.jsonl|docs/metrics/*.jsonl` matchava ogni `.jsonl` ovunque (anche fixture/seed). Fix: tabella sostituita con pattern espliciti — `docs/metrics/*.jsonl` auto-resolve, altri `*.jsonl` abort + manuale.
- **Numerazione stale "Step 1, point 7"** in `discovery-phase.md` § 1.6: dopo la renumerazione, point 7 è "Context Loading", non l'output kickoff. Fix: aggiornato a "point 9 (Business Rationale Extraction is the final point)".

### Verification

Modifiche applicate ma non testabili automaticamente (nessuna test suite in questo repo, come da `CLAUDE.md`). Validazione richiesta in un consumer reale:
1. Lanciare `/prd` su una feature qualunque → verificare la creazione `.worktrees/prd-<slug>/` + entry `kind: "docs"` nel registry.
2. Aprire una seconda sessione Claude in parallelo e fare `git status` sul main → deve essere pulito.
3. Procedere fino allo Step 7 → verificare il prompt di conferma merge, completare → worktree rimosso, branch eliminato, PRD su `develop`.
4. Resume test: chiudere/riaprire la sessione a metà PRD → Step 0 deve trovare il worktree dal registry e `cd` lì.
5. `/prd-add` su PRD attivo → deve riusare il worktree esistente, non crearne uno nuovo.

## [3.21.2] - 2026-05-26

Aggiunto un livello di automazione che intercetta il drift fra la skill `/baldart-update` (v3.21.0+) e le sorgenti CLI che essa replica in chat. La skill replica **5 decision point fissi** del comando `npx baldart update`; quando una release tocca `src/commands/update.js` (nuovi prompt), `src/utils/hooks.js` (nuove entry in `HOOK_REGISTRY`), o `framework/templates/baldart.config.template.yml` (nuove top-level key), la skill può silenziosamente disallinearsi. Il bug v3.21.0 → v3.21.1 (`git -C .framework fetch` errato) è già stato un esempio del problema: la skill prometteva un pattern che il test live ha smentito. Senza un meccanismo di alert, futuri drift sarebbero individuabili solo per accidente.

### Added — Drift detection script

- **[scripts/check-update-skill-drift.js](scripts/check-update-skill-drift.js)**: Node script standalone (no dipendenze) che misura:
  - Numero di prompt interattivi in `src/commands/update.js` (regex `await confirm(` + `inquirer.prompt(`).
  - Numero di entry attive in `HOOK_REGISTRY` di `src/utils/hooks.js` (linee `id: 'baldart-...'` non commentate).
  - Elenco delle top-level key in `framework/templates/baldart.config.template.yml`.
  Confronta con i valori attesi salvati nel marker `<!-- DRIFT-CHECK ... -->` dentro lo SKILL.md della skill, e stampa un report human-readable. Exit code sempre 0 — drift è warning, non errore: la decisione finale resta umana.

### Added — Marker DRIFT-CHECK nello SKILL.md

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: HTML comment in cima (subito dopo il frontmatter) con i conteggi/key attesi e il campo `last_verified` (versione in cui l'allineamento è stato confermato). Il marker è invisibile al consumer (HTML comment) ma viene letto dallo script.

### Added — GitHub Actions workflow

- **[.github/workflows/check-update-skill-drift.yml](.github/workflows/check-update-skill-drift.yml)**: workflow CI triggered su tre eventi:
  - `push tags: v*.*.*` (ogni release).
  - `pull_request` su `main` con paths filter sui file rilevanti (update.js, hooks.js, config template, SKILL.md, drift script).
  - `workflow_dispatch` (lancio manuale).
  Esegue lo script e annota eventuali drift come GitHub warnings (`::warning::`). Job non blocca mai il merge / la release.

### Schema change propagation

Nessuna nuova chiave in `baldart.config.yml`, nessuna modifica al CLI. La modifica è puramente CI/dev tooling.

### Versioning rationale

PATCH (3.21.1 → 3.21.2) per la decision tree di [MAINTAINING.md](MAINTAINING.md): tooling interno, nessun cambio di behaviour CLI, nessun consumer-facing impact. Il marker HTML nello SKILL.md è invisibile al consumer.

### Verification

Eseguito localmente: `node scripts/check-update-skill-drift.js` riporta baseline attuale `update_js_prompts=6, hook_registry_entries=3, config_template_top_level_keys=[features, git, identity, lsp, paths, stack, tools, version]` e "No drift detected" — coerente col marker `last_verified: v3.21.2`.

## [3.21.1] - 2026-05-26

Fix del Step 0 e Step 1 della skill `/baldart-update` introdotta in v3.21.0. La prima release usava `git -C .framework fetch origin main` per il pre-flight check del numero di commit ahead — pattern **sbagliato** perché `.framework/` è un git subtree all'interno del repo consumer, non un git repo separato: i comandi `git` eseguiti con `-C .framework` ricadono sul `.git` del consumer e fetcherebbero il remote del CONSUMER (es. `github.com/antbald/mayo`), non quello di BALDART. Bug individuato durante test live su `mayo` (l'output mostrava `Da https://github.com/antbald/mayo  * branch  main -> FETCH_HEAD` invece di BALDART).

### Fixed — Pattern git corretto nella skill `/baldart-update`

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: Step 0 ora delega completamente a `npx baldart version` per ottenere installed version, commit ahead count, e BALDART repo URL — nessuna git operation a mano. Step 1 usa il pattern corretto del CLI (`src/utils/git.js`): `git fetch <BALDART-repo-url> main && git log HEAD..FETCH_HEAD --oneline -- .framework/`, dove l'URL viene estratto dal campo `Repository:` di Step 0. Aggiunta callout box dopo la tabella dei 5 decision point con la regola esplicita: *"do NOT use `git -C .framework fetch`"*, motivazione (`.framework/` è subtree, non repo separato), e pattern corretto.

### Schema change propagation

Nessuna nuova chiave in `baldart.config.yml`. Nessun cambiamento al CLI.

### Versioning rationale

PATCH (3.21.0 → 3.21.1) per la decision tree di [MAINTAINING.md](MAINTAINING.md): bug fix interno alla skill, nessun cambio di behaviour del CLI, nessuna nuova capability, nessun cambio di directory layout o install command.

### Verification (manuale)

Eseguita in `~/mayo`:

1. Test del comando originale ERRATO: `git -C .framework fetch origin main` → l'output mostrava `Da https://github.com/antbald/mayo` (remote di mayo, NOT BALDART) — bug confermato.
2. Test del pattern CORRETTO: `npx baldart version` espone `Repository: https://github.com/antbald/BALDART`. `git fetch https://github.com/antbald/BALDART.git main && git log HEAD..FETCH_HEAD --oneline -- .framework/` rileva correttamente i commit BALDART ahead del subtree merge nel consumer.

## [3.21.0] - 2026-05-26

Fino a v3.20.x l'aggiornamento di un consumer all'ultima versione del framework passava da due strade: **CLI diretta** (`npx baldart update`, interattivo, con 5 prompt nativi gestiti dall'utente in TTY) oppure **chat ad hoc** (l'utente chiede a Claude di aggiornare, e Claude — senza protocollo standardizzato — improvvisa ogni volta, gestendo gli edge case in modo non riproducibile). La prima opzione è solida ma richiede di lasciare la chat e tornare al terminale; la seconda è comoda ma fragile: silenzia decisioni che il CLI farebbe esplicitamente.

Questa release introduce la skill `/baldart-update` come **wrapper conversazionale agent-driven** di `npx baldart update`, complementare a `/baldart-push`. La skill **replica in chat ognuno dei 5 decision point nativi del CLI** prima di lanciare il comando con `--yes` — non per silenziarli, ma perché ogni prompt è già stato risposto in chat. Il CLI resta source of truth: la skill non re-implementa la logica di `update.js`, la guida e la riporta.

### Added — Skill `/baldart-update`

- **[framework/.claude/skills/baldart-update/SKILL.md](framework/.claude/skills/baldart-update/SKILL.md)**: nuova skill (~220 righe) modellata strutturalmente su [/baldart-push](framework/.claude/skills/baldart-push/SKILL.md) ma con divergenza intenzionale sul modello esecutivo: `/baldart-push` è pure-guidance (l'utente lancia il CLI in TTY), `/baldart-update` è agent-driven con conferme replicate in chat. La skill:
  - **Pre-flight read-only** (Step 0): legge `.framework/VERSION` e conta i commit ahead con `git -C .framework rev-list HEAD..origin/main --count`. Se 0 → "Already up-to-date", stop senza lanciare il CLI.
  - **Replica 5 decision point** (Step 1–4) — surface in chat di: preview diff (delta versione + commit message), working tree dirty + stash automatico in ref `baldart-pre-update-<timestamp>`, hooks drift confrontato con `HOOK_REGISTRY` di `.framework/src/utils/hooks.js`, schema config drift confrontato con `.framework/framework/templates/baldart.config.template.yml`, auto-commit classification (BALDART-managed vs user-owned per path-prefix matching su `.framework/`, `.claude/agents/`, `.claude/skills/`, `.claude/commands/`, `AGENTS.md`, `agents/`).
  - **Run CLI** (Step 5): `Bash` con `timeout: 600000` (10 min — il default 120s è insufficiente per subtree pull + symlink reconcile + post-update wizard + hooks register + auto-commit su repo reali), comando `npx baldart update --yes`. NON passa `--auto-stash` (ridondante con `--yes`, segnalerebbe imprecisione).
  - **Report** (Step 6): estrae backup tag con `git tag -l 'baldart-pre-update-*' --sort=-creatordate | head -1` (più affidabile del parsing stdout), nuova versione da `cat .framework/VERSION`, e detection stash-pop conflict via grep su stdout/stderr per pattern `CONFLICT` / `stash pop failed` / `unmerged paths`. Fornisce sempre il comando di rollback `git reset --hard <backup-tag>`.

### Bootstrap paradox — esplicitato in cima al SKILL.md

I consumer pre-v3.21.0 incontreranno il paradosso al primo aggiornamento dopo questa release: la skill non è ancora installata, quindi `/baldart-update` non esiste. La skill mette in **box CAPS in prima posizione** l'istruzione di lanciare `npx baldart update` direttamente nel terminale una volta — dopo, `/baldart-update` diventa disponibile per ogni aggiornamento successivo. Non è un edge case nascosto: è il caso primario del rollout, e va trattato come tale.

### Hard rules nella skill

Sette hard rule chiudono i vettori di errore:

1. NEVER re-implementare la logica di `update.js`.
2. MUST replicare in chat tutti e 5 i decision point prima di passare `--yes`.
3. MUST passare `timeout: 600000` al Bash.
4. NEVER passare `--auto-stash`.
5. MUST riportare il backup tag.
6. NEVER lanciare update se `.framework/` manca → defer a `npx baldart add`.
7. NEVER assumere default quando un pre-check fallisce — chiedi all'utente o aborta.

### Schema change propagation

**Nessuna nuova chiave in `baldart.config.yml`** — la skill legge solo `.framework/VERSION`, `.baldart/state.json`, `.claude/settings.json` (file BALDART-owned, non gated da config). La regola di propagazione schema (memoria utente + CLAUDE.md) non si applica a questa release.

### Versioning rationale

MINOR (3.20.0 → 3.21.0) secondo la decision tree di [MAINTAINING.md](MAINTAINING.md):
- Nuova skill (capability additiva) → MINOR.
- Nessuna rimozione / cambio directory layout / cambio install command → non MAJOR.
- Non è un fix doc-only / perf / security senza behaviour change → non PATCH.

### Verification (manuale, no test suite)

Da verificare in un consumer downstream (dopo merge + push + `npx baldart update`):

1. **Discoverability**: dopo l'update, `.claude/skills/baldart-update` esiste come symlink a `.framework/framework/.claude/skills/baldart-update/SKILL.md`. La skill appare nella lista delle skill caricate alla prossima sessione Claude Code.
2. **Collision test (bootstrap edge case)**: creando `.claude/skills/baldart-update/` user-owned PRIMA dell'update, `SymlinkUtils.mergeSkills` logga in `.baldart/skill-conflicts.json` e NON sovrascrive. La skill ufficiale resta non disponibile — comportamento corretto e documentato.
3. **Happy path**: in repo allineata, `/baldart-update` risponde "Already up-to-date" via Step 0 (count = 0), senza lanciare il CLI.
4. **Behind path**: forzato `cd .framework && git reset --hard HEAD~1`, `/baldart-update` rileva 1 commit ahead, replica Step 1–4 in chat, lancia CLI con timeout 600000, riporta backup tag + nuova versione.
5. **Dirty path**: working tree modificato → Step 2 lista i file e chiede conferma esplicita per lo stash. Se rifiuto → STOP pulito.
6. **Conflict path**: file modificato sia upstream che locale → CLI esce non-zero, skill riporta verbatim + comando rollback con backup tag.

## [3.20.0] - 2026-05-26

Su `mayo` è stato osservato il seguente comportamento patologico: il modello ha chiamato `Agent({ subagent_type: "code-reviewer" })` e `Agent({ subagent_type: "doc-reviewer" })` (entrambi agent BALDART legittimi e fisicamente presenti su disco), il tool è ritornato `0 tool uses · Done` senza errore visibile, e il modello ha **silenziosamente fatto fallback** su `feature-dev:code-reviewer` + `plan-auditor` — proseguendo come se nulla fosse. Né l'utente né l'agent orchestratore hanno avuto modo di accorgersi che la review codice + doc review obbligatorie non erano state eseguite dagli agent attesi.

Ricerca su issue tracker Anthropic ha confermato due bug Claude Code che si combinano in questo scenario: [#56869](https://github.com/anthropics/claude-code/issues/56869) (sub-agent error non propagato al parent — `0 tool uses · Done` è il sintomo) e [#20931](https://github.com/anthropics/claude-code/issues/20931) (file-based discovery di `.claude/agents/*.md` documentato come BROKEN in certi scenari, anche con file validi). **Nessuno dei due è patchabile lato BALDART** — il framework può solo mitigare con difese stratificate.

Questa release introduce **quattro layer di difesa stratificati** contro la silent-substitution di agent BALDART, senza nuove chiavi in `baldart.config.yml`:

### Added — Hook `agent-discovery-gate` (PreToolUse, bloccante)

- **[framework/.claude/hooks/agent-discovery-gate.js](framework/.claude/hooks/agent-discovery-gate.js)**: nuovo PreToolUse hook con `matcher: 'Agent'` che intercetta ogni invocazione del tool `Agent` (alias `Task`). Estrae `subagent_type` (o `agent_type`) dall'input, lo confronta con la lista degli agent BALDART derivata via `fs.readdirSync` su `.framework/framework/.claude/agents/*.md` (esclusa `REGISTRY.md`). Se il nome è BALDART e il symlink `.claude/agents/<name>.md` del consumer è mancante o rotto (`fs.statSync` segue il link → ENOENT/ELOOP), emette `permissionDecision: "deny"` con reason esplicito che istruisce: *"riavvia la sessione, esegui `npx baldart doctor`, NON sostituire con un altro agent"*. Nomi non-BALDART (built-in `Explore`/`Plan`/`general-purpose`, marketplace `feature-dev:*`/`vercel:*`/`sentry:*`, plugin) passano sempre. Fail-safe contract identico a `framework-edit-gate.js`: ogni try-catch interno esce 0 (allow), mai bloccare per errore proprio.

### Added — Hook `agent-discovery-info` (SessionStart, informativo)

- **[framework/.claude/hooks/agent-discovery-info.sh](framework/.claude/hooks/agent-discovery-info.sh)**: nuovo SessionStart hook bash che enumera gli agent BALDART attesi (stesso `find` su `.framework/framework/.claude/agents/`) e li confronta con `.claude/agents/<name>.md` nel consumer. Quando uno o più sono mancanti, inietta `additionalContext` come **fatto dichiarativo** (best practice anti-prompt-injection da [Claude Code hooks docs](https://code.claude.com/docs/en/hooks)) elencando i nomi non disponibili. Silente quando tutto è OK. **Limite onesto**: il bug Claude Code [#10373](https://github.com/anthropics/claude-code/issues/10373) può saltare l'iniezione su new conversations — in quel caso la difesa scende sul gate bloccante + regola AGENTS.md.

### Added — Regola MUST `framework/AGENTS.md` § 4.a

- **[framework/AGENTS.md](framework/AGENTS.md)**: nuova regola in sezione *Universal MUST*, inserita immediatamente dopo la MUST su `codebase-architect`. Istruisce ogni agent che lavora in un consumer BALDART a **NON fare fallback silenzioso** quando una chiamata `Agent` ritorna `0 tool uses · Done`, va in timeout, o riceve `permissionDecision: deny`. Comportamento richiesto: STOP, replicare verbatim al user il messaggio *"Agent `<name>` non discoverato o fallito in questa sessione — riavvia Claude Code e verifica con `npx baldart doctor`"*, attendere direzione. Nessun auto-routing a `feature-dev:*`/`general-purpose`/altri marketplace. La regola chiude esplicitamente: *"This rule is the ONLY defense against bug #56869, which BALDART cannot patch from outside Claude Code; deviating from it reintroduces the exact failure mode the rule exists to prevent."* È inseparabile dall'hook bloccante — senza la MUST, il `deny` del gate produrrebbe esattamente la silent-substitution che si vuole prevenire.

### Added — `baldart doctor` check integrità symlink agent

- **[src/commands/doctor.js](src/commands/doctor.js)**: aggiunta detection `state.agentSymlinksBroken` nel blocco `detectState` (stesso pattern del check `hooksStatus`, non-blocking probe in `try`/`catch` silenzioso). Enumera `.framework/framework/.claude/agents/*.md`, per ogni nome verifica `fs.statSync('.claude/agents/<name>.md')`, raccoglie i broken. Nuova action `repair-agent-symlinks` con `autoOk: true` che chiama direttamente `new SymlinkUtils().mergeAgents({ tools })` — NON dispatcha `require('./update')` (l'update completo avrebbe side-effect indesiderati: pull, overlay merge, hook register, config drift verify). Tools letti via `loadConfig(cwd).tools.enabled` con fallback `['claude']`.

### Added — Registry idempotente per i due nuovi hook

- **[src/utils/hooks.js](src/utils/hooks.js)**: appese due entry a `HOOK_REGISTRY`: `baldart-agent-discovery-gate` (PreToolUse, matcher `Agent`, order 200 — corre dopo `framework-edit-gate`) e `baldart-agent-discovery-info` (SessionStart, matcher `*`, order 100). Nessuna modifica strutturale al modulo — l'infrastruttura multi-hook esistente (`registerAll`/`getStatus`/`createDriftPrompt`, introdotta in v3.18.0) gestisce automaticamente i due nuovi hook: `baldart add`/`update`/`doctor` li registrano idempotentemente in `.claude/settings.json` del consumer, e `unregisterAll` li rimuove pulitamente. Backfill garantito su consumer pre-3.20.0 via `baldart update`.

### Changed — Nota machine-readability in REGISTRY.md

- **[framework/.claude/agents/REGISTRY.md](framework/.claude/agents/REGISTRY.md)**: nuova nota in cima che dichiara la directory filesystem `framework/.claude/agents/` come fonte di verità machine-readable per i tre nuovi consumatori (gate hook, info hook, doctor). Aggiungere un agent richiede solo `<name>.md` nella directory — nessun manifest JSON da mantenere, nessuna drift da sincronizzare. La tabella Agent Map resta human-readable.

### Limiti dichiarati

Onestà tecnica > marketing: questa release **non risolve** i bug Claude Code che hanno causato il problema su `mayo`, li mitiga.

- **#56869 (silent sub-agent fallback)** non è patchabile dall'esterno di Claude Code. L'unica difesa è la regola MUST in `AGENTS.md` — soft, non meccanicamente bloccante. Il successo dipende dall'aderenza del modello alla regola.
- **#20931 (file-based discovery broken con file validi)** resta **scoperto dal gate** quando il file esiste e Claude Code lo ignora comunque: il gate vede il file → ritorna allow → la call torna `0 tool uses` (bug originale). In quello scenario worst-case, l'unica difesa attiva è la regola MUST.
- **#10373 (SessionStart non iniettato)** può saltare il warning informativo su new conversations — accettato come best-effort.

In pratica, il caso comune (symlink rotto / consumer mid-migration) è coperto al 100% dal gate. Il caso patologico (Claude Code ignora silenziosamente un file `.md` valido) è coperto solo dalla regola comportamentale.

### Schema change propagation

- **Nessuna nuova chiave in `baldart.config.yml`** — la lista degli agent vive nel filesystem del payload, gli hook sono auto-registrati via `HOOK_REGISTRY`, il check doctor è always-on quando `state.frameworkPresent`. Schema-change propagation rule non si applica a questa release.

### Versioning rationale

MINOR (3.19.0 → 3.20.0) secondo la decision tree di [MAINTAINING.md](MAINTAINING.md):
- Due nuovi hook (capability) → MINOR.
- Nuovo doctor check + nuova action (capability) → MINOR.
- Nuova MUST in AGENTS.md (regola comportamentale aggiuntiva ma additiva, non rompe install esistenti) → MINOR.
- Nessuna rimozione / cambio directory layout / cambio install command → non MAJOR.

### Verification (manuale, no test suite)

Eseguita in-place su questa working copy via smoke test del hook standalone (`node framework/.claude/hooks/agent-discovery-gate.js < input`):

1. **Happy path** — input `{ tool_name: 'Agent', tool_input: { subagent_type: 'definitely-not-an-agent' } }`: exit 0, output vuoto. ✓ (nome non in `baldartAgents` → allow).
2. **Deny path** — input `{ tool_name: 'Agent', tool_input: { subagent_type: 'code-reviewer' }, cwd: '/tmp' }`: exit 0 con stdout JSON `permissionDecision: "deny"` e reason verbose istruttivo. ✓.
3. **Allow path** — tmpdir con `.claude/agents/code-reviewer.md` simulato + invocation: exit 0, output vuoto. ✓ (symlink healthy → allow).
4. **Module load** — `node -e "require('./src/utils/hooks.js')"` riporta 3 entries in HOOK_REGISTRY. ✓.
5. **Doctor module load** — `node -e "require('./src/commands/doctor.js')"` carica senza syntax error. ✓.

Da verificare manualmente in un consumer downstream (dopo merge):
- `baldart doctor` con un symlink agent volontariamente rotto → propone action `repair-agent-symlinks` con `autoOk: true`, l'esecuzione ripristina il symlink.
- `baldart update` da consumer pre-3.20.0 → backfilla i due nuovi hook in `.claude/settings.json` senza toccare le entry esistenti.
- Sessione Claude Code in consumer con symlink rotto → `agent-discovery-info.sh` inietta `additionalContext` al boot; chiamata `Agent` verso quel nome → gate emette deny + il modello (se aderisce alla MUST) replica il messaggio all'utente invece di sostituire.

## [3.19.0] - 2026-05-26

`.baldart/overlays/` smette di essere un percorso "andare al buio". Fino a v3.18.x, un consumer che voleva personalizzare uno skill / agent / command doveva ricordare a memoria tre canali distinti, due modelli di merging (concat per le skill, marker-based per agent/command), il path overlay corretto per ogni canale, lo schema frontmatter (`base_<kind>`, `base_<kind>_version`, `mode`) e la versione framework da targetare — il tutto senza nessun feedback su drift quando il base file cambiava upstream. L'unico touchpoint guida era il messaggio del hook `framework-edit-gate` quando bloccava un edit dentro `.framework/`, che diceva "metti la roba in `.baldart/overlays/<skill>.md`" senza spiegare *quale path esatto*, *quale schema*, *quale versione*. Risultato: overlay scritti raramente o male.

Questa release introduce uno skill `/overlay` + tre nuovi sotto-comandi CLI (`baldart overlay scaffold|validate|drift`) che colmano il gap end-to-end, seguendo il pattern stabilito da `/baldart-push` (skill = guidance, CLI = source of truth). Nessuna nuova chiave in `baldart.config.yml`: gli overlay vivono su path universali che non dipendono dalla configurazione del consumer.

### Added — Skill `/overlay`

- **Nuova skill [framework/.claude/skills/overlay/SKILL.md](framework/.claude/skills/overlay/SKILL.md)**. Thin conversational wrapper attorno al nuovo sotto-comando CLI. Sette fasi: pre-check (`.framework/` esiste?), discovery (skill/agent/command + nome), inspect base + overlay esistente, scaffold via CLI, author body con Edit, validate via CLI, drift maintenance. La skill non re-implementa mai la logica del merger — invoca sempre `npx baldart overlay <sub>`. Quattro hard rules: mai editare `.framework/` direttamente, mai editare file con marker `<!-- baldart-generated: -->`, mai inventare `base_file_sha` o `base_<kind>_version` (computati dalla CLI), mai re-implementare i marker sezionali (vivono in `overlay-merger.js`). Frontmatter porta `contamination_scan: skip` perché contiene esempi illustrativi con token di overlay reali.

### Added — Sotto-comando CLI `baldart overlay {scaffold,validate,drift}`

- **Nuovo file [src/commands/overlay.js](src/commands/overlay.js)** + dispatcher aggiunto in [bin/baldart.js](bin/baldart.js) come gruppo `overlay` (stesso pattern di `routines`). Tre sotto-comandi:
  - `baldart overlay scaffold <type>/<name> [--mode extend|override]` — idempotente. Valida che il base esista in `.framework/`, computa `base_file_sha` (sha256 12-char) dal contenuto base, legge framework `VERSION`, scrive `.baldart/overlays/<path>` con frontmatter completo e body placeholder appropriato al kind (concat per skill, marker-based per agent/command). Refuse pulito se l'overlay esiste già — punta l'utente a `drift` o all'Edit diretto.
  - `baldart overlay validate <path>` — dry-run del merger. Per agent/command invoca `mergeOverlay()` e riporta numero linee generate + warning (es. heading non matchato nel base, che diventa nuova sezione in coda). Per skill valida frontmatter YAML + esistenza base. Confronta `base_file_sha` overlay vs SHA attuale e segnala se il base è cambiato.
  - `baldart overlay drift [<path>] [--all]` — drift report cross-overlay. SHA-based quando `base_file_sha` è presente nel frontmatter, version-based fallback altrimenti. Stampa summary `{clean, drifted, unknown}` su tutti gli overlay sotto `.baldart/overlays/` (root + `agents/` + `commands/`). Skip `*.example.md` e `README.md`.
- **Pre-check fail-safe**: ogni sotto-comando rifiuta con messaggio chiaro quando `.framework/` non esiste (consumer non installato). Non assume mai stato.

### Changed — `overlay-merger.js` arricchito (additivo, retro-compat)

- **[src/utils/overlay-merger.js](src/utils/overlay-merger.js)**: nuova funzione esportata `computeBaseFileSha(content): string` (12-char sha256 prefix) usata dal CLI scaffolder per scrivere `base_file_sha` nel frontmatter overlay. Il marker `<!-- baldart-generated: -->` ora include opzionalmente `base_sha=<sha>` come campo aggiuntivo; `readMarker()` lo legge se presente, altrimenti ritorna `null` per quel campo (retro-compatibile con file generati pre-v3.19.0). Il merger continua a produrre output bit-identico nei casi senza `base_file_sha` nel overlay frontmatter — l'arricchimento è opt-in via lo scaffolder.
- **YAML SHA quote**: il scaffolder quota il sha (`base_file_sha: "fd940fcc1786"`) perché un 12-char hex può essere tutto-digit, che YAML interpreterebbe come numero (`0` per `000000000000`). `parseFrontmatter()` lato CLI coerce sempre a string per difensività.

### Changed — Hook `framework-edit-gate` con handoff `/overlay`

- **[framework/.claude/hooks/framework-edit-gate.js](framework/.claude/hooks/framework-edit-gate.js)**: due piccoli edit testuali (logica del hook intatta). Nel messaggio "(B) Project-specific opinion" e nel messaggio "Cannot edit BALDART-generated file" la guida ora suggerisce di invocare `/overlay` per scaffolding guidato + drift check. La consistenza è garantita perché skill, CLI e hook vengono distribuiti insieme dal `baldart update`.

### Schema change propagation

- **Nessuna nuova chiave in `baldart.config.yml`** — gli overlay vivono su path universali (`.framework/`, `.baldart/overlays/`) non parametrizzati. La regola di propagazione schema (template + configure prompt + update detector + doctor) NON si applica a questa release.
- **Schema change additivo nel frontmatter overlay**: nuovo campo opzionale `base_file_sha`. Retro-compatibile — overlay scritti pre-v3.19.0 senza questo campo restano validi; `drift` li classifica come "unknown" (drift detection by version only) invece di errori. Documentato in `framework/templates/overlays/README.md` come campo opzionale via il body placeholder dello scaffolder.

### Versioning rationale

MINOR (3.18.1 → 3.19.0): added capability (nuova skill + nuovi sotto-comandi CLI). No breaking change: nessuna API esistente modificata semanticamente; `readMarker()` resta backward-compatible; il merger produce lo stesso output per ogni input pre-esistente. Decision tree CLAUDE.md: "Added an agent / command / skill / routine / template → MINOR" qualifica.

### Verification (manuale, no test suite)

Tested in sandbox `/tmp/overlay-test` con symlink `.framework/ → /Users/antoniobaldassarre/BALDART` per simulare un consumer:

1. `baldart overlay scaffold agents/coder` → overlay creato a `.baldart/overlays/agents/coder.md` con frontmatter completo (`base_agent: coder`, `base_agent_version: 3.19.0`, `base_file_sha: "<sha>"`, `mode: extend`).
2. `baldart overlay validate .baldart/overlays/agents/coder.md` → dry-run del merger, "Overlay valid (agent, marker model), 350 lines generated".
3. `baldart overlay drift --all` → cross-overlay drift report, summary corretto.
4. Drift positivo: alterazione manuale di `base_file_sha` nel frontmatter → `drift` segnala "drifted (sha mismatch)".
5. Scaffold idempotente: re-run con overlay esistente → "Overlay already exists, run drift to check it".
6. Skill scaffolding: `baldart overlay scaffold bug` → overlay skill creato a `.baldart/overlays/bug.md` con `base_skill: bug`, body concat-friendly.
7. Pre-check: `baldart overlay scaffold ...` in dir senza `.framework/` → exit 1 con messaggio chiaro.

## [3.18.1] - 2026-05-25

Chiusura del debito tecnico introdotto da v3.18.0. Il release v3.18.0 ha annunciato che `/design-review` supporta un programmatic JSON output mode invocabile da `/e2e-review` per gating uniforme — ma l'implementazione era una sola nota nel frontmatter ("se invocato con `mode: programmatic` ritorna JSON") senza body command che la concretizzasse, senza schema vincolato, senza mode-detection deterministica, senza taxonomy mapping tra le severity Markdown (Blockers/High/Medium/Nitpicks) e le severity programmatic (critical/major/minor) del `visual-fidelity-verifier`. Il risultato: una feature half-implemented, parsable only in teoria. Questo PATCH la rende reale.

L'adversarial review del piano v3.19.0 (multi-viewport + interactive states + Phase 4b design judgment pass) ha esplicitamente identificato il problema: "`/design-review` programmatic JSON è vaporware in v3.18.0 — va prima implementato bene come release a sé". Questa release fa esattamente quello, separando la chiusura del debito dall'aggiunta di nuove feature, in modo che v3.19.0 (quando arriverà, post-dogfooding) possa appoggiarsi a un contratto programmatic stabile invece di costruirsi sopra una stub.

### Fixed — `/design-review` dual-mode reale (Markdown ↔ JSON)

- **[framework/.claude/commands/design-review.md](framework/.claude/commands/design-review.md) riscritto** (frontmatter invariato, body completamente sostituito). Quattro sezioni nuove + workflow rivisto:
  - **§ Invocation Modes**: documenta esplicitamente Mode A (interactive, Markdown — il comportamento storico) e Mode B (programmatic, JSON-only — il nuovo contratto). L'envelope JSON di input per Mode B è specificato verbatim: `{mode, card_id, route, viewport, dev_server_port, registry_paths: {index, tokens, components_dir}, ui_guidelines_path, features: {has_design_system, multi_tenant_theming}, tolerance}`. Il comando lo riceve passato dall'orchestratore `/e2e-review` quando Phase 4b sarà introdotta.
  - **§ Programmatic output schema**: schema JSON di output vincolato, allineato verbatim a `framework/.claude/agents/visual-fidelity-verifier.md` § "Output Schema" per uniformità di parsing nell'aggregatore orchestratore. Campo `source: "design-review"` distingue le finding di questo command da quelle del verifier in modalità debug; i campi `findings[].severity`/`category`/`description`/`expected`/`actual`/`fix_hint`/`evidence`/`ds_drift_code`/`confidence` sono shape-identici. La deduplicazione cross-source avviene per `(route, category, ds_drift_code)` con precedenza al verifier quando duplicate (più strutturato).
  - **§ Severity normalization (Markdown ↔ programmatic)**: tabella di mapping deterministica Blocker → `critical`, High → `major`, Medium → `minor`, Nitpick → DROP. Il drop dei Nitpick in modalità programmatic è deliberato (troppo noise per gating; restano nel report Markdown per uso interattivo).
  - **§ Programmatic-mode category taxonomy**: enumera le 10 categorie ammesse in Mode B (`primitive-reinvented`, `primitive-missing-spec`, `token-bypass`, `component-stale`, `index-drift`, `authority-violation`, `brand-voice-drift`, `a11y-contrast`, `a11y-focus-visible`, `interactive-state-missing`) — sottoinsieme della tassonomia canonica del verifier adattato allo scope di design judgment di questo command. Categorie non in lista vanno emesse come stringhe libere in `gaps[]`, MAI come nuove categorie inventate (l'orchestratore non sa pesarle).
  - **§ Workflow (both modes)**: passi 0–5 unificati per Mode A e Mode B, con la divergenza solo nel formato di output finale (passi 4 e 5). Mode B usa `dev_server_port` dall'envelope per costruire URL, salva screenshot in `/tmp/design-review/<route-slug>.png` per cross-reference dall'orchestratore.
  - **§ Mode-detection contract**: regola deterministica anti-ambiguità — Mode B è triggered SOLO se l'input contiene la substring esatta `"mode": "programmatic"` (con quote, come appare nell'envelope JSON). Vietato auto-detection da contesto invocazione ("sto girando dentro a una skill") — produce silent Markdown leakage nel parser dell'orchestratore che rompe il gate. Su envelope malformato emette `{status: "error", source: "design-review", reason: "malformed_input_envelope", findings: []}` invece di fallback a Mode A (il fallback silenzioso bloccherebbe la card invece di segnalare).

### Notes

- **Nessun cambio framework payload oltre al command.** Nessun nuovo agente, nessuna nuova skill, nessuna nuova chiave config, nessuna modifica a `/new` o `/e2e-review`. Solo il command `/design-review` viene reso effettivamente dual-mode come annunciato in v3.18.0. `/e2e-review` non lo invoca ancora — quella connessione (Phase 4b) viene rimandata a una future release post-dogfooding, sulla base di dati reali su quanto valore aggiunge la judgment pass sopra il `visual-fidelity-verifier`.
- **Backward compatibility totale per uso interattivo**: utenti che invocano `/design-review /merchant/dashboard` continuano a ricevere il Markdown report identico a v3.17.x e prima. Mode A è il default; Mode B è opt-in via envelope JSON esplicito.
- **Severity taxonomy in lockstep**: il body del command include una nota esplicita "quando aggiorni la taxonomy qui, aggiorna in lockstep `framework/.claude/agents/visual-fidelity-verifier.md` § Severity Taxonomy". Le due fonti restano allineate per evitare drift cross-source nell'aggregatore.
- **Versioning rationale.** PATCH (3.18.0 → 3.18.1): no new capability. Il contratto Mode B era già annunciato in v3.18.0; questa release rende l'annuncio reale. Decision tree CLAUDE.md: "Doc fix, script bugfix, perf, security patch with no behaviour change → PATCH" — qualifica perché la behavior change è solo per chi sta ancora costruendo l'integrazione (nessun consumer esistente la invoca programmatically oggi).
- **Verification** (manuale, no test suite): (1) `/design-review /merchant/dashboard` interattivo → Markdown identico al pre-3.18.1 (regression check Mode A); (2) input contenente `"mode": "programmatic"` → JSON-only output, no preamble, parsable da `JSON.parse()`; (3) envelope malformato → emette `{status: "error", reason: "malformed_input_envelope"}` senza fallback a Markdown; (4) campi `route`/`dev_server_port` mancanti dall'envelope → stesso errore; (5) finding di severity Nitpick → presente in Mode A, assente da Mode B `findings[]`.
- **Path post-3.18.1**: dogfooding di v3.18.0 + 3.18.1 per 1–2 settimane su feature UI reali. Sulla base di falsi positivi/negativi raccolti, decidere data-driven quali estensioni includere in v3.19.0 tra: multi-viewport (mobile/desktop/tablet), interactive states (hover/focus/active/disabled + opt-in loading/empty/error), Phase 4b design judgment pass che invoca `/design-review` in Mode B all'interno di `/e2e-review`. L'adversarial review ha esplicitamente sconsigliato il bundle v3.19.0 senza dati di dogfooding — questa release rispetta quella raccomandazione.

## [3.18.0] - 2026-05-25

The end-to-end review fase post-implementazione diventa un **gate deterministico e BLOCKING** invece di un assortimento di check advisory. Until v3.17.x, la chiusura di una card via `/new` attraversava tre fasi eterogenee tutte non-bloccanti sul fronte UI: Phase 2.5 (Implementation Completeness Check) validava solo card AC / PRD alignment / code quality / schema / artifacts senza alcun gate visivo o funzionale runtime; Phase 2.6 (E2E Testing) era *condizionale* (solo con `test_plan.e2e_required: true` o QA profile BALANCED/DEEP) e invocava `qa-sentinel` che per charter (`qa-sentinel.md:151-161`) esegue solo gate meccanici (lint/type/test/build/audit) — non test browser-based né design; Phase 2.7 (Visual Design Review) invocava `ui-expert` ma era esplicitamente marcata "advisory, non-blocking" (`new/SKILL.md:897` — "findings logged but do NOT block commit"). Le skill `webapp-testing` e `playwright-skill` esistevano ma non erano mai auto-invocate da `/new`. La routine `ds-drift` (`framework/routines/ds-drift.routine.yml`) girava settimanalmente, catturando il drift visivo 1–7 giorni dopo il merge. **Conseguenza concreta** lamentata dall'utente: doveva rifare manualmente la review schermata-per-schermata di ogni card UI, e i dettagli grafici dei mockup venivano regolarmente ignorati.

Questo release risolve il problema con un **HYBRID architecture** validata da advisor: nuova skill orchestratore `/e2e-review` + nuovo agente specializzato `visual-fidelity-verifier`, integrazione BLOCKING in `/new` Phase 2.6 (unificando 2.6 + 2.7 attuali), gating tunabile via `features.e2e_review.*`, con riuso massimo dell'esistente (`coder` per scrivere `.spec.ts`, `playwright-skill` per esecuzione, `webapp-testing` per ispezione runtime in compliance-only, `/design-review` esteso a output JSON, `ui-expert` cascade riusata in compliance-only mode). L'architettura segue il **three-layer harness pattern** documentato da Anthropic in [Effective Harnesses for Long-Running Agents](https://www.anthropic.com/engineering/effective-harnesses-for-long-running-agents): implementer (`coder`) e verifier (`visual-fidelity-verifier`) sono distinti, con aggregazione rule-based in mezzo — nessun agente valuta il proprio lavoro. Il pattern **Definition of Done machine-readable** ([Policy Cards, arXiv 2510.24383](https://arxiv.org/abs/2510.24383)) guida la sintesi Gherkin del piano di verifica. La combo **Playwright MCP + Vision** ([Playwright MCP](https://playwright.dev/mcp/introduction), [Giving Claude Code Eyes — round-trip screenshot testing](https://medium.com/@rotbart/giving-claude-code-eyes-round-trip-screenshot-testing-ce52f7dcc563)) è il pattern canonico 2025–2026 per browser+vision in agentic dev.

### Added — Nuova skill `/e2e-review` (orchestratore BLOCKING)

- **Nuova skill [framework/.claude/skills/e2e-review/SKILL.md](framework/.claude/skills/e2e-review/SKILL.md)** (~530 righe). Sei fasi sequenziali:
  - **Phase 1 — Verification Plan Extraction**: legge `card.acceptance_criteria[]`, `card.test_plan` (quando presente, schema compatibile con quello già scritto da `/prd` Step 6, senza modifiche al PRD), `card.requirements[]`, `card.links.design[]`, `card.scope.routes[]` (con fallback derivato dal diff via `codebase-architect`). Sintetizza scenari Gherkin (Given/When/Then) e li persiste in `.baldart/e2e-review/<CARD-ID>/plan.json` come contratto machine-readable consumato dalle fasi successive.
  - **Phase 2 — Mockup Source Cascade (4 livelli)**: (a) URL Figma + Figma MCP runtime probe → `get_design_context` + `get_screenshot`; (b) path locale referenziato in `card.links.design[]` (PNG/JPG/PDF — PDF estratto con `pdftoppm`, degrada gracefully); (c) no mockup ma `features.has_design_system: true` → modalità **compliance-only** che riusa la cascade BLOCKING del [design-system-protocol.md](framework/agents/design-system-protocol.md) (INDEX.md + tokens-reference.md + components/<Name>.md); (d) nulla → skip visual + warning in `gaps[]`, functional E2E continua comunque. Nessun hard fail per assenza di Figma — la dipendenza è genuinamente opzionale.
  - **Phase 3 — Functional E2E**: spawn `coder` con contratto preciso (file in `${paths.e2e_tests_dir}/<CARD-ID-slug>.spec.ts`, screenshot path fissato a `.baldart/e2e-review/<CARD-ID>/screenshots/<route-slug>.png` per consumo da Phase 4, no chromium.launch, no edit fuori `${paths.e2e_tests_dir}`). Esegue `npx playwright test --reporter=json` headless in modalità programmatica (headed riservato a flussi OTP in invocazione manuale). Mappa scenari falliti in finding funzionali con severity rule-based (auth/payments/data-mutation = Critical; display-only = Major; flaky-passed-on-retry = Minor + `confidence: low`).
  - **Phase 4 — Visual Fidelity**: pre-filter con pixel-diff (`pixel_diff_threshold` configurabile, default `0.02`) prima di invocare Vision — quando la diff è sotto soglia, il route passa senza spendere token Vision (primary latency/cost saver). Sopra soglia o in compliance-only, spawn `visual-fidelity-verifier` con payload strutturato; deduplicazione findings per `(route, category, region)`.
  - **Phase 5 — Aggregation + Strict Severity Gate**: combina functional + visual findings, applica filtro tolerance (`strict` default → Critical+Major+Minor block; Minor con `confidence: low` auto-demote ad advisory — escape hatch documentato contro font-rendering false positive). Self-heal loop bounded (`max_self_heal_iterations`, default `2`): re-spawn `coder` con findings come fix instructions, re-esegui Phase 3+4, re-decidi. Override path: dopo esaurimento iterazioni in modalità manuale chiede reason obbligatoria (`require_override_reason: true`); in modalità programmatica restituisce `verdict: "blocked"` lasciando la decisione a `/new`.
  - **Phase 6 — Return Protocol**: scrive `.baldart/e2e-review/<CARD-ID>/report.json` con schema strutturato `{status, iterations, findings[], ds_drift_codes[], gaps[], override_reason, transcript_paths[]}`. Modalità manuale renderizza anche Markdown summary; programmatica emette solo JSON come messaggio finale (parsato direttamente da `/new`).
- **Gate di skip pre-flight** per evitare overhead inutile: skill REFUSES quando `features.has_e2e_review: false` (preserva backward compat); auto-skip quando card non ha `links.design` E `git diff` non mostra file UI (`.tsx`/`.css`/`.scss`/`.svelte`/`.vue` sotto `${paths.app_dir}` o `${paths.components_primitives}`); auto-skip su card type `docs`/`chore`/`config`/`backend`/`api`/`db`/`infra`. Lo skip non è failure — `/new` procede a Phase 3 normalmente.
- **Re-run trigger**: se `/codexreview` (Phase 3.7) modifica file sotto `${paths.app_dir}`/`${paths.components_primitives}` o qualsiasi `*.css`/`*.scss` dopo il primo run, l'orchestratore re-invoca `/e2e-review` PRIMA del commit di Phase 4. Protegge contro code-review changes che reintroducono silenziosamente drift visivo.

### Added — Nuovo agente `visual-fidelity-verifier`

- **Nuovo agente [framework/.claude/agents/visual-fidelity-verifier.md](framework/.claude/agents/visual-fidelity-verifier.md)** (`model: sonnet`, ~280 righe). Worker stateless multimodale invocato esclusivamente da `/e2e-review` Phase 4. Unico nuovo agente del release — l'advisor ha bocciato la creazione di un secondo `e2e-functional-verifier` perché duplicherebbe il pattern Phase 2.6 attuale (`coder` scrive `.spec.ts`, Playwright esegue).
  - **Hard prohibitions non-negotiable**: NEVER reads application source code (anti assertion-fitting bias — antipattern documentato Meta/Capgemini 2025: LLM-generated assertions che lockano il bug come "expected behavior"), NEVER edits files, NEVER proposes architectural refactors, NEVER declares "done" o "passed" (la decisione di gate è dell'orchestratore).
  - **Input contract strict JSON**: `{card_id, route, viewport, implementation_screenshot_path, mockup_source: {level, mockup_path | figma_node_id | design_system_index_path | tokens_reference_path | components_in_scope}, tolerance, fix_hints_enabled}`.
  - **Severity Taxonomy canonica** (SSOT consumata anche da `/e2e-review` e `/design-review`): **Critical** (`layout-break`, `element-order`, `responsiveness-break`, `component-missing`, `component-duplicated`, `unreachable-action`) blocca in ogni tolerance; **Major** (`spacing-off-scale`, `typography-{family,weight,size,line-height}`, `color-mismatch`, `color-opacity`, `gradient-direction`, `token-bypass`, `interactive-state-missing`, `a11y-contrast`, `a11y-focus-visible`) blocca in strict+balanced, advisory in lenient; **Minor** (`border-radius-off`, `shadow-off`, `micro-misalignment`, `font-rendering-variance`) blocca solo in strict (con auto-demote `confidence: low` ad advisory anche in strict).
  - **Output schema strict JSON**: `{status, route, mockup_source_level, viewport, compliance_score, findings: [{severity, category, description, expected, actual, fix_hint, evidence: {region, screenshot_crop_path}, ds_drift_code, confidence}], ds_drift_codes[], gaps[], transcript_path}`. Citazioni token rigorose (mai paraphrase — `--color-action-primary` non "the primary blue"). Anti-hallucination discipline (no finding emessi senza evidenza visibile; `status: error` invece di guessing quando screenshot insufficiente).
  - **Mockup source cascade behaviour**: in modalità `figma`/`local` il mockup è ground truth visuale; in `compliance-only` riusa la cascade BLOCKING del [design-system-protocol.md](framework/agents/design-system-protocol.md) (INDEX + tokens-reference + per-component specs) e mappa findings ai drift code canonici `DS_TOKENS_DRIFT`/`DS_COMPONENT_STALE`/`DS_INDEX_DRIFT` (definiti nel protocollo, non qui — un solo SSOT).

### Added — Schema config propagation (regola interna del repo)

- **Template [framework/templates/baldart.config.template.yml](framework/templates/baldart.config.template.yml)**: aggiunto `features.has_e2e_review: false` (default opt-in esplicito — backward compatibility totale) e nuova sezione top-level `e2e_review:` con 4 chiavi commentate. La chiave esistente `paths.e2e_tests_dir` è ora consumata anche da `e2e-review` oltre che da `playwright-skill`.
- **`features.e2e_review` tuning sub-section**: `fidelity_tolerance` (`strict` default — Critical+Major+Minor block con escape hatch low-confidence; `balanced` blocca Critical+Major; `lenient` solo Critical), `max_self_heal_iterations` (default `2`, allineato a Phase 2.5 retry policy in `new/SKILL.md:699`), `pixel_diff_threshold` (default `0.02`, range 0.0–1.0 — pre-filter pixel-diff per skippare la Vision call quando implementazione e mockup sono pixel-identici), `require_override_reason` (default `true` — la reason loggata in tracker `## Issues & Flags` e in `report.json`).
- **[src/commands/configure.js](src/commands/configure.js)** prompt + status: aggiunto `has_e2e_review` al loop dei feature prompt (linea ~563 nuovo); nuova sezione "E2E review tuning" prima della sezione LSP (linea ~624 nuovo) che gating su `has_e2e_review: true` chiede via `UI.select` la tolerance e poi le 3 chiavi nested; aggiunta riga "E2E review:" nel box AUTODETECTED. Il detector schema-drift in `update.js` (linee 462–503) cattura automaticamente la nuova chiave `features.has_e2e_review` via template scan generico — nessun cambio dedicato necessario. `doctor.js` `configSchemaDrift()` (linee 70–82) usa lo stesso meccanismo.
- **[framework/AGENTS.md](framework/AGENTS.md)** tabella MUST aggiornata (linea 18): aggiunta riga `E2E review BLOCKING gate (Phase 2.6 of /new invokes /e2e-review) | features.has_e2e_review: true`.

### Changed — `/new` Phase 2.6 unificata e BLOCKING

- **[framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md) linee 814–901**: sostituite integralmente la legacy Phase 2.6 (E2E Testing condizionale invocante `qa-sentinel`) e Phase 2.7 (Visual Design Review advisory invocante `ui-expert`) con una singola Phase 2.6 unificata "End-to-End Review (BLOCKING, since v3.18.0)" che invoca `/e2e-review` in modalità programmatica. Il gate documentato sostituisce il branching condizionale precedente (`test_plan.e2e_required` + QA profile + legacy fallback heuristic): 3 condizioni di skip (feature disabled / backend-only card / docs|chore|config card), payload JSON strutturato all'invocazione, contract di ritorno JSON parsato programmaticamente. Mapping verdict→azione: `passed`/`skipped`/`overridden` → procede; `blocked` STOPpa la card senza procedere a Phase 3 e chiede all'utente override/escalation/abandon; `error` chiede retry/skip/abandon. Re-run trigger dopo Phase 3.7 `/codexreview` se file UI modificati. Tracker output strutturato per audit cross-card.

### Changed — `/design-review` command estensione output JSON

- **[framework/.claude/commands/design-review.md](framework/.claude/commands/design-review.md)** prologo: aggiunta nota di invocazione che documenta il dual-mode. Interactive (default) produce Markdown report (Blockers/High/Medium/Nitpicks) come oggi; programmatic mode (input contiene `"mode": "programmatic"`) ritorna un singolo JSON object matching lo schema di `visual-fidelity-verifier` per gating dell'orchestratore. Nessuna logica nuova nel command body — solo flag di output format. Permette al `/e2e-review` skill di riusare `/design-review` come tooling alternativo quando appropriato senza duplicare la cascade design-system.

### Changed — Documentazione integrazione skill tooling

- **[framework/.claude/skills/playwright-skill/SKILL.md](framework/.claude/skills/playwright-skill/SKILL.md)**: nuova sezione "E2E Review Integration (since v3.18.0)" prima di Quick Reference. Documenta il contratto consumer: spec generata da `coder` partendo da `plan.json`, screenshot path fissato per consumo da Phase 4, headless in modalità programmatica, mapping severity di scenari falliti. Nessuna logica nuova nella skill — solo documentazione del nuovo consumer.
- **[framework/.claude/skills/webapp-testing/SKILL.md](framework/.claude/skills/webapp-testing/SKILL.md)**: nuova sezione "E2E Review Integration (since v3.18.0)" in testa. Documenta l'uso in compliance-only mode (cascade level c) per fetch dei computed style runtime quando manca mockup-as-image; ruolo del server lifecycle helper. Nessuna logica nuova.
- **[framework/.claude/skills/prd/assets/card-template.yml](framework/.claude/skills/prd/assets/card-template.yml)** linea 134 ca.: aggiunto commento sopra `test_plan:` che documenta il nuovo dual consumer (`/new` legacy fallback + `/e2e-review` Phase 1 — il mapping verso Gherkin scenarios). Nessuna modifica allo schema PRD; le card scritte prima della v3.18.0 sono pienamente compatibili (Phase 1 di `/e2e-review` ha fallback da `acceptance_criteria` quando `test_plan` manca).

### Changed — Registry e routing

- **[framework/.claude/agents/REGISTRY.md](framework/.claude/agents/REGISTRY.md)**: aggiunta riga per `visual-fidelity-verifier` immediatamente sotto `ui-expert` (sezione Design). Specializzazione: visual diff, severity taxonomy, design-system compliance via registry cascade. Auto-invoke yes via `/e2e-review` — non spawnabile ad-hoc (l'orchestratore è il solo entry point).

### Changed — Documentation utente

- **[README.md](README.md)**: agent count aggiornato da 24 a 27 (visual-fidelity-verifier + numerazione resa coerente Design&UX→Specialized); skill count da 24 a 25 (e2e-review aggiunto sotto "Code quality"); nuova sezione "End-to-End Review BLOCKING Gate (new in v3.18.0)" prima della sezione "UI Excellence + Post-Intervention Coherence Gate (v3.12.0)" che riassume l'intera architettura, citando le fonti del 2025–2026 e documentando il tuning `e2e_review.*`.
- **[framework/docs/PROJECT-CONFIGURATION.md](framework/docs/PROJECT-CONFIGURATION.md)**: tabella paths aggiornata (e2e_tests_dir ora cita anche e2e-review); nuova sezione "§ 4.2.1 `e2e_review` — tuning for the BLOCKING end-to-end review gate (v3.18.0+)" che documenta ogni chiave con tipo/default/effetto e citazioni cross-reference al protocollo `design-system-protocol.md` per i drift code.

### Notes

- **Backward compatibility totale.** Il default `features.has_e2e_review: false` significa che progetti pre-3.18.0 non vedono nessun cambio di comportamento finché non eseguono `npx baldart configure` (che proporrà la nuova feature) o editano manualmente la config. `/new` Phase 2.6 silenziosamente skippa la chiamata a `/e2e-review` quando il flag è false, preservando il workflow legacy con un breve log informativo nel tracker. Le card scritte prima della v3.18.0 (senza `test_plan` block, o con `test_plan.e2e_required: false`) sono pienamente supportate dalla nuova fase: Phase 1 di `/e2e-review` deriva Gherkin scenarios direttamente da `acceptance_criteria` quando `test_plan` è assente, e la mockup cascade degrada gracefully a compliance-only / skip quando `links.design` manca.
- **Riuso massimo, footprint minimo.** Un solo nuovo agente (`visual-fidelity-verifier`) e una sola nuova skill (`/e2e-review`). Riusati: `coder` (scrittura `.spec.ts` + self-heal loop), `playwright-skill` (esecuzione spec), `webapp-testing` (compliance-only computed styles fetch), `codebase-architect` (mapping route→file fallback), `/design-review` command (esteso a output JSON per gating), `ui-expert` cascade (referenziata da `visual-fidelity-verifier` in compliance-only mode), `design-system-protocol.md` (SSOT della cascade BLOCKING e dei drift code — referenziato, mai duplicato), `ds-drift` routine (resta safety net settimanale per edit umani che bypassano `/new`). Bocciata da advisor la creazione di un secondo agente `e2e-functional-verifier` (avrebbe duplicato il pattern Phase 2.6 attuale) e di un terzo "evaluator" three-agent-harness completo (overkill quando l'aggregazione è rule-based su severity strutturata, da introdurre solo se dogfooding rivela troppi falsi positivi).
- **Dependency contract.** Il consumer deve avere `@playwright/test` installato e `paths.e2e_tests_dir` valorizzato; lo skill rileva il gap in Phase 3 step 1 e fornisce setup hint actionable (`npm i -D @playwright/test && npx playwright install chromium`). Figma MCP è opzionale — la cascade Phase 2 degrada a level (b)/(c)/(d) senza errori. Vision usage è governata dal `pixel_diff_threshold` pre-filter — la maggior parte dei route passa senza spendere token Vision.
- **Override path con audit trail.** Quando self-heal esaurisce iterazioni in modalità manuale e l'utente vuole comunque procedere, una reason è obbligatoria (`require_override_reason: true` default). La reason è loggata sia nel `/tmp/batch-tracker-<FIRST-CARD-ID>.md` come `[E2E-OVERRIDE] <reason>` sia in `.baldart/e2e-review/<CARD-ID>/report.json` come campo `override_reason`. Il rationale: il gate deve essere strict per default ma deve avere una via di fuga documentata — gate troppo rigidi senza escape hatch generano user override fatigue e perdita di fiducia (failure mode documentato).
- **Re-run automatico dopo `/codexreview`.** Se Phase 3.7 modifica file UI, `/e2e-review` re-esegue PRIMA del commit di Phase 4. Lo state directory `.baldart/e2e-review/<CARD-ID>/` viene riusato (no re-fetch mockup, no re-build plan) — solo Phase 3 (functional) + Phase 4 (visual) vengono ri-eseguite. Protezione contro la classe di bug dove il code-review introduce silenziosamente drift visivo per "consistency cleanup".
- **Verification è manuale (no test suite — `npm test` resta no-op stub).** Dogfood scenarios da girare dopo l'install:
  1. `npx baldart configure` su progetto pre-3.18.0 → la propagazione schema drift detector cattura `features.has_e2e_review` mancante e offre `configure` automaticamente. Il prompt "Enable BLOCKING end-to-end review?" appare; rispondendo `Yes` la sezione "E2E review tuning" chiede tolerance/max-iter/threshold/require-reason.
  2. `/e2e-review CARD-ID` manuale su una card UI esistente con `features.has_e2e_review: true` + `links.design` valorizzato → il piano viene scritto in `.baldart/e2e-review/<CARD-ID>/plan.json`, il `.spec.ts` generato in `tests/e2e/`, screenshot scattate, `visual-fidelity-verifier` invocato per ogni route, report JSON + Markdown summary mostrato all'utente.
  3. `/new <CARD-ID>` su card UI dopo opt-in → Phase 2.6 invoca `/e2e-review` programmatically; verdict `passed` procede a Phase 3 normalmente; verdict `blocked` STOPpa la card e chiede override/escalation/abandon all'utente.
  4. `/new <CARD-ID>` su card backend-only (no `links.design`, no file UI nel diff) → Phase 2.6 auto-skip con log `"e2e-review: SKIP (backend-only card)"`, procede a Phase 3 senza errori.
  5. Test sintetico iniettivo: card con 3 deviazioni note pre-iniettate (colore button hardcoded, padding fuori scala token, border-radius literal) → report contiene esattamente 3 finding con severity (Major, Major, Minor in strict mode) e gate blocca il commit.
  6. Negative path Figma MCP non disponibile → cascade falls through a level (b) (immagine locale) o (c) (compliance-only) senza hard fail; warning loggato in `gaps[]`.
  7. Self-heal loop: card con token bypass fixabile → il loop converge in ≤ 2 iterazioni e il commit procede senza override richiesto.
  8. Override path: card con falso positivo evidente → l'utente fornisce reason, commit procede con `[E2E-OVERRIDE] <reason>` loggato nel tracker e in `report.json`.
- **Versioning rationale.** Impact MINOR (3.17.1 → 3.18.0): aggiunge una skill (`/e2e-review`), un agente (`visual-fidelity-verifier`), una fase BLOCKING nella skill esistente `/new`, e nuove chiavi di configurazione (`features.has_e2e_review` + sub-section `e2e_review.*`). Non rompe installazioni esistenti — il default `false` mantiene il comportamento legacy fino a opt-in esplicito via `configure`. Non rimuove agenti / cambia layout di directory / cambia comandi CLI (decision tree MAJOR non si applica).

## [3.17.1] - 2026-05-23

CLI quality-of-life patch shaken loose by a hands-on consumer update simulation (mayo, 3.13.0 → 3.16.0). Four real bugs surfaced when actually driving `baldart update` end-to-end from a scripted context. Two were show-stoppers for any non-interactive use (CI, AI-driven, headless update agent), one was cosmetic but ugly, one was a stale stub that lied to the user. All four are fixed; nothing in this release changes framework payload behaviour for end-users running BALDART interactively in a TTY.

### Fixed

- **`baldart update` gains `--yes`/`-y` and `--auto-stash` flags** in [bin/baldart.js](bin/baldart.js) and [src/commands/update.js](src/commands/update.js). Until now, every `UI.confirm` and the `UI.select` arrow-key stash prompt blocked indefinitely on a non-TTY stdin (inquirer's pattern detection mis-handles re-rendered prompts when fed via pipe or `expect`). Both flags route through a single internal `confirm()` wrapper that short-circuits to the prompt's default when `--yes` is set; `--auto-stash` is the narrower variant for users who want manual control over confirms but auto-stash on dirty trees. `--yes` implies `--auto-stash` and additionally passes `--non-interactive` down to the `baldart configure` sub-invocation triggered by the schema-drift detector, plus switches symlink reconcile from `mode: 'prompt'` to `mode: 'force'` (the conflict-resolution prompts inside `SymlinkUtils` would otherwise reintroduce blocking I/O even with `--yes`).
- **`baldart status` actually checks the framework against the upstream tip** in [src/commands/status.js](src/commands/status.js). Previously the "Update Status" section was a hard-coded `UI.success('Up to date')` stub regardless of the installed version — a 4-versions-stale consumer was told everything was fine. The fix calls `git.fetch('antbald/BALDART')` + `git.getRemoteVersion(...)` (already exported by `src/utils/git.js`), compares semver with a tiny inline `cmpSemver` helper, and renders one of four outcomes: update available (`X → Y`), local ahead of remote (unreleased commit, common during BALDART self-development), up to date, or "Cannot check (offline?)" on fetch failure. The SUMMARY box now carries the update line too, so it's visible without scrolling past the per-section output.
- **`baldart --version` and `baldart --help` exit cleanly** in [bin/baldart.js](bin/baldart.js). Commander 9 throws a `CommanderError` with `exitCode: 0` from these flags as its internal flow-control mechanism; the previous `bin/baldart.js` had no try/catch around `program.parse(process.argv)`, so the throw escaped to Node and printed a 12-line `CommanderError: 3.17.0` stack trace right after the version itself. Now wrapped in try/catch that recognises the three known sentinel codes (`commander.version`, `commander.help`, `commander.helpDisplayed`) and exits 0 silently; real errors keep the previous behaviour (log message + non-zero exit).
- **CLI version is read from the `VERSION` file, not `package.json`** in [bin/baldart.js](bin/baldart.js). `package.json.version` is only synced at publish time via `npm run sync-version` (see `scripts/sync-version`), so in dev / local-source / pre-publish contexts it lagged the actual `VERSION` file — `node bin/baldart.js --version` was printing `3.14.1` while VERSION was `3.17.0`. A new `resolveVersion()` reads `VERSION` at module load with `package.json.version` as fallback. Published `baldart@<x.y.z>` on npm is unaffected (the workflow still runs `sync-version` first, so both files agree in published artefacts).

### Notes

- **No framework payload changes** — all four fixes are inside `bin/` and `src/`. Skills, agents, templates, hooks, routines, docs are untouched. The next `baldart update` for consumers picks up the new flags and the working status check without any change to the framework files they install.
- **Not a v3.18.0 because no new capability shipped** — this is purely "what should already have worked". The new flags are additive (zero-config end-users in a TTY see no change) and the status fix is replacing a stub with the real check it was always supposed to be.
- **Bug #4 from the mayo simulation (schema-drift detector skipped with `--no-commit`) was a false positive** — the drift detector at lines 462–503 runs before `postUpdateAutoCommit` (line 506), so `--no-commit` doesn't reach it. Verified by reading the call sequence, no code change needed.
- **Verification** (manual, no test suite): (1) `node bin/baldart.js --version` prints `3.17.1` and exits 0 silently; (2) `node bin/baldart.js update --help` shows the three options (`--no-commit`, `--yes/-y`, `--auto-stash`); (3) in a stale consumer repo, `baldart status` reports "Update available" with the version diff; (4) `baldart update --yes` on a dirty consumer auto-stashes, pulls, reconciles symlinks in force mode, runs `configure --non-interactive`, and exits 0 with no blocking prompts.

## [3.17.0] - 2026-05-23

The `/prd` skill gains a third option in the design phase. Until v3.16.0, when a user reached Step 3 without having provided mockups (`mockups.status: none`), the only path forward was internal generation via the `ui-design` skill (3 HTML options → iteration → save). With Claude Design now offering a design-system-sync'd visual generator, users who prefer iterating in that surface had to abandon the PRD flow halfway, work externally, and then re-enter as if they'd had mockups from the start. This release adds **Step 3.0 — Mockup Source Decision**: when `mockups.status: none` AND UI impact ≠ N/A, the skill explicitly asks "internal generation or handoff to Claude Design?". The handoff branch generates a context-rich prompt (discovery + brand voice + design-system tokens + screens in scope + stack target) ready to paste, then STOPs. On user re-entry with local paths, it re-uses the existing Step 1.6 Mockup Intake verbatim — copy → analyze → populate state — and Step 3 falls naturally into Hybrid mode. Zero code paths downstream of the bivio are new; the entire feature is a decision point + a prompt generator on top of pre-existing infrastructure.

### Added — PRD Step 3.0 Mockup Source Decision

- **§ Step 3.0 in [framework/.claude/skills/prd/references/ui-design-phase.md](framework/.claude/skills/prd/references/ui-design-phase.md)** before the precondition decision tree. Activates only when `mockups.status: none` AND UI impact ≠ N/A. Poses the binary question (Internal `ui-design` vs Handoff Claude Design) with explicit STOP gate — silence or ambiguity re-asks rather than defaulting silently. Branch A sets `mockups.source: internal` and falls through unchanged. Branch B collects context from state file + `baldart.config.yml` + `.baldart/overlays/prd.md` + `${paths.design_system}/INDEX.md` + `tokens-reference.md` (when `features.has_design_system: true`), renders the handoff prompt template, presents it inside a fenced code-block with copy-paste instructions, persists `mockups.status: pending_external` + `mockups.source: claude-design` + `mockups.handoff_prompt_path` in the state file (prompt saved to `${paths.prd_dir}/<slug>/handoff/claude-design-prompt.md` for session-restart survival), and STOPs.
- **Re-entry handler in same file**. When the user returns with local paths after handoff, the skill invokes the existing Step 1.6 Mockup Intake procedure verbatim (no code duplication) — copy into `mockups/` with collision-safe rename, run Mockup analysis schema, populate `## UI Design`. Then flips `mockups.status` from `pending_external` to `provided` or `partial` based on coverage of `screens_in_scope[]`, keeps `mockups.source: claude-design` as audit trail, and falls through to the precondition decision tree which routes to Hybrid mode unchanged.
- **Precondition decision tree extended** with two new rows: `mockups.status: pending_external` ⇒ STOP (handoff in flight); `mockups.status: none` AND user picked Branch A ⇒ Full (preserves backwards-compatible default). The existing `provided` / `partial` ⇒ Hybrid and UI = N/A ⇒ Skip rows are unchanged.
- **New template asset [framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md](framework/.claude/skills/prd/assets/claude-design-handoff-prompt.template.md)**. Seven-section structure populated at runtime: (1) feature objective, (2) user flow + personas, (3) screens in scope with required states + primary actions + data shown, (4) brand & voice (conditional on overlay), (5) design system constraints with tokens excerpt + registered primitives + numeric tables from `framework/agents/design-system-protocol.md` (conditional on `features.has_design_system: true`), (6) stack target + output format preference derived from `stack.framework`, (7) deliverable spec (PNG exports per state + code/Figma + inline notes on new components / token deviations / layout decisions). Generic and project-agnostic — all project-specific content is injected as placeholders.
- **Hard Rule 16 note in [framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md)** added to the existing Mockup Intake rule. Codifies that a NO answer at intake routes through Step 3.0 (not directly to Full mode), with cross-reference to the ui-design-phase section. The rule itself remains unchanged for backwards compatibility — Step 3.0 is purely additive.

### Notes

- **No new `baldart.config.yml` keys** — the schema-change propagation rule does not apply to this release. The bivio is runtime-only per the user's preference (zero persistent flag); no template/configure/update/doctor updates needed. All context for the handoff prompt is read from existing config keys (`paths.*`, `stack.*`, `features.has_design_system`).
- **Backwards compatibility.** PRDs in flight on v3.16.x finish on v3.16.x semantics. New PRD sessions started on v3.17.0+ see Step 3.0 only when they actually reach Step 3 with `mockups.status: none` — the default (Branch A) preserves the v3.16.x behavior exactly. Existing PRDs that already provided mockups never enter Step 3.0.
- **`ui-design` skill is unchanged** — Step 3.0 sits in front of it, doesn't modify it. The Full pipeline (3a → 3d) is reached identically when the user picks Branch A or when no mockup-related state exists at all. The `ui-expert` agent and `huashu-design`/`frontend-design` skills are equally untouched.
- **`mockup_analysis` schema is unchanged** — the new `pending_external` value of `mockups.status` is purely transitional and is overwritten by `provided` / `partial` on user re-entry, so existing PRD templates and state files keep validating without migration.
- **Files modified outside the Step 3.0 scope** (`card-template.yml`, `backlog-phase.md`, `validation-phase.md`, `prd-card-writer.md`, `framework/.claude/skills/new/SKILL.md`) are the v3.16.0 in-flight release (agent routing + UI specialist briefing — see entry below) and are NOT part of v3.17.0. v3.16.0 should be tagged and pushed before v3.17.0 in the release sequence.
- **Verification is manual** (no test suite). Dogfood scenarios: (1) `/prd` with mockups provided at intake → never enters Step 3.0, Hybrid mode as today (regression check); (2) `/prd` without mockups, answer "A" at Step 3.0 → Full mode runs identically to v3.16.x; (3) `/prd` without mockups, answer "B" → prompt rendered with discovery + tokens + screens + stack, saved to `handoff/`, state shows `pending_external`, skill STOPs; (4) simulate re-entry with a PNG path → file copied to `mockups/`, status flips to `provided`, Step 3 enters Hybrid mode invoking `ui-design` only for uncovered screens; (5) `features.has_design_system: false` → § 5 of the rendered prompt is omitted entirely; (6) no overlay present → § 4 brand voice omitted.

## [3.16.0] - 2026-05-23

The `/new` orchestrator becomes specialist-aware: it now reads `owner_agent` from each card YAML and dispatches the right implementation agent — `coder` for backend/logic, `ui-expert` for UI cards, with `plan`/`visual-designer`/`motion-expert` reserved as stubs that fall back to `coder` with a WARN. UI cards (`owner_agent: ui-expert`) receive a dedicated mission briefing that enforces the registry-first BLOCKING cascade (read `${paths.design_system}/INDEX.md` + `tokens-reference.md` + per-component specs BEFORE coding), the implementation-only constraint (no mockup redesign — STOP and ask on registry conflicts), and the Post-Intervention Coherence Check (reconcile INDEX + components + tokens in the same commit). The PRD side enforces the contract end-to-end: `owner_agent` is now a mandatory enum on every child card, UI cards have UI-only scope (logic moves to a sibling `coder` card), and the validation phase blocks commit on enum violations + warns on mixed-scope UI cards.

Until now, every card from `/new` was spawned to the generic `coder` agent regardless of `owner_agent`, and mockups arrived as a textual "match this reference" hint without triggering the registry/tokens/specs cascade that `ui-expert` mandates. The symptom reported by users: graphical code generated from mockups was "not quite right" — wrong shadows, off-scale font sizes, off-palette colors. Root cause: the right specialist existed and PRD cards declared it (`owner_agent: ui-expert`), but the dispatcher never read the field.

### Added — Agent router + UI specialist briefing

- **§ Agent Routing section in [framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md)** between Pre-flight and Per-card pipeline. Documents the `owner_agent` → spawn-target mapping in a single table (coder | ui-expert | plan | visual-designer | motion-expert → resolved agent + briefing variant) with a cross-reference list of the 5 layers that must stay in lockstep (template, prd.md enum, prd-card-writer rule, validation-phase check, REGISTRY.md decision tree).
- **Step 6b "Agent dispatch (router)" in Phase 2** of [framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md). Reads `card.owner_agent`, resolves the spawn target via a switch with explicit fallbacks: `coder` → coder briefing; `ui-expert` → ui-expert briefing; `plan`/`visual-designer`/`motion-expert` → coder briefing + WARN ("specialized briefing not yet implemented"); empty/`claude`/missing → coder briefing + WARN; unknown value → HALT and ask the user. The resolved spawn + briefing pair is logged in the tracker as the audit trail.
- **`[UI-ONLY]` mission briefing sections** in Phase 2 step 7 of [framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md). Three new sections inserted between "Design Reference" and "File Permissions", included ONLY when the router resolves `briefing = "ui-expert"`:
  - **Registry-first BLOCKING pre-work** — reproduces verbatim the cascade defined in [framework/.claude/agents/ui-expert.md](framework/.claude/agents/ui-expert.md) § "Registry-first pre-work (BLOCKING)": read `INDEX.md` + `tokens-reference.md` + `components/<Name>.md` for every primitive in scope, declare which Authority Matrix rows govern the card in the first response. Orchestrator rejects the implementation if the declaration is missing.
  - **Implementation-only constraint** — explicit DO NOTs: don't redesign the mockup, don't silently adapt registry-conflicting details (STOP + `[DS-DRIFT] mockup-vs-registry` log + ask the user), don't extend scope into business logic / API / state / validation (split-into-sibling-card is upstream PRD concern).
  - **Post-Intervention Coherence Check** — citation of [framework/agents/design-system-protocol.md](framework/agents/design-system-protocol.md) § "Post-Intervention Coherence Check": reconcile `INDEX.md` (drift code `DS_INDEX_DRIFT`) + `components/<Name>.md` (`DS_COMPONENT_STALE`) + `tokens-reference.md` (`DS_TOKENS_DRIFT`) in the same commit. The completion report MUST include a `design_system_coherence:` block enumerating the artifacts updated.
- **`[UI-ONLY]` design-system conformance step in Phase 2.5** of [framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md). New row in the Completeness Check verification depth, runs only when `briefing = "ui-expert"` AND `features.has_design_system: true`. Greps changed files for hardcoded design values that bypass the registry (hex colors, `rgb(`, inline `box-shadow:` offsets, raw px/rem `border-radius:`/`font-size:`/spacing); verifies the `design_system_coherence:` block in the completion report; verifies new primitives ship their `components/<Name>.md` spec in the same commit; verifies new tokens land in `tokens-reference.md`. Each gap classified Partial → fix-coder sub-loop.

### Added — PRD-side enforcement (split-scope + mandatory enum)

- **§ MANDATORY Card Rules (Rule A + Rule B)** in [framework/.claude/skills/prd/references/backlog-phase.md](framework/.claude/skills/prd/references/backlog-phase.md). New section between MANDATORY Card Structure and Specialist Audits. **Rule A**: every child card MUST declare `owner_agent` with one of the five enum values; missing/empty/placeholder/non-enum BLOCKS commit. **Rule B**: cards with `owner_agent: ui-expert` MUST be UI-only — components, layout, styling, motion, accessibility — and MUST NOT bundle business logic, API integration, state management, validation logic, or backend changes; mixed steps split into a `ui-expert` card (visual, consumes mocked contract) + a `coder` card (logic, API, state) wired via `depends_on:`/`blocks:`. Ships a canonical Login feature split example (bad: single mixed card; good: `FEAT-XXXX-02` UI + `FEAT-XXXX-01` logic).
- **§ Agent Specialization Rules in [framework/.claude/agents/prd-card-writer.md](framework/.claude/agents/prd-card-writer.md)** before Card Atomicity Rules. The same Rule A + Rule B formulated from the agent writer's POV (which value to pick per work type, what NOT to bundle, when not to assign `ui-expert` to a tight-knit mixed card). Cross-references the backlog-phase split example for canonical guidance.
- **§ Step 6 Agent Specialization Audit (new step 0)** in [framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md). Runs FIRST, before the Field Name Audit. Step 0a: `owner_agent` enum check (BLOCKING) — halts the audit on missing/empty/placeholder/non-enum with an actionable message. Step 0b: UI-only scope heuristic (WARN, soft gate) — for each `ui-expert` card, greps `scope.summary` + `requirements` + `acceptance_criteria` for logic markers (`api call`, `endpoint`, `route handler`, `state machine`, `validation logic`, `business rule`, `database`, `migration`, `prisma`, `auth flow`, `token storage`, `webhook`, `cron`) and emits `[UI-SCOPE-WARN]` lines for human review. Intentionally soft: legitimate UI references to mocked contracts ("consumes `useAuth()` mock") don't trip the WARN thanks to the consume/mock/contract-reference exception.
- **§ Card-template fields updated** in [framework/.claude/skills/prd/assets/card-template.yml](framework/.claude/skills/prd/assets/card-template.yml). The placeholder `owner_agent: claude` is replaced with `owner_agent: <REQUIRED — coder | ui-expert | plan | visual-designer | motion-expert>` so a card left at the placeholder fails validation. A leading comment block documents each enum value (one line each) and a comment under `scope:` reminds the writer of the UI-only constraint for `ui-expert` cards.
- **§ Epic-template aligned to the tracker-only contract** in [framework/.claude/skills/prd/assets/epic-template.yml](framework/.claude/skills/prd/assets/epic-template.yml). `owner_agent` changes from the legacy `claude` placeholder to `""` (empty string) with an inline comment clarifying that epics are tracker-only — the dispatcher skips them and the validation-phase enum-check exempts them — so the empty value is the correct signal that no specialist is assigned to an epic. Prevents the false-positive of writing a child enum value (e.g. `coder`) on an epic and implying it carries implementation work.

### Notes

- **No new `baldart.config.yml` keys** — the schema-change propagation rule does not apply to this release. The router consumes an existing field (`owner_agent`) already present in the PRD card schema.
- **Backwards compatibility for cards written before v3.16.0.** The dispatcher accepts `owner_agent: ""` / missing / legacy `claude` value via the WARN-and-fallback-to-coder branch, so existing backlogs keep working — the WARN surfaces the gap on first use without blocking. New PRD sessions on v3.16.0+ produce enum-compliant cards from the start; the validation phase's enum BLOCKER applies only to NEW cards generated under v3.16.0+.
- **Specialist briefings for `plan` / `visual-designer` / `motion-expert` are stubs** in this release — they fall back to the coder briefing with a WARN. Specialized briefings will follow in future iterations without further schema changes (the router structure is already in place).
- **`ui-expert` in `/new` is implementation-only by design** — refinement of the mockup remains a PRD-phase responsibility (the `ui-design-phase` skill handles design decisions). If the implementation step finds a mockup-vs-registry conflict, `ui-expert` STOPs and asks the user; it does not auto-adapt the mockup. This boundary is documented in the `[UI-ONLY] Implementation-only constraint` briefing section.
- **REGISTRY.md decision tree** in [framework/.claude/agents/REGISTRY.md](framework/.claude/agents/REGISTRY.md) gains a header note clarifying that `/new` dispatches by `owner_agent` (not by re-running the decision tree) — the tree remains authoritative for ad-hoc spawns and for `prd-card-writer`'s writing decisions, with a lockstep-with-skill warning to keep the 5 layers in sync.
- **Verification is manual** (no test suite — `npm test` remains a no-op stub). Dogfood scenarios to run after install: (1) `/new <CARD-ID>` on a card with `owner_agent: coder` → identical behavior to v3.15.x (no regression); (2) `/new <CARD-ID>` on a card with `owner_agent: ui-expert` + valorized `links.design` → `ui-expert` is spawned (not coder), the briefing contains the BLOCKING cascade verbatim, the implementation reads `INDEX.md` + `tokens-reference.md` + `components/<Name>.md` BEFORE coding, the completion report ships the `design_system_coherence:` block, the commit reconciles the three design-system sources; (3) `/new` on a legacy card with `owner_agent: claude` or missing field → fallback to coder + WARN line in tracker; (4) `/prd` on a feature involving login (UI + logic) → produces TWO cards (`UI-LOGIN-*` ui-expert + `LOGIC-LOGIN-*` coder) wired via `depends_on:` rather than one mixed coder card; (5) PRD validation phase on a card with `owner_agent: ""` → enum BLOCKER, commit refused.

## [3.15.0] - 2026-05-23

Two changes ship together. **(1) PRD becomes mockup-aware** — formal intake of existing mockups between Kickoff and Discovery, with a per-screen decision tree in Step 3 that skips generation for screens already covered. **(2) Framework becomes stack-aware** — four new `stack.*` cardinal scalars (`database`, `auth_provider`, `framework`, `deployment`) drive vocabulary, gate sections, anti-pattern checklists, and deploy commands across `/prd`, `coder`, `api-perf-cost-auditor`, `/new`, `code-reviewer`, `security-reviewer`, `bug`. Project-identity assumptions previously hard-coded (mayo personas, real test credentials, Italian merchant business names) are removed from the framework and now resolved from `identity.audience_segments[]` + `.baldart/overlays/prd.md`. Sanitization sweep removed all PII (phone numbers, real usernames, real passwords, real store names) from the published payload.

### Added — Mockup-aware PRD

- **Step 1.6 — Mockup Intake** in [framework/.claude/skills/prd/references/discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md). New mandatory step between Kickoff (Step 1) and Discovery (Step 2). Asks the user "Hai dei mockup a disposizione?" — if yes, asks for format (chat images / local paths / mixed), STOPs, and on receipt: copies local files into `${paths.prd_dir}/<slug>/mockups/` (with collision-safe rename), analyzes every mockup against the **Mockup analysis schema** (screens, user_flow, components, states_visible, copy_excerpts, gaps, design_system_alignment with violations when `features.has_design_system: true`), and populates `## UI Design` in the state file before entering Discovery. If "no", marks `mockups.status: none` and proceeds with the standard flow — fully backwards-compatible for projects without mockups.
- **Step 3 decision tree (Full / Hybrid / Skip)** in [framework/.claude/skills/prd/references/ui-design-phase.md](framework/.claude/skills/prd/references/ui-design-phase.md). Replaces the legacy "skip if UI N/A" precondition. The tree reads `mockups.status` + `screens_in_scope[]` from the state file and routes per-screen: covered-by-mockups screens skip 3a (options) and 3b (generation) — only Component Registry Lookup, approval gate (3c), and inventory (3d) run; uncovered screens invoke the `ui-design` subskill scoped to that single screen, passing the existing mockups as "design language anchor" so the new screen stays consistent. Includes a canonical 5-screen example (3 covered + 2 new) showing exactly which subskill invocations happen and how the final inventory merges.
- **"Design Reference & Mockup Inventory" section in the PRD template** ([framework/.claude/skills/prd/assets/prd-template.md](framework/.claude/skills/prd/assets/prd-template.md)). New section inserted between Documentation Impact and Section 1 (Problem Statement). Tracks `mockups.status`, `step_3_mode`, the screen inventory table (mapping each screen to its user story + mockup path + origin), the component-mapping table when a design system is in play (reused + new components + token violations), and the source-files traceability list (original user paths → canonical `mockups/` paths, or `chat://image-N` for inline images).
- **Mockup-aware fields in the state template** ([framework/.claude/skills/prd/assets/state-template.md](framework/.claude/skills/prd/assets/state-template.md)). Extends `## UI Design` with `mockups.{status,format,original_paths,canonical_paths}`, `mockup_analysis.{screens,user_flow,design_system_alignment}`, `step_3_mode`, `design.html_path`, `screens_in_scope`, `ui_inventory`. Single source of truth for everything Step 1.6 produces and Step 3 + PRD Writing consume.
- **PRD writing acknowledges mockup canonicality** in [framework/.claude/skills/prd/references/prd-writing-phase.md](framework/.claude/skills/prd/references/prd-writing-phase.md). The canonical sources resolution step now adds the `mockups/` directory as a canonical visual reference when `mockups.status` ∈ {`provided`, `partial`}, and the PRD sections checklist includes a new "Design Reference & Mockup Inventory" entry plus revised UI Specifications guidance to avoid duplication.
- **Hard Rule 16 (Mockup Intake) + Step 1.6 in the flow overview + progress-bar row** in [framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md). Codifies the new step as MANDATORY in the hard-rules section, adds the row `1.6 Mockup intake` to both the in-progress and completed progress-bar templates (with the `Mockup` and `Step 3 mode` summary lines), and adds a "Mockup-driven pre-population" note to the Quick Reference Comprehension Dimensions explaining how dimension 5 (UI impact) and partially dimension 2 (User journey) are pre-resolved from `mockup_analysis` when mockups are provided.
- **New-Screen Check during Discovery** in [framework/.claude/skills/prd/references/discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md). When a user answer reveals a UI screen NOT in `mockup_analysis.screens[]`, the skill no longer treats it as a generic scope expansion (which would offer `/prd-add`). Instead it appends the screen to `screens_in_scope[]` with `covered_by_mockups: false` and logs inline that Step 3 will generate that single screen in Hybrid mode. The Scope Expansion Check still applies for non-UI entities (new endpoints, roles, collections).

### Added — Stack-aware framework

- **Four new `stack.*` scalar keys** in [framework/templates/baldart.config.template.yml](framework/templates/baldart.config.template.yml): `stack.database` (enum: firestore/supabase/postgres/mysql/mongodb/dynamodb/sqlite/none), `stack.auth_provider` (firebase-auth/supabase-auth/clerk/auth0/cognito/nextauth/lucia/custom/none), `stack.framework` (nextjs/remix/sveltekit/astro/nuxt/rails/django/fastapi/express/none), `stack.deployment` (vercel/firebase/aws/gcp/cloudflare/render/fly/self-hosted/none). Empty string preserves the always-ask contract — skills prompt the user when a needed scalar is unset and suggest `npx baldart configure`.
- **`baldart configure` autodetection + prompts for the 4 stack scalars** in [src/commands/configure.js](src/commands/configure.js). Autodetection probes `package.json` deps (`firebase`/`@supabase/*`/`pg`/`mongoose`/`@clerk/*`/`@auth0/*`/`next-auth`/`lucia`/`@aws-sdk/client-cognito-identity-provider`/`next`/`@remix-run/*`/`@sveltejs/kit`/`astro`/`nuxt`/`express`), file presence (`vercel.json`/`firebase.json`/`wrangler.toml`/`fly.toml`/`render.yaml`/`Gemfile`/`manage.py`/`requirements.txt`), and content patterns (Django/FastAPI in requirements, rails gem in Gemfile). Detected values pre-fill the interactive prompt. AUTODETECTED box surfaces all 4 values to the user.
- **`baldart update` schema-drift detector covers scalar `stack.*` keys** in [src/commands/update.js](src/commands/update.js). Extends the existing detector (which already flagged missing `features.*`, `paths.*`, `git.*` keys after an update) to enumerate `tpl.stack[*]` and report only the string-typed top-level scalars as `stack.<key>`. Sub-object keys (`charting`/`animation`/`testing`/`monorepo`/`design_system_signals`) are intentionally ignored — their drift is handled by their dedicated configure prompts. On missing scalars, the detector auto-offers `baldart configure`.
- **Database vocabulary branching in PRD writing** in [framework/.claude/skills/prd/references/prd-writing-phase.md](framework/.claude/skills/prd/references/prd-writing-phase.md). The Schema Verification Gate header now reads "MANDATORY if feature touches the persistence layer" instead of "if feature touches Firestore"; vocabulary adapts ({entity} = collection|table, {field} = field|column); the schema-registry path resolves via overlay → `stack.database` convention (Firestore `field-registry.json`, SQL `prisma/schema.prisma` or migrations dir, Mongo collection markdown). The PRD-template's Section 5 "Data Model" now ships **5 variants** (Firestore Composite, Relational B-tree+RLS, MongoDB compound, DynamoDB GSI/LSI, None) — the writer keeps only the variant matching `stack.database`.
- **api-perf-gate refactored as universal-core + stack addenda** in [framework/.claude/skills/prd/references/api-perf-gate.md](framework/.claude/skills/prd/references/api-perf-gate.md). Gate 5 (keyword scan): universal keywords + "Stack-specific keyword addenda" table activated only by the matching `stack.database`. Gates 1–3: universal checklist + "Stack-specific addenda" per `stack.database` (firestore listener/index/ID-hotspot rules, postgres/supabase RLS+EXPLAIN+JSONB rules, mongo compound-field-order+embed rules, dynamodb GSI+hot-partition rules) and per `stack.framework` (nextjs Route Handler `revalidate`/`use cache`, remix `headers`+`shouldRevalidate`, sveltekit runtime declaration, astro `prerender`). Reference Data section split into "Database pricing & cost-model snapshots" (4 stacks), "Runtime limits by `stack.deployment`" (Vercel/Cloudflare/AWS/Firebase table), "Framework caching primitives by `stack.framework`" (4 frameworks). User cites only the variant matching their config.
- **coder.md Database Index Invariant** in [framework/.claude/agents/coder.md](framework/.claude/agents/coder.md). Renamed from "Firestore Index Invariant"; ships per-stack instructions (Firestore composite + `firestore.indexes.json`, Postgres/Supabase `EXPLAIN` + migration with covering/GIN/RLS, MongoDB `createIndex` with equality→range→sort field order, DynamoDB GSI/LSI in CDK/Terraform, None/unset → skip with warning).
- **prd-card-writer Field Grounding Rule per-stack registry resolution** in [framework/.claude/agents/prd-card-writer.md](framework/.claude/agents/prd-card-writer.md). The Grep target for field verification is no longer hard-coded to `docs/references/field-registry.json` — it resolves via `.baldart/overlays/prd.md § Schema Registry` first, then by convention matching `stack.database` (Firestore field-registry, Prisma schema, SQL migrations, Mongo collection docs, DynamoDB table definition). Universal identifiers (`id`/`uuid`/PK, `createdAt`/`created_at`, `updatedAt`/`updated_at`) are exempt regardless of stack.
- **api-perf-cost-auditor reads stack scalars at session start** in [framework/.claude/agents/api-perf-cost-auditor.md](framework/.claude/agents/api-perf-cost-auditor.md). The agent's "Project Context" section is no longer hard-coded to "Next.js 16 + Firestore + Vercel Fluid Compute" — it now lists universal hard rules + addenda tables for `stack.framework`, `stack.database`, `stack.deployment`, and cites the resolved values in every audit header so the user knows which variant applied. Perf budgets move out of the agent into `${paths.references_dir}/perf-budgets.md` (consumer-owned); absence is flagged as a `BUDGETS_GAP` warning instead of using framework-baked numbers.
- **security-reviewer multi-stack access-rule coverage** in [framework/.claude/agents/security-reviewer.md](framework/.claude/agents/security-reviewer.md). Rule 6 (cloud/infra risks) lists the access-rule variant per `stack.database`: Firebase security rules, Supabase RLS policies, MongoDB validators + collection access, DynamoDB IAM policies, Postgres GRANT/REVOKE + RLS.
- **`/new` Production Readiness Checklist becomes stack-aware** in [framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md). Detection table and auto-executable commands branch on `stack.deployment` + `stack.database`. Firebase commands are no longer the default — they're one branch among many (vercel deploy, supabase db push, CDK/Terraform apply, firebase deploy). When `stack.deployment` is empty, the skill infers from config-file presence and falls back to asking the user — never auto-executes a guess.
- **`/new` end-to-end flow generalized for non-Firestore stacks** in [framework/.claude/skills/new/SKILL.md](framework/.claude/skills/new/SKILL.md). Five additional spots got the same treatment so a Supabase / Postgres / Mongo / DynamoDB project doesn't see Firestore-only instructions: pre-flight `data_fields` warning triggers on `db_indexes` (any dialect) and reads "touches the persistence layer" instead of "touches Firestore"; Codex parallel-conflict analyzer asks about same-{entity} writes in the project's vocabulary; DEEP-review trigger keys off `db_indexes` (legacy `firestore_indexes` still recognized); the Coder Briefing's index section ships per-`stack.database` instructions (Firestore JSON snippet, SQL migration / Prisma `@@index`, Mongo `createIndex`, DynamoDB GSI/LSI in IaC) and explicitly tells the agent which artifact to stage; post-implementation verification of compound queries branches into 4 dialects with their own missing-index severity; Docs Routing Reminder uses `{entity}` vocabulary and points at the stack-matched index artifact; and the post-deploy "DB Index Verification" section (previously Firestore-only with curl to the Firestore REST API) now opens with a Skip rule and 4 dialect variants (Firestore REST API, Postgres `pg_indexes` + `pg_stat_progress_create_index`, Mongo `getIndexes` + `currentOp`, DynamoDB `describe-table` GSI status). The Firestore variant retains its full procedure verbatim — no regression for Firestore consumers.
- **bug/logging-patterns.md framework-agnostic** in [framework/.claude/skills/bug/references/logging-patterns.md](framework/.claude/skills/bug/references/logging-patterns.md). The Next.js 16 logging section is now one of four (Next.js, Remix, SvelteKit, Astro/Nuxt) plus a generic `pino`/`winston` fallback. DB-tracing env var examples document the per-stack equivalent (`DEBUG_FIRESTORE=true`, `SUPABASE_DEBUG=true`, `DEBUG=knex:query`, `DEBUG=mongoose:*`).
- **Persona references generalized to `identity.audience_segments[]`** in [framework/.claude/skills/prd/references/discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md), [framework/.claude/skills/prd/references/prd-writing-phase.md](framework/.claude/skills/prd/references/prd-writing-phase.md), [framework/.claude/skills/prd/references/audit-phase.md](framework/.claude/skills/prd/references/audit-phase.md), [framework/.claude/skills/prd/assets/card-template.yml](framework/.claude/skills/prd/assets/card-template.yml), [framework/.claude/skills/prd/assets/epic-template.yml](framework/.claude/skills/prd/assets/epic-template.yml), [framework/.claude/skills/prd/assets/prd-template.md](framework/.claude/skills/prd/assets/prd-template.md). Hard-coded persona literals (`CUSTOMER`/`MERCHANT`/`MERCHANT_STAFF`/`SUPER_ADMIN`) are gone from the framework — replaced by `{{from identity.audience_segments[]}}` placeholders and explicit instructions to loop over the project-declared segments. AC grouping in PRD writing now reads "one group per persona drawn from `identity.audience_segments[]`, plus cross-cutting groups".
- **`db_indexes` field replaces `firestore_indexes` in card YAML** in [framework/.claude/skills/prd/assets/card-template.yml](framework/.claude/skills/prd/assets/card-template.yml). The card template renames the section; entries gain a `dialect` field matching `stack.database` and a `kind` field (composite/covering/partial/GIN/GSI/LSI/unique/fulltext). The legacy `firestore_indexes` name remains accepted as an alias so v3.14.x cards keep working — the validator in [framework/.claude/skills/prd/references/validation-phase.md](framework/.claude/skills/prd/references/validation-phase.md) recognizes both. Metric `has_firestore_indexes_pct` in [framework/.claude/skills/prd/SKILL.md](framework/.claude/skills/prd/SKILL.md) renamed to `has_db_indexes_pct`.
- **PROJECT-CONFIGURATION.md § 4.4 documents the 4 new scalars** in [framework/docs/PROJECT-CONFIGURATION.md](framework/docs/PROJECT-CONFIGURATION.md). New "Stack-cardinal scalars" subsection enumerates the 8 skills/agents that branch on them, the always-ask contract on empty values, and the schema-drift detector behavior.

### Removed — PII sanitization

- **Test credentials (phone, username, password, store name) removed from the published payload.** [framework/.claude/skills/prd/references/discovery-phase.md](framework/.claude/skills/prd/references/discovery-phase.md), [framework/.claude/skills/prd/assets/prd-template.md](framework/.claude/skills/prd/assets/prd-template.md), [framework/.claude/skills/prd/assets/card-template.yml](framework/.claude/skills/prd/assets/card-template.yml) no longer ship any real credential as "example". The discovery Test Strategy step now (a) consults `.baldart/overlays/prd.md § Test Credentials` first, (b) asks the user one structured question per persona if the overlay is silent, (c) stores a credential REFERENCE (secret-manager path, `.env.test:VAR_NAME`) in the state file instead of the literal secret, and (d) explicitly forbids the framework from carrying real PII.

### Notes

- **One CLI change — `baldart configure`.** v3.15.0 adds autodetection + prompts for the 4 `stack.*` scalars and extends the `baldart update` schema-drift detector to flag missing scalar `stack.*` keys. Consumers upgrading from v3.14.x get the new prompts on the next `baldart configure` invocation; the detector triggers automatically on the next `baldart update`.
- **The schema-change propagation rule applied end-to-end** for the 4 new scalars: template ✓, configure prompt ✓, update detector ✓, doctor not extended (the always-ask contract in skills already covers the empty case), CHANGELOG ✓.
- **Backwards-compatibility for v3.14.x cards.** Cards with the legacy `firestore_indexes` field still validate (the writer-side renamed it to `db_indexes`, but the validator and downstream agents accept both names). Cards with `type: firestore` in `data_sources` are equivalent to `type: db, db_dialect: firestore`.
- **Active PRD sessions started under v3.14.x** do not have `## UI Design` populated with the mockup fields; the skill treats `pending`/missing as `mockups.status: none` and falls into the Full Step-3 branch (legacy behavior). New sessions on v3.15.0+ pick up Step 1.6 automatically.
- **Step 3 Hybrid mode preserves the design-system cascade.** When `features.has_design_system: true`, the BLOCKING reads of `${paths.design_system}/INDEX.md` + `tokens-reference.md` run BOTH in Step 1.6 (during mockup analysis, to flag violations early) AND in Step 3 (during per-screen Component Registry Lookup, to validate covered + new screens uniformly). Violations bubble up to the unified 3c approval gate.
- **Sanitization is final, not a heuristic.** Grep across `framework/` for `3486417303`, `antonio.baldassarre2336`, `bRkS2bzqyesK`, `Bar Roma` returns zero hits after this release. Consumers who already had `.baldart/overlays/prd.md` with their real credentials are unaffected — the overlay never leaves the consumer repo.
- **Verification is manual** (no test suite — `npm test` is no-op stub). Scenarios to dogfood after install: (1) `npx baldart update` on a v3.14.x consumer → schema-drift detector flags 4 new `stack.*` keys → `baldart configure` autodetection pre-fills them; (2) `/prd` on a Supabase project (`stack.database: supabase`) → Schema Verification Gate uses Variant B (Relational), api-perf-gate adds postgres+supabase addenda, audit-phase enforces RLS; (3) `/prd` on a Firestore project (`stack.database: firestore`) → behavior identical to v3.14.x (no regression); (4) `/prd` with mockups → mockup intake + Hybrid Step 3; (5) `/new` on a Cloudflare deployment (`stack.deployment: cloudflare`) → no `firebase deploy` commands proposed.

## [3.14.1] - 2026-05-23

Hotfix for the `.framework/` gitignore self-heal shipped in v3.14.0. The detector misread `git check-ignore -v` output: when `check-ignore` matched a negation pattern (`!.framework`), it still reported the match, and `checkFrameworkIgnored` flagged the path as ignored. The result: every consumer who hit the self-heal on first run got `.framework/ is STILL ignored after auto-heal` and `process.exit(1)` — the heal *had* actually worked (the negation was in place), but the verifier didn't recognize its own fix. Reported during the v3.13.0 → v3.14.0 update in a downstream consumer.

### Fixed

- **`GitUtils.checkFrameworkIgnored()` recognizes negation patterns** in `src/utils/git.js`. Per `gitignore(5)`, a leading `!` flips the match semantics: `check-ignore` still prints the pattern (matching ≠ ignored), but the path is RE-INCLUDED, not ignored. The detector now inspects the pattern, returns `{ ignored: false, negatedBy }` on a negation match, and `ensureFrameworkNotIgnored`'s post-heal recheck reports `stillIgnored: false` as expected. Smoke-tested end-to-end: `.gitignore` with `.framework` → heal → recheck reports not-ignored → idempotent second call is a no-op → `git add .framework/foo` succeeds.
- **CHANGELOG consolidation for v3.14.0.** The original 3.14.0 entry had a duplicate `### Added` / `### Notes` block (LSP-only Notes + gitignore-only Notes side by side). Merged into a single Notes section so the release reads as one feature shipped, not two.

### Notes

- **v3.14.0 is functionally broken on first install of the heal.** Consumers who upgrade `npm i -g baldart@latest` get 3.14.1 directly and never observe the regression. Consumers who already updated to 3.14.0 (CLI binary or `npx baldart`) should reinstall: `npm i -g baldart@latest`, then re-run `baldart` — the heal converges on the second pass because the negation block already in `.gitignore` short-circuits the detector cleanly.
- **No framework payload changes.** Single-file fix in `src/utils/git.js` plus CHANGELOG.

## [3.14.0] - 2026-05-23

BALDART closes the **LSP install completeness gap** AND adds a long-overdue **`.framework/` gitignore self-heal** so install / update / push never trip over a stale or pre-existing ignore rule.

### LSP install completeness

Since v3.10.0, opting into `features.has_lsp_layer: true` installed the language-server binary (typescript-language-server, pyright, gopls, rust-analyzer, ruby-lsp) into the consumer repo — but the matching native Claude Code plugin (`<lang>-lsp@claude-plugins-official`) was left for the user to install manually via `/plugin install`. Almost nobody did, so Claude itself never invoked the LSP and code-exploration silently fell back to Grep. The "opt-in to LSP" UX was effectively broken: users thought they had it, agents behaved as if they didn't. v3.14.0 wires the two layers together — when the user opts into LSP, BALDART now installs the binary AND enables the native plugin in the same `configure` step, idempotently and gated on `claude` being in `tools.enabled`.

### `.framework/` `.gitignore` self-heal

The other historical sharp edge in BALDART: consumers who had `.framework` (or a parent pattern like `framework/` or `*framework*`) in `.gitignore` saw every `baldart push` and most `baldart update` autofix flows blow up with `paths are ignored by one of your .gitignore files`, leaving half-finished `chore:` commits on HEAD and requiring a manual `git reset --hard` to recover. The path is a git subtree managed by BALDART — it MUST be tracked — and the failure mode was both confusing (the user never added `.framework` to ignore on purpose) and unfixable from inside the broken flow. v3.14.0 detects the collision and rewrites `.gitignore` idempotently at the top of every install / update / push, so a single `baldart` invocation always converges to a working state.

### Added

- **`GitUtils.checkFrameworkIgnored()` + `GitUtils.ensureFrameworkNotIgnored()`** in `src/utils/git.js` — central, idempotent self-heal for the `.framework/` ↔ `.gitignore` collision. `checkFrameworkIgnored` runs `git check-ignore -v --no-index -- .framework` and returns `{ ignored, source, line, pattern, raw }` (degrade-open: simple-git throws on non-zero exit, treated as "not ignored"). `ensureFrameworkNotIgnored` strips standalone `.framework` / `.framework/` / `/.framework` rules from the project `.gitignore`, appends a single negation block (`!.framework` + `!.framework/**`) with a marker comment so re-runs are no-ops, then re-runs the check and surfaces `stillIgnored` if a rule still wins from outside the repo (parent-dir `.gitignore`, exotic `core.excludesFile`). Returns `{ wasFixed, stillIgnored, removedLines, appendedNegation, source, pattern, recheck }`. Never throws — callers can safely wrap it in best-effort try/catch.
- **`baldart add` / `baldart update` / `baldart push` auto-heal `.gitignore`** — each command calls `ensureFrameworkNotIgnored()` at the top of its flow, before `git subtree add` / `git subtree pull` / any `git add` runs. Healing prints a yellow warning so the user sees what changed, then continues without prompting (purely local file rewrite, fully idempotent). If a rule outside the project still wins, the flow exits with a clear, actionable message instead of failing deep in the autofix step with the cryptic `paths are ignored` git error.
- **`baldart doctor` `.gitignore` diagnostic + `unignore-framework` action** — `detectState` populates `state.frameworkIgnored` with the same payload; the status table renders a yellow `.gitignore IGNORES .framework/ (<source>:<line>) — will be auto-healed` row when triggered, and the planner inserts an `autoOk: true` action that calls the same helper. So `baldart` (no-arg doctor mode) detects, reports, and self-heals in a single invocation. Backfills the heal for consumers predating v3.14.0 that hit the broken-push state.
- **`claudePluginId()` returns real plugin ids** in every LSP adapter (`src/utils/lsp-adapters/{typescript,python,go,rust,ruby}.js`). Previously a `null` placeholder; now resolves to `typescript-lsp@claude-plugins-official`, `pyright-lsp@claude-plugins-official`, `gopls-lsp@claude-plugins-official`, `rust-analyzer-lsp@claude-plugins-official`, `ruby-lsp@claude-plugins-official` respectively. All plugins live in the Anthropic-published [`anthropics/claude-plugins-official`](https://github.com/anthropics/claude-plugins-official) marketplace.
- **`LspInstaller.installClaudePlugins(serverNames, opts)`** in `src/utils/lsp-installer.js` — high-level installer that (1) verifies the `claude` CLI is on PATH, (2) idempotently registers the `claude-plugins-official` marketplace via `claude plugin marketplace add anthropics/claude-plugins-official --scope user` (no-op when already registered — list is parsed first), (3) reads `claude plugin list --json` to skip plugins already installed at any scope, (4) runs `claude plugin install <id> --scope user` per missing plugin. Defaults to `--scope user` so a single install is reused across every project on the machine (LSP plugins are machine-wide useful — project scope would create N copies). Always returns `{ installed, skipped, manual, marketplace }`; never throws.
- **`LspInstaller.ensureClaudeMarketplace()`, `isClaudeCliAvailable()`, `listInstalledClaudePlugins()`, `verifyClaudePlugins(serverNames)`** — supporting primitives, all idempotent and degrade-open (parsing failure returns empty set → next install is attempted rather than incorrectly skipped).
- **`baldart configure` companion-plugin step** — after installing the language servers, the configure flow now calls `installClaudePlugins(merged.lsp.installed_servers)`. Surfaces clear messaging for each branch: success (✓ Installed plugin ids), idempotent skip ("Already installed"), Claude CLI missing (manual install commands printed verbatim per plugin), or `claude` opted out of `tools.enabled` (silent skip — Codex-only users don't want Claude marketplace mutation).
- **`baldart doctor` companion-plugin diagnostic** — `detectState` now calls `verifyClaudePlugins` when LSP is enabled and the `claude` CLI is available, populating `state.lspMissingClaudePlugins`. The `LSP layer` row in the diagnostic table reports `N server(s) verified, M Claude plugin(s) missing (…)` when the layer is half-installed. Severity `warn`.
- **`lsp-install-claude-plugins` action** in the doctor planner — `autoOk: true` (user-scope plugin install is idempotent and fully reversible via `claude plugin uninstall`). Backfills missing plugins for installs predating v3.14.0 and recovers from manual plugin uninstalls. Pairs with other actions; never short-circuits.

### Changed

- **LSP opt-in is now end-to-end.** Pre-v3.14.0: `features.has_lsp_layer: true` installed the binary but the integration was dormant until the user manually enabled the plugin. Post-v3.14.0: opting in installs both layers in the same step. The framework's "always-ask, never-assume" contract still applies — when `claude` is not in `tools.enabled` OR the `claude` CLI is missing, the install gracefully degrades (no error, clear hint, deferred until the prerequisites are in place). `baldart doctor` then surfaces the half-install state on every subsequent invocation.

### Notes

- **No new `baldart.config.yml` keys.** The plugin install reuses `features.has_lsp_layer`, `lsp.installed_servers`, and `tools.enabled` already in place — so the [schema-change propagation rule](../.claude/projects/-Users-antoniobaldassarre-BALDART/memory/feedback_schema_change_propagation.md) does not require a new template / configure prompt / update detector for this release. Scope is hard-coded to `user` (machine-wide LSP plugins) rather than exposed as `lsp.claude_plugin_scope` — the decision is opinionated and consistent with how the official marketplace tracks its own installs. If a future release needs per-project plugin scope, *that* would trigger the propagation rule (template + prompt + update detector + this changelog).
- **Marketplace and plugins live at `--scope user`, not project.** Three reasons: (1) LSP plugins are machine-wide useful — typescript-lsp installed once at user scope is reused by every TS project on the machine, not duplicated N times; (2) marketplace install at user scope means subsequent `baldart configure` runs in sibling projects skip the marketplace add entirely (idempotent); (3) project-scope plugins live under `.claude/` in the consumer repo, which would either need committing (noisy diffs) or `.gitignore` adjustments (extra protocol burden). User scope avoids all three.
- **Idempotency is checked twice.** Marketplace add reads `claude plugin marketplace list --json` first; plugin install reads `claude plugin list --json` first. Re-running `baldart configure` (or `baldart doctor` with the action accepted) is a no-op when everything is in place — no spurious install commands, no error noise.
- **Gate on `claude` in `tools.enabled`.** When the consumer has `tools.enabled: [codex]` (Codex-only), the plugin install is a silent no-op — Codex has no equivalent plugin model and Claude marketplace mutation would be off-target. Doctor diagnostics for missing plugins are similarly gated.
- **No framework payload changes.** All work is inside `src/` — adapters, installer, configure command, doctor command, and `GitUtils`. No framework agent/skill/command/protocol module touched, so existing consumers see the new behavior the next time they run `baldart configure` or `baldart doctor` after upgrading the CLI (`npm i -g baldart@latest`).
- **`.gitignore` heal is idempotent across re-runs.** The negation marker is checked before appending; the strip step is a `filter` over current lines. Running `add` / `update` / `push` repeatedly on a healthy repo is a no-op (the negation block is added once, future calls match the marker and skip).
- **`.gitignore` heal has no false positives.** The strip filter matches only the exact forms (`.framework`, `.framework/`, `/.framework`, `/.framework/`, `.framework/*`, `/.framework/*`) — `.frameworks/` or `framework/something.bak` patterns are preserved untouched.

## [3.13.0] - 2026-05-23

BALDART closes **two drift visibility gaps in one release**. (1) The CLI binary (installed globally via `npm i -g baldart`) and the framework payload (`.framework/` inside each consumer repo, updated by `baldart update`) drifted silently — users on an old CLI never learned that new flags, new doctor checks, or new adapters had shipped. (2) The framework payload on disk and the `.baldart/state.json` ledger could drift silently when the ledger write failed (the catch in `update.js` was silent until now), when an update flow exited mid-way, or when a manual commit excluded `.baldart/state.json` — `baldart doctor` had no way to detect or fix this. Both gaps are now closed: a **non-invasive CLI self-update notifier** for the first, and **ledger drift detection + self-heal** in the doctor for the second. The unifying theme is "no silent drift" — every layer that can fall out of sync now reports it and offers a one-step fix.

### Added

- **`src/utils/update-notifier.js`** — best-effort CLI self-update check. HTTPS GET to `https://registry.npmjs.org/baldart/latest` with a 1.5s timeout, cached at `~/.baldart/cli-update-check.json` (TTL 24h). Banner is printed from the cache on subsequent invocations (the very first run shows nothing — that is intentional; the check never blocks the command). The background refresh fire-and-forget: its timer is `unref()`'d and the underlying socket dies with the process, so the check can never hold the event loop open. Suppression: `BALDART_NO_UPDATE_CHECK=1`, `CI=true`, `NODE_ENV=test`, non-TTY stdout, or `--offline` on `baldart` / `baldart doctor` / `baldart version`.
- **Top-of-run banner in `bin/baldart.js`** — when a newer baldart npm package has been detected on a previous run, every command prints a 3-line hint at startup (yellow arrow + bold version, then two gray lines): `↑ baldart X.Y.Z available (you have A.B.C)` + `Update with: npm i -g baldart@latest` + `Suppress with: BALDART_NO_UPDATE_CHECK=1`. Zero-latency (reads cache only); no banner if cache is missing or up-to-date.
- **`CLI` row in `baldart doctor` diagnostic table** — always shown (independent of git/framework state), surfaces drift inline alongside framework version / config / overlays / hooks so the doctor remains the single pane of glass. Severity `warn` when an update is available, `ok` otherwise.
- **CLI drift in `baldart version` box** — the existing `CLI: vX.Y.Z` line now appends `→ vA.B.C available (npm i -g baldart@latest)` when a newer version was detected.
- **`State.reconcileInstalledVersion({ to, reason })` in `src/utils/state.js`** — new ledger reconciliation helper. Idempotent (no-op when `from === to`), audit-trailed (writes a `ledger-reconcile` history entry distinct from real `update` events), and reversible (pure local file write). Used by the doctor's new `reconcile-state-ledger` action and available for direct call from future recovery flows.
- **Ledger drift detection in `baldart doctor`** — `detectState` now compares `.framework/VERSION` against `.baldart/state.json:installed_version` and surfaces a `stateLedgerDrift` state field when they diverge. The action planner inserts a `reconcile-state-ledger` action (`autoOk: true`, idempotent, no remote effects) that one-shot fixes the drift via `State.reconcileInstalledVersion()`. Drift is non-blocking — it pairs with other proposed actions instead of short-circuiting them.

### Changed

- **`src/commands/update.js`** — replaced the silent `try { State.recordUpdate(...) } catch (_) {}` on the ledger write with a visible warning + actionable next step. When the write fails or returns an unexpected `installed_version`, the user now sees a yellow warning explaining the drift (framework on disk is at vNEW but ledger still records vOLD) and is told to run `baldart doctor` to reconcile. This is the root cause of most "my doctor says I'm on the wrong version" reports — silent catches were hiding write failures (permissions, EBUSY) for weeks. New behavior makes the failure immediately visible and self-healable.

### Notes

- **CLI vs framework — two independent layers.** `npm i -g baldart@latest` updates the binary on disk (this package); `baldart update` updates the *framework payload* inside the consumer repo (the `.framework/` subtree). They are *independent* by design — a consumer may want to update the framework without touching the global CLI (or vice versa). The notifier surfaces drift on the *CLI* layer; the existing `Remote` row in doctor (and the `Remote:` line in `version`) surfaces drift on the *framework* layer. Both layers are now visible at a glance from the same diagnostic surface.
- **Why not auto-install.** Three reasons we deliberately reject `npx baldart` auto-running `npm i -g`: (1) blast radius — global install impacts *every* project on the user's machine, not just the one they invoked from; (2) permissions — `npm i -g` often needs sudo (without nvm/volta), and a no-args `baldart` cannot trigger sudo prompts silently; (3) reproducibility — `--auto` runs in CI must be deterministic, so auto-updating the CLI mid-run would change behavior unpredictably. The notifier is the right level: visible, dismissible, never invasive.
- **No new `baldart.config.yml` keys.** The notifier cache lives in `~/.baldart/cli-update-check.json` (per-user, not per-project), so the schema-change propagation rule (per `feedback_schema_change_propagation`) does not apply to this release. Suppression is via environment variable (`BALDART_NO_UPDATE_CHECK=1`) which is intentionally global — disabling the notifier in one project but enabling it in another is not a use case worth modeling.
- **Backwards-compatible.** Pre-3.13 consumers see the new banner on their next `baldart` run after the npm package upgrades. Consumers that never want the check set `BALDART_NO_UPDATE_CHECK=1` in their shell rc; CI runs are auto-suppressed.
- **No framework payload changes.** All work is inside `src/` and `bin/` — the framework agents/skills/commands/protocols are unchanged. This is purely a CLI-layer improvement; no consumer files are touched by `baldart update`.

## [3.12.0] - 2026-05-23

BALDART completes the **UI excellence loop**: the `ui-expert` agent is upgraded from a "good but generic" baseline to a world-class UI/UX reviewer/designer, and — crucially — the registry-first discipline gains a **post-intervention coherence gate** so visual coherence is enforced not only when work begins (BLOCKING reads) but also when work ends (BLOCKING completion check). The asymmetry of v3.11.0 (registry read before, no verification after) is closed: every UI change introduced by `ui-expert`, the `ui-design` skill, the `frontend-design` skill, or caught by `code-reviewer` must now reconcile the registry (INDEX + per-component specs + tokens-reference) in the **same change**, never deferring drift to the weekly `ds-drift` routine. This release contains zero schema changes — the cascade reuses `features.has_design_system` + `paths.*` already in place.

### Added

- **`framework/agents/design-system-protocol.md` § "Reference Tables (embed)"** — canonical numeric ranges that every agent/skill must cite verbatim: type scale (6 modular ratios + line-height pairing rule), color contrast targets (WCAG 2.x + APCA Lc as secondary perceived-legibility gate), spacing scale (base-4 vs base-8 with use-case mapping), density tiers (compact/cozy/comfortable/spacious with row-height + padding numbers). Replaces the previous "agents pick their own numbers" failure mode.
- **`framework/agents/design-system-protocol.md` § "Token Cascade: primitive → semantic → component"** — three-layer token discipline (raw → purpose-named → optional component alias) that makes components theme-agnostic and multi-brand-ready by construction. Documents OKLCH > HSL for palette generation and forbids `filter: invert()`-style dark mode shortcuts.
- **`framework/agents/design-system-protocol.md` § "Post-Intervention Coherence Check"** — new BLOCKING completion gate (4 verifications + decision matrix + reporting format + relationship with the weekly `ds-drift` routine as safety net). This is the SSOT consumed by `ui-expert`, `ui-design`, and `code-reviewer`.
- **`framework/.claude/skills/motion-design/reference/accessibility-and-modern-apis.md`** — new reference file covering `prefers-reduced-motion` collapse semantics (0.01ms, not 0 — Safari bug), the View Transitions API (same-doc + shared-element + cross-doc), spring vs cubic-bezier decision matrix, choreography budget (≤ 600ms total), and animations that communicate state and must NOT be collapsed under reduced-motion.
- **`framework/.claude/skills/frontend-design/SKILL.md` § "Performance Gates (Core Web Vitals 2026)"** — CWV thresholds table (LCP / INP / CLS / TTFB / FCP) with p75 good/poor cuts, canonical LCP image pattern (preload + fetchpriority + AVIF/WebP + explicit dimensions), INP fixes (`scheduler.yield`, debounce-to-frame, off-main-thread parsing), CLS budget (dimensions on every replaced element, font-display strategies, reserved space for late-injected UI).
- **`framework/.claude/skills/frontend-design/SKILL.md` § "Modern CSS (2025–2026)"** — container queries as default for component-level responsiveness, `:has()` for styling-only state reactions (never business logic), View Transitions API, subgrid, logical properties for free i18n, `color-mix()` for derived states, `@scope`.
- **`framework/.claude/skills/frontend-design/SKILL.md` § "Form quality (always-on requirements)"** — non-optional form contract (label association, `autocomplete=`, validation on blur, actionable error messages, `aria-invalid` + `aria-describedby`, `inputMode=`, paste normalization, submit-disabled-only-after-failed-attempt).
- **`framework/.claude/skills/ui-design/SKILL.md` Step H — Registry Coherence Reconciliation** — new BLOCKING completion gate inside the `ui-design` workflow that walks the UI Element Inventory from Step G and reconciles each item with INDEX + per-component spec + tokens-reference before sign-off. Catches drift at design time when it's cheapest to prevent.
- **`framework/docs/UPGRADE-3.12-UI-COHERENCE.md`** — onboarding doc consumable by Claude in any consumer repo. Step-by-step alignment of an existing project's UI organization with the new discipline: registry presence check, overlay drift audit, token/primitive hardcoding scan, one-shot `ds-drift` baseline, prioritized reconciliation backlog.

### Changed

- **`framework/.claude/agents/ui-expert.md` (massive upgrade — from ~250 to ~500 lines)** — § "Your Expertise" expanded to cite WCAG 2.2 SCs (2.4.11/2.4.12 focus, 2.5.5 target size, 1.3.5 input purpose) + APCA, OKLCH-based palette generation, modern CSS surface (container queries, `:has()`, View Transitions, subgrid, logical properties), motion intent-driven curve selection, Core Web Vitals literacy, AI-era patterns, inclusive design beyond AA, i18n with text-expansion buffers. **New § "UI States Taxonomy"** (8 states explicitly: idle / loading / empty / partial / success / error / offline / optimistic) with loading rules (sub-1s → no indicator, 1–10s → skeleton, > 10s → progress with cancel) and skeleton-must-match-real-dimensions discipline. **New § "Token Cascade Discipline"** (semantic-only consumption, primitive → semantic → component, OKLCH preferred). **New § "Composition over Configuration"** (Radix Slot / `asChild`, controlled+uncontrolled contract). **New § "AI-Era UI Patterns"** (streaming-without-CLS, always-visible stop/regenerate, ARIA live regions, confidence affordance, hallucination guardrails). **New § "Performance Gates"** with UI-level red flags. **Red flag list** expanded from 14 generic items to **60+ categorized findings** (registry & tokens / minimalism / architecture / mobile / a11y / state design / forms / motion / data viz / modern CSS / i18n). **Workflow steps** gain step 10 (Review) and step 12 (Design) — both BLOCKING completion gates that run the Post-Intervention Coherence Check. **Linked Skills** section enabled (was commented out) with explicit "MUST use" routing to `frontend-design`, `motion-design`, `ui-design`, `ui-ux-pro-max`, `webapp-testing` / `playwright-skill`.
- **`framework/.claude/agents/code-reviewer.md` § "Design System Compliance"** — gains rule 8 (Post-Intervention Coherence Check as BLOCKING merge gate). If the check trips at code-review time, it means the upstream `ui-design`/`ui-expert` checks were skipped — the violation is flagged alongside the drift. Code-reviewer is the third and final gate before the weekly safety net.
- **`framework/.claude/skills/ui-design/SKILL.md` § "Design Principles (quick reference)"** — gains a BLOCKING bullet point pointing at Step H so the registry-coherence rule is visible even to readers who skim only the quick reference.

### Notes

- **No new `baldart.config.yml` keys.** The post-intervention gate, the upgraded `ui-expert`, the new motion / frontend-design sections, and the `code-reviewer` rule 8 all gate on the existing `features.has_design_system` and read paths already declared in `paths.*`. The schema-change propagation rule is satisfied — this release only adds behaviors, expands an agent, and adds reference content.
- **Backwards-compatible.** Projects with `has_design_system: false` see no behavioral change for the registry-coherence gate (it short-circuits when the flag is false). Projects with `has_design_system: true` get the gate active immediately on next agent/skill invocation — if their codebase already has drift (silent primitives, missing specs, hardcoded tokens), the agent will surface it as findings on the first UI task post-update rather than fixing it silently. Use the `UPGRADE-3.12-UI-COHERENCE.md` doc to baseline and prioritize the reconciliation.
- **Three-layer gating design.** Drift detection is now redundant by design: (1) `ui-design`/`ui-expert`/`frontend-design` post-intervention check (per-task) → (2) `code-reviewer` rule 8 (per-merge) → (3) `ds-drift` routine (weekly safety net). The failure of any one layer is caught by the next. The per-task check is the cheapest reconciliation point — the weekly routine should now find very little to report when the upstream gates run correctly.
- **SSOT discipline preserved.** All numeric reference tables (type scale, contrast, spacing, density, CWV thresholds, motion durations / easings, spring parameters) live in exactly one place. The agent is explicitly instructed to cite from there verbatim and not invent numbers. The four post-intervention conditions are defined once in `design-system-protocol.md` and referenced from `ui-expert`, `ui-design`, and `code-reviewer` rather than restated.
- **Migration for consumers.** No file the user owns is touched. `baldart update` symlinks the upgraded framework payload; `.baldart/overlays/ui-expert.md` (if present) keeps overriding sections by name as before — but consumers with overlays should run the `UPGRADE-3.12-UI-COHERENCE.md` walkthrough to spot overlay sections that may now conflict with the expanded base (e.g. overlays that previously codified red flags or workflow steps the base now defines authoritatively).

## [3.11.0] - 2026-05-23

BALDART makes the **component registry the SSOT for visual coherence**. Until now, the registry-first discipline (`${paths.design_system}/INDEX.md` + `tokens-reference.md` + `components/<Name>.md`) was BLOCKING only inside the `ui-design` and `frontend-design` skills, while the `ui-expert` agent — the most-used entry point for ad-hoc UI edits — and the `/design-review` command had only a generic "follow the style guide" prompt. This release closes the gap end-to-end (agent + skill + command + onboarding + bootstrap) so every UI change passes through the same inventory and token contract, regardless of which entry point routes the work. A new `/design-system-init` skill scaffolds a registry from the codebase's current state, so projects without one can adopt the discipline with a single command.

### Added

- **`framework/agents/design-system-protocol.md`** — new protocol module that is the textual SSOT of the registry-first cascade (Component Discovery, token contract, Authority Matrix, "primitive missing" decision tree). Referenced from `framework/agents/index.md`. All UI-touching agents/skills/commands cite it instead of duplicating the BLOCKING-reads block. Same pattern as `code-search-protocol.md`.
- **`framework/.claude/skills/design-system-init/`** — new invokable skill (`/design-system-init`). Inventories primitives via `codebase-architect`, extracts tokens from `${paths.global_styles}` + Tailwind config, scaffolds `INDEX.md` + `tokens-reference.md` + a `components/<Name>.md` per primitive (template under `scripts/component-spec.template.md`), and flips `features.has_design_system: true` + `paths.design_system` in `baldart.config.yml`. Idempotent — refuses to overwrite an existing registry, supports incremental re-runs.
- **`scripts/component-spec.template.md`** — dense per-component spec template (props, variants, tokens consumed, accessibility, anti-patterns, MUST rules, changelog) used by `design-system-init` and by hand when extending the registry.
- **Design-system bootstrap hint in `configure`** — when the user opts in to `has_design_system: true` but autodetect found no path on disk, configure now surfaces `/design-system-init` as the next step so the flag doesn't end up pointing at nothing. When the user opts out, a soft pointer to `/design-system-init` is shown for future adoption.

### Changed

- **`framework/.claude/agents/ui-expert.md`** — now follows the registry-first protocol. Gains a Project Context header citing `baldart.config.yml` keys, replaces the lone `ui-guidelines.md` BLOCKING-read with the full cascade (`INDEX.md` + `tokens-reference.md` + per-component specs) gated by `has_design_system`, prepends Component Discovery (registry → source-tree → catalog → create) to both `When Reviewing UI Code` and `When Designing New Interfaces`, and adds red flags for re-implementing existing primitives and hardcoding values that bypass the token contract.
- **`framework/.claude/commands/design-review.md` + `framework/agents/design-review.md`** — both gain a step 0 BLOCKING when `has_design_system: true` (read `INDEX.md` + `tokens-reference.md` + `components/<Name>.md` for primitives on the route under review). The checklist gains a new top section **Design-System Conformance** (primitives reused, primitives reinvented HIGH, tokens bypassed HIGH, spec drift MEDIUM, new primitives proposed). The agent module ships a Report Template for that section; the command file points the subagent at it.
- **`framework/.claude/agents/code-reviewer.md`** — the existing § "Design System Compliance (MANDATORY for UI work)" now cites `design-system-protocol.md` as the SSOT, makes the BLOCKING reads conditional on `features.has_design_system: true` (instead of "if the project has a design system"), and adds two explicit HIGH findings: re-implementation of an existing registry primitive, and new primitives introduced without their per-component spec.
- **`framework/.claude/skills/ui-design/SKILL.md` + `framework/.claude/skills/frontend-design/SKILL.md`** — both gain a one-paragraph header on the Prerequisites section declaring `framework/agents/design-system-protocol.md` as the SSOT when local rules drift. Existing BLOCKING-read content kept verbatim (skills remain readable stand-alone).
- **`framework/agents/index.md`** — new routing entry: "if you touch ANY visual surface and `features.has_design_system: true` → read `agents/design-system-protocol.md`". `design-system-protocol.md` added to the Modules list.
- **`src/commands/configure.js`** — after the `has_design_system` prompt, surfaces `/design-system-init` either as the recommended next step (when the user opts in but no path is detected) or as a soft pointer (when the user opts out).

### Notes

- **No new `baldart.config.yml` keys.** The cascade gates on the existing `features.has_design_system` and reads paths already declared in `paths.*`. The schema-change propagation rule (per `feedback_schema_change_propagation`) is therefore satisfied without template/configure/update detector churn — this release only adds behaviors, a skill, and a protocol module.
- **Backwards-compatible.** Projects with `has_design_system: false` see no behavioral change (the BLOCKING reads in the agent/skill/command are no-ops when the flag is false). Projects with `has_design_system: true` already had the discipline inside `ui-design`/`frontend-design`/`code-reviewer` — this release just propagates it to the remaining surfaces (`ui-expert`, `/design-review`).
- **Bootstrap path.** Projects that want the discipline but lack a registry now have a single command (`/design-system-init`) that inventories existing primitives, scaffolds the registry from them, and flips the flag — eliminating the chicken-and-egg gap where the BLOCKING reads pointed at files that didn't exist.
- **SSOT trade-off.** `design-system-protocol.md` is the textual SSOT, but the BLOCKING-reads block is still inlined verbatim in `ui-design/SKILL.md`, `frontend-design/SKILL.md`, `code-reviewer.md`, and `ui-expert.md` (each file gains an SSOT-pointer header instead of being structurally deduplicated). Skills/agents stay readable stand-alone and don't depend on runtime module loading; the cost is that future protocol changes need to touch the consumers as well. Acceptable for v3.11.0 — revisit if the cascade churns frequently.

## [3.10.0] - 2026-05-23

BALDART grows an **LSP symbol-search layer**. When enabled, `codebase-architect` and the code-exploration skills (`context-primer`, `bug`, `prd`, `new`, `simplify`) prefer LSP `find-references` / `go-to-definition` over Grep for identifier-shaped queries. The filtering happens **before** Claude reads files — a function-name search that previously returned thousands of textual matches now resolves to the actual callsites of the same symbol. Grep remains the fallback for free-text queries and when the LSP layer is unavailable, so existing flows degrade gracefully.

The layer is opt-in: at install / update / configure, BALDART asks "Enable LSP symbol-search layer?" and (on yes) detects the project's languages and installs the matching language servers — npm devDeps for TypeScript and Python, system commands printed for Go, Rust, Ruby.

### Added

- **`baldart.config.yml` → `features.has_lsp_layer`** — new boolean (default `false`). Gates the LSP retrieval tier in agents and skills. When `true`, also populates the new `lsp` section with the installed language servers.
- **`baldart.config.yml` → `lsp.installed_servers` + `lsp.auto_verify`** — new top-level section tracking which language servers are recorded for the project and whether `baldart doctor` should re-verify them on every run.
- **`framework/agents/code-search-protocol.md`** — new protocol module defining the retrieval hierarchy (RAG hybrid → LSP for symbols → Grep → Git), query-type discrimination, LSP budget (max 3 calls/task), and fallback rules. Referenced from `framework/agents/index.md`.
- **`framework/.claude/skills/lsp-bootstrap/SKILL.md`** — new invokable skill (`/lsp-bootstrap`) that runs the install + verify flow as a standalone workflow. Refuses to run when `features.has_lsp_layer: false`. Idempotent.
- **`src/utils/lsp-adapters/`** — new pluggable adapter registry (TypeScript, Python via Pyright, Go via gopls, Rust via rust-analyzer, Ruby via ruby-lsp). Each adapter exposes `detect(cwd)`, `installCommand()`, `verifyCommand()`, and `claudePluginId()`. Same pattern as `src/utils/tool-adapters/`. Add a new language by dropping in a `<lang>.js` file and registering it in `index.js`.
- **`src/utils/lsp-installer.js`** — high-level installer used by `configure.js` and the `doctor` LSP step. `detectLanguages()` / `recommend()` / `installServers()` / `verifyServers()`. Pure detection has no side effects; install ops respect `--non-interactive`.
- **`src/commands/configure.js` LSP block** — when the user opts in, autodetects languages, prompts per server, installs npm-mode adapters in-process, prints system-mode install commands, verifies binaries, and persists `lsp.installed_servers`. Re-running configure on an existing project re-detects and updates the list.
- **`src/commands/doctor.js` LSP step** — new "LSP layer" line in the diagnostic table + a `lsp-fix` action that reinstalls broken servers. Surfaces missing binaries without breaking other doctor flows.
- **`framework/templates/overlays/agents/codebase-architect.lsp-example.md`** — starter overlay demonstrating how to add project-specific LSP tuning (monorepo quirks, generated-code exclusions, callsite thresholds) on top of the shipped protocol.

### Changed

- **`framework/.claude/agents/codebase-architect.md`** — Investigation Protocol step 3 now points at `agents/code-search-protocol.md` for LSP escalation. Behavior under `features.has_lsp_layer: false` is unchanged.
- **`framework/.claude/agents/REGISTRY.md`** — `codebase-architect` row gains "LSP symbol resolution (when `features.has_lsp_layer: true`)" as a specialization.
- **`framework/.claude/skills/context-primer/SKILL.md`** — step 3 of the retrieval loop redirects to the code-search protocol for symbol-shaped queries when the flag is on.
- **`framework/.claude/skills/bug/SKILL.md`** — Phase 0 step 3 references the protocol; `codebase-architect` now automatically uses LSP for symbol lookups during code-path mapping.
- **`framework/.claude/skills/simplify/SKILL.md`** — deduplication step 3 uses LSP `find-references` to confirm callsites before recommending consolidation; eliminates false positives from textual name collisions and dead-code candidates.
- **`framework/.claude/skills/prd/SKILL.md` / `new/SKILL.md`** — one-line note that LSP usage is transitive via `context-primer` / `codebase-architect` when the flag is on.
- **`framework/templates/baldart.config.template.yml`** — adds `features.has_lsp_layer` and the new `lsp:` block with inline documentation.
- **`framework/docs/PROJECT-CONFIGURATION.md`** — new § 4.5 row for `has_lsp_layer` and a new § 4.6 documenting the `lsp.*` keys, per-adapter install modes, and the fallback contract.

### Notes

- **Schema-change propagation** (matches the `v3.9.0` pattern): the new key is wired into the template, the configure prompt, the update-detector, the doctor table, the relevant skills, and the changelog.
- **Backwards-compatible.** Pre-v3.10 consumers running `npx baldart update` see the standard "new config keys" warning (`has_lsp_layer`) and are offered `configure`. Agents and skills behave exactly as before when the flag is absent or `false`.
- **Fallback contract.** When the layer is enabled but a server is missing, broken, or returns no references for a known symbol, agents silently fall back to Grep. Code search never fails because of LSP issues; `baldart doctor` surfaces the mismatch.
- **Languages out of scope** (Java, C/C++, PHP, Elixir, …) can be added by dropping a new file under `src/utils/lsp-adapters/`. No core change required.

## [3.9.0] - 2026-05-22

`worktree-manager` (`/mw`) gains a configurable merge strategy. The previous flow forced every consumer through `gh pr create` + `gh pr merge`; in repos where `develop` is not protected on GitHub, that roundtrip is unnecessary and was observed to stall (PR opened, never merged). The new `git.merge_strategy` key lets each project pick its lane.

### Added

- **`baldart.config.yml` → `git.merge_strategy`** — new top-level config section with one key (default `pr`):
  - `pr` (default, backwards-compatible) — push feature branch, open PR via `gh pr create`, merge via `gh pr merge`. Required if `develop` is protected on origin.
  - `local-push` — after the rebase in `/mw` step 4b, fast-forward push the feature branch directly to `origin/develop` (`git push origin <feat>:develop`). No PR, no `gh` dependency. Fails hard with a clear hint if `develop` is protected on origin.
- **`framework/.claude/skills/worktree-manager/SKILL.md` step 4c** — fully rewritten into two clearly labelled strategies (A: `pr`, B: `local-push`) plus a shared local-develop-sync block. Both strategies remain strictly worktree-isolated — neither runs `git checkout` / `git switch` / `git branch` on the main repo.
- **Programmatic API output** — `/mw` now returns `strategy: "pr"|"local-push"`. `prNumber` is `null` in `local-push` mode.

### Changed

- **`framework/.claude/skills/new/SKILL.md`** — Phase 6 description and fail-safe rules reference the configured strategy instead of hard-coding `gh pr merge`.
- **`worktree-manager` Project Context header** — added at the top of the skill, citing `git.merge_strategy` + `paths.backlog_dir` per the always-ask-never-assume contract in `framework/agents/project-context.md`.
- **`src/commands/configure.js`** — gained autodetection + interactive prompt for `git.merge_strategy`. The probe runs `gh api repos/:o/:r/branches/develop/protection` to recommend `pr` (when develop is protected) or `local-push` (when not). Falls back to `pr` safely when `gh` is missing or the remote is unreachable. The detection reason is surfaced inline so the user knows *why* a strategy is recommended.
- **`src/commands/update.js`** — schema-drift detection now also covers new top-level sections (currently `git.*`). When new keys are detected, `update` proactively offers to run `configure` instead of just warning. This means an existing consumer running `npx baldart update` will be **asked** about the new `git.merge_strategy` key, not silently defaulted.

### Notes

- No migration is required for existing consumers. `git.merge_strategy` defaults to `pr` when the key is absent, preserving the v3.4.0+ behaviour exactly.
- On `npx baldart update`, existing consumers are **prompted** to run `configure`, where the merge-strategy autodetection probes their repo and recommends `pr` or `local-push` with a one-line reason. The user picks; nothing is auto-applied.
- `develop` remains the only supported integration branch; configurable `base_branch` is out of scope for this release.

## [3.8.2] - 2026-05-22

`baldart` (smart doctor) no longer double-confirms safe actions. Previously, running `baldart` with a remote-ahead state would ask *"Run: Pull N commit(s)?"* and then the `update` command would ask *"Proceed with update?"* immediately after — two prompts for the same intent.

### Fixed

- **`src/commands/doctor.js` action runner** — every action now carries an `autoOk` flag. Safe & idempotent actions (`update`, `configure*`, `migrate`, `register-edit-gate`) run without the outer doctor confirmation (their own internal prompts take over). Actions that change external state or need explicit version classification (`add`, `push`) keep the outer confirmation so the user signals intent explicitly.

Result: `baldart` in a repo with "remote ahead" now goes straight into the update flow (with its own pre-flight stash + diff preview + proceed prompt), instead of double-asking.

## [3.8.1] - 2026-05-22

Documentation: promote the "one command for everything" install path in the README and surface the global-install recommendation in CLAUDE.md.

### Changed

- **`README.md` Quick Start** — restructured. Primary path is now `npm i -g baldart` once, then `baldart` (no args) in any project. Added a table that maps every repo state to the action `baldart` proposes (install / migrate / configure / update / push). The `npx baldart …` form remains documented as the no-global-install alternative.
- **`CLAUDE.md`** — added the recommended end-user install (`npm i -g baldart`) to the distribution note.

No behavioural changes; the smart-doctor entry point has worked this way since v3.2.0, this release just makes it discoverable.

## [3.8.0] - 2026-05-22

`.claude/agents/` and `.claude/commands/` move from bulk symlink to **per-item merge** with an **overlay system** (compile-time merge of base + overlay). Closes the long-standing gap where specializing a framework agent forced you to either fork the whole file (and lose upstream updates) or contaminate `.framework/`.

### Added

- **`src/utils/overlay-merger.js`** — new pure-function module. `mergeOverlay({ kind, name, baseContent, overlayContent, frameworkVersion })` returns the generated file content + warnings. Section markers: `## [OVERRIDE]`, `## [APPEND]`, `## [PREPEND]`; un-marked H2 sections in the overlay are appended at the end. Base frontmatter (`name`, `description`, `tools`, `model`) is preserved verbatim. Output carries a leading `<!-- baldart-generated: kind=… name=… base_version=… overlay_sha=… -->` marker so future updates know it's regeneration-safe.
- **`SymlinkUtils.mergeAgents(opts)` / `mergeCommands(opts)`** — per-item merge with overlay awareness:
  - No overlay → install as symlink (same model as skills).
  - Overlay present → write a real file with the merged content + marker.
  - Existing generated file → detected by marker → safely regenerated.
  - User-authored fork (no marker) → conflict logged, file left untouched, user told to migrate to overlay.
  - Broken symlink → silently re-linked (same fix-pattern as v3.5.1).
- **`tool-adapters/claude.js`** declares `agentsDir()` and `commandsDir()`. Codex returns `null` (no equivalent), so the dispatch skips it cleanly.
- **`framework/.claude/hooks/framework-edit-gate.js`** extended: blocks `Edit`/`Write` of any file whose first line is `<!-- baldart-generated:` unless the new content also starts with the same marker. Forces the user to edit the overlay instead.
- **Example overlays**: `framework/templates/overlays/agents/coder.example.md` and `framework/templates/overlays/commands/codexreview.example.md` showing all three section markers in use.
- **`framework/agents/project-context.md` § 5.bis** — new section documenting the agent/command overlay protocol, layout, frontmatter, section markers, generation rules, drift handling, and hard rules.
- **`baldart doctor` Overlays line** now counts skill overlays + agent overlays + command overlays.

### Changed

- **`SymlinkUtils.createBulkSymlinks()`** — no longer creates bulk symlinks for `.claude/agents/` and `.claude/commands/`. If a legacy bulk symlink is detected (v3.7.x and earlier layout), it's unlinked and replaced with a real directory, populated per-item by `mergeAgents`/`mergeCommands` immediately after.
- **`SymlinkUtils.createAllSymlinks({ tools })`** — calls `mergeSkills` + `mergeAgents` + `mergeCommands` in sequence.
- **`SymlinkUtils.verifySymlinks()`** — `AGENTS.md` and `agents/` stay bulk symlinks; `.claude/agents/` and `.claude/commands/` are now validated as real dirs containing at least one framework-managed file (symlink or generated). Per-tool, only for tools whose adapter declares a non-null `agentsDir()` / `commandsDir()`.
- **`src/commands/update.js`** — post-update reconcile now runs the new agent + command merges in addition to skills.
- **`src/commands/migrate.js`** — additionally converts legacy `.claude/agents` and `.claude/commands` bulk symlinks to per-item.

### Migration (pre-3.8.0 → 3.8.0)

On the next `npx baldart update`, an old consumer sees:

```
⚠ Detected legacy bulk symlink at .claude/agents. Converting to per-item layout (v3.8.0+).
⚠ Detected legacy bulk symlink at .claude/commands. Converting to per-item layout (v3.8.0+).
✓ [Claude Code] agent linked: .claude/agents/coder.md
…
```

Zero data loss (the bulk symlink was just one pointer; the real content stays in `.framework/`). After the convert, the user can create `.baldart/overlays/agents/<name>.md` to specialize any framework agent without forking.

### Verification (proven in sandbox)

1. **Legacy → per-item migration**: bulk `.claude/agents` symlink → real dir + per-item symlinks ✓
2. **Overlay create → regenerate**: real generated file with marker + overlay content visible ✓
3. **Overlay remove → revert to symlink**: generated file deleted, symlink re-created ✓
4. **User fork without marker → conflict logged**: framework version NOT overwritten, user file preserved, guidance printed ✓
5. **Heading mismatch in overlay**: warning emitted, content appended as new section (no silent loss) ✓
6. **Marker round-trip**: SHA-256-truncated overlay hash + framework version recoverable from generated file header ✓

### Out of scope (explicitly deferred)

- **Codex agent/command equivalents**: Codex doesn't have subagents or slash commands (its custom prompts are deprecated). The overlay system is Claude-only by mechanism; the cross-tool flat skill model (v3.7.0) continues to handle Codex.
- **Skill overlay refactor**: skills today use runtime concatenation (SKILL.md body reads overlay), which works. Migrating skills to compile-time merge is opt-in for a future release.
- **Drift-section diff during update**: structural support is in place via `base_agent_version` in overlay frontmatter and `base_version` in generated marker, but the actual section-level diff against the previous-version base content is a future enhancement (the framework just emits a one-line "overlay vs vX → vY" warning today).

## [3.7.1] - 2026-05-22

`baldart update` now actively migrates pre-v3.7.0 configs to the new multi-tool layout instead of silently leaving them on `[claude]` only.

### Fixed

- **`src/commands/update.js` legacy-config migration** — after the subtree pull, if `baldart.config.yml` exists but `tools.enabled` is missing/empty, the CLI now:
  1. Detects the legacy state.
  2. Surfaces the new capability and shows the autodetected tool list (Claude always, plus Codex if `~/.codex/` exists).
  3. Asks: *"Backfill `tools.enabled: [claude, codex]` into baldart.config.yml?"*.
  4. On accept: writes the array into the config AND re-runs `mergeSkills` for the new tools so `.agents/skills/` is populated immediately — not deferred to the next update.

Without this, a pre-v3.7.0 install upgrading to v3.7.x would never get the Codex skill dir populated, because the per-tool dispatch falls back to `['claude']` when `tools.enabled` is absent.

## [3.7.0] - 2026-05-22

BALDART is now AI-tool agnostic. Every framework skill is installed into both Claude Code (`.claude/skills/`) AND OpenAI Codex CLI (`.agents/skills/`) when both are enabled — same source, two symlinks, zero duplication. Codex skill format is structurally identical to Claude's (`SKILL.md` with `name` + `description` frontmatter + optional `scripts/`/`references/`/`assets/`), so the same file works for both runtimes.

### Added

- **`src/utils/tool-adapters/`** — new adapter registry (mirrors the existing `routine-adapters/` pattern). Each adapter declares the directory its tool reads skills from, plus capability flags (subagents, slash commands, hooks). Currently: `claude.js`, `codex.js`. Adding Cursor / Aider / Cline later is a one-file addition.
- **`tools.enabled` in `baldart.config.yml`** — array of enabled tool names. Default: `[claude]`, plus `codex` if `~/.codex/` is detected on the user's machine. Honored by `add`, `update`, `migrate`, and `verifySymlinks`.
- **`baldart configure` AI-tools section** — prompts per available tool with the autodetected default. Refuses to disable Claude (the framework's primary target).
- **`AUTODETECTED` summary** now shows `AI tools: claude, codex`.

### Changed

- **`SymlinkUtils.mergeSkills({ tools })`** — runs the per-item merge once per enabled tool, resolving each tool's target directory via its adapter. The same source under `.framework/framework/.claude/skills/<skill>/` is linked into every enabled tool's expected location.
- **`SymlinkUtils.createAllSymlinks({ tools })`** — accepts the tool list and passes it through to `mergeSkills`.
- **`SymlinkUtils.verifySymlinks()`** — reads `tools.enabled` from `baldart.config.yml` and validates each tool's skills dir separately. Output lines are now tagged `[Claude Code]` / `[OpenAI Codex CLI]`.
- **Per-item skill merge resilience** — `_mergeSkillsForTool` now detects broken symlinks via `fs.lstatSync` and re-links them silently instead of crashing on EEXIST (same fix-pattern as v3.5.1, now applied per-tool).

### Why it matters

Until now BALDART was Claude-Code-only by construction. A consumer using Codex saw nothing — Codex's discovery walks `.agents/skills/`, and the framework had no awareness of that path. v3.7.0 makes the install universal: declare `tools.enabled: [claude, codex]` (or both, autodetected) and Codex picks up the same 24 skills, indexed by its own discovery, with `AGENTS.md` at the root continuing to work for both as it did before. Adding more tools later (Cursor `.cursor/rules/`, Aider `CONVENTIONS.md`, Cline, etc.) only requires writing a new adapter — the rest of the install pipeline is already tool-pluggable.

### Out of scope (Codex-specific limitations)

- **Subagents** (Claude's `.claude/agents/<name>.md`) have no Codex equivalent. They remain Claude-only. AGENTS.md sections cover the cross-tool coordination protocol.
- **Slash commands** (Claude's `.claude/commands/`) — Codex deprecated custom prompts in favor of skills, so `/commandX` patterns stay Claude-only. If you want a workflow callable from Codex, author it as a skill.
- **Hooks** (Claude's `.claude/hooks/`) — Codex has no equivalent hook system. `framework-edit-gate` remains a Claude-only safety net.

## [3.6.4] - 2026-05-22

Two configure-flow fixes surfaced by a real-world `mayo` install audit.

### Fixed

- **API docs false negative** — `has_api_docs` only checked for `docs/.../api/schemas.md` (and OpenAPI/GraphQL specs). Projects that ship API docs as one markdown per resource (e.g. `docs/references/api/{index,admin,push,…}.md`) were marked `has_api_docs: false` despite having extensive API documentation. Added a populated-directory probe: if `docs/references/api/` or `docs/api/` exists and contains ≥1 `.md` file, the feature flag flips to `true` and `api_index` falls back to `<dir>/index.md`.

### Changed

- **Prompt label clarity** — `"Charting libraries (canonical, comma-separated)"` → `"Approved charting libraries — comma-separated package names (empty if none)"`. Same edit for forbidden and animation. The previous label read like the user was being asked to type the word `canonical`; one tester actually did.

## [3.6.3] - 2026-05-22

Smarter project autodetection in `baldart configure`. The previous probe only looked at a single hardcoded path (`docs/design-system/INDEX.md`), which silently failed on the vast majority of real projects (monorepos, inline `src/ui/`, `packages/ui/`, shadcn/ui setups, Tailwind-theme-only projects, etc.). The redundant "Design philosophy" prompt is now skipped automatically when a design system is detected.

### Changed

- **`src/commands/configure.js` `detect()`** — multi-signal design system detection:
  - **Path probes**: `docs/design-system`, `packages/ui`, `packages/design-system`, `packages/components`, `packages/ds`, `src/design-system`, `src/ui` (plus per-package expansion in monorepos).
  - **Storybook**: `.storybook/` at root or in any monorepo package.
  - **shadcn**: `components.json` marker.
  - **Tailwind theme**: `tailwind.config.{ts,js,mjs,cjs}` with `theme.extend` (regex inspection).
  - **DS libraries**: `@radix-ui/*`, `@chakra-ui/*`, `@mui/material`, `antd`, `@mantine/core`, `@nextui-org/*`, `daisyui`, `flowbite-react`, `@headlessui/react`, etc.
- **Monorepo awareness** — detects `pnpm-workspace.yaml`, `lerna.json`, `nx.json`, `turbo.json`, `rush.json`, or `package.json` `workspaces`. Expands every path probe across `packages/*`, `apps/*`, `services/*`, `libs/*`.
- **`components_primitives`** — when a design system is detected but no `components/ui` directory exists, probes the DS's own subdirs (`<ds>/src/components`, `<ds>/components`, `<ds>/src/ui`) and only returns paths that actually exist on disk.
- **UI guidelines** — widened to also detect `STYLEGUIDE.md`, `BRANDING.md`, `BRAND.md`, `docs/style-guide.md`, `docs/brand/README.md` plus monorepo variants.
- **API docs** — widened to detect OpenAPI specs (`openapi.{yaml,yml,json}`) and GraphQL schemas (`schema.graphql`) in addition to the existing markdown locations.
- **Brand name** — fallback chain: `package.json` `name` (scope stripped) → first H1 in `README.md` → `path.basename(cwd)`. No more empty `brand_name` for projects without a `package.json` name.
- **E2E framework** — also detects via dependency (`@playwright/test`, `cypress`) and `playwright.config.mjs`.
- **Charting/animation libraries** — added `chart.js`, `d3`, `visx`, `@tremor/react`, `@react-spring/web`, `auto-animate`, `motion`.
- **`interactivePrompts()` design philosophy** — gated on `!detected.features.has_design_system`. If a DS is detected, the prompt is skipped and a heuristic hint (e.g. `"Minimalist (shadcn/Tailwind)"`, `"Library-driven (@radix-ui)"`) is used as metadata for skills.
- **AUTODETECTED summary box** — now shows brand name, DS yes/no with its concrete signals (path/storybook/shadcn/tailwind-theme/library), monorepo detection + package count, API docs yes/no.
- **New config fields** under `stack.*`:
  - `stack.monorepo: { detected, roots }` — exposes detected monorepo packages to skills.
  - `stack.design_system_signals: { path, storybook, shadcn, tailwind_theme, library }` — granular signals for skills that want to adapt to the specific DS flavor.

### Why it matters

Before this change, a project with a real design system at `src/ui/` (or any non-canonical location) was told `Design system: — (none found)` and then asked the wrong question (`Design philosophy?`). Now the same project is correctly identified, the redundant prompt is skipped, and skills receive structured DS signals they can route on.

## [3.6.2] - 2026-05-22

BALDART is now published to npm as [`baldart`](https://www.npmjs.com/package/baldart) on every `v*.*.*` tag. End-users can run `npx baldart <cmd>` (without the `-y github:antbald/BALDART` prefix), which resolves through the npm registry and avoids the stale-tarball cache that plagued the GitHub-source installation.

### Added

- **`.github/workflows/publish-npm.yml`** — triggers on `v*.*.*` tag push (and `workflow_dispatch`). Steps: checkout the tagged ref, setup Node 20 with npm registry auth, sync `package.json` version to the `VERSION` file, verify tag matches VERSION (hard fail otherwise), `npm ci`, `npm publish --access public --provenance`. Requires `NPM_TOKEN` repo secret.
- **`package.json` scripts** — `sync-version` reads `VERSION` and writes it into `package.json`. `prepublishOnly` runs it so manual `npm publish` is also safe.
- **`package.json` files** — added `VERSION` and `CHANGELOG.md` to the published tarball; removed dangling `LICENSE` reference; removed unused `main` field (this is a CLI package, `bin` is what matters).

### Changed

- **README.md** — primary install/usage examples now use `npx baldart <cmd>` (npm form). The legacy `npx -y github:antbald/BALDART <cmd>` is mentioned as the fallback for unreleased commits on `main`.
- **CLAUDE.md** — documented the dual distribution model (npm registry + Git subtree for the framework payload) and the workflow's tag-vs-VERSION gate.
- **MAINTAINING.md** — release checklist now mentions the npm publish trigger and the `npm view baldart version` verification step.

### Why it matters

Before this, `npx -y github:antbald/BALDART` was the only install path, and npx's GitHub-tarball cache regularly served stale CLI code even after `update` to a newer framework version — producing confusing errors like *"Template not found at .framework/templates/baldart.config.template.yml"* that referenced bugs already fixed on `main`. Publishing to the npm registry sidesteps that entire caching layer.

### Operational note

The first tag push to v3.6.2 will fail if `NPM_TOKEN` is not yet configured as a GitHub Actions secret. Generate an **Automation token** on npmjs.com (Settings → Access Tokens → Generate New Token → Automation) and add it as `NPM_TOKEN` under repo Settings → Secrets and variables → Actions.

## [3.6.1] - 2026-05-22

Fix a long-standing path bug: the bulk symlinks (`AGENTS.md`, `agents/`, `.claude/agents/`, `.claude/commands/`), the customizable-template copies (hooks, ui-guidelines, brand-guidelines, backlog templates), and the `baldart.config.template.yml` lookup all referenced `.framework/<sub>` (single `framework/` segment). The Git subtree pull nests the entire BALDART repo under `.framework/`, and the repo itself nests its shippable content under a top-level `framework/` directory — so the real path is `.framework/framework/<sub>`. The bulk symlinks were therefore being created broken (pointed nowhere) and `configure` couldn't find its template, surfacing the misleading *"Run `npx baldart add` first"* error during `update`.

### Fixed

- **`src/utils/symlinks.js`** — introduced `FRAMEWORK_PAYLOAD = .framework/framework` constant. All bulk symlinks (`AGENTS.md`, `agents`, `.claude/agents`, `.claude/commands`), the `copyCustomizableFiles()` source paths (hooks, docs/references, templates copy loop), and the relative-mode symlink callsites now resolve through it. The relative symlink callers no longer mis-prepend `..` (which previously combined with `path.join(cwd, target)` to navigate ABOVE the consumer repo).
- **`src/commands/configure.js`** — `TEMPLATE_PATH` corrected to `.framework/framework/templates/baldart.config.template.yml`.
- **`src/commands/update.js`** — schema-drift template lookup corrected to the same path.
- **`src/commands/doctor.js`** — `loadConfigTemplate()` corrected to the same path.

### Why it matters

Until now, every consumer had **broken** `AGENTS.md`/`agents/`/`.claude/agents/`/`.claude/commands/` symlinks (pointing at paths that don't exist) — Claude Code silently saw "no shared agents/commands" and used only what the user had authored locally. Same story for `configure`: it could never find its template, so it bailed and the user's `baldart.config.yml` was never generated automatically during `update`. After v3.6.1, on the next `npx baldart update` the symlinks get re-created with the correct `.framework/framework/` target and `configure` runs end-to-end.

## [3.6.0] - 2026-05-22

`npx baldart update` now handles a dirty consumer worktree gracefully instead of crashing with `fatal: working tree has modifications. Cannot add.` from `git subtree pull`. Before the subtree pull runs, the CLI detects uncommitted changes, lists them, and offers to auto-stash + re-apply automatically after the update.

### Added

- **`src/commands/update.js` pre-flight check** — after the user confirms "Proceed with update?", if the working tree is dirty: the CLI lists the dirty paths and asks "Auto-stash now and re-apply after the update (recommended) / Abort". Stash is labelled `baldart-pre-update-<ISO timestamp>` for traceability.
- **Auto-pop after success** — on a successful subtree pull the stash is popped. If the pop conflicts with the framework update, the stash is left intact and the user gets a `STASH CONFLICT` box with recovery commands (`git stash list`, `git stash pop`).
- **Auto-restore on failure** — if the subtree pull itself fails after stashing, the stash is restored automatically so the user's pre-update changes never appear lost.

### Why it matters

Combined with the v3.5.0 post-update auto-commit, the full update flow now keeps the worktree clean on entry AND on exit, without ever silently dropping the user's work-in-progress.

## [3.5.1] - 2026-05-22

Fix `npx baldart update` crashing with `EEXIST: file already exists, symlink …` when the bulk symlinks (`AGENTS.md`, `agents`, `.claude/agents`, `.claude/commands`) were broken (target sparito dopo un cleanup o move). `fs.existsSync` segue il symlink e tornava `false` per i broken link → il ramo di cleanup veniva saltato → `fs.symlinkSync` falliva creando il nuovo link sopra quello rotto.

### Fixed

- **`src/utils/symlinks.js` `createSymlink()`** — switched from `fs.existsSync` to `fs.lstatSync` (with try/catch) to detect broken symlinks and treat them as "existing symlink to wrong target" → unlink + recreate.
- **`src/utils/symlinks.js` `verifySymlinks()`** — same fix on the diagnostic path, so broken bulk symlinks are now reported as `Broken symlink: <link> → <target>` instead of being misclassified as `Missing`.

## [3.5.0] - 2026-05-22

`npx baldart update` now offers to commit the post-update reconcile (symlinks, hook registration, state.json, routine adapters, etc.) in a single dedicated commit, so the consumer's worktree stays clean and there is a clear revert point per upgrade.

### Added

- **`src/commands/update.js` `postUpdateAutoCommit()`** — runs after all post-update reconcile steps. Scans `git status`, filters to a BALDART-managed allowlist (`.framework/`, `AGENTS.md`, `agents`, `.claude/{agents,commands,skills,hooks,routines,settings.json}`, `.baldart/`, `baldart.config.yml`, `docs/references/{ui-guidelines.template,brand-guidelines}.md`, `templates/`, `.github/workflows/baldart-*`, `scripts/routines/`), shows a preview and asks before committing as `chore(baldart): post-update reconcile to vX.Y.Z`. User-owned dirty paths are never staged.
- **`bin/baldart.js` `--no-commit` flag on `update`** — skips the prompt entirely for CI/scripted runs.

### Why it matters

Before v3.5.0 every `update` left a dirty worktree (the subtree-pull commit only covers `.framework/`; symlink reconcile + hook + state writes happen outside it). Consumers either had to `git status` and craft a commit by hand or live with a noisy `git status`. The new flow keeps the post-update side effects bundled in one self-describing commit you can `git revert` if anything goes wrong.

## [3.4.1] - 2026-05-22

Fix `npx baldart update` crashing with `ReferenceError: newVersion is not defined` after a successful framework update. The update completed correctly but the final "WHAT CHANGED" summary referenced an out-of-scope `const`, surfacing the error through the outer catch and exiting non-zero — confusing the user and breaking CI signal.

### Fixed

- **`src/commands/update.js`** — hoisted `newVersion` declaration above the inner try/catch (`let newVersion;`) so the "WHAT CHANGED" box can read it. The `const` lived inside the inner try block (lines 105–138) and was unreachable from the success summary below it.

## [3.4.0] - 2026-05-22

Worktree manager now respects terminal isolation. The `/mw` skill no longer runs `git checkout develop` on the main repo (which is a shared resource across parallel terminals in worktree-driven projects). Same for `/nw` pre-flight. Reported from a production failure in the `mayo` consumer where CLAUDE.md declares terminal-isolation as an absolute rule and Claude Code's classifier preemptively blocked the checkout.

### Changed

- **`framework/.claude/skills/worktree-manager/SKILL.md` `/mw` step 4c** — replaced local-checkout merge (`git checkout develop && git pull --ff-only && git merge && git push`) with remote merge via GitHub PR (`gh pr create` + `gh pr merge --merge --delete-branch`). The main repo's `HEAD` is never touched. Local `develop` is synced via `git -C <main> pull --ff-only` ONLY when the main repo HEAD is already `develop`; otherwise we just fetch and leave the user's working state alone.
- **`/mw` step 4c fallback** — if `gh pr merge` CLI fails (typically on the local-branch cleanup when `develop` is checked out in another worktree), fall back to the REST API (`gh api -X PUT repos/:owner/:repo/pulls/:n/merge`). Hard-fails (no local-checkout fallback) — the user installs `gh` and re-runs.
- **`/nw` pre-flight (step 1) + step 3** — no longer runs `git checkout develop` on the main repo. Pre-flight is now `git fetch origin develop` (read-only); worktrees branch from `origin/develop` directly.
- **`/mw` Programmatic API output** — now returns `prNumber` alongside `mergeCommit`.
- **Safety Rules** in worktree-manager SKILL.md — added explicit `NEVER git checkout/switch/branch on main repo` rule with rationale (terminal isolation, classifier pre-blocking).
- **`framework/.claude/skills/new/SKILL.md` Phase 6 description + Fail-safe rules** — aligned to the new flow.

### Why it matters

Many consumer projects declare a "shared main repo" / terminal-isolation rule in their CLAUDE.md. The classifier in Claude Code pattern-matches the rule text (not the runtime state) and preemptively denies `git checkout develop` on the main repo. The legacy flow therefore failed silently in those projects, and the `/new` orchestrator could not close the merge loop — the user had to merge by hand. With v3.4.0 the merge happens entirely server-side via the GitHub API and the main repo is never touched.

### Note for consumers

`gh` CLI must be installed and authenticated (`gh auth login`). If it isn't, `/mw` STOPs and asks the user to install it — it does NOT fall back to the legacy local-checkout flow.

## [3.3.0] - 2026-05-21

Real-time contamination gate. A Claude Code `PreToolUse` hook now intercepts every `Edit`/`Write`/`MultiEdit` whose target resolves (via symlink) to a path inside `.framework/`, runs the contamination scanner on the new content, and blocks the call with an informative reason if project-specific tokens are detected. Auto-registered in every consumer at install time.

### Added

- **`framework/.claude/hooks/framework-edit-gate.js`** — Claude Code `PreToolUse` hook (Node, cross-platform, fail-safe). Reads tool input from stdin, resolves the real path with `fs.realpathSync` (follows symlinks), and checks if it lands inside `.framework/`. If so, scans the new content via the existing contamination utility and returns `permissionDecision: "deny"` with a structured reason walking the user through three resolution paths: (A) reformulate generically using `${paths.X}`/`identity.X`/`stack.X`, (B) move to `.baldart/overlays/<skill>.md`, (C) declare the file opt-out with the contamination-scan marker.
- **`src/utils/hooks.js`** — registration utility. `register()` / `unregister()` / `isRegistered()` operate on `.claude/settings.json` with idempotent JSON merge (preserves user-authored hooks). The registered entry carries a stable `id: "baldart-framework-edit-gate"` for future detection.
- **Auto-registration**:
  - `baldart add` registers the hook after symlinks are created (creates `.claude/settings.json` if absent, merges otherwise).
  - `baldart update` re-registers (idempotent) — backfills consumers installed before v3.3.0.
  - `baldart doctor` detects missing registration and proposes "Register framework-edit-gate hook" as a remediation action.
- **`baldart doctor` status line**: new "Edit gate" row in the diagnostic table showing registered vs not-registered.

### How it works

A typical flow now looks like this. Claude is editing a skill in fidelity-app:

```
Claude → Edit .claude/skills/ui-design/SKILL.md (new_string contains "Neo-Brutalism")
       ↓
       framework-edit-gate.js fires (PreToolUse)
       ↓
       Resolves symlink → /…/.framework/framework/.claude/skills/ui-design/SKILL.md
       ↓
       Detects ".framework/" segment → triggers
       ↓
       Runs contamination.scan() on new_string → finds "Neo-Brutalism"
       ↓
       Returns permissionDecision: "deny" with structured reason
       ↓
Claude reads the reason, moves the rule to .baldart/overlays/ui-design.md instead.
```

The hook is fail-safe: any internal error (missing scanner, parse error, IO error) exits 0 and lets the tool call through. The scanner is a safety net, not a tribunal.

### Disabling

If the gate gets in the way for a specific session, remove the entry from `.claude/settings.json` under `hooks.PreToolUse[]` (look for `id: "baldart-framework-edit-gate"`). `baldart doctor` will detect the absence and offer to re-register on next run.

For per-file legitimate exceptions (docs that legitimately quote canonical paths as illustrative examples), add the existing opt-out marker:

```markdown
<!-- contamination-scan: skip -->
```

or in YAML frontmatter:

```yaml
contamination_scan: skip
```

## [3.2.0] - 2026-05-21

Single smart entry point. `npx baldart` with no arguments now runs a doctor that diagnoses the install and proposes the next sensible action.

### Added

- **`baldart doctor` command** + **no-argument shortcut**. Detects (in priority order): not-a-git-repo, framework absent, legacy v2.0.x bulk-skills layout, malformed config, missing config, config schema drift, remote ahead, local framework changes, overlay version drift. Renders a status table, then proposes the matching action(s) and runs each with confirmation. Acts as the new informational default — `npx baldart` no longer prints commander help, it prints a diagnostic.
- **`--auto` flag**: skips yes/no confirmations for CI; errors out with exit 2 if multiple actions are proposed (avoids guessing in ambiguous states). Pairs cleanly with `--offline` for fully sandboxed runs.
- **`--offline` flag**: skips the upstream fetch (no remote drift detection).
- Both flags work without typing `doctor`: `npx baldart --auto --offline` is equivalent to `npx baldart doctor --auto --offline`.

### Changed

- `npx baldart` with no arguments previously dumped the commander help. It now runs the doctor flow instead. The help is still accessible via `npx baldart --help` or `npx baldart <subcommand> --help`.

## [3.1.0] - 2026-05-21

Contribution flow upgrade. Centralized framework versioning via `.baldart/state.json`. New `/baldart-push` skill orchestrates safe upstream contributions with automatic contamination autofix, version bump, CHANGELOG generation, and tag push.

### Added

- **`/baldart-push` skill** (`framework/.claude/skills/baldart-push/`) + slash command (`framework/.claude/commands/baldart-push.md`). Conversational orchestration of the contribution flow with hard rules: no secrets, no overlay/config push, always show current version first.
- **`src/utils/contamination.js`** — scanner + autofix library. Catalogues every fidelity-app-specific token pattern that v3.0.0 cleaned up. Three severities: `autofixable` (safe substitutions like `docs/design-system/` → `${paths.design_system}/`), `requires-decision` (identity/stack tokens — user reviews per file), `block` (secrets — hard fail).
- **`src/utils/state.js`** — `.baldart/state.json` read/write. Centralized versioning state: installed_version, install_date, last_update_date, last_pushed_version, last_push_date, rolling history of the last 20 events. Updated automatically by `add`/`update`/`push`.
- **`baldart version`** command rewritten — now shows installed version + install date, remote drift (ahead/behind), local uncommitted-files count, last-push info, CLI version. New `--offline` flag to skip the upstream fetch.
- **`baldart push`** command rewritten — auto-pull when behind, file triage, new-skill discovery, contamination scan with per-file decisions, auto VERSION bump, auto-generated CHANGELOG entry (Added/Changed/Removed grouped from diff), annotated tag push, state.json update. Replaces the previous "next steps required" manual workflow.
- **Triage rules**: hooks customizations (`.claude/hooks/*.sh` non-template) are blocked from push automatically; overlay starter examples (`framework/templates/overlays/*`) are exempted from contamination scan.

### Changed

- `src/commands/add.js`, `src/commands/update.js` now record events into `.baldart/state.json` after successful operations.
- The push flow now exits with `Description is required.` when the user provides empty description (was previously a silent failure mode).
- `AGENTS.md` `§ 4 Non-negotiables (MUST)` split into **4.a Universal MUST** (apply everywhere) and **4.b Workflow MUST** (gated by `features.has_backlog` / `features.has_adrs` / existence of `project-status.md` / overlay-declared conventions). Pre-v3 absolute MUST rules around backlog / ADRs / GitHub issue labels / `git_strategy` are now correctly feature-gated. Duplicate ADR completeness MUST removed.
- `framework/agents/project-context.md` § 6 autodetection table rewritten as the source of truth, listing all 20 probes that `configure.js detect()` actually runs.
- README "Migrating an Existing Install" rewritten as `v1.x / v2.x → v3.x` (was v2.1.1 framing), with explicit `npx baldart configure` step and links to `PROJECT-CONFIGURATION.md` § 9.
- README duplicate `push` command section removed; Quick Start now lists `configure`.
- `MAINTAINING.md` prefaced with v3.1.0+ automated flow TL;DR (the CLI now does steps that used to be manual: VERSION bump, CHANGELOG entry, tag).

### Fixed

- `src/commands/push.js` "skip file" branch now distinguishes status `A` (newly added — `git rm -f`) from `M`/`R`/`D` (existing upstream — `git checkout origin/main`). Previously the `checkout` failed silently on newly-added files and they were pushed despite the user's skip decision.
- `src/commands/push.js` autofix commit now stages only the files autofix actually modified (was sweeping all pushable files, capturing unrelated edits).
- `src/commands/push.js` partial failure surfaces a rollback SHA snapshot (`git reset --hard <preFlightSHA>`) so the user can undo chore commits cleanly.
- `src/commands/push.js` path comparisons normalized to forward slashes — fixes Windows where `simple-git` returns `/` but `path.join` produces `\`, so `CHANGELOG.md` and `VERSION` previously fell into the `pushable` bucket and were double-committed.
- `src/commands/push.js` `buildChangelogEntry` strips the `.framework/` mount-point prefix from path entries (was `.framework/framework/.claude/skills/foo/SKILL.md`; now `framework/.claude/skills/foo/SKILL.md`).
- `src/commands/configure.js` `mergePreserving` preserves deliberate empty-string user values (was overwriting them with detected defaults).
- `src/utils/contamination.js` `stack-recharts` rule case-insensitive (was missing lowercase `import 'recharts'`).
- `src/utils/contamination.js` `secret-api-key` rule catches unquoted assignments (`API_KEY=abc123...` in `.env` files).
- `src/utils/contamination.js` added secret patterns: GitHub PATs (`ghp_`/`ghs_`/`ghu_`/`gho_`), Slack tokens (`xox[baprs]-`), JWTs.

## [3.0.0] - 2026-05-21

This is a MAJOR release. Skills no longer hard-code paths, brand identity, or technology stack choices — they read them from `baldart.config.yml` and project-specific overlays. The same skills now work across projects with different design systems, audiences, and stacks.

### Added

- **Project context system** (3 layers):
  - `baldart.config.yml` at repo root — schema versioned, populated by the new `npx baldart configure` command. Defines `paths.*`, `identity.*`, `stack.*`, `features.*` for the project.
  - `.baldart/overlays/<skill>.md` — consumer-authored per-skill extensions with `base_skill_version` frontmatter for drift detection. Sections marked `## [OVERRIDE] <topic>` replace base skill sections.
  - `framework/agents/project-context.md` — protocol module loaded as MANDATORY pre-read for any skill that touches project-specific facts.
- **`npx baldart configure` command** — interactive prompts + filesystem autodetection (probes for design-system index, components dirs, backlog, ADRs, wiki overlay, package.json dependencies for charting/animation/E2E framework). Idempotent: re-running merges without clobbering user values. Available as `--non-interactive` for CI.
- **Project context header convention** — every refactored skill now declares its config dependencies in a dense 4-line header citing `framework/agents/project-context.md` for the protocol.
- **Overlay starter templates** under `framework/templates/overlays/`:
  - `ui-design.fidelity-example.md` — Neo-Brutalism, Recharts/nivo stack, merchant theming, Safari open command, evaluation thresholds.
  - `copywriting.fidelity-example.md` — 4-pillar brand voice, Italian-only rule, audience register matrix, forbidden vocabulary.
- **`framework/docs/PROJECT-CONFIGURATION.md`** — comprehensive guide for consumers: schema reference, overlay system, per-skill behaviour when keys are missing, ground-up examples, v2→v3 migration cheat sheet, FAQ.
- **Status command extensions** — reports `baldart.config.yml` presence + schema version, lists authored overlays with version-drift warnings.
- **Update command extensions** — never overwrites `baldart.config.yml`; warns when new framework version adds config keys not yet in the consumer's file (suggests re-running configure).

### Changed (BREAKING)

- **Skills no longer assume the fidelity-app shape**. Sixteen skills with significant hard-coded paths or identity tokens were refactored to resolve them from `baldart.config.yml`; the remaining skills (`api-design-principles`, `remotion-best-practices`, `seo-audit`, `skill-creator`, `worktree-manager`, `find-skills`) had no significant project-leaks and were left untouched. The refactored skills:
  - `ui-design`, `ui-design/references/*` — design-system reads, charting stack, identity-driven aesthetic
  - `copywriting` — brand voice pillars now overlay-driven
  - `prd`, `prd-add`, `prd/references/*`, `prd/assets/*` — backlog/PRD/references paths, design-system gating
  - `new` — backlog and references paths, validation tooling generalized
  - `bug`, `bug/references/logging-patterns.md` — project-specific debug entry points moved to overlay
  - `kie-ai` — design-system reads gated by `features.has_design_system`
  - `motion-design` — illustration-motion path resolved from config
  - `capture` — wiki overlay path from `paths.wiki_dir`, domain vocabulary from overlay
  - `context-primer` — backlog/references/wiki paths from config; feature-gated probing
  - `playwright-skill`, `webapp-testing` — design-system reads gated, theming pairing from overlay
  - `gamification-design` — design system + audience segment from config, customer surface guidance overlay-driven
  - `doc-writing-for-rag` — schemas/errors paths from config, examples marked as illustrative
  - `frontend-design`, `simplify` — design-system reads gated by `features.has_design_system`
- **Skills refuse to run when their gating feature is `false`**. E.g. `/new` requires `features.has_backlog: true`, `/prd` requires `features.has_prd_workflow: true`, `/capture` requires `features.has_wiki_overlay: true`. This replaces silent degradation with explicit refusal + suggestion to enable the feature in config.
- **Always-ask on missing keys**. When a skill encounters a required path that is `""`, or a `features.*` flag absent from the YAML, it prompts the user and suggests persisting the answer via `npx baldart configure`. Never defaults to `false` from absence.
- **`templates/` install excludes framework-internal templates**. `baldart.config.template.yml`, `skill-project-context.snippet.md`, and the `overlays/` example directory live in `.framework/templates/` only — they are consumed by CLI commands and references, not copied to the user's `templates/`.

### Migration from 2.x

`npx baldart update` warns if `baldart.config.yml` is missing and offers to run configure. Existing v2.x installs need:

1. `npx baldart update` (pulls v3.0.0).
2. `npx baldart configure` (interactive — autodetects ~80% of values from typical fidelity-app layouts).
3. For projects that relied on the pre-v3 hard-coded opinions (Neo-Brutalism, merchant/customer, Recharts-only): copy the matching `*.fidelity-example.md` from `.framework/templates/overlays/` into `.baldart/overlays/` and adapt.
4. `npx baldart status` — verify config + overlays.
5. Commit `baldart.config.yml` and `.baldart/overlays/`.

Full migration guide: `.framework/docs/PROJECT-CONFIGURATION.md` § 9.

## [1.0.0] - 2026-02-13

### Added
- Complete npm package implementation with CLI
- 5 commands: add, update, push, version, status
- Interactive prompts with colored terminal output
- Git subtree bidirectional sync (pull updates + push improvements)
- Professional utility classes (git, ui, symlinks)
- Comprehensive README with npm/npx usage
- 9 generic AI agents (codebase-architect, coder, code-reviewer, doc-reviewer, prd, plan-auditor, senior-researcher, api-perf-cost-auditor)
- 17 domain modules (architecture, workflows, testing, security, etc.)
- 3 commands (/new, /design-review, /issue-review)
- Templates (feature-card, spec, breaking-change-checklist, ui-guidelines, brand-guidelines)
- Symlink-based auto-update mechanism
- Customizable files (hooks, UI guidelines, templates)

### Changed
- **BREAKING**: Replaced bash scripts with npm package
- **BREAKING**: Installation now via `npx baldart add` instead of bash script
- **BREAKING**: All commands now use `npx baldart <command>` syntax
- **BREAKING**: Framework files moved to `framework/` subdirectory

### Removed
- install-framework.sh (replaced by src/commands/add.js)
- update-framework.sh (replaced by src/commands/update.js)
- push-improvements.sh (replaced by src/commands/push.js)

## [1.1.0] - 2026-02-27

### Added
- 12 new specialized agents: hybrid-ml-architect, hyper-gamification-designer, legal-counsel-gdpr, marketing-conversion-strategist, motion-expert, onboarding-architect-lead, qa-sentinel, remotion-animator-orchestrator, seo-analytics-strategist, ui-expert, visual-designer, website-orchestrator
- `/qa` command: Standardized QA workflow with 3 profiles (Light, Balanced, Deep)
- `/check` command: Pre-development parallel quality audits on backlog cards using agent teams
- QA Protocol in REGISTRY.md with profile selection guide and self-healing loop
- Batch tracking protocol in `/new` command for parallel session safety
- Context recovery protocol in `/new` for resilience after context compaction
- Phase 3.5 (QA Validation) in `/new` pipeline with automatic profile selection
- Phase 7 (Production Readiness Checklist) in `/new` pipeline
- Branching Strategy section in AGENTS.md (Simplified Git Flow with develop branch)
- Execution Modes section in AGENTS.md (local/cloud/hotfix)
- Pre-commit vs Pre-PR build check separation in AGENTS.md
- Macro Feature Identification Protocol in doc-reviewer
- Single Source of Truth (SSOT) Protocol in doc-reviewer
- Post-Development Doc Debt tracking in doc-reviewer
- Multi-phase review with risk matrix in plan-auditor
- PRD Review & Confirmation phase (Phase 2.1) with [TO CONFIRM] markers in prd
- API Performance & Cost Audit phase (Phase 2.5) in prd
- Autonomous technical decisions (Phase 1A) in prd — agent decides tech, asks user only product questions
- Parent/child card structure with epic support in prd
- Bug card template with mandatory clarity analysis in prd

### Changed
- **REGISTRY.md**: Expanded from 9 to 21 agents with compact table format, decision tree, and QA Protocol section
- **doc-reviewer**: Major rewrite — now fully responsible for documentation (audit + write), with SSOT sync and doc debt tracking
- **plan-auditor**: Enhanced with 4 expert personas (Staff Engineer, Tech Lead, Security Engineer, SRE), anti-patterns checklist, and pre-mortem scenario
- **prd**: Major expansion — one-question-at-a-time discovery, gamification validation for B2C, performance audit integration, detailed card templates
- **senior-researcher**: Improved with AI-readable output format (retrieval index, evidence map, structured reading notes)
- **code-reviewer**: Added structured output format (Critical/Major/Minor/Recommendations), review checklist, and Linked Skills pattern
- **coder**: Added explicit build/test/lint gates, task scoping guidelines, and Linked Skills pattern
- **api-perf-cost-auditor**: Added project context integration section and Linked Skills pattern
- **new.md**: Added worktree grouping, QA validation phase, production readiness checklist, and context recovery
- **AGENTS.md**: Added branching strategy, execution modes, testing gates separation, commit hygiene rules

## [2.1.1] - 2026-05-18

Critical migration-safety fix. v2.0.0 / v2.1.0 introduced a bulk symlink for
`.claude/skills/` that — on existing installations — silently renamed the
user's personal skills directory to `.claude/skills.backup` and replaced it
with the framework symlink. This release fixes the merge strategy and ships
a migration command for repos already affected.

### Added

- **`npx baldart migrate` command** — idempotent recovery for repos that ran
  v2.0.0 / v2.1.0 update with the old bulk-symlink strategy:
  - Detects legacy `.claude/skills` bulk symlink and converts it to a real
    directory.
  - Re-merges framework skills as per-item symlinks alongside user skills.
  - Restores `.claude/skills.backup/*` user skills into `.claude/skills/`
    (skipping name collisions, which stay in `.backup/` for manual review).
  - Removes empty `.backup/` directory at the end.
  - Records remaining collisions in `.baldart/skill-conflicts.json`.
- **Per-skill merge strategy** in `symlinks.js`:
  - `.claude/skills/` is now a **real directory the user owns**.
  - Each framework skill is symlinked individually:
    `.claude/skills/<name> → ../../.framework/framework/.claude/skills/<name>`.
  - User-authored skills coexist in the same directory and are NEVER
    overwritten or backed up by the framework.
  - Name collisions are logged to `.baldart/skill-conflicts.json` with
    enough context for the user to resolve them.
- **`createSymlink` modes** in `symlinks.js`:
  - `safe`: refuses to overwrite a user-customised file/dir (used on fresh
    installs).
  - `prompt` (default): asks before backing up a customised file/dir.
  - `force`: legacy v2.0.x behaviour (no longer used by default).

### Changed

- `npx baldart add` now runs symlink creation in `safe` mode — first-time
  installs never silently rename user customisations.
- `npx baldart update` no longer issues a generic "Recreate broken symlinks?"
  prompt that triggered the destructive path. Instead it explains exactly
  what will happen (per-item merge, no overwrites without confirmation) and
  prompts in `prompt` mode.
- `verifySymlinks()` now spot-checks `.claude/skills/` for the per-item
  layout and reports a clear actionable warning when the legacy bulk
  symlink is detected (`Run npx baldart update or npx baldart migrate`).
- `createAllSymlinks(opts)` is now `async` because some bulk-symlink
  reconciliations may ask the user for confirmation. All callers updated.

### Fixed

- **Destructive update path on existing repos**: v2.0.0 / v2.1.0
  `npx baldart update` could rename `.claude/skills/` to `.claude/skills.backup`
  on existing installs without explicit user consent. The new merge strategy
  never touches user-owned content under `.claude/skills/`.

### Migration (v1.x / v2.0.x / v2.1.0 → v2.1.1)

After updating BALDART:

```bash
npx baldart update     # pulls v2.1.1 framework
npx baldart migrate    # one-shot: converts skills layout, restores .backup
npx baldart status     # confirm
```

`migrate` is idempotent — safe to re-run.

---

## [2.1.0] - 2026-05-18

Minor release that closes the gap left by v2.0.0: the framework now ships the
**scheduled routines** that make agents like `wiki-curator` and `skill-improver`
actually run on a cadence. Without these schedules the auto-learning loops are
dormant, so every BALDART install is now prompted to configure them.

### Added

- **`framework/routines/` directory** with 6 declarative routine specs:
  - `wiki-review` — nightly @ 02:00 UTC — drives the LLM-wiki auto-learning loop
  - `doc-review` — nightly @ 00:00 UTC — audits doc changes, flags SSOT drift
  - `code-review` — nightly @ 01:00 UTC — reviews last-24h commits
  - `skill-improve` — weekly Sunday @ 02:00 UTC — refines skills/agents
  - `ds-drift` — weekly Monday @ 03:00 UTC — design-system drift check (optional)
  - `full-sweep` — weekly Sunday @ 03:00 UTC — full SSOT audit (optional)
- **3 backend adapters** (`src/utils/routine-adapters/`):
  - `claude-code-cloud` — generates `.claude/routines/<name>.json` for RemoteTrigger
  - `github-actions` — generates `.github/workflows/baldart-<name>.yml`
  - `cron` — generates `scripts/routines/<name>.sh` + prints crontab line
- **`npx baldart routines` command** with 4 subcommands:
  - `list` — shows status for every routine (installed / available / blocked /
    skipped / disabled / unavailable)
  - `install <name>` — interactive install with backend picker
  - `disable <name>` — removes the schedule artifact and updates the lock
  - `doctor` — verifies installed routines still have their artifacts present
- **Post-install / post-update wizard** — `npx baldart add` and
  `npx baldart update` now surface newly-available routines and offer to
  configure them right after a successful install/update. Skipped routines
  are remembered in `.baldart/routines.lock.json` and not re-prompted.
- **`.baldart/routines.lock.json`** — durable record of which routines are
  installed, their backend, since-version, and per-routine status.

### Changed

- `npx baldart add` next-steps box now includes a "Review scheduled routines"
  step pointing to `npx baldart routines list`.
- `npx baldart update` "what changed" box now includes a routines review
  pointer.
- Added `js-yaml@^4.1.0` as a runtime dependency for parsing routine specs.

### Notes

- Routines are **opt-in per backend**: BALDART never installs schedules
  silently. The user picks the backend and reviews the generated artifact
  before committing.
- The wizard skips routines whose `required_artifacts` are missing (e.g.
  `wiki-review` is blocked until `docs/wiki/` exists). Run `npx baldart
  routines install <name>` later once the artifact lands.
- The CLI is non-interactive when stdin is not a TTY — safe to run in CI
  contexts (the wizard simply exits).

---

## [2.0.0] - 2026-05-18

Major release distilled from ~3 months of intensive use of v1.1.0 inside a real
multi-merchant fidelity/loyalty platform. Introduces the **Skills** category, a
new **LLM Wiki Overlay methodology**, the `/codexreview` deep multi-agent code
review command, and a substantial rewrite of every core agent.

### Added

- **Skills category (new)**: `framework/.claude/skills/` directory with 22
  generic, portable skills shipped out-of-the-box:
  - Workflow skills: `new`, `prd`, `prd-add`, `bug`, `simplify`,
    `worktree-manager`, `issue-review`, `context-primer`
  - Code-quality skills: `skill-creator`, `find-skills`,
    `webapp-testing`, `playwright-skill`
  - Design skills: `frontend-design`, `ui-design`, `motion-design`,
    `gamification-design`
  - Product skills: `seo-audit`, `copywriting`, `api-design-principles`
  - Knowledge skills: `doc-writing-for-rag`, `capture` (LLM wiki overlay)
  - Integration: `kie-ai`, `remotion-best-practices`
- **5 new agents**:
  - `security-reviewer` — dedicated AppSec auditor for auth/secrets/multi-tenant/infra
  - `deep-human-insight` — psychological / sociological analysis for B2C UX
  - `email-deliverability-architect` — transactional email design + deliverability
  - `skill-improver` — weekly auto-improvement of skills/agents based on review/QA findings
  - `prd-card-writer` — atomic backlog-card generation from approved PRDs
  - `wiki-curator` — derived LLM wiki overlay maintenance (re-introduced and generalized)
- **`/codexreview` command** — deep multi-agent code review with mandatory
  false-positive validation. Pools findings from `code-reviewer`,
  `security-reviewer`, `api-perf-cost-auditor`, `doc-reviewer`, and
  `plan-auditor` into a single ranked report with dedup and risk weighting.
- **`agents/llm-wiki-methodology.md`** — full methodology for building a
  derived LLM wiki overlay: layered knowledge stack, page types, frontmatter
  discipline, auto-learning loop (RAG telemetry → synthesis candidates →
  `/capture` skill → new wiki pages), adoption checklist.
- **Post-Approval Complexity Gate** (in `coder.md`) — explicit isolation-mode
  decision (heavy/light worktree) made by the orchestrator before spawning
  the coder, eliminating ambiguity about who owns branch creation.
- **High-Risk Path Code Review trigger table** (in `plan-auditor.md`) — five
  generic triggers that force a per-card `/codexreview` BEFORE merge.
- **Specialist Auto-Spawn matrix** (in `plan-auditor.md`) — auto-routes plans
  to specialist auditors (security, api-perf, ui, ml, docs) based on domain
  signals.
- **NFR enforcement framework** in `code-reviewer.md` and `new` skill —
  generic performance / security / accessibility MUST rules with a project-
  adaptable anti-pattern list (the "Never demote" cluster).
- **Findings Schema** — single YAML emit shape across all reviewing agents so
  `/codexreview` can pool and rank findings.
- **YOLO MODE protocol** — explicit instruction that all Task-spawned
  subagents must use `mode: "bypassPermissions"`.

### Changed

- **All 9 core agents heavily rewritten** based on 3 months of production use
  in a real project:
  - `plan-auditor.md` (+22 KB): 4 expert personas (Staff Eng / Tech Lead /
    Security / SRE), High-Risk Path triggers, Specialist Auto-Spawn matrix,
    multi-phase review with risk matrix, anti-patterns checklist, pre-mortem.
  - `coder.md` (+17.6 KB): explicit build/test/lint gates, design-system
    SSOT read protocol, conditional binary-outcome items, reuse analysis,
    branch/worktree safety check, completion-report schema, Linked Skills.
  - `codebase-architect.md` (+15.3 KB): Linking Protocol v1 for canonical-
    source resolution, design-system resolution path, agent memory, retrieval
    protocol consumption.
  - `code-reviewer.md` (+14.3 KB): confidence-based filtering (HIGH/MED/LOW),
    "Never demote" anti-pattern cluster, Findings Schema emission, design-
    system compliance section, scope boundary discipline.
  - `doc-reviewer.md` (+10.6 KB): design-system scope (when project has one),
    SCIP-symbol code refs, drift validator suite integration.
  - `api-perf-cost-auditor.md` (+9.2 KB): project context integration, agent
    memory, Findings Schema, retrieval protocol consumption.
  - `qa-sentinel.md` (+6.5 KB): clear gate-runner mandate (no AC checks, no
    code analysis), command output management, operating modes
    (Quick / Full / Deep), documentation coverage check.
  - `senior-researcher.md` (+2.7 KB): retrieval-optimized report format,
    structured reading notes, agent memory.
  - `REGISTRY.md` (+5.4 KB): expanded to 24 agents, Model Selection Matrix,
    QA Protocol, Domain Ownership map, doc-writing responsibility split.
- **REGISTRY.md** is the **single source of truth** for agent invocation
  routing. `agents/index.md` defers to it.
- **`coder` vs `doc-reviewer` responsibility split** — `coder` writes only
  minimal invariant doc stubs (API index, UI route, collection entry, env
  var, dependency, SSOT registry); `doc-reviewer` owns full doc writing,
  SSOT sync, and drift resolution.
- **Pre-commit vs Pre-PR build separation** (clarified in AGENTS.md): lint +
  typecheck + markdownlint at every commit; full `npm run build` and test
  suite only before opening a PR. This matches multi-hour build realities
  in larger projects.

### Removed

- **`obsidian-sync` agent** removed from the core framework. Mirroring docs to
  an external corpus (Obsidian / Notion / Confluence) is **project-specific**
  and should ship as a project-local agent. The `wiki-curator` agent still
  mentions external-vault sync as an optional coordination step.
- **Standalone `/qa` command file**. QA functionality remains available via
  the `qa-sentinel` agent and the `/new` skill's Phase 3.5; invocation
  patterns documented in REGISTRY.md § QA Protocol.

### Migration Guide (v1.1.0 → v2.0.0)

- Run `npx baldart update` from your project root. The framework files in
  `.framework/` are auto-updated via git subtree.
- The new `.claude/skills/` directory will land in your project after the
  update. Existing skills you authored locally are not touched.
- If you relied on the old `obsidian-sync` agent, move it to your project's
  `.claude/agents/` (it's a project-specific concern). Update any references
  in `AGENTS.md` to make the dependency explicit.
- The new `wiki-curator` agent expects a `docs/wiki/` directory and a
  frontmatter validator. See `framework/agents/llm-wiki-methodology.md` for
  the adoption checklist; the agent degrades gracefully if you don't ship a
  RAG layer.
- Calls to `Task tool` spawning subagents must now pass
  `mode: "bypassPermissions"` (the YOLO MODE protocol). Without this flag the
  subagent will pause for permission prompts and break the orchestration.

---

## [Unreleased]

### Added

### Changed

### Deprecated

### Removed

### Fixed

### Security

---

## Version Format

**MAJOR.MINOR.PATCH**

- **MAJOR**: Breaking changes (incompatible API updates, directory structure changes, removed features)
- **MINOR**: New features (backwards compatible additions)
- **PATCH**: Bug fixes (backwards compatible fixes)

## Change Categories

- **Added**: New features
- **Changed**: Changes in existing functionality
- **Deprecated**: Soon-to-be removed features
- **Removed**: Removed features
- **Fixed**: Bug fixes
- **Security**: Security fixes
