baldart 5.2.0 → 5.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -5,6 +5,84 @@ 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
+ ## [5.3.0] - 2026-07-03
9
+
10
+ **Haiku banned fleet-wide + FEAT-0067 post-mortem fixes.** The FEAT-0067 audit
11
+ (mayo, session `a8b6bc75`) found a haiku `general-purpose` commit agent running
12
+ its first git command with `cd <worktree> &&` and its SECOND without — so the
13
+ card-DONE bookkeeping commit executed in the MAIN checkout and landed on the
14
+ trunk mid-run (`13aa63b62`), forking `ssot-registry.md`, raising a merge
15
+ BLOCKER (a 5.3M-token repair), and blocking epic closure. Second haiku-class
16
+ failure after the v4.42.0 worktree-setup fabrication → the plumbing carve-out
17
+ is revoked.
18
+
19
+ - **Model policy — haiku eliminated, sonnet is the floor** (`REGISTRY.md`
20
+ § Rules rewritten): no named specialist AND no `general-purpose` plumbing may
21
+ use haiku. Replaced in every spawn site: `new2.js` (`commit:`,
22
+ `integrate:commit`, `rollback:`, `review:*:codex` driver),
23
+ `new-final-review.js` + `new-card-review.js` (`codex-preflight`), and the
24
+ `seo-analytics-strategist` agent (→ sonnet, v1.1.0). Where an op is truly
25
+ deterministic the fix stays a script (`setup-worktree.sh`,
26
+ `revert-files.sh`), not a cheaper model.
27
+ - **`new2.js` commit agents — cwd HARD RULE + placement verify**: every shell
28
+ command must be prefixed `cd <worktree> &&` in the same Bash invocation (the
29
+ cwd resets between calls), and the per-card commit agent now verifies the
30
+ returned sha equals `git -C <worktree> log -1 --format=%H`, reporting
31
+ `committed:false` with the stray sha instead of silently corrupting the
32
+ trunk.
33
+ - **`new2.js` Phase-1 retriever — schema + drift false-positive fix**: the
34
+ prompt now states the exact required StructuredOutput shape (`ok` REQUIRED —
35
+ the FEAT-0067 run burned 5+3 schema-validation retries), and `missingPaths`
36
+ excludes paths the card itself CREATES (new migrations/tests were flagged as
37
+ drift and disproved downstream).
38
+ - **`scripts/new-session-audit.mjs` — workflow-hosted agents counted**: the
39
+ analyzer now traverses `subagents/workflows/<wf>/agent-*.jsonl`; a new2 run
40
+ previously reported the orchestrator alone (FEAT-0067: 3.88M reported vs
41
+ 53.89M real — 93% of the run invisible to TOTAL, baselines, and telemetry).
42
+
43
+ Codex parity: portable / N/A — the three workflows are Claude-only by design
44
+ (documented gap with inline fallback); the agent `model:` field is omitted in
45
+ the Codex TOML transpile, so the model-policy change is Claude-side only; the
46
+ audit script is maintainer tooling, not payload.
47
+
48
+ ## [5.2.1] - 2026-07-03
49
+
50
+ **`/mw` local-trunk sync robustness.** The worktree merge's local-trunk sync
51
+ (step 4c "Common", both `pr` and `local-push` strategies) failed to
52
+ fast-forward on the majority of real checkouts, needlessly leaving
53
+ `SYNC-DEFERRED` / `SYNC-NEEDS-DECISION`. Two targeted fixes, kept single-sourced
54
+ across the executed SSOT (`framework/.claude/skills/worktree-manager/scripts/merge-worktree.sh`)
55
+ and the prose (`worktree-manager/SKILL.md` step 4c) — **without `git stash`**,
56
+ which stays forbidden here because `refs/stash` is shared across all worktrees
57
+ via `git.commondir` (the FEAT-0522 incident): a stash from the main checkout can
58
+ pop into the wrong worktree, and two parallel `/mw` runs race on the shared
59
+ stash list.
60
+
61
+ - **Fix 1 — safe ref-advance when main is on another branch.** Previously, if
62
+ the main repo's HEAD was not on `$TRUNK`, sync did a fetch-only and always
63
+ emitted `SYNC-DEFERRED`, never advancing the local `$TRUNK` ref. Now it
64
+ fast-forwards `refs/heads/$TRUNK` via a ref-only, ff-only fetch
65
+ (`git fetch origin $TRUNK:$TRUNK`) that **never touches any working tree** and
66
+ never checks out — turning a large class of deferrals into a clean sync
67
+ (`sync_marker: none`). If `$TRUNK` is checked out in another worktree, git
68
+ refuses and it falls back to fetch-only + defer.
69
+ - **Fix 2 — accurate ff-fail disambiguation.** When the ff failed with a
70
+ tracked-clean tree and no owned/foreign dirt, the code fell to a misleading
71
+ "clean (diverged?)" marker (the dirty scan uses `--untracked-files=no`, so an
72
+ untracked-file collision was invisible to it). Now it disambiguates via
73
+ `merge-base --is-ancestor` between a **genuine branch divergence** (needs a
74
+ rebase/merge decision) and an **untracked-file collision** (the blocking files
75
+ are enumerated), emitting the correct actionable marker.
76
+
77
+ Behaviour is strictly safer: cases #2 (foreign uncommitted work) and #3 (real
78
+ divergence) still defer to ONE explicit user gate — never auto-committed, never
79
+ stashed — which is the correct behaviour on a shared checkout.
80
+
81
+ **Codex parity: portable.** The change is pure git plumbing inside the
82
+ dual-runtime `merge-worktree.sh` (executed identically under Claude and Codex)
83
+ plus its prose twin; no runtime-specific mechanic, no config key. The
84
+ schema-change propagation rule does not apply (no `baldart.config.yml` key).
85
+
8
86
  ## [5.2.0] - 2026-07-03
