baldart 4.15.0 → 4.16.1

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,44 @@ All notable changes to BALDART will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [4.16.1] - 2026-06-05
9
+
10
+ **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`).
11
+
12
+ > **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.
13
+
14
+ ### Fixed
15
+
16
+ - **`framework/.claude/workflows/new2.js`**:
17
+ - **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).
18
+ - **`status:'fatal'` di `new2-resolve` ora onorato** — `resolve()` setta `batchFatal` e sospende final review + merge, invece di degradarlo a `followup`.
19
+ - **Phase 7 Production Readiness aggiunta** (post-merge, non-bloccante) — ripristina la full-parity dichiarata (auto-esegue i deploy stack-matched, riporta gli item manuali).
20
+ - **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).
21
+ - **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.
22
+ - **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).
23
+ - **`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.
24
+
25
+ ## [4.16.0] - 2026-06-05
26
+
27
+ **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).
28
+
29
+ > **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()`).
30
+
31
+ > **`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.
32
+
33
+ > **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`.
34
+
35
+ ### Added
36
+
37
+ - **`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.
38
+ - **`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.
39
+ - **`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).
40
+
41
+ ### Changed
42
+
43
+ - **`framework/docs/WORKFLOWS.md`** — tabella "What ships today": aggiunte le righe `new2` e `new2-resolve` + nota "esperimento, non rimpiazzo".
44
+ - **`README.md`** — conteggio skill 29 → 30; `new2` aggiunto alla categoria Workflow con nota EXPERIMENTAL/Claude-only.
45
+
8
46
  ## [4.15.0] - 2026-06-04
9
47
 
10
48
  **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).
package/README.md CHANGED
@@ -208,12 +208,12 @@ Skills always-ask when required keys are missing — never silently default.
208
208
  never overwrites your file. Full guide:
209
209
  [`framework/docs/PROJECT-CONFIGURATION.md`](framework/docs/PROJECT-CONFIGURATION.md).
210
210
 
211
- ### Skills (29 portable skills)
211
+ ### Skills (30 portable skills)
212
212
 
213
213
  Skills live under `.claude/skills/` and are auto-discovered by Claude Code.
214
214
  Bundled skills:
215
215
 
216
- - **Workflow**: `new`, `prd`, `prd-add`, `bug`, `simplify`, `worktree-manager`, `issue-review`, `context-primer`
216
+ - **Workflow**: `new`, `new2` (v4.16.0 — EXPERIMENTAL workflow-hosted `/new`, Claude-only, for A/B testing context economy), `prd`, `prd-add`, `bug`, `simplify`, `worktree-manager`, `issue-review`, `context-primer`
217
217
  - **Code quality**: `skill-creator`, `find-skills`, `webapp-testing`, `playwright-skill`, `lsp-bootstrap` (v3.10.0), `e2e-review` (v3.18.0)
218
218
  - **Design**: `frontend-design`, `ui-design`, `motion-design`, `gamification-design`, `design-system-init` (v3.11.0)
