baldart 4.54.0 → 4.54.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,24 @@ 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.54.1] - 2026-06-19
|
|
9
|
+
|
|
10
|
+
**Two bugs in the Codex relay of the `/new` review workflows — found live as a ~20-minute "freeze".** During a real `/new FEAT-0037` the Codex step of `new-card-review` appeared stuck; on-disk diagnosis (`/tmp/codexreview-wave-FEAT-0037*.md` + `ps`) showed **Codex had actually finished 9 minutes earlier** (full review, zero actionable findings) but the relay couldn't detect it, because of two independent, compounding bugs:
|
|
11
|
+
|
|
12
|
+
1. **`$$` placeholder in the `/tmp` filenames.** The workflows built `/tmp/codextask-${cardId}-$$.txt` / `/tmp/codexreview-wave-${cardId}-$$.md`. In a JS template string `$$` is the literal two characters — the `Write` tool keeps it verbatim, but **bash expands `$$` to the PID of each subshell**, so the launch, the poll, and the extract Bash calls each referenced a *different* path that never agreed. Observed: the first launch `cat`-ed a PID-named task file that didn't exist → Codex ran with an empty task ("Provide a prompt…", 68 bytes); the relay retried with clean names (Codex then ran for real), but the poll loop kept `grep`-ing a `-$$.md` file with a fresh PID each iteration → never terminal.
|
|
13
|
+
2. **Codex emits prose, not the sentinel block.** Even with the right file, Codex (GPT-5.x via the companion) wrote its findings as a prose report (`**Findings** No actionable… **Cleared Concerns** …`) instead of between `<<<FINDINGS_JSON>>>`/`<<<END_FINDINGS_JSON>>>`, so the relay's only terminal condition (`grep END_FINDINGS_JSON`) never matched → it spun to the 10-minute window before falling back to `code-reviewer`.
|
|
14
|
+
|
|
15
|
+
Nothing broke downstream (the fallback is wired), but every occurrence cost ~12 wasted minutes + a redundant full `code-reviewer` re-run. Fix:
|
|
16
|
+
- **Stable per-wave/batch filenames (no `$$`)** — `cardId`/`firstCardId` is already unique per wave/batch and the files are `/tmp` scratch truncated by `>` on launch, so the launch/poll/extract calls now agree on one literal path.
|
|
17
|
+
- **Robust terminal detection** — the relay also polls for the companion's reliable `Turn completed.` marker. New return logic: a non-empty awk extraction ⇒ `codexAvailable:true` + parsed findings; `Turn completed.`/`CODEX_NOT_FOUND` seen **but** the awk output empty (Codex finished with prose, no machine-readable block) ⇒ `codexAvailable:false` + **exit immediately** (route to the `code-reviewer` fallback — never parse findings out of prose, which would risk dropping a BLOCKER); only a full 10-minute window with no marker ⇒ false. So a prose-only completion now falls back at Codex-done time, not +10 min.
|
|
18
|
+
- **Task-prompt hardening (best-effort)** — Codex is told its FINAL message must be ONLY the sentinel block (no `Findings`/`Cleared Concerns`/`Validation` prose; cleared concerns go as `requires_action:false` entries inside the JSON), reducing how often the prose-fallback path is hit.
|
|
19
|
+
|
|
20
|
+
Verified by parsing both workflows (`node -c`), confirming zero `$$` remain, and running the exact `awk` + marker logic against the real incident fixture (prose-only → empty awk + `Turn completed.` → `false`/fallback) and a synthetic sentinel file (→ valid JSON, `true`). Scope is the two review workflows only; `new2.js`'s Codex usage is a different shape (a `$FILE` var, no sentinel-poll dependency; a synchronous `--wait` per-card call) and inherits the fix at the Final via its delegation to `new-final-review`. **PATCH** (relay bugfix; no new agent/skill/command, no `baldart.config.yml` key, no install/layout change).
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- **`framework/.claude/workflows/new-card-review.js`** + **`new-final-review.js`** — Codex relay: (1) dropped the `$$` placeholder from the `/tmp` task/review filenames (stable per-wave/batch names); (2) added `Turn completed.` as a poll terminal + a deterministic 3-way return rule (non-empty JSON ⇒ available; finished-without-block ⇒ unavailable, immediate fallback, never prose-parse; full-window-no-marker ⇒ unavailable); (3) task prompt now demands the FINAL message be ONLY the sentinel block.
|
|
25
|
+
|
|
8
26
|
## [4.54.0] - 2026-06-19
|
|
9
27
|
|
|
10
28
|
**The i18n anti-hardcoded gate is now DIFF-SCOPED in every per-change context, and the two UI-authoring surfaces (`ui-expert`, `ui-design`) finally honor the i18n layer.** Surfaced by a real `/new FEAT-0037` run on a consumer (`mayo`) that is *partially* i18n-adopted: the coder had to manually triage 25 eslint-i18n errors + 4 parity failures and discovered they were **pre-existing baseline debt in files the card never touched** (`products/edit`, `products/new`, `sw-register`, …). Root cause: the i18n gate ran **whole-repo** (`npx eslint --config eslint.i18n.config.mjs .`) in every per-change consumer, while the *normal* lint gate was already diff-scoped — so on any mid-adoption project every card failed the i18n gate on unrelated baseline debt. The coder did the right thing (distrusted its own report, verified independently) but should never have had to. (The parity failure and the `Cannot find module '@/lib/i18n'` tsc note in that run were project-side, not framework — handled soundly by the coder.)
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.54.
|
|
1
|
+
4.54.1
|
|
@@ -229,13 +229,17 @@ const codexReviewTask =
|
|
|
229
229
|
`Run a deep code review over this wave's committed diff. The code is already written and committed — find bugs, regressions, security issues, and quality problems, per the protocol in ${protocolRef} (Phase 3.7).\n\n` +
|
|
230
230
|
`${waveBrief}\n\n${baselineBrief}\n\n` +
|
|
231
231
|
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones. Actionability: set requires_action:false on a VERIFIED observation that needs NO change (a cleared concern — MED/LOW only; a BLOCKER/HIGH is always requires_action:true).\n\n` +
|
|
232
|
-
`
|
|
232
|
+
`Your FINAL message MUST be ONLY the sentinel block below — NOTHING else. Do NOT write a prose report, do NOT add "Findings" / "Cleared Concerns" / "Validation" headings before or after it. Put every cleared concern as a \`requires_action:false\` entry INSIDE the JSON array. Emit EXACTLY:\n` +
|
|
233
233
|
`<<<FINDINGS_JSON>>>\n` +
|
|
234
234
|
`[{"finding_id":"<CARD-ID>-F###","title":"...","severity":"BLOCKER|HIGH|MEDIUM|LOW","confidence":<0-100>,"evidence":"exact file:line + code quote","minimal_fix_direction":"...","domain":"doc|security|migration|code|perf|test","requires_action":true}]\n` +
|
|
235
235
|
`<<<END_FINDINGS_JSON>>>\n` +
|
|
236
236
|
`Use an empty array [] between the sentinels if there are no real bugs.`
|
|
237
|
-
|
|
238
|
-
|
|
237
|
+
// Stable per-wave names — NO `$$`: in a JS template string `$$` is the literal two chars, which the
|
|
238
|
+
// Write tool keeps verbatim but bash expands to a per-subshell PID → the launch/poll/extract Bash calls
|
|
239
|
+
// each saw a DIFFERENT path and never agreed (observed live: poll grepped a nonexistent PID-named file
|
|
240
|
+
// forever). cardId is already unique per wave; the file is /tmp scratch truncated by `>` on launch.
|
|
241
|
+
const codexTaskFile = `/tmp/codextask-${cards[0].cardId}.txt`
|
|
242
|
+
const codexReviewFile = `/tmp/codexreview-wave-${cards[0].cardId}.md`
|
|
239
243
|
const codexPrompt =
|
|
240
244
|
`You are a THIN RELAY around the Codex companion — do NOT review or investigate code yourself, do NOT grep/sed/Read source to "confirm" findings (Codex's findings are authoritative and already FP-validated; re-verifying them is wasted work). Your ONLY job: launch Codex, wait, extract its JSON, return it.\n\n` +
|
|
241
245
|
`The companion script is ALREADY CONFIRMED PRESENT at:\n ${codexScriptPath}\n\n` +
|
|
@@ -243,11 +247,17 @@ const codexPrompt =
|
|
|
243
247
|
` 1. Write the task text (everything after "TASK PROMPT:" at the end of this message) to ${codexTaskFile} using the Write tool — do NOT inline it into the shell command (avoids quote breakage).\n` +
|
|
244
248
|
` 2. Launch Codex in the BACKGROUND (run_in_background:true — a sync run hits the Bash timeout):\n` +
|
|
245
249
|
` node "${codexScriptPath}" task "$(cat ${codexTaskFile})" --cwd "${a.worktreePath || '.'}" > ${codexReviewFile} 2>&1\n` +
|
|
246
|
-
` 3. POLL ${codexReviewFile} (BashOutput / repeated reads) until it contains
|
|
250
|
+
` 3. POLL ${codexReviewFile} (BashOutput / repeated reads) until it contains ANY of these — whichever appears FIRST ends the poll (do NOT keep waiting the full window once one is present):\n` +
|
|
251
|
+
` • "<<<END_FINDINGS_JSON>>>" → Codex emitted the JSON block (success path)\n` +
|
|
252
|
+
` • "Turn completed." → the companion's reliable end-of-run marker — Codex FINISHED even if it wrote prose instead of the block\n` +
|
|
253
|
+
` • "CODEX_NOT_FOUND" → companion missing\n` +
|
|
254
|
+
` • the full 10-minute window has elapsed\n` +
|
|
247
255
|
` 4. Extract the findings with this EXACT command (deterministic — skips the [codex] trace + truncated echo, takes the LAST complete sentinel pair):\n` +
|
|
248
256
|
` awk '/<<<FINDINGS_JSON>>>/{c=1;b="";next}/<<<END_FINDINGS_JSON>>>/{c=0;last=b;next}c{b=b $0 ORS}END{printf "%s",last}' ${codexReviewFile}\n` +
|
|
249
|
-
`
|
|
250
|
-
`
|
|
257
|
+
` 5. Decide the return — do NOT re-verify, re-grep, or Read any source file:\n` +
|
|
258
|
+
` • awk output is a NON-EMPTY JSON array → parse it and return it verbatim as \`findings\`, codexAvailable:true (its keys already match the schema).\n` +
|
|
259
|
+
` • awk output is EMPTY but the file has "Turn completed." (or "CODEX_NOT_FOUND") → Codex finished WITHOUT the sentinel block (e.g. it wrote a prose report). Return codexAvailable:false, findings:[] and EXIT NOW — do NOT wait the rest of the window, and do NOT parse findings out of the prose (a prose-only completion routes to the code-reviewer fallback; never risk dropping a real finding by guessing).\n` +
|
|
260
|
+
` • none of the markers ever appeared after the FULL 10-minute window → codexAvailable:false. NEVER set false because a single poll returned slowly.\n\n` +
|
|
251
261
|
`TASK PROMPT:\n${codexReviewTask}`
|
|
252
262
|
|
|
253
263
|
const tcGateLines = [
|
|
@@ -185,13 +185,17 @@ const codexReviewTask =
|
|
|
185
185
|
`Run a deep code review over this batch diff, per the /codexreview protocol in ${protocolRef} (Step F.3). The code is already written and committed — find bugs, regressions, security issues, and quality problems.\n\n` +
|
|
186
186
|
`${scopeBrief}\n\n${baselineBrief}\n\n` +
|
|
187
187
|
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones. Actionability: set requires_action:false on a VERIFIED observation that needs NO change (a cleared concern — MED/LOW only; a BLOCKER/HIGH is always requires_action:true).\n\n` +
|
|
188
|
-
`
|
|
188
|
+
`Your FINAL message MUST be ONLY the sentinel block below — NOTHING else. Do NOT write a prose report, do NOT add "Findings" / "Cleared Concerns" / "Validation" headings before or after it. Put every cleared concern as a \`requires_action:false\` entry INSIDE the JSON array. Emit EXACTLY:\n` +
|
|
189
189
|
`<<<FINDINGS_JSON>>>\n` +
|
|
190
190
|
`[{"finding_id":"<CARD-ID>-F###","title":"...","severity":"BLOCKER|HIGH|MEDIUM|LOW","confidence":<0-100>,"evidence":"exact file:line + code quote","minimal_fix_direction":"...","domain":"doc|security|migration|code|perf|test","requires_action":true}]\n` +
|
|
191
191
|
`<<<END_FINDINGS_JSON>>>\n` +
|
|
192
192
|
`Use an empty array [] between the sentinels if there are no real bugs.`
|
|
193
|
-
|
|
194
|
-
|
|
193
|
+
// Stable per-batch names — NO `$$`: in a JS template string `$$` is the literal two chars, which the
|
|
194
|
+
// Write tool keeps verbatim but bash expands to a per-subshell PID → the launch/poll/extract Bash calls
|
|
195
|
+
// each saw a DIFFERENT path and never agreed (observed live: poll grepped a nonexistent PID-named file
|
|
196
|
+
// forever). firstCardId is unique per batch; the file is /tmp scratch truncated by `>` on launch.
|
|
197
|
+
const codexTaskFile = `/tmp/codextask-batch-${a.firstCardId || 'batch'}.txt`
|
|
198
|
+
const codexReviewFile = `/tmp/codexreview-batch-${a.firstCardId || 'batch'}.md`
|
|
195
199
|
const codexPrompt =
|
|
196
200
|
`You are a THIN RELAY around the Codex companion — do NOT review or investigate code yourself, do NOT grep/sed/Read source to "confirm" findings (Codex's findings are authoritative and already FP-validated). Your ONLY job: launch Codex, wait, extract its JSON, return it.\n\n` +
|
|
197
201
|
`The companion script is ALREADY CONFIRMED PRESENT at:\n ${codexScriptPath}\n\n` +
|
|
@@ -199,11 +203,17 @@ const codexPrompt =
|
|
|
199
203
|
` 1. Write the task text (everything after "TASK PROMPT:" at the end of this message) to ${codexTaskFile} using the Write tool — do NOT inline it into the shell command (avoids quote breakage).\n` +
|
|
200
204
|
` 2. Launch Codex in the BACKGROUND (run_in_background:true — a sync run hits the Bash timeout):\n` +
|
|
201
205
|
` node "${codexScriptPath}" task "$(cat ${codexTaskFile})" --cwd "${a.worktreePath || '.'}" > ${codexReviewFile} 2>&1\n` +
|
|
202
|
-
` 3. POLL ${codexReviewFile} (BashOutput / repeated reads) until it contains
|
|
206
|
+
` 3. POLL ${codexReviewFile} (BashOutput / repeated reads) until it contains ANY of these — whichever appears FIRST ends the poll (do NOT keep waiting the full window once one is present):\n` +
|
|
207
|
+
` • "<<<END_FINDINGS_JSON>>>" → Codex emitted the JSON block (success path)\n` +
|
|
208
|
+
` • "Turn completed." → the companion's reliable end-of-run marker — Codex FINISHED even if it wrote prose instead of the block\n` +
|
|
209
|
+
` • "CODEX_NOT_FOUND" → companion missing\n` +
|
|
210
|
+
` • the full 10-minute window has elapsed\n` +
|
|
203
211
|
` 4. Extract the findings with this EXACT command (deterministic — skips the [codex] trace + truncated echo, takes the LAST complete sentinel pair):\n` +
|
|
204
212
|
` awk '/<<<FINDINGS_JSON>>>/{c=1;b="";next}/<<<END_FINDINGS_JSON>>>/{c=0;last=b;next}c{b=b $0 ORS}END{printf "%s",last}' ${codexReviewFile}\n` +
|
|
205
|
-
`
|
|
206
|
-
`
|
|
213
|
+
` 5. Decide the return — do NOT re-verify, re-grep, or Read any source file:\n` +
|
|
214
|
+
` • awk output is a NON-EMPTY JSON array → parse it and return it verbatim as \`findings\`, codexAvailable:true (its keys already match the schema).\n` +
|
|
215
|
+
` • awk output is EMPTY but the file has "Turn completed." (or "CODEX_NOT_FOUND") → Codex finished WITHOUT the sentinel block (e.g. it wrote a prose report). Return codexAvailable:false, findings:[] and EXIT NOW — do NOT wait the rest of the window, and do NOT parse findings out of the prose (a prose-only completion routes to the code-reviewer fallback; never risk dropping a real finding by guessing).\n` +
|
|
216
|
+
` • none of the markers ever appeared after the FULL 10-minute window → codexAvailable:false. NEVER set false because a single poll returned slowly.\n\n` +
|
|
207
217
|
`TASK PROMPT:\n${codexReviewTask}`
|
|
208
218
|
|
|
209
219
|
const docPrompt =
|