9
87
 
10
88
  **The local design studio.** `/ui-design` is refounded from the root as the
package/VERSION CHANGED
@@ -1 +1 @@
1
- 5.2.0
1
+ 5.3.0
@@ -7,6 +7,14 @@ per-item agent merge, the doctor probes, and the discovery hooks — like
7
7
 
8
8
  Formato: [Keep a Changelog](https://keepachangelog.com/) · per-agent SemVer.
9
9
 
10
+ ## [seo-analytics-strategist v1.1.0] — 2026-07-03 (framework v5.3.0)
11
+
12
+ - `model: haiku` → `model: sonnet` — haiku is banned fleet-wide (REGISTRY
13
+ § Rules, v5.3.0): the FEAT-0067 post-mortem showed a haiku plumbing agent
14
+ dropping the `cd <worktree>` invariant between two consecutive git commands
15
+ and committing to the main checkout's trunk. No named agent may run below
16
+ the sonnet floor. `version:` frontmatter added (was pre-v5.0 style).
17
+
10
18
  ## [ui-expert v2.1.0] — 2026-07-03 (framework v5.2.0)
11
19
 
12
20
  The `/ui-design` v2.0.0 studio refactor. No H2 moved; edits are within
@@ -210,9 +210,9 @@ Use this table when spawning agents via the `Task` tool. The `model` field in ea
210
210
  | **hybrid-ml-architect** | opus | — (always opus) | ML architecture is complex by nature |
211
211
  | **senior-researcher** | sonnet | opus at orchestrator's discretion for `PROFILE=deep` surveys | Web-native retrieval + synthesis — sonnet is sufficient and fast; depth comes from the profile loop (`DEPTH=`), not the model |
212
212
 
213
- **Rules**: Never use haiku for any **named specialist agent** in the matrix above. Opus for code writing and creative/complex work. Sonnet for analysis, review, and documentation. `qa-sentinel` stays on sonnet even though it is a mechanical gate runner — its failure interpretation and SCOPED-vs-FULL tiering benefit from sonnet.
213
+ **Rules**: **Haiku is BANNED fleet-wide** — never for the named specialist agents in the matrix above, and never for `general-purpose` plumbing either (see below). Opus for code writing and creative/complex work. **Sonnet is the floor** for everything else: analysis, review, documentation, AND mechanical plumbing. `qa-sentinel` stays on sonnet even though it is a mechanical gate runner — its failure interpretation and SCOPED-vs-FULL tiering benefit from sonnet.
214
214
 
215
- **Plumbing carve-out (haiku allowed):** the "never haiku" rule above governs the specialist agents. **Mechanical plumbing spawned as `general-purpose`** with no reasoning and an explicit ROLE BOUNDARY MAY use `model: "haiku"` via override. One sanctioned use today, in `/new`: the **file-scoped revert agent** that restores unauthorized files to their pre-commit state (`references/implement.md` Phase 2.4) a deterministic git/file op, never code authoring. (The background **worktree-setup** subagent `references/setup.md` §4b was haiku through v4.41.0 but moved to **`model: "sonnet"`** in v4.42.0: it runs the multi-step `/nw` skill **via the Skill tool**, a sustained tool-execution chain a too-weak model fabricates rather than executes the observed 2/2 `baseline: pass`-with-no-worktree failures. Its return is verified by the orchestrator's disk gate regardless, so a capable model is the right trade.)
215
+ **Plumbing carve-out ABOLISHED (v5.3.0):** through v5.2.0 mechanical `general-purpose` plumbing MAY have used `model: "haiku"` via override. That carve-out is **revoked** after two independent haiku failures on "trivially mechanical" ops: (1) the worktree-setup fabrication (2/2 `baseline: pass` with nothing on disk v4.42.0, moved to sonnet); (2) the FEAT-0067 commit corruption the haiku commit agent ran its first git command with `cd <worktree> &&` and its SECOND without, so a card-DONE bookkeeping commit executed in the MAIN checkout and landed on the trunk mid-run, forking `ssot-registry.md`, triggering a merge BLOCKER, and blocking epic closure. The lesson generalizes: multi-step shell plumbing is a sustained tool-execution chain, and a too-weak model drops invariants (cwd, ROLE BOUNDARY, staging scope) between steps. Where the op is truly deterministic, the fix is a **script, not a cheaper model** (`setup-worktree.sh`, `revert-files.sh`); where a model stays in the loop, the floor is `sonnet`.
216
216
 
217
217
  ## Notes
218
218
 
@@ -1,7 +1,8 @@
1
1
  ---
2
2
  name: seo-analytics-strategist
3
3
  description: "Use this agent when you need to define SEO strategy, metadata requirements, analytics event plans, or technical SEO specifications for a company website. This agent produces actionable requirements documents that coder agents can implement directly.\n\nExamples:\n\n<example>\nContext: User is building a new company website and needs SEO foundations.\nuser: \"We're launching our company website next month and need to make sure it's discoverable on Google.\"\nassistant: \"I'll use the SEO & Analytics Strategist agent to create a comprehensive SEO and analytics plan for your website launch.\"\n<Task tool invocation to launch seo-analytics-strategist agent>\n</example>\n\n<example>\nContext: User wants to add analytics tracking to existing pages.\nuser: \"We need to track user interactions on our website - button clicks, form submissions, that kind of thing.\"\nassistant: \"Let me invoke the SEO & Analytics Strategist agent to design a complete GA4/GTM event tracking plan with exact specifications for implementation.\"\n<Task tool invocation to launch seo-analytics-strategist agent>\n</example>\n\n<example>\nContext: User mentions search rankings or organic traffic concerns.\nuser: \"Our competitors are ranking higher than us for industry keywords. What should we do?\"\nassistant: \"I'll use the SEO & Analytics Strategist agent to develop a keyword and intent map along with a metadata strategy to improve your search visibility.\"\n<Task tool invocation to launch seo-analytics-strategist agent>\n</example>\n\n<example>\nContext: Developer needs SEO requirements before implementing pages.\nuser: \"I'm about to code the new services pages. What SEO elements do I need to include?\"\nassistant: \"Let me launch the SEO & Analytics Strategist agent to provide you with exact SEO requirements including metadata, schema markup, and internal linking specifications that you can implement directly.\"\n<Task tool invocation to launch seo-analytics-strategist agent>\n</example>"
4
- model: haiku
4
+ version: 1.1.0
5
+ model: sonnet
5
6
  color: pink
6
7
  ---
7
8
 
@@ -2,6 +2,25 @@
2
2
 
3
3
  Formato: [Keep a Changelog](https://keepachangelog.com/) · [SemVer](https://semver.org/).
4
4
 
5
+ ## 1.1.0 — 2026-07-03
6
+
7
+ ### Fixed — local-trunk sync robustness (step 4c "Common", both strategies)
8
+
9
+ Il sync del trunk locale falliva l'ff nella maggior parte dei checkout reali,
10
+ lasciando `SYNC-DEFERRED` / `SYNC-NEEDS-DECISION` inutilmente. Due fix mirati,
11
+ **senza `git stash`** (vietato — `refs/stash` è condiviso tra worktree, incidente
12
+ FEAT-0522). SSOT script `scripts/merge-worktree.sh` + prose SKILL.md tenuti in sync.
13
+
14
+ - **Fix 1** — quando il main è su un branch ≠ `$TRUNK`, avanza il *ref* locale
15
+ `refs/heads/$TRUNK` con un fetch ref-only ff-only (`git fetch origin $TRUNK:$TRUNK`),
16
+ **senza toccare il working tree** e senza checkout. Converte una grossa fetta di
17
+ `SYNC-DEFERRED` in sync pulito (`sync_marker: none`). Se `$TRUNK` è checked out in
18
+ un altro worktree git rifiuta → fallback al fetch-only + defer.
19
+ - **Fix 2** — quando l'ff fallisce con tree tracked-clean e nessun file owned/foreign,
20
+ disambigua via `merge-base --is-ancestor` tra **divergenza reale** del branch (serve
21
+ rebase/merge) e **collisione con file untracked** (elencati) — prima riportava sempre
22
+ il fuorviante "clean (diverged?)" (lo scan usa `--untracked-files=no`).
23
+
5
24
  ## 1.0.0 — 2026-07-01
6
25
 
7
26
  - Baseline: versioning per-skill introdotto al framework v4.82.0.
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  name: worktree-manager
3
3
  effort: medium
4
- version: 1.0.0
4
+ version: 1.1.0
5
5
  description: >
6
6
  Manage fully independent git worktrees for parallel coding agents.
7
7
  Commands: /nw (new worktree) creates an isolated workspace with its own
@@ -1168,16 +1168,37 @@ if [ "$CURRENT_BRANCH" = "$TRUNK" ]; then
1168
1168
  echo "[SYNC-NEEDS-DECISION] framework-managed reconcile rebase conflict on $TRUNK — orchestrator MUST raise ONE explicit user gate."
1169
1169
  fi
1170
1170
  else
1171
- echo "[SYNC-NEEDS-DECISION] main repo $TRUNK cannot fast-forward and the working tree is clean (diverged?) orchestrator MUST raise ONE explicit user gate."
1171
+ # Fix 2 tracked tree clean, nothing owned/foreign dirty, yet ff failed.
1172
+ # Disambiguate a genuine branch divergence from an untracked-file collision the
1173
+ # incoming ff would overwrite (origin/$TRUNK is current — the pull fetched it).
1174
+ # NEVER stash to clear it (refs/stash is shared across worktrees — FEAT-0522).
1175
+ if git -C "$MAIN" merge-base --is-ancestor "$TRUNK" "origin/$TRUNK" 2>/dev/null; then
1176
+ COLLIDE=""
1177
+ for f in $(git -C "$MAIN" ls-files --others --exclude-standard); do
1178
+ git -C "$MAIN" cat-file -e "origin/$TRUNK:$f" 2>/dev/null && COLLIDE="$COLLIDE $f"
1179
+ done
1180
+ echo "[SYNC-NEEDS-DECISION] main repo $TRUNK is fast-forwardable but untracked file(s) block the merge:${COLLIDE:- (unknown)} — orchestrator MUST raise ONE explicit user gate."
1181
+ else
1182
+ echo "[SYNC-NEEDS-DECISION] main repo $TRUNK has diverged from origin/$TRUNK (local commits not on origin) — needs a rebase/merge decision; orchestrator MUST raise ONE explicit user gate."
1183
+ fi
1172
1184
  fi
1173
1185
  fi
1174
1186
  else
1175
- echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' not switching. Next manual \`git pull\` on $TRUNK will sync."
1176
- git -C "$MAIN" fetch origin "$TRUNK" --quiet
1177
- # Structured marker for upstream orchestrators (e.g. /new Phase 6c).
1178
- # `/mw` standalone keeps fetch-only behavior (terminal isolation); orchestrators
1179
- # parse this marker and route reconciliation through an explicit user gate.
1180
- echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded — orchestrator workspace-hygiene phase must reconcile."
1187
+ # Fix 1 main is on another branch: do NOT checkout $TRUNK. Fast-forward the local
1188
+ # $TRUNK *ref* without touching any working tree via a ref-only, ff-only fetch.
1189
+ # `git fetch origin $TRUNK:$TRUNK` advances refs/heads/$TRUNK only on a fast-forward
1190
+ # and fails cleanly otherwise (never a checkout, never a stash). If $TRUNK is checked
1191
+ # out in another worktree git refuses we fall back to fetch-only + defer.
1192
+ if git -C "$MAIN" fetch origin "$TRUNK:$TRUNK" --quiet 2>/dev/null; then
1193
+ echo "✅ Local $TRUNK ref fast-forwarded to origin/$TRUNK (main HEAD='$CURRENT_BRANCH' untouched)."
1194
+ else
1195
+ echo "ℹ️ Main repo HEAD is '$CURRENT_BRANCH' — not switching. Next manual \`git pull\` on $TRUNK will sync."
1196
+ git -C "$MAIN" fetch origin "$TRUNK" --quiet
1197
+ # Structured marker for upstream orchestrators (e.g. /new Phase 6c).
1198
+ # `/mw` standalone keeps fetch-only behavior (terminal isolation); orchestrators
1199
+ # parse this marker and route reconciliation through an explicit user gate.
1200
+ echo "[SYNC-DEFERRED] main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded (diverged or checked out elsewhere) — orchestrator workspace-hygiene phase must reconcile."
1201
+ fi
1181
1202
  fi
1182
1203
  ```
1183
1204
 
@@ -492,12 +492,34 @@ if [ "$CURRENT_BRANCH" = "$TRUNK" ]; then
492
492
  M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="framework-managed reconcile rebase conflict on $TRUNK"
493
493
  fi
494
494
  else
495
- M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="main repo $TRUNK cannot fast-forward and the working tree is clean (diverged?)"
495
+ # Fix 2 tracked tree clean, nothing owned/foreign dirty, yet ff failed.
496
+ # Disambiguate a genuine branch divergence from an untracked-file collision
497
+ # the incoming ff would overwrite (origin/$TRUNK is current — pull fetched it).
498
+ # NEVER stash to clear it (refs/stash is shared across worktrees — FEAT-0522).
499
+ if git -C "$MAIN" merge-base --is-ancestor "$TRUNK" "origin/$TRUNK" 2>/dev/null; then
500
+ COLLIDE=""
501
+ for f in $(git -C "$MAIN" ls-files --others --exclude-standard 2>/dev/null); do
502
+ git -C "$MAIN" cat-file -e "origin/$TRUNK:$f" 2>/dev/null && COLLIDE="$COLLIDE $f"
503
+ done
504
+ M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="main repo $TRUNK is fast-forwardable but untracked file(s) block the merge:${COLLIDE:- (unknown)} — orchestrator MUST raise ONE explicit user gate"
505
+ else
506
+ M_SYNCMARK="SYNC-NEEDS-DECISION"; M_SYNCDETAIL="main repo $TRUNK has diverged from origin/$TRUNK (local commits not on origin) — needs a rebase/merge decision, orchestrator MUST raise ONE explicit user gate"
507
+ fi
496
508
  fi
497
509
  fi
498
510
  else
499
- git -C "$MAIN" fetch origin "$TRUNK" --quiet 2>>"$LOG" || true
500
- M_SYNCMARK="SYNC-DEFERRED"; M_SYNCDETAIL="main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded"
511
+ # Fix 1 main is on another branch: do NOT checkout $TRUNK. Fast-forward the local
512
+ # $TRUNK *ref* (refs/heads/$TRUNK) WITHOUT touching any working tree via a ref-only,
513
+ # ff-only fetch. `git fetch origin $TRUNK:$TRUNK` advances the local branch ref only
514
+ # on a fast-forward and fails cleanly (non-destructive) otherwise — never a checkout,
515
+ # never a stash. If $TRUNK is checked out in another worktree git refuses and we fall
516
+ # back to the deferred fetch below.
517
+ if git -C "$MAIN" fetch origin "$TRUNK:$TRUNK" --quiet 2>>"$LOG"; then
518
+ M_SYNCMARK="none"; M_SYNCDETAIL="local $TRUNK ref fast-forwarded to origin (main HEAD=${CURRENT_BRANCH}, working tree untouched)"
519
+ else
520
+ git -C "$MAIN" fetch origin "$TRUNK" --quiet 2>>"$LOG" || true
521
+ M_SYNCMARK="SYNC-DEFERRED"; M_SYNCDETAIL="main repo HEAD=${CURRENT_BRANCH}, local $TRUNK not fast-forwarded (diverged or checked out elsewhere)"
522
+ fi
501
523
  fi
502
524
 
503
525
  # 7. Cleanup worktree + registry (only after a verified land). Individual cmds.
@@ -321,7 +321,7 @@ if (typeof a.codexScriptPath === 'string' && /codex-companion\.mjs$/.test(a.code
321
321
  `Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
322
322
  " ls -d ~/.claude/plugins/marketplaces/openai-codex/plugins/codex/scripts/codex-companion.mjs ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs 2>/dev/null | sort -V | tail -1\n" +
323
323
  `Return codexScriptPath = the trimmed stdout (a single absolute path), or "" if the command printed nothing. Do NOT run Codex, do NOT read any other file, do NOT reason about availability — just report the command's stdout.`,
324
- { label: 'codex-preflight', phase: 'Discovery', model: 'haiku', schema: PREFLIGHT_SCHEMA }
324
+ { label: 'codex-preflight', phase: 'Discovery', model: 'sonnet', schema: PREFLIGHT_SCHEMA }
325
325
  )
326
326
  codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
327
327
  } catch (_) { codexScriptPath = '' }
@@ -302,7 +302,7 @@ if (typeof a.codexScriptPath === 'string' && /codex-companion\.mjs$/.test(a.code
302
302
  `Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
303
303
  " ls -d ~/.claude/plugins/marketplaces/openai-codex/plugins/codex/scripts/codex-companion.mjs ~/.claude/plugins/cache/openai-codex/codex/*/scripts/codex-companion.mjs 2>/dev/null | sort -V | tail -1\n" +
304
304
  `Return codexScriptPath = the trimmed stdout (a single absolute path), or "" if the command printed nothing. Do NOT run Codex, do NOT read any other file, do NOT reason about availability — just report the command's stdout.`,
305
- { label: 'codex-preflight', phase: 'Review', model: 'haiku', schema: PREFLIGHT_SCHEMA }
305
+ { label: 'codex-preflight', phase: 'Review', model: 'sonnet', schema: PREFLIGHT_SCHEMA }
306
306
  )
307
307
  codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
308
308
  } catch (_) { codexScriptPath = '' }
@@ -505,7 +505,7 @@ async function rollbackCard(cardId, mayEdit) {
505
505
  try {
506
506
  const r = await agentSafe(
507
507
  `In the worktree ${sharedCtx.worktreePath}, restore the working tree to a CLEAN state for a FAILED card: \`git restore --source=HEAD --worktree --staged -- ${scope}\` then \`git clean -fd ${scope === '.' ? '' : '-- ' + scope}\` (with scope '.' this cleans the whole worktree — safe: all committed work lives in HEAD). Do NOT touch other cards' committed work. Confirm \`git status --porcelain\` is empty for the scope.`,
508
- { label: `rollback:${cardId}`, phase: 'Implement', agentType: 'general-purpose', model: 'haiku',
508
+ { label: `rollback:${cardId}`, phase: 'Implement', agentType: 'general-purpose', model: 'sonnet',
509
509
  schema: { type: 'object', required: ['clean'], additionalProperties: true, properties: { clean: { type: 'boolean' }, note: { type: 'string' } } } }
510
510
  )
511
511
  return !!(r && r.clean)
@@ -562,7 +562,8 @@ async function runCard(cardId, cardPath) {
562
562
  // agents/analysis-profiles.md § 3 caller routing table.
563
563
  `PROFILE=${node.hasMockup ? 'ui' : 'feature'} OUTPUT=terse — resolve your analysis profile per agents/analysis-profiles.md (Grep "### PROFILE: ${node.hasMockup ? 'ui' : 'feature'}", Read only that section + its cited shared blocks).\n\n${cardBrief}\n\n` +
564
564
  `Explore the codebase exactly as your system prompt + profile mandate for this card's scope (requirements + files_likely_touched: ${JSON.stringify(node.filesLikelyTouched || [])}). Write your COMPLETE untruncated findings (file paths, type signatures, patterns, high-risk paths) to ${baselinePath} — refresh it if a stale copy exists. The owner agent and the per-card reviewers will Read that file; keep your structured return MINIMAL.\n\n` +
565
- `Also report missingPaths: every path in files_likely_touched that does NOT exist in the worktree (factual ls/test check — no judgement). Return: { ok, missingPaths:[...], note }`,
565
+ `Also report missingPaths: every path in files_likely_touched that the card expects to ALREADY exist but does NOT exist in the worktree (factual ls/test check — no judgement). EXCLUDE paths the card itself CREATES (NEW files: migrations with placeholder timestamps, new tests, new components declared in scope) — those are not drift (FEAT-0067 false-positive class).\n\n` +
566
+ `Your StructuredOutput MUST be exactly this shape — the boolean property \`ok\` is REQUIRED at the top level (missing it fails schema validation and burns a retry): { "ok": true|false, "missingPaths": ["..."], "note": "..." }. ok:false ONLY if you could not explore the worktree at all.`,
566
567
  { label: `architect:${cardId}`, phase: 'Implement', agentType: 'codebase-architect',
567
568
  schema: { type: 'object', required: ['ok'], additionalProperties: true, properties: { ok: { type: 'boolean' }, missingPaths: { type: 'array', items: { type: 'string' } }, note: { type: 'string' } } } }
568
569
  )
@@ -853,11 +854,12 @@ async function runCard(cardId, cardPath) {
853
854
  `Read the Codex output ONLY through a [codex]-trace-stripping filter. **Fallback**: if it exits non-zero / prints CODEX_NOT_FOUND / stays empty, return note:'codex-unavailable' with EMPTY blocks/scopeExpansion — do NOT review yourself (role boundary); the workflow spawns the real code-reviewer.\n\n` +
854
855
  `Map Codex BLOCKER/HIGH findings to blocks:[{gate:'codex-light',domain,evidence}] (each non-empty gate AND evidence — F-014). Map legitimate findings BEYOND this card's AC to scopeExpansion:[{evidence,domain,withinOwnership,newAC}].\n\n` +
855
856
  `Return: { blocks:[...], scopeExpansion:[...], note }`,
856
- // Haiku, not the inherited opus: this agent is a pure DRIVER — it runs the Codex companion
857
+ // Sonnet, not the inherited opus: this agent is a pure DRIVER — it runs the Codex companion
857
858
  // (node + --wait), strips [codex] traces, and maps the companion's findings into the schema.
858
859
  // The review INTELLIGENCE is Codex (a different model, run externally); driving it needs no
859
- // opus reasoning, same as the other mechanical git/ops steps.
860
- { label: `review:${cardId}:codex`, phase: 'Implement', agentType: 'general-purpose', model: 'haiku', schema: reviewSchema }
860
+ // opus reasoning. Haiku is BANNED fleet-wide since v5.3.0 (FEAT-0067 post-mortem: a haiku
861
+ // plumbing agent dropped the `cd` on its second git command and committed to the MAIN repo).
862
+ { label: `review:${cardId}:codex`, phase: 'Implement', agentType: 'general-purpose', model: 'sonnet', schema: reviewSchema }
861
863
  ).catch(onErr)
862
864
  }
863
865
  return stdReview(ra)
@@ -929,10 +931,11 @@ async function runCard(cardId, cardPath) {
929
931
  try {
930
932
  commitRes = await agentSafe(
931
933
  `Commit card ${cardId} in worktree ${sharedCtx.worktreePath}. MECHANICAL — do NOT re-read reference modules. ROLE BOUNDARY: you NEVER modify file contents except the card YAML status/note fields, the parent-epic YAML status (epic-closure gate only), and the ssot-registry entries — source/doc changes are not yours.\n` +
932
- `Steps: (1) \`git status --porcelain\`; (2) stage = MAY-EDIT (${JSON.stringify(mayEdit)}) dirty NEVER \`git add -A\`, NEVER \`git stash\`; if dirty has files OUTSIDE MAY-EDIT, do NOT stage them and set reconcileNote; (3) commit message \`[${cardId}] <concise>\`; ${doneStep} (5) 'nothing to commit' = already committed (record HEAD).\n` +
934
+ `HARD RULE — cwd: EVERY shell command MUST begin with \`cd ${sharedCtx.worktreePath} && \` IN THE SAME Bash invocation. The shell cwd RESETS between Bash calls to the MAIN repo: a bare \`git commit\` executes against the main checkout's current branch and corrupts it (observed in FEAT-0067 — a card-DONE commit landed on the trunk mid-run). No exceptions, including 'quick' status/log commands.\n` +
935
+ `Steps: (1) \`git status --porcelain\`; (2) stage = MAY-EDIT (${JSON.stringify(mayEdit)}) ∩ dirty — NEVER \`git add -A\`, NEVER \`git stash\`; if dirty has files OUTSIDE MAY-EDIT, do NOT stage them and set reconcileNote; (3) commit message \`[${cardId}] <concise>\`; ${doneStep} (5) 'nothing to commit' = already committed (record HEAD); (6) VERIFY placement: \`cd ${sharedCtx.worktreePath} && git log -1 --format=%H\` MUST equal the sha you return — if it does not, your commit landed outside the worktree: report committed:false with reconcileNote naming the stray sha, do NOT retry blindly.\n` +
933
936
  `On COMMIT_LOCK: clear stale lock + retry once. Still locked → committed:false.\n\n` +
934
937
  `Return: { committed, commit, filesChanged, reconcileNote }`,
935
- { label: `commit:${cardId}`, phase: 'Implement', agentType: 'general-purpose', model: 'haiku',
938
+ { label: `commit:${cardId}`, phase: 'Implement', agentType: 'general-purpose', model: 'sonnet',
936
939
  schema: { type: 'object', required: ['committed'], additionalProperties: true, properties: { committed: { type: 'boolean' }, commit: { type: 'string' }, filesChanged: { type: 'array', items: { type: 'string' } }, reconcileNote: { type: 'string' } } } }
937
940
  )
938
941
  } catch (e) {
@@ -1116,10 +1119,10 @@ async function integrateCrossCard() {
1116
1119
  // ONE integration commit (the fixes cross card boundaries); mark fully-freed cards DONE in YAML.
1117
1120
  try {
1118
1121
  await agentSafe(
1119
- `Commit the cross-card integration fixes in worktree ${sharedCtx.worktreePath}. MECHANICAL — do NOT modify source beyond what is already changed on disk. Steps:\n` +
1122
+ `Commit the cross-card integration fixes in worktree ${sharedCtx.worktreePath}. MECHANICAL — do NOT modify source beyond what is already changed on disk. HARD RULE — cwd: EVERY shell command MUST begin with \`cd ${sharedCtx.worktreePath} && \` in the SAME Bash invocation (the shell cwd resets to the MAIN repo between calls; a bare git command corrupts the main checkout). Steps:\n` +
1120
1123
  `(1) \`git status --porcelain\`; (2) stage ONLY dirty files within the batch union ${JSON.stringify(batchUnion)} — NEVER \`git add -A\`, NEVER \`git stash\`; (3) for each fully-freed card YAML [${fullyFreed.join(', ') || '(none)'}] set status to DONE and add implementation_note "cross-card integration: deferred AC resolved with batch-union ownership (new2 v4.30.0)"${SESSION_ID ? ' and set provenance.implementation_session: "' + SESSION_ID + '" (do NOT touch planning_session)' : ''}; (4) commit message \`[integrate] cross-card residual fixes${fullyFreed.length ? ' (' + fullyFreed.join(', ') + ')' : ''}\`; (5) 'nothing to commit' = the resolve already committed (record HEAD).\n` +
1121
1124
  `Return: { committed, commit, filesChanged }`,
1122
- { label: 'integrate:commit', phase: 'Integrate', agentType: 'general-purpose', model: 'haiku',
1125
+ { label: 'integrate:commit', phase: 'Integrate', agentType: 'general-purpose', model: 'sonnet',
1123
1126
  schema: { type: 'object', required: ['committed'], additionalProperties: true, properties: { committed: { type: 'boolean' }, commit: { type: 'string' }, filesChanged: { type: 'array', items: { type: 'string' } } } } }
1124
1127
  )
1125
1128
  } catch (e) { if (e && e.transientExhausted) noteDegraded('outage') }
@@ -1259,7 +1262,7 @@ if (!committed.length) {
1259
1262
  // YAML status reconciliation + grep-based epic closure + leave-and-report hygiene gates), and
1260
1263
  // the correctness-critical checks (F-029 forcedDone guard, F-040 deferred guard) are enforced
1261
1264
  // in JS AFTER it returns — they do NOT depend on the agent's reasoning. Opus here is pure cost;
1262
- // haiku would be too weak for the conditional epic-closure / G19-23 divergence logic.
1265
+ // sonnet is also the fleet-wide plumbing floor (haiku banned since v5.3.0).
1263
1266
  { label: 'merge', phase: 'Merge', agentType: 'general-purpose', model: 'sonnet', schema: MERGE_SCHEMA }
1264
1267
  )
1265
1268
  } catch (e) { if (e && e.transientExhausted) noteDegraded('outage'); mergeResult = null }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "5.2.0",
3
+ "version": "5.3.0",
4
4
  "description": "Claude Agent Framework - Reusable framework for coordinating AI agents and humans in software projects",
5
5
  "bin": {
6
6
  "baldart": "./bin/baldart.js"