baldart 4.52.4 → 4.53.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,32 @@ 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.53.1] - 2026-06-18
9
+
10
+ **Review workflows no longer spawn fixers for verified-but-no-action findings, and a `finding_id` collision that turned applied fixes into false residuals is closed.** Diagnosed from a real `/new FEAT-0035` team-mode run where the per-wave review cluster spawned `security-reviewer` (Sonnet) + `coder` (Opus) for ~110k tokens to apply ~3 LOW fixes and **no-op ~8 non-actionable findings** — finders (simplify / security / codex / api-perf / doc) emit *verified observations that need NO change* (`minimal_fix_direction` = "No fix required" / "acceptable as-is" / "verified non-issue") as findings; these survive the false-positive check (they are TRUE, not false positives), are `preValidated` → skip Verify → classified `VERIFIED` → flood the fix partitions. The system conflated **VERIFIED with actionable**. Fix (two fail-safe layers): the `FINDING` schema gains an optional `requires_action` flag, every finder prompt is told to set it `false` (or simply not emit) a no-change observation, and a deterministic `isActionable()` gate — honoured for MED/LOW only, a **BLOCKER/HIGH is always actionable** so a cross-wave merge-ordering HIGH still surfaces as residual — segregates no-action findings into a recorded/counted bucket (`summary.noAction`, `decision=skipped reason=no-action`) that **never reaches a writer and is never silently dropped**. A regex backstop on the fix direction covers a finder that omits the flag. **Second defect (root cause of an observed empty-coder re-spawn):** finders number `finding_id` (`F###`) independently, so a security `F003` and a migration `F003` collide; the Fix-phase bookkeeping (`appliedIds`/`unresolvedIds`/`codeResidual`/`bucket`) keys on `finding_id` with flat Sets, so one finder's `unresolved` id dragged another finder's *applied* same-id fix into residual → the skill then re-spawned a coder that found nothing to do. Fixed by making `finding_id` globally unique at the fan-in (prefix `source` + index). Verified with a deterministic test that extracts the real gate from source and runs it over the actual FEAT-0035 findings (4 no-action LOW suppressed incl. a mid-prose "no fix required", 3 real LOW + the cross-wave HIGH kept) plus a collision regression (bug reproduces pre-fix, gone post-fix). Applied to **both** review workflows + their prose-SSOT (so the inline fallback benefits) + (opt-in) the finder agents' system prompts. **PATCH** (economy/quality refinement of existing workflows; no new agent/skill/command, no install-layout change, no `baldart.config.yml` key).
11
+
12
+ ### Fixed
13
+
14
+ - **`framework/.claude/workflows/new-card-review.js`** — `FINDING` schema gains `requires_action`; finder prompts (codex / simplify / security / code-reviewer fallback) instructed to flag no-action observations; new `NO_ACTION_RE` + `isNoAction()` gate (MED/LOW only) segregates a `noAction` partition excluded from `securityFix`/`actionable`/`docResidual` and logged; `finding_id` made globally unique at the fan-in (fixes the cross-finder `appliedIds`/`unresolvedIds`/`codeResidual` contamination); coder fix-pass prompt clarifies `applied` = edit made even if the build stays red for out-of-scope reasons, `unresolved` = the edit itself could not be made; `summary.noAction` added.
15
+ - **`framework/.claude/workflows/new-final-review.js`** — same `requires_action` schema field + finder-prompt instruction (codex / doc / api-perf / code-reviewer fallback) + `finding_id` uniqueness at the fan-in; no-action VERIFIED MED/LOW findings segregated out of the returned `findings` into a new `noActionFindings` field + `summary.noAction`, so the skill's F.5 writer partition never routes them.
16
+ - **`framework/.claude/skills/new/references/review-cycle.md`** / **`final-review.md`** — drift-note + Phase-2.55 step-4 / F.4 classification + F.5 partition updated to document the actionability gate and the `finding_id` uniqueness (the inline fallback path mirrors the workflow). `codex-gate.md` unchanged (its per-card gate only auto-fixes BLOCKER/HIGH, which are never no-action).
17
+ - **`framework/.claude/agents/code-reviewer.md`**, **`api-perf-cost-auditor.md`**, **`security-reviewer.md`**, **`doc-reviewer.md`** — an Actionability clause after the FP challenge: a verified-safe / already-accurate observation is a cleared concern, not an actionable finding (set `requires_action:false` or omit; MED/LOW only) — defense-in-depth that also benefits standalone `/codexreview`, `/prd`, `/bug`.
18
+ - **`framework/.claude/workflows/new2.js`** / **`new2-resolve.js`** — unchanged (justified): they route only BLOCKER/HIGH findings, which the severity invariant always keeps actionable, so no-action MED/LOW never reach them; they group by file/area, not `finding_id`.
19
+
20
+ ## [4.53.0] - 2026-06-18
21
+
22
+ **Worktree setup for `/new` / `/nw` / `new2` is now a deterministic SSOT script — no model in the loop for pure plumbing.** Diagnosed from a real `/new FEAT-0036 -full` run where the Phase-0 worktree-setup subagent **created the worktree but went idle before `npm install`** (node_modules missing, no registry entry), forcing the orchestrator to burn turns probing → messaging the agent → waiting → cleaning the half-built orphan → falling back to inline `/nw` (which reloads the ~1200-line skill body into the orchestrator — the exact cost the delegation was meant to avoid). Same root cause as the earlier haiku *fabrication* (a well-formed `baseline: pass` with nothing on disk, 2/2): a **model in the loop for mechanical work** — it either fabricates the expected return or stalls mid-execution. The fix removes the model: the entire build sequence (worktree add → sync untracked cards → install → copy `stack.env_files` → allocate port → write registry → baseline) now lives **once** in `framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh` and is invoked identically by all three callers as a background `Bash`. A deterministic script cannot fabricate or stall: it does the work or fails honestly with a log + a structured manifest. The orchestrator's disk-verification gate (setup.md step 6a) and git-authoritative idempotency pre-check (step 4a2) are unchanged — defence-in-depth survives the redesign. Non-interactive HARD (stdin closed, `CI=1`, build under `timeout 600`); port allocation is atomic under a `.worktrees/.wt-setup.lock` mutex (mirrors `allocate-id.sh`) so concurrent `/nw` runs never collide; the registry is written via `node` (atomic tmp+rename, no jq dependency); the script never self-repairs a failing baseline (role boundary — the coder does, downstream). Verified end-to-end on a throwaway repo: happy path (worktree + env+PORT + card-sync + `buildVerified:true` + manifest), collision (loud fail, exit 3), build-fail (`baseline:fail`, port held, `buildVerified:false`), and second-worktree port allocation (3001+3002 reserved → 3003). Opt-in-with-fallback: a pre-this-release subtree without the script falls back to inline `/nw`. **MINOR** (new shipped SSOT asset + behaviour change to existing skills; backward-compatible, no install-layout or command change, no `baldart.config.yml` key — `stack.env_files` / `toolchain.commands.*` are reused).
23
+
24
+ ### Added
25
+
26
+ - **`framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh`** — the single source of truth for code-worktree creation + baseline, consumed by `/nw`, `/new` (setup.md step 4), and `new2`. Resolves config (`$MAIN`/`$TRUNK`/`stack.env_files`/`toolchain.commands.*`/port range) from args with `baldart.config.yml` fallback; writes a stable manifest block (`status`/`error`/`worktree_path`/`branch`/`port`/`created_at`/`baseline`/`baseline_log`) on every exit path.
27
+
28
+ ### Changed
29
+
30
+ - **`framework/.claude/skills/new/references/setup.md`** — Phase-0 step 4 replaces the Sonnet-subagent-via-Skill delegation (+ its `/nw`-inline fallback chain) with a background launch of `setup-worktree.sh`; step 5 barrier + step 6a disk gate now read the manifest; fallback collapses to `script → inline /nw → HALT` (honest/deterministic failures are not fallback-eligible). `SKILL.md` worktree `Created:` note updated.
31
+ - **`framework/.claude/skills/worktree-manager/SKILL.md`** — `/nw` steps 3-6 (create/isolate/baseline/registry) collapsed to an invocation of `setup-worktree.sh` + a prose description of what it does; steps 7-8 read `worktree_path`/`port` from the manifest instead of in-process shell vars.
32
+ - **`framework/.claude/workflows/new2.js`** — the Pre-flight agent's worktree bullet now runs `setup-worktree.sh` instead of hand-rolling the sequence; it still independently verifies the worktree on disk for the (structurally weaker) E2.5 evidence gate, and falls back to the inline sequence if the script is absent.
33
+
8
34
  ## [4.52.4] - 2026-06-18
