baldart 4.17.0 → 4.17.2
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 +28 -0
- package/VERSION +1 -1
- package/framework/.claude/skills/new2/SKILL.md +9 -4
- package/framework/.claude/workflows/new-final-review.js +66 -17
- package/framework/.claude/workflows/new2-resolve.js +47 -36
- package/framework/.claude/workflows/new2.js +48 -6
- package/framework/docs/WORKFLOWS.md +3 -3
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,34 @@ 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.2] - 2026-06-08
|
|
9
|
+
|
|
10
|
+
**`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).
|
|
11
|
+
|
|
12
|
+
### Fixed
|
|
13
|
+
|
|
14
|
+
- **G1 — `new2-resolve.js`: rimossi gli handler self-healing orfani (`agent-crash`, `baseline-fail`).** `new2.js` non vi instradava MAI (un crash → `crashResult` marca `failed` + residual; un baseline fail → `batchFatal`). Erano codice morto irraggiungibile: `runCard` va in crash PRIMA della fase commit, quindi un eventuale `resolved` non potrebbe comunque committare/mergiare. Il path residual (lo skill materializza la follow-up) è la SSOT; commento esplicativo in `crashResult`.
|
|
15
|
+
- **G2 — `new2.js`: detection Codex G3 cross-card deterministica + skip mono-card.** Il check Codex del pre-flight usava ancora il **soft self-report** che v4.17.1 aveva corretto solo nella final review (stesso falso negativo, per batch multi-card). Ora il bullet G3 risolve PRIMA il glob del companion (esistenza decisa dal glob, non dal giudizio dell'agent), gira Codex in **background con poll**, e vieta un `CODEX_NOT_FOUND` prematuro; su batch **mono-card** il check è saltato del tutto (niente cross-card). Nuovo `codexResolved` nello schema pre-flight + ledger `G3-codex` + campo telemetria `codex_resolved` → un falso negativo è ora visibile.
|
|
16
|
+
- **G3 — `new2.js`: niente silent drop dei finding merge-artifact.** Il filtro F-035 scartava i finding "esiste già nel worktree" con solo un ledger SKIP; un regex lasco poteva far sparire un BLOCKER reale. Ora ogni finding scartato diventa anche un `residuals` (`merge-artifact-skipped`, `materialized:false`) → tracciato, nulla perso.
|
|
17
|
+
- **G4 — `new2/SKILL.md`: materializzazione follow-up offline-safe via `prd-card-writer`.** Il fallback dello skill scriveva uno stub minimale, incoerente col fix #2 (F-039, workflow → prd-card-writer). Ora lo Step 5.1 delega a `prd-card-writer` (card-template, Rule C, owner_agent, traceability), con stub minimale solo come ultima risorsa in outage totale.
|
|
18
|
+
- **G5 — `new2.js`: routing `security-reviewer` non solo da `scopeFiles`.** Una card di sicurezza con file non-matchanti saltava la security review quando `high_risk_modules` non è configurato. Ora il trigger considera anche l'`owner_agent` della card e il suo brief (titolo/requirements), oltre allo scope.
|
|
19
|
+
- **G6 — classificatore di dominio doc allineato.** `isDocDomain` (new2.js) e la branch `doc` di `normDomain` (new2-resolve.js) ora portano un commento di sincronizzazione reciproca (stessi token `doc|wiki|ssot|readme`) per prevenire drift.
|
|
20
|
+
- **G7 — sync documentazione.** `WORKFLOWS.md` (righe `new-final-review`/`new2`/`new2-resolve`) e `new2/SKILL.md` allineati al comportamento v4.17.1/v4.17.2 (doc routing, prd-card-writer followup, Codex deterministico, final review snella mono-card, rimozione handler orfani).
|
|
21
|
+
|
|
22
|
+
## [4.17.1] - 2026-06-08
|
|
23
|
+
|
|
24
|
+
**`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).
|
|
25
|
+
|
|
26
|
+
> **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.
|
|
27
|
+
|
|
28
|
+
### Fixed
|
|
29
|
+
|
|
30
|
+
- **`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*.
|
|
31
|
+
- **`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.
|
|
32
|
+
- **`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".
|
|
33
|
+
- **`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.
|
|
34
|
+
- **`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.
|
|
35
|
+
|
|
8
36
|
## [4.17.0] - 2026-06-07
|
|
9
37
|
|
|
10
38
|
**`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).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.17.
|
|
1
|
+
4.17.2
|
|
@@ -128,10 +128,15 @@ returns when the batch is done. It returns:
|
|
|
128
128
|
|
|
129
129
|
1. **Materialise missing follow-ups (offline-safe — you have filesystem access, the
|
|
130
130
|
workflow does not).** For every `residuals[]` entry with `materialized:false`,
|
|
131
|
-
|
|
132
|
-
TODO
|
|
133
|
-
|
|
134
|
-
|
|
131
|
+
create a follow-up card `${paths.backlog_dir}/<card>-followup-<kind>.yml` (status:
|
|
132
|
+
TODO). **Delegate the write to the `prd-card-writer` agent** — the same owner the
|
|
133
|
+
workflow uses (card-template, Rule C `review_profile`, `owner_agent` routed to the
|
|
134
|
+
residual's domain, traceability) — derived from the residual (≥1 requirement;
|
|
135
|
+
`acceptance_criteria` = the verbatim residual; `files_likely_touched` from the card's
|
|
136
|
+
ownership). Do NOT hand-write a minimal stub — the offline path must match the
|
|
137
|
+
agent-path quality (F-039). It MUST pass the `/new` pre-flight field check. If
|
|
138
|
+
`prd-card-writer` itself is unavailable (total outage), fall back to a minimal valid
|
|
139
|
+
stub so the card still exists. This is the layer that guarantees **nothing is ever
|
|
135
140
|
dropped, even when every agent was dead** during the run.
|
|
136
141
|
2. **Resume if degraded.** If `degraded` is true, re-invoke the workflow with
|
|
137
142
|
`Workflow({ scriptPath, resumeFromRunId })` (same `args` + the new `ts`). The
|
|
@@ -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
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
export const meta = {
|
|
2
2
|
name: 'new2-resolve',
|
|
3
3
|
description:
|
|
4
|
-
"Self-healing resolution pass for the autonomous new2 batch workflow. Called by new2 whenever a deterministic gate would otherwise need a human: a card fail/blocker (ac-unmet | blocker | qa-fail | e2e-blocked | merge-blocker)
|
|
4
|
+
"Self-healing resolution pass for the autonomous new2 batch workflow. Called by new2 whenever a deterministic gate would otherwise need a human: a card fail/blocker (ac-unmet | blocker | qa-fail | e2e-blocked | merge-blocker) or a legitimate scope-EXPANDING finding (scope-expansion). Tier-1 targeted fix with a TERMINAL short-circuit (skips the costly multi-attempt when the problem is impossible-by-definition, verified not trusted), then a judged multi-attempt; a MANDATORY adversarial judge cross-checks every verified claim against the real diff (prevents fabricated success). Specialized per domain (doc→doc-reviewer, ui→ui-expert, security→security-reviewer judge, perf→api-perf-cost-auditor judge). Terminal is a tracked follow-up. Accepts a `findings` list (batched per area). Uses agent()/parallel() only — no nested workflows.",
|
|
5
5
|
phases: [
|
|
6
6
|
{ title: 'Diagnose', detail: 'classify + terminal short-circuit + scope-expansion boundary' },
|
|
7
7
|
{ title: 'Repair', detail: 'targeted fix, then judged multi-attempt if needed' },
|
|
@@ -25,7 +25,22 @@ const evidence = a.evidence || ''
|
|
|
25
25
|
const worktree = a.worktreePath || '(cwd)'
|
|
26
26
|
const mayEdit = Array.isArray(a.mayEditPaths) ? a.mayEditPaths : []
|
|
27
27
|
const scopeFiles = Array.isArray(a.scopeFiles) ? a.scopeFiles : []
|
|
28
|
-
|
|
28
|
+
// F-038 — review agents emit FREEFORM domain strings (e.g. 'documentation', 'frontend').
|
|
29
|
+
// Normalize to the routing buckets the fixer/judge maps key on, so a doc finding never
|
|
30
|
+
// falls through to the costly `coder` (the v4.17.0 'documentation'→coder regression).
|
|
31
|
+
// G6: the doc token set below MUST stay in sync with isDocDomain() in new2.js (which picks
|
|
32
|
+
// the doc MAY-EDIT) — drift would route a finding to doc-reviewer but with code ownership.
|
|
33
|
+
function normDomain(d) {
|
|
34
|
+
const s = String(d || 'code').toLowerCase()
|
|
35
|
+
if (/doc|wiki|ssot|readme/.test(s)) return 'doc'
|
|
36
|
+
if (/ui|frontend|front-end|design|css|visual|component/.test(s)) return 'ui'
|
|
37
|
+
if (/sec|auth|secret|rls|tenant|xss|csrf|injection/.test(s)) return 'security'
|
|
38
|
+
if (/migrat|schema|ddl|\bsql\b/.test(s)) return 'migration'
|
|
39
|
+
if (/perf|cost|n\+1|latency|throughput/.test(s)) return 'perf'
|
|
40
|
+
if (/\btest|qa\b|spec|coverage/.test(s)) return 'test'
|
|
41
|
+
return 'code'
|
|
42
|
+
}
|
|
43
|
+
const domain = normDomain(a.domain || 'code')
|
|
29
44
|
const findings = Array.isArray(a.findings) && a.findings.length ? a.findings : [{ kind, evidence, domain }]
|
|
30
45
|
const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
|
|
31
46
|
const cfg = a.config || {}
|
|
@@ -81,8 +96,9 @@ const FOLLOWUP_SCHEMA = {
|
|
|
81
96
|
properties: { created: { type: 'boolean' }, followupCard: { type: 'string' }, note: { type: 'string' } },
|
|
82
97
|
}
|
|
83
98
|
|
|
84
|
-
// F-024 — domain-specialized fixer + judge (full map
|
|
85
|
-
|
|
99
|
+
// F-024 — domain-specialized fixer + judge (full map; reviewer-owns-its-domain — a doc
|
|
100
|
+
// finding is fixed by doc-reviewer, a security finding by security-reviewer, never coder).
|
|
101
|
+
const fixerAgent = ({ doc: 'doc-reviewer', ui: 'ui-expert', security: 'security-reviewer' })[domain] || 'coder'
|
|
86
102
|
const judgeAgent = (domain === 'security' || domain === 'migration') ? 'security-reviewer' : domain === 'perf' ? 'api-perf-cost-auditor' : 'code-reviewer'
|
|
87
103
|
|
|
88
104
|
const findingsBlock = findings.map((f, i) => ` ${i + 1}. [${f.kind || kind}/${f.domain || domain}] ${f.evidence}`).join('\n')
|
|
@@ -105,6 +121,15 @@ function filesInScope(files) {
|
|
|
105
121
|
if (!mayEdit.length) return false
|
|
106
122
|
return files.every((f) => mayEdit.some((m) => String(f).includes(m) || m.includes(String(f))))
|
|
107
123
|
}
|
|
124
|
+
// F-033 — fabrication guard for the judge cross-check: a REAL fix touched AT LEAST ONE
|
|
125
|
+
// owned file. The judge naturally also lists adjacent changed files (e.g. the code a doc
|
|
126
|
+
// describes) — that is NOT fabrication, so `.every()` here over-rejected a sound fix and
|
|
127
|
+
// forced a wasted Tier-2 fan-out. `.some()` rejects only a verdict that confirmed ZERO
|
|
128
|
+
// owned files (pure phantom).
|
|
129
|
+
function someInScope(files) {
|
|
130
|
+
if (!Array.isArray(files) || !files.length || !mayEdit.length) return false
|
|
131
|
+
return files.some((f) => mayEdit.some((m) => String(f).includes(m) || m.includes(String(f))))
|
|
132
|
+
}
|
|
108
133
|
function collectOOS(...attemptObjs) {
|
|
109
134
|
const out = []
|
|
110
135
|
for (const o of attemptObjs) for (const x of (o && o.outOfScopeFindings) || []) out.push(x)
|
|
@@ -113,18 +138,10 @@ function collectOOS(...attemptObjs) {
|
|
|
113
138
|
|
|
114
139
|
phase('Diagnose')
|
|
115
140
|
|
|
116
|
-
//
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
r = await agentSafe(
|
|
121
|
-
`The batch worktree's BASELINE build fails (trunk may not build). Try to make it build per ${REF}/setup.md step 6a.\n\n${brief}\n\nFix tractable build errors (missing dep, stale lock, trivial type error). Re-run tsc+lint+build. verified:true only if the baseline now passes; else verified:false.`,
|
|
122
|
-
{ label: `resolve:baseline:${card}`, phase: 'Diagnose', agentType: 'coder', schema: FIX_SCHEMA }
|
|
123
|
-
)
|
|
124
|
-
} catch (e) { if (e && e.transientExhausted) return { status: 'followup', reason: 'outage during baseline repair', outOfScopeFindings: [] } ; throw e }
|
|
125
|
-
if (r && r.verified) { log('baseline recovered.'); return { status: 'resolved', outOfScopeFindings: collectOOS(r) } }
|
|
126
|
-
return { status: 'fatal', reason: 'baseline build irrecoverable' }
|
|
127
|
-
}
|
|
141
|
+
// NOTE: new2.js never routes 'baseline-fail' (→ batchFatal at pre-flight) nor 'agent-crash'
|
|
142
|
+
// (→ crashResult marks the card 'failed' + residual) here — a crash happens BEFORE runCard's
|
|
143
|
+
// commit phase, so a 'resolved' verdict could never be committed/merged. Those self-healing
|
|
144
|
+
// branches were removed (v4.17.2 G1) as unreachable dead code; the residual path is the SSOT.
|
|
128
145
|
|
|
129
146
|
// ---- scope-expansion (Asse 2): deterministic boundary ---
|
|
130
147
|
if (kind === 'scope-expansion') {
|
|
@@ -144,19 +161,6 @@ if (kind === 'scope-expansion') {
|
|
|
144
161
|
return await materialiseFollowup('scope-expansion', (decide && decide.note) || 'outside ownership / new AC / protected', collectOOS(decide))
|
|
145
162
|
}
|
|
146
163
|
|
|
147
|
-
// ---- agent-crash (E1) ---
|
|
148
|
-
if (kind === 'agent-crash') {
|
|
149
|
-
let r = null
|
|
150
|
-
try {
|
|
151
|
-
r = await agentSafe(
|
|
152
|
-
`A subagent crashed. Re-attempt with the CORRECT specialist (${fixerAgent}) per ${REF}/commit.md § Sub-agent failure protocol.\n\n${brief}\n\nRetry once. ${protectedDomain ? 'PROTECTED domain: if you cannot complete it, verified:false — NEVER inline-bypass.' : ''} Re-verify the gate; verified:true only if done.`,
|
|
153
|
-
{ label: `resolve:crash:${card}`, phase: 'Repair', agentType: fixerAgent, schema: FIX_SCHEMA }
|
|
154
|
-
)
|
|
155
|
-
} catch (e) { if (e && e.transientExhausted) return { status: 'followup', reason: 'outage during crash recovery', outOfScopeFindings: [] }; throw e }
|
|
156
|
-
if (r && r.verified) { const ok = await judgeVerify([{ i: 1, r }]); if (ok.ok) return { status: 'resolved', outOfScopeFindings: collectOOS(r) } }
|
|
157
|
-
return await materialiseFollowup('agent-crash', 'subagent crash unrecovered' + (protectedDomain ? ' (protected — not bypassed)' : ''), collectOOS(r))
|
|
158
|
-
}
|
|
159
|
-
|
|
160
164
|
// ───────────────────────────────────────────────────────────────────────────
|
|
161
165
|
// Asse 1 — Tier-1 (with terminal short-circuit) → judged multi-attempt → follow-up.
|
|
162
166
|
// ───────────────────────────────────────────────────────────────────────────
|
|
@@ -209,10 +213,16 @@ const canFanOut = (typeof budget === 'undefined' || !budget.total || budget.rema
|
|
|
209
213
|
let tier2OOS = []
|
|
210
214
|
if (canFanOut && !protectedDomain) {
|
|
211
215
|
phase('Repair')
|
|
212
|
-
|
|
216
|
+
// F-036 — the 3-angle fan-out (root-cause / call-site / reuse-utility) is code-bug-shaped.
|
|
217
|
+
// For deterministic non-code domains (doc/test) it is 3× waste — a single targeted retry
|
|
218
|
+
// is enough. Reserve the fan-out for code/perf/ui where the angles genuinely diverge.
|
|
219
|
+
const codeShaped = !/^(doc|test)$/.test(domain)
|
|
220
|
+
const angles = codeShaped ? [
|
|
213
221
|
'Fix at the root cause (change the underlying logic/contract).',
|
|
214
222
|
'Fix defensively at the call site (guard/validate without changing the contract).',
|
|
215
223
|
'Fix by reusing an existing utility/pattern instead of new code.',
|
|
224
|
+
] : [
|
|
225
|
+
'Apply the single correct fix — the residual is deterministic: re-derive the exact change and apply it.',
|
|
216
226
|
]
|
|
217
227
|
const tries = (await parallel(angles.map((angle, i) => () =>
|
|
218
228
|
agentSafe(
|
|
@@ -249,8 +259,8 @@ async function judgeVerify(verifiedAttempts) {
|
|
|
249
259
|
} catch (e) { if (e && e.transientExhausted) return { ok: false, best: 0 }; throw e }
|
|
250
260
|
if (!judge || !(judge.best > 0)) return { ok: false, best: 0 }
|
|
251
261
|
// Deterministic JS cross-check: the judge-confirmed files must be within MAY-EDIT.
|
|
252
|
-
if (mayEdit.length && Array.isArray(judge.verifiedFiles) && judge.verifiedFiles.length && !
|
|
253
|
-
log('judge verifiedFiles
|
|
262
|
+
if (mayEdit.length && Array.isArray(judge.verifiedFiles) && judge.verifiedFiles.length && !someInScope(judge.verifiedFiles)) {
|
|
263
|
+
log('judge verifiedFiles are ENTIRELY outside MAY-EDIT — rejecting (possible fabrication).')
|
|
254
264
|
return { ok: false, best: 0 }
|
|
255
265
|
}
|
|
256
266
|
return { ok: true, best: judge.best }
|
|
@@ -259,11 +269,12 @@ async function judgeVerify(verifiedAttempts) {
|
|
|
259
269
|
async function materialiseFollowup(k, reason, oos) {
|
|
260
270
|
let r = null
|
|
261
271
|
try {
|
|
262
|
-
// F-
|
|
272
|
+
// F-039 — backlog cards are owned by prd-card-writer (card-template + Rule C
|
|
273
|
+
// review_profile + owner_agent + traceability), NOT a hand-written Haiku stub.
|
|
263
274
|
r = await agentSafe(
|
|
264
|
-
`
|
|
265
|
-
`
|
|
266
|
-
{ label: `resolve:followup:${card}`, phase: 'Verify', agentType: '
|
|
275
|
+
`Create ONE follow-up backlog card so this residual is TRACKED, not dropped (per ${REF}/completeness.md Phase 2.5b option 3). You are prd-card-writer: apply your card-template, Rule C (review_profile), owner_agent routing, and traceability rules — do NOT emit a minimal stub.\n\n${brief}\nKind: ${k}\nResidual domain: ${domain}\nReason unresolved: ${reason}\n\n` +
|
|
276
|
+
`Write ${backlogDir}/${card}-followup-<gate>.yml with status: TODO, derived from the residual: requirements + acceptance_criteria (the verbatim residual as ≥1 AC), owner_agent routed to the residual domain (${domain}), review_profile per Rule C, files_likely_touched ≥1 from the card ownership / remedy files. It MUST pass the /new pre-flight field check. Return the created card id.`,
|
|
277
|
+
{ label: `resolve:followup:${card}`, phase: 'Verify', agentType: 'prd-card-writer', schema: FOLLOWUP_SCHEMA }
|
|
267
278
|
)
|
|
268
279
|
} catch (e) {
|
|
269
280
|
// F-020 — could not materialise (e.g. outage): return WITHOUT a followupCard so the
|
|
@@ -144,6 +144,9 @@ const PREFLIGHT_SCHEMA = {
|
|
|
144
144
|
excluded: { type: 'array', items: { type: 'object', additionalProperties: true } },
|
|
145
145
|
ownershipMapPath: { type: 'string' },
|
|
146
146
|
crossCard: { type: 'string' },
|
|
147
|
+
// G2 — deterministic Codex availability (glob-resolved, NOT a self-reported judgement),
|
|
148
|
+
// so a false negative on the cross-card check is visible in telemetry. true=companion found.
|
|
149
|
+
codexResolved: { type: 'boolean' },
|
|
147
150
|
workspaceNote: { type: 'string' },
|
|
148
151
|
archBaselinePaths: { type: 'array', items: { type: 'string' } },
|
|
149
152
|
},
|
|
@@ -173,6 +176,14 @@ const projectBrief = [
|
|
|
173
176
|
// ───────────────────────────────────────────────────────────────────────────
|
|
174
177
|
// Phase Pre-flight — ONE ops agent runs all git-heavy work + returns the dep graph.
|
|
175
178
|
// ───────────────────────────────────────────────────────────────────────────
|
|
179
|
+
// G2 — Codex cross-card grounding. SKIP entirely on a single-card batch (nothing
|
|
180
|
+
// cross-card to find). For multi-card, make the availability DETERMINISTIC: resolve the
|
|
181
|
+
// companion glob FIRST (existence taken out of the agent's judgement), run it in the
|
|
182
|
+
// BACKGROUND with a file poll, and forbid a premature CODEX_NOT_FOUND — same discipline as
|
|
183
|
+
// new-final-review.js, fixing the parallel false-negative the v4.17.1 fix #3 left here.
|
|
184
|
+
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`
|
|
176
187
|
phase('Pre-flight')
|
|
177
188
|
let preflight = null
|
|
178
189
|
try {
|
|
@@ -183,7 +194,7 @@ try {
|
|
|
183
194
|
`DETERMINISTIC GATE POLICIES (NO user prompts):\n` +
|
|
184
195
|
`• 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` +
|
|
185
196
|
`• 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` +
|
|
186
|
-
|
|
197
|
+
g3Bullet +
|
|
187
198
|
`• 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` +
|
|
188
199
|
`• 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` +
|
|
189
200
|
`• cardGraph (REQUIRED, F-021): for every runnable card return { id, dependsOn:[IN-BATCH deps only], ownerAgent (the card's owner_agent; G25 unknown→'coder'), reviewProfile (the card's review_profile; default 'balanced'), policyDeferredACs }.\n` +
|
|
@@ -206,6 +217,8 @@ if (!preflight || preflight.ok === false || preflight.baseline === 'fail') {
|
|
|
206
217
|
for (const ex of preflight.excluded || []) ledger(ex.card, 'preflight-exclude', 'EXCLUDED', ex.reason)
|
|
207
218
|
if (preflight.workspaceNote) ledger(firstCard, 'G1/G2-workspace', 'AUTO', preflight.workspaceNote)
|
|
208
219
|
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')
|
|
209
222
|
|
|
210
223
|
const runnableCards = preflight.cards || []
|
|
211
224
|
const cardGraph = preflight.cardGraph || []
|
|
@@ -234,6 +247,20 @@ cardIds.forEach((id, i) => { pathById[id] = cardPaths[i] || `${paths.backlog_dir
|
|
|
234
247
|
// new2-resolve bridge — dedup (F-009) + accepted-deferral skip (F-028) + group batching (F-007).
|
|
235
248
|
// `findings` is a list; a single residual is a list of 1 (retro-compat). Returns 'resolved'|'followup'|'fatal'.
|
|
236
249
|
// ───────────────────────────────────────────────────────────────────────────
|
|
250
|
+
// F-038 — doc-domain findings are fixed in the doc tree, NOT the card's code MAY-EDIT.
|
|
251
|
+
// Passing code-only ownership made the doc fixer edit docs/ "out of scope" and the JS
|
|
252
|
+
// fabrication cross-check reject a correct, judge-approved fix → a wasted Tier-2 fan-out
|
|
253
|
+
// (~150k tok) and a risk of a bogus follow-up for an already-resolved finding. Give doc
|
|
254
|
+
// findings their real owner territory.
|
|
255
|
+
// G6 — token set MUST stay in sync with the doc branch of normDomain() in new2-resolve.js;
|
|
256
|
+
// drift would route a finding to doc-reviewer there but pass code ownership from here.
|
|
257
|
+
function isDocDomain(d) { return /doc|wiki|ssot|readme/i.test(String(d || '')) }
|
|
258
|
+
function domainMayEdit(dom, codeScope) {
|
|
259
|
+
if (!isDocDomain(dom)) return codeScope
|
|
260
|
+
const docPaths = [paths.docs_dir, paths.references_dir, paths.wiki_dir, paths.prd_dir].filter(Boolean)
|
|
261
|
+
return docPaths.length ? docPaths : codeScope // doc-only ownership; fall back to code scope if no doc paths configured
|
|
262
|
+
}
|
|
263
|
+
|
|
237
264
|
async function resolve(kind, card, evidence, extra) {
|
|
238
265
|
const s = sig(card, kind, evidence)
|
|
239
266
|
if (resolvedSignatures.has(s) || acceptedDeferrals.has(s)) {
|
|
@@ -241,15 +268,16 @@ async function resolve(kind, card, evidence, extra) {
|
|
|
241
268
|
return 'resolved'
|
|
242
269
|
}
|
|
243
270
|
resolvedSignatures.add(s)
|
|
271
|
+
const dom = (extra && extra.domain) || 'code'
|
|
244
272
|
let res
|
|
245
273
|
try {
|
|
246
274
|
res = await workflow('new2-resolve', {
|
|
247
275
|
kind, cardId: card, evidence,
|
|
248
|
-
findings: (extra && extra.findings) || [{ kind, evidence, domain:
|
|
276
|
+
findings: (extra && extra.findings) || [{ kind, evidence, domain: dom }],
|
|
249
277
|
worktreePath: sharedCtx.worktreePath,
|
|
250
|
-
mayEditPaths: (extra && extra.mayEditPaths) || [],
|
|
278
|
+
mayEditPaths: domainMayEdit(dom, (extra && extra.mayEditPaths) || []),
|
|
251
279
|
scopeFiles: (extra && extra.scopeFiles) || [],
|
|
252
|
-
domain:
|
|
280
|
+
domain: dom,
|
|
253
281
|
refModulesBase: REF, config: cfg, ts: TS,
|
|
254
282
|
})
|
|
255
283
|
} catch (e) {
|
|
@@ -350,10 +378,15 @@ async function runCard(cardId, cardPath, lessons) {
|
|
|
350
378
|
}
|
|
351
379
|
|
|
352
380
|
// --- Review fan-out (F-024/F-025): specialized agents, trimmed by review_profile. ---
|
|
381
|
+
// G5 — scopeFiles tokens alone miss a security card whose files don't carry them. Also
|
|
382
|
+
// trigger security-reviewer on the card's owner_agent and its brief (title/requirements).
|
|
383
|
+
const securityRelevant = highRisk.length
|
|
384
|
+
|| ownerAgent === 'security-reviewer'
|
|
385
|
+
|| /auth|security|secret|migration|rls/i.test(`${scopeFiles.join(' ')} ${cardBrief}`)
|
|
353
386
|
const reviewers = reviewProfile === 'skip' ? []
|
|
354
387
|
: reviewProfile === 'light' ? ['code-reviewer']
|
|
355
388
|
: ['code-reviewer', 'doc-reviewer', 'qa-sentinel', 'api-perf-cost-auditor'].concat(
|
|
356
|
-
|
|
389
|
+
securityRelevant ? ['security-reviewer'] : [])
|
|
357
390
|
let reviewResults = []
|
|
358
391
|
try {
|
|
359
392
|
reviewResults = (await parallel(reviewers.map((ra) => () => agentSafe(
|
|
@@ -503,6 +536,9 @@ if (committed.length && !batchFatal && !degraded) {
|
|
|
503
536
|
firstCardId: firstCard, worktreePath: sharedCtx.worktreePath, baseBranch: TRUNK,
|
|
504
537
|
cardPaths: committed.map((r) => pathById[r.card]).filter(Boolean),
|
|
505
538
|
reviewScopeFiles, archBaselinePaths: allArch, hasApiDataFiles, config: cfg,
|
|
539
|
+
// F-041 — single-card batch: doc-reviewer + api-perf already ran per-card and there is
|
|
540
|
+
// NO cross-card conflict to find. Keep only the cross-model Codex pass + qa gates.
|
|
541
|
+
singleCard: committed.length === 1,
|
|
506
542
|
})
|
|
507
543
|
} catch (e) { if (e && isTransient(e)) noteDegraded('outage'); final = null }
|
|
508
544
|
|
|
@@ -512,7 +548,12 @@ if (committed.length && !batchFatal && !degraded) {
|
|
|
512
548
|
const all = (final.findings || []).filter((f) => (f.classification === 'VERIFIED' || f.classification === 'NEEDS_MANUAL_CONFIRMATION') && (f.severity === 'BLOCKER' || f.severity === 'HIGH'))
|
|
513
549
|
// F-035 — drop merge-artifact doc findings (content exists in worktree, the merge carries it).
|
|
514
550
|
const actionable = all.filter((f) => !/in (the )?main repo|not (in|on) (the )?main|merge[- ]?time|worktree.*(not|missing).*main/i.test(`${f.title} ${f.evidence}`))
|
|
515
|
-
|
|
551
|
+
// G3 — the merge-artifact regex is loose; a BLOCKER/HIGH it over-matches must NOT vanish.
|
|
552
|
+
// Track every skipped finding as a residual (the skill materialises it) — no silent drop.
|
|
553
|
+
for (const f of all) if (!actionable.includes(f)) {
|
|
554
|
+
ledger(f.finding_id || firstCard, 'final-merge-artifact', 'SKIP', 'resolved-by-merge')
|
|
555
|
+
residuals.push({ card: f.finding_id || firstCard, kind: 'merge-artifact-skipped', evidence: `${f.severity} ${f.title}: ${f.evidence}`, materialized: false })
|
|
556
|
+
}
|
|
516
557
|
// F-007/F-037 — group actionable findings by fix-area (file) → one resolve per area.
|
|
517
558
|
const byArea = {}
|
|
518
559
|
for (const f of actionable) {
|
|
@@ -624,6 +665,7 @@ function buildTelemetry() {
|
|
|
624
665
|
degraded,
|
|
625
666
|
degradation_reasons: degradationReasons,
|
|
626
667
|
execution_mode: preflight ? preflight.executionMode : 'sequential',
|
|
668
|
+
codex_resolved: (preflight && cardIds.length >= 2) ? !!preflight.codexResolved : null, // G2 — null = not probed (single-card)
|
|
627
669
|
// cost — total_tokens via budget.spent() delta; agent_count via counter; wall_clock_s stamped by the SKILL.
|
|
628
670
|
total_tokens: totalTokens,
|
|
629
671
|
agent_count: agentCount,
|
|
@@ -13,9 +13,9 @@ workflows are unavailable behaves exactly as before.
|
|
|
13
13
|
|
|
14
14
|
| Workflow | Used by | What it does |
|
|
15
15
|
| :--- | :--- | :--- |
|
|
16
|
-
| `new-final-review` | `/new` Final Review (Step F.1.5) | Runs the read-only cross-batch review fan-out — architecture baseline + Codex ‖ doc-reviewer ‖ api-perf-cost-auditor ‖ qa-sentinel — then adversarially verifies low-confidence findings and returns them classified. Applies no fixes (the skill owns fix application + user gates). |
|
|
17
|
-
| `new2` (v4.17.
|
|
18
|
-
| `new2-resolve` (v4.17.
|
|
16
|
+
| `new-final-review` | `/new` Final Review (Step F.1.5) | Runs the read-only cross-batch review fan-out — architecture baseline + Codex ‖ doc-reviewer ‖ api-perf-cost-auditor ‖ qa-sentinel — then adversarially verifies low-confidence findings and returns them classified. Applies no fixes (the skill owns fix application + user gates). **v4.17.1+:** Codex availability is resolved by a **deterministic pre-flight glob + background poll** (no false negatives from a synchronous-run timeout); a **single-card batch** skips the duplicate doc/api reviewers (already run per-card), keeping only the cross-model Codex pass + qa gates. |
|
|
17
|
+
| `new2` (v4.17.2) | `/new2` skill (the whole batch) | **EXPERIMENTAL A/B variant of `/new`.** Hosts the ENTIRE batch in the background runtime so subagent output never enters the main context. A **dependency-gated DAG scheduler** runs a card only when all in-batch deps are *committed* (and blocks transitive dependents of a failed dep instead of routing them to resolve); each card uses its **owner_agent** + a **specialized review fan-out** (not general-purpose); the worktree is kept **atomic per card** (rollback-to-HEAD on failure); transient API errors are retried and a sustained **outage degrades cleanly** (`degraded` return + durable resume via the skill); a **run ledger** dedups resolves and records accepted deferrals (no re-routing loop); the **merge is integrity-gated** (never force-DONE, never `git add` unreviewed code, never merge an incomplete/degraded batch); the commit step runs on **Haiku** while **follow-up cards are written by `prd-card-writer`**; telemetry carries real **cost** (`total_tokens` via `budget.spent()`, `agent_count`, skill-stamped `wall_clock_s`) + `degraded`. **v4.17.2:** the pre-flight **G3 cross-card Codex check is deterministic** (glob-first + background poll, skipped on single-card batches; `codex_resolved` in telemetry); a non-transient card crash is terminal-with-residual (no orphaned self-healing). Agents Read `/new`'s reference modules for semantics. |
|
|
18
|
+
| `new2-resolve` (v4.17.2) | `new2` (self-healing) | Resolution pass for any gate that would otherwise need a human (`ac-unmet · blocker · qa-fail · e2e-blocked · merge-blocker · scope-expansion`). A **terminal short-circuit** skips the costly multi-attempt when the problem is impossible-by-definition (`out-of-ownership` verified in JS; other terminal reasons ratified by a judge); a **MANDATORY adversarial judge** cross-checks every `verified` claim — the judge independently greps the files and the workflow verifies **at least one** falls inside MAY-EDIT (`.some()`, so listing adjacent changed files is not mistaken for fabrication); accepts a **batched `findings` list** (one resolve per fix-area). **The domain is normalized** (freeform `documentation`→`doc`, …) before routing the **fixer** (doc→doc-reviewer, ui→ui-expert, security→security-reviewer, else coder) and judge, and a **doc finding gets doc-tree MAY-EDIT** (not the card's code scope); the 3-angle Tier-2 fan-out is reserved for code domains (single retry for doc/test). Follow-ups are written by **`prd-card-writer`**, offline-safe (deferred to the skill if no agent can write). |
|
|
19
19
|
|
|
20
20
|
> **`new2` is an experiment, not a replacement.** It exists to A/B-test the
|
|
21
21
|
> context-economy of workflow-hosting `/new`. `/new` remains the SSOT, the
|