@dzhechkov/harness-core 0.8.5 → 0.8.10

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.
Files changed (134) hide show
  1. package/.dz-manifest.json +286 -82
  2. package/README.md +54 -2
  3. package/dist/amendment-trace.d.ts.map +1 -1
  4. package/dist/amendment-trace.js +12 -1
  5. package/dist/amendment-trace.js.map +1 -1
  6. package/dist/compounding.d.ts +54 -0
  7. package/dist/compounding.d.ts.map +1 -1
  8. package/dist/compounding.js +221 -1
  9. package/dist/compounding.js.map +1 -1
  10. package/dist/eta.d.ts +92 -0
  11. package/dist/eta.d.ts.map +1 -0
  12. package/dist/eta.js +488 -0
  13. package/dist/eta.js.map +1 -0
  14. package/dist/feature-adr-checkpoints.d.ts +11 -2
  15. package/dist/feature-adr-checkpoints.d.ts.map +1 -1
  16. package/dist/feature-adr-checkpoints.js +38 -3
  17. package/dist/feature-adr-checkpoints.js.map +1 -1
  18. package/dist/feature-adr-decision-recall.d.ts +167 -0
  19. package/dist/feature-adr-decision-recall.d.ts.map +1 -0
  20. package/dist/feature-adr-decision-recall.js +519 -0
  21. package/dist/feature-adr-decision-recall.js.map +1 -0
  22. package/dist/feature-adr-landing.d.ts +37 -0
  23. package/dist/feature-adr-landing.d.ts.map +1 -0
  24. package/dist/feature-adr-landing.js +59 -0
  25. package/dist/feature-adr-landing.js.map +1 -0
  26. package/dist/feature-adr-routing.d.ts +58 -23
  27. package/dist/feature-adr-routing.d.ts.map +1 -1
  28. package/dist/feature-adr-routing.js +194 -69
  29. package/dist/feature-adr-routing.js.map +1 -1
  30. package/dist/guard-promotion.d.ts +41 -0
  31. package/dist/guard-promotion.d.ts.map +1 -1
  32. package/dist/guard-promotion.js +218 -4
  33. package/dist/guard-promotion.js.map +1 -1
  34. package/dist/guard-volume.d.ts +108 -0
  35. package/dist/guard-volume.d.ts.map +1 -0
  36. package/dist/guard-volume.js +536 -0
  37. package/dist/guard-volume.js.map +1 -0
  38. package/dist/guard.d.ts +34 -0
  39. package/dist/guard.d.ts.map +1 -1
  40. package/dist/guard.js +113 -4
  41. package/dist/guard.js.map +1 -1
  42. package/dist/index.d.ts +18 -4
  43. package/dist/index.d.ts.map +1 -1
  44. package/dist/index.js +12 -2
  45. package/dist/index.js.map +1 -1
  46. package/dist/integration-apply.d.ts +25 -0
  47. package/dist/integration-apply.d.ts.map +1 -0
  48. package/dist/integration-apply.js +299 -0
  49. package/dist/integration-apply.js.map +1 -0
  50. package/dist/integration-evidence.d.ts +46 -0
  51. package/dist/integration-evidence.d.ts.map +1 -0
  52. package/dist/integration-evidence.js +44 -0
  53. package/dist/integration-evidence.js.map +1 -0
  54. package/dist/integration-probe-worker.d.ts +22 -0
  55. package/dist/integration-probe-worker.d.ts.map +1 -0
  56. package/dist/integration-probe-worker.js +334 -0
  57. package/dist/integration-probe-worker.js.map +1 -0
  58. package/dist/integrations-verify.d.ts +60 -0
  59. package/dist/integrations-verify.d.ts.map +1 -0
  60. package/dist/integrations-verify.js +194 -0
  61. package/dist/integrations-verify.js.map +1 -0
  62. package/dist/lesson-generalization.d.ts +29 -0
  63. package/dist/lesson-generalization.d.ts.map +1 -0
  64. package/dist/lesson-generalization.js +84 -0
  65. package/dist/lesson-generalization.js.map +1 -0
  66. package/dist/loop-blobs.generated.js +8 -8
  67. package/dist/loop-blobs.generated.js.map +1 -1
  68. package/dist/loop-plan.d.ts +13 -1
  69. package/dist/loop-plan.d.ts.map +1 -1
  70. package/dist/loop-plan.js +15 -1
  71. package/dist/loop-plan.js.map +1 -1
  72. package/dist/loop-render.d.ts.map +1 -1
  73. package/dist/loop-render.js +51 -6
  74. package/dist/loop-render.js.map +1 -1
  75. package/dist/loop-trace.d.ts +20 -1
  76. package/dist/loop-trace.d.ts.map +1 -1
  77. package/dist/loop-trace.js +83 -1
  78. package/dist/loop-trace.js.map +1 -1
  79. package/dist/model-recommender.d.ts +8 -0
  80. package/dist/model-recommender.d.ts.map +1 -1
  81. package/dist/model-recommender.js +31 -4
  82. package/dist/model-recommender.js.map +1 -1
  83. package/dist/operations.d.ts +27 -0
  84. package/dist/operations.d.ts.map +1 -1
  85. package/dist/operations.js +178 -2
  86. package/dist/operations.js.map +1 -1
  87. package/dist/patterns.d.ts +27 -1
  88. package/dist/patterns.d.ts.map +1 -1
  89. package/dist/patterns.js +211 -45
  90. package/dist/patterns.js.map +1 -1
  91. package/dist/setup.d.ts.map +1 -1
  92. package/dist/setup.js +20 -17
  93. package/dist/setup.js.map +1 -1
  94. package/dist/target-integrations.d.ts +65 -0
  95. package/dist/target-integrations.d.ts.map +1 -0
  96. package/dist/target-integrations.js +152 -0
  97. package/dist/target-integrations.js.map +1 -0
  98. package/dist/vector-tier.d.ts +6 -1
  99. package/dist/vector-tier.d.ts.map +1 -1
  100. package/dist/vector-tier.js +32 -7
  101. package/dist/vector-tier.js.map +1 -1
  102. package/dist/workflow-run.d.ts.map +1 -1
  103. package/dist/workflow-run.js +18 -12
  104. package/dist/workflow-run.js.map +1 -1
  105. package/package.json +19 -15
  106. package/sbom.json +591 -81
  107. package/src/amendment-trace.ts +12 -1
  108. package/src/compounding.ts +300 -1
  109. package/src/eta.ts +590 -0
  110. package/src/feature-adr-checkpoints.ts +39 -3
  111. package/src/feature-adr-decision-recall.ts +652 -0
  112. package/src/feature-adr-landing.ts +109 -0
  113. package/src/feature-adr-routing.ts +224 -65
  114. package/src/guard-promotion.ts +245 -4
  115. package/src/guard-volume.ts +752 -0
  116. package/src/guard.ts +148 -4
  117. package/src/index.ts +65 -3
  118. package/src/integration-apply.ts +332 -0
  119. package/src/integration-evidence.ts +89 -0
  120. package/src/integration-probe-worker.ts +310 -0
  121. package/src/integration-receipts/claude-code/mcp/2.1.235.json +35 -0
  122. package/src/integrations-verify.ts +258 -0
  123. package/src/lesson-generalization.ts +115 -0
  124. package/src/loop-blobs.generated.ts +8 -8
  125. package/src/loop-plan.ts +36 -3
  126. package/src/loop-render.ts +50 -6
  127. package/src/loop-trace.ts +91 -2
  128. package/src/model-recommender.ts +35 -4
  129. package/src/operations.ts +199 -2
  130. package/src/patterns.ts +252 -43
  131. package/src/setup.ts +20 -17
  132. package/src/target-integrations.ts +225 -0
  133. package/src/vector-tier.ts +44 -14
  134. package/src/workflow-run.ts +18 -11
