baldart 4.17.2 → 4.18.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,20 @@ 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.18.0] - 2026-06-08
9
+
10
+ **Codex come finder a profilo `light` (`/new` + `new2`) + engine Codex opt-in nelle routine cron.** Sposta il carico di review da Claude alla licenza Codex dove la review è read-only e genera finding, mantenendo la diversità di modello (Codex trova, l'agent Claude verifica). **MINOR** (additivo/opt-in con fallback; nessuna rottura install, **nessuna chiave `baldart.config.yml`** → schema-propagation N/A — `review_engine` è schema routine-payload).
11
+
12
+ ### Changed
13
+
14
+ - **Profilo `light` → Codex è il finder per-card (inverte la scelta v3.38.0).** Prima `light` faceva `code-reviewer` (finder) + FP-gate e deferiva Codex al solo Final FULL gate. Ora `/codexreview` a `light` lancia **Codex come unico finder** + lo Step 3 FP-gate (`code-reviewer` + `codebase-architect` validano): la lettura full-diff esce da Claude, Claude conserva solo la validazione. `full` invariato (entrambi i finder). **Fallback obbligatorio**: Codex non disponibile → `light` degrada a `code-reviewer` finder (comportamento pre-v4.18.0), la review non salta mai. L'invariante BUG-0530 ne esce rafforzata (Codex per-card su ogni card non-triviale). File: `framework/.claude/commands/codexreview.md` (Step -0.5/Step 2/invariant/Method-log), `framework/.claude/skills/new/references/codex-gate.md` (razionale Phase 3.7).
15
+ - **Team-mode allineato: i `LIGHT_CARDS` non skippano più D.4b.** La partizione LIGHT/FULL esisteva per tenere Codex fuori da `light`; ora ogni card non-triviale gira `/codexreview` a D.4b (light → Codex-light, full → Codex-full). La D.2 group pass diventa **doc-reviewer only** (il code-reviewer di gruppo sui light cards è rimosso — la loro review si sposta a D.4b). Coverage-assertion e clausola "REVIEW DEPTH SCALES" aggiornate. File: `framework/.claude/skills/new/references/team-mode.md`, `framework/.claude/skills/new/SKILL.md`, `framework/.claude/agents/prd-card-writer.md` (Rule C — semantica Codex-`light`).
16
+ - **`new2.js`: mirror del Codex finder a `light`.** Il companion Codex è ora risolto in pre-flight per **ogni** batch (prima solo multi-card per il cross-card), con il path propagato in `sharedCtx`. A `light` il fan-out per-card usa un reviewer **`codex`** (un agent general-purpose che lancia `codex-companion.mjs --wait` sul diff) quando il companion è risolto, con fallback `code-reviewer`. Il FP-gate è preservato a valle dal judge avversariale di `new2-resolve` (F-015). Nuovo campo schema `codexScriptPath`; ledger `codex-companion`; telemetria `codex_resolved` ora probed per ogni batch.
17
+
18
+ ### Added
19
+
20
+ - **`review_engine: codex` opt-in nelle routine di review (solo backend cron).** `code-review.routine.yml` accetta `review_engine: claude|codex` (default `claude`) + un `prompt_codex:` per la review olistica single-pass. L'adapter `cron` risolve il companion Codex e lo invoca (`--wait`), con **guard `CODEX_NOT_FOUND` → fallback a `claude --agent`**; il check `ANTHROPIC_API_KEY` è ammorbidito a warning sul ramo Codex (Codex usa `~/.codex`). I backend `github-actions` e `claude-code-cloud` **non** supportano Codex (no plugin/auth in CI, schema RemoteTrigger ignoto) → emettono l'artefatto Claude + un warning esplicito. Codex non spawna specialist → la variante Codex perde la profondità security-reviewer/api-perf (documentato; usa `claude` per quella). File: `framework/routines/code-review.routine.yml`, `src/utils/routine-adapters/{cron,github-actions,claude-code-cloud}.js`, README.
21
+
8
22
  ## [4.17.2] - 2026-06-08
9
23
 
10
24
  **`new2` hardening: 7 lacune latenti chiuse da una revisione adversariale dell'intero workflow.** Dopo i fix di v4.17.1 (nati da run reali), una revisione olistica di `new2` (orchestratore + resolve + final-review + skill) ha isolato 7 lacune non ancora emerse nei run ma reali alla lettura del codice — validate di persona scartando i falsi positivi (l'integrity gate di merge è corretto; lo skill "prose-only" è by-design). **PATCH** (hardening dei workflow `new2`/`new2-resolve` + skill/doc; nessuna nuova capability/layout, **nessuna chiave `baldart.config.yml`** → schema-propagation N/A).
package/README.md CHANGED
@@ -310,6 +310,14 @@ Three backend adapters are bundled:
310
310
  - **`github-actions`** — `.github/workflows/baldart-<name>.yml`
311
311
  - **`cron`** — `scripts/routines/<name>.sh` + a crontab line
312
312
 
313
+ **Review engine (v4.18.0+)** — a review routine (e.g. `code-review`) may set `review_engine: codex`
314
+ in its `.routine.yml` to shift the finding pass onto the OpenAI Codex companion (spend a Codex
315
+ licence instead of Claude tokens). This is **cron-only** — it needs the Codex plugin + an
316
+ authenticated `~/.codex/` in the cron user's home; the `github-actions` and `claude-code-cloud`
317
+ backends fall back to the `claude` engine with a warning. Codex cannot spawn specialist subagents,
318
+ so the Codex engine runs a holistic single-pass review (no `security-reviewer` / `api-perf-cost-auditor`
319
+ split) — keep `review_engine: claude` (the default) when you need that depth.
320
+
313
321
  During `npx baldart add` and `npx baldart update`, BALDART surfaces routines
314
322
  the user has never reviewed and prompts to install them. Standalone command:
315
323
 
package/VERSION CHANGED
@@ -1 +1 @@
1
- 4.17.2
1
+ 4.18.0
@@ -198,7 +198,9 @@ This field materializes the per-card review DEPTH **at authoring time**, so the
198
198
  `/new` orchestrator READS it instead of re-deriving it heuristically at runtime.
199
199
  The single field drives BOTH the QA depth (`/new` Phase 3.5) and the Codex review
200
200
  profile (`/new` Phase 3.7 — `skip`/`light` → Codex `light`, `balanced`/`deep` →
201
- Codex `full`). **This table is the Single Source of Truth for the decision
201
+ Codex `full`). Note (since v4.18.0): Codex-`light` is no longer a "no-Codex" lane it runs
202
+ **Codex as the per-card finder** with `code-reviewer` as the FP-gate verifier; `full` runs both
203
+ as finders. **This table is the Single Source of Truth for the decision
202
204
  criteria** — how skip/light/balanced/deep is chosen lives ONLY here (Rule C).
203
205
  `/new` does NOT re-derive the profile from a duplicate criteria table: it READS
204
206
  the card's `review_profile` value and runs an **escalation-only** detector (it may
