baldart 4.53.7 → 4.53.8

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,26 @@ 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.8] - 2026-06-19
9
+
10
+ **Five `/new` refinements from a deep cost/correctness pass over the same `/new FEAT-0035` epic run that produced v4.53.4–6** (re-reading the orchestrator + all subagent + workflow transcripts of session `e502c890`). The v4.53.5 gate prose was present in this very run yet still not executed — the analysis isolated *why*, plus four other concrete wastes. Recurring theme: in two places the orchestrator ignored a discipline that already existed — so these make existing rules **executable/forcing**, they do not add new policy.
11
+
12
+ 1. **Empty-result gate: the "read the rested agent's LAST event" step is now executable for background teammates.** v4.53.5 told the orchestrator to classify rate-limit-death vs genuine-empty-result by reading the dead teammate's last event — but a background teammate killed mid-flight delivers *nothing* to the orchestrator, so "no report + no diff" is the identical observable for both causes. In this run the orchestrator therefore classified the two rate-limit deaths (03, 07) as *generic empty-result* and re-fired both within 16s (same burst), the exact anti-pattern v4.53.5 forbids. The gate now spells out HOW: the failure signature lives ONLY in the teammate's on-disk transcript (`<session-subagents-dir>/agent-*.jsonl`, matched by label via the sibling `*.meta.json`) — locate + `tail` it, look for `isApiErrorMessage`/`Rate limited`/`Server is temporarily limiting requests`/`(not your usage limit)`, and STATE the classification + evidence before choosing the remedy. (`new2` is unaffected — its F-019 workflow runtime surfaces transient errors to the script directly, no disk dig needed.)
13
+ 2. **No more SendMessage-to-fetch-a-report.** Same gate locator is reused for report retrieval: when a teammate rests without its final message captured, `SendMessage`-ing it forces a full-context **resume** (~10–15M tokens reloaded just to re-emit a report) — observed twice on the L0 coders in this run, then ignored. Step C now forbids it: the completion-report is already the teammate's last transcript message; read it from disk for free.
14
+ 3. **Per-wave fix-agent scope discipline.** The wave-3 "apply ALL verified findings" coder ran away to 215 turns / ~32M tokens (the single most expensive agent in the run) by re-surveying the architecture. `new-card-review.js` `applyFixPass` brief now mandates going DIRECTLY to each finding's cited `evidence` file:line, finding-by-finding, no re-mapping the codebase (a verified finding is a precise instruction, not a research prompt).
15
+ 4. **grep binary-heuristic false-empty added to the grep-verification discipline.** The orchestrator burned ~6 turns when plain `grep` treated an 8000-line accented `data.ts` as *binary* and returned 0 matches for present tokens. The discipline (completeness.md) gains a third failure mode: prefer the already-mandated `rg`; if you must use `grep`, `grep -a`; a 0-match from plain `grep` on a known-modified file is almost always this artifact.
16
+ 5. **Codex companion path resolved once per run, passed via workflow args.** Both review workflows spawned a Haiku resolver agent per invocation (4× in this run). They now accept `args.codexScriptPath` (resolved once by the orchestrator, threaded at all three delegation sites) and **fall back to the in-workflow resolver when absent** — fully backward-compatible.
17
+
18
+ **PATCH** (refinements to `/new` orchestration + one optional, fallback-guarded workflow arg; no new agent/skill/command, no `baldart.config.yml` key, no install/layout change).
19
+
20
+ ### Fixed / Changed
21
+
22
+ - **`framework/.claude/skills/new/references/team-mode.md`** — empty-result gate: concrete on-disk transcript locator + rate-limit signatures + mandate to state the classification (Int 1 of the pass); Step C forbids `SendMessage`-to-fetch-report and points at the same locator (Int 2); Step-D delegation args gain `codexScriptPath` with a resolve-once glob (Int 5).
23
+ - **`framework/.claude/skills/new/references/completeness.md`** — Grep-verification discipline: "Two failure modes" → "Three", adds the binary-heuristic false-empty bullet (`rg`/`grep -a`).
24
+ - **`framework/.claude/workflows/new-card-review.js`** — `fixBrief` gains a SCOPE DISCIPLINE clause (go to the cited file:line, no re-survey); accepts `args.codexScriptPath` (caller-provided) with Haiku-resolver fallback.
25
+ - **`framework/.claude/workflows/new-final-review.js`** — accepts `args.codexScriptPath` with Haiku-resolver fallback.
26
+ - **`framework/.claude/skills/new/references/review-cycle.md`** + **`final-review.md`** — the two remaining `Workflow({...})` delegation sites pass `codexScriptPath` (resolve-once, omit-if-empty).
27
+
8
28
  ## [4.53.7] - 2026-06-19