@@ -14,8 +14,8 @@
14
14
  *
15
15
  * The load-bearing property: a model that WRITES code must not also SELF-QE.
16
16
  * When `args.models.qe` is unset, the QE stage is auto-routed to the OTHER
17
- * family than the resolved coder (codex-coder → Claude `opus`; Claude-coder →
18
- * `codex:<top>:high`, or `opus` if codex is unavailable — never a block).
17
+ * family than the resolved coder (codex-coder → Claude `sonnet`; Claude-coder →
18
+ * the Codex-budget cell, or `opus` if codex is unavailable — never a block).
19
19
  *
20
20
  * DESIGN CONSTRAINT — the Workflow parser is STRICTER than `node --check`
21
21
  * (no nested template literals, no inline `cond ? agent() : null` in arrays).
@@ -29,40 +29,27 @@
29
29
  /** Blob version stamp read by scripts/gen-loop-blobs.mjs (feature loop-designer, ADR-004) — the
30
30
  * ONLY loop-designer change to this canonical file; bump when any blob-exported semantic changes. */
31
31
  export const BLOB_VERSION = '1.0.0';
32
- /**
33
- * Per-stage reasoning applied under the usage override (LOCKED L-4, arch §4.1 verbatim):
34
- * design/code/plan stages ⇒ `xhigh`; router/qe/fleet ⇒ `high`. A pure DATA table (not control
35
- * flow); user-overridable via `env.usageReasoning`. An unknown stage falls back to `'high'`.
36
- */
37
- export const OVERRIDE_REASONING = {
38
- router: 'high',
39
- requirements: 'xhigh',
40
- research: 'xhigh',
41
- adr: 'xhigh',
42
- ideation: 'xhigh',
43
- ddd: 'xhigh',
44
- architecture: 'xhigh',
45
- plan: 'xhigh',
46
- code: 'xhigh',
47
- qe: 'high',
48
- fleet: 'high',
49
- };
50
- /**
51
- * The gpt-5.6-ready TOP codex-id pick — SHARED by the cross-model QE default and the usage
52
- * override so adding an id to {@link KNOWN_CODEX} (e.g. `gpt-5.7`) retargets BOTH with zero
53
- * control-flow diff (AC-3). Pinned `CODEX_MODEL` when ≠ `'auto'`; else the last non-`auto` key of
54
- * `KNOWN_CODEX`.
55
- */
32
+ export const STAGE_EFFORT = { override: {
33
+ router: 'medium',
34
+ requirements: 'medium',
35
+ research: 'medium',
36
+ adr: 'high',
37
+ ideation: 'medium',
38
+ ddd: 'high',
39
+ architecture: 'high',
40
+ plan: 'high',
41
+ code: 'medium',
42
+ qe: 'high',
43
+ fleet: 'medium',
44
+ } };
45
+ /** One-release compatibility alias for existing public imports. */
46
+ // Frozen (QE round-1 F10): the alias exports the SAME object as the live effort table, so a
47
+ // consumer mutating the compatibility alias would silently mutate shipped routing. Freezing keeps
48
+ // the read-compatibility promise while making any such write throw loudly in strict mode.
49
+ export const OVERRIDE_REASONING = Object.freeze(STAGE_EFFORT.override);
50
+ /** The flagship Codex id, independent of the spellability allowlist's insertion order. */
56
51
  export function topCodexId(env) {
57
- let top = env.CODEX_MODEL;
58
- if (top === 'auto') {
59
- const ids = Object.keys(KNOWN_CODEX);
60
- for (let i = 0; i < ids.length; i++) {
61
- if (ids[i] !== 'auto')
62
- top = ids[i] || top;
63
- }
64
- }
65
- return top;
52
+ return env.CODEX_MODEL !== 'auto' ? env.CODEX_MODEL : CODEX_TIERS.flagship;
66
53
  }
