baldart 4.16.2 → 4.17.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 +27 -0
- package/VERSION +1 -1
- package/framework/.claude/agents/prd-card-writer.md +25 -0
- package/framework/.claude/skills/new2/SKILL.md +42 -17
- package/framework/.claude/workflows/new-final-review.js +66 -17
- package/framework/.claude/workflows/new2-resolve.js +219 -92
- package/framework/.claude/workflows/new2.js +432 -225
- package/framework/docs/WORKFLOWS.md +2 -2
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,33 @@ 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.17.1] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
**`new2` post-refactor: tre falle di routing/detection emerse al secondo run reale (card FEAT-0019-followup).** Dopo l'hardening v4.17.0, gli agent principali sono correttamente specializzati, ma il *resolve* e il *final review* mostravano tre errori di instradamento. **PATCH** (bugfix di orchestrazione nei workflow `new2-resolve`/`new-final-review`; nessuna nuova capability, nessun cambio layout, **nessuna chiave `baldart.config.yml`** → schema-propagation N/A).
|
|
11
|
+
|
|
12
|
+
> **Why.** Tutti e tre i bug condividono la stessa radice: una decisione che dovrebbe essere **deterministica** (quale agent per quale dominio / esiste Codex) era affidata o a una mappa incompleta o al self-report soft di un subagent. Il dominio freeform `documentation` non matchava la chiave `doc`, il companion Codex (installato e funzionante) veniva dichiarato assente per un timeout di una Bash-call sincrona, e le follow-up card nascevano da un Haiku general-purpose anziché dal loro owner.
|
|
13
|
+
|
|
14
|
+
### Fixed
|
|
15
|
+
|
|
16
|
+
- **`framework/.claude/workflows/new2-resolve.js` — routing per dominio robusto (F-038/F-024).** Le review agent emettono `domain` come **stringa libera** (`documentation`, `frontend`, …); la mappa fixer keyava solo su `doc`/`ui`, così `documentation` cadeva sul costoso `coder` (Opus) per un finding di pura documentazione. Aggiunto `normDomain()` che normalizza ai bucket di routing (`doc|ui|security|migration|perf|test|code`) **prima** di scegliere fixer e judge, e mappa fixer estesa (`doc→doc-reviewer`, `ui→ui-expert`, `security→security-reviewer`, resto→`coder`) — reviewer-owns-its-domain anche in fase di *apply*, non solo di *judge*.
|
|
17
|
+
- **`framework/.claude/workflows/new2-resolve.js` — follow-up card via `prd-card-writer` (F-039).** La materializzazione della follow-up usava `general-purpose` + Haiku scrivendo uno stub YAML minimale; ora delega all'agent **`prd-card-writer`** (owner del `card-template`, Rule C per `review_profile`, `owner_agent`, traceability) — le card di follow-up rispettano le stesse regole SSOT di quelle native.
|
|
18
|
+
- **`framework/.claude/workflows/new-final-review.js` — detection Codex deterministica + background (F-040).** Codex risultava "non disponibile" pur essendo installato e funzionante: il workflow delegava l'intera invocazione a un subagent generico che la eseguiva **in modo sincrono** dentro una Bash-call e auto-dichiarava `codexAvailable` — un run reale di Codex (minuti) superava il timeout della call → falso negativo → fallback a `code-reviewer`. Ora un **pre-flight deterministico** (un Haiku risolve SOLO il glob del companion e ritorna il path: la decisione di esistenza è tolta al review agent), poi Codex gira in **background con poll su file** e finestra 10 min (allineato a `references/final-review.md` F.3/F.4); `codexAvailable:false` è ammesso solo per `CODEX_NOT_FOUND` reale o file vuoto a fine timeout. Un **unico** fallback deterministico a `code-reviewer` copre sia "companion assente" sia "run non completato".
|
|
19
|
+
- **`new2.js` + `new-final-review.js` — final review snella per batch mono-card (F-041).** Un batch da 1 sola card eseguiva la batch final review **per intero** (`doc-reviewer` + `api-perf-cost-auditor` + `qa-sentinel`), **duplicando** la review per-card (Phase 3) che aveva già girato gli stessi agent sugli stessi file — e senza alcun conflitto cross-card da trovare (~300k token ripetuti su un run reale da 1,28M). Ora `new2.js` passa `singleCard: committed.length === 1`; con quel flag la final review tiene **solo** il passaggio cross-model Codex (valore unico: modello diverso → bug diversi) + i gate `qa-sentinel` (letti dall'integrity gate di merge), e salta i duplicati Claude. Batch multi-card invariati.
|
|
20
|
+
- **`new2.js` + `new2-resolve.js` — fan-out Tier-2 inutile su finding doc (F-038/F-033/F-036).** Un singolo merge-blocker doc-domain già **risolto al Tier-1 e confermato dal judge** (`best:1`) faceva comunque partire **3 `doc-reviewer` paralleli (~150k token)** — con rischio di follow-up spuria per un problema già chiuso. Tre cause composte, tutte corrette: **(A)** la resolve riceveva come MAY-EDIT i file di **codice** della card (`reviewScopeFiles`), così il fixer doc editava `docs/` "fuori ownership" — ora `domainMayEdit()` dà ai finding doc la loro territory reale (`docs_dir`/`references_dir`/`wiki_dir`/`prd_dir`); **(B)** la guardia anti-fabbricazione del judge usava `.every()` (rigetto se *anche un solo* file confermato cade fuori MAY-EDIT) — ma il judge lista naturalmente i file adiacenti (il codice che una doc descrive); ora `someInScope()` (`.some()`) rigetta solo un verdetto che non conferma **nessun** file di proprietà (phantom puro); **(C)** il fan-out a 3 angoli (`root-cause`/`call-site`/`reuse-utility`) è code-bug-shaped → per domini deterministici non-code (doc/test) ora un **singolo** retry mirato invece di 3.
|
|
21
|
+
|
|
22
|
+
## [4.17.0] - 2026-06-07
|
|
23
|
+
|
|
24
|
+
**`new2` hardening single-wave: 36 finding dal primo run reale risolti in un colpo.** Il primo run reale di `new2` (batch FEAT-0021, 9 card) sotto un outage 529 prolungato è costato **7.4M token / 100 agent / 5h08m producendo solo 6/9 card reali**, con 2 stati `DONE` falsi nel backlog, codice security RLS-bypass non revisionato finito su `develop` via un "safety commit", e 1 follow-up persa. Una raccolta diagnostica end-to-end (36 finding verificati su codice + telemetria reale) ha isolato cause **strutturali**; questo rilascio le risolve tutte in un'unica wave, riportando il costo atteso a ~1.5-2.5M e — prioritario — azzerando le violazioni di integrità/sicurezza sotto degradazione. **MINOR** (cambio sostanziale di capacità del workflow esperimentale `new2`; nessuna rimozione/cambio layout d'install; **nessuna nuova chiave `baldart.config.yml`** → schema-propagation N/A).
|
|
25
|
+
|
|
26
|
+
> **Why.** `new2` è un A/B di `/new` per misurare l'economia di contesto del workflow-hosting. Il run reale ha mostrato che, oltre al costo, l'autonomia zero-ask degradava in modo **non sicuro** sotto outage (DONE falsi, codice non revisionato su trunk, follow-up persa) — esattamente il recovery che `/new` (gated, main-loop) avrebbe fermato. Decisioni utente vincolanti: *Decision-auto, infra-deferred* (niente `BALDART_AUTONOMOUS=1`; azioni infra owner-gated sempre policy-deferred) e *agenti specializzati come `/new`* (no general-purpose dove conta la qualità).
|
|
27
|
+
|
|
28
|
+
### Changed
|
|
29
|
+
- **`framework/.claude/workflows/new2.js` — scheduler a DAG + resilienza + integrità.** Il for-loop sequenziale e il ramo team-mode sono sostituiti da uno **scheduler dependency-gated** (semantica single-worktree): una card parte solo quando TUTTE le dipendenze in-batch sono `committed`; i dipendenti di una dep fallita vanno `blocked` (niente resolve doomed, F-017/F-021); fallimento transitorio → `pending` + re-invocazione (cap); outage sostenuto → **degrada pulito** (`degraded:true` + worktree clean + return-as-ledger, F-012/F-019/F-020). Implement usa l'**owner_agent** della card; review è un **fan-out specializzato** sfrondato dal `review_profile` (F-024/F-025); commit è **Haiku** con `git status` reconcile, mai `git add -A` (F-023). Skip-completed idempotente su commit-sha+receipt+no-open-followup, NON sul flag `DONE` (F-026). Merge **integrity-gated**: mai force-DONE (F-029), mai commit di codice non revisionato (F-030), mai merge di batch incompleto/degradato (F-031). Telemetria veritiera: `total_tokens` via `budget.spent()`, `agent_count`, `degraded`, `cards_real_done` vs `cards_force_done`=0 (F-032). Parse-guard `args` (F-001).
|
|
30
|
+
- **`framework/.claude/workflows/new2-resolve.js` — short-circuit + anti-fabbricazione + batching.** Verdetto **terminale** che salta il multi-attempt quando il problema è impossibile-by-definition (`out-of-ownership` verificato in JS contro MAY-EDIT; altri reason ratificati dal judge → niente escape-hatch, F-008). **Judge avversariale OBBLIGATORIO** su ogni claim `verified`, con **cross-check JS** dei file confermati indipendentemente dal judge (defeats fabricated success, beccata 2× nel run reale — F-015/F-033). Lista **`findings` batchata** (1 resolve per fix-area, F-007/F-035/F-037). Fixer/judge **per dominio** (doc→doc-reviewer, ui→ui-expert, security→security-reviewer judge, perf→api-perf-cost-auditor judge — F-024). `outOfScopeFindings` non più persi (F-022). Follow-up su Haiku, **offline-safe** (deferita al skill se nessun agente può scrivere, F-020). Parse-guard `args` (F-004).
|
|
31
|
+
- **`framework/.claude/skills/new2/SKILL.md` — reconcile + resume + telemetria reale.** Step 4 passa `ts` (il workflow non ha clock). Step 5: materializza le follow-up dei `residuals` non ancora su disco (offline-safe — il skill ha il filesystem → chiude "nothing dropped" sotto outage, F-020), **rilancia** con `resumeFromRunId` se `degraded` (idempotente via skip-completed), registra telemetria veritiera (`wall_clock_s`, `followups_on_disk` contando i file — F-032).
|
|
32
|
+
- **`framework/.claude/agents/prd-card-writer.md` — Rule A.1 (AC↔ownership consistency).** Fix a monte del difetto ricorrente "AC fuori dal MAY-EDIT" (F-016): ogni AC dev'essere soddisfabile editando i file della propria card.
|
|
33
|
+
- **`framework/docs/WORKFLOWS.md`** — righe `new2`/`new2-resolve` aggiornate al comportamento v4.17.0.
|
|
34
|
+
|
|
8
35
|
## [4.16.2] - 2026-06-06
|
|
9
36
|
|
|
10
37
|
**`baldart update` non si blocca più all'infinito su un edit `.framework/` ormai assorbito a monte.** Un edit locale a un file framework (es. `agents/ui-expert.md`) committato con un subject che *non* contiene la parola "baldart" sfuggiva alla classificazione `chore-wrapper` (rumore) e veniva marcato `custom-overlay-able`. Quel commit resta per sempre ancestor di `HEAD` ma mai di `FETCH_HEAD`, quindi `git log FETCH_HEAD..HEAD -- .framework` lo ripesca a **ogni rilascio successivo** — e il flusso seamless si fermava sul gate `divergence-capture-incomplete` perché l'overlay (additivo) non riproduceva byte-a-byte il file `.framework`, anche quando il contenuto era già identico all'upstream e la personalizzazione viveva correttamente nell'overlay. Risultato: lo stesso commit "fantasma" rifaceva muro a ogni `update`, senza via d'uscita via overlay (riconciliare un overlay additivo non può mai far passare il round-trip stretto). **PATCH** (bugfix del detector di divergenza + reset-safety degli overlay additivi; nessun cambio di capability, nessuna chiave `baldart.config.yml`).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.
|
|
1
|
+
4.17.1
|
|
@@ -136,6 +136,31 @@ that list — not this one — is the source of truth.
|
|
|
136
136
|
**Epic cards** use `owner_agent: ""` (empty string) because they are
|
|
137
137
|
trackers, not implementation. The dispatcher skips them.
|
|
138
138
|
|
|
139
|
+
### Rule A.1 — AC↔ownership consistency (every AC must be satisfiable inside the card)
|
|
140
|
+
|
|
141
|
+
For EVERY acceptance criterion you write, the file(s) that AC requires editing
|
|
142
|
+
MUST be a subset of that card's `files_likely_touched` (its MAY-EDIT ownership).
|
|
143
|
+
An AC whose ONLY implementation site lives in another card's file is
|
|
144
|
+
**structurally unsatisfiable** by this card — at execution time it can never
|
|
145
|
+
close, so it burns an entire resolve cycle and then defers anyway (and, on
|
|
146
|
+
impossible tasks, *induces* fabricated "fixed" claims). Concretely:
|
|
147
|
+
|
|
148
|
+
- If an AC needs the JSX **render** of a value, the card that owns the render
|
|
149
|
+
file (e.g. `homepage-store.tsx`) must carry that AC — not the data/view-model
|
|
150
|
+
card that merely computes the value.
|
|
151
|
+
- If an AC needs **wiring at a call-site** (e.g. `after(() => dispatch())` inside
|
|
152
|
+
a create funnel in `data.ts`), assign it to the card that owns that file, or
|
|
153
|
+
add that file to this card's `files_likely_touched`, or express the dependency
|
|
154
|
+
explicitly via `depends_on` + a card that owns the site.
|
|
155
|
+
- An AC whose remedy is an **owner-gated infra action** (remote DB push, deploy,
|
|
156
|
+
secret rotation, DNS) is NOT a code AC — phrase it as a tracked manual/ops
|
|
157
|
+
step, never as a per-card code acceptance criterion.
|
|
158
|
+
|
|
159
|
+
Self-check before emitting cards: for each AC, name the file it edits and
|
|
160
|
+
confirm that file is in the same card's `files_likely_touched`. If it isn't,
|
|
161
|
+
move the AC or widen ownership. (This is the upstream fix for the recurring
|
|
162
|
+
"AC outside MAY-EDIT" failure; `new2`/`/new` also re-assert it at pre-flight.)
|
|
163
|
+
|
|
139
164
|
### Rule B — UI cards (`ui-expert`) have UI-ONLY scope
|
|
140
165
|
|
|
141
166
|
When you assign `owner_agent: ui-expert` to a card, its scope MUST cover
|
|
@@ -99,30 +99,55 @@ Workflow({ name: 'new2', args: {
|
|
|
99
99
|
metricsDir, // $METRICS
|
|
100
100
|
refModulesBase, // .claude/skills/new/references (semantic SSOT)
|
|
101
101
|
config, // the parsed baldart.config.yml (paths.*/stack.*/features.*/git.*)
|
|
102
|
+
ts, // ISO timestamp NOW — the workflow has no clock (Date.now() unavailable there)
|
|
102
103
|
flags: { stats, effort, full }
|
|
103
104
|
}})
|
|
104
105
|
```
|
|
105
106
|
|
|
107
|
+
Record the kickoff time (`ts`) before the call — Step 5 uses it to stamp the
|
|
108
|
+
real wall-clock duration (the workflow cannot read a clock).
|
|
109
|
+
|
|
106
110
|
The workflow runs the entire batch in the background. **Do not poll it** — it
|
|
107
111
|
returns when the batch is done. It returns:
|
|
108
112
|
|
|
109
113
|
```
|
|
110
|
-
{ report, perCardResults, gateLedger, residualFollowups,
|
|
114
|
+
{ report, perCardResults, gateLedger, residualFollowups, residuals,
|
|
115
|
+
degraded, degradationReasons, telemetry }
|
|
111
116
|
```
|
|
112
117
|
|
|
113
|
-
`report`
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
118
|
+
- `report` — ready-to-show markdown batch summary.
|
|
119
|
+
- `residuals` — the **OFFLINE-SAFE ledger of record**: every residual the workflow
|
|
120
|
+
could not finish, each `{ card, kind, evidence, materialized }`. A residual with
|
|
121
|
+
`materialized:false` has NO follow-up card on disk yet (e.g. the workflow hit an
|
|
122
|
+
outage where no agent could write the file). **You (the skill) must reconcile it.**
|
|
123
|
+
- `degraded` / `degradationReasons` — the batch stopped early under a sustained
|
|
124
|
+
outage (or another degradation). The batch is NOT complete; it must be resumed.
|
|
125
|
+
- `telemetry` — the Phase-8 record (`variant:"new2"`).
|
|
126
|
+
|
|
127
|
+
### Step 5 — Reconcile, resume, present, record
|
|
128
|
+
|
|
129
|
+
1. **Materialise missing follow-ups (offline-safe — you have filesystem access, the
|
|
130
|
+
workflow does not).** For every `residuals[]` entry with `materialized:false`,
|
|
131
|
+
write a follow-up card `${paths.backlog_dir}/<card>-followup-<kind>.yml` (status:
|
|
132
|
+
TODO; ≥1 requirement derived from the residual; acceptance_criteria = the verbatim
|
|
133
|
+
residual; files_likely_touched from the card's ownership) — it MUST pass the `/new`
|
|
134
|
+
pre-flight field check. This is the layer that guarantees **nothing is ever
|
|
135
|
+
dropped, even when every agent was dead** during the run.
|
|
136
|
+
2. **Resume if degraded.** If `degraded` is true, re-invoke the workflow with
|
|
137
|
+
`Workflow({ scriptPath, resumeFromRunId })` (same `args` + the new `ts`). The
|
|
138
|
+
per-card **skip-completed** guard makes the resume idempotent — already-committed
|
|
139
|
+
cards are skipped, only the incomplete/blocked ones run. Repeat until `degraded`
|
|
140
|
+
is false (or the same cards stall twice → surface to the user).
|
|
141
|
+
3. **Present.** Print `report` verbatim. Surface `residuals` prominently
|
|
142
|
+
("questi residui sono tracciati come follow-up: …") — the post-run review that
|
|
143
|
+
replaced the ~25 mid-run questions. If `degraded`, say so plainly (the run was
|
|
144
|
+
incomplete and resumed).
|
|
145
|
+
4. **Record truthful telemetry.** Before appending `telemetry` to
|
|
146
|
+
`${metricsDir}/skill-runs.jsonl`, fill the fields the workflow could not compute:
|
|
147
|
+
`wall_clock_s` (now − kickoff `ts`) and `followups_on_disk` (count the actual
|
|
148
|
+
follow-up files on disk, NOT `residualFollowups.length` — which double-counts).
|
|
149
|
+
`total_tokens`/`agent_count` come from the workflow (`budget.spent()` delta +
|
|
150
|
+
spawn counter); if `total_tokens` is null, run the `/new` Phase-8 `-stats` script
|
|
151
|
+
to backfill real `usage`. Keep `degraded`/`degradation_reasons` in the record so
|
|
152
|
+
the A/B comparison can exclude or weight degraded runs. Do NOT re-summarise the
|
|
153
|
+
cards — the workflow already did.
|
|
@@ -116,11 +116,40 @@ if (baselinePaths.length) {
|
|
|
116
116
|
// ───────────────────────────────────────────────────────────────────────────
|
|
117
117
|
phase('Review')
|
|
118
118
|
|
|
119
|
+
// F-040 — DETERMINISTIC Codex pre-flight (fixes the false-negative where Codex is
|
|
120
|
+
// installed+working but the review subagent reported codexAvailable:false because a
|
|
121
|
+
// synchronous inline run hit its Bash timeout). The workflow sandbox has no Bash, so a
|
|
122
|
+
// minimal Haiku agent runs ONLY the resolution glob and returns the path — the existence
|
|
123
|
+
// decision is thus taken OUT of the review agent's hands. Found → Codex runs in the
|
|
124
|
+
// BACKGROUND with file-poll (canonical final-review.md F.3/F.4). Not found → straight to
|
|
125
|
+
// the code-reviewer fallback, no wasted Codex agent.
|
|
126
|
+
const PREFLIGHT_SCHEMA = {
|
|
127
|
+
type: 'object', required: ['codexScriptPath'], additionalProperties: false,
|
|
128
|
+
properties: { codexScriptPath: { type: 'string', description: 'absolute path to codex-companion.mjs, or "" if none' } },
|
|
129
|
+
}
|
|
130
|
+
let codexScriptPath = ''
|
|
131
|
+
try {
|
|
132
|
+
const pf = await agent(
|
|
133
|
+
`Resolve the Codex companion script path. Run EXACTLY this one Bash command and nothing else:\n` +
|
|
134
|
+
" 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" +
|
|
135
|
+
`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.`,
|
|
136
|
+
{ label: 'codex-preflight', phase: 'Review', model: 'haiku', schema: PREFLIGHT_SCHEMA }
|
|
137
|
+
)
|
|
138
|
+
codexScriptPath = (pf && typeof pf.codexScriptPath === 'string') ? pf.codexScriptPath.trim() : ''
|
|
139
|
+
} catch (_) { codexScriptPath = '' }
|
|
140
|
+
const codexResolved = /codex-companion\.mjs$/.test(codexScriptPath)
|
|
141
|
+
log(codexResolved
|
|
142
|
+
? `Codex companion resolved deterministically: ${codexScriptPath}`
|
|
143
|
+
: 'Codex companion NOT found (deterministic pre-flight) — primary review via code-reviewer fallback.')
|
|
144
|
+
|
|
119
145
|
const codexPrompt =
|
|
120
146
|
`Run a deep, cross-model code review over this batch diff, following the /codexreview protocol summarized in ${protocolRef} (Step F.3). The code is already written and committed — find bugs, regressions, security issues, and quality problems.\n\n` +
|
|
121
|
-
`
|
|
122
|
-
|
|
123
|
-
`
|
|
147
|
+
`The Codex companion script is ALREADY CONFIRMED PRESENT at:\n ${codexScriptPath}\n` +
|
|
148
|
+
`Launch it in the BACKGROUND and poll for completion — do NOT run it synchronously (a sync run would hit the Bash tool timeout and is the exact bug this design fixes):\n` +
|
|
149
|
+
` • REVIEW_FILE=a unique /tmp file (e.g. /tmp/codexreview-batch-${a.firstCardId || 'batch'}-$$.md)\n` +
|
|
150
|
+
` • node "${codexScriptPath}" task "<your review instructions>" > "$REVIEW_FILE" 2>&1 — run this with run_in_background:true (the launching call returns immediately).\n` +
|
|
151
|
+
` • Then POLL $REVIEW_FILE (BashOutput / repeated reads) until it holds a terminal result, up to a full 10-minute window.\n` +
|
|
152
|
+
`Return codexAvailable:false ONLY if $REVIEW_FILE ends up containing "CODEX_NOT_FOUND" or stays empty after the FULL 10-minute window — NEVER because a single Bash call returned slowly. The script's existence is already verified, so a premature false here is a bug, not a capability gap.\n\n` +
|
|
124
153
|
`${scopeBrief}\n\n${baselineBrief}\n\n` +
|
|
125
154
|
`For each finding return: finding_id, title, severity (BLOCKER|HIGH|MEDIUM|LOW), confidence (0-100), evidence (exact file:line + code quote), minimal_fix_direction, and domain (doc|security|migration|code|perf|test). ` +
|
|
126
155
|
`Run the mandatory false-positive check on every finding and suppress the unconvincing ones (your findings are treated as already FP-validated). Set codexAvailable:true when the review ran.`
|
|
@@ -134,16 +163,29 @@ const apiPrompt =
|
|
|
134
163
|
const qaPrompt =
|
|
135
164
|
`Run MECHANICAL GATES ONLY over the batch scope, per ${protocolRef} Step F.3 (qa-sentinel row): lint, type-check, the full test suite, build, dependency audit, and markdownlint as applicable to this project. Do NOT read source for code findings, do NOT emit severities — return only a PASS/FAIL/SKIP gate table.\n\nWorktree: ${a.worktreePath || '(cwd)'}\nChanged files:\n${scope.join('\n')}`
|
|
136
165
|
|
|
166
|
+
// F-041 — single-card batch: the per-card review (Phase 3) already ran doc-reviewer +
|
|
167
|
+
// api-perf-cost-auditor over these exact files, and a 1-card batch has NO cross-card
|
|
168
|
+
// conflict to surface. Keep ONLY the cross-model Codex pass (its unique value — a different
|
|
169
|
+
// model finds different bugs) + qa-sentinel gates; skip the Claude-agent duplicates.
|
|
170
|
+
const slim = a.singleCard === true
|
|
171
|
+
// qa-sentinel always runs — the merge integrity gate reads its PASS/FAIL table.
|
|
137
172
|
const reviewThunks = [
|
|
138
|
-
() => agent(codexPrompt, { label: 'codex', phase: 'Review', schema: CODEX_SCHEMA }).then((r) => ({ kind: 'codex', r })),
|
|
139
|
-
() => agent(docPrompt, { label: 'doc-reviewer', phase: 'Review', agentType: 'doc-reviewer', schema: FINDINGS_SCHEMA }).then((r) => ({ kind: 'doc', r })),
|
|
140
173
|
() => agent(qaPrompt, { label: 'qa-sentinel', phase: 'Review', agentType: 'qa-sentinel', schema: GATES_SCHEMA }).then((r) => ({ kind: 'qa', r })),
|
|
141
174
|
]
|
|
142
|
-
|
|
143
|
-
|
|
175
|
+
if (!slim) {
|
|
176
|
+
reviewThunks.unshift(() => agent(docPrompt, { label: 'doc-reviewer', phase: 'Review', agentType: 'doc-reviewer', schema: FINDINGS_SCHEMA }).then((r) => ({ kind: 'doc', r })))
|
|
177
|
+
}
|
|
178
|
+
// Codex thunk runs ONLY when the pre-flight resolved the companion (else: no wasted agent).
|
|
179
|
+
if (codexResolved) {
|
|
180
|
+
reviewThunks.unshift(() => agent(codexPrompt, { label: 'codex', phase: 'Review', schema: CODEX_SCHEMA }).then((r) => ({ kind: 'codex', r })))
|
|
181
|
+
}
|
|
182
|
+
// api-perf-cost-auditor: skipped when no API/data files OR on a slim single-card pass.
|
|
183
|
+
if (!slim && a.hasApiDataFiles !== false) {
|
|
144
184
|
reviewThunks.push(() => agent(apiPrompt, { label: 'api-perf-cost-auditor', phase: 'Review', agentType: 'api-perf-cost-auditor', schema: FINDINGS_SCHEMA }).then((r) => ({ kind: 'api', r })))
|
|
145
185
|
} else {
|
|
146
|
-
log(
|
|
186
|
+
log(slim
|
|
187
|
+
? 'Review: single-card batch — doc-reviewer + api-perf skipped (already run per-card); kept cross-model Codex + qa gates.'
|
|
188
|
+
: 'Review: api-perf-cost-auditor skipped (no API/data files in scope).')
|
|
147
189
|
}
|
|
148
190
|
|
|
149
191
|
const reviewResults = (await parallel(reviewThunks)).filter(Boolean)
|
|
@@ -151,20 +193,16 @@ const reviewResults = (await parallel(reviewThunks)).filter(Boolean)
|
|
|
151
193
|
// ---- Fan-in (F.4 step 9): collect findings + Codex fallback branch ----------
|
|
152
194
|
let raw = []
|
|
153
195
|
let gateTable = []
|
|
154
|
-
let codexEngine = 'codex'
|
|
196
|
+
let codexEngine = codexResolved ? 'codex' : 'code-reviewer (fallback)'
|
|
197
|
+
let codexRan = false
|
|
155
198
|
for (const item of reviewResults) {
|
|
156
199
|
if (item.kind === 'codex') {
|
|
157
200
|
if (item.r && item.r.codexAvailable && Array.isArray(item.r.findings)) {
|
|
201
|
+
codexRan = true
|
|
158
202
|
raw.push(...item.r.findings.map((f) => ({ ...f, source: 'codex', preValidated: true })))
|
|
159
203
|
} else {
|
|
160
|
-
//
|
|
161
|
-
|
|
162
|
-
log('Review: Codex unavailable — falling back to code-reviewer for the primary code review.')
|
|
163
|
-
const fb = await agent(
|
|
164
|
-
`Codex was unavailable for the batch final review. Run the FULL code review yourself over the batch diff, per ${protocolRef} Step F.3.\n\n${scopeBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied.`,
|
|
165
|
-
{ label: 'code-reviewer (fallback)', phase: 'Review', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
|
|
166
|
-
)
|
|
167
|
-
if (fb && Array.isArray(fb.findings)) raw.push(...fb.findings.map((f) => ({ ...f, source: 'code-reviewer', preValidated: false })))
|
|
204
|
+
// Resolved but the run itself failed (CODEX_NOT_FOUND written / empty after timeout).
|
|
205
|
+
log('Review: Codex companion resolved but its review did not complete — falling back to code-reviewer.')
|
|
168
206
|
}
|
|
169
207
|
} else if (item.kind === 'qa') {
|
|
170
208
|
gateTable = (item.r && item.r.gates) || []
|
|
@@ -173,6 +211,17 @@ for (const item of reviewResults) {
|
|
|
173
211
|
}
|
|
174
212
|
}
|
|
175
213
|
|
|
214
|
+
// F-040 — ONE deterministic fallback: either the pre-flight found no companion, or it ran
|
|
215
|
+
// but did not complete. Either way the primary code review must still happen (F.4 step 8).
|
|
216
|
+
if (!codexRan) {
|
|
217
|
+
codexEngine = 'code-reviewer (fallback)'
|
|
218
|
+
const fb = await agent(
|
|
219
|
+
`Codex was unavailable for the batch final review. Run the FULL code review yourself over the batch diff, per ${protocolRef} Step F.3.\n\n${scopeBrief}\n\n${baselineBrief}\n\nReturn findings using the schema fields, with a self false-positive check applied.`,
|
|
220
|
+
{ label: 'code-reviewer (fallback)', phase: 'Review', agentType: 'code-reviewer', schema: FINDINGS_SCHEMA }
|
|
221
|
+
)
|
|
222
|
+
if (fb && Array.isArray(fb.findings)) raw.push(...fb.findings.map((f) => ({ ...f, source: 'code-reviewer', preValidated: false })))
|
|
223
|
+
}
|
|
224
|
+
|
|
176
225
|
// ───────────────────────────────────────────────────────────────────────────
|
|
177
226
|
// Phase Verify (F.4) — adversarial validation of low-confidence findings
|
|
178
227
|
// Codex findings are pre-FP-validated → VERIFIED. Claude-agent findings with
|