9
29
 
10
30
  **`setup-worktree.sh` no longer turns an empty `toolchain.commands.build` into a spurious `baseline: fail`.** Observed on a real `/new FEAT-0037` run in a consumer (`mayo`) whose `baldart.config.yml` declares `build: ''` (single-quoted empty scalar — the project's build is not a separate toolchain gate; per `toolchain-protocol.md` an empty command must fall back to the default `npm run build`). The deterministic SSOT worktree script (v4.53.0) read that value with a sed that stripped only DOUBLE quotes (`\"?([^\"#]*)\"?`), so `build: ''` came through as the **literal 2-char string `''`** — non-empty — so the `[ -n "$TC_BUILD" ]` fallback never fired and the script ran `bash -c "''"`, an empty *quoted* command → `bash: : command not found` (exit 127) → `baseline: fail`. tsc + biome had actually passed and the worktree was green on disk; the failure was entirely the unresolved empty scalar. Fix: (1) `_tc()` now resolves a YAML scalar to its true value — `build:`, `build: ''`, `build: ""`, `build: ~`, `build: null` all collapse to empty so the default fallback fires (strips one layer of matching single OR double quotes + YAML null markers + inline `# comment`); (2) the script now tracks build provenance and implements the protocol's **SKIP tier** — the *fallback* `npm run build` on a project with no `build` npm script is a no-build library → SKIP (baseline stays pass), never a spurious fail, while a *configured* (non-empty) build command is always run as a real gate. Verified by executing the resolver against every empty-scalar form + real values, the fallback/SKIP decision on a has-build vs no-build `package.json`, and reproducing the exact `bash: : command not found` exit-127 from the old sed. The prose inline fallbacks (`setup.md`, `worktree-manager/SKILL.md`, `new2.js`) are model-driven and never had the sed bug — no parallel fix needed. **PATCH** (deterministic-script bugfix; correctly-configured consumers see no behaviour change; no new agent/skill/command/config key, no install change).
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.53.7
1
+ 4.53.8
@@ -6,9 +6,10 @@
6
6
 
7
7
  Before triggering any review, you MUST verify that the coder agent implemented **every requirement and acceptance criterion** in the backlog card. This is a blocking gate — do NOT proceed to Phase 3 with unimplemented items.
8
8
 
9
- > **Grep-verification discipline (MUST — applies to every grep check in this phase: step 0, step 4, the `data_fields` checks).** The completeness checks below confirm an item by grepping the changed files for a token. Two failure modes turn a *false* "not found" into a *false* "Missing/Partial":
9
+ > **Grep-verification discipline (MUST — applies to every grep check in this phase: step 0, step 4, the `data_fields` checks).** The completeness checks below confirm an item by grepping the changed files for a token. Three failure modes turn a *false* "not found" into a *false* "Missing/Partial":
10
10
  > 1. **Quote-broken pattern.** Code-shaped tokens contain shell/regex metacharacters — a TS union literal `mode:'catalog'|'picker'`, an object key, anything with `'` `"` `|` `[]` `?` `(`. Embedding such a pattern in an `echo "=== … ===" && grep …` block or an unquoted `-E` arg breaks the shell/regex parse → the grep matches **nothing even though the code is there**. **Search literals with `rg -F '<exact string>'` (fixed-string, no regex), single-quote the WHOLE pattern, and run ONE pattern per `rg` call** — never fold the search token into a quoted header/echo composition.
11
11
  > 2. **`0 matches` ≠ absent.** A zero-match result is ambiguous (true miss vs. broken pattern / wrong file / wrong escaping). Do NOT classify an item `Missing`/`Partial` on a 0-match alone. **If the 0-match CONTRADICTS the coder completion report's claim that the item is implemented, treat it as a likely broken grep — re-confirm by reading the report's cited `file:line` (or a corrected `rg -F`) BEFORE flagging or spawning a gap-fix agent.** Never re-spawn a coder on a 0-match alone. When unsure, a targeted `Read` at the report's `file:line` is more reliable than a hand-rolled grep.
12
+ > 3. **Binary-heuristic false-empty.** Plain `grep` decides a file is *binary* when it sees a byte it reads as non-text (a non-UTF-8 byte, an accented char under some locales) or very long lines, and then prints `Binary file … matches` / **nothing** instead of the line — so a perfectly valid source file (e.g. an 8000-line `data.ts` with accented strings) silently returns 0 matches for tokens that ARE present. The mandated `rg` does NOT have this failure mode (it searches UTF-8 and long lines fine). **Always reach for `rg -F` first; if you must use `grep`, force text mode with `grep -a`.** A 0-match from plain `grep` on a file you KNOW was just modified is almost always this artifact — switch to `rg`/`grep -a` and re-check before concluding the code is missing.
12
13
 
13
14
  **Step-by-step**:
14
15
 
@@ -92,7 +92,8 @@ that is a **gate violation**: log it as
92
92
  config, // the parsed baldart.config.yml
93
93
  singleCard: cardPaths.length === 1, // N=1 batch — enables the F-041 per-finder slim
94
94
  slimDoc: cardPaths.length === 1 && <Phase-3 doc-review RAN for this card, NOT deferred>,
95
- slimApi: false // api-perf NEVER runs per-card in classic /new → final is its ONLY run; never slim it
95
+ slimApi: false, // api-perf NEVER runs per-card in classic /new → final is its ONLY run; never slim it
96
+ codexScriptPath // resolved ONCE per run (glob in team-mode.md § Step D); omit if empty → workflow self-resolves
96
97
  }})