67
54
  /**
68
55
  * The PURE hysteresis core (the load-bearing safety property, AC-1). Given the previous override
@@ -104,10 +91,69 @@ export function decideUsageAction(prevOverride, signal, threshold) {
104
91
  // ── Data tables (data-only extensibility — gpt-5.6-ready) ───────────────────
105
92
  /** Known codex ids. Adding a new id (e.g. `'gpt-5.7'`) is a DATA-ONLY change. */
106
93
  export 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 };
94
+ /**
95
+ * Capability tiers are routing data, separate from {@link KNOWN_CODEX}'s spellability role.
96
+ * The allowlist is not an availability check — probe every id before every run
97
+ * (`codex exec -m <id> "Reply with exactly: OK"`); ids drift in both directions on a
98
+ * timescale of days (probed 2026-08-18: 3 ids HTTP 400; probed 2026-08-29: all 3 exit 0).
99
+ */
100
+ export const CODEX_TIERS = {
101
+ flagship: 'gpt-5.6-sol',
102
+ workhorse: 'gpt-5.6-terra',
103
+ 'high-volume': 'gpt-5.6-luna',
104
+ };
105
+ export function codexIdForTier(tier, env) {
106
+ return env.CODEX_MODEL !== 'auto' ? env.CODEX_MODEL : CODEX_TIERS[tier];
107
+ }
108
+ export const BUDGET_PRESETS = {
109
+ normal: { claude: 'normal', codex: 'normal' },
110
+ eco: { claude: 'eco', codex: 'eco' },
111
+ hybrid: { claude: 'eco', codex: 'normal' },
112
+ };
113
+ export function resolveBudgetMode(raw) {
114
+ if (raw === undefined)
115
+ return BUDGET_PRESETS.normal;
116
+ if (typeof raw === 'string') {
117
+ const preset = BUDGET_PRESETS[raw];
118
+ if (!preset)
119
+ throw new RangeError('budget: unknown preset "' + raw + '" — valid: normal|eco|hybrid');
120
+ return preset;
121
+ }
122
+ if (raw && typeof raw === 'object') {
123
+ const value = raw;
124
+ for (const key of Object.keys(value)) {
125
+ if (key !== 'claude' && key !== 'codex') {
126
+ throw new RangeError('budget: unknown family key "' + key + '" — valid: claude|codex');
127
+ }
128
+ }
129
+ for (const key of ['claude', 'codex']) {
130
+ const level = value[key];
131
+ if (level !== undefined && level !== 'normal' && level !== 'eco') {
132
+ throw new RangeError('budget.' + key + ': unknown level "' + level + '" — valid: normal|eco');
133
+ }
134
+ }
135
+ return {
136
+ claude: value.claude || 'normal',
137
+ codex: value.codex || 'normal',
138
+ };
139
+ }
140
+ throw new RangeError('budget: expected a preset name or {claude,codex} object, got ' + typeof raw);
141
+ }
142
+ export function budgetPresetName(axis) {
143
+ for (const name of ['normal', 'eco', 'hybrid']) {
144
+ const preset = BUDGET_PRESETS[name];
145
+ if (preset.claude === axis.claude && preset.codex === axis.codex)
146
+ return name;
147
+ }
148
+ return null;
149
+ }
107
150
  /** The Claude model names the Workflow runtime accepts as `agent()` `model`. */