219
219
  - **Product**: `seo-audit`, `copywriting`, `api-design-principles`
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.15.0
1
+ 4.16.1
@@ -0,0 +1,128 @@
1
+ ---
2
+ name: new2
3
+ effort: high
4
+ description: >
5
+ EXPERIMENTAL workflow-hosted variant of /new (A/B testing). Implements one or
6
+ more backlog cards end-to-end by delegating the WHOLE batch to a background
7
+ dynamic workflow — so subagent output never enters the main orchestrator
8
+ context. Fully autonomous (zero AskUserQuestion): every /new gate is replaced by
9
+ a deterministic policy + a self-healing resolution pass. Claude-only (needs the
10
+ Workflow tool). Usage: /new2 CARD-IDS (same arg grammar as /new). Triggers on:
11
+ /new2, "implementa le card con workflow", "new2".
12
+ ---
13
+
14
+ > **EXPERIMENT — not a replacement for `/new`.** `new2` exists to A/B-test the
15
+ > context-economy of hosting the batch orchestration inside a Claude Code dynamic
16
+ > workflow instead of the main loop. `/new` remains the SSOT, the cross-tool
17
+ > default, and the recovery-safe path. Do NOT route to `new2` unless the user
18
+ > explicitly asks for it.
19
+
20
+ > **ZERO-ASK CONTRACT.** A dynamic workflow cannot prompt the user mid-run. `new2`
21
+ > therefore runs the entire batch autonomously: every `/new` `AskUserQuestion`
22
+ > gate is replaced by a deterministic policy (auto-resolve seamless defaults, or
23
+ > fail → self-healing `new2-resolve`, or — last resort — auto-materialise a
24
+ > tracked follow-up card). Destructive/outward ops (`reset --hard`, force-push,
25
+ > stash drop) are NEVER auto-run; they degrade to "leave intact + report".
26
+
27
+ ## Project Context
28
+
29
+ **Reads from `baldart.config.yml`:** `paths.backlog_dir`, `paths.references_dir`, `paths.metrics`, `paths.high_risk_modules`, `git.trunk_branch` (autodetected from `origin/HEAD` when absent), `git.merge_strategy`, `stack.*`, and `features.has_e2e_review` / `features.has_design_system` / `features.has_lsp_layer`.
30
+ **Gated by features:** `features.has_backlog` (REFUSES to run when `false` — operates on backlog cards by definition).
31
+ **Overlay:** loads `.baldart/overlays/new.md` if present (shared with `/new`: canonical-docs registry, tooling paths).
32
+ **On missing/empty keys:** the deterministic pre-flight autodetects (trunk via `origin/HEAD`); only HALT when nothing resolves. See `framework/agents/project-context.md` § 3.
33
+
34
+ ## Effort
35
+
36
+ **Baseline:** `effort: high` (frontmatter — the workflow runs a full implement+review
37
+ batch autonomously, so it warrants deep reasoning). **Inline override:** pass
38
+ `effort=<low|medium|high|xhigh|max>` anywhere in the invocation; detect once at
39
+ kickoff, strip the token, and forward it to the workflow via `args.flags.effort`
40
+ (agents inside the workflow honour it). Mapping/parsing: `framework/agents/effort-protocol.md`.
41
+
42
+ ---
43
+
44
+ ## What this skill does (thin coordinator — keep it out of the way)
45
+
46
+ `new2` holds **almost no content in the main loop**. Its entire job is: parse the
47
+ invocation, gather project facts, hand the batch to the `new2` background workflow,
48
+ and present the report it returns. All the heavy orchestration (architect, coder,
49
+ reviewers, Codex, QA, E2E, merge) lives in the workflow's isolated runtime — its
50
+ output never lands in your context.
51
+
52
+ ### Step 1 — Degradation gate (BLOCKING, first thing)
53
+
54
+ `new2` is **useless without the `Workflow` tool** (its whole point is the background
55
+ runtime). If the `Workflow` tool is **not available** in this session (OpenAI Codex
56
+ install — workflows are Claude-only; `disableWorkflows` set; or a Claude Code older
57
+ than the dynamic-workflows research preview), do NOT attempt any fallback:
58
+
59
+ > Print: `"/new2 richiede i dynamic workflow di Claude Code (non disponibili in questa sessione). Usa /new — stessa semantica, esecuzione nel main loop."` and **HALT**.
60
+
61
+ Also verify the workflow script is linked: if `.claude/workflows/new2.js` is absent,
62
+ print the same guidance and HALT (the framework did not link it — Codex tree, or a
63
+ pre-v4.16 install).
64
+
65
+ ### Step 2 — Parse the invocation (same grammar as `/new`)
66
+
67
+ Parse `CARD-IDS` exactly as `/new` does — space-separated, hyphen-range
68
+ (`GLOB-001-GLOB-008`), comma-separated, and **epic expansion** (`-full` / `--full`
69
+ expands all children of a parent: filename prefix `<PARENT>-` ∪ cards whose
70
+ `group.parent == <PARENT>`, parent excluded, sorted by `group.sequence`). Strip the
71
+ `-full` / `--full` and `-stats` / `--stats` tokens and the `effort=<level>` token
72
+ before treating the rest as card IDs. If no card IDs are given, ask which cards
73
+ (this is the ONE pre-launch question — it is NOT a mid-run gate). Resolve each card
74
+ to its backlog YAML path under `${paths.backlog_dir}`.
75
+
76
+ ### Step 3 — Feature gate + gather facts
77
+
78
+ - Read `features.has_backlog`. If `false` → print the same refusal as `/new`
79
+ (`"requires features.has_backlog: true — run npx baldart configure"`) and HALT.
80
+ - Read `baldart.config.yml` and resolve: `$MAIN` (the main repo absolute path — walk
81
+ up from a worktree if needed), `$TRUNK` (`git.trunk_branch`, else autodetect via
82
+ `git symbolic-ref refs/remotes/origin/HEAD` → `develop`/`main`/`master`), `$METRICS`
83
+ (`paths.metrics`, default `docs/metrics`), and the `paths.*` / `stack.*` /
84
+ `features.*` the agents will need. Resolve `refModulesBase` = the directory holding
85
+ `/new`'s reference modules (`.claude/skills/new/references` — the workflow's agents
86
+ Read them for the review SEMANTICS, so `new2` stays a thin orchestration host and
87
+ the A/B test isolates the host, not the protocol).
88
+
89
+ ### Step 4 — Delegate the whole batch to the workflow
90
+
91
+ Call the `Workflow` tool:
92
+
93
+ ```
94
+ Workflow({ name: 'new2', args: {
95
+ cardIds, // expanded list (ranges/-full already resolved here)
96
+ cardPaths, // resolved backlog YAML paths
97
+ mainRepo, // $MAIN absolute path
98
+ trunk, // resolved $TRUNK
99
+ metricsDir, // $METRICS
100
+ refModulesBase, // .claude/skills/new/references (semantic SSOT)
101
+ config, // the parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
102
+ flags: { stats, effort, full }
103
+ }})
104
+ ```
105
+
106
+ The workflow runs the entire batch in the background. **Do not poll it** — it
107
+ returns when the batch is done. It returns:
108
+
109
+ ```
110
+ { report, perCardResults, gateLedger, residualFollowups, telemetry }
111
+ ```
112
+
113
+ `report` is a ready-to-show markdown batch summary (files changed per card, review
114
+ findings, QA, merge hash, any auto-created follow-up cards). `residualFollowups`
115
+ lists every follow-up card `new2-resolve` materialised for an unresolved
116
+ gate/scope-expansion (nothing was silently dropped). `telemetry` is the Phase-8
117
+ record (same schema as `/new`, with `variant: "new2"`).
118
+
119
+ ### Step 5 — Present + record
120
+
121
+ - Print `report` to the user verbatim (it is already the single batch summary).
122
+ - If `residualFollowups` is non-empty, surface them prominently ("queste AC/finding
123
+ non risolti autonomamente sono stati tracciati come follow-up card: …") — this is
124
+ the post-run review that replaced the ~25 mid-run questions.
125
+ - Append `telemetry` (with `variant: "new2"`) to `${metricsDir}/skill-runs.jsonl`
126
+ and, when `flags.stats`, run the session token-telemetry script the same way
127
+ `/new` Phase 8 does (`-stats`), so the A/B comparison has real `usage` data for
128
+ both variants. Do NOT re-summarise the cards yourself — the workflow already did.
@@ -0,0 +1,180 @@
1
+ export const meta = {
2
+ name: 'new2-resolve',
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), a legitimate scope-EXPANDING finding (scope-expansion), or a cross-cutting edge (agent-crash | baseline-fail). Runs an aggressive fix→verify loop (targeted coder → judged multi-attempt → adversarial re-verify); its TERMINAL is auto-materialising a tracked follow-up card so nothing is ever silently dropped. Domain-Override aware: doc fixes go to doc-reviewer, security/migration to coder and NEVER inline-bypassed. Agents Read /new's reference modules for semantics (args.refModulesBase). Uses agent()/parallel() only — no nested workflows.",
5
+ phases: [
6
+ { title: 'Diagnose', detail: 'classify the problem + decide scope-expansion boundary' },
7
+ { title: 'Repair', detail: 'targeted fix, then judged multi-attempt if needed' },
8
+ { title: 'Verify', detail: 'adversarial re-verify against the originating gate' },
9
+ ],
10
+ }
11
+
12
+ // ───────────────────────────────────────────────────────────────────────────
13
+ // args contract (from new2.js resolve()):
14
+ // kind 'ac-unmet'|'blocker'|'qa-fail'|'e2e-blocked'|'merge-blocker'
15
+ // |'scope-expansion'|'agent-crash'|'baseline-fail'
16
+ // cardId string card (or finding id) the problem belongs to
17
+ // evidence string verbatim AC / failing gate / finding evidence
18
+ // worktreePath string where the fix happens
19
+ // mayEditPaths string[] the card's file-ownership MAY-EDIT set
20
+ // scopeFiles string[] files in scope for re-verify
21
+ // domain 'doc'|'security'|'migration'|'code'|'perf'|'test'
22
+ // refModulesBase string /new reference modules dir (semantics)
23
+ // config object parsed baldart.config.yml
24
+ //
25
+ // return (consumed by new2.js):
26
+ // { status: 'resolved'|'followup'|'fatal', followupCard?, reason? }
27
+ // ───────────────────────────────────────────────────────────────────────────
28
+
29
+ const a = args || {}
30
+ const kind = a.kind || 'blocker'
31
+ const card = a.cardId || 'CARD'
32
+ const evidence = a.evidence || ''
33
+ const worktree = a.worktreePath || '(cwd)'
34
+ const mayEdit = Array.isArray(a.mayEditPaths) ? a.mayEditPaths : []
35
+ const scopeFiles = Array.isArray(a.scopeFiles) ? a.scopeFiles : []
36
+ const domain = a.domain || 'code'
37
+ const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
38
+ const cfg = a.config || {}
39
+ const paths = cfg.paths || {}
40
+ const backlogDir = paths.backlog_dir || 'backlog'
41
+ const protectedDomain = domain === 'security' || domain === 'migration'
42
+
43
+ const FIX_SCHEMA = {
44
+ type: 'object', required: ['applied', 'verified'], additionalProperties: true,
45
+ properties: {
46
+ applied: { type: 'boolean', description: 'true if an edit was made' },
47
+ verified: { type: 'boolean', description: 'true if the originating gate now passes (re-run it)' },
48
+ crashed: { type: 'boolean', description: 'agent could not act (for agent-crash routing)' },
49
+ note: { type: 'string' },
50
+ },
51
+ }
52
+ const JUDGE_SCHEMA = {
53
+ type: 'object', required: ['best'], additionalProperties: false,
54
+ properties: { best: { type: 'number', description: '1-based index of the best attempt, or 0 if none works' }, rationale: { type: 'string' } },
55
+ }
56
+ const FOLLOWUP_SCHEMA = {
57
+ type: 'object', required: ['created', 'followupCard'], additionalProperties: true,
58
+ properties: { created: { type: 'boolean' }, followupCard: { type: 'string' }, note: { type: 'string' } },
59
+ }
60
+
61
+ const brief = [
62
+ `Worktree: ${worktree} (cd into it; run all gates/git yourself).`,
63
+ `Card: ${card}`,
64
+ `Problem kind: ${kind}`,
65
+ `Evidence: ${evidence}`,
66
+ `MAY-EDIT (file-ownership — do NOT touch anything else): ${mayEdit.join(', ') || '(card scope)'}`,
67
+ `Scope files for re-verify: ${scopeFiles.join(', ') || '(diff)'}`,
68
+ `Domain: ${domain}${protectedDomain ? ' (PROTECTED — security/migration: never inline-bypass; the right specialist MUST apply)' : ''}`,
69
+ `Reference modules (Read for exact semantics): ${REF}/`,
70
+ `UI re-verify obligation: if your fix touches a UI file (app dir / components / *.css / *.scss), you MUST re-run the Phase 2.6 E2E review (per ${REF}/review-cycle.md) before declaring verified — a code-review/Codex fix can reintroduce visual/functional drift (mirrors codex-gate.md step 6).`,
71
+ ].join('\n')
72
+
73
+ // The agent that OWNS a fix in this domain (Domain-Override): doc → doc-reviewer.
74
+ const fixerAgent = domain === 'doc' ? 'doc-reviewer' : 'coder'
75
+
76
+ // ───────────────────────────────────────────────────────────────────────────
77
+ // Phase Diagnose — branch by kind.
78
+ // ───────────────────────────────────────────────────────────────────────────
79
+ phase('Diagnose')
80
+
81
+ // ---- baseline-fail (E2): try to make the trunk build; fatal if irrecoverable ---
82
+ if (kind === 'baseline-fail') {
83
+ const r = await agent(
84
+ `The batch worktree's BASELINE build fails (trunk may not build — pre-existing). Try to make it build per ${REF}/setup.md step 6a.\n\n${brief}\n\nFix the build errors if they are tractable (missing dep, stale lock, trivial type error). Re-run tsc+lint+build. Return verified:true only if the baseline now passes. If the breakage is non-trivial/pre-existing and not safely fixable, return verified:false (the caller will treat the batch as fatal).`,
85
+ { label: `resolve:baseline:${card}`, phase: 'Diagnose', agentType: 'coder', schema: FIX_SCHEMA }
86
+ )
87
+ if (r && r.verified) { log('baseline recovered.'); return { status: 'resolved' } }
88
+ log('baseline irrecoverable — batch fatal.')
89
+ return { status: 'fatal', reason: 'baseline build irrecoverable' }
90
+ }
91
+
92
+ // ---- scope-expansion (Asse 2): deterministic boundary, no arbitrary threshold ---
93
+ if (kind === 'scope-expansion') {
94
+ // Integrate iff: within MAY-EDIT ownership AND not security/migration AND not a NEW AC.
95
+ const decide = await agent(
96
+ `A review finding EXPANDS scope beyond card ${card}'s acceptance criteria (not a fail — extra legitimate work). Decide per the deterministic boundary (no line-count threshold):\n` +
97
+ `INTEGRATE NOW iff ALL hold: (1) the fix stays inside MAY-EDIT ownership; (2) domain is NOT security/migration; (3) it does NOT introduce a new user-facing acceptance criterion. Otherwise → MATERIALISE A FOLLOW-UP.\n\n${brief}\n\n` +
98
+ `If INTEGRATE: apply the fix via the domain owner discipline (you are ${fixerAgent}), re-run lint+tsc, return applied:true verified:true. If FOLLOW-UP: return applied:false verified:false note:'needs-followup: <why boundary failed>'. Do NOT prompt.`,
99
+ { label: `resolve:scope:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
100
+ )
101
+ if (decide && decide.verified) { log('scope-expansion integrated within ownership.'); return { status: 'resolved' } }
102
+ return await materialiseFollowup('scope-expansion', (decide && decide.note) || 'outside ownership / new AC / protected domain')
103
+ }
104
+
105
+ // ---- agent-crash (E1): retry → route to the right specialist; protected = never bypass --
106
+ if (kind === 'agent-crash') {
107
+ const r = await agent(
108
+ `A subagent crashed while handling this work. Re-attempt it with the CORRECT specialist (${fixerAgent}) per ${REF}/commit.md § Sub-agent failure protocol.\n\n${brief}\n\nRetry the work once. ${protectedDomain ? 'This is a PROTECTED domain (security/migration): if you cannot complete it, return verified:false — NEVER inline-bypass.' : 'If transient, complete it.'} Re-verify the originating gate. Return verified:true only if done + verified.`,
109
+ { label: `resolve:crash:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
110
+ )
111
+ if (r && r.verified) return { status: 'resolved' }
112
+ return await materialiseFollowup('agent-crash', 'subagent crash unrecovered' + (protectedDomain ? ' (protected domain — not bypassed)' : ''))
113
+ }
114
+
115
+ // ───────────────────────────────────────────────────────────────────────────
116
+ // Asse 1 (ac-unmet | blocker | qa-fail | e2e-blocked | merge-blocker)
117
+ // Tier 1: targeted fix → re-verify. Tier 2: judged multi-attempt. Terminal: follow-up.
118
+ // ───────────────────────────────────────────────────────────────────────────
119
+ const gateHint = {
120
+ 'ac-unmet': `Implement the unmet acceptance criterion EXACTLY (no scope expansion), per ${REF}/completeness.md Phase 2.5b. Re-verify the AC against the code.`,
121
+ blocker: `Fix the blocking finding per ${REF}/codex-gate.md / ${REF}/completeness.md. Re-run the failing gate.`,
122
+ 'qa-fail': `Fix the failing QA gates per ${REF}/review-cycle.md Phase 3.5 — fix the code, not the tests (unless a test is wrong). Re-run qa-sentinel.`,
123
+ 'e2e-blocked': `Fix the implementation so the E2E review passes per ${REF}/review-cycle.md Phase 2.6 (never override known-broken UI). Re-run /e2e-review.`,
124
+ 'merge-blocker': `Fix the verified BLOCKER/HIGH from the final review per ${REF}/final-review.md F.5. Re-verify.`,
125
+ }[kind] || `Fix the blocker and re-verify.`
126
+
127
+ phase('Repair')
128
+ let attempt = await agent(
129
+ `Tier-1 targeted repair for card ${card} (${kind}).\n\n${brief}\n\n${gateHint}\n\nApply the minimal correct fix within MAY-EDIT only. Re-run the originating gate yourself and report verified honestly (do NOT claim verified without re-running it).`,
130
+ { label: `resolve:${kind}:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
131
+ )
132
+ if (attempt && attempt.verified) { log(`${kind} resolved (tier 1).`); return { status: 'resolved' } }
133
+
134
+ // Tier 2 — judged multi-attempt (parallel divergent fixes → judge → adversarial verify).
135
+ // Budget guard: skip the fan-out when the run is near its token target.
136
+ const canFanOut = !budget.total || budget.remaining() > 60000
137
+ if (canFanOut && !protectedDomain) {
138
+ phase('Repair')
139
+ const angles = [
140
+ 'Fix at the root cause (change the underlying logic/contract).',
141
+ 'Fix defensively at the call site (guard/validate without changing the contract).',
142
+ 'Fix by reusing an existing utility/pattern instead of new code.',
143
+ ]
144
+ const attempts = (await parallel(angles.map((angle, i) => () =>
145
+ agent(
146
+ `Tier-2 repair attempt #${i + 1} for card ${card} (${kind}), angle: ${angle}\n\n${brief}\n\n${gateHint}\n\nApply your fix within MAY-EDIT, re-run the gate, and report whether it now passes. Work in isolation; the best attempt will be selected.`,
147
+ { label: `resolve:${kind}:${card}#${i + 1}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
148
+ ).then((r) => ({ i: i + 1, r })).catch(() => null)
149
+ ))).filter(Boolean)
150
+
151
+ const verified = attempts.filter((x) => x.r && x.r.verified)
152
+ if (verified.length) {
153
+ phase('Verify')
154
+ // Adversarial pick: judge which verified attempt is the soundest (refute the rest).
155
+ const judge = await agent(
156
+ `Adversarially judge these repair attempts for card ${card} (${kind}). Each claims the gate now passes. Pick the SOUNDEST (best minimal, least regression-prone) — try to refute each with path:line reasoning. Return its 1-based index, or 0 if none truly holds.\n\nEvidence: ${evidence}\nAttempts that claim verified: ${verified.map((v) => `#${v.i}: ${v.r.note || ''}`).join(' | ')}`,
157
+ { label: `resolve:judge:${card}`, phase: 'Verify', agentType: 'code-reviewer', schema: JUDGE_SCHEMA }
158
+ )
159
+ if (judge && judge.best > 0) { log(`${kind} resolved (tier 2, attempt #${judge.best}).`); return { status: 'resolved' } }
160
+ }
161
+ } else if (protectedDomain) {
162
+ log('protected domain — single specialist attempt only (no parallel fan-out).')
163
+ } else {
164
+ log('budget near target — skipping tier-2 fan-out.')
165
+ }
166
+
167
+ // Terminal — nothing resolved autonomously: materialise a tracked follow-up card.
168
+ return await materialiseFollowup(kind, attempt && attempt.note ? attempt.note : 'unresolved after repair tiers')
169
+
170
+ // ───────────────────────────────────────────────────────────────────────────
171
+ async function materialiseFollowup(k, reason) {
172
+ const r = await agent(
173
+ `Materialise a follow-up backlog card so this residual is TRACKED, not dropped (per ${REF}/completeness.md Phase 2.5b option 3 — the sanctioned, NON-silent deferral).\n\n${brief}\nKind: ${k}\nReason it could not be auto-resolved: ${reason}\n\n` +
174
+ `Create ${backlogDir}/${card}-followup-<gate>.yml with status: TODO and the MINIMUM valid fields (non-empty requirements ≥1 derived from the residual, acceptance_criteria ≥1 = the verbatim residual, files_likely_touched ≥1 from this card's ownership). It MUST pass the /new pre-flight field check. Return the created card id.`,
175
+ { label: `resolve:followup:${card}`, phase: 'Verify', agentType: 'general-purpose', schema: FOLLOWUP_SCHEMA }
176
+ )
177
+ const followupCard = (r && r.followupCard) || `${card}-followup`
178
+ log(`${k} → follow-up card ${followupCard} (nothing dropped).`)
179
+ return { status: 'followup', followupCard, reason }
180
+ }
@@ -0,0 +1,480 @@
1
+ export const meta = {
2
+ name: 'new2',
3
+ description:
4
+ "EXPERIMENTAL workflow host for /new (A/B testing). Runs an ENTIRE backlog-card batch autonomously in the background runtime — pre-flight, per-card implement+review pipeline (sequential or team-mode parallel coders), cross-batch final review, and auto-merge — so subagent output never enters the main orchestrator context. Zero AskUserQuestion: each /new gate is a deterministic policy; blocking gates and scope-expanding findings are routed to the new2-resolve self-healing workflow, whose terminal is an auto-materialised follow-up card (nothing silently dropped). Agents Read /new's reference modules for review semantics (args.refModulesBase) so this script encodes only orchestration shape + gate policy. Claude-only.",
5
+ phases: [
6
+ { title: 'Pre-flight', detail: 'deterministic workspace hygiene + worktree + cross-card grounding (Phase 0)' },
7
+ { title: 'Implement', detail: 'per-card implement+review pipeline; team-mode runs coders in parallel per wave' },
8
+ { title: 'Final', detail: 'cross-batch final review (delegates to new-final-review)' },
9
+ { title: 'Merge', detail: 'auto-merge to trunk via git.merge_strategy + cleanup' },
10
+ { title: 'Production', detail: 'post-merge production-readiness checklist (Phase 7, non-blocking)' },
11
+ ],
12
+ }
13
+
14
+ // ───────────────────────────────────────────────────────────────────────────
15
+ // args contract — supplied by the new2 skill (it owns config-read + invocation):
16
+ // cardIds string[] expanded card ids (ranges/-full resolved by skill)
17
+ // cardPaths string[] resolved backlog YAML paths (agents Read them)
18
+ // mainRepo string absolute path of the main repo (not a worktree)
19
+ // trunk string resolved integration trunk (git.trunk_branch)
20
+ // metricsDir string paths.metrics (telemetry dir)
21
+ // refModulesBase string dir holding /new reference modules (semantic SSOT)
22
+ // config object parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
23
+ // flags { stats, effort, full }
24
+ //
25
+ // return (consumed by the skill at Step 5):
26
+ // { report, perCardResults, gateLedger, residualFollowups, telemetry }
27
+ // ───────────────────────────────────────────────────────────────────────────
28
+
29
+ const a = args || {}
30
+ const cfg = a.config || {}
31
+ const cardIds = Array.isArray(a.cardIds) ? a.cardIds : []
32
+ const cardPaths = Array.isArray(a.cardPaths) ? a.cardPaths : []
33
+ const MAIN = a.mainRepo || '(cwd)'
34
+ const TRUNK = a.trunk || 'main'
35
+ const METRICS = a.metricsDir || 'docs/metrics'
36
+ const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
37
+ const FLAGS = a.flags || {}
38
+ const features = cfg.features || {}
39
+ const paths = cfg.paths || {}
40
+ const gitCfg = cfg.git || {}
41
+ const highRisk = paths.high_risk_modules || []
42
+ const mergeStrategy = gitCfg.merge_strategy || 'pr'
43
+
44
+ // Mutable batch state — the workflow's variables ARE the tracker (kept out of the
45
+ // main loop). Agents also write the on-disk /tmp/batch-tracker-<id>.md for recovery.
46
+ const firstCard = cardIds[0] || 'BATCH'
47
+ const gateLedger = [] // { card, gate, decision, detail }
48
+ const residualFollowups = []// { card, kind, followupCard, reason }
49
+ const perCardResults = [] // structured per-card outcome
50
+ let batchFatal = false // set when a resolve() returns 'fatal' (unrecoverable)
51
+ let prodReadiness = null // Phase 7 result (post-merge, non-blocking)
52
+
53
+ function ledger(card, gate, decision, detail) {
54
+ gateLedger.push({ card, gate, decision, detail: detail || '' })
55
+ log(`gate[${card}] ${gate} → ${decision}${detail ? ' — ' + detail : ''}`)
56
+ }
57
+
58
+ if (!cardIds.length) {
59
+ log('new2: no card IDs supplied — nothing to do.')
60
+ return emptyReturn('no cards supplied')
61
+ }
62
+
63
+ // ───────────────────────────────────────────────────────────────────────────
64
+ // Schemas
65
+ // ───────────────────────────────────────────────────────────────────────────
66
+ const PREFLIGHT_SCHEMA = {
67
+ type: 'object',
68
+ required: ['ok', 'worktreePath', 'branch', 'baseline', 'executionMode', 'cards'],
69
+ additionalProperties: false,
70
+ properties: {
71
+ ok: { type: 'boolean', description: 'false only when the workspace cannot be made workable (e.g. baseline irrecoverable)' },
72
+ worktreePath: { type: 'string' },
73
+ branch: { type: 'string' },
74
+ port: { type: ['number', 'string'], description: 'dev port or "-"' },
75
+ baseline: { enum: ['pass', 'fail'] },
76
+ baselineLog: { type: 'string' },
77
+ executionMode: { enum: ['sequential', 'team'] },
78
+ groups: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'team-mode waves: [{ level, cards:[ids] }]' },
79
+ cards: { type: 'array', items: { type: 'string' }, description: 'card ids cleared to run (invalid/blocked-dep excluded)' },
80
+ excluded: { type: 'array', items: { type: 'object', additionalProperties: true }, description: '[{ card, reason }] for G4/G5 exclusions' },
81
+ ownershipMapPath: { type: 'string', description: '/tmp path to the file-ownership map' },
82
+ crossCard: { type: 'string', description: 'distilled cross-card findings or PASS/SKIPPED/UNAVAILABLE' },
83
+ workspaceNote: { type: 'string', description: 'G1/G2 distilled (stash label, ff-pull, divergence left + reported)' },
84
+ archBaselinePaths: { type: 'array', items: { type: 'string' }, description: 'per-card /tmp/arch-baseline-<id>.md (for final-review dedup)' },
85
+ },
86
+ }
87
+
88
+ const MERGE_SCHEMA = {
89
+ type: 'object', required: ['merged'], additionalProperties: false,
90
+ properties: {
91
+ merged: { type: 'boolean' },
92
+ mergeCommit: { type: 'string' },
93
+ mergeTs: { type: 'string' },
94
+ reconciliation: { type: 'string', description: 'Phase 6b/6c distilled (divergence left+reported, stash restore, sync markers)' },
95
+ note: { type: 'string' },
96
+ },
97
+ }
98
+
99
+ // Shared context fragment every per-card agent gets.
100
+ const projectBrief = [
101
+ `Main repo: ${MAIN}`,
102
+ `Trunk: ${TRUNK}`,
103
+ `Reference modules (Read these for the EXACT /new semantics — this workflow only sequences them): ${REF}/`,
104
+ `Config facts: stack=${JSON.stringify(cfg.stack || {})}; features=${JSON.stringify(features)}; high_risk_modules=${JSON.stringify(highRisk)}; paths.backlog_dir=${paths.backlog_dir || '?'}; paths.references_dir=${paths.references_dir || '?'}.`,
105
+ FLAGS.effort ? `Reasoning effort for this run: ${FLAGS.effort}.` : '',
106
+ ].filter(Boolean).join('\n')
107
+
108
+ // ───────────────────────────────────────────────────────────────────────────
109
+ // Phase Pre-flight (Phase 0 + Pre-flight) — ONE ops agent runs all the git-heavy,
110
+ // deterministic workspace work (the script itself has no shell). Gate policies:
111
+ // G1 dirty-tree → auto-stash (restore at merge); restore-conflict → leave+report
112
+ // G2 divergence → behind: ff-pull; ahead/diverged: proceed on isolated worktree + report
113
+ // G3 cross-card cap unavailable → proceed + log CAPABILITY_UNAVAILABLE
114
+ // G4 card-field invalid → exclude that card + continue
115
+ // G5 depends-on (dep outside batch, not DONE) → exclude card + its dependents
116
+ // E2 baseline build fail → retry once inside the agent; surface baseline:fail
117
+ // ───────────────────────────────────────────────────────────────────────────
118
+ phase('Pre-flight')
119
+ const preflight = await agent(
120
+ `You are the deterministic PRE-FLIGHT for an autonomous /new batch (variant new2). Follow ${REF}/setup.md (Phase 0 + Pre-flight) and ${REF}/implement.md (Phase 1 depends-on gate) for the SEMANTICS, but replace EVERY AskUserQuestion with the deterministic policy below. You run all git/bash yourself (the workflow cannot).\n\n` +
121
+ `${projectBrief}\n\nCards in batch (Read each YAML):\n${cardPaths.join('\n')}\nCard IDs: ${cardIds.join(' ')}\n\n` +
122
+ `Create/maintain the recovery tracker at /tmp/batch-tracker-${firstCard}.md (per setup.md § Context Tracking).\n\n` +
123
+ `DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
124
+ `• G1 dirty-tree (main repo ${MAIN}): partition framework-managed noise exactly as setup.md step 3 ($METRICS=${METRICS}, .baldart/generated|state.json|skill-conflicts.json — NOT overlays/). If genuine user work remains → auto-stash 'baldart-new2-${firstCard}-<ts>' (main checkout, safe) and record the MESSAGE label for restore at merge. Never commit/abort/prompt.\n` +
125
+ `• G2 divergence vs origin/${TRUNK}: behind-only AND HEAD==${TRUNK} → ff-pull; ahead-genuine or diverged → DO NOT push/rebase/reset; proceed (the worktree is isolated) and put a one-line note in workspaceNote. Framework-management commits are auto-ignored (setup.md step 4 reclassification).\n` +
126
+ `• G3 cross-card grounding (setup.md 3d): run the Codex cross-card check with the provenance skip; on CODEX_NOT_FOUND fall back to code-reviewer; if BOTH unavailable → proceed and set crossCard='CAPABILITY_UNAVAILABLE'. Read the audit file ONLY via the [codex]-stripping filter; return DISTILLED findings. CRITICAL — if conflicts are FOUND you MUST APPLY them, not just report: FILE_CONFLICT / ORDER_RISK → force the conflicting cards SEQUENTIAL (encode this in executionMode/groups AND the ownership map); STATE_MUTATION → fold a warning into the affected cards via the ownership map. The returned executionMode/groups MUST already reflect these constraints.\n` +
127
+ `• G4 card-field validation (setup.md 1b/1c): a card missing requirements/acceptance_criteria/files_likely_touched → EXCLUDE it (add to excluded[] with reason); continue with valid cards. Never HALT the batch for one bad card.\n` +
128
+ `• G5 depends-on (implement.md Phase 1 step 2): if a card's depends_on names a card that is NOT DONE and is NOT in this batch → exclude that card AND, TRANSITIVELY, every in-batch card that depends on it (excluded[], reason 'depends_on <dep> not DONE'). GUARANTEE: the returned cards[] must contain NO card that transitively depends on an excluded card. If the dep IS in this batch, keep it and order it first.\n` +
129
+ `• Worktree (setup.md step 4): create ONE code worktree off ${TRUNK} (slug from git_strategy.branch or the parent/lead card title), install deps, assign a port, run the baseline (tsc+lint+build). Use the git-authoritative idempotency pre-check (step 4a2). E2: if baseline FAILS, fix-attempt once (the trunk itself may not build); if still failing return baseline:'fail' with baselineLog (the caller treats it as batch-fatal).\n` +
130
+ `• Complexity (setup.md 3c): decide executionMode sequential|team and, for team, the groups [{level,cards}]. Build the file-ownership map; write it to /tmp and return ownershipMapPath.\n` +
131
+ `• Persist per-card architecture baselines you gather to /tmp/arch-baseline-<CARD>.md and return archBaselinePaths (final review reuses them).\n\n` +
132
+ `Return the structured PREFLIGHT object. ok:false ONLY if the workspace is unworkable (baseline irrecoverable / worktree uncreatable).`,
133
+ { label: 'preflight', phase: 'Pre-flight', agentType: 'general-purpose', schema: PREFLIGHT_SCHEMA }
134
+ )
135
+
136
+ if (!preflight || preflight.ok === false || preflight.baseline === 'fail') {
137
+ // E2 terminal — the only batch-fatal case: no workable tree.
138
+ ledger(firstCard, 'E2-baseline', 'BATCH-FATAL', (preflight && preflight.baselineLog) || 'workspace unworkable')
139
+ return finalReturn({ fatal: true, reason: 'baseline build irrecoverable — see baselineLog' })
140
+ }
141
+
142
+ for (const ex of preflight.excluded || []) ledger(ex.card, 'preflight-exclude', 'EXCLUDED', ex.reason)
143
+ if (preflight.workspaceNote) ledger(firstCard, 'G1/G2-workspace', 'AUTO', preflight.workspaceNote)
144
+ if (preflight.crossCard) ledger(firstCard, 'G3-cross-card', 'INFO', preflight.crossCard)
145
+
146
+ const runnableCards = preflight.cards || []
147
+ const sharedCtx = {
148
+ worktreePath: preflight.worktreePath,
149
+ branch: preflight.branch,
150
+ ownershipMapPath: preflight.ownershipMapPath,
151
+ archBaselinePaths: preflight.archBaselinePaths || [],
152
+ }
153
+
154
+ // ───────────────────────────────────────────────────────────────────────────
155
+ // new2-resolve bridge — every blocking gate and every scope-expanding finding
156
+ // goes here (Asse 1/2/3). Returns 'resolved' | 'followup' | 'fatal'.
157
+ // ───────────────────────────────────────────────────────────────────────────
158
+ async function resolve(kind, card, evidence, extra) {
159
+ const res = await workflow('new2-resolve', {
160
+ kind,
161
+ cardId: card,
162
+ evidence,
163
+ worktreePath: sharedCtx.worktreePath,
164
+ mayEditPaths: (extra && extra.mayEditPaths) || [],
165
+ scopeFiles: (extra && extra.scopeFiles) || [],
166
+ domain: (extra && extra.domain) || 'code',
167
+ refModulesBase: REF,
168
+ config: cfg,
169
+ }).catch((e) => ({ status: 'followup', reason: 'resolve workflow error: ' + (e && e.message) }))
170
+ const status = (res && res.status) || 'followup'
171
+ if (status === 'fatal') { batchFatal = true; ledger(card, 'resolve:' + kind, 'FATAL', (res && res.reason) || ''); return status }
172
+ if (status === 'followup' && res && res.followupCard) {
173
+ residualFollowups.push({ card, kind, followupCard: res.followupCard, reason: res.reason || '' })
174
+ }
175
+ ledger(card, 'resolve:' + kind, status, (res && (res.followupCard || res.reason)) || '')
176
+ return status
177
+ }
178
+
179
+ // A crashed runCard (E1) routes through new2-resolve(agent-crash) for a specialist
180
+ // retry, then ALWAYS lands as a tracked followup — never a silent drop.
181
+ async function crashFollowup(id, e) {
182
+ const msg = String(e && e.message)
183
+ const s = await resolve('agent-crash', id, 'runCard crashed: ' + msg, { mayEditPaths: [], scopeFiles: [] })
184
+ if (s !== 'followup') residualFollowups.push({ card: id, kind: 'agent-crash', followupCard: '(specialist retry attempted — manual check)', reason: msg })
185
+ return { card: id, status: 'followup', gates: [{ gate: 'runCard', decision: 'ERROR', detail: msg }], commit: '-', telemetry: {} }
186
+ }
187
+
188
+ // ───────────────────────────────────────────────────────────────────────────
189
+ // Per-card pipeline — sequences /new Phases 1→4 via agents that Read the modules.
190
+ // Each phase agent returns a structured verdict; this function applies the
191
+ // deterministic gate policy and routes blocks to new2-resolve.
192
+ // ───────────────────────────────────────────────────────────────────────────
193
+ async function runCard(cardId, cardPath, lessons) {
194
+ const gates = []
195
+ const tele = {}
196
+ function g(name, decision, detail) { gates.push({ gate: name, decision, detail: detail || '' }); ledger(cardId, name, decision, detail) }
197
+
198
+ const cardBrief = `${projectBrief}\n\nCard: ${cardId}\nCard YAML: ${cardPath}\nWorktree: ${sharedCtx.worktreePath} (cd into it)\nFile-ownership map: ${sharedCtx.ownershipMapPath}\nBatch lessons so far: ${lessons.length ? lessons.join(' | ') : '(none)'}\nArch baseline (write to /tmp/arch-baseline-${cardId}.md): reuse if present.`
199
+
200
+ // --- Phase 1+2: claim, ground, implement (coder), completeness; returns the
201
+ // completion verdict + the AC ledger + the file diff. Self-heals build gates
202
+ // (G26) up to the module's retry cap before reporting unresolved. ---
203
+ const impl = await agent(
204
+ `Implement card ${cardId} per ${REF}/implement.md (Phase 1 claim+architect+plan-auditor, Phase 2 coder dispatch via owner_agent — G25: unknown owner_agent → default to coder, never HALT) and ${REF}/completeness.md (Phase 2.5 completeness + 2.5b AC-closure ledger). Run all gates/bash yourself.\n\n${cardBrief}\n\n` +
205
+ `POLICIES: G25 unknown owner_agent → coder + log. G26 Phase-2 lint/tsc/test/build failing after the module's retry cap (or stuck-loop) → return buildBlocked:true with the failing gate. Do NOT prompt. Build the AC Closure Ledger (one row per acceptance_criteria: implemented | unmet) — DO NOT silently defer; just report unmet rows. Persist the architecture baseline to /tmp/arch-baseline-${cardId}.md and the diff to /tmp/diff-${cardId}.txt.\n\n` +
206
+ `Return: { epic:boolean, buildBlocked:boolean, blockedGate:string, unmetACs:[{n,text}], scopeFiles:[...], mayEditPaths:[...], fileDiffViolation:boolean (already auto-reverted per 11b), note }`,
207
+ { label: `implement:${cardId}`, phase: 'Implement', agentType: 'general-purpose',
208
+ schema: { type: 'object', required: ['epic', 'buildBlocked', 'unmetACs', 'scopeFiles'], additionalProperties: true,
209
+ properties: { epic: { type: 'boolean' }, buildBlocked: { type: 'boolean' }, blockedGate: { type: 'string' }, unmetACs: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeFiles: { type: 'array', items: { type: 'string' } }, mayEditPaths: { type: 'array', items: { type: 'string' } }, fileDiffViolation: { type: 'boolean' }, note: { type: 'string' } } } }
210
+ )
211
+
212
+ if (impl && impl.epic) { g('router', 'EPIC-SKIPPED', 'epic card — not implemented'); return { card: cardId, status: 'epic-skipped', gates, commit: '-', telemetry: tele } }
213
+
214
+ const mayEdit = (impl && impl.mayEditPaths) || []
215
+ const scopeFiles = (impl && impl.scopeFiles) || []
216
+ if (impl && impl.fileDiffViolation) g('E4-file-diff', 'AUTO-REVERTED', 'coder touched files outside ownership — reverted per implement.md 11b')
217
+
218
+ // G26 — build gates not converging → self-heal.
219
+ if (impl && impl.buildBlocked) {
220
+ const s = await resolve('blocker', cardId, `Phase-2 gate failing: ${impl.blockedGate}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'code' })
221
+ g('G26-build', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP', impl.blockedGate)
222
+ if (s !== 'resolved') return { card: cardId, status: 'followup', gates, commit: '-', scopeFiles, telemetry: tele }
223
+ }
224
+
225
+ // G6+G7 — AC-Closure: every unmet AC must be implemented (never silently deferred).
226
+ for (const ac of (impl && impl.unmetACs) || []) {
227
+ const s = await resolve('ac-unmet', cardId, `AC-${ac.n}: ${ac.text}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'code' })
228
+ g('G7-ac-closure', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP', `AC-${ac.n}`)
229
+ }
230
+
231
+ // --- Phases 2.55 / 2.6 / 3 / 3.5 / 3.7 — review fan-out. One agent runs the
232
+ // review pipeline per the modules and returns each gate's verdict + any
233
+ // scope-expanding findings. Auto-revert gates (G9/G12) are handled inside. ---
234
+ const review = await agent(
235
+ `Run the per-card review pipeline for ${cardId} per ${REF}/review-cycle.md (Phase 2.55 Simplify, 2.6 E2E, 3 doc, 3.5 QA) and ${REF}/codex-gate.md (Phase 3.7 Codex). Run all agents/gates yourself.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\n\n` +
236
+ `POLICIES (NO prompts): G9 simplify-regression after retries → AUTO-REVERT the simplify fixes, proceed. G12 doc-phase regression after retries → AUTO-REVERT the doc edits that broke the build. G11 E2E 'error' (tooling) → proceed with e2eStatus:'ERROR' (flag only). For BLOCKING failures that survive the module's retry cap, report them so the caller routes them to new2-resolve: G10 E2E 'blocked', G13 QA fail, G14 Codex BLOCKER/HIGH, G8 alias-mutation un-guarded, G15 Codex crash (security domain: never inline-bypass). Also report scopeExpansion findings (legitimate findings BEYOND this card's AC: from Simplify/Codex/doc-drift→bug).\n\n` +
237
+ `Return: { simplify:'clean|reverted|applied', e2eStatus:'passed|skipped|blocked|error|overridden', qa:'pass|fail|skip', codex:'pass|blocker|crash', blocks:[{gate,domain,evidence}], scopeExpansion:[{evidence,domain,withinOwnership:boolean,newAC:boolean}], docGaps:{found,fixed}, qaFirstAttempt:'pass|fail|n/a' }`,
238
+ { label: `review:${cardId}`, phase: 'Implement', agentType: 'general-purpose',
239
+ schema: { type: 'object', required: ['blocks', 'scopeExpansion'], additionalProperties: true,
240
+ properties: { simplify: { type: 'string' }, e2eStatus: { type: 'string' }, qa: { type: 'string' }, codex: { type: 'string' }, blocks: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeExpansion: { type: 'array', items: { type: 'object', additionalProperties: true } }, docGaps: { type: 'object', additionalProperties: true }, qaFirstAttempt: { type: 'string' } } } }
241
+ )
242
+
243
+ if (review) {
244
+ if (review.simplify) g('G9-simplify', review.simplify === 'reverted' ? 'AUTO-REVERT' : 'OK', review.simplify)
245
+ if (review.e2eStatus) g('G10/G11-e2e', review.e2eStatus === 'error' ? 'FLAG' : review.e2eStatus.toUpperCase())
246
+ if (review.qaFirstAttempt) tele.qa_first_attempt = review.qaFirstAttempt
247
+ if (review.docGaps) tele.doc_gaps = review.docGaps
248
+ }
249
+
250
+ // Route blocking review failures (G8/G10/G13/G14/G15) to new2-resolve.
251
+ let cardBlocked = false
252
+ for (const b of (review && review.blocks) || []) {
253
+ const kind = b.gate && /e2e/i.test(b.gate) ? 'e2e-blocked' : (b.gate && /qa/i.test(b.gate) ? 'qa-fail' : 'blocker')
254
+ const s = await resolve(kind, cardId, `${b.gate}: ${b.evidence || ''}`, { mayEditPaths: mayEdit, scopeFiles, domain: b.domain || 'code' })
255
+ g(b.gate || 'review-block', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP', b.evidence || '')
256
+ if (s !== 'resolved') cardBlocked = true
257
+ }
258
+
259
+ // Scope-expansion findings (Asse 2) — deterministic boundary inside new2-resolve.
260
+ for (const sx of (review && review.scopeExpansion) || []) {
261
+ const s = await resolve('scope-expansion', cardId, sx.evidence || '', { mayEditPaths: mayEdit, scopeFiles, domain: sx.domain || 'code' })
262
+ g('scope-expansion', s === 'resolved' ? 'INTEGRATED' : 'FOLLOWUP', sx.evidence || '')
263
+ }
264
+
265
+ if (cardBlocked) return { card: cardId, status: 'followup', gates, commit: '-', scopeFiles, archBaselinePath: `/tmp/arch-baseline-${cardId}.md`, telemetry: tele }
266
+
267
+ // --- Phase 4 — commit (G16: auto-clear COMMIT_LOCK + retry; persistent → resolve). ---
268
+ const commitRes = await agent(
269
+ `Commit card ${cardId} in the worktree per ${REF}/commit.md (Phase 4 explicit-staging — NEVER git add -A, NEVER git stash in a worktree — + Phase 5 context clean; mark the card DONE + ssot-registry). Run git yourself.\n\n${cardBrief}\n\n` +
270
+ `POLICY G16: on commit failure auto-clear stale COMMIT_LOCK and retry (module cap). 'nothing to commit' = already committed (record HEAD). If still locked after the cap → return committed:false (caller routes to new2-resolve).\n\n` +
271
+ `Return: { committed:boolean, commit:string, filesChanged:[...] }`,
272
+ { label: `commit:${cardId}`, phase: 'Implement', agentType: 'general-purpose',
273
+ schema: { type: 'object', required: ['committed'], additionalProperties: true, properties: { committed: { type: 'boolean' }, commit: { type: 'string' }, filesChanged: { type: 'array', items: { type: 'string' } } } } }
274
+ )
275
+
276
+ if (!commitRes || !commitRes.committed) {
277
+ const s = await resolve('blocker', cardId, 'commit blocked after retries', { mayEditPaths: mayEdit, scopeFiles, domain: 'code' })
278
+ g('G16-commit', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP')
279
+ if (s !== 'resolved') return { card: cardId, status: 'followup', gates, commit: '-', scopeFiles, archBaselinePath: `/tmp/arch-baseline-${cardId}.md`, telemetry: tele }
280
+ }
281
+
282
+ g('commit', 'COMMITTED', (commitRes && commitRes.commit) || '')
283
+ return {
284
+ card: cardId, status: 'committed',
285
+ commit: (commitRes && commitRes.commit) || '-',
286
+ filesChanged: (commitRes && commitRes.filesChanged) || [],
287
+ scopeFiles, archBaselinePath: `/tmp/arch-baseline-${cardId}.md`,
288
+ gates, telemetry: tele,
289
+ }
290
+ }
291
+
292
+ // ───────────────────────────────────────────────────────────────────────────
293
+ // Phase Implement — driver: sequential or team-mode (parallel coders per wave).
294
+ // ───────────────────────────────────────────────────────────────────────────
295
+ phase('Implement')
296
+ const lessons = []
297
+ const pathById = {}
298
+ cardIds.forEach((id, i) => { pathById[id] = cardPaths[i] || `${paths.backlog_dir || ''}/${id}.yml` })
299
+
300
+ if (preflight.executionMode === 'team' && Array.isArray(preflight.groups) && preflight.groups.length) {
301
+ // Waves run in order (a higher level waits for lower levels); cards within a wave
302
+ // run concurrently — parallel() keeps every coder's bloat inside the runtime.
303
+ const sortedGroups = preflight.groups.slice().sort((x, y) => (x.level || 0) - (y.level || 0))
304
+ for (const grp of sortedGroups) {
305
+ const ids = (grp.cards || []).filter((id) => runnableCards.includes(id))
306
+ if (!ids.length) continue
307
+ log(`Team wave level=${grp.level}: ${ids.length} card(s) in parallel.`)
308
+ const waveResults = await parallel(ids.map((id) => () => runCard(id, pathById[id], lessons).catch((e) => crashFollowup(id, e))))
309
+ for (const r of waveResults.filter(Boolean)) { perCardResults.push(r); if (r.note) lessons.push(`${r.card}: ${r.note}`) }
310
+ }
311
+ } else {
312
+ // Sequential — cards share one worktree and run one at a time.
313
+ for (const id of runnableCards) {
314
+ const r = await runCard(id, pathById[id], lessons).catch((e) => crashFollowup(id, e))
315
+ perCardResults.push(r)
316
+ if (r && r.note) lessons.push(`${r.card}: ${r.note}`)
317
+ }
318
+ }
319
+
320
+ const committed = perCardResults.filter((r) => r.status === 'committed')
321
+
322
+ // ───────────────────────────────────────────────────────────────────────────
323
+ // Phase Final — cross-batch final review (reuse new-final-review). F.5 user gates
324
+ // become deterministic: G17 needs-manual → treat as VERIFIED (the workflow already
325
+ // drops only FALSE_POSITIVE); G18 unresolved BLOCKER/HIGH → new2-resolve, else
326
+ // block the merge.
327
+ // ───────────────────────────────────────────────────────────────────────────
328
+ phase('Final')
329
+ let finalSummary = null
330
+ let mergeBlocked = batchFatal
331
+ if (committed.length && !batchFatal) {
332
+ const reviewScopeFiles = dedupe(committed.flatMap((r) => r.scopeFiles || []))
333
+ const archPaths = committed.map((r) => r.archBaselinePath).filter(Boolean)
334
+ const allArch = archPaths.length === committed.length ? archPaths : null
335
+ const hasApiDataFiles = reviewScopeFiles.some((f) => /api\/|data-model|\.sql$|migrations?\//i.test(f))
336
+
337
+ const final = await workflow('new-final-review', {
338
+ firstCardId: firstCard,
339
+ worktreePath: sharedCtx.worktreePath,
340
+ baseBranch: TRUNK,
341
+ cardPaths: committed.map((r) => pathById[r.card]).filter(Boolean),
342
+ reviewScopeFiles,
343
+ archBaselinePaths: allArch,
344
+ hasApiDataFiles,
345
+ config: cfg,
346
+ }).catch(() => null)
347
+
348
+ if (final) {
349
+ finalSummary = final.summary || null
350
+ ledger(firstCard, 'final-review', 'DONE', `engine=${final.codexEngine}; verified=${final.summary ? final.summary.verified : '?'}`)
351
+ // G18 — apply BLOCKER/HIGH fixes; unresolved → block merge. G17: a workflow
352
+ // cannot ask the user, so NEEDS_MANUAL_CONFIRMATION is treated as VERIFIED
353
+ // (attempt the fix) — never silently ignored (the count-only path was the bug).
354
+ const blockers = (final.findings || []).filter((f) => (f.classification === 'VERIFIED' || f.classification === 'NEEDS_MANUAL_CONFIRMATION') && (f.severity === 'BLOCKER' || f.severity === 'HIGH'))
355
+ for (const f of blockers) {
356
+ const s = await resolve('merge-blocker', f.finding_id || firstCard, `${f.classification} ${f.severity} ${f.title}: ${f.evidence}`, { mayEditPaths: reviewScopeFiles, scopeFiles: reviewScopeFiles, domain: f.domain || 'code' })
357
+ if (s !== 'resolved') mergeBlocked = true
358
+ }
359
+ if ((final.summary && final.summary.failingGates && final.summary.failingGates.length)) {
360
+ const s = await resolve('qa-fail', firstCard, `final gates failing: ${final.summary.failingGates.join(', ')}`, { mayEditPaths: reviewScopeFiles, scopeFiles: reviewScopeFiles, domain: 'code' })
361
+ if (s !== 'resolved') mergeBlocked = true
362
+ }
363
+ } else {
364
+ ledger(firstCard, 'final-review', 'SKIPPED', 'workflow returned null')
365
+ }
366
+ } else {
367
+ ledger(firstCard, 'final-review', 'SKIPPED', batchFatal ? 'batch fatal — review/merge halted' : 'no committed cards')
368
+ }
369
+
370
+ // ───────────────────────────────────────────────────────────────────────────
371
+ // Phase Merge — auto-merge to trunk (G24) + Phase 6b/6c reconciliation (conservative
372
+ // git policies: G19–G23). Skipped when a final BLOCKER/HIGH is unresolved.
373
+ // ───────────────────────────────────────────────────────────────────────────
374
+ phase('Merge')
375
+ let mergeResult = null
376
+ if (!committed.length) {
377
+ ledger(firstCard, 'merge', 'SKIPPED', 'no committed cards')
378
+ } else if (mergeBlocked) {
379
+ ledger(firstCard, 'merge', 'BLOCKED', 'unresolved final BLOCKER/HIGH — worktree left intact for review')
380
+ } else {
381
+ mergeResult = await agent(
382
+ `Auto-merge the batch worktree to ${TRUNK} per ${REF}/merge-cleanup.md (Phase 6 via /mw programmatic with checksAlreadyPassed:true, Phase 6b status reconciliation, Phase 6c workspace hygiene). Run git yourself.\n\n${projectBrief}\nWorktree: ${sharedCtx.worktreePath}\nBranch: ${sharedCtx.branch}\nmerge_strategy: ${mergeStrategy}\nCommitted cards: ${committed.map((r) => r.card).join(' ')}\nPhase-0 stash to restore (if any): see /tmp/batch-tracker-${firstCard}.md ## Workspace Snapshot.\n\n` +
383
+ `DETERMINISTIC POLICIES (NO prompts): G24 → auto-merge via merge_strategy. G19 sync-deferred → if HEAD==${TRUNK} ff-pull, else leave+report. G20 sync-needs-decision → leave+report (never touch foreign files). G21 post-batch dirty (no stash) → partition-ignore framework artifacts; leave the rest + report. G22 divergence → behind: ff-pull; ahead/both: leave+report; NEVER reset --hard / force-push. G23 stash restore conflict → leave the stash intact + report (never drop). Phase 6b: force any non-DONE batch card to DONE.\n\n` +
384
+ `Return: { merged:boolean, mergeCommit, mergeTs, reconciliation, note }`,
385
+ { label: 'merge', phase: 'Merge', agentType: 'general-purpose', schema: MERGE_SCHEMA }
386
+ )
387
+ ledger(firstCard, 'G24-merge', (mergeResult && mergeResult.merged) ? 'MERGED' : 'INCOMPLETE', (mergeResult && (mergeResult.mergeCommit || mergeResult.note)) || '')
388
+ if (mergeResult && mergeResult.reconciliation) ledger(firstCard, 'G19-23-reconcile', 'AUTO', mergeResult.reconciliation)
389
+ }
390
+
391
+ // ───────────────────────────────────────────────────────────────────────────
392
+ // Phase Production (Phase 7) — post-merge readiness checklist (non-blocking, full
393
+ // parity with /new). Auto-executes stack-matched deploys; reports the manual items.
394
+ // ───────────────────────────────────────────────────────────────────────────
395
+ phase('Production')
396
+ if (mergeResult && mergeResult.merged) {
397
+ prodReadiness = await agent(
398
+ `Run the post-merge Production Readiness checklist per ${REF}/production-readiness.md (Phase 7) over the batch's changed files. Auto-EXECUTE only stack-matched index / access-rule / cron deploys; REPORT (do not execute) env vars, feature flags, DB migrations, third-party secrets, DNS changes. NON-BLOCKING — never fail the batch.\n\n${projectBrief}\nChanged files: ${dedupe(committed.flatMap((r) => r.filesChanged || [])).join(', ') || '(derive from git)'}\n\nReturn: { autoExecuted:[...], manualItems:[...], note }`,
399
+ { label: 'production-readiness', phase: 'Production', agentType: 'general-purpose',
400
+ schema: { type: 'object', required: ['manualItems'], additionalProperties: true, properties: { autoExecuted: { type: 'array', items: { type: 'string' } }, manualItems: { type: 'array', items: { type: 'string' } }, note: { type: 'string' } } } }
401
+ )
402
+ ledger(firstCard, 'phase7-production', 'DONE', `auto=${((prodReadiness && prodReadiness.autoExecuted) || []).length} manual=${((prodReadiness && prodReadiness.manualItems) || []).length}`)
403
+ } else {
404
+ ledger(firstCard, 'phase7-production', 'SKIPPED', 'not merged')
405
+ }
406
+
407
+ // ───────────────────────────────────────────────────────────────────────────
408
+ // Return — assemble the report + Phase-8 telemetry (variant new2).
409
+ // ───────────────────────────────────────────────────────────────────────────
410
+ return finalReturn({ fatal: false })
411
+
412
+ // ───────────────────────────────────────────────────────────────────────────
413
+ function finalReturn(opts) {
414
+ const fatal = opts && opts.fatal
415
+ const telemetry = buildTelemetry()
416
+ const report = buildReport({ fatal, reason: opts && opts.reason, finalSummary, mergeResult })
417
+ return { report, perCardResults, gateLedger, residualFollowups, telemetry }
418
+ }
419
+
420
+ function buildTelemetry() {
421
+ const total = cardIds.length
422
+ const done = perCardResults.filter((r) => r.status === 'committed').length
423
+ const fixCycles = gateLedger.filter((x) => /resolve:/.test(x.gate)).length
424
+ return {
425
+ variant: 'new2',
426
+ batch: cardIds,
427
+ first_card: firstCard,
428
+ cards_total: total,
429
+ cards_committed: done,
430
+ cards_followup: perCardResults.filter((r) => r.status === 'followup').length,
431
+ followups_created: residualFollowups.length,
432
+ resolve_invocations: fixCycles,
433
+ final_review: finalSummary || null,
434
+ merged: !!(mergeResult && mergeResult.merged),
435
+ execution_mode: preflight ? preflight.executionMode : 'sequential',
436
+ per_card: perCardResults.map((r) => ({ card: r.card, status: r.status, telemetry: r.telemetry || {} })),
437
+ stats_requested: !!FLAGS.stats,
438
+ }
439
+ }
440
+
441
+ function buildReport(o) {
442
+ const L = []
443
+ L.push(`# new2 batch — ${cardIds.join(' ')}`)
444
+ L.push(`Variant: **new2** (workflow-hosted) · Mode: ${preflight ? preflight.executionMode : '?'} · Trunk: ${TRUNK}`)
445
+ if (o.fatal) { L.push(``, `## ⛔ BATCH FATAL`, o.reason || 'workspace unworkable'); return L.join('\n') }
446
+ L.push(``, `## Esito card`)
447
+ L.push(`| Card | Status | Commit | File |`)
448
+ L.push(`|------|--------|--------|------|`)
449
+ for (const r of perCardResults) L.push(`| ${r.card} | ${r.status} | ${r.commit || '-'} | ${(r.filesChanged || []).length} |`)
450
+ if (finalSummary) {
451
+ L.push(``, `## Final review`)
452
+ L.push(`Verified: ${finalSummary.verified} (BLOCKER ${finalSummary.blockers} / HIGH ${finalSummary.highs}) · needs-manual ${finalSummary.needsManual} · failing gates: ${(finalSummary.failingGates || []).join(', ') || 'none'}`)
453
+ }
454
+ L.push(``, `## Merge`)
455
+ if (mergeResult && mergeResult.merged) L.push(`Merged → ${TRUNK} @ ${mergeResult.mergeCommit || '?'}${mergeResult.reconciliation ? ' · ' + mergeResult.reconciliation : ''}`)
456
+ else if (mergeBlocked) L.push(`Merge BLOCKED — ${batchFatal ? 'batch fatal (new2-resolve)' : 'unresolved final BLOCKER/HIGH'}. Worktree left intact at ${sharedCtx.worktreePath}.`)
457
+ else L.push(`Not merged (no committed cards or incomplete).`)
458
+ if (prodReadiness) {
459
+ const man = prodReadiness.manualItems || []
460
+ L.push(``, `## Production readiness (Phase 7)`)
461
+ L.push(`Auto-eseguiti: ${(prodReadiness.autoExecuted || []).length} · Da fare a mano: ${man.length}`)
462
+ for (const m of man) L.push(`- [ ] ${m}`)
463
+ }
464
+ if (residualFollowups.length) {
465
+ L.push(``, `## ⚠️ Follow-up auto-creati (residui non risolti autonomamente — nulla perso)`)
466
+ for (const f of residualFollowups) L.push(`- **${f.followupCard}** ← ${f.card} (${f.kind})${f.reason ? ': ' + f.reason : ''}`)
467
+ }
468
+ const excluded = gateLedger.filter((x) => x.decision === 'EXCLUDED')
469
+ if (excluded.length) {
470
+ L.push(``, `## Card escluse in pre-flight`)
471
+ for (const e of excluded) L.push(`- ${e.card}: ${e.detail}`)
472
+ }
473
+ L.push(``, `_Gate ledger: ${gateLedger.length} decisioni deterministiche, 0 AskUserQuestion._`)
474
+ return L.join('\n')
475
+ }
476
+
477
+ function dedupe(arr) { return Array.from(new Set(arr)) }
478
+ function emptyReturn(reason) {
479
+ return { report: `# new2 — no-op\n${reason}`, perCardResults: [], gateLedger: [], residualFollowups: [], telemetry: { variant: 'new2', cards_total: 0, reason } }
480
+ }
@@ -14,6 +14,13 @@ workflows are unavailable behaves exactly as before.
14
14
  | Workflow | Used by | What it does |
15
15
  | :--- | :--- | :--- |
16
16
  | `new-final-review` | `/new` Final Review (Step F.1.5) | Runs the read-only cross-batch review fan-out — architecture baseline + Codex ‖ doc-reviewer ‖ api-perf-cost-auditor ‖ qa-sentinel — then adversarially verifies low-confidence findings and returns them classified. Applies no fixes (the skill owns fix application + user gates). |
17
+ | `new2` (v4.16.0) | `/new2` skill (the whole batch) | **EXPERIMENTAL A/B variant of `/new`.** Hosts the ENTIRE batch orchestration — pre-flight, per-card implement+review pipeline (sequential or team-mode parallel coders), final review (delegates to `new-final-review`), and auto-merge — in the background runtime, so subagent output never enters the main context. Fully autonomous: every `/new` `AskUserQuestion` gate is a deterministic policy; blocking gates / scope-expanding findings route to `new2-resolve`. Agents Read `/new`'s reference modules for semantics, so the script encodes only orchestration shape + gate policy. |
18
+ | `new2-resolve` (v4.16.0) | `new2` (self-healing) | Resolution pass invoked by `new2` for any gate that would otherwise need a human: card fail/blocker, scope-EXPANDING finding, or cross-cutting edge (agent-crash, baseline-fail). Runs targeted fix → judged multi-attempt → adversarial re-verify; its terminal auto-materialises a tracked follow-up card so nothing is silently dropped. Domain-Override aware (doc→doc-reviewer; security/migration never inline-bypassed). |
19
+
20
+ > **`new2` is an experiment, not a replacement.** It exists to A/B-test the
21
+ > context-economy of workflow-hosting `/new`. `/new` remains the SSOT, the
22
+ > cross-tool default, and the recovery-safe path. `new2` is Claude-only and its
23
+ > skill HALTs with a pointer to `/new` when the `Workflow` tool is unavailable.
17
24
 
18
25
  ## How distribution works
19
26
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.15.0",
3
+ "version": "4.16.1",
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"