97
98
  ```
98
99
 
@@ -46,9 +46,10 @@ so it surfaces in telemetry.
46
46
  ```
47
47
  Workflow({ name: 'new-card-review', args: {
48
48
  cards: [{ cardId, cardPath, scopeFiles, editableFiles, qaTier, hasSecurityFiles, runSimplify, archBaselinePath }],
49
- worktreePath, baseBranch, config // from Phase 0 / tracker
49
+ worktreePath, baseBranch, config, codexScriptPath // from Phase 0 / tracker
50
50
  }})
51
51
  ```
52
+ `codexScriptPath` — resolve the Codex companion path ONCE per `/new` run (glob in `references/team-mode.md` § Step D delegation) and reuse it here + at the Final, so the workflow skips its internal Haiku resolver; omit when stdout is empty (the workflow self-resolves as fallback).
52
53
 
53
54
  The workflow runs Simplify + Codex (agent-launched, code-reviewer fallback) + qa-sentinel + security,
54
55
  FP-checks each specialist's own findings, then the **domain writer applies its VERIFIED findings**
@@ -138,7 +138,7 @@ The orchestrator waits for ALL background agents in the group to complete. It wi
138
138
  > **Do NOT poll with `sleep N; echo "waiting..."` loops.** Background agents (and background `Bash` commands) re-invoke the orchestrator automatically when they finish — issuing `sleep`/`echo` "waiting" turns burns orchestrator turns and tokens for zero progress. Simply end your turn after spawning the background work; you will be woken on completion. This applies to EVERY barrier in Step D (D.1 build, D.2 reviewers, D.3/D.3b agents, D.4 qa-sentinel, D.4a doc-reviewer, D.4b `/codexreview`), not just Step C.
139
139
 
140
140
  For each completed agent:
141
- 1. Read the completion report from the agent's output.
141
+ 1. Read the completion report from the agent's returned output. **If the output did not arrive as content (the teammate went idle / "rested" without you capturing its final message), do NOT `SendMessage` the teammate to ask for the report** — messaging a rested background teammate forces a full-context **resume** that reprocesses its entire transcript (~10–15M tokens reloaded) just to re-emit a report you can read for free. The completion-report block is already the teammate's last message in its on-disk transcript: read it with the SAME locator as the empty-result gate below (`tail`/`grep 'completion-report'` the `<session-subagents-dir>/agent-*.jsonl` that matches the label). Disk is the source of truth either way.
142
142
  2. Log to tracker: card ID, status, files changed, build/lint status.
143
143
  3. Do NOT read or store implementation details — only the verdict.
144
144
 
@@ -146,8 +146,11 @@ For each completed agent:
146
146
  - **A Step-7 completion report was emitted.** A teammate that rested with **no completion report = empty-result failure.** (Do not infer "done" from the rest event alone.)
147
147
  - **Its `files_changed` actually changed in the worktree** — `git status --porcelain` intersected with that card's File Ownership Map is non-empty and covers the claimed paths (use the grep-verification discipline above — never a quote-broken check). **Zero changes in the card's ownership files = empty-result failure; report claims files that are not on disk = fabrication.**
148
148
  - A genuinely legitimate no-op card (its AC turned out already satisfied) is the rare exception: confirm it from the report's per-requirement `evidence` pointing at PRE-EXISTING code, and log it explicitly as `no-op (AC pre-satisfied)` — never assume zero-diff means success.
149
- **Classify the CAUSE before re-spawning — read the rested agent's LAST event:**
150
- - **Transient infra failure (the common cause of a silent empty rest in a WIDE parallel wave).** The agent's last output is an API error / `Rate limited` / overload / `(not your usage limit)` i.e. the background teammate was **killed mid-flight by API rate-limiting** (it had done real work — reads, a plan — then died), NOT a model fabrication or "decided nothing to do". The runtime does NOT auto-resume a dead teammate, so it rests with no report + no diff. **Re-spawn it, but STAGGERED with backoff — do NOT re-fire several transient-failed agents in the same parallel burst** (that re-saturates the API and kills them again). Space them out (one at a time, or after a brief pause). This still costs a fresh run (the killed agent's reads are lost — the runtime cannot resume it), so **prevention beats recovery**: if a wave repeatedly hits rate limits, NARROW the parallel fan-out width for that wave (spawn fewer coders at once) rather than re-firing the full wave. A transient failure does NOT consume the Step-B genuine-failure budget.
149
+ **Classify the CAUSE before re-spawning — you MUST read the rested agent's LAST event, never infer the cause from "no report" alone.** A background teammate killed mid-flight dies WITHOUT delivering anything to you, so its failure signature lives ONLY in its on-disk transcript — "no report + no diff" is the SAME observable for a rate-limit death and a genuine empty-result, and they need OPPOSITE remedies. Before choosing one, locate and tail that transcript:
150
+ - Subagent transcripts live at `<session-subagents-dir>/agent-*.jsonl`, each with a sibling `agent-*.meta.json` whose `agentType` equals the teammate label. Resolve the file and read its tail, e.g.:
151
+ `D="$(ls -dt ~/.claude/projects/*/${CLAUDE_SESSION_ID:-*}/subagents 2>/dev/null | head -1)"; f="$(grep -l '"<teammate-label>"' "$D"/*.meta.json 2>/dev/null | head -1 | sed 's/\.meta\.json$/.jsonl/')"; tail -c 4000 "$f"`
152
+ (if `$CLAUDE_SESSION_ID` is unset, the most recent `subagents/` dir under this project is the live session's.) If the transcript truly cannot be located, fall back to whatever the `Agent` tool result surfaced — but do NOT default to "genuine empty-result" without having looked. **State the classification you reached + the evidence in your narration** (e.g. "03 last event = `Rate limited` → transient").
153
+ - **Transient infra failure (the common cause of a silent empty rest in a WIDE parallel wave).** The agent's last event is an API error — signatures: `isApiErrorMessage:true`, `Rate limited`, `Server is temporarily limiting requests`, `overload`, `(not your usage limit)` — i.e. the background teammate was **killed mid-flight by API rate-limiting** (it had done real work — reads, a plan — then died), NOT a model fabrication or "decided nothing to do". The runtime does NOT auto-resume a dead teammate, so it rests with no report + no diff. **Re-spawn it, but STAGGERED with backoff — do NOT re-fire several transient-failed agents in the same parallel burst** (that re-saturates the API and kills them again). Space them out (one at a time, or after a brief pause). This still costs a fresh run (the killed agent's reads are lost — the runtime cannot resume it), so **prevention beats recovery**: if a wave repeatedly hits rate limits, NARROW the parallel fan-out width for that wave (spawn fewer coders at once) rather than re-firing the full wave. A transient failure does NOT consume the Step-B genuine-failure budget.
151
154
  - **Genuine empty-result / fabrication.** The agent rested CLEANLY (no error in its last event), with no completion report and no diff. THIS is the model-fault case: take the one Step-B re-spawn below (same cap), re-briefing the coder with an explicit "write your ownership files and confirm them on disk before reporting done" mandate. A second empty result → `AskUserQuestion` (skip/abandon) — do not re-spawn a third time.
152
155
 
153
156
  **If an agent fails** (status: failed after 3 retries — the central repair cap):
@@ -204,9 +207,13 @@ After ALL agents in the group complete successfully:
204
207
  ```