108
151
  export const CLAUDE_NAMES = { fable: 1, opus: 1, sonnet: 1, haiku: 1 };
109
- /** The codex-companion `--effort` vocabulary; a spec's `<reasoning>` is clamped to this (unknown ⇒ 'high'). */
110
- export const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1 };
152
+ /**
153
+ * The codex-companion `--effort` vocabulary. `minimal` remains temporarily for compatibility
154
+ * until a live companion probe establishes whether it can be retired; `max` is part of GPT-5.6's ladder.
155
+ */
156
+ export const VALID_REASONING = { none: 1, minimal: 1, low: 1, medium: 1, high: 1, xhigh: 1, max: 1 };
111
157
  /**
112
158
  * The proven DEFAULT TABLE, applied only when the user opts into routing.
113
159
  * `code`/`qe` are `null` SENTINELS: their defaults are DERIVED (the coder knob /
@@ -126,6 +172,68 @@ export const DEFAULT_MODELS = {
126
172
  qe: null,
127
173
  fleet: 'sonnet',
128
174
  };
175
+ /** Four family half-tables; presets compose the Claude and Codex halves independently. */
176
+ export const ROUTING_TABLES = {
177
+ claude: {
178
+ claude: {
179
+ normal: { router: 'sonnet', requirements: 'sonnet', research: 'sonnet', adr: 'fable', ideation: 'sonnet', ddd: 'fable', architecture: 'fable', plan: 'opus', code: 'sonnet', fleet: 'sonnet' },
180
+ eco: { router: 'sonnet', requirements: 'sonnet', research: 'sonnet', adr: 'opus', ideation: 'sonnet', ddd: 'opus', architecture: 'opus', plan: 'sonnet', code: 'sonnet', fleet: 'sonnet' },
181
+ },
182
+ codex: { normal: {}, eco: {} },
183
+ },
184
+ codex: {
185
+ claude: {
186
+ normal: { router: 'sonnet', qe: 'sonnet', fleet: 'sonnet' },
187
+ eco: { router: 'sonnet', qe: 'sonnet', fleet: 'sonnet' },
188
+ },
189
+ codex: { normal: {}, eco: {} },
190
+ },
191
+ };
192
+ function codexCell(tier, effort, env) {
193
+ return 'codex:' + codexIdForTier(tier, env) + ':' + effort;
194
+ }
195
+ export function budgetTable(primary, mode, env) {
196
+ const claudeHalf = ROUTING_TABLES[primary].claude[mode.claude];
197
+ let codexHalf;
198
+ if (primary === 'claude') {
199
+ const qeSpec = mode.codex === 'normal'
200
+ ? codexCell('flagship', 'high', env)
201
+ : codexCell('workhorse', 'medium', env);
202
+ codexHalf = { ...ROUTING_TABLES.claude.codex[mode.codex], qe: env.codexAvailable === false ? 'opus' : qeSpec };
203
+ }
204
+ else {
205
+ const normal = mode.codex === 'normal';
206
+ const design = codexCell(normal ? 'flagship' : 'workhorse', normal ? 'high' : 'medium', env);
207
+ codexHalf = {
208
+ requirements: design,
209
+ research: design,
210
+ adr: design,
211
+ ideation: design,
212
+ ddd: design,
213
+ architecture: design,
214
+ plan: codexCell(normal ? 'flagship' : 'workhorse', normal ? 'high' : 'low', env),
215
+ code: codexCell(normal ? 'flagship' : 'workhorse', 'medium', env),
216
+ };
217
+ }
218
+ return { ...claudeHalf, ...codexHalf };
219
+ }
220
+ /** Optional Claude precision review after A-normal's recall-oriented Sonnet QE. */
221
+ export function qePrecisionPassSpec(primary, budget, tier, _env) {
222
+ if (primary !== 'codex')
223
+ return null;
224
+ if (budget.claude !== 'normal')
225
+ return null;
226
+ if (tier !== 'L' && tier !== 'XL')
227
+ return null;
228
+ return 'opus';
229
+ }
230
+ /** Build/test-time guard for the matrix's load-bearing cross-family property. */
231
+ export function assertCrossFamilyQe(codeSpec, qeSpec) {
232
+ const fam = (s) => (s && String(s).split(':')[0] === 'codex') ? 'codex' : 'claude';
233
+ if (fam(codeSpec) === fam(qeSpec)) {
234
+ throw new Error('cross-family QE violated: code=' + codeSpec + ' qe=' + qeSpec);
235
+ }
236
+ }
129
237
  // ── Pure resolvers (byte-equivalent to the workflow's inline block) ──────────