9
35
 
10
36
  **SSOT hygiene: `/cont` (context-primer) no longer restates the code-search tier hierarchy inline.** Follow-up to v4.52.3. The prompt template `context-primer` hands to `codebase-architect` re-stated the "prefer LSP/graph over grep for symbols" guidance inline (step 3a) instead of citing the protocol modules — so after v4.52.3 added the anti-flail rule to `code-search-protocol.md` / `code-graph-protocol.md` / the `codebase-architect` system prompt, that inline copy was the one spot that didn't carry it. Functionally harmless (the spawned `codebase-architect` already governs via its own system prompt + the protocols), but a latent drift surface. Trimmed step 3a to a citation of both protocol modules ("follow their tier order, budgets, and anti-flail rule — don't restate them here"), keeping context-primer's own task shaping (canonical-router-first, agent memory, backlog, git, verification) intact. Verified no other skill restates the hierarchy (`lsp-bootstrap`'s line is a user-facing confirmation, not a protocol copy). **PATCH** (doc/guidance hygiene; no behaviour change — the search behaviour was already governed by the agent + protocols; no `baldart.config.yml` key).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.52.4
1
+ 4.53.1
@@ -202,6 +202,10 @@ Consider:
202
202
 
203
203
  **Exception**: never-demote items above are never false positives in this project — do not suppress them.
204
204
 
205
+ ### Actionability Pass (after the FP challenge)
206
+
207
+ A finding that survives the FP challenge can still need NO change — the pattern is acceptable as-is (e.g. an admin path where the optimization is genuinely not worth it). That is a cleared concern, **not an actionable finding**: reporting it spawns a downstream fixer to no-op it. Either omit it, or — when a consumer schema exposes a `requires_action` field — set `requires_action:false` (recorded, never routed to a writer). Record cleared concerns under a separate "Verified, no action" list. Guard: MEDIUM/LOW only (a BLOCKER/HIGH is never no-action); a change needed but out of your scope is NOT no-action — report it (it surfaces as residual).
208
+
205
209
  ## Load Simulation Pass (MANDATORY — execute mentally before findings)
206
210
 
207
211
  Walk the changed handler/cron/query as if it were running under realistic production load. For each entry point in scope:
@@ -288,6 +288,17 @@ Consider:
288
288
 
289
289
  **Never-demote items above are never false positives — do not suppress them.**
290
290
 
291
+ ### Actionability Pass (after the FP challenge)
292
+
293
+ A finding that survives the FP challenge can still need NO change — the code is fine as-is, and the only honest "fix" is "no fix required" / "acceptable as-is" / "verified safe". That is **not a finding**: it is a cleared concern. Do NOT report it as an actionable finding (a downstream fixer would be spawned to no-op it). Either omit it, or — when a consumer schema exposes a `requires_action` field — set `requires_action:false` so it is recorded but never routed to a writer. Record cleared concerns separately:
294
+
295
+ <details>
296
+ <summary>Verified, no action (N items)</summary>
297
+ - **Title** — why it is safe as-is
298
+ </details>
299
+
300
+ Guard: this applies to MEDIUM/LOW only — a BLOCKER/HIGH is never "no action". And if a change IS needed but is out of your scope, that is NOT no-action: report it as a real finding (it will surface as residual).
301
+
291
302
  ## Diff Simulation Pass (MANDATORY — execute mentally before findings)
292
303
 
293
304
  Walk the diff as if you were the runtime. For each non-trivial changed function/handler:
@@ -233,6 +233,7 @@ Key points:
233
233
  - Fast Mode: verdict + one-liner only.
234
234
 
235
235
  ## Constraints
236
+ - **Actionability (when pooled as a finder, e.g. in the final-review fan-out):** a doc you VERIFIED is already accurate / up-to-date is a cleared concern, **not a finding** — do not emit it as a `LOW`/`MEDIUM` finding (a downstream fixer would be spawned to no-op it). Either omit it, or — when the pooled schema exposes a `requires_action` field — set `requires_action: false` (recorded, never routed to a writer). In your own write mode this is moot (you simply make no edit). A `BLOCKER`/`HIGH` is never no-action.
236
237
  - For **feature documentation**: WRITE missing docs directly. You are fully responsible -- do not defer to other agents.
237
238
  - For **general audits**: PROPOSE structural changes, but WRITE fixes for factual gaps, stale content, and missing entries.
238
239
  - **ALWAYS identify the macro feature** -- even for small bug fixes or one-off cards.
@@ -144,6 +144,13 @@ Map your severity labels to the pooled enum: Critical → `BLOCKER`, High → `H
144
144
  `# Security Review Summary` (scope / overall risk / main attack surfaces / most critical concern) so
145
145
  the orchestrator has a header. Do NOT emit the long markdown finding blocks in this mode.
146
146
 