205
208
  Workflow({ name: 'new-card-review', args: {
206
209
  cards: [ …one entry per non-trivial card in the wave… ],
207
- worktreePath, baseBranch, config
210
+ worktreePath, baseBranch, config, codexScriptPath
208
211
  }})
209
212
  ```
213
+ `codexScriptPath` — resolve the Codex companion path ONCE per `/new` run and reuse the cached value
214
+ for every wave + the Final (so the workflow skips its internal Haiku resolver). Resolve with:
215
+ `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`
216
+ (empty stdout → omit the arg; the workflow falls back to its own resolver — never block on this).
210
217
  The workflow fans out the finders per card, runs ONE Codex pass + ONE qa-sentinel (group max tier)
211
218
  over the union, and the **domain writer applies all VERIFIED fixes for the whole group** (canonical
212
219
  writer map v4.26.1: `security` → `security-reviewer`, then `code`/`perf`/`migration`/`test`/`simplify`
@@ -197,15 +197,23 @@ const PREFLIGHT_SCHEMA = {
197
197
  properties: { codexScriptPath: { type: 'string', description: 'absolute path to codex-companion.mjs, or "" if none' } },
198
198
  }
199
199
  let codexScriptPath = ''
200
- try {
201
- const pf = await agent(
202
- `Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
203
- " 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" +
204
- `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.`,
205
- { label: 'codex-preflight', phase: 'Discovery', model: 'haiku', schema: PREFLIGHT_SCHEMA }
206
- )
207
- codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
208
- } catch (_) { codexScriptPath = '' }
200
+ // Caller-provided path (F-???: the orchestrator resolves it ONCE and passes it in args, so we don't
201
+ // spawn a Haiku resolver per workflow invocation). Falls back to the in-workflow resolver when absent,
202
+ // so consumers/skills that don't pass it yet keep working unchanged.
203
+ if (typeof a.codexScriptPath === 'string' && /codex-companion\.mjs$/.test(a.codexScriptPath.trim())) {
204
+ codexScriptPath = a.codexScriptPath.trim()
205
+ log('Codex companion path received from caller args (resolver agent skipped).')
206
+ } else {
207
+ try {
208
+ const pf = await agent(
209
+ `Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
210
+ " 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" +
211
+ `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.`,
212
+ { label: 'codex-preflight', phase: 'Discovery', model: 'haiku', schema: PREFLIGHT_SCHEMA }
213
+ )
214
+ codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
215
+ } catch (_) { codexScriptPath = '' }
216
+ }
209
217
  const codexResolved = /codex-companion\.mjs$/.test(codexScriptPath)
210
218
  log(codexResolved
211
219
  ? `Codex companion resolved deterministically: ${codexScriptPath}`
@@ -422,6 +430,7 @@ async function applyFixPass(findings, writer, label, role) {
422
430
  `You MAY edit ONLY these files (ownership map — touching anything else is a violation):\n${unionEditable.join('\n')}\n\n` +
423
431
  `Findings to fix (fix the code, not the tests unless a test itself is wrong; do NOT expand scope beyond the finding):\n` +
424
432
  findings.map((f) => `- [${f.finding_id}] (${f.card || '?'} / ${f.domain} / ${f.severity}) ${f.title}\n evidence: ${f.evidence}\n direction: ${f.minimal_fix_direction}`).join('\n') +
433
+ `\n\nSCOPE DISCIPLINE (do not run away — every finding already cites the exact site to fix): go DIRECTLY to each finding's \`evidence\` file:line and apply its \`direction\`. Do NOT re-map the architecture, do NOT re-survey the codebase, do NOT read files beyond the cited evidence + their immediate dependencies. Work finding-by-finding (and card-by-card when findings span several cards) — a verified finding is a precise instruction, not a research prompt. Open only what each fix needs.` +
425
434
  `\n\nAfter applying: run \`${tc('lint', 'npm run lint')}\` and (when the project uses typescript) \`${tc('typecheck', 'npx tsc --noEmit')}\` and \`${tc('build', 'npm run build')}\` in the worktree. If a check fails because of an edit you made, fix the regression — at most 2 retries — staying within the allowed files. ` +
426
435
  `Do NOT commit. Do NOT git stash (refs/stash is shared across worktrees). ` +
427
436
  `Return: applied (finding_ids you fixed — list a finding here if you MADE the edit, even when the build stays red for reasons OUTSIDE this finding / your ownership, e.g. a known cross-wave cascade; the build status is reported separately in checks), unresolved (finding_ids you could NOT fix within the allowed files / 2 retries — i.e. the EDIT itself could not be made), and checks (PASS/FAIL/SKIP for lint, tsc, build).`
@@ -153,15 +153,23 @@ const PREFLIGHT_SCHEMA = {
153
153
  properties: { codexScriptPath: { type: 'string', description: 'absolute path to codex-companion.mjs, or "" if none' } },
154
154
  }
155
155
  let codexScriptPath = ''
156
- try {
157
- const pf = await agent(
158
- `Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
159
- " 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" +
160
- `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.`,
161
- { label: 'codex-preflight', phase: 'Review', model: 'haiku', schema: PREFLIGHT_SCHEMA }
162
- )
163
- codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
164
- } catch (_) { codexScriptPath = '' }
156
+ // Caller-provided path: the orchestrator resolves codex-companion.mjs ONCE and passes it in args, so we
157
+ // skip the per-invocation Haiku resolver. Falls back to the in-workflow resolver when absent, so
158
+ // consumers/skills that don't pass it yet keep working unchanged.
159
+ if (typeof a.codexScriptPath === 'string' && /codex-companion\.mjs$/.test(a.codexScriptPath.trim())) {
160
+ codexScriptPath = a.codexScriptPath.trim()
161
+ log('Codex companion path received from caller args (resolver agent skipped).')
162
+ } else {
163
+ try {
164
+ const pf = await agent(
165
+ `Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
166
+ " 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" +
167
+ `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.`,
168
+ { label: 'codex-preflight', phase: 'Review', model: 'haiku', schema: PREFLIGHT_SCHEMA }
169
+ )
170
+ codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
171
+ } catch (_) { codexScriptPath = '' }
172
+ }
165
173
  const codexResolved = /codex-companion\.mjs$/.test(codexScriptPath)
166
174
  log(codexResolved
167
175
  ? `Codex companion resolved deterministically: ${codexScriptPath}`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.53.7",
3
+ "version": "4.53.8",
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"