130
238
  /**
131
239
  * Turn a compact model SPEC into `agent()` opts.
@@ -157,11 +265,8 @@ export function specToOpts(spec, env) {
157
265
  id = env.CODEX_MODEL;
158
266
  }
159
267
  let reasoning = parts[2] || 'high';
160
- // Clamp to the codex-companion --effort vocabulary so a bad token (e.g. `codex:gpt-5.6:banana`)
161
- // can't silently reach the runtime as `--effort banana` (QE LOW). Unknown ⇒ 'high'.
162
268
  if (!VALID_REASONING[reasoning]) {
163
- log('models: unknown reasoning ' + reasoning + ' — using high');
164
- reasoning = 'high';
269
+ throw new RangeError('models: invalid reasoning "' + reasoning + '"valid: ' + Object.keys(VALID_REASONING).join('|'));
165
270
  }
166
271
  return { agentType: 'codex:codex-rescue', codexModel: id, _reasoning: reasoning };
167
272
  }
@@ -188,34 +293,43 @@ export function resolveCoderSpec(env) {
188
293
  * `MODELS.code='codex'` still routes QE to Claude (never codex-self-QE).
189
294
  */
190
295
  export function coderIsCodex(env) {
191
- if (env.CODER === 'codex' || env.CODER === 'codex-fallback')
192
- return true;
193
296
  const codeSpec = env.MODELS.code;
194
- if (codeSpec && String(codeSpec).split(':')[0] === 'codex')
297
+ if (codeSpec !== undefined && codeSpec !== null)
298
+ return String(codeSpec).split(':')[0] === 'codex';
299
+ if (env.CODER === 'codex' || env.CODER === 'codex-fallback')
195
300
  return true;
196
- return false;
301
+ return env.primary === 'codex';
197
302
  }
