@dzhechkov/skills-feature-adr 1.5.6 → 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 +224 -62
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,32 +1486,40 @@ 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
|
-
|
|
1401
|
-
|
|
1517
|
+
// Widened 2026-08-28 (MEASURED — see feature-adr-routing.ts for the reproducer): slop-lint's coder
|
|
1518
|
+
// job was confirmed STILL RUNNING at 16m38s while the old 120s window had already declared
|
|
1519
|
+
// "genuinely not landed" to Step-8 QE, producing a grade-D report about an absence that was never
|
|
1520
|
+
// real.
|
|
1521
|
+
const DEFAULT_CODE_LANDING_MAX_WAIT_MS = 1020000
|
|
1522
|
+
const DEFAULT_CODE_LANDING_BACKOFF_MS = [1000, 2000, 2000, 5000, 5000, 10000, 10000, 15000, 20000, 25000, 25000, 30000, 30000, 60000, 60000, 60000, 90000, 90000, 120000, 120000, 120000, 120000]
|
|
1402
1523
|
const CODE_LANDED_BARRIER_SLEEPS_SECONDS = DEFAULT_CODE_LANDING_BACKOFF_MS.map((ms) => ms / 1000)
|
|
1403
1524
|
const CODE_LANDING_PIPELINE_PREFIXES = ['features/', '.dz/', '.agentic-qe/', 'roam/']
|
|
1404
1525
|
|
|
@@ -2049,7 +2170,7 @@ async function designStage(promptText, opts, artifactPath, baseLabel) {
|
|
|
2049
2170
|
if (!needsLandedBarrier(opts)) return await agent(promptText, opts)
|
|
2050
2171
|
const codexOpts = {}
|
|
2051
2172
|
for (const k in opts) if (k !== 'schema') codexOpts[k] = opts[k]
|
|
2052
|
-
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)
|
|
2053
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' })
|
|
2054
2175
|
if (res && probe && /landed=/.test(String(probe))) return { wrote: [artifactPath], summary: String(res).slice(0, 300) }
|
|
2055
2176
|
log('design artifact did not land on codex (' + artifactPath + ') — falling back to Claude')
|
|
@@ -2134,7 +2255,7 @@ modelsUsed.router = modelLabel(routerOpts)
|
|
|
2134
2255
|
// router checkpoint: its result (tier + recalled-pattern rationale) seeds every downstream hash.
|
|
2135
2256
|
// The Step-0 RECALL is part of the stage — a resumed router restores the SAME recalled patterns the
|
|
2136
2257
|
// original run applied (fresh lessons taught since then enter on the next live run, not mid-resume).
|
|
2137
|
-
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])
|
|
2138
2259
|
const router = await withCheckpoint('router', 'Router', routerHash, async () => agent(routerPrompt + codexEffortHint(routerOpts), routerOpts))
|
|
2139
2260
|
if (resumedStages.indexOf('router') !== -1) modelsUsed.router = modelsUsed.router + ' (resumed)'
|
|
2140
2261
|
let tier = A.tier || (router ? router.tier : 'M')
|
|
@@ -2279,7 +2400,7 @@ if (isMplus) {
|
|
|
2279
2400
|
// patterns, the project-skills manifest state, the usage override, and CODEX_MODEL (a bare 'codex'
|
|
2280
2401
|
// model spec resolves through it). The artifact probe requires EVERY tier-active design artifact
|
|
2281
2402
|
// (Codex QE #2 — a one-file probe accepted a design missing its ADR/architecture).
|
|
2282
|
-
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])
|
|
2283
2404
|
const designArtifacts = ['01_requirements.md']
|
|
2284
2405
|
if (isMplus) designArtifacts.push('03_adr/', '03.5_ideation_report.md', '05_architecture.md')
|
|
2285
2406
|
if (isLplus) designArtifacts.push('02_research.md', '04_domain_model.md')
|
|
@@ -2437,7 +2558,7 @@ const planIsCodex = (planModel.agentType === 'codex:codex-rescue') || (MODELS.pl
|
|
|
2437
2558
|
// plan checkpoint: keyed on the design fan's RESULT (a stale design invalidates the plan) + the
|
|
2438
2559
|
// planner spec. Covers the standard L/XL two-phase flow: the stop-after-plan re-invoke resumes
|
|
2439
2560
|
// router+design+plan instead of re-running them.
|
|
2440
|
-
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])
|
|
2441
2562
|
let plan = await withCheckpoint('plan', 'Plan', planHash, async () => {
|
|
2442
2563
|
let plan = null
|
|
2443
2564
|
if (planIsCodex) {
|
|
@@ -2649,7 +2770,7 @@ const codePrompt = 'Step 7 (Code) of /feature-adr for "' + DESC + '" (' + SLUG +
|
|
|
2649
2770
|
// code:null sentinel → resolveCoderSpec(). A Claude resolution merges {model} onto the Claude branch;
|
|
2650
2771
|
// under the BC omit-path it is {} (byte-identical).
|
|
2651
2772
|
const codeModel = resolveStageModel('code')
|
|
2652
|
-
const codeIsCodexFirst = (
|
|
2773
|
+
const codeIsCodexFirst = (codeModel.agentType === 'codex:codex-rescue') && (MODELS.code !== undefined || CODER !== 'codex-fallback')
|
|
2653
2774
|
const codeClaudeModel = codeIsCodexFirst ? {} : (codeModel.agentType ? {} : codeModel)
|
|
2654
2775
|
const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), phase: 'Code', schema: ARTIFACT, effort: 'high' }, codeClaudeModel)
|
|
2655
2776
|
// code checkpoint: COMPOSITE — the branchy claude/codex/fallback block sets three interdependent
|
|
@@ -2658,7 +2779,7 @@ const codeClaudeOpts = mergeOpts({ label: stageLabel('code', codeClaudeModel), p
|
|
|
2658
2779
|
// the checkpoint (it only feeds the expected-targets parse, already consumed by the original run).
|
|
2659
2780
|
// R6: the landing token is salted into the code stage's PARTS (not CKPT_SCHEMA_VERSION, which
|
|
2660
2781
|
// stays 'fa-ckpt-2' deliberately) so ONLY this stage's pre-protocol checkpoints hash stale.
|
|
2661
|
-
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])
|
|
2662
2783
|
const codeStage = await withCheckpoint('code', 'Code', codeHash, async () => {
|
|
2663
2784
|
let code = null
|
|
2664
2785
|
let coderUsed = 'claude'
|
|
@@ -2814,13 +2935,16 @@ const wqNote = writerQuiescence.verdict === 'quiet'
|
|
|
2814
2935
|
? ' WRITER-QUIESCENCE: quiet (' + writerQuiescence.note + ').'
|
|
2815
2936
|
: ' WRITER-QUIESCENCE GATE (MANDATORY to acknowledge): ' + writerQuiescence.note + ' State this standing explicitly in 08_qe_report.md next to the grade.'
|
|
2816
2937
|
await usageProbe('QE')
|
|
2817
|
-
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')
|
|
2818
2939
|
// CROSS-MODEL QE (load-bearing): resolveStageModel('qe') derives the OTHER family than the resolved
|
|
2819
2940
|
// coder when args.models.qe is unset (coder-codex ⇒ opus; coder-Claude ⇒ codex, or opus if codex absent).
|
|
2820
2941
|
// An explicit args.models.qe wins. A Claude qe spec is merged onto the qe-code-reviewer base (role
|
|
2821
2942
|
// PRESERVED); a codex qe spec REPLACES agentType with codex:codex-rescue (as today). The codex-null→
|
|
2822
2943
|
// Claude guard is retained as the runtime belt so codex-unavailable never blocks.
|
|
2823
|
-
|
|
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'))
|
|
2824
2948
|
// Single tested source of truth (feature-adr-routing.ts:qeShouldUseCodex) — closes the self-QE hole where
|
|
2825
2949
|
// the legacy qeReviewer='codex' knob used to re-route QE back to codex even when the CODER was codex.
|
|
2826
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)')
|
|
@@ -2828,15 +2952,16 @@ if (MODELS.qe === undefined && QE_REVIEWER === 'codex' && coderIsCodex()) log('Q
|
|
|
2828
2952
|
// (which reads the pre-override config) says claude — dispatching that through the "claude" branch
|
|
2829
2953
|
// would run the stub wrapper AND mislabel qeReviewerUsed='claude' (a codex-on-codex review recorded
|
|
2830
2954
|
// as cross-family). The resolved agentType is the truth; either signal routes the codex branch.
|
|
2831
|
-
const qeIsCodex =
|
|
2955
|
+
const qeIsCodex = !!(qeModel && qeModel.agentType === 'codex:codex-rescue')
|
|
2832
2956
|
const qeClaudeModel = qeIsCodex ? {} : qeModel
|
|
2833
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)
|
|
2834
2959
|
// qe checkpoint: COMPOSITE (verdict + reviewer identity) keyed on the CODE stage's result — a re-coded
|
|
2835
2960
|
// feature always re-QEs. The teach/fa-record side effects belong to the stage: a resumed QE does not
|
|
2836
2961
|
// re-teach (the original run already stored its lessons — replaying teach would double-store).
|
|
2837
2962
|
// R6: the review SCOPE is part of what a QE verdict is about, so it enters the hash — a resume must
|
|
2838
2963
|
// not present a verdict obtained over one scope as if it had been obtained over another.
|
|
2839
|
-
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])
|
|
2840
2965
|
let crossFamilyQeReport = null
|
|
2841
2966
|
const qeStage = await withCheckpoint('qe', 'QE', qeHash, async () => {
|
|
2842
2967
|
let qe = null
|
|
@@ -2894,7 +3019,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2894
3019
|
}
|
|
2895
3020
|
}
|
|
2896
3021
|
}
|
|
2897
|
-
let codexQe = await codexReviewAgent('qe', QE_SCOPE, QE_SCOPE_REF, 'QE')
|
|
3022
|
+
let codexQe = await codexReviewAgent('qe', QE_SCOPE, QE_SCOPE_REF, 'QE', qeCodexLabelOpts)
|
|
2898
3023
|
// SCOPE THE VERDICT to this feature. Findings about other dirty work are real and are kept, but they
|
|
2899
3024
|
// may not decide THIS feature's grade. When the change set is unmeasured the partition is 'unscoped'
|
|
2900
3025
|
// and the grade stands exactly as the reviewer gave it — attributing nothing is the honest move.
|
|
@@ -2933,7 +3058,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2933
3058
|
// mode-B bookkeeping note would hide why the independent review did not happen.
|
|
2934
3059
|
log('QE: mode B not constructible — no declared changed files to scope it to; keeping mode A reason')
|
|
2935
3060
|
} else {
|
|
2936
|
-
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)
|
|
2937
3062
|
if (codexQe === null) lastCodexDecline = 'mode A: ' + String(modeADecline) + ' | mode B: ' + String(lastCodexDecline)
|
|
2938
3063
|
}
|
|
2939
3064
|
}
|
|
@@ -2955,7 +3080,7 @@ if (qe === null && (qeIsCodex || QE_REVIEWER === 'codex-fallback')) {
|
|
|
2955
3080
|
// with no 08_qe_report.md at all. Named by cross-family review of b6973199. The verdict itself
|
|
2956
3081
|
// is real (Codex produced it), so a failed transcription DEGRADES the run rather than voiding
|
|
2957
3082
|
// it — but it must be visible, and it must never read as a clean QE.
|
|
2958
|
-
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
|
|
2959
3084
|
// WITNESS THE REWRITE, not the existence. On a re-QE or a resume with the same slug an OLD
|
|
2960
3085
|
// 08_qe_report.md is already sitting there, and an existence probe reports that stale file as
|
|
2961
3086
|
// landed — so a scribe that wrote nothing still marked the new verdict recorded, and the stage
|
|
@@ -3007,12 +3132,49 @@ if (qe === null && qeIsCodex) {
|
|
|
3007
3132
|
crossFamilyQeReport = cfBelt.report
|
|
3008
3133
|
}
|
|
3009
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
|
+
}
|
|
3010
3171
|
if (qe === null) return null
|
|
3011
|
-
return { qe: qe, qeReviewerUsed: qeReviewerUsed, modelUsed: modelsUsed.qe }
|
|
3172
|
+
return { qe: qe, qeReviewerUsed: qeReviewerUsed, modelUsed: modelsUsed.qe, qe2: qe2, qe2ModelUsed: modelsUsed.qe2 || null }
|
|
3012
3173
|
}, { validate: function (r) { return !!(r && typeof r === 'object' && r.qe && typeof r.qe === 'object' && typeof r.qeReviewerUsed === 'string') } })
|
|
3013
3174
|
qe = qeStage ? qeStage.qe : null
|
|
3014
3175
|
let qeReviewerUsed = qeStage ? qeStage.qeReviewerUsed : 'claude'
|
|
3015
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)' : '')
|
|
3016
3178
|
|
|
3017
3179
|
// Step 8 claim-gate: fold the QE agent's reported claim-check counts into an additive result field.
|
|
3018
3180
|
const claimGate = step8ClaimGate(qe && qe.claimCheck ? qe.claimCheck : null)
|
|
@@ -3131,7 +3293,7 @@ if (isLplus) {
|
|
|
3131
3293
|
() => agent(fleetTracePrompt, fleetTraceOpts),
|
|
3132
3294
|
() => agent(fleetCovPrompt, fleetCovOpts),
|
|
3133
3295
|
]
|
|
3134
|
-
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])
|
|
3135
3297
|
fleet = await withCheckpoint('fleet', 'FleetQE', fleetHash, async () => {
|
|
3136
3298
|
const fleetRuns = await parallel(fleetThunks)
|
|
3137
3299
|
return fleetRuns.every(function (x) { return x !== null && x !== undefined }) ? 'run' : null
|