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 +26 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/api-perf-cost-auditor.md +4 -0
- package/framework/.claude/agents/code-reviewer.md +11 -0
- package/framework/.claude/agents/doc-reviewer.md +1 -0
- package/framework/.claude/agents/security-reviewer.md +7 -0
- package/framework/.claude/skills/new/SKILL.md +1 -1
- package/framework/.claude/skills/new/references/final-review.md +12 -3
- package/framework/.claude/skills/new/references/review-cycle.md +14 -1
- package/framework/.claude/skills/new/references/setup.md +24 -32
- package/framework/.claude/skills/worktree-manager/SKILL.md +55 -188
- package/framework/.claude/skills/worktree-manager/scripts/setup-worktree.sh +457 -0
- package/framework/.claude/workflows/new-card-review.js +35 -10
- package/framework/.claude/workflows/new-final-review.js +29 -8
- package/framework/.claude/workflows/new2.js +1 -1
- package/package.json +1 -1
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.
|
|
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
|
|
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
|
-
>
|
|
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
|
|
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. **
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
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 pathology — haiku 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
|
|
313
|
-
- **
|
|
314
|
-
- **
|
|
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
|
|
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
|
|
577
|
+
### 3-6. Create + isolate + baseline + registry — `scripts/setup-worktree.sh` (SSOT)
|
|
578
578
|
|
|
579
|
-
|
|
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
|
-
|
|
589
|
+
Invoke it from the **main checkout** (cwd = `$MAIN`):
|
|
582
590
|
|
|
583
591
|
```bash
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
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
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
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: <
|
|
656
|
+
Path: <worktree_path>
|
|
790
657
|
Branch: feat/<CARD-ID>-<slug> (or feat/<slug> if no card)
|
|
791
|
-
Port: <
|
|
658
|
+
Port: <port>
|
|
792
659
|
Card: <CARD-ID> (or "none" if slug-only)
|
|
793
|
-
Build: verified ✓
|
|
794
|
-
Dev: http://localhost:<
|
|
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.
|