baldart 4.17.0 → 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
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.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
|
+
|
|
8
22
|
## [4.17.0] - 2026-06-07
|
|
9
23
|
|
|
10
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).
|
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
4.17.
|
|
1
|
+
4.17.1
|
|
@@ -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
|
|
@@ -25,7 +25,20 @@ 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
|
+
function normDomain(d) {
|
|
32
|
+
const s = String(d || 'code').toLowerCase()
|
|
33
|
+
if (/doc|wiki|ssot|readme/.test(s)) return 'doc'
|
|
34
|
+
if (/ui|frontend|front-end|design|css|visual|component/.test(s)) return 'ui'
|
|
35
|
+
if (/sec|auth|secret|rls|tenant|xss|csrf|injection/.test(s)) return 'security'
|
|
36
|
+
if (/migrat|schema|ddl|\bsql\b/.test(s)) return 'migration'
|
|
37
|
+
if (/perf|cost|n\+1|latency|throughput/.test(s)) return 'perf'
|
|
38
|
+
if (/\btest|qa\b|spec|coverage/.test(s)) return 'test'
|
|
39
|
+
return 'code'
|
|
40
|
+
}
|
|
41
|
+
const domain = normDomain(a.domain || 'code')
|
|
29
42
|
const findings = Array.isArray(a.findings) && a.findings.length ? a.findings : [{ kind, evidence, domain }]
|
|
30
43
|
const REF = (a.refModulesBase || '.claude/skills/new/references').replace(/\/$/, '')
|
|
31
44
|
const cfg = a.config || {}
|
|
@@ -81,8 +94,9 @@ const FOLLOWUP_SCHEMA = {
|
|
|
81
94
|
properties: { created: { type: 'boolean' }, followupCard: { type: 'string' }, note: { type: 'string' } },
|
|
82
95
|
}
|
|
83
96
|
|
|
84
|
-
// F-024 — domain-specialized fixer + judge (full map
|
|
85
|
-
|
|
97
|
+
// F-024 — domain-specialized fixer + judge (full map; reviewer-owns-its-domain — a doc
|
|
98
|
+
// finding is fixed by doc-reviewer, a security finding by security-reviewer, never coder).
|
|
99
|
+
const fixerAgent = ({ doc: 'doc-reviewer', ui: 'ui-expert', security: 'security-reviewer' })[domain] || 'coder'
|
|
86
100
|
const judgeAgent = (domain === 'security' || domain === 'migration') ? 'security-reviewer' : domain === 'perf' ? 'api-perf-cost-auditor' : 'code-reviewer'
|
|
87
101
|
|
|
88
102
|
const findingsBlock = findings.map((f, i) => ` ${i + 1}. [${f.kind || kind}/${f.domain || domain}] ${f.evidence}`).join('\n')
|
|
@@ -105,6 +119,15 @@ function filesInScope(files) {
|
|
|
105
119
|
if (!mayEdit.length) return false
|
|
106
120
|
return files.every((f) => mayEdit.some((m) => String(f).includes(m) || m.includes(String(f))))
|
|
107
121
|
}
|
|
122
|
+
// F-033 — fabrication guard for the judge cross-check: a REAL fix touched AT LEAST ONE
|
|
123
|
+
// owned file. The judge naturally also lists adjacent changed files (e.g. the code a doc
|
|
124
|
+
// describes) — that is NOT fabrication, so `.every()` here over-rejected a sound fix and
|
|
125
|
+
// forced a wasted Tier-2 fan-out. `.some()` rejects only a verdict that confirmed ZERO
|
|
126
|
+
// owned files (pure phantom).
|
|
127
|
+
function someInScope(files) {
|
|
128
|
+
if (!Array.isArray(files) || !files.length || !mayEdit.length) return false
|
|
129
|
+
return files.some((f) => mayEdit.some((m) => String(f).includes(m) || m.includes(String(f))))
|
|
130
|
+
}
|
|
108
131
|
function collectOOS(...attemptObjs) {
|
|
109
132
|
const out = []
|
|
110
133
|
for (const o of attemptObjs) for (const x of (o && o.outOfScopeFindings) || []) out.push(x)
|
|
@@ -209,10 +232,16 @@ const canFanOut = (typeof budget === 'undefined' || !budget.total || budget.rema
|
|
|
209
232
|
let tier2OOS = []
|
|
210
233
|
if (canFanOut && !protectedDomain) {
|
|
211
234
|
phase('Repair')
|
|
212
|
-
|
|
235
|
+
// F-036 — the 3-angle fan-out (root-cause / call-site / reuse-utility) is code-bug-shaped.
|
|
236
|
+
// For deterministic non-code domains (doc/test) it is 3× waste — a single targeted retry
|
|
237
|
+
// is enough. Reserve the fan-out for code/perf/ui where the angles genuinely diverge.
|
|
238
|
+
const codeShaped = !/^(doc|test)$/.test(domain)
|
|
239
|
+
const angles = codeShaped ? [
|
|
213
240
|
'Fix at the root cause (change the underlying logic/contract).',
|
|
214
241
|
'Fix defensively at the call site (guard/validate without changing the contract).',
|
|
215
242
|
'Fix by reusing an existing utility/pattern instead of new code.',
|
|
243
|
+
] : [
|
|
244
|
+
'Apply the single correct fix — the residual is deterministic: re-derive the exact change and apply it.',
|
|
216
245
|
]
|
|
217
246
|
const tries = (await parallel(angles.map((angle, i) => () =>
|
|
218
247
|
agentSafe(
|
|
@@ -249,8 +278,8 @@ async function judgeVerify(verifiedAttempts) {
|
|
|
249
278
|
} catch (e) { if (e && e.transientExhausted) return { ok: false, best: 0 }; throw e }
|
|
250
279
|
if (!judge || !(judge.best > 0)) return { ok: false, best: 0 }
|
|
251
280
|
// 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
|
|
281
|
+
if (mayEdit.length && Array.isArray(judge.verifiedFiles) && judge.verifiedFiles.length && !someInScope(judge.verifiedFiles)) {
|
|
282
|
+
log('judge verifiedFiles are ENTIRELY outside MAY-EDIT — rejecting (possible fabrication).')
|
|
254
283
|
return { ok: false, best: 0 }
|
|
255
284
|
}
|
|
256
285
|
return { ok: true, best: judge.best }
|
|
@@ -259,11 +288,12 @@ async function judgeVerify(verifiedAttempts) {
|
|
|
259
288
|
async function materialiseFollowup(k, reason, oos) {
|
|
260
289
|
let r = null
|
|
261
290
|
try {
|
|
262
|
-
// F-
|
|
291
|
+
// F-039 — backlog cards are owned by prd-card-writer (card-template + Rule C
|
|
292
|
+
// review_profile + owner_agent + traceability), NOT a hand-written Haiku stub.
|
|
263
293
|
r = await agentSafe(
|
|
264
|
-
`
|
|
265
|
-
`
|
|
266
|
-
{ label: `resolve:followup:${card}`, phase: 'Verify', agentType: '
|
|
294
|
+
`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` +
|
|
295
|
+
`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.`,
|
|
296
|
+
{ label: `resolve:followup:${card}`, phase: 'Verify', agentType: 'prd-card-writer', schema: FOLLOWUP_SCHEMA }
|
|
267
297
|
)
|
|
268
298
|
} catch (e) {
|
|
269
299
|
// F-020 — could not materialise (e.g. outage): return WITHOUT a followupCard so the
|
|
@@ -234,6 +234,18 @@ cardIds.forEach((id, i) => { pathById[id] = cardPaths[i] || `${paths.backlog_dir
|
|
|
234
234
|
// new2-resolve bridge — dedup (F-009) + accepted-deferral skip (F-028) + group batching (F-007).
|
|
235
235
|
// `findings` is a list; a single residual is a list of 1 (retro-compat). Returns 'resolved'|'followup'|'fatal'.
|
|
236
236
|
// ───────────────────────────────────────────────────────────────────────────
|
|
237
|
+
// F-038 — doc-domain findings are fixed in the doc tree, NOT the card's code MAY-EDIT.
|
|
238
|
+
// Passing code-only ownership made the doc fixer edit docs/ "out of scope" and the JS
|
|
239
|
+
// fabrication cross-check reject a correct, judge-approved fix → a wasted Tier-2 fan-out
|
|
240
|
+
// (~150k tok) and a risk of a bogus follow-up for an already-resolved finding. Give doc
|
|
241
|
+
// findings their real owner territory.
|
|
242
|
+
function isDocDomain(d) { return /doc|wiki|ssot|readme/i.test(String(d || '')) }
|
|
243
|
+
function domainMayEdit(dom, codeScope) {
|
|
244
|
+
if (!isDocDomain(dom)) return codeScope
|
|
245
|
+
const docPaths = [paths.docs_dir, paths.references_dir, paths.wiki_dir, paths.prd_dir].filter(Boolean)
|
|
246
|
+
return docPaths.length ? docPaths : codeScope // doc-only ownership; fall back to code scope if no doc paths configured
|
|
247
|
+
}
|
|
248
|
+
|
|
237
249
|
async function resolve(kind, card, evidence, extra) {
|
|
238
250
|
const s = sig(card, kind, evidence)
|
|
239
251
|
if (resolvedSignatures.has(s) || acceptedDeferrals.has(s)) {
|
|
@@ -241,15 +253,16 @@ async function resolve(kind, card, evidence, extra) {
|
|
|
241
253
|
return 'resolved'
|
|
242
254
|
}
|
|
243
255
|
resolvedSignatures.add(s)
|
|
256
|
+
const dom = (extra && extra.domain) || 'code'
|
|
244
257
|
let res
|
|
245
258
|
try {
|
|
246
259
|
res = await workflow('new2-resolve', {
|
|
247
260
|
kind, cardId: card, evidence,
|
|
248
|
-
findings: (extra && extra.findings) || [{ kind, evidence, domain:
|
|
261
|
+
findings: (extra && extra.findings) || [{ kind, evidence, domain: dom }],
|
|
249
262
|
worktreePath: sharedCtx.worktreePath,
|
|
250
|
-
mayEditPaths: (extra && extra.mayEditPaths) || [],
|
|
263
|
+
mayEditPaths: domainMayEdit(dom, (extra && extra.mayEditPaths) || []),
|
|
251
264
|
scopeFiles: (extra && extra.scopeFiles) || [],
|
|
252
|
-
domain:
|
|
265
|
+
domain: dom,
|
|
253
266
|
refModulesBase: REF, config: cfg, ts: TS,
|
|
254
267
|
})
|
|
255
268
|
} catch (e) {
|
|
@@ -503,6 +516,9 @@ if (committed.length && !batchFatal && !degraded) {
|
|
|
503
516
|
firstCardId: firstCard, worktreePath: sharedCtx.worktreePath, baseBranch: TRUNK,
|
|
504
517
|
cardPaths: committed.map((r) => pathById[r.card]).filter(Boolean),
|
|
505
518
|
reviewScopeFiles, archBaselinePaths: allArch, hasApiDataFiles, config: cfg,
|
|
519
|
+
// F-041 — single-card batch: doc-reviewer + api-perf already ran per-card and there is
|
|
520
|
+
// NO cross-card conflict to find. Keep only the cross-model Codex pass + qa gates.
|
|
521
|
+
singleCard: committed.length === 1,
|
|
506
522
|
})
|
|
507
523
|
} catch (e) { if (e && isTransient(e)) noteDegraded('outage'); final = null }
|
|
508
524
|
|