@@ -222,9 +224,9 @@ exception**: the LIGHT branch (b) above, a strictly UI-presentational card (`are
222
224
  `paths.components_primitives` path). This narrow carve-out targets the `-ui` half of a
223
225
  logic/ui split pair (e.g. a `…-submit-ui` card whose logic lives in its `…-submit-logic`
224
226
  sibling): reviewing a pixels-only diff at Codex-`full` depth is waste, and the design-system
225
- safety net still fires regardless of profile (a `light` card keeps its per-card/group
226
- code-review with `code-reviewer` rule 8 / the registry-first DS cascade, and is covered by
227
- the batch-wide Final FULL gate only the per-card Codex-*adversarial* pass is deferred). Any
227
+ safety net still fires regardless of profile (a `light` card keeps its per-card code-review —
228
+ since v4.18.0 a per-card Codex finder + `code-reviewer` FP-gate plus `code-reviewer` rule 8 /
229
+ the registry-first DS cascade, and is covered by the batch-wide Final FULL gate). Any
228
230
  doubt — a hint of logic, state, data binding, a new/modified primitive, or `areas` beyond
229
231
  `[ui]` — disqualifies branch (b): the card stays `balanced`. When in doubt between `balanced`
230
232
  and `deep`, choose `deep`. `files_likely_touched` counts are advisory only — never downgrade a
@@ -65,21 +65,28 @@ For the (single) card in scope, check for `/tmp/codexreview-lean-<CARD-ID>.json`
65
65
  api-perf-cost-auditor**, which runs batch-wide before merge — so a per-card run here would be a
66
66
  duplicate. It is an independent breadth agent (NOT a Step 3 FP-gate validator), so omitting it is
67
67
  safe (unlike `code-reviewer`, which the FP-gate depends on). *(A3)*
68
- - `profile: "light"` → ALSO omit agent #2 (`api-perf-cost-auditor`),
69
- **agent #4 (Codex adversarial)** (since v3.38.0), and **skip Step 3.5 (CoVe)**. Only agent #1
70
- (`code-reviewer`) and the Step 3 false-positive gate run. The Codex adversarial pass is the
71
- prerogative of `full` and of the final cross-check every `light` card's code is still reviewed
72
- by Codex adversarial at the unconditional Final-review FULL gate before merge (see `/new`
73
- "Final-review FULL gate"), and any high-risk diff trigger escalates the card to `full` per-card
74
- so it gets Codex adversarial immediately. *(B1)*
68
+ - `profile: "light"` → **Codex (agent #4) is the SOLE finder** (since v4.18.0 — inverts the
69
+ v3.38.0 "light drops Codex" choice). Omit agent #1 (`code-reviewer`) **as a Step-2 finder**, omit
70
+ agent #2 (`api-perf-cost-auditor`) and agent #3 (`doc-reviewer`), and **skip Step 3.5 (CoVe)**.
71
+ The **Step 3 false-positive gate STILL runs** (`code-reviewer` + `codebase-architect` validate the
72
+ Codex findings) — so `code-reviewer` is present at `light`, just as a *verifier*, not a finder.
73
+ This is the cost-shift lane: the diff sweep runs on Codex, Claude only validates the findings.
74
+ **Codex-unavailable fallback (MANDATORY)**: if the companion glob resolves empty, or Codex exits
75
+ non-zero / `CODEX_UNAVAILABLE`, **spawn agent #1 (`code-reviewer`) as the finder** (the pre-v4.18.0
76
+ `light` behavior) so the per-card review never silently vanishes; the Step 3 FP-gate then validates
77
+ its own findings as before. Any high-risk diff trigger escalates the card to `full` (both finders)
78
+ per the Review Profile Selector; the Final-review FULL gate re-runs Codex batch-wide regardless. *(B1)*
75
79
  - `profile: "full"` (or missing) → full agent set incl. Codex adversarial, minus #3 if `skip_doc_reviewer`.
76
80
 
77
- **Invariant**: lean mode NEVER suppresses the run itself — at minimum `code-reviewer` + the Step 3
78
- FP-gate execute on every card (`light`); `full` additionally runs the Codex adversarial pass. If the
79
- contract file is malformed/unreadable, fall back to **full mode** and note it. **Consume-once**: delete `/tmp/codexreview-lean-<CARD-ID>.json`
80
- immediately after parsing it, so a later standalone `/codexreview <CARD-ID>` on the same card never
81
- inherits a stale lean contract from a prior `/new` run. Record the resolved mode in the Step 4 report
82
- `Method` section, e.g. `lean: profile=light, architect=reused, doc-reviewer=skipped(Phase 3), codex=skipped(light), cove=skipped`.
81
+ **Invariant**: lean mode NEVER suppresses the run itself — at minimum a **finder + the Step 3 FP-gate**
82
+ execute on every card. At `light` the finder is **Codex** (or `code-reviewer` on the Codex-unavailable
83
+ fallback); at `full` both `code-reviewer` and Codex run as finders. If the contract file is
84
+ malformed/unreadable, fall back to **full mode** and note it. **Consume-once**: delete
85
+ `/tmp/codexreview-lean-<CARD-ID>.json` immediately after parsing it, so a later standalone
86
+ `/codexreview <CARD-ID>` on the same card never inherits a stale lean contract from a prior `/new` run.
87
+ Record the resolved mode in the Step 4 report `Method` section, e.g.
88
+ `lean: profile=light, architect=reused, doc-reviewer=skipped(Phase 3), codex=finder(light), code-reviewer=fp-gate, cove=skipped`
89
+ (or `codex=unavailable→code-reviewer finder(light)` when the fallback fired).
83
90
 
84
91
  ---
85
92
 
@@ -162,11 +169,14 @@ Launch these agents in parallel for each card:
162
169
  > **Lean agent set (Step -0.5)**: when a contract file is active, launch only the agents the resolved
163
170
  > mode permits — `skip_doc_reviewer` omits #3 (`doc-reviewer`); `skip_api_perf_auditor` omits #2
164
171
  > (`api-perf-cost-auditor`) **even at `full`** (since v4.7.0 — deferred to the Final F.3 batch-wide
165
- > auditor); `profile: light` additionally omits #2 AND **#4 (Codex adversarial)** (since v3.38.0). At
166
- > `light`, only agent #1 (`code-reviewer`) launches (+ the Step 3 FP-gate). At `full` with
167
- > `skip_api_perf_auditor`, launch #1 + #4 (Codex) (+ #3 unless `skip_doc_reviewer`). At `full` with no
168
- > skips (and in full mode with no contract file), launch all four. The standalone
169
- > `/codexreview <CARD-ID>` invocation (no contract) is always full with the complete set.
172
+ > auditor); `profile: light` (since v4.18.0) launches **only agent #4 (Codex) as the finder** agent
173
+ > #1 (`code-reviewer`) does NOT run as a Step-2 finder, #2 and #3 are omitted, and Step 3.5 CoVe is
174
+ > skipped; `code-reviewer` then runs in the **Step 3 FP-gate** as a verifier. **Codex-unavailable
175
+ > fallback at `light`**: if the companion is missing or errors, launch agent #1 (`code-reviewer`) as
176
+ > the finder instead (the Step 3 FP-gate validates its findings). At `full` with `skip_api_perf_auditor`,
177
+ > launch #1 + #4 (Codex) (+ #3 unless `skip_doc_reviewer`). At `full` with no skips (and in full mode
178
+ > with no contract file), launch all four. The standalone `/codexreview <CARD-ID>` invocation (no
179
+ > contract) is always full with the complete set.
170
180
 
171
181
  **Codex invocation rules (agent #4):**
172
182
 
@@ -15,7 +15,7 @@ description: >
15
15
 
16
16
  > **NO PHASE SKIP FOR PERCEIVED TIME, EVER (BLOCKING)**: MANDATORY phases are non-negotiable in BOTH sequential mode and team mode. The phrases `time budget`, `context budget`, `context pressure as override`, `for time`, `to save tokens`, `to save iterations`, or any model-invented constraint are NOT valid skip reasons. The only valid skip reasons are those explicitly enumerated in each phase's Gate table (e.g. `features.has_e2e_review: false`, backend-only diff, card type `docs`/`chore`/`config`, **the card's `review_profile`-driven review depth — see next paragraph — the `IS_TRIVIAL` fast-lane (`review_profile=skip` + non-source diff + 0 high-risk triggers), defined once in § "Trivial-card fast-lane", and the v4.7.0 Final-deferred reviews: plan-auditor grounding skipped on `/prd` holistic-audit provenance + no drift; per-card doc-review / qa-sentinel / api-perf-cost-auditor deferred to the unconditional Final FULL gate when their `review_profile`/provenance condition holds**). Documenting skipped phases at the end of a batch as `"saltati per time budget"` is a protocol violation per the FEAT-0006 incident — treat any such phrase in your own output as a self-correction trigger: stop, surface the situation via `AskUserQuestion`, and resume only after explicit user direction. Auto Mode does NOT override this. Team-mode orchestrators (L0/L1) propagate every MANDATORY phase per-card and never aggregate, reduce, or pre-filter the per-card pipeline **for any model-invented reason**.
17
17
 
18
- > **REVIEW DEPTH SCALES WITH `review_profile` (deterministic — NOT a time/token skip)**: the review *depth* of a card MAY scale with its `review_profile` (the PRD-authored field, escalation-only via the Step-A detector — `light`→`full` never the reverse), exactly as the QA tier (D.4) and Codex `light`/`full` depth (D.4b / Phase 3.7 Step C) already do. This is an **enumerated, deterministic gate reason**, the polar opposite of a `time budget` skip: it is driven by a field a human authored at PRD time, not by the model's perception of how long the batch is taking. In **team mode** this means a `light`/`skip` (0-trigger) card receives its single per-card code review at the **D.2 group pass** and defers Codex-adversarial to the unconditional **Final-review FULL gate** (so D.4b is skipped for it), while a `full` card receives its per-card code review at the **D.4b `/codexreview`** pass (so the D.2 group code-reviewer does not re-review it). Every card still gets ≥1 per-card/group code review **plus** the cross-batch Final FULL gate — coverage is preserved, redundancy is removed. The **sequential** per-card pipeline is unchanged (it has no D.2 group code-reviewer, so its Phase 3.7 `/codexreview` stays the single per-card code review). Skipping a gate WITHOUT an enumerated `review_profile`/Gate-table reason remains a protocol violation.
18
+ > **REVIEW DEPTH SCALES WITH `review_profile` (deterministic — NOT a time/token skip)**: the review *depth* of a card MAY scale with its `review_profile` (the PRD-authored field, escalation-only via the Step-A detector — `light`→`full` never the reverse), exactly as the QA tier (D.4) and Codex `light`/`full` depth (D.4b / Phase 3.7 Step C) already do. This is an **enumerated, deterministic gate reason**, the polar opposite of a `time budget` skip: it is driven by a field a human authored at PRD time, not by the model's perception of how long the batch is taking. In **team mode** (since v4.18.0) every non-trivial card receives its per-card code review at the **D.4b `/codexreview`** pass: a `light` (0-trigger) card runs `/codexreview` at `profile: light` (**Codex finder + `code-reviewer` FP-gate**), a `full` card at `profile: full` (both finders). The D.2 group pass is now **doc-reviewer only** (the old D.2 group code-reviewer over light cards is removed — their code review moved to D.4b). `TRIVIAL_CARDS` (non-source diff) skip D.4b and rely on the doc-reviewer pass + the Final FULL gate. Every card still gets ≥1 per-card code review **plus** the cross-batch Final FULL gate. The **sequential** per-card pipeline is analogous (its Phase 3.7 `/codexreview` runs at `light`/`full` per the card profile). Skipping a gate WITHOUT an enumerated `review_profile`/Gate-table reason remains a protocol violation.
19
19
 
20
20
  ## Project Context
21
21
 
@@ -421,8 +421,8 @@ skip: it is gated entirely on deterministic, PRD-authored signals.
421
421
 
422
422
  **Fail-safe guard (degrade toward MORE review, never less)**: if ANY condition fails — a single
423
423
  source file in the diff, one Step-A trigger, or a non-`skip` profile — `IS_TRIVIAL=false` and the
424
- card follows its normal path (in team mode that is `LIGHT_CARDS`: D.2 code-reviewer + Final; in
425
- sequential, the full per-card pipeline). A mis-authored `skip` on a code card, or a `skip` card
424
+ card follows its normal path (in team mode that is `LIGHT_CARDS`: D.4b `/codexreview` at `light`
425
+ Codex finder + FP-gate — plus the Final FULL gate; in sequential, the full per-card pipeline). A mis-authored `skip` on a code card, or a `skip` card
426
426
  whose text mentions `auth`/`payment`/etc., is therefore always caught. The classifier can only ever
427
427
  *remove* review from a card that demonstrably has no code to review.
428
428
 
@@ -8,7 +8,7 @@
8
8
 
9
9
  > **Single carve-out — `IS_TRIVIAL` fast-lane (since v4.6.0)**: a card that is `IS_TRIVIAL` on its ACTUAL committed diff (`review_profile=skip` + non-source diff + 0 Step-A triggers — § "Trivial-card fast-lane") **skips this gate**, because there is no code for Codex to review. Its adversarial coverage is carried by the SAME unconditional **Final-review FULL gate** the `light` path already relies on (it runs Codex over the entire batch diff, including every trivial card's files, before merge). This is NOT a `time budget` skip — it is the deterministic `IS_TRIVIAL` gate reason, and the guard means a single source file in the diff flips the card back onto this gate. Log `codex-review: SKIPPED (trivial — non-source diff; covered by Final FULL gate)`.
10
10
 
11
- **Rationale**: leaving the gate conditional caused it to be silently skipped on the majority of cards, defeating the AGENTS.md "MUST run BEFORE merge" requirement. The gate itself is unconditional — what varies is its DEPTH (`light` vs `full`). The protection against a missed blocker on a "low-risk" path (BUG-0530-class regressions) is carried by two mechanisms, not by an always-on per-card Codex pass: (1) the Step A detector escalates any risky diff to `full` per-card, and (2) the unconditional **Final-review FULL gate** runs Codex adversarial over the entire batch diff before merge. So `light` can safely run `code-reviewer` + FP-gate only (since v3.38.0) without weakening the merge gate.
11
+ **Rationale**: leaving the gate conditional caused it to be silently skipped on the majority of cards, defeating the AGENTS.md "MUST run BEFORE merge" requirement. The gate itself is unconditional — what varies is its DEPTH (`light` vs `full`). Since v4.18.0 **both depths run Codex per-card**: at `light` Codex is the SOLE finder and `code-reviewer` validates in the FP-gate; at `full` both run as finders. So the BUG-0530-class "missed blocker on a low-risk path" guard is now carried directly (Codex sweeps every non-trivial card), reinforced by (1) the Step A detector escalating any risky diff to `full`, and (2) the unconditional **Final-review FULL gate** running Codex over the entire batch diff before merge. The Codex-unavailable fallback (`light` degrades to `code-reviewer` finder) keeps the merge gate intact when the companion is absent.
12
12
 
13
13
  #### Step A — Detect (signal-logging only, NEVER gates the next step)
14
14
 
@@ -43,26 +43,28 @@ For EVERY card (no conditional skip — the gate ALWAYS runs; only its DEPTH var
43
43
  The card's `review_profile` is the deterministic floor; the diff detector is the safety net on
44
44
  top of it.
45
45
 
46
- `light` is **NOT** a skip: `/codexreview` still runs `code-reviewer` + the Step 3 false-positive
47
- gate. **Since v3.38.0 the per-card Codex adversarial pass is NO LONGER run at `light`** — it is the
48
- prerogative of `full` and of the final cross-check. `light` drops the Codex adversarial pass AND the
49
- breadth agents (qa-sentinel, api-perf-cost-auditor, doc-reviewer) AND Step 3.5 CoVe leaving exactly
50
- `code-reviewer` + FP-gate, the cheap per-card pass that yields the early feedback worth having on a
51
- 0-trigger, QA-light diff. `full` is unchanged (Codex adversarial + the full agent set, minus the
52
- duplicate doc-reviewer).
53
- **Where the BUG-0530 invariant now lives.** Pre-v3.38.0 the per-card Codex adversarial was the
54
- "never miss a blocker on a low-risk path" guard. Two mechanisms now carry that guarantee instead,
55
- so dropping it from `light` is safe:
46
+ `light` is **NOT** a skip. **Since v4.18.0 the per-card Codex pass runs at `light` as the SOLE
47
+ finder** (inverting the v3.38.0 choice that deferred Codex to the final cross-check). At `light`
48
+ `/codexreview` launches **Codex (finder)** + the **Step 3 false-positive gate** (`code-reviewer` +
49
+ `codebase-architect` validate the Codex findings) dropping the breadth agents (qa-sentinel,
50
+ api-perf-cost-auditor, doc-reviewer) AND Step 3.5 CoVe. So `code-reviewer` is still present at
51
+ `light`, but as the *verifier*, not the finder: the diff sweep moves off Claude onto Codex (the
52
+ cost-shift), Claude only validates. `full` is unchanged (Codex adversarial + `code-reviewer` both as
53
+ finders + the full agent set, minus the duplicate doc-reviewer).
54
+ **Codex-unavailable fallback (MANDATORY).** If the companion glob resolves empty or Codex errors,
55
+ `light` degrades to **`code-reviewer` as the finder** (the pre-v4.18.0 behavior) + the FP-gate — the
56
+ per-card review never silently vanishes.
57
+ **Where the BUG-0530 invariant now lives.** The "never miss a blocker on a low-risk path" guard is
58
+ now carried **directly** — Codex sweeps every non-trivial card per-card — reinforced by two nets:
56
59
  1. **Escalation-only (Step A)** — any high-risk trigger on the ACTUAL diff promotes `light` → `full`,
57
- so genuinely risky paths (auth/payment/permission/schema/…) STILL get per-card Codex adversarial.
58
- Only a 0-trigger, authored-`light` card defers it.
60
+ so genuinely risky paths (auth/payment/permission/schema/…) get BOTH finders per-card.
59
61
  2. **Final-review FULL gate (since v3.37.0)** — the **Final review ALWAYS runs a single FULL
60
62
  `/codexreview` over the entire batch diff before merge** (no N=1 skip, no scope reduction — see
61
- "Final review → Final-review FULL gate"), and FULL includes Codex adversarial. So every line of
62
- every `light` card receives Codex adversarial at least once before merge.
63
- Net effect: `light` per-card = early `code-reviewer` feedback; the Codex adversarial review is paid
64
- once, at the final batch gate (or per-card the moment a risk trigger fires). To put per-card Codex
65
- back on `light`, re-add agent #5 to the `light` branch of `/codexreview` Step -0.5.
63
+ "Final review → Final-review FULL gate"). So every line of every card is re-swept by Codex
64
+ batch-wide before merge.
65
+ Net effect: `light` per-card = Codex finder + `code-reviewer` FP-gate (or `code-reviewer` finder on
66
+ the Codex-unavailable fallback). To revert per-card Codex off `light`, restore the v3.38.0 `light`
67
+ branch of `/codexreview` Step -0.5 (`code-reviewer` finder only).
66
68
 
67
69
  Then dump the card diff (reviewer grounding) and write the lean contract `/codexreview` consumes:
68
70
  ```bash
@@ -103,9 +105,11 @@ For EVERY card (no conditional skip — the gate ALWAYS runs; only its DEPTH var
103
105
 
104
106
  `/codexreview` Step -0.5 consumes `/tmp/codexreview-lean-<CARD-ID>.json`: reuses the Phase-1
105
107
  architecture baseline (no re-spawn), drops the duplicate doc-reviewer, and for `profile: light`
106
- also drops qa-sentinel + api-perf-cost-auditor + **the Codex adversarial pass** + Step 3.5 CoVe
107
- (since v3.38.0). `code-reviewer` + Step 3 FP-gate + Step 4 report ALWAYS run. (`full` = standard
108
- pipeline incl. Codex adversarial, minus the duplicate doc-reviewer.)
108
+ runs **Codex as the sole finder** (since v4.18.0) + the Step 3 FP-gate (`code-reviewer` +
109
+ `codebase-architect` validate), dropping qa-sentinel + api-perf-cost-auditor + Step 3.5 CoVe. On
110
+ Codex-unavailable, `light` degrades to `code-reviewer` finder. Step 3 FP-gate + Step 4 report ALWAYS
111
+ run. (`full` = standard pipeline incl. Codex adversarial + `code-reviewer` finder, minus the
112
+ duplicate doc-reviewer.)
109
113
 
110
114
  3. **Read the consolidated report**. `/codexreview` writes it to `/tmp/codexreview-report-<TIMESTAMP>.md` and RETURNS that exact path in its Skill-tool result — use the returned path, do NOT guess the timestamp. If the return value did not surface the path, locate the newest report whose body names this `<CARD-ID>` (`grep -l "<CARD-ID>" /tmp/codexreview-report-*.md | xargs ls -t | head -1`), never just the newest file by mtime (a parallel card's report could be newer). Extract:
111
115
  - Verified BLOCKER count
@@ -114,7 +118,7 @@ For EVERY card (no conditional skip — the gate ALWAYS runs; only its DEPTH var
114
118
 
115
119
  4. **Apply fix sub-loop** (mirror of Phase 3.5 retry pattern):
116
120
  - If 0 BLOCKER and 0 HIGH → log `verdict: PASS — proceeding to Phase 4` in tracker. Done. (MEDIUM/LOW findings are advisory at this per-card gate; they are not silently lost — the post-batch **Final-review FULL gate** applies every VERIFIED finding ≥ MEDIUM. Log the MEDIUM count in the tracker so it is visible.)
117
- - If 1+ BLOCKER OR 1+ HIGH → spawn `coder` agent with the report path + list of VERIFIED bugs. **At `full` profile** the report contains Codex-suggested inline patches: pass them and have the coder **apply the suggested patches** with the right system prompt (project conventions, naming, testing patterns) — it does NOT re-do the analysis or re-grep (since v3.28.3), BUT it MUST first confirm each patch still applies against the current file state (prior fix-loop iterations may have shifted line offsets); if a patch no longer applies cleanly, the coder re-locates the target by content and applies the equivalent edit rather than a stale-offset verbatim paste. **At `light` profile** no Codex adversarial patches exist (the pass was dropped) the findings come from `code-reviewer`; brief the coder accordingly (apply the `code-reviewer` fix direction; there are no Codex patches to paste). After coder fixes, **re-write the lean contract `/tmp/codexreview-lean-<CARD-ID>.json` (it is consumed-once and deleted by `/codexreview`)** and re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (NOT a bare prose mention — the card ID MUST be passed so the retry reviews THIS card, not an inferred one). Repeat **max 2 times**.
121
+ - If 1+ BLOCKER OR 1+ HIGH → spawn `coder` agent with the report path + list of VERIFIED bugs. **At `full` profile** the report contains Codex-suggested inline patches: pass them and have the coder **apply the suggested patches** with the right system prompt (project conventions, naming, testing patterns) — it does NOT re-do the analysis or re-grep (since v3.28.3), BUT it MUST first confirm each patch still applies against the current file state (prior fix-loop iterations may have shifted line offsets); if a patch no longer applies cleanly, the coder re-locates the target by content and applies the equivalent edit rather than a stale-offset verbatim paste. **At `light` profile** (since v4.18.0) the findings come from **Codex** (the sole finder) — the report carries Codex's `minimal_fix_direction`; brief the coder to apply it (treat it like the `full`-profile Codex fix direction). **On the Codex-unavailable fallback** the `light` findings come from `code-reviewer` instead — brief the coder to apply the `code-reviewer` fix direction (no Codex patches to paste). After coder fixes, **re-write the lean contract `/tmp/codexreview-lean-<CARD-ID>.json` (it is consumed-once and deleted by `/codexreview`)** and re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (NOT a bare prose mention — the card ID MUST be passed so the retry reviews THIS card, not an inferred one). Repeat **max 2 times**.
118
122
  - If still BLOCKER/HIGH after 2 retries → log in `## Issues & Flags` and **ask the user** whether to proceed, escalate, or stop. The Phase 4 commit MUST NOT happen until the Pre-Merge Codex Review verdict is PASS or user explicitly overrides.
119
123
  - **Telemetry** — for EVERY codex finding processed (verified BLOCKER, verified HIGH, or false-positive-filtered), append one row to `## Fix Application Log`: `3.7 | codex-<security|correctness|other> | est_lines=<n> | decision=<coder|skipped> | applied_by=<coder|-> | severity=<BLOCKER|HIGH|FALSE-POSITIVE> | retry=<n>`. Classify domain: `security` for findings touching RLS / auth / permissions / payments; `correctness` for logic / data integrity / race conditions; `other` for everything else.
120
124
 
@@ -161,12 +161,12 @@ After ALL agents in the group complete successfully:
161
161
  - `FULL_CARDS` = all others (effective profile `balanced`/`deep`, OR any Step-A trigger).
162
162
  - **Sub-classify `TRIVIAL_CARDS` ⊆ `LIGHT_CARDS`** = cards that are `IS_TRIVIAL` on the committed diff (§ "Trivial-card fast-lane": `review_profile == skip` AND 0 Step-A triggers AND **non-source diff**). These are the LIGHT cards that have nothing for `code-reviewer` to review. (A `skip` card whose diff DID touch a source file is in `LIGHT_CARDS` but NOT `TRIVIAL_CARDS` — the guard keeps it on the code-review path.)
163
163
  - **Sub-classify `DOC_DEFER_CARDS`** (since v4.7.0, for #2 doc deferral) = cards with `review_profile == light` whose committed diff touches **NO documentation file** (no `.md`, no path under `${paths.references_dir}`, no data-model/ssot/api doc). Their per-card doc-review is deferred to the Final F.3 doc-reviewer. (Trivial cards, and any card whose diff touches a doc file, are NOT in this set — doc-review stays relevant for them.)
164
- - Log: `## D.1.5 Effective Profiles\n<CARD-ID>: profile=<floor> triggers=<n> diff=<source|non-source> → effective=<light|full> (<LIGHT_CARDS|FULL_CARDS>)<, TRIVIAL / DOC_DEFER if applicable>` per card. This single computation is the SSOT for D.2 (code-reviewer + doc-reviewer scoping), D.3b/D.3c (already skipped for trivial), and D.4b (inclusion) — do NOT recompute it downstream.
164
+ - Log: `## D.1.5 Effective Profiles\n<CARD-ID>: profile=<floor> triggers=<n> diff=<source|non-source> → effective=<light|full> (<LIGHT_CARDS|FULL_CARDS>)<, TRIVIAL / DOC_DEFER if applicable>` per card. This single computation is the SSOT for D.2 (doc-reviewer scoping), D.3b/D.3c (already skipped for trivial), and D.4b (inclusion + per-card Codex profile: `light` cards → `/codexreview` `light`, `full` cards → `full`) — do NOT recompute it downstream.
165
165
 
166
- 2. **D.2 — Combined static review (group)** — Two reviewers, **scoped by the D.1.5 partition** so each card gets exactly ONE per-card/group code review proportional to its risk (the other is the Final FULL gate):
167
- - **doc-reviewer — over the group MINUS `DOC_DEFER_CARDS`** (since v4.7.0). It runs **read-only here** (it cannot write while code-reviewer reads in parallel — parallel-safety), MUST **attribute every doc finding to a specific card** (by file → ownership map), and applies the full Phase 3 mandate INCLUDING the spec/docs-drift→bug lens (since v3.35.0). The `DOC_DEFER_CARDS` (light, no-doc diff per D.1.5) are excluded — their doc-review is deferred to the Final F.3 doc-reviewer (batch-wide). **If the group is entirely `DOC_DEFER_CARDS` → skip the D.2 doc-reviewer** and log `D.2 doc-reviewer: DEFERRED to Final FULL gate (all cards light + no-doc diff)`. D.4a consumes the per-card findings (for the non-deferred cards) and dispatches the doc-reviewer (now alone, in write mode) to apply them — no second AUDIT spawn, but the FIXES are still owned by doc-reviewer.
168
- - **code-reviewer scoped to the diff-union of `LIGHT_CARDS` MINUS `TRIVIAL_CARDS`** (run it in parallel with the doc-reviewer over those files). These light non-trivial cards skip D.4b, so D.2 is their single per-card code review (the cross-batch Codex pass at Final covers them adversarially). **`TRIVIAL_CARDS` are excluded from the code-reviewer scope** — their diff is non-source, so there is nothing to code-review (doc-reviewer still covers them via the whole-group pass above; the Final FULL gate covers them adversarially). **If `LIGHT_CARDS \ TRIVIAL_CARDS` is empty → do NOT spawn code-reviewer at D.2** (every remaining card is either `FULL_CARDS` — deeper review at D.4b or `TRIVIAL_CARDS` nothing to review). Log the deterministic gate reason: `D.2 code-reviewer: scope=[<ids>] | excluded-trivial=[<ids>] | SKIPPED (no code-bearing light cards)`.
169
- - **Tradeoff (documented, accepted — see top-of-file `REVIEW DEPTH SCALES` clause)**: for `FULL_CARDS`, cross-card detection moves from the per-group D.2 pass to the per-batch **Final-review FULL gate** (Codex over the entire batch diff, plus code-reviewer fallback) same merge-gate safety, feedback arrives at batch-end rather than per-group. This is the intended de-duplication: `FULL_CARDS` are NOT re-reviewed by a group-level code-reviewer on top of their D.4b `/codexreview`.
166
+ 2. **D.2 — Combined static review (group)** — **doc-reviewer only** (since v4.18.0 the code-reviewer pass moved to D.4b; see below):
167
+ - **doc-reviewer — over the group MINUS `DOC_DEFER_CARDS`** (since v4.7.0). It runs **read-only here**, MUST **attribute every doc finding to a specific card** (by file → ownership map), and applies the full Phase 3 mandate INCLUDING the spec/docs-drift→bug lens (since v3.35.0). The `DOC_DEFER_CARDS` (light, no-doc diff per D.1.5) are excluded — their doc-review is deferred to the Final F.3 doc-reviewer (batch-wide). **If the group is entirely `DOC_DEFER_CARDS` → skip the D.2 doc-reviewer** and log `D.2 doc-reviewer: DEFERRED to Final FULL gate (all cards light + no-doc diff)`. D.4a consumes the per-card findings (for the non-deferred cards) and dispatches the doc-reviewer (in write mode) to apply them — no second AUDIT spawn, but the FIXES are still owned by doc-reviewer.
168
+ - **No D.2 code-reviewer (since v4.18.0).** Previously `code-reviewer` ran here over `LIGHT_CARDS \ TRIVIAL_CARDS` as their single per-card code review (to keep Codex off light). Now that `light` runs Codex per-card (Codex finder + FP-gate at D.4b `code-reviewer` validates there), the light cards' code review lives at **D.4b** alongside the full cards. D.2 no longer spawns a code-reviewer; log `D.2 code-reviewer: N/A (light cards code-reviewed at D.4b via Codex-light since v4.18.0)`.
169
+ - **Tradeoff (documented, accepted — see top-of-file `REVIEW DEPTH SCALES` clause)**: every non-trivial card now reaches a per-card Codex pass at D.4b (light → Codex-light, full Codex-full). `TRIVIAL_CARDS` (non-source diff) are still covered only by the doc-reviewer group pass + the Final FULL gate. The batch-wide **Final-review FULL gate** remains the cross-card safety net (Codex over the entire batch diff, plus code-reviewer fallback).
170
170
 
171
171
  3. **D.3 — Apply fixes (group)** — If D.2 findings exist, spawn ONE fix-coder to apply all fixes in a single pass. Run build + lint after.
172
172
 
@@ -183,10 +183,11 @@ After ALL agents in the group complete successfully:
183
183
 
184
184
  4a. **D.4a — Per-card doc gate (consumes D.2, since v3.35.0; doc-reviewer-applied since v3.40.0)** — Do NOT re-run the doc AUDIT (D.2 already produced per-card-attributed findings). If any card in the group has doc findings, invoke the **doc-reviewer once in write mode** over the group, passing the per-card-attributed findings from D.2, to APPLY all doc fixes in a single pass (it is now alone — code-reviewer is done — so the D.2 parallel-safety constraint no longer applies). Do **NOT** spawn a fix-coder for doc findings: `doc` is owned by `doc-reviewer` (see "Domain-Override Domains"); a code-oriented coder lacks the doc-invariant contract. The only exception is a doc-drift→bug finding rooted in CODE — that follows the D.4b code fix path. (Previously D.4a spawned a fix-coder per card; the audit was already collapsed to a single attributed D.2 pass. D.4b's per-card `/codexreview` also skips its doc-reviewer via the lean contract, so the doc AUDIT runs once per group while the doc FIXES run once per group via doc-reviewer.) **Telemetry** — append one row per applied doc finding to `## Fix Application Log`: `D.4a | doc | est_lines=<n> | decision=doc-reviewer | applied_by=doc-reviewer | card=<ID> | finding=<1-line>`. **Phase-8 producer (named counter)** — ALSO record per card the `doc_gaps: found=<N> fixed=<M>` line in that card's `## Completed Cards` entry (same named counter as sequential Phase 3 step 15), so Phase 8 reads `doc_gaps_found`/`doc_gaps_fixed` from a structured field, not a free-form row.
185
185
 
186
- 4b. **D.4b — Pre-Merge Codex Review Gate (per-card, profile-gated via D.1.5)** — Invoke `/codexreview` for **EACH card in `FULL_CARDS`** (the partition computed at D.1.5), one at a time, BEFORE any commit. This mirrors Phase 3.7 of the sequential path.
187
- - **`LIGHT_CARDS` → SKIP D.4b** (enumerated `review_profile`-driven gate reason, NOT a time skip). A `light`/`skip` 0-trigger card already received its single per-card code review at the **D.2 group pass**, and its Codex-adversarial review is **guaranteed by the unconditional Final-review FULL gate** (Codex over the entire batch diff, post-batch). Running `/codexreview` here at `light` would only re-run `code-reviewer` + FP-gate (Codex is dropped at `light` since v3.38.0) — a pure duplicate of D.2. Log per card: `codex-review: SKIPPED (light, 0 triggers D.2 group code-review covered + Codex adversarial guaranteed by Final FULL gate)`.
188
- - **`FULL_CARDS` → run `/codexreview` as today** (the per-card Codex adversarial + CoVe + FP-gate pass is exactly where the per-card code review for these cards lives — D.2 deliberately did NOT re-review them). The profile passed is always `full` (D.1.5 already resolved it; do NOT recompute). Keep the sequential one-at-a-time loop (avoid N concurrent `/codexreview`, each of which fans out multiple sub-agents → rate-limit risk).
189
- Apply the same fix sub-loop as sequential Phase 3.7 Step C.4: if the consolidated report shows verified BLOCKER/HIGH findings, spawn a fix-coder and — **re-writing the consumed-once lean contract first** — re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (max 2 retries per card; a bare prose mention or a missing card-ID would let the retry review the wrong card). If still BLOCKER/HIGH after retries, ask the user before proceeding to D.5. The D.5 commits MUST NOT happen until every card in the group has a PASS verdict (or explicit user override via `AskUserQuestion`). Log results in the tracker under `## Pre-Merge Codex Review` per card. **Lean + profile (since v3.35.0)**: before each card's `/codexreview`, apply the same Review Profile Selector and write the same `/tmp/codexreview-lean-<CARD-ID>.json` contract as sequential Phase 3.7 Step C — `arch_baseline_path` pointing at `/tmp/arch-baseline-group-<FIRST-CARD-ID>.md`, `skip_doc_reviewer: true`, `skip_api_perf_auditor: true` (since v4.7.0 — api-perf deferred to the Final F.3 batch-wide auditor), and `profile: full` (every card that reaches D.4b is in `FULL_CARDS` per D.1.5 — `light`/`skip` 0-trigger cards were already partitioned out and skip D.4b entirely; do NOT recompute the profile here). `full` runs the standard pipeline (Codex adversarial + CoVe + FP-gate, minus the duplicate doc-reviewer). The merge-gate safety for the skipped `LIGHT_CARDS` is the post-batch **Final-review FULL gate** (a single FULL `/codexreview` over the entire batch diff, which team mode reaches via "Post-batch same as sequential mode"). To make D.4b run for every card again (pre-v4.5.0 behavior), drop the D.1.5 partition and iterate over all group cards here at their per-card profile; the final full gate stays regardless.
186
+ 4b. **D.4b — Pre-Merge Codex Review Gate (per-card, profile-gated via D.1.5)** — Invoke `/codexreview` for **EVERY non-trivial card** (i.e. all of `LIGHT_CARDS \ TRIVIAL_CARDS` plus all `FULL_CARDS`), one at a time, BEFORE any commit. This mirrors Phase 3.7 of the sequential path. (Since v4.18.0 `LIGHT_CARDS` are NO LONGER skipped here — `light` runs a per-card Codex pass.)
187
+ - **`LIGHT_CARDS \ TRIVIAL_CARDS` → run `/codexreview` at `profile: light`** (since v4.18.0): **Codex is the sole finder** + the Step 3 FP-gate (`code-reviewer` + `codebase-architect` validate); no api-perf/doc/CoVe. This is where these cards' per-card code review now lives (D.2 no longer code-reviews them). On Codex-unavailable, `light` degrades to `code-reviewer` finder. Log `codex-review: <verdict> (light — Codex finder + FP-gate)`.
188
+ - **`TRIVIAL_CARDS` → SKIP D.4b** (non-source diff nothing for Codex to review; covered by the doc-reviewer group pass + the Final FULL gate). Log `codex-review: SKIPPED (trivial non-source diff; covered by Final FULL gate)`.
189
+ - **`FULL_CARDS` run `/codexreview` at `profile: full`** (Codex adversarial + `code-reviewer` finder + CoVe + FP-gate). Keep the sequential one-at-a-time loop (avoid N concurrent `/codexreview`, each of which fans out multiple sub-agents rate-limit risk). ⚠️ Since light cards now also run here, the D.4b loop is longer per group accepted cost of moving the diff sweep onto Codex.
190
+ Apply the same fix sub-loop as sequential Phase 3.7 Step C.4: if the consolidated report shows verified BLOCKER/HIGH findings, spawn a fix-coder and — **re-writing the consumed-once lean contract first** — re-invoke `/codexreview` via the Skill tool with `args: <CARD-ID>` (max 2 retries per card; a bare prose mention or a missing card-ID would let the retry review the wrong card). If still BLOCKER/HIGH after retries, ask the user before proceeding to D.5. The D.5 commits MUST NOT happen until every card in the group has a PASS verdict (or explicit user override via `AskUserQuestion`). Log results in the tracker under `## Pre-Merge Codex Review` per card. **Lean + profile (since v3.35.0)**: before each card's `/codexreview`, apply the same Review Profile Selector and write the same `/tmp/codexreview-lean-<CARD-ID>.json` contract as sequential Phase 3.7 Step C — `arch_baseline_path` pointing at `/tmp/arch-baseline-group-<FIRST-CARD-ID>.md`, `skip_doc_reviewer: true`, `skip_api_perf_auditor: true` (since v4.7.0 — api-perf deferred to the Final F.3 batch-wide auditor), and `profile` resolved from D.1.5 (`light` cards → `light`, `full` cards → `full`; do NOT recompute). The post-batch **Final-review FULL gate** (a single FULL `/codexreview` over the entire batch diff, which team mode reaches via "Post-batch — same as sequential mode") remains the cross-card safety net for every card including the trivial ones.
190
191
 
191
192
  5. **D.5 — Commit** — One commit per card using explicit staging from the ownership map. **NO stash in worktrees** (stashes are globally shared via `refs/stash` — see Phase 4 WORKTREE COMMIT RULE):
192
193
  ```bash
@@ -220,10 +221,10 @@ Before moving to Step E, the orchestrator MUST verify the tracker contains, for
220
221
  - `doc-review: <status | DEFERRED to Final FULL gate (DOC_DEFER_CARDS: light + no-doc diff)>` (from D.4a — runs for trivial cards too; the DEFERRED form is valid ONLY for `DOC_DEFER_CARDS` per D.1.5)
221
222
  - `qa: <profile=... | verdict=... | mechanical-only (trivial) | DEFERRED to Final FULL gate (group max=balanced, no risk escalation)>` (group-level from D.4; the DEFERRED form is valid when the group max profile ≤ balanced with no Step-A escalation)
222
223
  - `api-perf: <covered at D.4b | DEFERRED to Final FULL gate (skip_api_perf_auditor)>` (per-card — `skip_api_perf_auditor` defers it to F.3 batch-wide; DEFERRED is the normal v4.7.0 value)
223
- - `code-review: <covered at D.2 | covered at D.4b | SKIPPED (trivial — non-source diff)>` (per-card — the trivial SKIP is valid ONLY for `TRIVIAL_CARDS` per the D.1.5 partition)
224
- - `codex-review: <verdict | SKIPPED (light, 0 triggers D.2 group code-review covered + Final FULL gate) | SKIPPED (trivial — non-source diff; covered by Final FULL gate)>` (per-card from D.4b — the SKIPPED forms are valid ONLY for `LIGHT_CARDS` / `TRIVIAL_CARDS` respectively)
224
+ - `code-review: <covered at D.4b (Codex-light FP-gate or Codex-full) | SKIPPED (trivial — non-source diff)>` (per-card — since v4.18.0 light cards are code-reviewed at D.4b, not D.2; the trivial SKIP is valid ONLY for `TRIVIAL_CARDS` per the D.1.5 partition)
225
+ - `codex-review: <verdict (light Codex finder + FP-gate) | verdict (full) | SKIPPED (trivial — non-source diff; covered by Final FULL gate)>` (per-card from D.4b — since v4.18.0 `LIGHT_CARDS` produce a Codex verdict, NOT a skip; the SKIPPED form is valid ONLY for `TRIVIAL_CARDS`)
225
226
 
226
- A missing entry means a sub-step was skipped. An entry whose value is a **`review_profile`-driven SKIP/DEFER**, an **`IS_TRIVIAL` SKIP**, or a **`/prd`-provenance SKIP** with a documented enumerated reason above (D.3b `skip` profile; D.4b `LIGHT_CARDS`; code/codex `TRIVIAL_CARDS`; doc-review `DOC_DEFER_CARDS`; QA `group max=balanced, no escalation`; api-perf `skip_api_perf_auditor`; plan-auditor `holistic_audit provenance`) is a VALID, present entry — not a violation; all defer to the unconditional Final FULL gate. What remains forbidden: a missing entry, or a skip whose reason is `time budget` / `to save tokens` / any model-invented constraint. If any entry is genuinely missing, do NOT proceed to Step E — return to the missing sub-step and execute it. Documenting "skipped per time budget" or similar is a protocol violation per the top-of-file rigidity clause.
227
+ A missing entry means a sub-step was skipped. An entry whose value is a **`review_profile`-driven SKIP/DEFER**, an **`IS_TRIVIAL` SKIP**, or a **`/prd`-provenance SKIP** with a documented enumerated reason above (D.3b `skip` profile; D.4b/code/codex `TRIVIAL_CARDS`; doc-review `DOC_DEFER_CARDS`; QA `group max=balanced, no escalation`; api-perf `skip_api_perf_auditor`; plan-auditor `holistic_audit provenance`) is a VALID, present entry — not a violation; all defer to the unconditional Final FULL gate. What remains forbidden: a missing entry, or a skip whose reason is `time budget` / `to save tokens` / any model-invented constraint. If any entry is genuinely missing, do NOT proceed to Step E — return to the missing sub-step and execute it. Documenting "skipped per time budget" or similar is a protocol violation per the top-of-file rigidity clause.
227
228
 
228
229
  #### Step E: Context purge + next group
229
230
 
@@ -147,6 +147,9 @@ const PREFLIGHT_SCHEMA = {
147
147
  // G2 — deterministic Codex availability (glob-resolved, NOT a self-reported judgement),
148
148
  // so a false negative on the cross-card check is visible in telemetry. true=companion found.
149
149
  codexResolved: { type: 'boolean' },
150
+ // v4.18.0 — the resolved companion path (absolute), threaded into per-card review so a
151
+ // `light` card can run Codex as its finder. Empty string when codexResolved is false.
152
+ codexScriptPath: { type: 'string' },
150
153
  workspaceNote: { type: 'string' },
151
154
  archBaselinePaths: { type: 'array', items: { type: 'string' } },
152
155
  },
@@ -181,9 +184,13 @@ const projectBrief = [
181
184
  // companion glob FIRST (existence taken out of the agent's judgement), run it in the
182
185
  // BACKGROUND with a file poll, and forbid a premature CODEX_NOT_FOUND — same discipline as
183
186
  // new-final-review.js, fixing the parallel false-negative the v4.17.1 fix #3 left here.
187
+ // v4.18.0 — resolve the Codex companion for EVERY batch (single + multi), because `light` cards now
188
+ // run a per-card Codex finder (not just the multi-card cross-card check). The glob decides existence,
189
+ // not the agent's judgement; the resolved PATH is threaded to runCard via sharedCtx.
190
+ const codexResolveBullet = `• Codex companion resolution (ALWAYS — single AND multi-card; needed for the per-card Codex-light review since v4.18.0): run \`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\`. Set codexResolved:true AND codexScriptPath:<the printed absolute path> IFF it prints a path (existence is glob-decided, NOT your judgement); else codexResolved:false AND codexScriptPath:''. ALWAYS return BOTH fields.\n`
184
191
  const g3Bullet = cardIds.length < 2
185
- ? `• G3 cross-card grounding: SKIPPED — only one runnable card, nothing cross-card exists. Set crossCard='N/A-single-card' and codexResolved:false (not probed).\n`
186
- : `• G3 cross-card grounding (setup.md 3d), MULTI-card: FIRST resolve the Codex companion deterministically — run \`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\` and set codexResolved:true IFF it prints a path (existence is glob-decided, NOT your judgement; ALWAYS return codexResolved). If resolved, run the cross-card conflict check by launching \`node "$CODEX" task "<cross-card conflict questions: FILE_CONFLICT/IMPLICIT_DEP/ORDER_RISK/STATE_MUTATION>" > "$FILE" 2>&1\` in the BACKGROUND (run_in_background:true) and POLL "$FILE" to completion (full window). Read the audit ONLY via the [codex]-stripping filter; return DISTILLED findings. Fall to code-reviewer / set crossCard='CAPABILITY_UNAVAILABLE' ONLY when the glob is EMPTY, or it resolved but "$FILE" held CODEX_NOT_FOUND / stayed empty after the FULL window — NEVER because a single synchronous Bash call returned slowly. Provenance-skip still applies (same-provenance cards → nothing cross-card). If conflicts FOUND you MUST APPLY them: FILE_CONFLICT/ORDER_RISK → force the cards SEQUENTIAL and encode it in dependsOn (the conflicting later card dependsOn the earlier).\n`
192
+ ? `• G3 cross-card grounding: SKIPPED — only one runnable card, nothing cross-card exists. Set crossCard='N/A-single-card'. (The Codex companion is still resolved per the bullet above, for the per-card Codex-light review.)\n`
193
+ : `• G3 cross-card grounding (setup.md 3d), MULTI-card: using the codexScriptPath resolved above, IF codexResolved run the cross-card conflict check by launching \`node "$codexScriptPath" task "<cross-card conflict questions: FILE_CONFLICT/IMPLICIT_DEP/ORDER_RISK/STATE_MUTATION>" > "$FILE" 2>&1\` in the BACKGROUND (run_in_background:true) and POLL "$FILE" to completion (full window). Read the audit ONLY via the [codex]-stripping filter; return DISTILLED findings. Set crossCard='CAPABILITY_UNAVAILABLE' ONLY when codexResolved is false, or it resolved but "$FILE" held CODEX_NOT_FOUND / stayed empty after the FULL window — NEVER because a single synchronous Bash call returned slowly. Provenance-skip still applies (same-provenance cards → nothing cross-card). If conflicts FOUND you MUST APPLY them: FILE_CONFLICT/ORDER_RISK → force the cards SEQUENTIAL and encode it in dependsOn (the conflicting later card dependsOn the earlier).\n`
187
194
  phase('Pre-flight')
188
195
  let preflight = null
189
196
  try {
@@ -194,6 +201,7 @@ try {
194
201
  `DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
195
202
  `• G1 dirty-tree (main repo ${MAIN}): partition framework-managed noise exactly as setup.md step 3 ($METRICS=${METRICS}, .baldart/generated|state.json|skill-conflicts.json — NOT overlays/). Genuine user work → auto-stash 'baldart-new2-${firstCard}' (main checkout) and record the label. Never commit/abort/prompt.\n` +
196
203
  `• Worktree (setup.md step 4): create ONE code worktree off ${TRUNK}; install deps; assign a port; run the baseline (tsc+lint+build). Copy ONLY the artifacts needed (env/.env.local/.env.example/supabase/.temp) — do NOT bulk-copy untracked files from the main repo (avoids stray backlog cards in the worktree). Use the git-authoritative idempotency pre-check. E2: baseline FAILS → fix once; still failing → baseline:'fail' + baselineLog (batch-fatal).\n` +
204
+ codexResolveBullet +
197
205
  g3Bullet +
198
206
  `• G4 card-field validation (setup.md 1b/1c): card missing requirements/acceptance_criteria/files_likely_touched → EXCLUDE (excluded[] + reason). Never HALT for one bad card.\n` +
199
207
  `• G5 depends-on: a card whose depends_on names a non-DONE card NOT in this batch → EXCLUDE it AND every in-batch card that transitively depends on it.\n` +
@@ -217,8 +225,9 @@ if (!preflight || preflight.ok === false || preflight.baseline === 'fail') {
217
225
  for (const ex of preflight.excluded || []) ledger(ex.card, 'preflight-exclude', 'EXCLUDED', ex.reason)
218
226
  if (preflight.workspaceNote) ledger(firstCard, 'G1/G2-workspace', 'AUTO', preflight.workspaceNote)
219
227
  if (preflight.crossCard) ledger(firstCard, 'G3-cross-card', 'INFO', preflight.crossCard)
220
- // G2 — surface the deterministic Codex availability (false negative now visible in telemetry).
221
- if (cardIds.length >= 2) ledger(firstCard, 'G3-codex', preflight.codexResolved ? 'CODEX' : 'FALLBACK', preflight.codexResolved ? 'companion glob-resolved' : 'companion not found → code-reviewer')
228
+ // G2/v4.18.0 — surface the deterministic Codex availability (now probed for every batch; drives
229
+ // both the multi-card cross-card check AND the per-card Codex-light finder).
230
+ ledger(firstCard, 'codex-companion', preflight.codexResolved ? 'RESOLVED' : 'FALLBACK', preflight.codexResolved ? `glob-resolved → per-card Codex-light enabled (${preflight.codexScriptPath || '?'})` : 'companion not found → code-reviewer finder fallback')
222
231
 
223
232
  const runnableCards = preflight.cards || []
224
233
  const cardGraph = preflight.cardGraph || []
@@ -229,6 +238,9 @@ const sharedCtx = {
229
238
  branch: preflight.branch,
230
239
  ownershipMapPath: preflight.ownershipMapPath,
231
240
  archBaselinePaths: preflight.archBaselinePaths || [],
241
+ // v4.18.0 — per-card Codex-light finder needs the resolved companion path in runCard scope.
242
+ codexResolved: !!preflight.codexResolved,
243
+ codexScriptPath: preflight.codexScriptPath || '',
232
244
  }
233
245
 
234
246
  // F-016/F-010 — materialise ONE follow-up per policy-deferred AC up front; never resolve.
@@ -383,20 +395,39 @@ async function runCard(cardId, cardPath, lessons) {
383
395
  const securityRelevant = highRisk.length
384
396
  || ownerAgent === 'security-reviewer'
385
397
  || /auth|security|secret|migration|rls/i.test(`${scopeFiles.join(' ')} ${cardBrief}`)
398
+ // v4.18.0 — at `light`, Codex is the SOLE finder (cost-shift off Claude); `code-reviewer` is the
399
+ // fallback when the companion is unavailable. The FP-gate equivalent is preserved downstream: any
400
+ // block routes through resolve(), whose mandatory adversarial judge (new2-resolve F-015, code domain
401
+ // → code-reviewer) cross-checks the Codex finding before a fix/followup. `balanced`/`deep` unchanged.
402
+ const codexAvail = !!sharedCtx.codexResolved && !!sharedCtx.codexScriptPath
386
403
  const reviewers = reviewProfile === 'skip' ? []
387
- : reviewProfile === 'light' ? ['code-reviewer']
404
+ : reviewProfile === 'light' ? (codexAvail ? ['codex'] : ['code-reviewer'])
388
405
  : ['code-reviewer', 'doc-reviewer', 'qa-sentinel', 'api-perf-cost-auditor'].concat(
389
406
  securityRelevant ? ['security-reviewer'] : [])
407
+ const reviewSchema = { type: 'object', required: ['blocks', 'scopeExpansion'], additionalProperties: true,
408
+ properties: { blocks: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeExpansion: { type: 'array', items: { type: 'object', additionalProperties: true } }, note: { type: 'string' } } }
390
409
  let reviewResults = []
391
410
  try {
392
- reviewResults = (await parallel(reviewers.map((ra) => () => agentSafe(
393
- `You are ${ra}. Review card ${cardId} per ${REF}/review-cycle.md + ${REF}/codex-gate.md (your domain only). Run your gates on the COMMITTED-or-working state.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\n\n` +
394
- `Report ONLY blocking failures that survive your retry cap as blocks:[{gate,domain,evidence}] (each MUST have non-empty gate AND evidence — F-014). Report legitimate findings BEYOND this card's AC as scopeExpansion:[{evidence,domain,withinOwnership,newAC}].\n\n` +
395
- `Return: { blocks:[...], scopeExpansion:[...], note }`,
396
- { label: `review:${cardId}:${ra}`, phase: 'Implement', agentType: ra,
397
- schema: { type: 'object', required: ['blocks', 'scopeExpansion'], additionalProperties: true,
398
- properties: { blocks: { type: 'array', items: { type: 'object', additionalProperties: true } }, scopeExpansion: { type: 'array', items: { type: 'object', additionalProperties: true } }, note: { type: 'string' } } } }
399
- ).catch((e) => { if (e && e.transientExhausted) noteDegraded('outage'); return null }))) ).filter(Boolean)
411
+ reviewResults = (await parallel(reviewers.map((ra) => () => {
412
+ const onErr = (e) => { if (e && e.transientExhausted) noteDegraded('outage'); return null }
413
+ if (ra === 'codex') {
414
+ // Codex-light finder: a general-purpose agent drives the resolved companion (Bash, --wait).
415
+ return agentSafe(
416
+ `You are the Codex review driver for card ${cardId} (review_profile=light Codex is the SOLE finder since v4.18.0; you are NOT code-reviewer). Run the OpenAI Codex companion as a REVIEW-ONLY adversarial pass over this card's diff, then return its material findings in the schema below.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\nMAY-EDIT: ${JSON.stringify(mayEdit)}\n\n` +
417
+ `Run it in the FOREGROUND (it blocks; do NOT pass run_in_background):\n node "${sharedCtx.codexScriptPath}" task "Review-only — DO NOT make edits, no --write flag. Adversarial review of card ${cardId} using the diff at /tmp/diff-${cardId}.txt. Focus: auth/permission boundaries, data-loss paths, race conditions, rollback safety, schema drift, invariant violations. Report ONLY material findings with file+line evidence." --wait\n` +
418
+ `Read the Codex output ONLY through a [codex]-trace-stripping filter. **Fallback**: if it exits non-zero / prints CODEX_NOT_FOUND / stays empty, set note='codex-unavailable' and perform the review yourself with the code-reviewer lens (your domain).\n\n` +
419
+ `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` +
420
+ `Return: { blocks:[...], scopeExpansion:[...], note }`,
421
+ { label: `review:${cardId}:codex`, phase: 'Implement', agentType: 'general-purpose', schema: reviewSchema }
422
+ ).catch(onErr)
423
+ }
424
+ return agentSafe(
425
+ `You are ${ra}. Review card ${cardId} per ${REF}/review-cycle.md + ${REF}/codex-gate.md (your domain only). Run your gates on the COMMITTED-or-working state.\n\n${cardBrief}\nDiff: /tmp/diff-${cardId}.txt\n\n` +
426
+ `Report ONLY blocking failures that survive your retry cap as blocks:[{gate,domain,evidence}] (each MUST have non-empty gate AND evidence — F-014). Report legitimate findings BEYOND this card's AC as scopeExpansion:[{evidence,domain,withinOwnership,newAC}].\n\n` +
427
+ `Return: { blocks:[...], scopeExpansion:[...], note }`,
428
+ { label: `review:${cardId}:${ra}`, phase: 'Implement', agentType: ra, schema: reviewSchema }
429
+ ).catch(onErr)
430
+ }))).filter(Boolean)
400
431
  } catch (_) { /* parallel never rejects; nulls filtered */ }
401
432
 
402
433
  // F-014 — only route well-formed blocks (non-empty gate+evidence).
@@ -665,7 +696,7 @@ function buildTelemetry() {
665
696
  degraded,
666
697
  degradation_reasons: degradationReasons,
667
698
  execution_mode: preflight ? preflight.executionMode : 'sequential',
668
- codex_resolved: (preflight && cardIds.length >= 2) ? !!preflight.codexResolved : null, // G2null = not probed (single-card)
699
+ codex_resolved: preflight ? !!preflight.codexResolved : null, // v4.18.0probed for EVERY batch (drives per-card Codex-light + multi-card cross-card)
669
700
  // cost — total_tokens via budget.spent() delta; agent_count via counter; wall_clock_s stamped by the SKILL.
670
701
  total_tokens: totalTokens,
671
702
  agent_count: agentCount,
@@ -10,6 +10,15 @@ schedule:
10
10
 
11
11
  agent: code-reviewer
12
12
 
13
+ # Review engine (since v4.18.0 — opt-in, additive, default claude). `codex` shifts the finding
14
+ # pass onto the OpenAI Codex companion to spend on a Codex licence instead of Claude tokens.
15
+ # IMPORTANT: only the `cron` backend supports `codex` — it needs the Codex plugin + `~/.codex/`
16
+ # authenticated in the cron user's home. The `github-actions` and `claude-code-cloud` backends
17
+ # fall back to `claude` with a warning (no Codex plugin / unknown remote schema). With `codex`,
18
+ # specialist auto-spawn is NOT available (Codex cannot spawn subagents) — use `claude` when you
19
+ # need the security-reviewer / api-perf-cost-auditor depth. See framework/docs/WORKFLOWS.md.
20
+ review_engine: claude
21
+
13
22
  prompt: |
14
23
  Run the nightly code review:
15
24
 
@@ -30,6 +39,22 @@ prompt: |
30
39
  Scope is changed files only. Pre-existing code is not in scope unless
31
40
  a change introduces a regression there.
32
41
 
42
+ # Used ONLY when review_engine: codex (cron backend). Single-pass adversarial review — Codex
43
+ # cannot spawn specialist subagents, so this is holistic (no security-reviewer / api-perf split).
44
+ prompt_codex: |
45
+ Review-only adversarial pass — DO NOT make edits, no --write flag.
46
+
47
+ 1. Compute the diff for commits from the last 24h on the main / develop branch
48
+ (changed files only; pre-existing code in scope only if a change regresses it).
49
+ 2. Holistic single-pass review (NO specialist subagents available): bugs, regressions,
50
+ logic flaws, AND — since there is no specialist split — also cover auth/permission
51
+ boundaries, data-loss paths, race conditions, and obvious API/perf/cost defects yourself.
52
+ 3. Report only material findings with file+line evidence, ordered by severity.
53
+ 4. Write the consolidated report to `docs/reports/{{YYYYMMDD}}-code-review.md`.
54
+
55
+ NOTE: this engine trades specialist depth for cost-shift onto the Codex licence. For full
56
+ security-reviewer / api-perf-cost-auditor coverage, set review_engine: claude.
57
+
33
58
  output:
34
59
  path: docs/reports/{{YYYYMMDD}}-code-review.md
35
60
  commit:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "baldart",
3
- "version": "4.17.2",
3
+ "version": "4.18.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"
@@ -53,10 +53,16 @@ class ClaudeCodeCloudAdapter {
53
53
  installed_at: new Date().toISOString()
54
54
  };
55
55
  fs.writeFileSync(this.configPath(spec.name), JSON.stringify(config, null, 2) + '\n');
56
+ // review_engine: codex is cron-only — the RemoteTrigger config schema does not carry it and the
57
+ // cloud runner is not guaranteed to have the Codex plugin/auth — so this backend always uses claude.
58
+ const codexWarn = String(spec.review_engine || '').toLowerCase() === 'codex'
59
+ ? [`⚠ review_engine: codex is NOT supported on claude-code-cloud — this routine runs on the claude engine.`]
60
+ : [];
56
61
  return {
57
62
  backend: this.name,
58
63
  artifacts: [path.relative(this.cwd, this.configPath(spec.name))],
59
64
  followup: [
65
+ ...codexWarn,
60
66
  `Open Claude Code in this repo and run:`,
61
67
  ` /schedule create --config .claude/routines/${spec.name}.json`,
62
68
  `(or invoke the agent once with /loop ${spec.schedule.cron} <command> to test the cadence)`
@@ -76,6 +76,10 @@ class CronAdapter {
76
76
  const commitEnabled = spec.output && spec.output.commit && spec.output.commit.enabled;
77
77
  const commitPrefix = (spec.output && spec.output.commit && spec.output.commit.prefix) || `[${spec.name.toUpperCase()}]`;
78
78
  const promptHeredoc = spec.prompt;
79
+ // review_engine (since v4.18.0): cron is the ONLY backend that supports `codex`. When set, the
80
+ // wrapper drives the OpenAI Codex companion and falls back to `claude --agent` if it is absent.
81
+ const engine = String(spec.review_engine || 'claude').toLowerCase();
82
+ const promptCodex = spec.prompt_codex || spec.prompt;
79
83
  const repoRoot = '"$(cd "$(dirname "$0")/../.." && pwd)"';
80
84
 
81
85
  return `#!/usr/bin/env bash
@@ -99,7 +103,13 @@ if ! command -v claude >/dev/null 2>&1; then
99
103
  echo "ERROR: claude CLI not found on PATH" >&2
100
104
  exit 1
101
105
  fi
102
- : "\${ANTHROPIC_API_KEY:?ANTHROPIC_API_KEY must be set (export it from the cron env or your shell profile)}"
106
+ ${engine === 'codex'
107
+ ? `# review_engine: codex — Codex authenticates via the plugin + ~/.codex (NOT ANTHROPIC_API_KEY).
108
+ # The key is only needed if the run falls back to claude, so this is a soft warning, not a hard stop.
109
+ if [ -z "\${ANTHROPIC_API_KEY:-}" ]; then
110
+ echo "NOTE: ANTHROPIC_API_KEY unset — fine for the Codex engine, but the claude fallback would fail." >&2
111
+ fi`
112
+ : `: "\${ANTHROPIC_API_KEY:?ANTHROPIC_API_KEY must be set (export it from the cron env or your shell profile)}"`}
103
113
 
104
114
  # Marks this as an unattended (no-human) run. The /baldart-update skill (and
105
115
  # any other autonomy-aware skill) auto-detects this and switches to
@@ -113,17 +123,37 @@ OUT_PATH="${outputPath}"
113
123
  OUT_PATH="\${OUT_PATH//\\{\\{YYYYMMDD\\}\\}/$DATE}"
114
124
  mkdir -p "$(dirname "$OUT_PATH")"
115
125
 
116
- claude --print --output-format json --mode bypassPermissions \\
117
- --agent ${spec.agent} \\
118
- > /tmp/baldart-${spec.name}.json <<'PROMPT'
126
+ # Claude engine (also the fallback for the Codex engine).
127
+ run_claude() {
128
+ claude --print --output-format json --mode bypassPermissions \\
129
+ --agent ${spec.agent} \\
130
+ > /tmp/baldart-${spec.name}.json <<'PROMPT'
119
131
  ${promptHeredoc}
120
132
  PROMPT
121
-
122
- # Persist the output if the agent did not already write to disk
123
- if [ ! -s "$OUT_PATH" ]; then
124
- jq -r '.result // .' /tmp/baldart-${spec.name}.json > "$OUT_PATH" 2>/dev/null \\
125
- || cp /tmp/baldart-${spec.name}.json "$OUT_PATH"
126
- fi
133
+ # Persist the output if the agent did not already write to disk
134
+ if [ ! -s "$OUT_PATH" ]; then
135
+ jq -r '.result // .' /tmp/baldart-${spec.name}.json > "$OUT_PATH" 2>/dev/null \\
136
+ || cp /tmp/baldart-${spec.name}.json "$OUT_PATH"
137
+ fi
138
+ }
139
+ ${engine === 'codex'
140
+ ? `# review_engine: codex — resolve the OpenAI Codex companion (glob decides existence, not us).
141
+ CODEX_SCRIPT="$(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)"
142
+ if [ -n "$CODEX_SCRIPT" ] && command -v node >/dev/null 2>&1; then
143
+ echo "Codex engine: $CODEX_SCRIPT"
144
+ node "$CODEX_SCRIPT" task "$(cat <<'CODEXPROMPT'
145
+ ${promptCodex}
146
+ CODEXPROMPT
147
+ )" --wait > "$OUT_PATH" 2>/tmp/baldart-${spec.name}-codex.err || true
148
+ if [ ! -s "$OUT_PATH" ]; then
149
+ echo "WARN: Codex produced no output (see /tmp/baldart-${spec.name}-codex.err) — falling back to claude" >&2
150
+ run_claude
151
+ fi
152
+ else
153
+ echo "WARN: CODEX_NOT_FOUND — codex plugin / ~/.codex not present for this user, or node missing — falling back to claude" >&2
154
+ run_claude
155
+ fi`
156
+ : `run_claude`}
127
157
  ${commitEnabled ? `
128
158
  # Auto-commit the report and any inline fixes
129
159
  if git diff --quiet && git diff --staged --quiet; then
@@ -36,10 +36,15 @@ class GitHubActionsAdapter {
36
36
  }
37
37
  const yml = this._renderWorkflow(spec);
38
38
  fs.writeFileSync(this.workflowPath(spec.name), yml);
39
+ // review_engine: codex is cron-only (no Codex plugin/auth in CI) — this backend always uses claude.
40
+ const codexWarn = String(spec.review_engine || '').toLowerCase() === 'codex'
41
+ ? [`⚠ review_engine: codex is NOT supported on github-actions (no Codex plugin/auth in CI) — this routine runs on the claude engine.`]
42
+ : [];
39
43
  return {
40
44
  backend: this.name,
41
45
  artifacts: [path.relative(this.cwd, this.workflowPath(spec.name))],
42
46
  followup: [
47
+ ...codexWarn,
43
48
  `Set the repository secret ANTHROPIC_API_KEY in GitHub.`,
44
49
  `Commit and push .github/workflows/baldart-${spec.name}.yml to enable the schedule.`,
45
50
  `Manual test: gh workflow run baldart-${spec.name}.yml`