baldart 4.14.1 → 4.16.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 +39 -0
- package/README.md +2 -2
- package/VERSION +1 -1
- package/framework/.claude/skills/new/SKILL.md +9 -2
- package/framework/.claude/skills/new/references/setup.md +54 -12
- package/framework/.claude/skills/new/references/team-mode.md +3 -3
- package/framework/.claude/skills/new2/SKILL.md +128 -0
- package/framework/.claude/workflows/new2-resolve.js +179 -0
- package/framework/.claude/workflows/new2.js +442 -0
- package/framework/docs/WORKFLOWS.md +7 -0
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,45 @@ 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.0] - 2026-06-05
|
|
9
|
+
|
|
10
|
+
**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).
|
|
11
|
+
|
|
12
|
+
> **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()`).
|
|
13
|
+
|
|
14
|
+
> **`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.
|
|
15
|
+
|
|
16
|
+
> **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`.
|
|
17
|
+
|
|
18
|
+
### Added
|
|
19
|
+
|
|
20
|
+
- **`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.
|
|
21
|
+
- **`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.
|
|
22
|
+
- **`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).
|
|
23
|
+
|
|
24
|
+
### Changed
|
|
25
|
+
|
|
26
|
+
- **`framework/docs/WORKFLOWS.md`** — tabella "What ships today": aggiunte le righe `new2` e `new2-resolve` + nota "esperimento, non rimpiazzo".
|
|
27
|
+
- **`README.md`** — conteggio skill 29 → 30; `new2` aggiunto alla categoria Workflow con nota EXPERIMENTAL/Claude-only.
|
|
28
|
+
|
|
29
|
+
## [4.15.0] - 2026-06-04
|
|
30
|
+
|
|
31
|
+
**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).
|
|
32
|
+
|
|
33
|
+
> **Why.** L'orchestrator di `/new` è long-lived: tutto ciò che entra nel suo contesto ci resta per l'intera run e viene ri-letto (a tariffa cache-read) a ogni turno. Tre cose lo gonfiavano al pre-flight senza alcun bisogno di restare nell'orchestrator: (1) l'invocazione inline di `/nw` caricava il corpo da **~1200 righe** di `worktree-manager` nel prefisso cached + camminava `npm install`/`tsc`/`lint`/`build` per ~15-20 turni foreground; (2) il trace `[codex]` del check cross-card rientrava nell'orchestrator perché il result-handling faceva `tail`/`cat` del file grezzo (pieno di righe di comando-trace) invece del solo verdict; (3) il tracker veniva creato e poi editato ~5 volte in modo incrementale durante un pre-flight che è idempotente e non ha bisogno di persistenza mid-flight. La cura sposta (1) e (2) in **contesti usa-e-getta** (subagente background + Bash background) di cui l'orchestrator trattiene solo il verdict strutturato, e collassa (3) in **un solo flush**. Il pattern è additivo agli invarianti già presenti (§ "Context economy" del core, regola sul canale inline) e non tocca nessun gate BLOCKING né il contratto di recovery per-fase dell'esecuzione card. Lezione metodologica confermata: dopo ogni fix il driver dominante può spostarsi d'asse — qui da "prosa nel prefisso" (v4.13/4.14) a "turni di pre-flight × prefisso crescente".
|
|
34
|
+
|
|
35
|
+
> **Onestà sulla magnitudine (post code-review + DUE giri di adversarial review).** Il risparmio vero è il **sollievo del prefisso dell'orchestrator** (corpo di worktree-manager fuori dal prefisso cached + ~15-30 turni foreground collassati in background ops): i token del build NON spariscono — vengono spesi nel subagente, sullo stesso pool di sessione — ma smettono di moltiplicarsi contro il prefisso crescente dell'orchestrator a ogni turno. La review avversariale ha **refutato** quattro punti rivelatisi difetti reali (poi fixati): (1) l'assunzione "un subagente può invocare una Skill" era **non provata e senza fallback** → **fallback inline a `/nw`** se il subagente non ritorna il blocco; (2) la barriera a due op poteva **risvegliarsi sul primo completamento** e leggere un `$AUDIT_FILE` a metà → **wait-for-ALL esplicito**; (3) la delega worktree apriva un **recovery-gap** (un worktree di codice NON è ri-creabile — `/nw` fa fail-loud su collisione). Il primo rimedio (pre-check su `registry.json`) è stato **a sua volta refutato dal secondo giro**: worktree-manager scrive l'entry registry SOLO a fine `/nw`, *dopo* il build, quindi per tutta la finestra di barriera il worktree esiste su disco ma NON è in registry → il check era cieco proprio nello scenario da coprire. Fix corretto: **rilevamento git-autoritativo** (`git worktree list` sul branch deterministico) con resume-se-completo / reset-e-ricrea-se-orfano (a pre-flight non c'è lavoro card da perdere); (4) il flush proponeva un `Write`-da-memoria che dopo una compaction avrebbe **droppato i campi Phase-0** (`$MAIN`/`$TRUNK`) → ora solo `Edit` chirurgici, mai overwrite; più una clausola nel **Context recovery protocol** del core che codifica il rientro in Pre-flight step 4. Per contro l'adversarial è stata essa stessa **refutata dai dati** sul filtro `[codex]`: lo static-grep del `.mjs` diceva "prefisso mai emesso", ma il file audit reale ha **63 righe `[codex]` su 73** (il wrapper Codex di Claude Code le emette nel redirect) → il filtro è corretto e tenuto.
|
|
36
|
+
|
|
37
|
+
### Changed
|
|
38
|
+
|
|
39
|
+
- **`framework/.claude/skills/new/references/setup.md`** — Pre-flight (once) step 4-6 riscritti:
|
|
40
|
+
- **Step 4 (Lever #1)**: il setup worktree non è più invocato inline; l'orchestrator spawna **un subagente background** (`general-purpose`, **`mode: bypassPermissions`**, `run_in_background`, `name: worktree-setup-<FIRST-CARD-ID>`) che invoca `/nw` programmatic e ritorna SOLO un blocco strutturato (`worktree_path`/`branch`/`port`/`created_at`/`baseline`/`baseline_log`). Il corpo da ~1200 righe di `worktree-manager` + i log install/build vivono e muoiono nel contesto del subagente. **Guardie (post-review, 2 giri)**: step **4a2** pre-check **git-autoritativo** (`git worktree list` sul branch deterministico — NON il registry, che è scritto solo a fine `/nw` ed è cieco durante il build) → worktree completo: resume; orfano mid-build: reset (`worktree remove --force` + `branch -D`) e ricrea; step **4d** fallback inline a `/nw` se il subagente non ritorna il blocco (capability Skill-da-subagente non assunta, mai strandare la barriera); campo `created_at` nel blocco (e letto dal registry sul path di resume) per non rompere `cycle_time_mins`; flush con soli `Edit` chirurgici (mai `Write`-da-memoria, che dopo compaction droppa i campi Phase-0).
|
|
41
|
+
- **Step 5 (barrier)**: l'orchestrator lancia 3d (Codex) + 4 (worktree subagent) insieme e **chiude il turno** su una barriera **wait-for-ALL** (attende OGNI op lanciata, non la prima — altrimenti leggerebbe un `$AUDIT_FILE` a metà; compaction mid-barrier rientra in pre-flight, resa sicura dal 4a2) — niente più ~30 turni foreground di walk-through (no `sleep`/`echo`, ri-invocazione automatica a completamento).
|
|
42
|
+
- **Step 6 (Lever #3)**: al resume, gate sul `baseline: fail` (STOP), verdict Codex distillato, e **un singolo Edit** che scrive l'intero blocco di pre-flight (`## File Ownership Map` + `## Execution Mode` + `## Worktree` + `## Cross-Card Conflicts (Codex)`) — niente Edit incrementali per sub-step (pre-flight idempotente; la persistenza incrementale resta per l'esecuzione card).
|
|
43
|
+
- **Step 3d "Result handling" (Lever #2)**: aggiunta la **verdict-extraction discipline** — il file `$AUDIT_FILE` contiene il trace `[codex]` davanti al verdict; vietato `cat`/`tail`/`head` grezzo, si legge solo via filtro `grep -vE '^\[codex\]' "$AUDIT_FILE" | tail -n 60` e si logga il finding distillato, mai il trace.
|
|
44
|
+
- **`framework/.claude/skills/new/references/team-mode.md`** — Team Mode Pre-flight: le sezioni `## Team Mode` + `## Parallel Groups` confluiscono nel **flush unico** di setup.md step 6c (niente Edit separato); il wave layout è risolto prima del lancio dei background ops, così è in-context per il flush.
|
|
45
|
+
- **`framework/.claude/skills/new/SKILL.md`** — § "Context economy": nuovo **punto 6** (delega il setup body-heavy a un contesto usa-e-getta — background subagent/Bash, l'orchestrator tiene solo il verdict; batch delle scritture tracker di pre-flight) + chiusura della regola estesa a «what the orchestrator chooses to retain».
|
|
46
|
+
|
|
8
47
|
## [4.14.1] - 2026-06-04
|
|
9
48
|
|
|
10
49
|
**Il gate Simplify del Team Mode di `/new` (D.3b) ora skippa solo le card davvero trivial (`TRIVIAL_CARDS`), non tutte quelle con `review_profile == skip` — chiude una divergenza col path sequential (Phase 2.55 `IS_TRIVIAL`) e con la stessa D.1.5 di team-mode.** La pipeline per-card di `/new` vive in due modalità (sequential e team) che ri-enunciano alcuni gate; due predicati gemelli erano davvero divergenti. **Drift 1 (bug, fixato)**: il sequential Phase 2.55 ri-conferma `IS_TRIVIAL` sul diff ACTUAL committato (tutte e 3 le condizioni, incl. il check non-source) → una card `review_profile=skip` con un file SORGENTE nel diff reale NON è trivial → ESEGUE Simplify; il team D.3b invece skippava sul solo `review_profile == skip`, ignorando il diff reale → la saltava. Aggravante: team-mode era incoerente con sé stesso — a D.1.5 calcola GIÀ `TRIVIAL_CARDS` col check non-source completo (e la sua nota SSOT dichiara «D.3b already skipped for trivial»), ma il gate D.3b gateava sul set più lasco. Blast radius: solo qualità (la card prendeva comunque D.2 code-review + il Final FULL gate), ma in team mode si perdeva la difesa contro la deviazione del coder che il sequential ha. **Drift 2 (intenzionale, documentato)**: lo scope per-GRUPPO della QA gate (D.4) vs per-CARD del sequential (21b) — stesso predicato (`deep`/risk → FULL, altrimenti defer al Final), granularità per necessità del modello a wave (qa-sentinel gira una volta sul diff combinato del gruppo); è il lato deliberatamente più conservativo (una card rischiosa → tutto il gruppo FULL), nessun gap di correttezza → comportamento invariato, aggiunta solo una frase di chiarimento. **PATCH** (bugfix di un falso-skip in team mode; nessuna capability, nessuna chiave `baldart.config.yml`).
|
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 (
|
|
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.
|
|
1
|
+
4.16.0
|
|
@@ -80,7 +80,7 @@ Branch: [feat/FEAT-XXXX-slug]
|
|
|
80
80
|
Slug: [the slug used to derive Branch/Path — see Pre-flight step 4]
|
|
81
81
|
Path: [.worktrees/feat-FEAT-XXXX-slug]
|
|
82
82
|
Port: [from registry]
|
|
83
|
-
Created: [ISO-8601 timestamp the worktree was created — Pre-flight step
|
|
83
|
+
Created: [ISO-8601 timestamp the worktree was created — Pre-flight step 6c backfills from the worktree-setup subagent's created_at; Phase 8 cycle_time_mins start anchor]
|
|
84
84
|
Group parent: [FEAT-XXXX or "standalone"]
|
|
85
85
|
Main repo: [/absolute/path/to/main/repo]
|
|
86
86
|
Trunk branch: [resolved git.trunk_branch — Phase 0 step 0 populates]
|
|
@@ -230,8 +230,14 @@ baselines. Keep that bulk on disk and pass **paths**, not bodies.
|
|
|
230
230
|
> inlining their concatenated contents.
|
|
231
231
|
> 5. **Targeted Reads.** When verifying code (Phase 2.5), Read only the evidence lines (grep with
|
|
232
232
|
> tight context around the `file:line` the completion report names) — not whole files.
|
|
233
|
+
> 6. **Delegate body-heavy setup to a discarded context.** Work whose *output* the orchestrator does
|
|
234
|
+
> not retain — worktree creation + baseline build (invoking `/nw` loads the ~1200-line
|
|
235
|
+
> worktree-manager skill into the cached prefix), external grounding checks — runs in a **background
|
|
236
|
+
> subagent / background Bash**; the orchestrator keeps only the structured verdict (a handful of
|
|
237
|
+
> fields), never the skill body, logs, or trace. And **batch the pre-flight tracker writes into one
|
|
238
|
+
> flush** — pre-flight is idempotent, so per-sub-step Edits are churn the cached prefix re-reads.
|
|
233
239
|
>
|
|
234
|
-
> This rule is
|
|
240
|
+
> This rule is about the **inline channel** and what the orchestrator chooses to retain. It does NOT change any agent's return contract,
|
|
235
241
|
> nor what the orchestrator is allowed to read at a decision point — only that bulk arrives via a
|
|
236
242
|
> path the consumer opens itself, not via the orchestrator's own context.
|
|
237
243
|
|
|
@@ -311,6 +317,7 @@ per-card nei sub-step D.x (mai aggregate). Caricalo quando Pre-flight seleziona
|
|
|
311
317
|
If at ANY point you are unsure where you are in the batch:
|
|
312
318
|
1. Read your tracker file (`/tmp/batch-tracker-<FIRST-CARD-ID>.md`)
|
|
313
319
|
2. Check `## Current Card` — if populated, resume that card at the listed phase.
|
|
320
|
+
2b. **Pre-flight not finished?** If `## Current Card` is empty / `(none — starting pre-flight)` AND `## Worktree` still shows skeleton placeholders (no real `Path:`/`Port:`), then a compaction hit mid-pre-flight (most likely during the step-5 background barrier). Re-enter **Pre-flight at step 4** (Read the `setup.md` module first per § "Routing"): its step-4a2 git pre-check re-detects an already-created worktree via `git worktree list` and resumes-or-resets it safely — so re-entry never double-creates or fails-loud.
|
|
314
321
|
3. **Re-Read the phase module** named in `## Current Card`'s `phase_module_loaded:` field (a compaction may have evacuated it from context — see § "Routing" HARD RULE). If the field is absent, map the listed phase to its module via the § "Routing" table and Read that.
|
|
315
322
|
4. Check `## Card Queue` — find the next unchecked card.
|
|
316
323
|
5. Check `## Completed Cards` — know what's already done (don't redo).
|
|
@@ -157,9 +157,9 @@
|
|
|
157
157
|
|
|
158
158
|
When `mode == sequential`, the per-card pipeline below runs exactly as documented. The `execution_strategy.groups` levels are simply ignored. When `mode == team`, skip the per-card pipeline and follow the **Team Mode** section at the end of this document.
|
|
159
159
|
|
|
160
|
-
**→ Create the Task spine now.** The execution mode and (in team mode) the wave layout are resolved — create the native Task spine per § "Progress Visibility" A: `Pre-flight` (→ `in_progress`) + one task per card (wave-labelled in team mode) + `Final review` + `Merge & cleanup`. Emit the first Progress Bar with this batch's table. Mark `Pre-flight` → `completed`
|
|
160
|
+
**→ Create the Task spine now.** The execution mode and (in team mode) the wave layout are resolved — create the native Task spine per § "Progress Visibility" A: `Pre-flight` (→ `in_progress`) + one task per card (wave-labelled in team mode) + `Final review` + `Merge & cleanup`. Emit the first Progress Bar with this batch's table. Mark `Pre-flight` → `completed` at the pre-flight resume (step 6d), once both background ops have returned and the consolidated tracker flush is written.
|
|
161
161
|
|
|
162
|
-
3d. **Codex batch cross-card grounding check** (
|
|
162
|
+
3d. **Codex batch cross-card grounding check** (background — launched together with the worktree-setup subagent in step 4, then a single barrier in step 5)
|
|
163
163
|
|
|
164
164
|
> **Why**: a non-Anthropic frontier model (Codex, via `codex-companion.mjs`) reviews the full card batch for cross-card conflicts that per-card plan-auditor checks cannot detect (each plan-auditor sees only one card).
|
|
165
165
|
|
|
@@ -228,7 +228,12 @@
|
|
|
228
228
|
**Result handling** (read before Phase 1 of first card):
|
|
229
229
|
- **Background completion barrier (BLOCKING):** record the background task id when you launch it; do NOT read `$AUDIT_FILE` until that task has completed. Because this gate's verdict steers Phase-1 card ordering, the first card MUST NOT start while the check is in flight.
|
|
230
230
|
- **TIMED_OUT branch:** the launch sets `timeout: 300000` (5 min). If the background task hits the timeout without producing a terminal result (the tail of `$AUDIT_FILE` shows no `PASS` / conflict block / `CODEX_NOT_FOUND`), treat it as **TIMED_OUT** — do NOT leave the task "in-progress" and silently proceed. Log `cross-card-check: TIMED_OUT` in `## Cross-Card Conflicts (Codex)` and take the SAME fallback as `CODEX_NOT_FOUND`: spawn `code-reviewer` over the batch with the four conflict questions. Never start the batch with cross-card review in an unresolved/in-flight state.
|
|
231
|
-
-
|
|
231
|
+
- **Verdict-extraction discipline (Context economy — the trace MUST NOT re-enter the orchestrator).** `$AUDIT_FILE` holds the full `[codex]` command trace (every `sed`/`grep`/`cat` Codex ran while investigating) *ahead of* the verdict — that trace is pure noise and must never land in the orchestrator's context. **NEVER `cat`/`tail`/`head` the raw file.** Read it only through a filter that strips the trace and keeps the terminal verdict:
|
|
232
|
+
```bash
|
|
233
|
+
grep -vE '^\[codex\]' "$AUDIT_FILE" | tail -n 60
|
|
234
|
+
```
|
|
235
|
+
Parse PASS / `CODEX_NOT_FOUND` / conflict-findings from that filtered output only. When logging to the tracker, write the **distilled findings** (one line per conflict: cards + type + one-line fix) — never paste the raw block, and never the trace.
|
|
236
|
+
- Read the exact `$AUDIT_FILE` path recorded in the tracker after the background command completes (always via the trace-stripping filter above).
|
|
232
237
|
- If the file contains **`CODEX_NOT_FOUND`** (Codex unavailable): do NOT silently proceed — **fallback**: spawn the `code-reviewer` agent over the full batch's card YAMLs + file-ownership map with the same cross-card conflict questions (FILE_CONFLICT / IMPLICIT_DEP / ORDER_RISK / STATE_MUTATION). If `code-reviewer` is also unavailable, surface `CAPABILITY_UNAVAILABLE: cross-card-review` to the user and ask whether to proceed without cross-card detection or halt. Never run a multi-card batch with cross-card review silently skipped.
|
|
233
238
|
- If **PASS** or file empty: proceed normally.
|
|
234
239
|
- If **conflicts found**: log in tracker under `## Cross-Card Conflicts (Codex)` and present to user. For each conflict:
|
|
@@ -236,14 +241,51 @@
|
|
|
236
241
|
- `IMPLICIT_DEP` → add `depends_on` entry to tracker notes (do NOT modify backlog YAML).
|
|
237
242
|
- `STATE_MUTATION` → add warning to both cards' Phase 2 briefings.
|
|
238
243
|
|
|
239
|
-
4. **Worktree setup
|
|
240
|
-
a. **Resolve the `slug`** (
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
244
|
+
4. **Worktree setup — delegate to a BACKGROUND subagent (Context economy: the worktree-manager skill body + the install/build walk-through must NOT enter the orchestrator context).** Do **NOT** invoke `/nw` inline from the orchestrator. Invoking the skill here loads the ~1200-line `worktree-manager` body into the orchestrator's **cached prefix** (re-read every later turn) and then steps through `npm install` → `tsc` → `lint` → `build` across ~15-20 foreground turns, each re-reading the whole growing prefix. Both costs are pure waste in the orchestrator — they belong in a context that is discarded. Instead:
|
|
245
|
+
a. **Resolve the `slug`** (in-context, cheap — the only part the orchestrator keeps). Derive it deterministically: if the parent card (or the lead card) has `git_strategy.branch` set, parse the slug from it (`feat/<PARENT-ID>-<slug>` → `<slug>`); otherwise slugify the parent/lead card `title` (lowercase, non-alphanumerics → `-`, collapse repeats, trim, cap ~40 chars). Do NOT let `/nw` invent its own slug — pass it explicitly.
|
|
246
|
+
a2. **Recovery/idempotency pre-check (BLOCKING — before any spawn; detect via GIT, not the registry).** A **code** worktree is NOT re-creatable: `/nw` **fails loudly on a slug/branch collision** (it does not resume like docs mode — see worktree-manager step 2 "code worktree collision → fail loudly"). And the registry is a **lagging** witness — worktree-manager writes its entry only at the END of `/nw`, *after* the full build (worktree-manager step 6), so for the whole barrier window a worktree can exist on disk while `.worktrees/registry.json` has NO entry for it. Detecting by registry presence would therefore be blind exactly during the compaction window this guard exists for. Use the **git-authoritative** check instead:
|
|
247
|
+
```bash
|
|
248
|
+
# WT_BRANCH MUST be resolved EXACTLY as /nw will derive it (or detection misses a custom branch
|
|
249
|
+
# and the re-spawn hits the fail-loud collision this guard exists to prevent):
|
|
250
|
+
# use the parent/lead card's git_strategy.branch verbatim if set, else the fallback feat/<PARENT-ID>-<slug>.
|
|
251
|
+
WT_BRANCH="<git_strategy.branch — or — feat/<PARENT-ID>-<slug>>"
|
|
252
|
+
WT_PATH="$(git -C "$MAIN" worktree list --porcelain \
|
|
253
|
+
| awk -v b="refs/heads/$WT_BRANCH" '$1=="worktree"{p=$2} $1=="branch"&&$2==b{print p}')"
|
|
254
|
+
```
|
|
255
|
+
- **`WT_PATH` empty** (no worktree) → proceed to 4b (spawn fresh).
|
|
256
|
+
- **`WT_PATH` set AND `registry.json` has a complete code entry for this slug** (a finished prior run — `buildVerified` recorded) → **resume**: read `path`/`branch`/`port`/`createdAt`/`buildVerified` from that entry, skip to step 6; re-run the baseline as a single background `Bash` (output to `/tmp`) **only if** `buildVerified` is not `true`.
|
|
257
|
+
- **`WT_PATH` set but NO complete registry entry** (a prior attempt interrupted mid-setup — the normal compaction-mid-barrier state: worktree created, build unfinished, entry never written) → it is a half-built orphan with **no card work** (we are still in pre-flight, zero commits). **Reset clean and recreate**: `git -C "$MAIN" worktree remove --force "$WT_PATH"` then `git -C "$MAIN" branch -D "$WT_BRANCH"` (both ignore-if-absent), then proceed to 4b. A pre-flight worktree has nothing to lose, so a clean recreate is always safe — and it sidesteps the fail-loud collision a naive re-spawn would hit.
|
|
258
|
+
This — detection by `git worktree list`, not the lagging registry — is what makes the deferred-flush pre-flight genuinely idempotent across compaction.
|
|
259
|
+
b. **Spawn ONE background subagent** (Agent tool, **`mode: "bypassPermissions"`** — mandatory per the SKILL.md meta-rules; a background agent that hit a permission prompt with no human present would stall the barrier forever — `run_in_background: true`, `name: "worktree-setup-<FIRST-CARD-ID>"`, a subagent type that can use the Skill tool — `general-purpose`) whose ENTIRE mission is to run `/nw` and return the block below. Briefing:
|
|
260
|
+
```
|
|
261
|
+
Invoke the worktree-manager skill in `/nw` programmatic mode with:
|
|
262
|
+
{ cards: [<all card IDs>], groupParent: <PARENT-ID|null>, slug: "<slug>" }
|
|
263
|
+
Let it: group cards, derive the branch from git_strategy.branch (fallback
|
|
264
|
+
feat/<PARENT-ID>-<slug>), create the worktree in .worktrees/, install deps,
|
|
265
|
+
copy env files, assign a free port, update .worktrees/registry.json (all card
|
|
266
|
+
IDs in the `cards` field), and run the baseline (tsc + lint + build).
|
|
267
|
+
Redirect ALL install/build/git output to files under /tmp — never inline it.
|
|
268
|
+
Return ONLY this block, nothing else (no logs, no narration, no skill recap):
|
|
269
|
+
worktree_path: <absolute path>
|
|
270
|
+
branch: <branch>
|
|
271
|
+
port: <n>
|
|
272
|
+
created_at: <ISO-8601, stamped WHEN you create the worktree — before install/build>
|
|
273
|
+
baseline: pass | fail
|
|
274
|
+
baseline_log: <path on failure, else "-">
|
|
275
|
+
If the build fails, return `baseline: fail` with the log path and STOP — do not continue.
|
|
276
|
+
```
|
|
277
|
+
c. The subagent's context (the worktree-manager skill body, install/build logs, git output) **lives and dies inside it** — the orchestrator receives only the structured block. Combined with the Codex check (3d, also background), this replaces the old ~30-turn foreground pre-flight tail with background ops and a single resume.
|
|
278
|
+
d. **Fallback if the subagent cannot run `/nw` (the Skill-from-subagent capability is NOT assumed).** If the subagent returns empty, or without the structured block — e.g. it cannot invoke the Skill tool in this consumer, or it returns the known "0 tool uses · Done" empty-result failure — do **NOT** strand the barrier: the orchestrator **falls back to invoking `/nw` inline itself** (the pre-v4.15.0 path — slug from 4a, then record `worktree_path`/`branch`/`port` and stamp `created_at` from the inline return). You lose the prefix saving for this one run, but pre-flight completes. This is the same opt-in-with-fallback discipline the Codex check (3d) and the dynamic-workflow gate use — never leave the worktree uncreated on a missing capability.
|
|
279
|
+
5. **End the turn — barrier on ALL launched background ops (wait for every one, not the first).** Having launched the Codex cross-card check (3d) and the worktree-setup subagent (4), the orchestrator has nothing to do until they return. **End the turn** — do NOT poll with `sleep`/`echo "waiting"` loops (§ "Context economy"; same rule as team-mode Step C). Background agents and background `Bash` re-invoke the orchestrator automatically on completion. **Wait for EVERY launched op before step 6**: each completion wakes you separately, so on each wake check whether *all* launched ops have returned — if one is still in flight, **end the turn again** and wait. Do NOT proceed to step 6 on the first completion, or you would read a half-written `$AUDIT_FILE` (and 3d's "If PASS or file empty: proceed normally" would silently swallow real conflicts) or a missing worktree block. (If 3d was SKIPPED by the provenance gate, the only op is the worktree subagent — or none, if step 4a2 resumed an existing worktree.) **Recovery**: a compaction mid-barrier re-enters pre-flight from step 4; the 4a2 git pre-check makes that safe (the worktree is detected via `git worktree list` and resumed-or-reset, never blindly re-created into a fail-loud collision).
|
|
280
|
+
6. **On resume — flush the pre-flight tracker sections in one pass (no incremental per-sub-step churn).** When all launched ops have returned:
|
|
281
|
+
a. **Baseline gate**: if the worktree subagent returned `baseline: fail` → STOP and report (point the user at `baseline_log`). Do NOT continue to Phase 1.
|
|
282
|
+
b. **Codex verdict**: handle it via the verdict-extraction discipline in 3d (read `$AUDIT_FILE` through the `[codex]`-stripping filter; keep distilled findings only).
|
|
283
|
+
c. **One-pass tracker flush (no round-trips).** Assemble the pre-flight sections **in-context** (they are all small) and fill them with **back-to-back `Edit`s and no intervening reads**. The win is killing the old read-modify-read-modify churn (~5 incremental edits), **not** the literal tool-call count. **Do NOT `Write`-overwrite the whole file from in-context memory**: Phase 0 already wrote `Main repo:` / `Trunk branch:` / `Metrics dir:` into `## Worktree` and the `Status`/divergence lines into `## Phase 0`, and after a barrier compaction you may no longer hold those in-context (`$MAIN` "does not survive context compaction" — § Phase 0 step 1) — an overwrite-from-memory would silently drop them and HALT later with "`$MAIN` absent from tracker". Surgical `Edit`s on the placeholder sections leave Phase 0's content intact. Sections filled here:
|
|
284
|
+
- `## File Ownership Map` (3b).
|
|
285
|
+
- `## Worktree` — path / branch / slug / port, plus `Created:` = **the subagent block's `created_at`** (worktree-creation time, NOT resume time, so Phase 8's `cycle_time_mins` still spans the build window). On the **4a2 resume** path, `Created:` = the registry entry's `createdAt`; on the **4d inline fallback**, stamp `date -u +%Y-%m-%dT%H:%M:%SZ` at creation. (Never leave `Created:` empty — `cycle_time_mins` anchors on it.)
|
|
286
|
+
- `## Cross-Card Conflicts (Codex)` — distilled findings (the 3d skip-decision already wrote the `SKIPPED`/`RUN — reason` line; append the distilled findings on the RUN path, nothing to add on SKIP).
|
|
287
|
+
- In team mode: `## Team Mode` + `## Parallel Groups` (per team-mode.md).
|
|
288
|
+
`## Execution Mode` was already written at step 3c (it must exist before the Task spine) — do NOT rewrite it here. **Rationale**: pre-flight is idempotent and cheap to redo (step 4a2's git pre-check guards worktree re-creation), so the data sections do not need mid-flight persistence; per-phase incremental writes resume for card execution, where mid-phase recovery actually matters. The file already exists (the skeleton was created at batch start per § Context Tracking; Phase 0 wrote `## Phase 0`) — backfill, do NOT re-create.
|
|
289
|
+
d. **→ Visibility**: mark the `Pre-flight` Task spine entry → `completed` and emit the first wave's Progress Bar.
|
|
248
290
|
|
|
249
291
|
---
|
|
@@ -15,10 +15,10 @@ It does NOT accumulate implementation details, codebase-architect findings, or r
|
|
|
15
15
|
|
|
16
16
|
### Team Mode Pre-flight
|
|
17
17
|
|
|
18
|
-
|
|
18
|
+
The standard pre-flight (setup.md Phase 0 + steps 1-6) runs unchanged; add:
|
|
19
19
|
|
|
20
|
-
1. Read the `execution_strategy.groups` (each with `level` + `cards`) and sort into execution layers by `level`. For legacy cards lacking the block, fall back to the on-the-fly topological-layer computation from step 3c.
|
|
21
|
-
2.
|
|
20
|
+
1. Read the `execution_strategy.groups` (each with `level` + `cards`) and sort into execution layers by `level`. For legacy cards lacking the block, fall back to the on-the-fly topological-layer computation from step 3c. This is resolved **before** launching the background ops (setup.md step 4), so the wave layout is in-context for the consolidated flush.
|
|
21
|
+
2. **Fold the team-mode sections into the single consolidated pre-flight write** (setup.md step 6c — do NOT add a separate Edit). The same one-shot flush that writes the ownership map / mode / worktree / cross-card sections also writes:
|
|
22
22
|
```
|
|
23
23
|
## Team Mode
|
|
24
24
|
Status: active
|
|
@@ -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,179 @@
|
|
|
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
|
+
].join('\n')
|
|
71
|
+
|
|
72
|
+
// The agent that OWNS a fix in this domain (Domain-Override): doc → doc-reviewer.
|
|
73
|
+
const fixerAgent = domain === 'doc' ? 'doc-reviewer' : 'coder'
|
|
74
|
+
|
|
75
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
76
|
+
// Phase Diagnose — branch by kind.
|
|
77
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
78
|
+
phase('Diagnose')
|
|
79
|
+
|
|
80
|
+
// ---- baseline-fail (E2): try to make the trunk build; fatal if irrecoverable ---
|
|
81
|
+
if (kind === 'baseline-fail') {
|
|
82
|
+
const r = await agent(
|
|
83
|
+
`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).`,
|
|
84
|
+
{ label: `resolve:baseline:${card}`, phase: 'Diagnose', agentType: 'coder', schema: FIX_SCHEMA }
|
|
85
|
+
)
|
|
86
|
+
if (r && r.verified) { log('baseline recovered.'); return { status: 'resolved' } }
|
|
87
|
+
log('baseline irrecoverable — batch fatal.')
|
|
88
|
+
return { status: 'fatal', reason: 'baseline build irrecoverable' }
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// ---- scope-expansion (Asse 2): deterministic boundary, no arbitrary threshold ---
|
|
92
|
+
if (kind === 'scope-expansion') {
|
|
93
|
+
// Integrate iff: within MAY-EDIT ownership AND not security/migration AND not a NEW AC.
|
|
94
|
+
const decide = await agent(
|
|
95
|
+
`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` +
|
|
96
|
+
`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` +
|
|
97
|
+
`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.`,
|
|
98
|
+
{ label: `resolve:scope:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
|
|
99
|
+
)
|
|
100
|
+
if (decide && decide.verified) { log('scope-expansion integrated within ownership.'); return { status: 'resolved' } }
|
|
101
|
+
return await materialiseFollowup('scope-expansion', (decide && decide.note) || 'outside ownership / new AC / protected domain')
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// ---- agent-crash (E1): retry → route to the right specialist; protected = never bypass --
|
|
105
|
+
if (kind === 'agent-crash') {
|
|
106
|
+
const r = await agent(
|
|
107
|
+
`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.`,
|
|
108
|
+
{ label: `resolve:crash:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
|
|
109
|
+
)
|
|
110
|
+
if (r && r.verified) return { status: 'resolved' }
|
|
111
|
+
return await materialiseFollowup('agent-crash', 'subagent crash unrecovered' + (protectedDomain ? ' (protected domain — not bypassed)' : ''))
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
115
|
+
// Asse 1 (ac-unmet | blocker | qa-fail | e2e-blocked | merge-blocker)
|
|
116
|
+
// Tier 1: targeted fix → re-verify. Tier 2: judged multi-attempt. Terminal: follow-up.
|
|
117
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
118
|
+
const gateHint = {
|
|
119
|
+
'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.`,
|
|
120
|
+
blocker: `Fix the blocking finding per ${REF}/codex-gate.md / ${REF}/completeness.md. Re-run the failing gate.`,
|
|
121
|
+
'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.`,
|
|
122
|
+
'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.`,
|
|
123
|
+
'merge-blocker': `Fix the verified BLOCKER/HIGH from the final review per ${REF}/final-review.md F.5. Re-verify.`,
|
|
124
|
+
}[kind] || `Fix the blocker and re-verify.`
|
|
125
|
+
|
|
126
|
+
phase('Repair')
|
|
127
|
+
let attempt = await agent(
|
|
128
|
+
`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).`,
|
|
129
|
+
{ label: `resolve:${kind}:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
|
|
130
|
+
)
|
|
131
|
+
if (attempt && attempt.verified) { log(`${kind} resolved (tier 1).`); return { status: 'resolved' } }
|
|
132
|
+
|
|
133
|
+
// Tier 2 — judged multi-attempt (parallel divergent fixes → judge → adversarial verify).
|
|
134
|
+
// Budget guard: skip the fan-out when the run is near its token target.
|
|
135
|
+
const canFanOut = !budget.total || budget.remaining() > 60000
|
|
136
|
+
if (canFanOut && !protectedDomain) {
|
|
137
|
+
phase('Repair')
|
|
138
|
+
const angles = [
|
|
139
|
+
'Fix at the root cause (change the underlying logic/contract).',
|
|
140
|
+
'Fix defensively at the call site (guard/validate without changing the contract).',
|
|
141
|
+
'Fix by reusing an existing utility/pattern instead of new code.',
|
|
142
|
+
]
|
|
143
|
+
const attempts = (await parallel(angles.map((angle, i) => () =>
|
|
144
|
+
agent(
|
|
145
|
+
`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.`,
|
|
146
|
+
{ label: `resolve:${kind}:${card}#${i + 1}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
|
|
147
|
+
).then((r) => ({ i: i + 1, r })).catch(() => null)
|
|
148
|
+
))).filter(Boolean)
|
|
149
|
+
|
|
150
|
+
const verified = attempts.filter((x) => x.r && x.r.verified)
|
|
151
|
+
if (verified.length) {
|
|
152
|
+
phase('Verify')
|
|
153
|
+
// Adversarial pick: judge which verified attempt is the soundest (refute the rest).
|
|
154
|
+
const judge = await agent(
|
|
155
|
+
`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(' | ')}`,
|
|
156
|
+
{ label: `resolve:judge:${card}`, phase: 'Verify', agentType: 'code-reviewer', schema: JUDGE_SCHEMA }
|
|
157
|
+
)
|
|
158
|
+
if (judge && judge.best > 0) { log(`${kind} resolved (tier 2, attempt #${judge.best}).`); return { status: 'resolved' } }
|
|
159
|
+
}
|
|
160
|
+
} else if (protectedDomain) {
|
|
161
|
+
log('protected domain — single specialist attempt only (no parallel fan-out).')
|
|
162
|
+
} else {
|
|
163
|
+
log('budget near target — skipping tier-2 fan-out.')
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Terminal — nothing resolved autonomously: materialise a tracked follow-up card.
|
|
167
|
+
return await materialiseFollowup(kind, attempt && attempt.note ? attempt.note : 'unresolved after repair tiers')
|
|
168
|
+
|
|
169
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
170
|
+
async function materialiseFollowup(k, reason) {
|
|
171
|
+
const r = await agent(
|
|
172
|
+
`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` +
|
|
173
|
+
`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.`,
|
|
174
|
+
{ label: `resolve:followup:${card}`, phase: 'Verify', agentType: 'general-purpose', schema: FOLLOWUP_SCHEMA }
|
|
175
|
+
)
|
|
176
|
+
const followupCard = (r && r.followupCard) || `${card}-followup`
|
|
177
|
+
log(`${k} → follow-up card ${followupCard} (nothing dropped).`)
|
|
178
|
+
return { status: 'followup', followupCard, reason }
|
|
179
|
+
}
|
|
@@ -0,0 +1,442 @@
|
|
|
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
|
+
],
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
14
|
+
// args contract — supplied by the new2 skill (it owns config-read + invocation):
|
|
15
|
+
// cardIds string[] expanded card ids (ranges/-full resolved by skill)
|
|
16
|
+
// cardPaths string[] resolved backlog YAML paths (agents Read them)
|
|
17
|
+
// mainRepo string absolute path of the main repo (not a worktree)
|
|
18
|
+
// trunk string resolved integration trunk (git.trunk_branch)
|
|
19
|
+
// metricsDir string paths.metrics (telemetry dir)
|
|
20
|
+
// refModulesBase string dir holding /new reference modules (semantic SSOT)
|
|
21
|
+
// config object parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
|
|
22
|
+
// flags { stats, effort, full }
|
|
23
|
+
//
|
|
24
|
+
// return (consumed by the skill at Step 5):
|
|
25
|
+
// { report, perCardResults, gateLedger, residualFollowups, telemetry }
|
|
26
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
27
|
+
|
|
28
|
+
const a = args || {}
|
|
29
|
+
const cfg = a.config || {}
|
|
30
|
+
const cardIds = Array.isArray(a.cardIds) ? a.cardIds : []
|
|
31
|
+
const cardPaths = Array.isArray(a.cardPaths) ? a.cardPaths : []
|
|
32
|
+
const MAIN = a.mainRepo || '(cwd)'
|
|
33
|
+
const TRUNK = a.trunk || 'main'
|
|
34
|
+
const METRICS = a.metricsDir || 'docs/metrics'
|
|
35
|
+
const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
|
|
36
|
+
const FLAGS = a.flags || {}
|
|
37
|
+
const features = cfg.features || {}
|
|
38
|
+
const paths = cfg.paths || {}
|
|
39
|
+
const gitCfg = cfg.git || {}
|
|
40
|
+
const highRisk = paths.high_risk_modules || []
|
|
41
|
+
const mergeStrategy = gitCfg.merge_strategy || 'pr'
|
|
42
|
+
|
|
43
|
+
// Mutable batch state — the workflow's variables ARE the tracker (kept out of the
|
|
44
|
+
// main loop). Agents also write the on-disk /tmp/batch-tracker-<id>.md for recovery.
|
|
45
|
+
const firstCard = cardIds[0] || 'BATCH'
|
|
46
|
+
const gateLedger = [] // { card, gate, decision, detail }
|
|
47
|
+
const residualFollowups = []// { card, kind, followupCard, reason }
|
|
48
|
+
const perCardResults = [] // structured per-card outcome
|
|
49
|
+
|
|
50
|
+
function ledger(card, gate, decision, detail) {
|
|
51
|
+
gateLedger.push({ card, gate, decision, detail: detail || '' })
|
|
52
|
+
log(`gate[${card}] ${gate} → ${decision}${detail ? ' — ' + detail : ''}`)
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!cardIds.length) {
|
|
56
|
+
log('new2: no card IDs supplied — nothing to do.')
|
|
57
|
+
return emptyReturn('no cards supplied')
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
61
|
+
// Schemas
|
|
62
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
63
|
+
const PREFLIGHT_SCHEMA = {
|
|
64
|
+
type: 'object',
|
|
65
|
+
required: ['ok', 'worktreePath', 'branch', 'baseline', 'executionMode', 'cards'],
|
|
66
|
+
additionalProperties: false,
|
|
67
|
+
properties: {
|
|
68
|
+
ok: { type: 'boolean', description: 'false only when the workspace cannot be made workable (e.g. baseline irrecoverable)' },
|
|
69
|
+
worktreePath: { type: 'string' },
|
|
70
|
+
branch: { type: 'string' },
|
|
71
|
+
port: { type: ['number', 'string'], description: 'dev port or "-"' },
|
|
72
|
+
baseline: { enum: ['pass', 'fail'] },
|
|
73
|
+
baselineLog: { type: 'string' },
|
|
74
|
+
executionMode: { enum: ['sequential', 'team'] },
|
|
75
|
+
groups: { type: 'array', items: { type: 'object', additionalProperties: true }, description: 'team-mode waves: [{ level, cards:[ids] }]' },
|
|
76
|
+
cards: { type: 'array', items: { type: 'string' }, description: 'card ids cleared to run (invalid/blocked-dep excluded)' },
|
|
77
|
+
excluded: { type: 'array', items: { type: 'object', additionalProperties: true }, description: '[{ card, reason }] for G4/G5 exclusions' },
|
|
78
|
+
ownershipMapPath: { type: 'string', description: '/tmp path to the file-ownership map' },
|
|
79
|
+
crossCard: { type: 'string', description: 'distilled cross-card findings or PASS/SKIPPED/UNAVAILABLE' },
|
|
80
|
+
workspaceNote: { type: 'string', description: 'G1/G2 distilled (stash label, ff-pull, divergence left + reported)' },
|
|
81
|
+
archBaselinePaths: { type: 'array', items: { type: 'string' }, description: 'per-card /tmp/arch-baseline-<id>.md (for final-review dedup)' },
|
|
82
|
+
},
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
const MERGE_SCHEMA = {
|
|
86
|
+
type: 'object', required: ['merged'], additionalProperties: false,
|
|
87
|
+
properties: {
|
|
88
|
+
merged: { type: 'boolean' },
|
|
89
|
+
mergeCommit: { type: 'string' },
|
|
90
|
+
mergeTs: { type: 'string' },
|
|
91
|
+
reconciliation: { type: 'string', description: 'Phase 6b/6c distilled (divergence left+reported, stash restore, sync markers)' },
|
|
92
|
+
note: { type: 'string' },
|
|
93
|
+
},
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// Shared context fragment every per-card agent gets.
|
|
97
|
+
const projectBrief = [
|
|
98
|
+
`Main repo: ${MAIN}`,
|
|
99
|
+
`Trunk: ${TRUNK}`,
|
|
100
|
+
`Reference modules (Read these for the EXACT /new semantics — this workflow only sequences them): ${REF}/`,
|
|
101
|
+
`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 || '?'}.`,
|
|
102
|
+
FLAGS.effort ? `Reasoning effort for this run: ${FLAGS.effort}.` : '',
|
|
103
|
+
].filter(Boolean).join('\n')
|
|
104
|
+
|
|
105
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
106
|
+
// Phase Pre-flight (Phase 0 + Pre-flight) — ONE ops agent runs all the git-heavy,
|
|
107
|
+
// deterministic workspace work (the script itself has no shell). Gate policies:
|
|
108
|
+
// G1 dirty-tree → auto-stash (restore at merge); restore-conflict → leave+report
|
|
109
|
+
// G2 divergence → behind: ff-pull; ahead/diverged: proceed on isolated worktree + report
|
|
110
|
+
// G3 cross-card cap unavailable → proceed + log CAPABILITY_UNAVAILABLE
|
|
111
|
+
// G4 card-field invalid → exclude that card + continue
|
|
112
|
+
// G5 depends-on (dep outside batch, not DONE) → exclude card + its dependents
|
|
113
|
+
// E2 baseline build fail → retry once inside the agent; surface baseline:fail
|
|
114
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
115
|
+
phase('Pre-flight')
|
|
116
|
+
const preflight = await agent(
|
|
117
|
+
`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` +
|
|
118
|
+
`${projectBrief}\n\nCards in batch (Read each YAML):\n${cardPaths.join('\n')}\nCard IDs: ${cardIds.join(' ')}\n\n` +
|
|
119
|
+
`Create/maintain the recovery tracker at /tmp/batch-tracker-${firstCard}.md (per setup.md § Context Tracking).\n\n` +
|
|
120
|
+
`DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
|
|
121
|
+
`• 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` +
|
|
122
|
+
`• 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` +
|
|
123
|
+
`• 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.\n` +
|
|
124
|
+
`• 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` +
|
|
125
|
+
`• 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 its in-batch dependents (excluded[], reason 'depends_on <dep> not DONE'). If the dep IS in this batch, keep it and ensure ordering.\n` +
|
|
126
|
+
`• 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` +
|
|
127
|
+
`• 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` +
|
|
128
|
+
`• Persist per-card architecture baselines you gather to /tmp/arch-baseline-<CARD>.md and return archBaselinePaths (final review reuses them).\n\n` +
|
|
129
|
+
`Return the structured PREFLIGHT object. ok:false ONLY if the workspace is unworkable (baseline irrecoverable / worktree uncreatable).`,
|
|
130
|
+
{ label: 'preflight', phase: 'Pre-flight', agentType: 'general-purpose', schema: PREFLIGHT_SCHEMA }
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
if (!preflight || preflight.ok === false || preflight.baseline === 'fail') {
|
|
134
|
+
// E2 terminal — the only batch-fatal case: no workable tree.
|
|
135
|
+
ledger(firstCard, 'E2-baseline', 'BATCH-FATAL', (preflight && preflight.baselineLog) || 'workspace unworkable')
|
|
136
|
+
return finalReturn({ fatal: true, reason: 'baseline build irrecoverable — see baselineLog' })
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
for (const ex of preflight.excluded || []) ledger(ex.card, 'preflight-exclude', 'EXCLUDED', ex.reason)
|
|
140
|
+
if (preflight.workspaceNote) ledger(firstCard, 'G1/G2-workspace', 'AUTO', preflight.workspaceNote)
|
|
141
|
+
if (preflight.crossCard) ledger(firstCard, 'G3-cross-card', 'INFO', preflight.crossCard)
|
|
142
|
+
|
|
143
|
+
const runnableCards = preflight.cards || []
|
|
144
|
+
const sharedCtx = {
|
|
145
|
+
worktreePath: preflight.worktreePath,
|
|
146
|
+
branch: preflight.branch,
|
|
147
|
+
ownershipMapPath: preflight.ownershipMapPath,
|
|
148
|
+
archBaselinePaths: preflight.archBaselinePaths || [],
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
152
|
+
// new2-resolve bridge — every blocking gate and every scope-expanding finding
|
|
153
|
+
// goes here (Asse 1/2/3). Returns 'resolved' | 'followup' | 'fatal'.
|
|
154
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
155
|
+
async function resolve(kind, card, evidence, extra) {
|
|
156
|
+
const res = await workflow('new2-resolve', {
|
|
157
|
+
kind,
|
|
158
|
+
cardId: card,
|
|
159
|
+
evidence,
|
|
160
|
+
worktreePath: sharedCtx.worktreePath,
|
|
161
|
+
mayEditPaths: (extra && extra.mayEditPaths) || [],
|
|
162
|
+
scopeFiles: (extra && extra.scopeFiles) || [],
|
|
163
|
+
domain: (extra && extra.domain) || 'code',
|
|
164
|
+
refModulesBase: REF,
|
|
165
|
+
config: cfg,
|
|
166
|
+
}).catch((e) => ({ status: 'followup', reason: 'resolve workflow error: ' + (e && e.message) }))
|
|
167
|
+
const status = (res && res.status) || 'followup'
|
|
168
|
+
if (status === 'followup' && res && res.followupCard) {
|
|
169
|
+
residualFollowups.push({ card, kind, followupCard: res.followupCard, reason: res.reason || '' })
|
|
170
|
+
}
|
|
171
|
+
ledger(card, 'resolve:' + kind, status, (res && (res.followupCard || res.reason)) || '')
|
|
172
|
+
return status
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
176
|
+
// Per-card pipeline — sequences /new Phases 1→4 via agents that Read the modules.
|
|
177
|
+
// Each phase agent returns a structured verdict; this function applies the
|
|
178
|
+
// deterministic gate policy and routes blocks to new2-resolve.
|
|
179
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
180
|
+
async function runCard(cardId, cardPath, lessons) {
|
|
181
|
+
const gates = []
|
|
182
|
+
const tele = {}
|
|
183
|
+
function g(name, decision, detail) { gates.push({ gate: name, decision, detail: detail || '' }); ledger(cardId, name, decision, detail) }
|
|
184
|
+
|
|
185
|
+
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.`
|
|
186
|
+
|
|
187
|
+
// --- Phase 1+2: claim, ground, implement (coder), completeness; returns the
|
|
188
|
+
// completion verdict + the AC ledger + the file diff. Self-heals build gates
|
|
189
|
+
// (G26) up to the module's retry cap before reporting unresolved. ---
|
|
190
|
+
const impl = await agent(
|
|
191
|
+
`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` +
|
|
192
|
+
`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` +
|
|
193
|
+
`Return: { epic:boolean, buildBlocked:boolean, blockedGate:string, unmetACs:[{n,text}], scopeFiles:[...], mayEditPaths:[...], fileDiffViolation:boolean (already auto-reverted per 11b), note }`,
|
|
194
|
+
{ label: `implement:${cardId}`, phase: 'Implement', agentType: 'general-purpose',
|
|
195
|
+
schema: { type: 'object', required: ['epic', 'buildBlocked', 'unmetACs', 'scopeFiles'], additionalProperties: true,
|
|
196
|
+
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' } } } }
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
if (impl && impl.epic) { g('router', 'EPIC-SKIPPED', 'epic card — not implemented'); return { card: cardId, status: 'epic-skipped', gates, commit: '-', telemetry: tele } }
|
|
200
|
+
|
|
201
|
+
const mayEdit = (impl && impl.mayEditPaths) || []
|
|
202
|
+
const scopeFiles = (impl && impl.scopeFiles) || []
|
|
203
|
+
|
|
204
|
+
// G26 — build gates not converging → self-heal.
|
|
205
|
+
if (impl && impl.buildBlocked) {
|
|
206
|
+
const s = await resolve('blocker', cardId, `Phase-2 gate failing: ${impl.blockedGate}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'code' })
|
|
207
|
+
g('G26-build', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP', impl.blockedGate)
|
|
208
|
+
if (s !== 'resolved') return { card: cardId, status: 'followup', gates, commit: '-', scopeFiles, telemetry: tele }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// G6+G7 — AC-Closure: every unmet AC must be implemented (never silently deferred).
|
|
212
|
+
for (const ac of (impl && impl.unmetACs) || []) {
|
|
213
|
+
const s = await resolve('ac-unmet', cardId, `AC-${ac.n}: ${ac.text}`, { mayEditPaths: mayEdit, scopeFiles, domain: 'code' })
|
|
214
|
+
g('G7-ac-closure', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP', `AC-${ac.n}`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// --- Phases 2.55 / 2.6 / 3 / 3.5 / 3.7 — review fan-out. One agent runs the
|
|
218
|
+
// review pipeline per the modules and returns each gate's verdict + any
|
|
219
|
+
// scope-expanding findings. Auto-revert gates (G9/G12) are handled inside. ---
|
|
220
|
+
const review = await agent(
|
|
221
|
+
`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` +
|
|
222
|
+
`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` +
|
|
223
|
+
`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' }`,
|
|
224
|
+
{ label: `review:${cardId}`, phase: 'Implement', agentType: 'general-purpose',
|
|
225
|
+
schema: { type: 'object', required: ['blocks', 'scopeExpansion'], additionalProperties: true,
|
|
226
|
+
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' } } } }
|
|
227
|
+
)
|
|
228
|
+
|
|
229
|
+
if (review) {
|
|
230
|
+
if (review.simplify) g('G9-simplify', review.simplify === 'reverted' ? 'AUTO-REVERT' : 'OK', review.simplify)
|
|
231
|
+
if (review.e2eStatus) g('G10/G11-e2e', review.e2eStatus === 'error' ? 'FLAG' : review.e2eStatus.toUpperCase())
|
|
232
|
+
if (review.qaFirstAttempt) tele.qa_first_attempt = review.qaFirstAttempt
|
|
233
|
+
if (review.docGaps) tele.doc_gaps = review.docGaps
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Route blocking review failures (G8/G10/G13/G14/G15) to new2-resolve.
|
|
237
|
+
let cardBlocked = false
|
|
238
|
+
for (const b of (review && review.blocks) || []) {
|
|
239
|
+
const kind = b.gate && /e2e/i.test(b.gate) ? 'e2e-blocked' : (b.gate && /qa/i.test(b.gate) ? 'qa-fail' : 'blocker')
|
|
240
|
+
const s = await resolve(kind, cardId, `${b.gate}: ${b.evidence || ''}`, { mayEditPaths: mayEdit, scopeFiles, domain: b.domain || 'code' })
|
|
241
|
+
g(b.gate || 'review-block', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP', b.evidence || '')
|
|
242
|
+
if (s !== 'resolved') cardBlocked = true
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Scope-expansion findings (Asse 2) — deterministic boundary inside new2-resolve.
|
|
246
|
+
for (const sx of (review && review.scopeExpansion) || []) {
|
|
247
|
+
const s = await resolve('scope-expansion', cardId, sx.evidence || '', { mayEditPaths: mayEdit, scopeFiles, domain: sx.domain || 'code' })
|
|
248
|
+
g('scope-expansion', s === 'resolved' ? 'INTEGRATED' : 'FOLLOWUP', sx.evidence || '')
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (cardBlocked) return { card: cardId, status: 'followup', gates, commit: '-', scopeFiles, archBaselinePath: `/tmp/arch-baseline-${cardId}.md`, telemetry: tele }
|
|
252
|
+
|
|
253
|
+
// --- Phase 4 — commit (G16: auto-clear COMMIT_LOCK + retry; persistent → resolve). ---
|
|
254
|
+
const commitRes = await agent(
|
|
255
|
+
`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` +
|
|
256
|
+
`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` +
|
|
257
|
+
`Return: { committed:boolean, commit:string, filesChanged:[...] }`,
|
|
258
|
+
{ label: `commit:${cardId}`, phase: 'Implement', agentType: 'general-purpose',
|
|
259
|
+
schema: { type: 'object', required: ['committed'], additionalProperties: true, properties: { committed: { type: 'boolean' }, commit: { type: 'string' }, filesChanged: { type: 'array', items: { type: 'string' } } } } }
|
|
260
|
+
)
|
|
261
|
+
|
|
262
|
+
if (!commitRes || !commitRes.committed) {
|
|
263
|
+
const s = await resolve('blocker', cardId, 'commit blocked after retries', { mayEditPaths: mayEdit, scopeFiles, domain: 'code' })
|
|
264
|
+
g('G16-commit', s === 'resolved' ? 'RESOLVED' : 'FOLLOWUP')
|
|
265
|
+
if (s !== 'resolved') return { card: cardId, status: 'followup', gates, commit: '-', scopeFiles, archBaselinePath: `/tmp/arch-baseline-${cardId}.md`, telemetry: tele }
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
g('commit', 'COMMITTED', (commitRes && commitRes.commit) || '')
|
|
269
|
+
return {
|
|
270
|
+
card: cardId, status: 'committed',
|
|
271
|
+
commit: (commitRes && commitRes.commit) || '-',
|
|
272
|
+
filesChanged: (commitRes && commitRes.filesChanged) || [],
|
|
273
|
+
scopeFiles, archBaselinePath: `/tmp/arch-baseline-${cardId}.md`,
|
|
274
|
+
gates, telemetry: tele,
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
279
|
+
// Phase Implement — driver: sequential or team-mode (parallel coders per wave).
|
|
280
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
281
|
+
phase('Implement')
|
|
282
|
+
const lessons = []
|
|
283
|
+
const pathById = {}
|
|
284
|
+
cardIds.forEach((id, i) => { pathById[id] = cardPaths[i] || `${paths.backlog_dir || ''}/${id}.yml` })
|
|
285
|
+
|
|
286
|
+
if (preflight.executionMode === 'team' && Array.isArray(preflight.groups) && preflight.groups.length) {
|
|
287
|
+
// Waves run in order (a higher level waits for lower levels); cards within a wave
|
|
288
|
+
// run concurrently — parallel() keeps every coder's bloat inside the runtime.
|
|
289
|
+
const sortedGroups = preflight.groups.slice().sort((x, y) => (x.level || 0) - (y.level || 0))
|
|
290
|
+
for (const grp of sortedGroups) {
|
|
291
|
+
const ids = (grp.cards || []).filter((id) => runnableCards.includes(id))
|
|
292
|
+
if (!ids.length) continue
|
|
293
|
+
log(`Team wave level=${grp.level}: ${ids.length} card(s) in parallel.`)
|
|
294
|
+
const waveResults = await parallel(ids.map((id) => () => runCard(id, pathById[id], lessons).catch((e) => ({ card: id, status: 'followup', gates: [{ gate: 'runCard', decision: 'ERROR', detail: String(e && e.message) }], commit: '-', telemetry: {} }))))
|
|
295
|
+
for (const r of waveResults.filter(Boolean)) { perCardResults.push(r); if (r.note) lessons.push(`${r.card}: ${r.note}`) }
|
|
296
|
+
}
|
|
297
|
+
} else {
|
|
298
|
+
// Sequential — cards share one worktree and run one at a time.
|
|
299
|
+
for (const id of runnableCards) {
|
|
300
|
+
const r = await runCard(id, pathById[id], lessons).catch((e) => ({ card: id, status: 'followup', gates: [{ gate: 'runCard', decision: 'ERROR', detail: String(e && e.message) }], commit: '-', telemetry: {} }))
|
|
301
|
+
perCardResults.push(r)
|
|
302
|
+
if (r && r.note) lessons.push(`${r.card}: ${r.note}`)
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
const committed = perCardResults.filter((r) => r.status === 'committed')
|
|
307
|
+
|
|
308
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
309
|
+
// Phase Final — cross-batch final review (reuse new-final-review). F.5 user gates
|
|
310
|
+
// become deterministic: G17 needs-manual → treat as VERIFIED (the workflow already
|
|
311
|
+
// drops only FALSE_POSITIVE); G18 unresolved BLOCKER/HIGH → new2-resolve, else
|
|
312
|
+
// block the merge.
|
|
313
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
314
|
+
phase('Final')
|
|
315
|
+
let finalSummary = null
|
|
316
|
+
let mergeBlocked = false
|
|
317
|
+
if (committed.length) {
|
|
318
|
+
const reviewScopeFiles = dedupe(committed.flatMap((r) => r.scopeFiles || []))
|
|
319
|
+
const archPaths = committed.map((r) => r.archBaselinePath).filter(Boolean)
|
|
320
|
+
const allArch = archPaths.length === committed.length ? archPaths : null
|
|
321
|
+
const hasApiDataFiles = reviewScopeFiles.some((f) => /api\/|data-model|\.sql$|migrations?\//i.test(f)) || reviewScopeFiles.length > 0
|
|
322
|
+
|
|
323
|
+
const final = await workflow('new-final-review', {
|
|
324
|
+
firstCardId: firstCard,
|
|
325
|
+
worktreePath: sharedCtx.worktreePath,
|
|
326
|
+
baseBranch: TRUNK,
|
|
327
|
+
cardPaths: committed.map((r) => pathById[r.card]).filter(Boolean),
|
|
328
|
+
reviewScopeFiles,
|
|
329
|
+
archBaselinePaths: allArch,
|
|
330
|
+
hasApiDataFiles,
|
|
331
|
+
config: cfg,
|
|
332
|
+
}).catch(() => null)
|
|
333
|
+
|
|
334
|
+
if (final) {
|
|
335
|
+
finalSummary = final.summary || null
|
|
336
|
+
ledger(firstCard, 'final-review', 'DONE', `engine=${final.codexEngine}; verified=${final.summary ? final.summary.verified : '?'}`)
|
|
337
|
+
// G18 — apply verified BLOCKER/HIGH fixes; unresolved → block merge.
|
|
338
|
+
const blockers = (final.findings || []).filter((f) => f.classification === 'VERIFIED' && (f.severity === 'BLOCKER' || f.severity === 'HIGH'))
|
|
339
|
+
for (const f of blockers) {
|
|
340
|
+
const s = await resolve('merge-blocker', f.finding_id || firstCard, `${f.severity} ${f.title}: ${f.evidence}`, { scopeFiles: reviewScopeFiles, domain: f.domain || 'code' })
|
|
341
|
+
if (s !== 'resolved') mergeBlocked = true
|
|
342
|
+
}
|
|
343
|
+
if ((final.summary && final.summary.failingGates && final.summary.failingGates.length)) {
|
|
344
|
+
const s = await resolve('qa-fail', firstCard, `final gates failing: ${final.summary.failingGates.join(', ')}`, { scopeFiles: reviewScopeFiles, domain: 'code' })
|
|
345
|
+
if (s !== 'resolved') mergeBlocked = true
|
|
346
|
+
}
|
|
347
|
+
} else {
|
|
348
|
+
ledger(firstCard, 'final-review', 'SKIPPED', 'workflow returned null')
|
|
349
|
+
}
|
|
350
|
+
} else {
|
|
351
|
+
ledger(firstCard, 'final-review', 'SKIPPED', 'no committed cards')
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
355
|
+
// Phase Merge — auto-merge to trunk (G24) + Phase 6b/6c reconciliation (conservative
|
|
356
|
+
// git policies: G19–G23). Skipped when a final BLOCKER/HIGH is unresolved.
|
|
357
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
358
|
+
phase('Merge')
|
|
359
|
+
let mergeResult = null
|
|
360
|
+
if (!committed.length) {
|
|
361
|
+
ledger(firstCard, 'merge', 'SKIPPED', 'no committed cards')
|
|
362
|
+
} else if (mergeBlocked) {
|
|
363
|
+
ledger(firstCard, 'merge', 'BLOCKED', 'unresolved final BLOCKER/HIGH — worktree left intact for review')
|
|
364
|
+
} else {
|
|
365
|
+
mergeResult = await agent(
|
|
366
|
+
`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` +
|
|
367
|
+
`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` +
|
|
368
|
+
`Return: { merged:boolean, mergeCommit, mergeTs, reconciliation, note }`,
|
|
369
|
+
{ label: 'merge', phase: 'Merge', agentType: 'general-purpose', schema: MERGE_SCHEMA }
|
|
370
|
+
)
|
|
371
|
+
ledger(firstCard, 'G24-merge', (mergeResult && mergeResult.merged) ? 'MERGED' : 'INCOMPLETE', (mergeResult && (mergeResult.mergeCommit || mergeResult.note)) || '')
|
|
372
|
+
if (mergeResult && mergeResult.reconciliation) ledger(firstCard, 'G19-23-reconcile', 'AUTO', mergeResult.reconciliation)
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
376
|
+
// Return — assemble the report + Phase-8 telemetry (variant new2).
|
|
377
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
378
|
+
return finalReturn({ fatal: false })
|
|
379
|
+
|
|
380
|
+
// ───────────────────────────────────────────────────────────────────────────
|
|
381
|
+
function finalReturn(opts) {
|
|
382
|
+
const fatal = opts && opts.fatal
|
|
383
|
+
const telemetry = buildTelemetry()
|
|
384
|
+
const report = buildReport({ fatal, reason: opts && opts.reason, finalSummary, mergeResult })
|
|
385
|
+
return { report, perCardResults, gateLedger, residualFollowups, telemetry }
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
function buildTelemetry() {
|
|
389
|
+
const total = cardIds.length
|
|
390
|
+
const done = perCardResults.filter((r) => r.status === 'committed').length
|
|
391
|
+
const fixCycles = gateLedger.filter((x) => /resolve:/.test(x.gate)).length
|
|
392
|
+
return {
|
|
393
|
+
variant: 'new2',
|
|
394
|
+
batch: cardIds,
|
|
395
|
+
first_card: firstCard,
|
|
396
|
+
cards_total: total,
|
|
397
|
+
cards_committed: done,
|
|
398
|
+
cards_followup: perCardResults.filter((r) => r.status === 'followup').length,
|
|
399
|
+
followups_created: residualFollowups.length,
|
|
400
|
+
resolve_invocations: fixCycles,
|
|
401
|
+
final_review: finalSummary || null,
|
|
402
|
+
merged: !!(mergeResult && mergeResult.merged),
|
|
403
|
+
execution_mode: preflight ? preflight.executionMode : 'sequential',
|
|
404
|
+
per_card: perCardResults.map((r) => ({ card: r.card, status: r.status, telemetry: r.telemetry || {} })),
|
|
405
|
+
stats_requested: !!FLAGS.stats,
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
function buildReport(o) {
|
|
410
|
+
const L = []
|
|
411
|
+
L.push(`# new2 batch — ${cardIds.join(' ')}`)
|
|
412
|
+
L.push(`Variant: **new2** (workflow-hosted) · Mode: ${preflight ? preflight.executionMode : '?'} · Trunk: ${TRUNK}`)
|
|
413
|
+
if (o.fatal) { L.push(``, `## ⛔ BATCH FATAL`, o.reason || 'workspace unworkable'); return L.join('\n') }
|
|
414
|
+
L.push(``, `## Esito card`)
|
|
415
|
+
L.push(`| Card | Status | Commit | File |`)
|
|
416
|
+
L.push(`|------|--------|--------|------|`)
|
|
417
|
+
for (const r of perCardResults) L.push(`| ${r.card} | ${r.status} | ${r.commit || '-'} | ${(r.filesChanged || []).length} |`)
|
|
418
|
+
if (finalSummary) {
|
|
419
|
+
L.push(``, `## Final review`)
|
|
420
|
+
L.push(`Verified: ${finalSummary.verified} (BLOCKER ${finalSummary.blockers} / HIGH ${finalSummary.highs}) · needs-manual ${finalSummary.needsManual} · failing gates: ${(finalSummary.failingGates || []).join(', ') || 'none'}`)
|
|
421
|
+
}
|
|
422
|
+
L.push(``, `## Merge`)
|
|
423
|
+
if (mergeResult && mergeResult.merged) L.push(`Merged → ${TRUNK} @ ${mergeResult.mergeCommit || '?'}${mergeResult.reconciliation ? ' · ' + mergeResult.reconciliation : ''}`)
|
|
424
|
+
else if (mergeBlocked) L.push(`Merge BLOCKED — unresolved final BLOCKER/HIGH. Worktree left intact at ${sharedCtx.worktreePath}.`)
|
|
425
|
+
else L.push(`Not merged (no committed cards or incomplete).`)
|
|
426
|
+
if (residualFollowups.length) {
|
|
427
|
+
L.push(``, `## ⚠️ Follow-up auto-creati (residui non risolti autonomamente — nulla perso)`)
|
|
428
|
+
for (const f of residualFollowups) L.push(`- **${f.followupCard}** ← ${f.card} (${f.kind})${f.reason ? ': ' + f.reason : ''}`)
|
|
429
|
+
}
|
|
430
|
+
const excluded = gateLedger.filter((x) => x.decision === 'EXCLUDED')
|
|
431
|
+
if (excluded.length) {
|
|
432
|
+
L.push(``, `## Card escluse in pre-flight`)
|
|
433
|
+
for (const e of excluded) L.push(`- ${e.card}: ${e.detail}`)
|
|
434
|
+
}
|
|
435
|
+
L.push(``, `_Gate ledger: ${gateLedger.length} decisioni deterministiche, 0 AskUserQuestion._`)
|
|
436
|
+
return L.join('\n')
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function dedupe(arr) { return Array.from(new Set(arr)) }
|
|
440
|
+
function emptyReturn(reason) {
|
|
441
|
+
return { report: `# new2 — no-op\n${reason}`, perCardResults: [], gateLedger: [], residualFollowups: [], telemetry: { variant: 'new2', cards_total: 0, reason } }
|
|
442
|
+
}
|
|
@@ -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
|
|