@dzhechkov/skills-feature-adr 1.5.7 → 1.5.8
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/.dz-manifest.json +4 -4
- package/README.md +1 -1
- package/package.json +4 -4
- package/sbom.json +3 -3
- package/templates/.claude/workflows/feature-adr.js +218 -60
package/.dz-manifest.json
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
},
|
|
14
14
|
{
|
|
15
15
|
"path": "README.md",
|
|
16
|
-
"sha256": "
|
|
16
|
+
"sha256": "dc985b443d8ac9d4091b09367330f7f610c35572d99f635855989fd893c5e6ac"
|
|
17
17
|
},
|
|
18
18
|
{
|
|
19
19
|
"path": "bin/cli.js",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
},
|
|
26
26
|
{
|
|
27
27
|
"path": "package.json",
|
|
28
|
-
"sha256": "
|
|
28
|
+
"sha256": "d57357d1feb9f111d0674f1c307a4d15a41cb005074351d37ebaf7dc1afe3575"
|
|
29
29
|
},
|
|
30
30
|
{
|
|
31
31
|
"path": "src/cli.js",
|
|
@@ -313,7 +313,7 @@
|
|
|
313
313
|
},
|
|
314
314
|
{
|
|
315
315
|
"path": "templates/.claude/workflows/feature-adr.js",
|
|
316
|
-
"sha256": "
|
|
316
|
+
"sha256": "f287a73bdf8c11c99eedbc071c6babb3201a6cec7d644d65f20ece3cd4ca51c1"
|
|
317
317
|
},
|
|
318
318
|
{
|
|
319
319
|
"path": "templates/lib/memory-protocol.md",
|
|
@@ -325,5 +325,5 @@
|
|
|
325
325
|
}
|
|
326
326
|
]
|
|
327
327
|
},
|
|
328
|
-
"signature": "
|
|
328
|
+
"signature": "t3rY38fu7zb8GjsIjG0OOeqtw04W4nlzawE//PhPKbHbtLUMgT/1SOaXFUWEyjQvNTb13dCJXrHggR0yf/5RBA=="
|
|
329
329
|
}
|
package/README.md
CHANGED
|
@@ -169,7 +169,7 @@ refused BY NAME: `path traversal ('..' segment)`, `degenerate path segment`, `em
|
|
|
169
169
|
`illegal character '<c>'`, `empty stem after the leading dot`. The old substring traversal test also
|
|
170
170
|
rejected the ordinary filename `foo..bar.ts`; it is a segment test now.
|
|
171
171
|
|
|
172
|
-
### The Step-8 amendment gate runs a command instead of judging (v1.5.
|
|
172
|
+
### The Step-8 amendment gate runs a command instead of judging (v1.5.8)
|
|
173
173
|
|
|
174
174
|
Every `AM-N` row must resolve to a test found INSIDE the file the row names, and the check is
|
|
175
175
|
`dz amendment-check --slug <slug> --json` rather than a paragraph asking the reviewer to confirm it.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dzhechkov/skills-feature-adr",
|
|
3
|
-
"version": "1.5.
|
|
3
|
+
"version": "1.5.8",
|
|
4
4
|
"description": "Adaptive Feature Development skill pack for Claude Code — 11-step pipeline with Complexity Router (S/M/L/XL), ADR-driven architecture, 15 agentic-qe skills, multi-agent fleet QE. Supports --full-qe, --full-qe-extended, --with-learning, and --knowledge-extractor modes.",
|
|
5
5
|
"bin": {
|
|
6
6
|
"skills-feature-adr": "./bin/cli.js"
|
|
@@ -42,12 +42,12 @@
|
|
|
42
42
|
},
|
|
43
43
|
"repository": {
|
|
44
44
|
"type": "git",
|
|
45
|
-
"url": "https://github.com/djd1m/dz-harness
|
|
45
|
+
"url": "git+https://github.com/djd1m/dz-harness.git",
|
|
46
46
|
"directory": "packages/@dzhechkov/skills-feature-adr"
|
|
47
47
|
},
|
|
48
|
-
"homepage": "https://github.com/djd1m/dz-
|
|
48
|
+
"homepage": "https://github.com/djd1m/dz-harness/tree/main/packages/@dzhechkov/skills-feature-adr#readme",
|
|
49
49
|
"bugs": {
|
|
50
|
-
"url": "https://github.com/djd1m/dz-
|
|
50
|
+
"url": "https://github.com/djd1m/dz-harness/issues"
|
|
51
51
|
},
|
|
52
52
|
"peerDependencies": {
|
|
53
53
|
"@dzhechkov/keysarium-core": "^1.0.0"
|
package/sbom.json
CHANGED
|
@@ -35,7 +35,7 @@
|
|
|
35
35
|
"hashes": [
|
|
36
36
|
{
|
|
37
37
|
"alg": "SHA-256",
|
|
38
|
-
"content": "
|
|
38
|
+
"content": "dc985b443d8ac9d4091b09367330f7f610c35572d99f635855989fd893c5e6ac"
|
|
39
39
|
}
|
|
40
40
|
]
|
|
41
41
|
},
|
|
@@ -69,7 +69,7 @@
|
|
|
69
69
|
},
|
|
70
70
|
{
|
|
71
71
|
"name": "dz:canonical-json-sha256-v2",
|
|
72
|
-
"value": "
|
|
72
|
+
"value": "d57357d1feb9f111d0674f1c307a4d15a41cb005074351d37ebaf7dc1afe3575"
|
|
73
73
|
}
|
|
74
74
|
]
|
|
75
75
|
},
|
|
@@ -789,7 +789,7 @@
|
|
|
789
789
|
"hashes": [
|
|
790
790
|
{
|
|
791
791
|
"alg": "SHA-256",
|
|
792
|
-
"content": "
|
|
792
|
+
"content": "f287a73bdf8c11c99eedbc071c6babb3201a6cec7d644d65f20ece3cd4ca51c1"
|
|
793
793
|
}
|
|
794
794
|
]
|
|
795
795
|
},
|
|
@@ -184,7 +184,7 @@ const CKPT_FILE = FDIR + '/.fa-state/checkpoints.jsonl'
|
|
|
184
184
|
// M10 Stage-A, feature loop-designer). This region is now a GENERATED BLOB (regen-diff-gated by
|
|
185
185
|
// loop-blobs-regen.test.ts): edit the canonical TS FIRST, run node scripts/gen-loop-blobs.mjs,
|
|
186
186
|
// then re-splice. The value-pinned wiring tests in feature-adr-checkpoints.test.ts stay the net.
|
|
187
|
-
// ── BEGIN BLOB checkpoints@1.
|
|
187
|
+
// ── BEGIN BLOB checkpoints@1.2.0 sha256:a44560c6036fd143b7a3f125fec00fa8b91c3c06ac5b9e1ecfb83d27a5211e9b src=packages/@dzhechkov/harness-core/src/feature-adr-checkpoints.ts ──
|
|
188
188
|
const CHECKPOINT_STAGES = ['router', 'design', 'plan', 'code', 'qe', 'fleet'];
|
|
189
189
|
const STAGE_ARTIFACTS = {
|
|
190
190
|
router: '00_complexity_assessment.md',
|
|
@@ -347,7 +347,7 @@ function parseArtifactProbe(opts) {
|
|
|
347
347
|
return null;
|
|
348
348
|
return found;
|
|
349
349
|
}
|
|
350
|
-
// ── END BLOB checkpoints@1.
|
|
350
|
+
// ── END BLOB checkpoints@1.2.0 ──
|
|
351
351
|
// glue (workflow-owned, hand-reviewed): legacy mirror names → the blob exports. The hash
|
|
352
352
|
// composition is UNCHANGED (same CKPT_SCHEMA_VERSION salt + JSON-tuple), so recorded
|
|
353
353
|
// checkpoints from pre-wrap runs still resume.
|
|
@@ -463,7 +463,7 @@ async function withCheckpoint(stage, phaseName, inputHash, runFn, ckptOpts) {
|
|
|
463
463
|
// via the reqe-proven sed idiom (the sandbox forbids Date; the pure half takes ts as a parameter).
|
|
464
464
|
const CAPTURE_PAIRS = A.captureTrainingPairs !== false
|
|
465
465
|
const captureFailures = []
|
|
466
|
-
const TP_SCHEMA = 'fa-trainpair-
|
|
466
|
+
const TP_SCHEMA = 'fa-trainpair-3'
|
|
467
467
|
const TP_MAX_IO = 48000
|
|
468
468
|
const TP_DIR = REPO + '/.dz/fa-training/' + SLUG
|
|
469
469
|
const TP_MARK_DIR = REPO + '/.dz/fa-training/.backfill-marks'
|
|
@@ -505,6 +505,26 @@ function captureFailureRecord(stage, mode, reason, detail) {
|
|
|
505
505
|
}
|
|
506
506
|
function tpFamily(spec) { return /codex|gpt|openai/i.test(String(spec == null ? '' : spec)) ? 'codex' : 'claude' }
|
|
507
507
|
function tpText(v) { if (typeof v === 'string') return v; if (v === null || v === undefined) return ''; try { const s = JSON.stringify(v); return typeof s === 'string' ? s : String(v) } catch (e) { return String(v) } }
|
|
508
|
+
function tpBudget(raw) {
|
|
509
|
+
try {
|
|
510
|
+
if (raw === undefined) return { primary: 'claude', claude: 'normal', codex: 'normal', preset: 'unset' }
|
|
511
|
+
if (raw === null || typeof raw !== 'object') return null
|
|
512
|
+
const primary = raw.primary
|
|
513
|
+
const claude = raw.claude
|
|
514
|
+
const codex = raw.codex
|
|
515
|
+
if (primary !== 'claude' && primary !== 'codex') return null
|
|
516
|
+
if (claude !== 'normal' && claude !== 'eco') return null
|
|
517
|
+
if (codex !== 'normal' && codex !== 'eco') return null
|
|
518
|
+
if (raw.preset === 'unset') return { primary: primary, claude: claude, codex: codex, preset: 'unset' }
|
|
519
|
+
let preset = 'custom'
|
|
520
|
+
if (claude === 'normal' && codex === 'normal') preset = 'normal'
|
|
521
|
+
else if (claude === 'eco' && codex === 'eco') preset = 'eco'
|
|
522
|
+
else if (claude === 'eco' && codex === 'normal') preset = 'hybrid'
|
|
523
|
+
return { primary: primary, claude: claude, codex: codex, preset: preset }
|
|
524
|
+
} catch (e) {
|
|
525
|
+
return null
|
|
526
|
+
}
|
|
527
|
+
}
|
|
508
528
|
// Operator-profile redaction (mirror of harness-core redactProfileBlock — ADR-001 Decision 5 /
|
|
509
529
|
// CF-6 of operator-profile). The PERSIST seam in `dz feature-adr-record` redacts independently;
|
|
510
530
|
// this mirror exists so the truncation fnv1a64 hashes below are hashes of the REDACTED text and
|
|
@@ -526,7 +546,7 @@ function tpRedact(text) {
|
|
|
526
546
|
rest = rest.slice(end + TP_PROFILE_END.length)
|
|
527
547
|
}
|
|
528
548
|
}
|
|
529
|
-
function buildTrainingPair(slug, stage, ts, inputRaw, outputRaw, evaluation, provenance, captureMode, resumed) {
|
|
549
|
+
function buildTrainingPair(slug, stage, ts, inputRaw, outputRaw, evaluation, provenance, budgetModeRaw, captureMode, resumed) {
|
|
530
550
|
// Redaction FIRST, before the oversize guard — same order as the core builder.
|
|
531
551
|
let input = tpRedact(tpText(inputRaw))
|
|
532
552
|
let output = tpRedact(tpText(outputRaw))
|
|
@@ -559,6 +579,7 @@ function buildTrainingPair(slug, stage, ts, inputRaw, outputRaw, evaluation, pro
|
|
|
559
579
|
tokens: (typeof pv.tokens === 'number' && isFinite(pv.tokens)) ? pv.tokens : null,
|
|
560
580
|
minutes: (typeof pv.minutes === 'number' && isFinite(pv.minutes)) ? pv.minutes : null,
|
|
561
581
|
},
|
|
582
|
+
budgetMode: tpBudget(budgetModeRaw),
|
|
562
583
|
truncated: truncated,
|
|
563
584
|
captureMode: captureMode === 'backfill' ? 'backfill' : 'capture',
|
|
564
585
|
resumed: resumed === true,
|
|
@@ -582,8 +603,9 @@ async function capturePairs(stage, phaseName, records, resumeGuardStage) {
|
|
|
582
603
|
mode = decideCaptureMode({ enabled: CAPTURE_PAIRS, resumed: resumed, recordCount: filteredRecords.length })
|
|
583
604
|
if (mode === 'skip-disabled' || mode === 'skip-empty') return
|
|
584
605
|
const lines = []
|
|
606
|
+
const budgetModeInput = { primary: PRIMARY, claude: BUDGET_MODE.claude, codex: BUDGET_MODE.codex, preset: A.budget === undefined ? 'unset' : undefined }
|
|
585
607
|
for (const r of filteredRecords) {
|
|
586
|
-
lines.push(JSON.stringify(buildTrainingPair(SLUG, stage, null, r.input, r.output, r.evaluation, r.provenance, mode === 'backfill' ? 'backfill' : 'capture', resumed === true)))
|
|
608
|
+
lines.push(JSON.stringify(buildTrainingPair(SLUG, stage, null, r.input, r.output, r.evaluation, r.provenance, budgetModeInput, mode === 'backfill' ? 'backfill' : 'capture', resumed === true)))
|
|
587
609
|
}
|
|
588
610
|
const file = TP_DIR + '/' + stage + '.jsonl'
|
|
589
611
|
const markStage = String(stage).replace(/\.\./g, '_').replace(/\//g, '_')
|
|
@@ -715,8 +737,8 @@ const PLANNER = (A.planner === 'codex') ? 'codex' : 'claude'
|
|
|
715
737
|
// LOAD-BEARING: when args.models.qe is unset the QE stage is auto-routed to the OTHER family than the
|
|
716
738
|
// coder (a model that codes must not also self-QE). BACKWARD-COMPATIBLE: omitting args.models AND the
|
|
717
739
|
// legacy knobs ⇒ routingRequested is false ⇒ every stage resolves to {} ⇒ byte-identical to today.
|
|
718
|
-
// Precedence: args.models[stage] > legacy planner/coder/qeReviewer/codexModel knobs >
|
|
719
|
-
//
|
|
740
|
+
// Precedence: args.models[stage] > legacy planner/coder/qeReviewer/codexModel knobs > primary/budget defaults.
|
|
741
|
+
// KNOWN_CODEX is spellability only; capability selection uses explicit CODEX_TIERS and a live pre-run probe.
|
|
720
742
|
// (string concat, explicit if/return, object-literal tables — NO template literals, NO inline ?:agent())
|
|
721
743
|
// mirror of src/feature-adr-routing.ts; keep the two in lock-step (a drift test asserts it).
|
|
722
744
|
// stage 2 pending (loop-designer M10/ADR-004 D4): still a hand-maintained mirror — the routing
|
|
@@ -724,16 +746,24 @@ const PLANNER = (A.planner === 'codex') ? 'codex' : 'claude'
|
|
|
724
746
|
// regeneration via model-resolver/usage-probes/codex-dispatch blobs is the tracked Stage-B item.
|
|
725
747
|
const MODELS = (A.models && typeof A.models === 'object') ? A.models : {}
|
|
726
748
|
const KNOWN_CODEX = { 'auto': 1, 'gpt-5.5': 1, 'gpt-5.6': 1, 'gpt-5.6-luna': 1, 'gpt-5.6-terra': 1, 'gpt-5.6-sol': 1 }
|
|
749
|
+
// The allowlist is not an availability check — probe every id before every run; ids drift in both directions.
|
|
750
|
+
const CODEX_TIERS = { flagship: 'gpt-5.6-sol', workhorse: 'gpt-5.6-terra', 'high-volume': 'gpt-5.6-luna' }
|
|
727
751
|
const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 }
|
|
728
|
-
const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 }
|
|
752
|
+
const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1, max: 1 }
|
|
729
753
|
const DEFAULT_MODELS = { router: 'fable', requirements: 'sonnet', research: 'sonnet', adr: 'opus', ideation: 'sonnet', ddd: 'opus', architecture: 'opus', plan: 'sonnet', code: null, qe: null, fleet: 'sonnet' }
|
|
730
|
-
const
|
|
754
|
+
const BUDGET_PRESETS = { normal: { claude: 'normal', codex: 'normal' }, eco: { claude: 'eco', codex: 'eco' }, hybrid: { claude: 'eco', codex: 'normal' } }
|
|
755
|
+
const ROUTING_TABLES = { claude: { claude: { normal: { router: 'sonnet', requirements: 'sonnet', research: 'sonnet', adr: 'fable', ideation: 'sonnet', ddd: 'fable', architecture: 'fable', plan: 'opus', code: 'sonnet', fleet: 'sonnet' }, eco: { router: 'sonnet', requirements: 'sonnet', research: 'sonnet', adr: 'opus', ideation: 'sonnet', ddd: 'opus', architecture: 'opus', plan: 'sonnet', code: 'sonnet', fleet: 'sonnet' } }, codex: { normal: {}, eco: {} } }, codex: { claude: { normal: { router: 'sonnet', qe: 'sonnet', fleet: 'sonnet' }, eco: { router: 'sonnet', qe: 'sonnet', fleet: 'sonnet' } }, codex: { normal: {}, eco: {} } } }
|
|
756
|
+
const STAGE_EFFORT = { override: { router: 'medium', requirements: 'medium', research: 'medium', adr: 'high', ideation: 'medium', ddd: 'high', architecture: 'high', plan: 'high', code: 'medium', qe: 'high', fleet: 'medium' } }
|
|
757
|
+
const BUDGET_MODE = resolveBudgetMode(A.budget)
|
|
758
|
+
const PRIMARY = (A.primary === 'codex') ? 'codex' : 'claude'
|
|
759
|
+
const routingRequested = (Object.keys(MODELS).length > 0) || (A.primary !== undefined) || (A.budget !== undefined) || (PLANNER === 'codex') || (CODER === 'codex' || CODER === 'codex-fallback') || (QE_REVIEWER === 'codex' || QE_REVIEWER === 'codex-fallback') || (A.usageAdaptive === true)
|
|
731
760
|
const modelsUsed = {}
|
|
732
761
|
|
|
733
762
|
// ── USAGE-ADAPTIVE ROUTING (pre-emptive codex switch at >= usageThreshold, default 70%) ──
|
|
734
763
|
// At every phase boundary a minimal haiku probe runs 'dz usage --json'; when SESSION or WEEKLY
|
|
735
764
|
// usage crosses the threshold BEFORE a phase launches, ALL remaining stages switch to
|
|
736
|
-
// codex:<topCodexId>
|
|
765
|
+
// codex:<topCodexId> using STAGE_EFFORT (official medium default; high only where the table names it).
|
|
766
|
+
// When a later probe reads
|
|
737
767
|
// BOTH metrics below threshold (positive numbers, not nulls) the normal mix is RESTORED. The
|
|
738
768
|
// load-bearing asymmetry: an agent-null probe (dispatch died — often MEANS limits) fail-safe-switches
|
|
739
769
|
// TO codex; a value-null (unconfigured limits) flips NOTHING. All additive behind USAGE_ADAPTIVE:
|
|
@@ -742,7 +772,7 @@ const modelsUsed = {}
|
|
|
742
772
|
// the pure library mirror (feature-adr-routing.ts) threads it via RoutingEnv — never a global.
|
|
743
773
|
const USAGE_THRESHOLD = Number(A.usageThreshold) > 0 ? Number(A.usageThreshold) : 70
|
|
744
774
|
const USAGE_ADAPTIVE = (A.usageAdaptive !== false) && (routingRequested || A.usageAdaptive === true)
|
|
745
|
-
const OVERRIDE_REASONING = mergeOpts(
|
|
775
|
+
const OVERRIDE_REASONING = mergeOpts(STAGE_EFFORT.override, (A.usageReasoning && typeof A.usageReasoning === 'object') ? A.usageReasoning : {})
|
|
746
776
|
const usageReasoning = OVERRIDE_REASONING
|
|
747
777
|
let usageOverride = false
|
|
748
778
|
const usageEvents = []
|
|
@@ -804,7 +834,7 @@ function specToOpts(spec) {
|
|
|
804
834
|
let id = parts[1] || CODEX_MODEL
|
|
805
835
|
if (id !== 'auto' && !KNOWN_CODEX[id]) { log('models: unknown codex id ' + id + ' — using ' + CODEX_MODEL); id = CODEX_MODEL }
|
|
806
836
|
let reasoning = parts[2] || 'high'
|
|
807
|
-
if (!VALID_REASONING[reasoning])
|
|
837
|
+
if (!VALID_REASONING[reasoning]) throw new RangeError('models: invalid reasoning "' + reasoning + '" — valid: ' + Object.keys(VALID_REASONING).join('|'))
|
|
808
838
|
return { agentType: 'codex:codex-rescue', codexModel: id, _reasoning: reasoning }
|
|
809
839
|
}
|
|
810
840
|
if (CLAUDE_NAMES[parts[0]]) return { model: parts[0] }
|
|
@@ -812,32 +842,94 @@ function specToOpts(spec) {
|
|
|
812
842
|
return {}
|
|
813
843
|
}
|
|
814
844
|
|
|
845
|
+
function codexIdForTier(tier) {
|
|
846
|
+
return CODEX_MODEL !== 'auto' ? CODEX_MODEL : CODEX_TIERS[tier]
|
|
847
|
+
}
|
|
848
|
+
|
|
849
|
+
function resolveBudgetMode(raw) {
|
|
850
|
+
if (raw === undefined) return BUDGET_PRESETS.normal
|
|
851
|
+
if (typeof raw === 'string') {
|
|
852
|
+
const preset = BUDGET_PRESETS[raw]
|
|
853
|
+
if (!preset) throw new RangeError('budget: unknown preset "' + raw + '" — valid: normal|eco|hybrid')
|
|
854
|
+
return preset
|
|
855
|
+
}
|
|
856
|
+
if (raw && typeof raw === 'object') {
|
|
857
|
+
for (const key of Object.keys(raw)) {
|
|
858
|
+
if (key !== 'claude' && key !== 'codex') throw new RangeError('budget: unknown family key "' + key + '" — valid: claude|codex')
|
|
859
|
+
}
|
|
860
|
+
for (const key of ['claude', 'codex']) {
|
|
861
|
+
const level = raw[key]
|
|
862
|
+
if (level !== undefined && level !== 'normal' && level !== 'eco') throw new RangeError('budget.' + key + ': unknown level "' + level + '" — valid: normal|eco')
|
|
863
|
+
}
|
|
864
|
+
return { claude: raw.claude || 'normal', codex: raw.codex || 'normal' }
|
|
865
|
+
}
|
|
866
|
+
throw new RangeError('budget: expected a preset name or {claude,codex} object, got ' + typeof raw)
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
function budgetPresetName(axis) {
|
|
870
|
+
for (const name of ['normal', 'eco', 'hybrid']) {
|
|
871
|
+
const preset = BUDGET_PRESETS[name]
|
|
872
|
+
if (preset.claude === axis.claude && preset.codex === axis.codex) return name
|
|
873
|
+
}
|
|
874
|
+
return null
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
function budgetTable(primary, mode) {
|
|
878
|
+
const claudeHalf = ROUTING_TABLES[primary].claude[mode.claude]
|
|
879
|
+
let codexHalf
|
|
880
|
+
if (primary === 'claude') {
|
|
881
|
+
const qeSpec = mode.codex === 'normal' ? 'codex:' + codexIdForTier('flagship') + ':high' : 'codex:' + codexIdForTier('workhorse') + ':medium'
|
|
882
|
+
codexHalf = { ...ROUTING_TABLES.claude.codex[mode.codex], qe: A.codexAvailable === false ? 'opus' : qeSpec }
|
|
883
|
+
} else {
|
|
884
|
+
const normal = mode.codex === 'normal'
|
|
885
|
+
const id = codexIdForTier(normal ? 'flagship' : 'workhorse')
|
|
886
|
+
const design = 'codex:' + id + ':' + (normal ? 'high' : 'medium')
|
|
887
|
+
codexHalf = { requirements: design, research: design, adr: design, ideation: design, ddd: design, architecture: design, plan: 'codex:' + id + ':' + (normal ? 'high' : 'low'), code: 'codex:' + id + ':medium' }
|
|
888
|
+
}
|
|
889
|
+
return { ...claudeHalf, ...codexHalf }
|
|
890
|
+
}
|
|
891
|
+
|
|
892
|
+
// qe2 is outside the 11 canonical stages: A-normal adds an independent Opus precision pass for L/XL.
|
|
893
|
+
function qePrecisionPassSpec(primary, budget, tier) {
|
|
894
|
+
if (primary !== 'codex') return null
|
|
895
|
+
if (budget.claude !== 'normal') return null
|
|
896
|
+
if (tier !== 'L' && tier !== 'XL') return null
|
|
897
|
+
return 'opus'
|
|
898
|
+
}
|
|
899
|
+
|
|
900
|
+
function assertCrossFamilyQe(codeSpec, qeSpec) {
|
|
901
|
+
const fam = function (s) { return (s && String(s).split(':')[0] === 'codex') ? 'codex' : 'claude' }
|
|
902
|
+
if (fam(codeSpec) === fam(qeSpec)) throw new Error('cross-family QE violated: code=' + codeSpec + ' qe=' + qeSpec)
|
|
903
|
+
}
|
|
904
|
+
|
|
815
905
|
function resolveCoderSpec() {
|
|
816
906
|
if (CODER === 'codex' || CODER === 'codex-fallback') return 'codex:' + CODEX_MODEL + ':high'
|
|
817
907
|
return 'opus'
|
|
818
908
|
}
|
|
819
909
|
|
|
820
910
|
function coderIsCodex() {
|
|
821
|
-
if (CODER === 'codex' || CODER === 'codex-fallback') return true
|
|
822
911
|
const codeSpec = MODELS.code
|
|
823
|
-
if (codeSpec && String(codeSpec).split(':')[0] === 'codex'
|
|
824
|
-
return
|
|
912
|
+
if (codeSpec !== undefined && codeSpec !== null) return String(codeSpec).split(':')[0] === 'codex'
|
|
913
|
+
if (CODER === 'codex' || CODER === 'codex-fallback') return true
|
|
914
|
+
return A.primary === 'codex'
|
|
825
915
|
}
|
|
826
916
|
|
|
827
917
|
function topCodexId() {
|
|
828
|
-
|
|
829
|
-
if (top === 'auto') {
|
|
830
|
-
const ids = Object.keys(KNOWN_CODEX)
|
|
831
|
-
for (let i = 0; i < ids.length; i++) { if (ids[i] !== 'auto') top = ids[i] || top }
|
|
832
|
-
}
|
|
833
|
-
return top
|
|
918
|
+
return CODEX_MODEL !== 'auto' ? CODEX_MODEL : CODEX_TIERS.flagship
|
|
834
919
|
}
|
|
835
920
|
|
|
836
|
-
function
|
|
837
|
-
if (
|
|
921
|
+
function resolveQeSpecForCoder(coderCodex) {
|
|
922
|
+
if (coderCodex) return 'sonnet'
|
|
838
923
|
const CODEX_AVAILABLE = A.codexAvailable !== false
|
|
839
924
|
if (!CODEX_AVAILABLE) return 'opus'
|
|
840
|
-
|
|
925
|
+
const budget = BUDGET_MODE
|
|
926
|
+
return budget.codex === 'eco'
|
|
927
|
+
? 'codex:' + codexIdForTier('workhorse') + ':medium'
|
|
928
|
+
: 'codex:' + codexIdForTier('flagship') + ':high'
|
|
929
|
+
}
|
|
930
|
+
|
|
931
|
+
function resolveQeSpec() {
|
|
932
|
+
return resolveQeSpecForCoder(coderIsCodex())
|
|
841
933
|
}
|
|
842
934
|
|
|
843
935
|
// qeShouldUseCodex: the load-bearing cross-model gate — the model that wrote the code must NEVER self-QE.
|
|
@@ -854,7 +946,7 @@ function qeShouldUseCodex() {
|
|
|
854
946
|
|
|
855
947
|
function resolveStageModel(stage) {
|
|
856
948
|
if (usageOverride) {
|
|
857
|
-
const r = (usageReasoning && usageReasoning[stage]) ||
|
|
949
|
+
const r = (usageReasoning && usageReasoning[stage]) || STAGE_EFFORT.override[stage] || 'medium'
|
|
858
950
|
const o = specToOpts('codex:' + topCodexId() + ':' + r)
|
|
859
951
|
o._usageSwitched = true
|
|
860
952
|
return o
|
|
@@ -862,7 +954,17 @@ function resolveStageModel(stage) {
|
|
|
862
954
|
let spec = MODELS[stage]
|
|
863
955
|
if (spec === undefined) {
|
|
864
956
|
if (!routingRequested) return {}
|
|
865
|
-
|
|
957
|
+
if (stage === 'code' && (CODER === 'codex' || CODER === 'codex-fallback')) {
|
|
958
|
+
return specToOpts(resolveCoderSpec())
|
|
959
|
+
}
|
|
960
|
+
if (stage === 'plan' && PLANNER === 'codex') {
|
|
961
|
+
return specToOpts('codex:' + CODEX_MODEL + ':high')
|
|
962
|
+
}
|
|
963
|
+
if (stage === 'qe') {
|
|
964
|
+
return specToOpts(resolveQeSpec())
|
|
965
|
+
}
|
|
966
|
+
const cell = budgetTable(PRIMARY, BUDGET_MODE)[stage]
|
|
967
|
+
spec = cell !== undefined ? cell : DEFAULT_MODELS[stage]
|
|
866
968
|
}
|
|
867
969
|
if (stage === 'code' && (spec === null || spec === undefined)) return specToOpts(resolveCoderSpec())
|
|
868
970
|
if (stage === 'qe' && (spec === null || spec === undefined)) return specToOpts(resolveQeSpec())
|
|
@@ -1020,11 +1122,13 @@ function codexExecCommand(input) {
|
|
|
1020
1122
|
const o = input || {}
|
|
1021
1123
|
const modelId = String(o.modelId === undefined || o.modelId === null ? '' : o.modelId)
|
|
1022
1124
|
if (!isSafeCodexId(modelId)) return null
|
|
1125
|
+
const reasoning = (o.reasoning === undefined || o.reasoning === null || o.reasoning === '') ? 'high' : String(o.reasoning)
|
|
1126
|
+
if (!VALID_REASONING[reasoning]) return null
|
|
1023
1127
|
const raw = Number(o.timeoutSeconds)
|
|
1024
1128
|
const seconds = (raw === raw && raw !== Infinity && raw > 0) ? Math.floor(raw) : CODEX_EXEC_TIMEOUT_SECONDS
|
|
1025
1129
|
const repo = String(o.repo === undefined || o.repo === null ? '' : o.repo)
|
|
1026
1130
|
const cd = repo === '' ? '' : ' -C ' + codexSq(repo)
|
|
1027
|
-
return timeoutBinOrDefault(o.timeoutBin) + ' ' + seconds + ' codex exec' + cd + ' -m ' + codexSq(modelId) + ' ' + codexSq(String(o.prompt === undefined || o.prompt === null ? '' : o.prompt)) + ' < /dev/null'
|
|
1131
|
+
return timeoutBinOrDefault(o.timeoutBin) + ' ' + seconds + ' codex exec' + cd + ' -m ' + codexSq(modelId) + ' -c model_reasoning_effort=' + codexSq(reasoning) + ' ' + codexSq(String(o.prompt === undefined || o.prompt === null ? '' : o.prompt)) + ' < /dev/null'
|
|
1028
1132
|
}
|
|
1029
1133
|
|
|
1030
1134
|
function codexReviewCommand(input) {
|
|
@@ -1268,7 +1372,14 @@ function isAgentTypeMissingError(err) {
|
|
|
1268
1372
|
// CX-3: a workflow naming an agent type the harness lacks must FALL BACK, not die. Any other error
|
|
1269
1373
|
// still propagates — we do not hide real bugs behind a fallback.
|
|
1270
1374
|
async function safeCodexAgent(prompt, opts) {
|
|
1271
|
-
|
|
1375
|
+
let dispatchOpts = opts
|
|
1376
|
+
if (opts && opts.agentType === 'codex:codex-rescue') {
|
|
1377
|
+
const requestedId = opts.codexModel !== 'auto' ? opts.codexModel : null
|
|
1378
|
+
const probed = await probeCodexId(requestedId)
|
|
1379
|
+
if (!probed) return null
|
|
1380
|
+
dispatchOpts = mergeOpts(opts, { codexModel: probed })
|
|
1381
|
+
}
|
|
1382
|
+
try { return await agent(prompt, dispatchOpts) }
|
|
1272
1383
|
catch (err) {
|
|
1273
1384
|
if (isAgentTypeMissingError(err)) { log('codex: agent type unavailable — falling back to Claude (' + String(err) + ')'); return null }
|
|
1274
1385
|
throw err
|
|
@@ -1276,7 +1387,7 @@ async function safeCodexAgent(prompt, opts) {
|
|
|
1276
1387
|
}
|
|
1277
1388
|
|
|
1278
1389
|
// CX-1: the allowlist says a name is spellable; only a probe says it answers. One probe per run.
|
|
1279
|
-
|
|
1390
|
+
const _probedCodexIds = {}
|
|
1280
1391
|
// Which timeout binary this machine actually has. ONE cached shell probe, run before either
|
|
1281
1392
|
// dispatch mode spends a model call.
|
|
1282
1393
|
//
|
|
@@ -1296,24 +1407,26 @@ async function probeTimeoutBin() {
|
|
|
1296
1407
|
return _timeoutBin
|
|
1297
1408
|
}
|
|
1298
1409
|
|
|
1299
|
-
async function probeCodexId() {
|
|
1300
|
-
|
|
1301
|
-
const raw = (CODEX_MODEL && CODEX_MODEL !== 'auto') ? [CODEX_MODEL,
|
|
1410
|
+
async function probeCodexId(requestedId) {
|
|
1411
|
+
const requested = isSafeCodexId(requestedId) ? String(requestedId) : null
|
|
1412
|
+
const raw = requested ? [requested] : ((CODEX_MODEL && CODEX_MODEL !== 'auto') ? [CODEX_MODEL, CODEX_TIERS.flagship, 'gpt-5.5'] : [CODEX_TIERS.flagship, 'gpt-5.5'])
|
|
1413
|
+
const cacheKey = raw.join('|')
|
|
1414
|
+
if (Object.prototype.hasOwnProperty.call(_probedCodexIds, cacheKey)) return _probedCodexIds[cacheKey]
|
|
1302
1415
|
const ids = raw.filter(isSafeCodexId)
|
|
1303
1416
|
for (const id of ids) {
|
|
1304
1417
|
// The probe is built with the SAME binary the real dispatch will use, so a machine that cannot
|
|
1305
1418
|
// bound a run fails here rather than mid-QE.
|
|
1306
1419
|
const tbin = await probeTimeoutBin()
|
|
1307
|
-
if (tbin === null) {
|
|
1420
|
+
if (tbin === null) { _probedCodexIds[cacheKey] = null; return null }
|
|
1308
1421
|
const cmd = codexProbeCommand(id, tbin)
|
|
1309
1422
|
if (!cmd) { log('codex: refusing unsafe model id ' + id); continue }
|
|
1310
1423
|
const out = await agent('Run EXACTLY this via Bash and reply with its stdout only: ' + cmd + ' — if it fails or times out reply with exactly ' + CODEX_UNAVAILABLE, { label: 'probe:' + id, phase: 'Route', model: 'haiku', effort: 'low' })
|
|
1311
1424
|
if (out && /\bOK\b/.test(String(out)) && String(out).indexOf(CODEX_UNAVAILABLE) === -1) {
|
|
1312
|
-
log('codex: probed ' + id + ' — available');
|
|
1425
|
+
log('codex: probed ' + id + ' — available'); _probedCodexIds[cacheKey] = id; return id
|
|
1313
1426
|
}
|
|
1314
1427
|
log('codex: probed ' + id + ' — NOT available')
|
|
1315
1428
|
}
|
|
1316
|
-
|
|
1429
|
+
_probedCodexIds[cacheKey] = null
|
|
1317
1430
|
return null
|
|
1318
1431
|
}
|
|
1319
1432
|
|
|
@@ -1350,9 +1463,9 @@ function codexQeSignalCommand(inner, outPath) {
|
|
|
1350
1463
|
// Shared tail of both dispatch modes: run the signal-wrapped command through a shell agent and
|
|
1351
1464
|
// CLASSIFY what came back. signalExpected is true here — on the pipeline path a swallowed sentinel
|
|
1352
1465
|
// means the command did not demonstrably run, which is a tool-error, never a pass.
|
|
1353
|
-
async function runCodexQeCommand(stage, cmd, phaseName, label, probed, mode, scopeRef, files, allowStatedGrade) {
|
|
1466
|
+
async function runCodexQeCommand(stage, cmd, phaseName, label, probed, mode, scopeRef, files, allowStatedGrade, requestedReasoning) {
|
|
1354
1467
|
const wrapped = 'Run EXACTLY this via Bash and reply with its stdout VERBATIM and nothing else, INCLUDING the final ' + CODEX_QE_SIGNAL_PREFIX + ' line (it is a machine signal, not prose — do not summarise, reformat or omit it). Only if you cannot run the command AT ALL (no shell, command not found) reply with exactly ' + CODEX_UNAVAILABLE + '; a timeout is NOT that case, it reports itself in the signal line.\n\n' + codexQeSignalCommand(cmd, '/tmp/dz-codex-qe-' + SLUG + '-' + stage + '-' + mode + '.out')
|
|
1355
|
-
const raw = await agent(wrapped, { label: stageLabel(label, { agentType: 'codex:codex-rescue', codexModel: probed, _reasoning: 'high' }), phase: phaseName, model: 'haiku', effort: 'low' })
|
|
1468
|
+
const raw = await agent(wrapped, { label: stageLabel(label, { agentType: 'codex:codex-rescue', codexModel: probed, _reasoning: requestedReasoning || 'high' }), phase: phaseName, model: 'haiku', effort: 'low' })
|
|
1356
1469
|
const sig = parseCodexReviewSignal(raw === null ? '' : String(raw))
|
|
1357
1470
|
const findings = parseCodexReviewFindings(sig.body)
|
|
1358
1471
|
// Mode A NEVER asked for a letter (every scope flag rejects a prompt), so any "Grade: X" in its
|
|
@@ -1373,28 +1486,32 @@ async function runCodexQeCommand(stage, cmd, phaseName, label, probed, mode, sco
|
|
|
1373
1486
|
// MODE A — the primary pass. codex review derives the review scope FROM THE DIFF, which is exactly
|
|
1374
1487
|
// the reconnaissance we were paying a model to do badly (MEASURED: 146s with a verdict, against 280s
|
|
1375
1488
|
// and exit 124 without one). It cannot carry our questions: every scope flag refuses [PROMPT].
|
|
1376
|
-
async function codexReviewAgent(stage, scope, scopeRef, phaseName) {
|
|
1489
|
+
async function codexReviewAgent(stage, scope, scopeRef, phaseName, requestedOpts) {
|
|
1377
1490
|
lastCodexDecline = null
|
|
1378
|
-
const
|
|
1491
|
+
const requestedId = requestedOpts && requestedOpts.codexModel !== 'auto' ? requestedOpts.codexModel : null
|
|
1492
|
+
const requestedReasoning = (requestedOpts && requestedOpts._reasoning) || 'high'
|
|
1493
|
+
const probed = await probeCodexId(requestedId)
|
|
1379
1494
|
if (!probed) return noteCodexDecline(stage, 'unavailable', { reason: 'no codex model id answered the probe' })
|
|
1380
|
-
const built = codexReviewCommand({ scope: scope, ref: scopeRef, modelId: probed, reasoning:
|
|
1495
|
+
const built = codexReviewCommand({ scope: scope, ref: scopeRef, modelId: probed, reasoning: requestedReasoning, timeoutSeconds: CODEX_REVIEW_TIMEOUT_SECONDS, timeoutBin: await probeTimeoutBin(), repo: REPO })
|
|
1381
1496
|
if (built.cmd === null) return noteCodexDecline(stage, 'tool-error', { exit: 2, detail: built.reason })
|
|
1382
|
-
return await runCodexQeCommand(stage, built.cmd, phaseName, stage + ':codex-review', probed, 'A', built.scope + (scopeRef ? ' ' + scopeRef : ''), [], false)
|
|
1497
|
+
return await runCodexQeCommand(stage, built.cmd, phaseName, stage + ':codex-review', probed, 'A', built.scope + (scopeRef ? ' ' + scopeRef : ''), [], false, requestedReasoning)
|
|
1383
1498
|
}
|
|
1384
1499
|
|
|
1385
1500
|
// MODE B — the narrowed follow-up. Carries OUR questions over files we name, and is refused outright
|
|
1386
1501
|
// when the prompt was not built by scopedQePrompt (see codexExecPlan).
|
|
1387
|
-
async function codexExecAgent(stage, prompt, phaseName, scoped, files) {
|
|
1502
|
+
async function codexExecAgent(stage, prompt, phaseName, scoped, files, requestedOpts) {
|
|
1388
1503
|
lastCodexDecline = null
|
|
1389
|
-
const
|
|
1504
|
+
const requestedId = requestedOpts && requestedOpts.codexModel !== 'auto' ? requestedOpts.codexModel : null
|
|
1505
|
+
const requestedReasoning = (requestedOpts && requestedOpts._reasoning) || 'high'
|
|
1506
|
+
const probed = await probeCodexId(requestedId)
|
|
1390
1507
|
const plan = codexExecPlan(stage, prompt.length, probed, scoped)
|
|
1391
1508
|
if (plan.mode !== 'exec') return noteCodexDecline(stage, 'unavailable', { reason: plan.reason })
|
|
1392
1509
|
// Was JSON.stringify(...) — DOUBLE quotes, in which the shell still expands a command substitution, and the
|
|
1393
1510
|
// prompt carries the user's own feature description. Single-quoted through codexSq closes that
|
|
1394
1511
|
// as a side effect of pinning the working directory.
|
|
1395
|
-
const inner = codexExecCommand({ modelId: probed, prompt: prompt, timeoutBin: await probeTimeoutBin(), timeoutSeconds: CODEX_EXEC_TIMEOUT_SECONDS, repo: REPO })
|
|
1512
|
+
const inner = codexExecCommand({ modelId: probed, reasoning: requestedReasoning, prompt: prompt, timeoutBin: await probeTimeoutBin(), timeoutSeconds: CODEX_EXEC_TIMEOUT_SECONDS, repo: REPO })
|
|
1396
1513
|
if (inner === null) return noteCodexDecline(stage, 'unavailable', { reason: 'unsafe codex id ' + String(probed) })
|
|
1397
|
-
return await runCodexQeCommand(stage, inner, phaseName, stage + ':codex-exec', probed, 'B', 'declared-targets(' + (files || []).length + ' declared, <=' + SCOPED_QE_MAX_FILES + ' reviewed)', files || [], true)
|
|
1514
|
+
return await runCodexQeCommand(stage, inner, phaseName, stage + ':codex-exec', probed, 'B', 'declared-targets(' + (files || []).length + ' declared, <=' + SCOPED_QE_MAX_FILES + ' reviewed)', files || [], true, requestedReasoning)
|
|
1398
1515
|
}
|
|
1399
1516
|
|
|
1400
1517
|
// Widened 2026-08-28 (MEASURED — see feature-adr-routing.ts for the reproducer): slop-lint's coder
|
|
@@ -2053,7 +2170,7 @@ async function designStage(promptText, opts, artifactPath, baseLabel) {
|
|
|
2053
2170
|
if (!needsLandedBarrier(opts)) return await agent(promptText, opts)
|
|
2054
2171
|
const codexOpts = {}
|
|
2055
2172
|
for (const k in opts) if (k !== 'schema') codexOpts[k] = opts[k]
|
|
2056
|
-
const res = await
|
|
2173
|
+
const res = await safeCodexAgent(promptText + codexEffortHint(codexOpts) + ' IMPORTANT: run the Codex task in FOREGROUND (synchronous — do NOT pass --background) so this call blocks until the file is fully written to disk.', codexOpts)
|
|
2057
2174
|
const probe = await agent('Confirm a Codex OUT-OF-BAND artifact write has LANDED before the next stage reads it. Run EXACTLY this via Bash and return its stdout verbatim, nothing else:\n' + landedProbeCmd(artifactPath), { label: 'design:confirm-landed', phase: 'Design', effort: 'low' })
|
|
2058
2175
|
if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
|
|
2059
2176
|
log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
|
|
@@ -2138,7 +2255,7 @@ modelsUsed.router = modelLabel(routerOpts)
|
|
|
2138
2255
|
// router checkpoint: its result (tier + recalled-pattern rationale) seeds every downstream hash.
|
|
2139
2256
|
// The Step-0 RECALL is part of the stage — a resumed router restores the SAME recalled patterns the
|
|
2140
2257
|
// original run applied (fresh lessons taught since then enter on the next live run, not mid-resume).
|
|
2141
|
-
const routerHash = ckptHash('router', [DESC, CODE_HINT, MODE, A.tier === undefined ? null : A.tier, BRAIN, MODELS.router === undefined ? null : MODELS.router, CODEX_MODEL, usageOverride, ROUTER_CONTRACT_TOKEN])
|
|
2258
|
+
const routerHash = ckptHash('router', [DESC, CODE_HINT, MODE, A.tier === undefined ? null : A.tier, BRAIN, MODELS.router === undefined ? null : MODELS.router, CODEX_MODEL, PRIMARY, BUDGET_MODE, usageOverride, ROUTER_CONTRACT_TOKEN])
|
|
2142
2259
|
const router = await withCheckpoint('router', 'Router', routerHash, async () => agent(routerPrompt + codexEffortHint(routerOpts), routerOpts))
|
|
2143
2260
|
if (resumedStages.indexOf('router') !== -1) modelsUsed.router = modelsUsed.router + ' (resumed)'
|
|
2144
2261
|
let tier = A.tier || (router ? router.tier : 'M')
|
|
@@ -2283,7 +2400,7 @@ if (isMplus) {
|
|
|
2283
2400
|
// patterns, the project-skills manifest state, the usage override, and CODEX_MODEL (a bare 'codex'
|
|
2284
2401
|
// model spec resolves through it). The artifact probe requires EVERY tier-active design artifact
|
|
2285
2402
|
// (Codex QE #2 — a one-file probe accepted a design missing its ADR/architecture).
|
|
2286
|
-
const designHash = ckptHash('design', [tier, DESC, CODE_HINT, fnv1a64(String(LEARNED)), MODELS.requirements === undefined ? null : MODELS.requirements, MODELS.adr === undefined ? null : MODELS.adr, MODELS.ideation === undefined ? null : MODELS.ideation, MODELS.architecture === undefined ? null : MODELS.architecture, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, CODEX_MODEL])
|
|
2403
|
+
const designHash = ckptHash('design', [tier, DESC, CODE_HINT, fnv1a64(String(LEARNED)), MODELS.requirements === undefined ? null : MODELS.requirements, MODELS.adr === undefined ? null : MODELS.adr, MODELS.ideation === undefined ? null : MODELS.ideation, MODELS.architecture === undefined ? null : MODELS.architecture, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, CODEX_MODEL, PRIMARY, BUDGET_MODE])
|
|
2287
2404
|
const designArtifacts = ['01_requirements.md']
|
|
2288
2405
|
if (isMplus) designArtifacts.push('03_adr/', '03.5_ideation_report.md', '05_architecture.md')
|
|
2289
2406
|
if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
|
|
@@ -2441,7 +2558,7 @@ const planIsCodex = (planModel.agentType === 'codex:codex-rescue') || (MODELS.pl
|
|
|
2441
2558
|
// plan checkpoint: keyed on the design fan's RESULT (a stale design invalidates the plan) + the
|
|
2442
2559
|
// planner spec. Covers the standard L/XL two-phase flow: the stop-after-plan re-invoke resumes
|
|
2443
2560
|
// router+design+plan instead of re-running them.
|
|
2444
|
-
const planHash = ckptHash('plan', [tier, DESC, fnv1a64(JSON.stringify(design === undefined ? null : design)), PLANNER, MODELS.plan === undefined ? null : MODELS.plan, CODEX_MODEL, usageOverride])
|
|
2561
|
+
const planHash = ckptHash('plan', [tier, DESC, fnv1a64(JSON.stringify(design === undefined ? null : design)), PLANNER, MODELS.plan === undefined ? null : MODELS.plan, CODEX_MODEL, PRIMARY, BUDGET_MODE, usageOverride])
|
|
2445
2562
|
let plan = await withCheckpoint('plan', 'Plan', planHash, async () => {
|
|
2446
2563
|
let plan = null
|
|
2447
2564
|
if (planIsCodex) {
|
|
@@ -2653,7 +2770,7 @@ const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG +
|
|
|
2653
2770
|
// code:null sentinel → resolveCoderSpec(). A Claude resolution merges {model} onto the Claude branch;
|
|
2654
2771
|
// under the BC omit-path it is {} (byte-identical).
|
|
2655
2772
|
const codeModel = resolveStageModel('code')
|
|
2656
|
-
const codeIsCodexFirst = (
|
|
2773
|
+
const codeIsCodexFirst = (codeModel.agentType === 'codex:codex-rescue') && (MODELS.code !== undefined || CODER !== 'codex-fallback')
|
|
2657
2774
|
const codeClaudeModel = codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel)
|
|
2658
2775
|
const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeClaudeModel)
|
|
2659
2776
|
// code checkpoint: COMPOSITE — the branchy claude/codex/fallback block sets three interdependent
|
|
@@ -2662,7 +2779,7 @@ const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), p
|
|
|
2662
2779
|
// the checkpoint (it only feeds the expected-targets parse, already consumed by the original run).
|
|
2663
2780
|
// R6: the landing token is salted into the code stage's PARTS (not CKPT_SCHEMA_VERSION, which
|
|
2664
2781
|
// stays 'fa-ckpt-2' deliberately) so ONLY this stage's pre-protocol checkpoints hash stale.
|
|
2665
|
-
const codeHash = ckptHash('code', [tier, DESC, fnv1a64(JSON.stringify(plan === undefined ? null : plan)), CODER, MODELS.code === undefined ? null : MODELS.code, CODEX_MODEL, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, LANDING_HASH_TOKEN])
|
|
2782
|
+
const codeHash = ckptHash('code', [tier, DESC, fnv1a64(JSON.stringify(plan === undefined ? null : plan)), CODER, MODELS.code === undefined ? null : MODELS.code, CODEX_MODEL, PRIMARY, BUDGET_MODE, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, LANDING_HASH_TOKEN])
|
|
2666
2783
|
const codeStage = await withCheckpoint('code', 'Code', codeHash, async () => {
|
|
2667
2784
|
let code = null
|
|
2668
2785
|
let coderUsed = 'claude'
|
|
@@ -2818,13 +2935,16 @@ const wqNote = writerQuiescence.verdict === 'quiet'
|
|
|
2818
2935
|
? ' WRITER-QUIESCENCE: quiet (' + writerQuiescence.note + ').'
|
|
2819
2936
|
: ' WRITER-QUIESCENCE GATE (MANDATORY to acknowledge): ' + writerQuiescence.note + ' State this standing explicitly in 08_qe_report.md next to the grade.'
|
|
2820
2937
|
await usageProbe('QE')
|
|
2821
|
-
const qePrompt = 'Step 8 (QE - brutal-honesty review, agentic-qe) of /feature-adr for "' + DESC + '" (' + SLUG + '). Adversarially review the SHIPPED code (read it): correctness, edge cases, error handling, and the LOAD-BEARING property the ADR named (ASSERT it has a test that DISCRIMINATES - the recurring lesson: a test that would still pass with the protection deleted is documentation, not a gate). Run this ADR gate before final grading: ' + ADR_FITNESS_CHECKLIST + ' ' + DISCRIMINATION_GATE + ' ' + MUTATION_GATE + ' ' + NO_STUBS_GATE + ' ' + AMENDMENT_GATE + ' Grade A/B/C/D honestly. Assess code-test adequacy + doc-test presence. List CONFIRMED gaps with severity. Write ' + FDIR + '/08_qe_report.md with an ADR Fitness Checklist section showing PASS/FAIL per ADR and evidence for the Confirmation-linked test. MANDATORY SELF-LEARNING STORE (close the loop, never skip): compare every candidate lesson against the Step-0 recalled LEARNED patterns above. Teach ONLY lessons NOT covered by Step-0 recall. On overlap, run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate; if --reinforce is unavailable, skip the duplicate teach and report `reinforced existing pattern <id>` in the QE report. Store every genuinely new lesson in the CANONICAL BRAIN store at `' + BRAIN + '` so it is NOT lost to a target repo you may have cd`d into. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal) — the `cd ' + BRAIN + ' &&` prefix + `--project ' + BRAIN + '` pin guarantee the lesson lands in the brain regardless of your CWD. Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled auto --run fa:' + SLUG + ' --count-project ' + BRAIN + ' --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` (run it verbatim via Bash, do not skip). Do NOT teach trivia or invent gaps. AUTHORING-TIME CLAIM-CHECK (Deliverable of claim-check-authoring-time): after writing ' + FDIR + '/08_qe_report.md, run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash, parse the {ok, findings, scanned} JSON, and report claimCheck: {findings: N, high: N, medium: N} (counts by severity) in your return object. TAG EVERY QUANTITATIVE CLAIM you write in the report using the convention the checker recognizes as honest — write "1131 tests pass (MEASURED — `npx vitest run`)", never a bare "1131 tests pass" — and where you QUOTE a forbidden phrase as an example (e.g. the retracted "100% passing" framing), backtick the literal so it reads as code, not an assertion, so your own compliant report scans clean. Return {grade, gaps, codeTestsAdequate, docTestsPresent, claimCheck}.' + ABSOLUTE_PATH_NOTE + landedNote + wqNote + PS_GUIDANCE('qe')
|
|
2938
|
+
const qePrompt = 'Step 8 (QE - brutal-honesty review, agentic-qe) of /feature-adr for "' + DESC + '" (' + SLUG + '). Adversarially review the SHIPPED code (read it): correctness, edge cases, error handling, and the LOAD-BEARING property the ADR named (ASSERT it has a test that DISCRIMINATES - the recurring lesson: a test that would still pass with the protection deleted is documentation, not a gate). Run this ADR gate before final grading: ' + ADR_FITNESS_CHECKLIST + ' ' + DISCRIMINATION_GATE + ' ' + MUTATION_GATE + ' ' + NO_STUBS_GATE + ' ' + AMENDMENT_GATE + ' Grade A/B/C/D honestly. Assess code-test adequacy + doc-test presence. List CONFIRMED gaps with severity. Write ' + FDIR + '/08_qe_report.md with the primary findings under the exact heading `## Primary QE pass` and an ADR Fitness Checklist section showing PASS/FAIL per ADR and evidence for the Confirmation-linked test. MANDATORY SELF-LEARNING STORE (close the loop, never skip): compare every candidate lesson against the Step-0 recalled LEARNED patterns above. Teach ONLY lessons NOT covered by Step-0 recall. On overlap, run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate; if --reinforce is unavailable, skip the duplicate teach and report `reinforced existing pattern <id>` in the QE report. Store every genuinely new lesson in the CANONICAL BRAIN store at `' + BRAIN + '` so it is NOT lost to a target repo you may have cd`d into. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal) — the `cd ' + BRAIN + ' &&` prefix + `--project ' + BRAIN + '` pin guarantee the lesson lands in the brain regardless of your CWD. Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled auto --run fa:' + SLUG + ' --count-project ' + BRAIN + ' --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` (run it verbatim via Bash, do not skip). Do NOT teach trivia or invent gaps. AUTHORING-TIME CLAIM-CHECK (Deliverable of claim-check-authoring-time): after writing ' + FDIR + '/08_qe_report.md, run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash, parse the {ok, findings, scanned} JSON, and report claimCheck: {findings: N, high: N, medium: N} (counts by severity) in your return object. TAG EVERY QUANTITATIVE CLAIM you write in the report using the convention the checker recognizes as honest — write "1131 tests pass (MEASURED — `npx vitest run`)", never a bare "1131 tests pass" — and where you QUOTE a forbidden phrase as an example (e.g. the retracted "100% passing" framing), backtick the literal so it reads as code, not an assertion, so your own compliant report scans clean. Return {grade, gaps, codeTestsAdequate, docTestsPresent, claimCheck}.' + ABSOLUTE_PATH_NOTE + landedNote + wqNote + PS_GUIDANCE('qe')
|
|
2822
2939
|
// CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
|
|
2823
2940
|
// coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
|
|
2824
2941
|
// An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
|
|
2825
2942
|
// PRESERVED); a codex qe spec REPLACES agentType with codex:codex-rescue (as today). The codex-null→
|
|
2826
2943
|
// Claude guard is retained as the runtime belt so codex-unavailable never blocks.
|
|
2827
|
-
|
|
2944
|
+
let qeModel = resolveStageModel('qe')
|
|
2945
|
+
// A codex-fallback coder can finish on either family. Default QE follows the ACTUAL runner, not the
|
|
2946
|
+
// pre-code knob; only an explicit models.qe is allowed to opt out of cross-family review.
|
|
2947
|
+
if (MODELS.qe === undefined && routingRequested) qeModel = specToOpts(resolveQeSpecForCoder(tpFamily(coderUsed) === 'codex'))
|
|
2828
2948
|
// Single tested source of truth (feature-adr-routing.ts:qeShouldUseCodex) — closes the self-QE hole where
|
|
2829
2949
|
// the legacy qeReviewer='codex' knob used to re-route QE back to codex even when the CODER was codex.
|
|
2830
2950
|
if (MODELS.qe === undefined && QE_REVIEWER === 'codex' && coderIsCodex()) log('QE: coder is codex — enforcing cross-model Claude QE (ignoring qeReviewer=codex to avoid self-review)')
|
|
@@ -2832,15 +2952,16 @@ if (MODELS.qe === undefined && QE_REVIEWER === 'codex' && coderIsCodex()) log('Q
|
|
|
2832
2952
|
// (which reads the pre-override config) says claude — dispatching that through the "claude" branch
|
|
2833
2953
|
// would run the stub wrapper AND mislabel qeReviewerUsed='claude' (a codex-on-codex review recorded
|
|
2834
2954
|
// as cross-family). The resolved agentType is the truth; either signal routes the codex branch.
|
|
2835
|
-
const qeIsCodex =
|
|
2955
|
+
const qeIsCodex = !!(qeModel && qeModel.agentType === 'codex:codex-rescue')
|
|
2836
2956
|
const qeClaudeModel = qeIsCodex ? {} : qeModel
|
|
2837
2957
|
const qeClaudeOpts = mergeOpts({ label: stageLabel('qe:brutal', qeClaudeModel), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qeClaudeModel)
|
|
2958
|
+
const qe2Spec = qePrecisionPassSpec(PRIMARY, BUDGET_MODE, tier)
|
|
2838
2959
|
// qe checkpoint: COMPOSITE (verdict + reviewer identity) keyed on the CODE stage's result — a re-coded
|
|
2839
2960
|
// feature always re-QEs. The teach/fa-record side effects belong to the stage: a resumed QE does not
|
|
2840
2961
|
// re-teach (the original run already stored its lessons — replaying teach would double-store).
|
|
2841
2962
|
// R6: the review SCOPE is part of what a QE verdict is about, so it enters the hash — a resume must
|
|
2842
2963
|
// not present a verdict obtained over one scope as if it had been obtained over another.
|
|
2843
|
-
const qeHash = ckptHash('qe', [fnv1a64(JSON.stringify(codeStage === undefined ? null : codeStage)), tier, DESC, QE_REVIEWER, MODELS.qe === undefined ? null : MODELS.qe, CODEX_MODEL, coderUsed, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, QE_SCOPE, QE_SCOPE_REF])
|
|
2964
|
+
const qeHash = ckptHash('qe', [fnv1a64(JSON.stringify(codeStage === undefined ? null : codeStage)), tier, DESC, QE_REVIEWER, MODELS.qe === undefined ? null : MODELS.qe, CODEX_MODEL, coderUsed, PRIMARY, BUDGET_MODE, qe2Spec, POLY.hasManifest, fnv1a64(String(POLY.report || '')), usageOverride, QE_SCOPE, QE_SCOPE_REF])
|
|
2844
2965
|
let crossFamilyQeReport = null
|
|
2845
2966
|
const qeStage = await withCheckpoint('qe', 'QE', qeHash, async () => {
|
|
2846
2967
|
let qe = null
|
|
@@ -2898,7 +3019,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2898
3019
|
}
|
|
2899
3020
|
}
|
|
2900
3021
|
}
|
|
2901
|
-
let codexQe = await codexReviewAgent('qe', QE_SCOPE, QE_SCOPE_REF, 'QE')
|
|
3022
|
+
let codexQe = await codexReviewAgent('qe', QE_SCOPE, QE_SCOPE_REF, 'QE', qeCodexLabelOpts)
|
|
2902
3023
|
// SCOPE THE VERDICT to this feature. Findings about other dirty work are real and are kept, but they
|
|
2903
3024
|
// may not decide THIS feature's grade. When the change set is unmeasured the partition is 'unscoped'
|
|
2904
3025
|
// and the grade stands exactly as the reviewer gave it — attributing nothing is the honest move.
|
|
@@ -2937,7 +3058,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2937
3058
|
// mode-B bookkeeping note would hide why the independent review did not happen.
|
|
2938
3059
|
log('QE: mode B not constructible — no declared changed files to scope it to; keeping mode A reason')
|
|
2939
3060
|
} else {
|
|
2940
|
-
codexQe = await codexExecAgent('qe', modeBPrompt + CODEX_HINT + codexEffortHint(qeCodexLabelOpts), 'QE', true, modeBFiles)
|
|
3061
|
+
codexQe = await codexExecAgent('qe', modeBPrompt + CODEX_HINT + codexEffortHint(qeCodexLabelOpts), 'QE', true, modeBFiles, qeCodexLabelOpts)
|
|
2941
3062
|
if (codexQe === null) lastCodexDecline = 'mode A: ' + String(modeADecline) + ' | mode B: ' + String(lastCodexDecline)
|
|
2942
3063
|
}
|
|
2943
3064
|
}
|
|
@@ -2959,7 +3080,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2959
3080
|
// with no 08_qe_report.md at all. Named by cross-family review of b6973199. The verdict itself
|
|
2960
3081
|
// is real (Codex produced it), so a failed transcription DEGRADES the run rather than voiding
|
|
2961
3082
|
// it — but it must be visible, and it must never read as a clean QE.
|
|
2962
|
-
const scribePrompt = 'Step 8 (QE) of /feature-adr for "' + DESC + '" (' + SLUG + '). The independent cross-family review has ALREADY BEEN DONE, by Codex. You are the SCRIBE, not the reviewer: RECORD it, do NOT re-grade it, do NOT soften it, do NOT add a verdict of your own, and do NOT mark anything resolved that the reviewer flagged. The grade is ' + codexQe.grade + ' and it is FINAL.\n\nWrite ' + FDIR + '/08_qe_report.md with: (1) the grade ' + codexQe.grade + ' stated verbatim; (2) HOW it was obtained — dispatch mode ' + codexQe.mode + ', scope ' + codexQe.scopeRef + ', wall-clock ' + codexQe.elapsedSeconds + 's, gradeSource ' + codexQe.gradeSource + ' (a DERIVED grade means the reviewer could not be asked for a letter and it was computed from the severities it reported — say so plainly); (3) the reviewer text below, verbatim,
|
|
3083
|
+
const scribePrompt = 'Step 8 (QE) of /feature-adr for "' + DESC + '" (' + SLUG + '). The independent cross-family review has ALREADY BEEN DONE, by Codex. You are the SCRIBE, not the reviewer: RECORD it, do NOT re-grade it, do NOT soften it, do NOT add a verdict of your own, and do NOT mark anything resolved that the reviewer flagged. The grade is ' + codexQe.grade + ' and it is FINAL.\n\nWrite ' + FDIR + '/08_qe_report.md with: (1) the grade ' + codexQe.grade + ' stated verbatim; (2) HOW it was obtained — dispatch mode ' + codexQe.mode + ', scope ' + codexQe.scopeRef + ', wall-clock ' + codexQe.elapsedSeconds + 's, gradeSource ' + codexQe.gradeSource + ' (a DERIVED grade means the reviewer could not be asked for a letter and it was computed from the severities it reported — say so plainly); (3) the reviewer text below, verbatim, under the exact heading `## Primary QE pass`; (4) an ADR Fitness Checklist section with PASS/FAIL per ADR and the evidence pointer for the Confirmation-linked test.\n\nREVIEWER TEXT (verbatim, do not edit or summarise):\n' + String(codexQe.text) + '\n\nMANDATORY SELF-LEARNING STORE (close the loop, never skip): compare candidate lessons against the Step-0 recalled LEARNED patterns. Teach ONLY lessons NOT already covered; on overlap run `dz teach --reinforce "<recalled pattern id or exact text>" --project ' + BRAIN + '` instead of minting a near-duplicate. Via Bash run EXACTLY `' + DZ_TEACH('<a durable reusable lesson from this feature - a rule/pattern/pitfall, NOT a checkpoint echo>', '<0.7-0.95>', '<area>') + '` for each genuine NEW lesson (1-3 max, high-signal). Then run `' + DZ + ' statusline --fa-record --slug ' + SLUG + ' --step "Step 8 QE" --recalled auto --run fa:' + SLUG + ' --count-project ' + BRAIN + ' --stored <count taught> --reinforced <count reinforced> --mode ' + MODE + ' --project ' + REPO + '` verbatim via Bash. Finally run EXACTLY `dz claim-check ' + FDIR + '/08_qe_report.md --json --fail-on none` via Bash and TAG every quantitative claim you write the way the checker recognises as honest.' + ABSOLUTE_PATH_NOTE
|
|
2963
3084
|
// WITNESS THE REWRITE, not the existence. On a re-QE or a resume with the same slug an OLD
|
|
2964
3085
|
// 08_qe_report.md is already sitting there, and an existence probe reports that stale file as
|
|
2965
3086
|
// landed — so a scribe that wrote nothing still marked the new verdict recorded, and the stage
|
|
@@ -3011,12 +3132,49 @@ if (qe === null && qeIsCodex) {
|
|
|
3011
3132
|
crossFamilyQeReport = cfBelt.report
|
|
3012
3133
|
}
|
|
3013
3134
|
}
|
|
3135
|
+
// A-normal L/XL only: Sonnet is the recall-oriented primary reviewer; Opus is a SECOND,
|
|
3136
|
+
// independent precision pass. It is advisory but real — never a table-only half-wire — and its
|
|
3137
|
+
// provenance stays separate in both the return object and 08_qe_report.md.
|
|
3138
|
+
let qe2 = null
|
|
3139
|
+
if (qe !== null && qe2Spec !== null) {
|
|
3140
|
+
const qe2Model = specToOpts(qe2Spec)
|
|
3141
|
+
const primaryGrade = String(qe.grade || '').trim().toUpperCase()
|
|
3142
|
+
const qe2ReportState = async function (label) {
|
|
3143
|
+
const report = shq(FDIR + '/08_qe_report.md')
|
|
3144
|
+
const cmd = 'p=' + report + '; h=$(sha256sum -- "$p" 2>/dev/null | awk "{print \\$1}"); [ -n "$h" ] || h=ABSENT; a=$(grep -cFx "## Primary QE pass" "$p" 2>/dev/null || true); b=$(grep -cFx "## Precision QE pass — Claude Opus" "$p" 2>/dev/null || true); c=$(grep -c "Combined Step-8 grade" "$p" 2>/dev/null || true); echo "QE2-REPORT sha=$h primary=$a precision=$b combined=$c"'
|
|
3145
|
+
const out = await agent('Run EXACTLY this via Bash and return only its stdout: ' + cmd, { label: label, phase: 'QE', effort: 'low' })
|
|
3146
|
+
const m = /QE2-REPORT sha=([0-9a-f]{64}|ABSENT) primary=(\d+) precision=(\d+) combined=(\d+)/.exec(String(out || ''))
|
|
3147
|
+
return m ? { sha: m[1], primary: Number(m[2]), precision: Number(m[3]), combined: Number(m[4]) } : null
|
|
3148
|
+
}
|
|
3149
|
+
const qe2Before = await qe2ReportState('qe:precision-before')
|
|
3150
|
+
const qe2Prompt = 'Step 8 precision QE second pass for "' + DESC + '" (' + SLUG + '). This is an INDEPENDENT precision-oriented review after the recall-oriented primary pass. First inspect the shipped code, tests, requirements, architecture, and every ADR and form your own findings WITHOUT consulting 08_qe_report.md. Only after your review is complete, open ' + FDIR + '/08_qe_report.md and APPEND (never replace) your findings under the exact heading `## Precision QE pass — Claude Opus`. Preserve `## Primary QE pass` as a separate provenance section. Grade A/B/C/D honestly. The primary grade was ' + primaryGrade + '; state a `Combined Step-8 grade` equal to the worse of that grade and your precision grade. Return {grade, gaps, codeTestsAdequate, docTestsPresent}.' + ABSOLUTE_PATH_NOTE + landedNote + wqNote
|
|
3151
|
+
const qe2Opts = mergeOpts({ label: stageLabel('qe:precision', qe2Model), phase: 'QE', agentType: 'qe-code-reviewer', schema: QE }, qe2Model)
|
|
3152
|
+
qe2 = await agent(qe2Prompt, qe2Opts)
|
|
3153
|
+
if (qe2) {
|
|
3154
|
+
const qe2After = await qe2ReportState('qe:precision-after')
|
|
3155
|
+
const qe2Recorded = !!(qe2Before && qe2After && qe2After.sha !== 'ABSENT' && qe2After.sha !== qe2Before.sha && qe2After.primary > 0 && qe2After.precision > qe2Before.precision && qe2After.combined > 0)
|
|
3156
|
+
const rank = { A: 0, B: 1, C: 2, D: 3 }
|
|
3157
|
+
const precisionGrade = String(qe2.grade || '').trim().toUpperCase()
|
|
3158
|
+
qe2 = mergeOpts(qe2, { reportWritten: qe2Recorded })
|
|
3159
|
+
if (qe2Recorded && rank[precisionGrade] !== undefined && rank[primaryGrade] !== undefined) {
|
|
3160
|
+
const combinedGrade = rank[precisionGrade] > rank[primaryGrade] ? precisionGrade : primaryGrade
|
|
3161
|
+
qe = mergeOpts(qe, { grade: combinedGrade, primaryGrade: primaryGrade, precisionPass: { reviewer: qe2Spec, grade: precisionGrade, gaps: Array.isArray(qe2.gaps) ? qe2.gaps : [] } })
|
|
3162
|
+
modelsUsed.qe2 = modelLabel(qe2Model)
|
|
3163
|
+
} else {
|
|
3164
|
+
log('QE precision pass: report append NOT WITNESSED or grade invalid — primary verdict remains authoritative')
|
|
3165
|
+
modelsUsed.qe2 = modelLabel(qe2Model) + ' (report-unverified)'
|
|
3166
|
+
}
|
|
3167
|
+
} else {
|
|
3168
|
+
log('QE precision pass: Opus returned null — primary QE verdict remains authoritative; qe2 recorded unavailable')
|
|
3169
|
+
}
|
|
3170
|
+
}
|
|
3014
3171
|
if (qe === null) return null
|
|
3015
|
-
return { qe: qe, qeReviewerUsed: qeReviewerUsed, modelUsed: modelsUsed.qe }
|
|
3172
|
+
return { qe: qe, qeReviewerUsed: qeReviewerUsed, modelUsed: modelsUsed.qe, qe2: qe2, qe2ModelUsed: modelsUsed.qe2 || null }
|
|
3016
3173
|
}, { validate: function (r) { return !!(r && typeof r === 'object' && r.qe && typeof r.qe === 'object' && typeof r.qeReviewerUsed === 'string') } })
|
|
3017
3174
|
qe = qeStage ? qeStage.qe : null
|
|
3018
3175
|
let qeReviewerUsed = qeStage ? qeStage.qeReviewerUsed : 'claude'
|
|
3019
3176
|
if (qeStage && qeStage.modelUsed) modelsUsed.qe = qeStage.modelUsed + (resumedStages.indexOf('qe') !== -1 ? ' (resumed)' : '')
|
|
3177
|
+
if (qeStage && qeStage.qe2ModelUsed) modelsUsed.qe2 = qeStage.qe2ModelUsed + (resumedStages.indexOf('qe') !== -1 ? ' (resumed)' : '')
|
|
3020
3178
|
|
|
3021
3179
|
// Step 8 claim-gate: fold the QE agent's reported claim-check counts into an additive result field.
|
|
3022
3180
|
const claimGate = step8ClaimGate(qe && qe.claimCheck ? qe.claimCheck : null)
|
|
@@ -3135,7 +3293,7 @@ if (isLplus) {
|
|
|
3135
3293
|
() => agent(fleetTracePrompt, fleetTraceOpts),
|
|
3136
3294
|
() => agent(fleetCovPrompt, fleetCovOpts),
|
|
3137
3295
|
]
|
|
3138
|
-
const fleetHash = ckptHash('fleet', [fnv1a64(JSON.stringify(qeStage === undefined ? null : qeStage)), tier, MODELS.fleet === undefined ? null : MODELS.fleet, CODEX_MODEL, usageOverride])
|
|
3296
|
+
const fleetHash = ckptHash('fleet', [fnv1a64(JSON.stringify(qeStage === undefined ? null : qeStage)), tier, MODELS.fleet === undefined ? null : MODELS.fleet, CODEX_MODEL, PRIMARY, BUDGET_MODE, usageOverride])
|
|
3139
3297
|
fleet = await withCheckpoint('fleet', 'FleetQE', fleetHash, async () => {
|
|
3140
3298
|
const fleetRuns = await parallel(fleetThunks)
|
|
3141
3299
|
return fleetRuns.every(function (x) { return x !== null && x !== undefined }) ? 'run' : null
|