147
+ **Actionability**: a control you have VERIFIED as safe-as-is (the honest recommendation is "no fix
148
+ required" / "verified safe") is a cleared concern, **not a finding** — emitting it as a `LOW`/`MEDIUM`
149
+ finding makes the orchestrator spawn a fixer to no-op it. Either omit it, or — when the pooled schema
150
+ exposes a `requires_action` field — set `requires_action: false` (recorded for the audit trail, never
151
+ routed to a writer). A `BLOCKER`/`HIGH` is never no-action; a fix needed but out of your ownership IS a
152
+ real finding (report it as residual, not as no-action).
153
+
147
154
  ### Mode B — Standalone review (when invoked directly by a human)
148
155
 
149
156
  When invoked directly (no pooling orchestrator), use this human-readable structure:
@@ -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 6c backfills from the worktree-setup subagent's created_at; Phase 8 cycle_time_mins start anchor]
83
+ Created: [ISO-8601 timestamp the worktree was created — Pre-flight step 6c backfills from the worktree-setup script's manifest 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]
@@ -124,7 +124,12 @@ that is a **gate violation**: log it as
124
124
  > *orchestration shape* of F.2–F.4; its agent briefs cite THIS module for the
125
125
  > review *semantics*, so the SSOT for what each reviewer checks stays here. When
126
126
  > you change the F.2–F.4 reviewer set, gates, or classification rules, update the
127
- > workflow's phase/agent wiring to match.
127
+ > workflow's phase/agent wiring to match. Since v4.53.1 the workflow also (a) makes every
128
+ > `finding_id` globally unique at the fan-in (finders number `F###` independently → a collision
129
+ > corrupts downstream finding_id-keyed routing/telemetry), and (b) runs an **actionability gate** that
130
+ > segregates VERIFIED-but-no-change findings (`requires_action:false` / regex backstop, MED/LOW only)
131
+ > into `noActionFindings` + `summary.noAction`, keeping them out of the returned `findings` the skill
132
+ > routes to writers in F.5. Keep both mirrored with the inline F.4 classification above.
128
133
 
129
134
  ### Step F.2 — Architecture baseline
130
135
 
@@ -189,6 +194,10 @@ that is a **gate violation**: log it as
189
194
  - minimal_fix_direction: what to change
190
195
  5. Run mandatory false-positive check: for each finding, ask 'What is the strongest
191
196
  argument this is a false positive?' Suppress if the FP argument is convincing.
197
+ 5.5 Actionability: an observation you have VERIFIED needs NO change anywhere (the honest direction
198
+ would be 'no fix required' / 'acceptable as-is' / 'verified non-issue') is not work — set
199
+ requires_action:false on it (recorded, never routed to a fixer), or simply do not report it.
200
+ This is for MEDIUM/LOW only; a BLOCKER/HIGH is never no-action.
192
201
  6. Classify surviving findings as VERIFIED, FALSE_POSITIVE, or NEEDS_MANUAL_CONFIRMATION.
193
202
 
194
203
  Return ONLY verified findings. If zero verified bugs: state 'No verified bugs found.'
@@ -248,12 +257,12 @@ that is a **gate violation**: log it as
248
257
  - **Each domain specialist OWNS its lane end-to-end** — it runs its OWN false-positive check in the finding pass (doc-reviewer for doc, api-perf-cost-auditor for api/perf/data, and the Codex/`code-reviewer`-fallback engine for code), so its surviving findings are **already validated**. Do **NOT** cross-validate a doc or api finding by spawning `code-reviewer` (that is the WRONG specialist judging another domain), and do **NOT** re-run the same specialist over its own findings (self-judge — no model diversity). Codex findings are likewise already FP-validated (Step F.3 protocol includes it).
249
258
  - **Residual only:** a finding the originating specialist explicitly leaves UNRESOLVED (`confidence < 80`) is routed to the finding's **domain specialist** (by `domain`: doc→doc-reviewer, api/perf→api-perf-cost-auditor, security/migration→security-reviewer, test→qa-sentinel, else code-reviewer) over the cited file:line. If that domain specialist **is** the originating finder (it already had its pass), classify `NEEDS_MANUAL_CONFIRMATION` instead of re-spawning it — never self-judge, never silently drop.
250
259
  - Classify: `VERIFIED` | `FALSE_POSITIVE` | `NEEDS_MANUAL_CONFIRMATION`.
251
- - `VERIFIED` findings proceed to fixes. **`NEEDS_MANUAL_CONFIRMATION` findings are NOT discarded** — list them in `## Issues & Flags` and surface them to the user via `AskUserQuestion` (treat as VERIFIED, treat as FALSE_POSITIVE, or hand off) before merge. Only `FALSE_POSITIVE` are dropped.
260
+ - `VERIFIED` findings proceed to fixes — **EXCEPT no-action ones** (`requires_action:false`, or a "no fix required" / "acceptable as-is" / "verified non-issue" direction; MEDIUM/LOW only). A no-action finding is TRUE but needs no change: record it (`decision=skipped | reason=no-action`) and do NOT route it to a writer. (The delegated `new-final-review` workflow returns these separately as `noActionFindings` + `summary.noAction`; the inline path classifies them here.) **`NEEDS_MANUAL_CONFIRMATION` findings are NOT discarded** — list them in `## Issues & Flags` and surface them to the user via `AskUserQuestion` (treat as VERIFIED, treat as FALSE_POSITIVE, or hand off) before merge. Only `FALSE_POSITIVE` are dropped.
252
261
 
253
262
  ### Step F.5 — Apply fixes and final build
254
263
 
255
264
  10. **Persist verified findings** to `/tmp/batch-final-review-<FIRST-CARD-ID>.md`.
256
- 11. **Merge-blocking gate (mirrors the per-card Phase 3.7 gate this final pass backstops):** if any VERIFIED **BLOCKER or HIGH** finding exists, it MUST be resolved before Phase 6 merge. Apply fixes by **domain owner** (since v3.40.0 — same Domain-Override routing as the per-card phases), then re-verify; if a BLOCKER/HIGH cannot be resolved in a single apply + one retry, log it in `## Issues & Flags` and invoke `AskUserQuestion` (override with reason / escalate to a follow-up card / halt) — do NOT proceed to Phase 6 with an unresolved BLOCKER or HIGH. VERIFIED findings of severity MEDIUM are also applied (advisory below that). Partition the verified findings by the **Domain-Override match rules** ("Domain-Override Domains"):
265
+ 11. **Merge-blocking gate (mirrors the per-card Phase 3.7 gate this final pass backstops):** if any VERIFIED **BLOCKER or HIGH** finding exists, it MUST be resolved before Phase 6 merge. Apply fixes by **domain owner** (since v3.40.0 — same Domain-Override routing as the per-card phases), then re-verify; if a BLOCKER/HIGH cannot be resolved in a single apply + one retry, log it in `## Issues & Flags` and invoke `AskUserQuestion` (override with reason / escalate to a follow-up card / halt) — do NOT proceed to Phase 6 with an unresolved BLOCKER or HIGH. VERIFIED findings of severity MEDIUM are also applied (advisory below that). Partition the verified findings — **excluding the no-action ones segregated in F.4** (`requires_action:false` / no-change direction, MED/LOW; they are recorded, never fixed) — by the **Domain-Override match rules** ("Domain-Override Domains"):
257
266
  - **`doc`-domain findings** (file path matching the `doc` match rule — `*.md` under `${paths.references_dir}`/`${paths.prd_dir}`, `CHANGELOG.md`, `ssot-registry.md`) → invoke the **doc-reviewer** agent once in write mode to apply them. NEVER route doc fixes to coder.
258
267
  - **`security`-domain findings** (path in `paths.high_risk_modules`, or RLS-policy SQL) → route to **security-reviewer** in write mode (canonical writer map v4.26.1 — it owns the security-invariant contract a coder lacks; NEVER route security fixes to coder). **`migration`-domain findings** (SQL under the migrations dir) → route to **coder**. For both, apply the Sub-agent failure protocol's STOP-on-crash rule (never inline-fallback on a security/migration fix). These are NOT collapsed into a generic "everything else" bucket.
259
268
  - **All remaining findings** (other code, perf, test) → invoke the **coder** agent once to apply them in a single pass.
@@ -74,6 +74,13 @@ so it surfaces in telemetry.
74
74
  `gateTable` is all PASS/SKIP and `checksFailed` is false** — a delegated QA FAIL must block exactly
75
75
  as the inline path does. (`gateTable` is **wave-scoped**, not per-card — qa-sentinel runs once over
76
76
  the worktree; a FAIL applies to the whole wave's commit.)
77
+ - **No-action findings (since v4.53.1)**: the workflow already segregated VERIFIED findings that
78
+ need NO change (finder `requires_action:false`, or a regex backstop on a "no fix required" /
79
+ "acceptable as-is" / "verified non-issue" direction — honoured for MED/LOW only; BLOCKER/HIGH are
80
+ always actionable). They are **counted in `summary.noAction`** and **excluded from both the writer
81
+ passes and `residual`** — so they never spawn a fixer and never surface as user-facing residual.
82
+ Append one `## Fix Application Log` row each as `decision=skipped | reason=no-action` (audit trail,
83
+ no silent drop). Do NOT re-route them to a writer.
77
84
  - **Telemetry**: append the workflow's `fixesApplied` + `residual` to `## Fix Application Log`
78
85
  (`decision=workflow`). Derive the Phase-8 producers from the return: `qa_first_attempt` ←
79
86
  `summary.qaRan ? (summary.failingGates.length === 0 && !summary.checksFailed ? "pass" : "fail") : "n/a"`
@@ -90,7 +97,12 @@ so it surfaces in telemetry.
90
97
  > review cluster; its agent briefs cite THIS module for the review *semantics*, so the SSOT for what each
91
98
  > finder checks stays here. When you change the Simplify lenses, the qa contract, the Codex/Step-A
92
99
  > profile rule, or the FP-check/routing, update the workflow's phase/agent wiring to match. The
93
- > delegated path runs the doc-review (Phase 3) on the FINAL post-fix/post-E2E code; the inline path runs
100
+ > workflow also (since v4.53.1) (a) makes every `finding_id` globally unique at the fan-in finders
101
+ > number `F###` independently and the Fix-phase bookkeeping keys on `finding_id`, so a collision used
102
+ > to drag one finder's applied fix into another's residual; and (b) runs an **actionability gate** that
103
+ > keeps VERIFIED-but-no-change findings (finder `requires_action:false` / regex backstop, MED/LOW only)
104
+ > out of the writer passes. Keep both in sync with the inline path below (step 4 skip-classification).
105
+ > The delegated path runs the doc-review (Phase 3) on the FINAL post-fix/post-E2E code; the inline path runs
94
106
  > it at its current position — both are doc-relevance-gated and both are backstopped by the Final FULL
95
107
  > doc-reviewer.
96
108
 
@@ -114,6 +126,7 @@ After completeness is verified, clean up the implementation before it reaches re
114
126
  - **Valid AND in a Domain-Override domain** (the finding's target file matches the `doc`, `security`, or `migration` match rule in "Domain-Override Domains") → do NOT apply inline. Delegate to the domain **writer** (canonical writer map v4.26.1): `doc` → `doc-reviewer` (write mode), `security` → `security-reviewer` (write mode — it owns the security-invariant contract a coder lacks), `migration` → `coder`. Even a one-line efficiency fix in `paths.high_risk_modules` (security) or a migration file goes to the owning agent — the orchestrator lacks that domain's invariant contract.
115
127
  - **Valid AND not in a Domain-Override domain** → fix directly (apply edits inline).
116
128
  - **False positive / not worth addressing** → skip, BUT record it (see telemetry). If the skip rests on a "covered by X" / "redundant" / "not needed" rationalization (the same family the AC-Closure Gate guards against), do NOT discard silently — verify the rationale by reading `X`, and if it does not hold, treat the finding as valid.
129
+ - **Verified but no action needed** (the finding is TRUE but its only honest direction is "no fix required" / "acceptable as-is" / "verified safe" — a cleared concern, not work) → do NOT apply and do NOT delegate; record it with `decision=skipped | reason=no-action`. This is distinct from a false positive (the observation holds) and from "not worth addressing" (a judgement call). Guard: this is for `MEDIUM`/`LOW` only — a `BLOCKER`/`HIGH` is never "no action". If a change IS needed but not here (out of this card's scope), it is NOT no-action — keep it valid and let it surface as residual. (This mirrors the delegated workflow's `requires_action` gate.)
117
130
 
118
131
  **Telemetry (Fix Application Log)** — for EVERY finding (valid OR skipped) append one row to the tracker's `## Fix Application Log` section per the schema above. Use `domain=simplify-{reuse|quality|efficiency}` matching the originating agent. Include the `severity` trailing key. Inline: `decision=inline | applied_by=orchestrator | est_lines=<n> | severity=<HIGH|MEDIUM> | finding=<1-line>`. Delegated (domain-override): `decision=<coder|doc-reviewer> | applied_by=<coder|doc-reviewer> | est_lines=<n> | severity=<...> | finding=<1-line>`. Skipped: `decision=skipped | applied_by=- | est_lines=0 | reason=<false-positive|not-worth-addressing>`.
119
132
 
@@ -184,7 +184,7 @@
184
184
 
185
185
  (No user-visible Task spine / Progress Bar is created — the internal tracker is the only state surface; see SKILL.md § "State surface — the tracker only".)
186
186
 
187
- 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)
187
+ 3d. **Codex batch cross-card grounding check** (background — launched together with the worktree-setup script in step 4, then a single barrier in step 5)
188
188
 
189
189
  > **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).
190
190
 
@@ -281,37 +281,29 @@
281
281
  - **`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`.
282
282
  - **`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.
283
283
  This — detection by `git worktree list`, not the lagging registry — is what makes the deferred-flush pre-flight genuinely idempotent across compaction.
284
- 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`, **`model: "sonnet"`** — the mission runs the multi-step `/nw` skill (group cards, create the worktree, install deps, allocate the dev-server port, write the registry entry, run the baseline) **via the Skill tool**: a sustained tool-execution chain, NOT one-shot plumbing. A too-weak model pattern-matches the expected return block instead of doing the workthe observed 2/2 fabrication was a well-formed block reporting `baseline: pass` in ~6s with **no worktree on disk**. Sonnet executes it for real or fails honestly; either way the returned block is **never trusted as evidence** — the orchestrator's disk-verification gate (step 6a below) is the source of truth) whose ENTIRE mission is to run `/nw` and return the block below. Briefing:
285
- ```
286
- Invoke the worktree-manager skill in `/nw` programmatic mode with:
287
- { cards: [<all card IDs>], groupParent: <PARENT-ID|null>, slug: "<slug>" }
288
- Let it: group cards, derive the branch from git_strategy.branch (fallback
289
- feat/<PARENT-ID>-<slug>), create the worktree in .worktrees/, install deps,
290
- copy the gitignored env artifacts in stack.env_files (files via cp, dirs via
291
- cp -r; missing file WARN, never abort), assign a free port, update
292
- .worktrees/registry.json (all card
293
- IDs in the `cards` field), and run the baseline (tsc + lint + build)
294
- UNDER A HARD TIMEOUT — wrap the build step in `timeout 600 <build-cmd>`
295
- (10 min) so a hung or interactive build cannot stall the pre-flight barrier.
296
- Redirect ALL install/build/git output to files under /tmp — never inline it.
297
- Return ONLY this block, nothing else (no logs, no narration, no skill recap):
298
- worktree_path: <absolute path>
299
- branch: <branch>
300
- port: <n>
301
- created_at: <ISO-8601, stamped WHEN you create the worktree — before install/build>
302
- baseline: pass | fail | timeout
303
- baseline_log: <path on failure/timeout, else "-">
304
- If the build fails, return `baseline: fail` with the log path and STOP.
305
- If the timeout kills the build, return `baseline: timeout` with the partial
306
- log path and STOP — do not continue.
307
- ```
308
- 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.
309
- d. **Fallback executor — when the subagent does not produce a VERIFIED worktree.** The subagent's returned block is **never trusted as evidence**; step 6a verifies the worktree on disk. Trigger this fallback when EITHER (i) the subagent returns empty / without the structured block (cannot invoke the Skill tool in this consumer, or the known "0 tool uses · Done" empty-result), OR (ii) it returns a well-formed block but the **step-6a disk gate is VERIFIED:false** (the observed 2/2 fabrication: `baseline: pass` in ~6s with no worktree on disk). In both cases do **NOT** strand the barrier: the orchestrator **falls back to invoking `/nw` inline itself** — a genuinely **DIFFERENT executor** (the full-model orchestrator interpreting the skill prose), **not** a re-spawn of the same subagent and **not** a frozen script, so neither a Skill-from-subagent capability gap nor a weak-model fabrication can recur on the fallback. Slug from 4a; record `worktree_path`/`branch`/`port` and stamp `created_at` from the inline return; then **re-run the step-6a disk gate ONCE**. **Cap (no loop):** the chain is strictly `subagent → inline /nw → HALT+report` — never re-spawn the subagent, never loop the inline path. If the inline attempt ALSO fails the gate, or the failure is **deterministic** (port exhaustion across 3001-3099, a corrupted lockfile, or a genuine `baseline: fail`/`timeout` build error — recreating cannot fix these), **HALT and report** rather than retry. You lose the prefix saving for this one run, but pre-flight completes or halts cleanly — never silently on a phantom worktree. This is the same opt-in-with-fallback discipline the Codex check (3d) and the dynamic-workflow gate use.
310
- 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).
284
+ b. **Launch the deterministic setup SCRIPT as a background `Bash` (NO model in the loop this is pure plumbing).** The SSOT for the entire code-worktree build sequence (worktree add install env copy port registry baseline) is **`scripts/setup-worktree.sh`** in the `worktree-manager` skill — the SAME script `/nw` and `new2` invoke (no per-caller duplication). A deterministic script **cannot fabricate or stall**: it does the work or fails honestly with a log. This replaces the old Sonnet-subagent-via-Skill delegation, whose two observed failure modes were exactly a model-in-the-loop pathologyhaiku FABRICATED a well-formed `baseline: pass` with nothing on disk (2/2), and Sonnet went IDLE mid-execution leaving a half-built worktree (`node_modules` missing). The orchestrator resolves only the cheap in-context args and passes them; the script resolves `stack.env_files` / toolchain commands / the port range from `baldart.config.yml` itself.
285
+ - **Script-presence check (opt-in-with-fallback).** Resolve it: `SETUP_SH="$(ls .framework/framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh .claude/skills/worktree-manager/scripts/setup-worktree.sh 2>/dev/null | head -1)"`. If **absent** (a pre-this-release install whose subtree predates the script), skip straight to the **4d inline `/nw` fallback** — never block on a missing script.
286
+ - **Launch** with `run_in_background: true` and `timeout: 640000` (a hair above the script's own 600 s build timeout, so the harness never kills it before the script's timeout fires):
287
+ ```bash
288
+ WT_MANIFEST="/tmp/wt-setup-<FIRST-CARD-ID>-<SESSION-ID>.txt"
289
+ bash "$SETUP_SH" \
290
+ --main "$MAIN" --trunk "$TRUNK" \
291
+ --branch "$WT_BRANCH" --slug "<slug from 4a>" \
292
+ --card "<PARENT-ID, or the lead card ID>" \
293
+ --cards "<comma-separated ALL card IDs in the batch>" \
294
+ --group-parent "<PARENT-ID, or empty>" \
295
+ --env-files "<stack.env_files as CSV, default '.env.local,.env'>" \
296
+ --manifest "$WT_MANIFEST"
297
+ ```
298
+ `$MAIN`/`$TRUNK` are the Phase-0 variables; `$WT_BRANCH` is the branch resolved in 4a2. **Pass `--env-files` explicitly** (resolve `stack.env_files` from `baldart.config.yml` — you have already read the config in Phase 0): the orchestrator's YAML read is authoritative, so the script's own best-effort `env_files` parser stays a `/nw`-standalone fallback and never sits on this load path. Record the exact `$WT_MANIFEST` path in the tracker `## Worktree` section so the resume step (6a) reads the SAME file. The script writes its structured block to that file on **every** exit path.
299
+ c. The install/build output lives in the script's own log (`/tmp/wt-setup-<slug>.log`), never in the orchestrator context — the same context-economy win the subagent gave, but with **no model to fabricate or stall**. Combined with the Codex check (3d, also background), this keeps the pre-flight tail to background ops + a single resume.
300
+ d. **Fallback executor — inline `/nw` — ONLY when the script could not run or the disk gate fails.** Trigger when EITHER (i) the script is **absent** (4b presence check), OR (ii) the **step-6a disk gate is VERIFIED:false** (defence-in-depth — a deterministic script can still fail mute on a full disk / partial install). In those cases the orchestrator **falls back to invoking `/nw` inline itself** — the full-model orchestrator interpreting the skill prose, a genuinely different executor — slug from 4a, then re-runs the step-6a disk gate ONCE. **Honest deterministic failures are NOT fallback-eligible**: a manifest `status: ok` is impossible with a bad build, and a manifest reporting `baseline: fail` / `baseline: timeout` (the build genuinely broke or hung) or `status: error` with a deterministic cause (collision the 4a2 reset already handled, port exhaustion across the configured range) → **HALT and report** (point the user at `baseline_log`); recreating cannot fix them, and a real baseline break is repaired by the coder downstream, not by re-running setup. **Cap (no loop):** the chain is strictly `script → inline /nw → HALT+report` — never re-launch the script, never loop the inline path.
301
+ 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 script (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 `Bash` re-invokes 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 half-written `$WT_MANIFEST`. (If 3d was SKIPPED by the provenance gate, the only op is the worktree script 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).
311
302
  6. **On resume — flush the pre-flight tracker sections in one pass (no incremental per-sub-step churn).** When all launched ops have returned:
312
- a. **Worktree integrity gate (BLOCKING — the disk is the source of truth, not the returned block).**
313
- - **Honest-failure signals first** (trust these a reported failure is real, not a fabrication, and recreating the worktree would not fix it): if the block reports `baseline: fail` STOP and report (point the user at `baseline_log`); if `baseline: timeout` (the build exceeded the launch timeout, §4b/§5) STOP and report the timeout. Do NOT continue to Phase 1; do NOT recreate.
314
- - **On `baseline: pass`, VERIFY the worktree on disk YOURSELF** these are orchestrator `Bash` calls run in this (full-model) context; **never** trust a `verified`-style field returned by a subagent (it is exactly as fabricable as the block itself). With `$WT_PATH`/`$WT_BRANCH` from the block and `$MAIN` from the tracker (`## Worktree` `Main repo:`):
303
+ a. **Worktree integrity gate (BLOCKING — the disk is the source of truth, not the manifest).** Read the manifest at the `$WT_MANIFEST` path recorded in the tracker (`## Worktree`) — a small fixed-key block (`status` / `error` / `worktree_path` / `branch` / `port` / `created_at` / `baseline` / `baseline_log`); never `cat` a log.
304
+ - **A TERMINAL manifest is a NECESSARY pre-condition for VERIFIED.** The script writes a *provisional* `status: error` manifest the instant the worktree exists, then overwrites it with `status: ok` + `baseline: pass` only after the build passes. So if the manifest is **missing, empty, or NOT terminal** (`status` is not `ok`, or `baseline` is not `pass`) → the worktree is half-built (the process was killed mid-build likely on a host without a `timeout` binary, where only the caller's background timeout bounds it). Do **NOT** run the disk checks 1-4 and do **NOT** declare VERIFIED on their strength: `node_modules` existing on a killed install is exactly the false-pass this guard prevents. Route a non-terminal/missing manifest to the **4d inline `/nw` fallback** (or HALT if 4d is exhausted) — never VERIFIED.
305
+ - **Honest-failure signals** (trust these — a reported failure is real, not a fabrication, and recreating the worktree would not fix it): if the manifest is `status: error` with `baseline: fail` STOP and report (point the user at `baseline_log`); `baseline: timeout` (the build exceeded the 600 s script timeout, §4b) → STOP and report the timeout; `status: error` with a deterministic setup cause (collision, port exhaustion see the `error:` line) → STOP and report. Do NOT continue to Phase 1; do NOT recreate. (A non-terminal `status: error` whose `error:` line is the provisional "setup incomplete" message — i.e. NOT one of these honest/deterministic causes — → 4d inline `/nw` fallback per the bullet above.)
306
+ - **On `status: ok` + `baseline: pass`, VERIFY the worktree on disk YOURSELF** — these are orchestrator `Bash` calls run in this (full-model) context; the manifest is corroboration, the disk is proof. With `$WT_PATH` = the manifest's `worktree_path`, `$WT_BRANCH` from 4a2, and `$MAIN` from the tracker (`## Worktree` `Main repo:`):
315
307
  1. `git -C "$MAIN" worktree list --porcelain` lists `$WT_PATH`;
316
308
  2. `test -d "$WT_PATH"`;
317
309
  3. `git -C "$WT_PATH" rev-parse --abbrev-ref HEAD` equals `$WT_BRANCH`;
@@ -321,7 +313,7 @@
321
313
  b. **Codex verdict**: handle it via the verdict-extraction discipline in 3d (read `$AUDIT_FILE` through the `[codex]`-stripping filter; keep distilled findings only).
322
314
  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:
323
315
  - `## File Ownership Map` (3b).
324
- - `## 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.)
316
+ - `## Worktree` — path / branch / slug / port, plus `Created:` = **the manifest'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.)
325
317
  - `## 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).
326
318
  - In team mode: `## Team Mode` + `## Parallel Groups` (per team-mode.md).
327
319
  `## Execution Mode` was already written at step 3c — 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.
@@ -574,202 +574,65 @@ Check registry for duplicate:
574
574
 
575
575
  Branch name: `feat/<CARD-ID>-<slug>` (with card) or `feat/<slug>` (slug-only). Use `hotfix/` prefix if card specifies hotfix.
576
576
 
577
- ### 3. Create the worktree
577
+ ### 3-6. Create + isolate + baseline + registry — `scripts/setup-worktree.sh` (SSOT)
578
578
 
579
- Base the new worktree on `origin/<$TRUNK>` (the remote tip we just fetched in step 1) rather than the local trunk branch that way we never need the local trunk to be checked out or up to date on the main repo.
579
+ Steps 1-2 resolved everything model-judgement is needed for: `$MAIN`, `$TRUNK`, the
580
+ branch, the slug, the card list. The **rest is pure plumbing** — create the worktree
581
+ off `origin/$TRUNK`, sync untracked cards, `npm install`, copy `stack.env_files`,
582
+ allocate a free port, write the registry entry, run the baseline. That sequence is
583
+ **not** re-authored here: it lives **once** in
584
+ `scripts/setup-worktree.sh`, the single source of truth invoked identically by
585
+ `/nw`, `/new` (setup.md step 4), and `new2`. Re-inlining it as a parallel bash copy
586
+ is exactly the duplication CLAUDE.md forbids — and a deterministic script cannot
587
+ fabricate a `baseline: pass` or stall mid-build the way a model-in-the-loop did.
580
588
 
581
- The worktree path is **deterministic** (R8): derived from the card ID + kebab-case slug only, never a timestamp — so a resumed run resolves to the same path and the duplicate check in step 1 is meaningful.
589
+ Invoke it from the **main checkout** (cwd = `$MAIN`):
582
590
 
583
591
  ```bash
584
- # With card ID:
585
- WORKTREE_PATH=".worktrees/feat-<CARD-ID>-<slug>"
586
- git worktree add "$WORKTREE_PATH" -b "feat/<CARD-ID>-<slug>" "origin/$TRUNK"
587
-
588
- # Slug-only (no card):
589
- WORKTREE_PATH=".worktrees/feat-<slug>"
590
- git worktree add "$WORKTREE_PATH" -b "feat/<slug>" "origin/$TRUNK"
591
- ```
592
-
593
- ### 3b. Sync untracked backlog cards from main repo
594
-
595
- Backlog cards created during `/prd` sessions may exist as untracked files in the main repo
596
- but are NOT on the trunk branch yet. The worktree (branched from the trunk) won't have them.
597
-
598
- ```bash
599
- # BACKLOG_DIR = value of paths.backlog_dir from baldart.config.yml (fallback `backlog`
600
- # when the key is absent — emit it resolved, never the literal ${paths.backlog_dir}
601
- # token, which is not valid bash). Same resolution as /new's card-scoped diff block.
602
- BACKLOG_DIR="<value of paths.backlog_dir, or 'backlog' if the key is absent>"
603
- # At this point cwd is still the MAIN checkout (the `cd "$WORKTREE_PATH"` happens
604
- # in step 4 below), so `--show-toplevel` IS the main repo root — correct for a
605
- # normal repo, a git submodule, and a `--separate-git-dir` repo alike. (The old
606
- # `git -C "$WORKTREE_PATH" --show-superproject-working-tree || pwd` was wrong for a
607
- # real submodule — it returned the SUPERPROJECT, not this repo's root where the
608
- # cards live — and only resolved otherwise by relying on this same cwd accident.
609
- # See § "Resolving the main repo root".)
610
- MAIN_ROOT="$(git rev-parse --show-toplevel)"
611
-
612
- # For each card in the batch, check if its YAML exists in the main repo but not in the worktree
613
- for CARD_FILE in $(ls "$MAIN_ROOT/$BACKLOG_DIR"/*.yml 2>/dev/null); do
614
- BASENAME=$(basename "$CARD_FILE")
615
- if [ ! -f "$WORKTREE_PATH/$BACKLOG_DIR/$BASENAME" ]; then
616
- cp "$CARD_FILE" "$WORKTREE_PATH/$BACKLOG_DIR/$BASENAME"
617
- echo "Synced untracked card: $BASENAME"
618
- fi
619
- done
592
+ SETUP_SH="$(ls .claude/skills/worktree-manager/scripts/setup-worktree.sh \
593
+ .framework/framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh 2>/dev/null | head -1)"
594
+ WT_MANIFEST="/tmp/nw-setup-<slug>.txt"
595
+ bash "$SETUP_SH" \
596
+ --main "$MAIN" --trunk "$TRUNK" \
597
+ --branch "<branch from step 2>" --slug "<slug>" \
598
+ --card "<CARD-ID or empty>" --cards "<comma-separated card IDs, or empty>" \
599
+ --group-parent "<groupParent or empty>" \
600
+ --env-files "<stack.env_files as CSV, default '.env.local,.env'>" \
601
+ --manifest "$WT_MANIFEST"
620
602
  ```
621
603
 
622
- This prevents "File not found" errors when Phase 4 tries to mark cards as DONE.
623
-
624
- ### 4. Full isolation setup
625
-
626
- Run ALL of these inside the worktree directory:
627
-
628
- ```bash
629
- cd "$WORKTREE_PATH"
630
-
631
- # 1. Own node_modules (full independent install)
632
- npm install
633
-
634
- # 2. Own .next cachealready isolated since each worktree has its own
635
- # directory tree. The default .next inside the worktree is sufficient.
636
-
637
- # 3. Copy environment artifacts from main repo root.
638
- # The build typically requires gitignored env secrets, so this copy is
639
- # critical but the SET is project-specific and lives in `stack.env_files`
640
- # (baldart.config.yml). Resolve that list and emit it LITERALLY below as a bash
641
- # array (default ('.env.local' '.env') when the key is absent). List ONLY
642
- # gitignored artifacts: a tracked file (e.g. an example env committed to git)
643
- # is already in the worktree checkout — copying it is redundant. Entries may be
644
- # FILES (cp) or DIRECTORIES (cp -r). The SAME list drives /nw, /new, and new2 —
645
- # no per-path divergence. (A directory entry that carries remote-link/credential
646
- # state makes the worktree act on the same remote as main; that is a per-project
647
- # opt-in the user adds to stack.env_files / their overlay — the skill itself is
648
- # stack-agnostic and just iterates the list.)
649
- #
650
- # cwd is the worktree; its parent (`.worktrees/`) lives inside the main repo,
651
- # so `git -C ..` resolves the MAIN repo's toplevel — correct under a
652
- # `--separate-git-dir` repo too (unlike `--git-common-dir`+parent). The old
653
- # silent `|| echo '../..'` fallback masked a resolution failure with a relative
654
- # guess; fail loud instead. See § "Resolving the main repo root".
655
- MAIN_ROOT="$(git -C .. rev-parse --show-toplevel 2>/dev/null)"
656
- [ -n "$MAIN_ROOT" ] || { echo "ERROR: cannot resolve main repo root from worktree $(pwd)" >&2; exit 1; }
657
-
658
- # <resolve stack.env_files and emit each entry quoted; default if the key is absent>
659
- ENV_FILES=( ".env.local" ".env" )
660
- copied_any=0
661
- primary_env="" # first FILE actually copied — PORT target (step 4)
662
- for ef in "${ENV_FILES[@]}"; do
663
- src="$MAIN_ROOT/$ef"
664
- dest_dir="$(dirname "$ef")"
665
- if [ -d "$src" ]; then
666
- # Directory artifact. cp -r MERGES into an existing
667
- # dest, so mirror it: remove first to avoid stale files lingering on a /nw
668
- # resume. Judge success by dest existence, NOT cp's exit code (BSD `cp -r`
669
- # returns non-zero on a broken inner symlink while still copying the rest).
670
- mkdir -p "$dest_dir"
671
- rm -rf "$ef"
672
- cp -R "$src" "$dest_dir"/ 2>/dev/null || true
673
- [ -e "$ef" ] && copied_any=1 || echo "WARN: env dir '$ef' (stack.env_files) present in main but copy failed — worktree may be incomplete." >&2
674
- elif [ -f "$src" ]; then
675
- # File artifact. PLAIN cp (NEVER cp -P): it dereferences a symlinked source,
676
- # so an `.env.local -> ../shared/.env` symlink yields real content here — a
677
- # preserved relative symlink would dangle inside `.worktrees/feat-X/`.
678
- mkdir -p "$dest_dir"
679
- if cp "$src" "$ef"; then copied_any=1; [ -z "$primary_env" ] && primary_env="$ef"; fi
680
- else
681
- # Absent in main. A missing FILE is the critical case (build likely needs it):
682
- # WARN loudly so a later cryptic build failure is diagnosable — but NEVER exit 1
683
- # (this runs on /new's programmatic path; aborting would strand the batch — the
684
- # build gate in step 5 is the real enforcement). A missing DIR is best-effort
685
- # (the worktree just isn't linked, which is safe) — we cannot stat a nonexistent
686
- # path to know it was a dir, so a single neutral WARN per entry covers both.
687
- echo "WARN: env artifact '$ef' (stack.env_files) not found in $MAIN_ROOT — the worktree build may fail if it is required." >&2
688
- fi
689
- done
690
- if [ "$copied_any" = 0 ] && [ "${#ENV_FILES[@]}" -gt 0 ]; then
691
- echo "WARN: NO env artifacts copied into the worktree (none of: ${ENV_FILES[*]} exist in $MAIN_ROOT). If the build needs env vars it WILL fail — add the file(s) to the main repo or fix stack.env_files." >&2
692
- fi
693
-
694
- # 4. Pick a free dev server port (avoid 3000 used by main)
695
- PORT=3001
696
- while lsof -i :$PORT -sTCP:LISTEN >/dev/null 2>&1; do
697
- PORT=$((PORT + 1))
698
- if [ $PORT -gt 3099 ]; then echo "ERROR: No free port in 3001-3099" >&2; exit 1; fi
699
- done
700
- # Write PORT to the project's PRIMARY env file — the first FILE that was actually
701
- # copied (NOT ENV_FILES[0], which could be a directory entry, where
702
- # `echo >> dir` / `grep dir` error out). Fall back to .env.local when nothing was
703
- # copied so a port is still persisted somewhere the dev server can read.
704
- PORT_ENV_FILE="${primary_env:-.env.local}"
705
- if grep -q "^PORT=" "$PORT_ENV_FILE" 2>/dev/null; then
706
- sed -i '' "s/^PORT=.*/PORT=$PORT/" "$PORT_ENV_FILE"
707
- else
708
- echo "PORT=$PORT" >> "$PORT_ENV_FILE"
709
- fi
710
-
711
- # 5. ASSERT git hooks are ACTIVE — not just readable.
712
- # The worktree shares core.hooksPath via .git/commondir, so the same
713
- # resolution applies here as in the main checkout. Reading the value
714
- # (the old `git config core.hooksPath || true`) is NOT enough: it never
715
- # catches the failure mode where core.hooksPath points at an empty dir
716
- # while the repo's versioned hooks live elsewhere (pre-commit / pre-push
717
- # silently inactive). Detect the versioned-hooks dir and assert it is served.
718
- HOOK_SRC=""
719
- for d in .husky .githooks scripts/git-hooks githooks; do
720
- if [ -d "$d" ] && ls "$d" 2>/dev/null | grep -qE '^(pre-commit|pre-push|commit-msg)$'; then
721
- HOOK_SRC="$d"; break
722
- fi
723
- done
724
- if [ -n "$HOOK_SRC" ]; then
725
- ACTIVE="$(git config --get core.hooksPath || echo .git/hooks)"
726
- # Lean literal-string match (HOOK_SRC is relative). Full path resolution +
727
- # canonicalization lives in `baldart doctor` (src/utils/githooks.js); an
728
- # absolute core.hooksPath here would warn spuriously — rare, run doctor to confirm.
729
- case "$ACTIVE" in
730
- "$HOOK_SRC"|"$HOOK_SRC"/*|.husky/_) : ;; # active dir serves the versioned hooks (incl. husky v9 .husky/_)
731
- *)
732
- echo "⚠️ WARNING: git hooks INACTIVE — the versioned hooks live in '$HOOK_SRC' but core.hooksPath = '$ACTIVE'." >&2
733
- echo " Re-activate them with: git config core.hooksPath $HOOK_SRC" >&2
734
- ;;
735
- esac
736
- fi
737
- ```
738
-
739
- ### 5. Verify baseline
740
-
741
- ```bash
742
- # Toolchain-aware (§ "Toolchain-aware gates"): when features.has_toolchain: true,
743
- # run toolchain.commands.{typecheck,lint,build} verbatim; else the defaults below.
744
- CFG="baldart.config.yml"; [ -f "$CFG" ] || CFG="$(git rev-parse --show-toplevel 2>/dev/null)/baldart.config.yml"
745
- _tc() { grep -E '^[[:space:]]*has_toolchain:[[:space:]]*true' "$CFG" >/dev/null 2>&1 || return 0
746
- grep -A20 '^toolchain:' "$CFG" 2>/dev/null | grep -A15 '^[[:space:]]*commands:' \
747
- | grep -E "^[[:space:]]+$1:" | head -1 \
748
- | sed -E "s/.*$1:[[:space:]]*\"?([^\"#]*)\"?.*/\1/" | sed -E 's/[[:space:]]+$//'; }
749
-
750
- TC_TC=$(_tc typecheck); TC_LINT=$(_tc lint); TC_BUILD=$(_tc build)
751
-
752
- # TypeScript + lint (fast)
753
- eval "${TC_TC:-npx tsc --noEmit}"
754
- eval "${TC_LINT:-npx eslint --max-warnings=0 src/}"
755
-
756
- # Full build verification (required — confirms worktree is functional)
757
- eval "${TC_BUILD:-npm run build}"
758
- ```
759
-
760
- If build fails → STOP and report. Do NOT continue — the worktree is broken.
761
- If only tsc/lint fails → report but continue (the trunk branch should be clean, may be a transient issue).
762
- (Severity is by-gate as stated here; the toolchain resolver only changes *which* command runs — § "Toolchain-aware gates".)
763
-
764
- ### 6. Update registry
765
-
766
- Add entry to `.worktrees/registry.json` with all fields: card, cards, groupParent, slug, branch, `trunkBranch` (the resolved `$TRUNK`), `mainRoot` (the resolved `$MAIN`), absolute path, port, `createdAt`, `envSyncedAt`, and `buildVerified`. Persisting `trunkBranch` + `mainRoot` (R6) lets `/mw`/`/cw` read them back with a presence guard instead of re-deriving or assuming `develop`.
604
+ Pass `--env-files` explicitly (resolve `stack.env_files` from `baldart.config.yml`)
605
+ the script's built-in `env_files` parser is only a best-effort last-resort fallback for
606
+ a bare invocation; the caller's read is authoritative.
607
+
608
+ What the script does (and what the omitted flags fall back to), so you can read its
609
+ output without re-deriving it:
610
+ - **Create**: `git worktree add .worktrees/<branch-with-slashes→dashes> -b <branch> origin/$TRUNK` (deterministic path, R8). Fails loud on a branch/path collision — a code worktree is never silently re-created (the caller's idempotency pre-check resets an orphan first).
611
+ - **Sync untracked cards** from `$MAIN/<paths.backlog_dir>` (so `/new` Phase 4 can mark cards DONE).
612
+ - **Install** deps non-interactively (stdin closed, `CI=1`).
613
+ - **Copy `stack.env_files`** (FILES via plain `cp`, DIRS via `cp -R`; missing FILE → WARN, never abort) and write `PORT=<n>` into the primary copied env file. The SAME `stack.env_files` list drives `/nw`, `/new`, `new2` — pass `--env-files` to override, else the script resolves it from config (default `.env.local,.env`).
614
+ - **Port**: first free in `3001-3099` (skips registry-held ports + live listeners), allocated under a `.worktrees/.wt-setup.lock` mutex so concurrent `/nw` runs never collide.
615
+ - **Baseline** (toolchain-aware — `toolchain.commands.{typecheck,lint,build}` when `features.has_toolchain: true`, else `npx tsc --noEmit` / `npx eslint --max-warnings=0 src/` / `npm run build`): tsc/lint are **report-but-continue**, the build is **STOP-on-fail** and runs under a hard `timeout 600` (so a hung/interactive build cannot stall a caller's barrier).
616
+ - **Registry**: writes/updates the `.worktrees/registry.json` entry (all fields incl. `trunkBranch`/`mainRoot`/`port`/`createdAt`/`buildVerified`) atomically via `node` `buildVerified` flips to `true` only after the build passes.
617
+
618
+ The script writes a structured manifest to `$WT_MANIFEST` on **every** exit path
619
+ (`status` / `error` / `worktree_path` / `branch` / `port` / `created_at` /
620
+ `baseline` / `baseline_log`) read THAT for the result; the build log lives in
621
+ `/tmp/wt-setup-<slug>.log` and never needs to enter context. `status: error` with
622
+ `baseline: fail`/`timeout` is an **honest** build failure (the coder repairs it
623
+ downstream the script never self-repairs, role boundary); `status: error` with a
624
+ deterministic setup cause (collision, port exhaustion) report, do not retry.
767
625
 
768
626
  ### 7. Auto-start dev server (if `--dev` flag or `/nw --dev`)
769
627
 
628
+ The script set up the worktree in its own process; resolve `$WORKTREE_PATH` and `$PORT`
629
+ from the manifest written in step 3-6 (`worktree_path` / `port`). Only on `status: ok`.
630
+
770
631
  If the user passed `--dev` (e.g., `/nw --dev FEAT-0500 menu-fix`), start the dev server in background:
771
632
 
772
633
  ```bash
634
+ WORKTREE_PATH="$(grep '^worktree_path:' "$WT_MANIFEST" | sed 's/^worktree_path:[[:space:]]*//')"
635
+ PORT="$(grep '^port:' "$WT_MANIFEST" | sed 's/^port:[[:space:]]*//')"
773
636
  cd "$WORKTREE_PATH"
774
637
  nohup npm run dev > .next/dev.log 2>&1 &
775
638
  DEV_PID=$!
@@ -784,14 +647,18 @@ fi
784
647
 
785
648
  ### 8. Report to user
786
649
 
650
+ Read the values from the `$WT_MANIFEST` block (`worktree_path` / `branch` / `port` /
651
+ `baseline`). On `status: error`, report the `error:` line and the `baseline_log` instead
652
+ of the success block.
653
+
787
654
  ```
788
655
  Worktree ready:
789
- Path: <full-absolute-path>
656
+ Path: <worktree_path>
790
657
  Branch: feat/<CARD-ID>-<slug> (or feat/<slug> if no card)
791
- Port: <PORT>
658
+ Port: <port>
792
659
  Card: <CARD-ID> (or "none" if slug-only)
793
- Build: verified ✓
794
- Dev: http://localhost:<PORT> (PID: <DEV_PID>) ← only if --dev
660
+ Build: verified ✓ (baseline: pass)
661
+ Dev: http://localhost:<port> (PID: <DEV_PID>) ← only if --dev
795
662
 
796
663
  Start dev server: cd <path> && npm run dev ← only if NOT --dev
797
664
  The worktree has its own node_modules and .next cache — fully independent.