baldart 4.27.1 → 4.28.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,23 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.28.0] - 2026-06-11
9
+
10
+ **`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).
11
+
12
+ ### Added
13
+
14
+ - **`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).
15
+ - **`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.
16
+
17
+ ## [4.27.2] - 2026-06-11
18
+
19
+ **`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`).
20
+
21
+ ### Changed
22
+
23
+ - **`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.
24
+
8
25
  ## [4.27.1] - 2026-06-11
9
26
 
10
27
  **`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).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.27.1
1
+ 4.28.0
@@ -93,6 +93,14 @@
93
93
  You MUST actively verify and produce one of the two outcomes (implementation OR TODO comment).
94
94
  See `coder.md § Conditional Requirements — Binary-Outcome Items`.
95
95
 
96
+ ### Migration context (include ONLY when this card appears in the tracker `## Migration` manifest's `affects_cards`)
97
+ The required DB migration `<summary>` was ALREADY APPLIED to the active DB before this batch
98
+ started (Phase 0 step 1b, modality `<id>`). The schema is LIVE — run REAL validation against it
99
+ (`validation_commands`, queries, DB-generated types). Do NOT defer the schema-apply AC and do NOT
100
+ build against a stubbed/absent schema: the table/column exists. If `## Migration` shows
101
+ `status: degraded` instead (artifact missing / not applied), treat the schema as NOT yet live and
102
+ follow the card's normal owner-gated deferral path.
103
+
96
104
  ### Business Context (WHY this feature exists)
97
105
  [paste business_rationale field from card YAML]