198
303
  /**
199
304
  * The CROSS-MODEL QE default (load-bearing). Called only when `MODELS.qe` is
200
305
  * unset. Resolves to the OTHER family than the coder:
201
- * - coder is codex → `'opus'` (Claude reviews codex's code)
202
- * - coder is Claude → codex-available ? `'codex:<top>:high'` : `'opus'` (never block)
203
- * `<top>` = `CODEX_MODEL` when pinned (≠'auto'), else the top `KNOWN_CODEX` id.
306
+ * - coder is codex → `'sonnet'` (the recall-oriented Claude reviewer)
307
+ * - coder is Claude → the Codex-budget cell (`sol:high` or `terra:medium`),
308
+ * or `'opus'` when Codex is unavailable (never block)
309
+ * A pinned `CODEX_MODEL` still outranks the tier selected by the budget table.
204
310
  */
205
- export function resolveQeSpec(env) {
206
- if (coderIsCodex(env))
207
- return 'opus';
311
+ export function resolveQeSpecForCoder(coderCodex, env) {
312
+ if (coderCodex)
313
+ return 'sonnet';
208
314
  const CODEX_AVAILABLE = env.codexAvailable !== false;
209
315
  if (!CODEX_AVAILABLE)
210
316
  return 'opus';
211
- return 'codex:' + topCodexId(env) + ':high';
317
+ const budget = resolveBudgetMode(env.budget);
318
+ return budget.codex === 'eco'
319
+ ? 'codex:' + codexIdForTier('workhorse', env) + ':medium'
320
+ : 'codex:' + codexIdForTier('flagship', env) + ':high';
321
+ }
322
+ export function resolveQeSpec(env) {
323
+ return resolveQeSpecForCoder(coderIsCodex(env), env);
212
324
  }
213
325
  /**
214
- * Whether the caller opted into routing at all. When FALSE (no `args.models`,
215
- * no codex knobs), every Claude stage resolves to `{}` → byte-identical to today.
326
+ * Whether the caller opted into routing at all. When FALSE (no axes, `args.models`,
327
+ * or Codex knobs), every Claude stage resolves to `{}` → byte-identical to today.
216
328
  */
217
329
  export function routingRequested(env) {
218
330
  return (Object.keys(env.MODELS).length > 0 ||
331
+ env.primary !== undefined ||
332
+ env.budget !== undefined ||
219
333
  env.PLANNER === 'codex' ||
220
334
  env.CODER === 'codex' ||
221
335
  env.CODER === 'codex-fallback' ||
@@ -247,12 +361,12 @@ export function qeShouldUseCodex(env) {
247
361
  * Resolve a stage to its `agent()` opts fragment.
248
362
  * 1. explicit `MODELS[stage]` wins
249
363
  * 2. else if the user did NOT opt into routing → `{}` (byte-identical BC path)
250
- * 3. else the DEFAULT TABLE fills the gap
364
+ * 3. else the primary × per-family-budget table fills the gap
251
365
  * 4. `code`/`qe` `null` sentinels resolve via the coder / cross-model rules
252
366
  */
253
367
  export function resolveStageModel(stage, env) {
254
368
  if (env.usageOverride) {
255
- const r = (env.usageReasoning && env.usageReasoning[stage]) || OVERRIDE_REASONING[stage] || 'high';
369
+ const r = (env.usageReasoning && env.usageReasoning[stage]) || STAGE_EFFORT.override[stage] || 'medium';
256
370
  const o = specToOpts('codex:' + topCodexId(env) + ':' + r, env);
257
371
  o._usageSwitched = true;
258
372
  return o;
@@ -261,7 +375,18 @@ export function resolveStageModel(stage, env) {
261
375
  if (spec === undefined) {
262
376
  if (!routingRequested(env))
263
377
  return {};
264
- spec = DEFAULT_MODELS[stage];
378
+ if (stage === 'code' && (env.CODER === 'codex' || env.CODER === 'codex-fallback')) {
379
+ return specToOpts(resolveCoderSpec(env), env);
380
+ }
381
+ if (stage === 'plan' && env.PLANNER === 'codex') {
382
+ return specToOpts('codex:' + env.CODEX_MODEL + ':high', env);
383
+ }
384
+ if (stage === 'qe') {
385
+ return specToOpts(resolveQeSpec(env), env);
386
+ }
387
+ const resolvedPrimary = env.primary || 'claude';
388
+ const cell = budgetTable(resolvedPrimary, resolveBudgetMode(env.budget), env)[stage];
389
+ spec = cell !== undefined ? cell : DEFAULT_MODELS[stage];
265
390
  }
266
391
  if (stage === 'code' && (spec === null || spec === undefined))
267
392
  return specToOpts(resolveCoderSpec(env), env);
@@ -297,15 +422,11 @@ export function codexEffortHint(opts) {
297
422
  return '';
298
423
  }
299
424
  // ── Step-7.5 CODE landed barrier (Codex out-of-band write flush) ────────────
300
- // Widened 2026-08-28 (MEASURED, not a guess): the dz-deadwood coder job ran 46m11s end to end, and
301
- // the slop-lint coder was independently confirmed STILL RUNNING (codex-companion status "running",
302
- // pid alive, mid-TDD-cycle, actively applying file changes) at 16m38s elapsed a full 8x past the
303
- // old 120s window. The old window declared "genuinely not landed" and fed Step-8 QE a false empty
304
- // tree, producing a grade-D report entirely about an absence that was never real. This does not fix
305
- // the underlying conflation of TIMEOUT with CONFIRMED-ABSENCE (filed as a backlog item — the barrier
306
- // still cannot see the codex-companion job's own liveness) — it only narrows how often the false
307
- // negative fires for a realistic M-tier build, at the cost of a longer worst-case wait when the
308
- // coder truly produced nothing.
425
+ // Widened 2026-08-28 (MEASURED): the dz-deadwood coder ran 46m11s, and slop-lint was still running
426
+ // with its recorded PID alive at 16m38s when the old 120s window had already declared absence.
427
+ // Changed 2026-08-30 (MEASURED: six false verdicts; plus task-mtfhglwk stayed status=running for
428
+ // 8h51m after recorded PID 3639268 disappeared): this is now one git-evidence/backoff window inside
429
+ // the liveness-driven loop, not the whole barrier. The separate hard ceiling bounds a live worker.
309
430
  export const DEFAULT_CODE_LANDING_MAX_WAIT_MS = 1_020_000;
310
431
  export const DEFAULT_CODE_LANDING_BACKOFF_MS = [
311
432
  1_000,
@@ -335,10 +456,10 @@ export const CODE_LANDED_BARRIER_SLEEPS_SECONDS = DEFAULT_CODE_LANDING_BACKOFF_M
335
456
  const CODE_LANDING_PIPELINE_PREFIXES = ['features/', '.dz/', '.agentic-qe/', 'roam/'];
336
457
  /** Bumped whenever the landing protocol's meaning changes. Recorded on every code-stage result so a
337
458
  * pre-protocol checkpoint reads as no-checkpoint instead of resuming into new semantics. */
338
- export const LANDING_PROTOCOL_VERSION = 2;
459
+ export const LANDING_PROTOCOL_VERSION = 3;
339
460
  /** R6: salted into the code stage's checkpoint hash PARTS (not the schema version — ADR-003 keeps
340
461
  * `CKPT_SCHEMA_VERSION` at 'fa-ckpt-2' deliberately, so ONLY the code stage re-runs). */
341
- export const LANDING_HASH_TOKEN = 'landing-v2';
462
+ export const LANDING_HASH_TOKEN = 'landing-v3';
342
463
  /**
343
464
  * The CLOSED landing-predicate vocabulary, as DATA (QE F6) — exhaustive-record derived, so `tsc`
344
465
  * fails the build if the union gains or loses a member. `'expected-path'` and `'any-code-change'`
@@ -1225,12 +1346,16 @@ export function codexExecCommand(input) {
1225
1346
  const modelId = String(o.modelId === undefined || o.modelId === null ? '' : o.modelId);
1226
1347
  if (!isSafeCodexId(modelId))
1227
1348
  return null;
1349
+ const reasoning = o.reasoning === undefined || o.reasoning === null || o.reasoning === '' ? 'high' : String(o.reasoning);
1350
+ if (!VALID_REASONING[reasoning])
1351
+ return null;
1228
1352
  const raw = Number(o.timeoutSeconds);
1229
1353
  const seconds = raw === raw && raw !== Infinity && raw > 0 ? Math.floor(raw) : CODEX_EXEC_TIMEOUT_SECONDS;
1230
1354
  const repo = String(o.repo === undefined || o.repo === null ? '' : o.repo);
1231
1355
  const cd = repo === '' ? '' : ' -C ' + codexSq(repo);
1232
1356
  return timeoutBinOrDefault(o.timeoutBin) + ' ' + seconds + ' codex exec' + cd
1233
- + ' -m ' + codexSq(modelId) + ' ' + codexSq(String(o.prompt === undefined || o.prompt === null ? '' : o.prompt)) + ' < /dev/null';
1357
+ + ' -m ' + codexSq(modelId) + ' -c model_reasoning_effort=' + codexSq(reasoning)
1358
+ + ' ' + codexSq(String(o.prompt === undefined || o.prompt === null ? '' : o.prompt)) + ' < /dev/null';
1234
1359
  }
1235
1360
  export function codexReviewCommand(input) {
1236
1361
  const o = input || {};