98
106
  [if field is empty/missing, read PRD Section 1b from card's links.prd path]
@@ -17,6 +17,19 @@
17
17
 
18
18
  1. **Resolve `$MAIN`** — the absolute path of the main repo (not a worktree). If `/new` was invoked from inside a worktree, walk up to the parent repo via `git rev-parse --show-superproject-working-tree` or `git worktree list` until you find the non-worktree root. Persist as `Main repo:` in the tracker `## Worktree` section. **Write `$MAIN` to the tracker the moment it is computed** — every later consumer (Phase 6c, Phase 6b) MUST re-read it from the tracker and HALT with "`$MAIN` absent from tracker" if the field is missing or empty, never silently use an undefined `$MAIN` (it does not survive context compaction).
19
19
 
20
+ 1b. **Migration Gate (BLOCKING only when a migration is *declared* — else a silent no-op)** — resolve DB migrations interactively **before** the worktree exists, so the schema is live before any card builds against it. *Why this exists*: a migration applied to a shared/remote DB is owner-gated, so without this gate it is deferred to the END of the batch — and every downstream card in the batch is then built and verified against a schema that is not yet live (`validation_commands` / QA / E2E / DB-generated `tsc` types fail falsely → those cards cascade into deferral/blocked). Front-loading the migration removes that root cause. **The declaration lives in the EPIC card** (`migration_plan` block — project-specific, authored by the user, typically via the `.baldart/overlays/new.md` overlay). Steps:
21
+
22
+ 1. **Find the batch's epic card(s).** Collect the distinct `group.parent` of the batch cards (or, for a `-full` invocation, the parent already known). For each, locate the epic YAML in `${paths.backlog_dir}` (filename `<PARENT>-*-epic.yml`, or the card whose `id == <PARENT>` / `group.is_epic: true`) and Read it.
23
+ 2. **Look for `migration_plan` with `required: true`.** If **no** epic in the batch declares one → write `## Migration\nnone (no migration_plan declared)` to the tracker and **proceed to step 2** (behaviour is identical to today — zero extra prompts). This is the common case; do not surface anything.
24
+ 3. **(declared) Verify the artifacts exist on disk.** For each path in `migration_plan.artifacts`, check it exists under `$MAIN`. If **any is missing** → do **NOT** apply or prompt; write `## Migration\ndeclared but artifact(s) missing: <paths> — degraded to deferred (author the migration first)` to the tracker and **proceed to step 2** (the migration falls back to the current end-of-batch owner-gated deferral — no regression). *(Auto-generating a missing artifact is out of scope; the migration must already be authored to be applied up-front.)*
25
+ 4. **(declared + artifacts present) Assemble the apply modalities**, in this source order, de-duplicating by `id`: (a) `migration_plan.apply_modalities` from the epic block; (b) a `## Migration modalities` section in `.baldart/overlays/new.md` if present; (c) any project-memory note on how this project applies migrations; (d) the built-in tail `["Già applicata — prosegui", "Abort"]`. Each modality is `{ id, label, command? }`.
26
+ 5. **`AskUserQuestion`** — `"La epic dichiara una migrazione DB (<summary>). Va applicata PRIMA del batch così le card downstream verificano contro lo schema reale. Come procedo?"` with up to 4 of the assembled modalities (always include "Già applicata — prosegui" and "Abort" as the last options). This is a legitimate Phase 0 question (Auto Mode does not override it).
27
+ 6. **Execute the choice in `$MAIN`** (project env):
28
+ - a **command** modality → run it with output to disk (`<cmd> > /tmp/migration-<FIRST-CARD-ID>.log 2>&1`), surface only the exit code; on exit 0 run the optional `migration_plan.verify` probe and require it green too. On **failure** → surface a bounded extract (`tail -n 30`) and re-ask (re-offer the modalities + Abort); never silently proceed against a non-live schema. **Never run a command without the user having selected it.**
29
+ - **"Già applicata — prosegui"** → run the optional `verify` probe if present; otherwise trust the user.
30
+ - **"Abort"** → HALT the batch cleanly (leave the tracker in place); do not create a worktree.
31
+ 7. **Record the manifest** in the tracker `## Migration` section: `status: applied|skipped|degraded`, `modality: <id>`, `artifacts: [...]`, `affects_cards: [...]`, `applied_at: <timestamp>`. Phase 1/2 briefings (implement.md) MUST surface, for any card in `affects_cards`, the note *"migration `<summary>` already applied to the active DB — run REAL validation (do not defer the schema AC)"*, so the live schema is actually exercised and the card's migration-apply AC is treated as satisfied, not deferred.
32
+
20
33
  2. **Fetch remote state**:
21
34
  ```bash
22
35
  git -C "$MAIN" fetch origin --quiet
@@ -86,6 +86,43 @@ to its backlog YAML path under `${paths.backlog_dir}`.
86
86
  Read them for the review SEMANTICS, so `new2` stays a thin orchestration host and
87
87
  the A/B test isolates the host, not the protocol).
88
88
 
89
+ ### Step 3.5 — Migration Gate (BLOCKING only when a migration is *declared* — else a silent no-op)
90
+
91
+ The workflow runs autonomously and **cannot** apply an owner-gated DB migration mid-run, so today a
92
+ declared migration is deferred to the end of the batch — and every downstream card is built and
93
+ verified against a schema that is not yet live (`validation_commands` / QA / E2E / DB-generated `tsc`
94
+ types fail falsely → those cards cascade into deferral). This gate front-loads the migration: it runs
95
+ **here, in the skill** (the main loop — it can interact; the workflow's zero-ask contract is
96
+ untouched), so the schema is live **before** the workflow starts. It mirrors `/new` Phase 0 step 1b
97
+ (`references/setup.md`) — same algorithm, same `migration_plan` epic-card convention.
98
+
99
+ 1. **Find the batch's epic card(s)** — distinct `group.parent` of the resolved cards (or the `-full`
100
+ parent). Read each epic YAML in `${paths.backlog_dir}` (`<PARENT>-*-epic.yml`, or the card whose
101
+ `id == <PARENT>` / `group.is_epic: true`).
102
+ 2. **Look for `migration_plan` with `required: true`.** None → set `migration = { status: 'none' }`
103
+ and go to Step 4 (identical to today — zero extra prompts; do not surface anything).
104
+ 3. **(declared) Verify `migration_plan.artifacts` exist on disk under `$MAIN`.** Any missing → do NOT
105
+ apply or prompt; set `migration = { status: 'degraded', reason: 'artifact missing', artifacts }`
106
+ and go to Step 4 (the migration falls back to the current end-of-batch owner-gated deferral — no
107
+ regression).
108
+ 4. **(declared + present) Assemble the apply modalities**, de-duped by `id`, in source order:
109
+ (a) `migration_plan.apply_modalities`; (b) a `## Migration modalities` section in
110
+ `.baldart/overlays/new.md`; (c) any project-memory note on how this project applies migrations;
111
+ (d) built-in tail `["Già applicata — prosegui", "Abort"]`.
112
+ 5. **`AskUserQuestion`** — `"La epic dichiara una migrazione DB (<summary>). La applico PRIMA del
113
+ batch così le card downstream verificano contro lo schema reale. Come procedo?"` with up to 4
114
+ modalities (always include "Già applicata — prosegui" and "Abort"). This is the SAME class as the
115
+ Step-2 "ONE pre-launch question" — pre-launch, not a mid-run gate; the zero-ask contract is about
116
+ the *workflow*, which is untouched.
117
+ 6. **Execute the choice in `$MAIN`**:
118
+ - a **command** modality → run with output to `/tmp/migration-<firstCard>.log`, surface only the
119
+ exit code; on exit 0 run the optional `migration_plan.verify` probe and require it green. On
120
+ failure → bounded extract (`tail -n 30`) + re-ask. **Never run a command the user did not pick.**
121
+ - **"Già applicata — prosegui"** → run `verify` if present; else trust the user.
122
+ - **"Abort"** → HALT cleanly; do NOT call the workflow.
123
+ 7. **Build the manifest** to pass to the workflow:
124
+ `migration = { status: 'applied'|'skipped', modality: <id>, summary, artifacts: [...], affects_cards: [...], appliedAt: ts }`.
125
+
89
126
  ### Step 4 — Delegate the whole batch to the workflow
90
127
 
91
128
  Call the `Workflow` tool:
@@ -100,6 +137,7 @@ Workflow({ name: 'new2', args: {
100
137
  refModulesBase, // .claude/skills/new/references (semantic SSOT)
101
138
  config, // the parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
102
139
  ts, // ISO timestamp NOW — the workflow has no clock (Date.now() unavailable there)
140
+ migration, // Step-3.5 manifest: { status:'none'|'applied'|'skipped'|'degraded', modality?, summary?, artifacts?, affects_cards? }
103
141
  flags: { stats, effort, full }
104
142
  }})
105
143
  ```
@@ -185,4 +223,8 @@ returns when the batch is done. It returns:
185
223
  `residualFollowups.length` — which double-counts). `total_tokens`/`agent_count` come from the
186
224
  workflow; if `total_tokens` is null, run the `/new` Phase-8 `-stats` script to backfill real
187
225
  `usage`. Keep `degraded`/`degradation_reasons` + `cards_deferred_done_pending` in the record so
188
- the A/B comparison stays honest. Do NOT re-summarise the cards — the workflow already did.
226
+ the A/B comparison stays honest. Also record `migration_gate: <migration.status>`
227
+ (`none`|`applied`|`skipped`|`degraded`) — the Step-3.5 gate is a pre-launch interaction, NOT a
228
+ mid-batch question, so it does not break the zero-ask-during-batch invariant; logging it keeps the
229
+ A/B honest about when a migration was front-loaded. Do NOT re-summarise the cards — the workflow
230
+ already did.
@@ -143,6 +143,34 @@ execution_strategy:
143
143
  - "{{FEAT-XXXX-N}} e {{FEAT-XXXX-M}} entrambi modificano {{file}} — forzato sequenziale"
144
144
  # omit if no conflicts
145
145
 
146
+ # =============================================================================
147
+ # Migration Plan (OPTIONAL — declare ONLY when the epic needs a DB schema change
148
+ # applied BEFORE its cards run). Read by /new (Phase 0 step 1b) and new2 (Step 3.5):
149
+ # the skill front-loads the migration interactively so downstream cards verify
150
+ # against the LIVE schema instead of a deferred one. Project-specific — the shape of
151
+ # `apply_modalities` (db:push, local apply, …) belongs in the consumer's
152
+ # `.baldart/overlays/new.md`; see framework/templates/overlays/new.migration-example.md.
153
+ # OMIT this block entirely when the epic introduces no schema change (the common case;
154
+ # the gate then stays a silent no-op). The migration `artifacts` must already exist on
155
+ # disk to be applied up-front — if absent, the gate degrades to the current
156
+ # end-of-batch owner-gated deferral.
157
+ # =============================================================================
158
+
159
+ # migration_plan:
160
+ # required: true
161
+ # summary: "{{Add bookings.status enum column + composite index}}"
162
+ # artifacts:
163
+ # - {{supabase/migrations/20260611_add_status.sql}}
164
+ # apply_modalities: # de-duped by id; merged with overlays/new.md + project memory
165
+ # - id: remote-push
166
+ # label: "db:push al DB remoto (eseguito dalla skill su tua approvazione)"
167
+ # command: "{{npx supabase db push}}"
168
+ # - id: local
169
+ # label: "Applica in locale"
170
+ # command: "{{npx supabase db reset --local}}"
171
+ # affects_cards: [{{FEAT-XXXX-02}}, {{FEAT-XXXX-03}}] # downstream che richiedono lo schema live
172
+ # verify: "{{npx supabase gen types typescript --local | head}}" # opzionale — probe post-apply
173
+
146
174
  # =============================================================================
147
175
  # Canonical Docs (epic-level — children inherit subset)
148
176
  # =============================================================================
@@ -1,7 +1,7 @@
1
1
  export const meta = {
2
2
  name: 'new2-resolve',
3
3
  description:
4
- "Self-healing resolution pass for the autonomous new2 batch workflow. Called by new2 whenever a deterministic gate would otherwise need a human: a card fail/blocker (ac-unmet | blocker | qa-fail | e2e-blocked | merge-blocker) or a legitimate scope-EXPANDING finding (scope-expansion). Tier-1 targeted fix with a TERMINAL short-circuit (skips the costly multi-attempt when the problem is impossible-by-definition, verified not trusted), then a judged multi-attempt; a MANDATORY adversarial judge cross-checks every verified claim against the real diff (prevents fabricated success) for every domain EXCEPT doc doc-reviewer is a reviewer-writer that owns its domain (applies + self-verifies in one pass), so a second adversarial doc-reviewer would just judge itself: wasteful, so the doc judge is skipped. Specialized per domain (doc→doc-reviewer single pass, ui→ui-expert, security→security-reviewer judge, perf→api-perf-cost-auditor judge). Terminal is a tracked follow-up. Accepts a `findings` list (batched per area). Uses agent()/parallel() only — no nested workflows.",
4
+ "Self-healing resolution pass for the autonomous new2 batch workflow. Called by new2 whenever a deterministic gate would otherwise need a human: a card fail/blocker (ac-unmet | blocker | qa-fail | e2e-blocked | merge-blocker) or a legitimate scope-EXPANDING finding (scope-expansion). Tier-1 targeted fix with a TERMINAL short-circuit (skips the costly multi-attempt when the problem is impossible-by-definition, verified not trusted), then a judged multi-attempt; a MANDATORY adversarial judge cross-checks every verified claim against the real diff (prevents fabricated success) whenever the judge is a DIFFERENT specialist than the fixer. When fixer === judge (doc→doc-reviewer, security→security-reviewer) the second pass would just judge its own work no cross-model diversity, pure waste so the judge AND the terminal-verdict ratification are skipped (the reviewer-writer self-verifies in its single pass). Specialized per domain (doc→doc-reviewer single pass, ui→ui-expert fix + code-reviewer judge, security→security-reviewer single pass, perf→api-perf-cost-auditor judge). Terminal is a tracked follow-up. Accepts a `findings` list (batched per area). Uses agent()/parallel() only — no nested workflows.",
5
5
  phases: [
6
6
  { title: 'Diagnose', detail: 'classify + terminal short-circuit + scope-expansion boundary' },
7
7
  { title: 'Repair', detail: 'targeted fix, then judged multi-attempt if needed' },
@@ -110,6 +110,15 @@ const judgeAgent = (domain === 'security' || domain === 'migration') ? 'security
110
110
  : domain === 'doc' ? 'doc-reviewer'
111
111
  : domain === 'test' ? 'qa-sentinel'
112
112
  : 'code-reviewer'
113
+ // Anti-self-judging — when the fixer and the adversarial judge resolve to the SAME agent type
114
+ // (doc→doc-reviewer, security→security-reviewer), the "judge" would just review its own work:
115
+ // no cross-model diversity, pure waste of tokens and time. These are reviewer-writer specialists
116
+ // that own their domain and self-verify (re-run the gate) inside the fix pass, so we trust the
117
+ // single pass and skip both the judge cross-check and the terminal-verdict ratification. Domains
118
+ // where fixer ≠ judge (ui/perf/test/code/migration) keep the mandatory adversarial cross-check —
119
+ // there the judge is a genuinely independent specialist. Condition is structural (not a domain
120
+ // allowlist), so a future fixer/judge map change can't silently re-introduce self-judging.
121
+ const selfJudges = fixerAgent === judgeAgent
113
122
 
114
123
  const findingsBlock = findings.map((f, i) => ` ${i + 1}. [${f.kind || kind}/${f.domain || domain}] ${f.evidence}`).join('\n')
115
124
  const brief = [
@@ -197,10 +206,10 @@ if (attempt && attempt.terminal) {
197
206
  let confirmed = false
198
207
  if (tr === 'out-of-ownership') {
199
208
  confirmed = !filesInScope(attempt.remedyFiles) // genuinely terminal iff remedy files are NOT in MAY-EDIT
200
- } else if (domain === 'doc') {
201
- // doc is fixed by doc-reviewer (reviewer-writer that owns its domain). Ratifying its
202
- // terminal verdict with a SECOND doc-reviewer is doc-reviewer-judges-doc-reviewer
203
- // no cross-model diversity, pure waste. Trust the single pass; no adversarial re-run.
209
+ } else if (selfJudges) {
210
+ // fixer === judge (doc→doc-reviewer, security→security-reviewer): ratifying the terminal
211
+ // verdict with a SECOND instance of the same specialist is self-judging no cross-model
212
+ // diversity, pure waste. Trust the reviewer-writer's single pass; no adversarial re-run.
204
213
  confirmed = true
205
214
  } else {
206
215
  // owner-gated / not-a-code-defect / baseline-not-reached — ratify with the judge.
@@ -265,11 +274,12 @@ return await materialiseFollowup(kind, (attempt && attempt.note) || 'unresolved
265
274
  // returns the files it independently confirmed changed; we cross-check ⊆ MAY-EDIT.
266
275
  async function judgeVerify(verifiedAttempts) {
267
276
  if (!verifiedAttempts.length) return { ok: false, best: 0 }
268
- // doc-reviewer is a reviewer-writer that owns its domain: it applies the fix AND
269
- // self-verifies in the same pass. A SECOND adversarial doc-reviewer cross-check is
270
- // doc-reviewer-judges-doc-reviewer no cross-model diversity, a pure waste of tokens
271
- // and time. Trust the single pass; accept the first verified attempt without re-spawning.
272
- if (domain === 'doc') {
277
+ // fixer === judge (doc→doc-reviewer, security→security-reviewer): the reviewer-writer
278
+ // already applied the fix AND self-verified (re-ran the gate) in the same pass. A SECOND
279
+ // adversarial instance of the same specialist would just judge its own work — no cross-model
280
+ // diversity, a pure waste of tokens and time. Trust the single pass; accept the first
281
+ // verified attempt without re-spawning. Domains where fixer ≠ judge keep the cross-check.
282
+ if (selfJudges) {
273
283
  const first = verifiedAttempts.find((v) => v.r && v.r.verified) || verifiedAttempts[0]
274
284
  return { ok: true, best: first.i }
275
285
  }
@@ -20,6 +20,10 @@ export const meta = {
20
20
  // metricsDir string paths.metrics (telemetry dir)
21
21
  // refModulesBase string dir holding /new reference modules (semantic SSOT)
22
22
  // config object parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
23
+ // migration object Migration Gate manifest (skill Step 3.5): { status:'none'|'applied'|
24
+ // 'skipped'|'degraded', modality?, summary?, artifacts?, affects_cards? }.
25
+ // status:'applied' → the declared schema is LIVE before the batch, so the
26
+ // affected cards' apply-AC is NOT owner-gated-deferred (F-016 exception).
23
27
  // flags { stats, effort, full }
24
28
  // ts string ISO timestamp injected by the skill (Date.now() is unavailable here)
25
29
  //
@@ -45,6 +49,12 @@ const METRICS = a.metricsDir || 'docs/metrics'
45
49
  const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
46
50
  const FLAGS = a.flags || {}
47
51
  const TS = a.ts || ''
52
+ // Migration Gate manifest (skill Step 3.5 — front-loaded BEFORE this workflow ran). When
53
+ // status:'applied', the declared schema is LIVE on the active DB, so its apply-AC must NOT be
54
+ // re-classified as an owner-gated policy-deferred AC (F-016) — the downstream cards verify for real.
55
+ const migration = a.migration || { status: 'none' }
56
+ const migrationApplied = migration && migration.status === 'applied'
57
+ const migrationAffects = Array.isArray(migration.affects_cards) ? migration.affects_cards : []
48
58
  const features = cfg.features || {}
49
59
  const paths = cfg.paths || {}
50
60
  const gitCfg = cfg.git || {}
@@ -230,6 +240,9 @@ try {
230
240
  `• cardGraph (REQUIRED, F-021): for every runnable card return { id, dependsOn:[IN-BATCH deps only], ownerAgent (the card's owner_agent; G25 unknown→'coder'), reviewProfile (the card's review_profile; default 'balanced'), policyDeferredACs, alreadyCommitted, alreadyCommittedSha, isEpic (implement.md §6b epic guard: id ends '-00' OR filename ends '-epic.yml' OR group.is_epic:true OR review_profile 'skip' with no requirements), filesLikelyTouched (verbatim from the YAML) }.\n` +
231
241
  `• B1/F-026 idempotency (per card, AFTER the worktree exists): set alreadyCommitted:true (+ alreadyCommittedSha) IFF ALL hold: (a) a commit referencing the card id exists in ${TRUNK}..HEAD of the worktree; (b) the card's validation_commands re-run GREEN right now; (c) NO open follow-up card for it exists in ${paths.backlog_dir || 'backlog'}. On a FRESH worktree ${TRUNK}..HEAD is empty → all false, zero extra work.\n` +
232
242
  `• F-016 AC↔ownership consistency: for each acceptance_criterion, derive the file(s) it requires editing. If those files are NOT a subset of the card's MAY-EDIT/files_likely_touched → add the AC to policyDeferredACs:[{n,text,owningCard|owningFile,reason}] (it will become ONE follow-up, never a resolve). Do the same for any AC whose remedy is an owner-gated infra action (remote db push / deploy / secret / DNS).\n` +
243
+ (migrationApplied
244
+ ? `• MIGRATION ALREADY APPLIED (skill Migration Gate, Step 3.5): the DB migration "${migration.summary || '(declared)'}" was applied to the active DB BEFORE this batch — the schema is LIVE. EXCEPTION to the F-016 owner-gated rule above: do NOT classify the schema-apply / "run migration" / db-push AC of card(s) [${migrationAffects.join(', ') || '(affects_cards from the epic)'}] as policyDeferredAC — it is SATISFIED. Those cards must run REAL validation against the live schema (validation_commands, queries, DB-generated types), not defer it. (A migration NOT covered by this manifest still follows the owner-gated deferral above.)\n`
245
+ : '') +
233
246
  `• Ownership (setup.md 3c): build the file-ownership map → /tmp; return ownershipMapPath. F-040: each card's MAY-EDIT = files_likely_touched ∪ every path NAMED EXPLICITLY in that card's acceptance_criteria/definition_of_done (an ADR the DoD says to update, the data-model / ER doc for a schema-change, etc.) — so editing a DoD-mandated doc is NOT a file-diff violation. Do NOT add another card's files this way.\n` +
234
247
  `• Do NOT write architecture baselines — the per-card codebase-architect specialist does that during the card pipeline (B7).\n\n` +
235
248
  `Return the structured PREFLIGHT object. ok:false ONLY if the workspace is unworkable.`,
@@ -429,7 +442,10 @@ async function runCard(cardId, cardPath) {
429
442
  return { card: cardId, status: 'committed', commit: node.alreadyCommittedSha || '-', filesChanged: [], scopeFiles: [], archBaselinePath: `/tmp/arch-baseline-${cardId}.md`, gates }
430
443
  }
431
444
 
432
- const cardBrief = `${projectBrief}\n\nCard: ${cardId}\nCard YAML: ${cardPath}\nOwner agent: ${ownerAgent} · Review profile: ${reviewProfile}\nWorktree: ${sharedCtx.worktreePath} (cd into it)\nFile-ownership map: ${sharedCtx.ownershipMapPath}\nNOTE: ACs already pre-classified as policy-deferred MUST NOT be implemented or routed — they are tracked as follow-ups.`
445
+ const migrationNote = (migrationApplied && migrationAffects.includes(cardId))
446
+ ? `\nMIGRATION LIVE: the DB migration "${migration.summary || '(declared)'}" was applied to the active DB BEFORE this batch (skill Migration Gate). The schema is REAL — run actual validation against it (validation_commands, queries, DB-generated types); do NOT defer the schema-apply AC or build against an absent schema.`
447
+ : ''
448
+ const cardBrief = `${projectBrief}\n\nCard: ${cardId}\nCard YAML: ${cardPath}\nOwner agent: ${ownerAgent} · Review profile: ${reviewProfile}\nWorktree: ${sharedCtx.worktreePath} (cd into it)\nFile-ownership map: ${sharedCtx.ownershipMapPath}\nNOTE: ACs already pre-classified as policy-deferred MUST NOT be implemented or routed — they are tracked as follow-ups.${migrationNote}`
433
449
 
434
450
  // --- Phase 1 (B7, v4.26.0) — SPECIALIST decomposition: each Phase-1 duty runs as its own
435
451
  // agent ("ognuno fa una cosa"), with handoff via /tmp files so the owner's context stays
@@ -966,6 +982,10 @@ function buildTelemetry() {
966
982
  cards_followup: perCardResults.filter((r) => r.status === 'followup').length,
967
983
  cards_blocked: runnableCards.filter((id) => state[id] === 'blocked').length,
968
984
  cards_deferred: residuals.filter((x) => x.kind === 'policy-deferred-ac').length,
985
+ // Migration Gate (skill Step 3.5, pre-launch — NOT a mid-batch question, so the zero-ask-during
986
+ // -batch invariant is intact). 'applied' = schema front-loaded; the affected cards' apply-AC was
987
+ // satisfied up-front instead of deferred owner-gated.
988
+ migration_gate: (migration && migration.status) || 'none',
969
989
  residuals_total: residuals.length,
970
990
  // followups_on_disk is filled by the SKILL after it materialises pending residuals.
971
991
  followups_materialized_in_workflow: residuals.filter((x) => x.materialized).length,
@@ -0,0 +1,60 @@
1
+ ---
2
+ base_skill: new
3
+ base_skill_version: 4.28.0
4
+ mode: extend
5
+ ---
6
+
7
+ # new — migration-gate overlay (example)
8
+
9
+ > Drop this into your repo at `.baldart/overlays/new.md` (it is shared by `/new`
10
+ > **and** `new2`) to teach the **Migration Gate** how *your* project applies a DB
11
+ > schema change. The gate runs **before** the batch (/new Phase 0 step 1b · new2
12
+ > Step 3.5): when an epic card declares a `migration_plan`, the skill front-loads
13
+ > the migration interactively so downstream cards verify against the **live**
14
+ > schema instead of one deferred to the end of the batch.
15
+ >
16
+ > Two layers cooperate:
17
+ > 1. **The epic card** declares *that* a migration is needed + the artifacts
18
+ > (`migration_plan` block — see `epic-template.yml`).
19
+ > 2. **This overlay** declares *how* this project applies migrations (the
20
+ > `## Migration modalities` section below). The gate merges the epic's
21
+ > `apply_modalities` with these, de-duped by `id`, and adds the built-in
22
+ > `["Già applicata — prosegui", "Abort"]` tail.
23
+ >
24
+ > Adapt the commands to your stack (Supabase / Prisma / Drizzle / raw SQL / …).
25
+
26
+ ## Migration modalities
27
+
28
+ These are offered (via `AskUserQuestion`) whenever an epic declares
29
+ `migration_plan.required: true` and its `artifacts` exist on disk. The skill runs
30
+ the chosen `command` in the main repo (`$MAIN`), captures output to
31
+ `/tmp/migration-<first-card>.log`, surfaces only the exit code, and — when the
32
+ `migration_plan.verify` probe is present — requires it green too. A command is
33
+ **never** run without the user selecting it.
34
+
35
+ - **id: `remote-push`** — applica al DB remoto/condiviso (owner-gated; eseguito
36
+ dalla skill SOLO su tua approvazione esplicita).
37
+ - command: `npx supabase db push`
38
+ - When to pick: dev DB condiviso o staging dove le card verificano contro il
39
+ DB reale.
40
+ - **id: `local`** — applica a un DB locale/shadow; la prod resta un deploy
41
+ manuale post-merge (Phase 7 lo riporta come item manuale).
42
+ - command: `npx supabase db reset --local` *(oppure `npx prisma migrate dev`)*
43
+ - When to pick: il batch verifica contro un DB locale effimero.
44
+ - **id: `prisma-deploy`** — esempio Prisma per uno stack diverso.
45
+ - command: `npx prisma migrate deploy`
46
+
47
+ > Verifica post-apply consigliata (referenziata da `migration_plan.verify`
48
+ > nell'epic): rigenera i tipi dal DB così `tsc` downstream è onesto, es.
49
+ > `npx supabase gen types typescript --local > src/types/db.ts` oppure
50
+ > `npx prisma generate`.
51
+
52
+ ## Notes
53
+
54
+ - Se l'epic NON dichiara `migration_plan` (o `required: false`), il gate è un
55
+ **no-op silenzioso** — nessun prompt aggiuntivo, comportamento identico a oggi.
56
+ - Se gli `artifacts` non esistono ancora su disco, il gate **degrada**: non
57
+ applica nulla e la migrazione segue il path owner-gated di fine batch (nessuna
58
+ regressione). La migrazione va autorata prima per essere front-loaddata.
59
+ - Le modalità possono anche arrivare dalla **memoria di progetto** (oltre che da
60
+ questo overlay e dal blocco epic): la skill le unisce tutte.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.27.1",
3
+ "version": "4.28.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"