@dzhechkov/harness-core 0.8.2 → 0.8.6

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 (81) hide show
  1. package/.dz-manifest.json +146 -58
  2. package/README.md +72 -2
  3. package/dist/cmd-usage.d.ts +148 -0
  4. package/dist/cmd-usage.d.ts.map +1 -0
  5. package/dist/cmd-usage.js +548 -0
  6. package/dist/cmd-usage.js.map +1 -0
  7. package/dist/compounding.d.ts +4 -0
  8. package/dist/compounding.d.ts.map +1 -1
  9. package/dist/compounding.js +6 -0
  10. package/dist/compounding.js.map +1 -1
  11. package/dist/contract-checklist.d.ts +123 -0
  12. package/dist/contract-checklist.d.ts.map +1 -0
  13. package/dist/contract-checklist.js +700 -0
  14. package/dist/contract-checklist.js.map +1 -0
  15. package/dist/feature-adr-checkpoints.d.ts +11 -2
  16. package/dist/feature-adr-checkpoints.d.ts.map +1 -1
  17. package/dist/feature-adr-checkpoints.js +37 -2
  18. package/dist/feature-adr-checkpoints.js.map +1 -1
  19. package/dist/feature-adr-routing.d.ts +58 -23
  20. package/dist/feature-adr-routing.d.ts.map +1 -1
  21. package/dist/feature-adr-routing.js +208 -59
  22. package/dist/feature-adr-routing.js.map +1 -1
  23. package/dist/guard.d.ts +25 -0
  24. package/dist/guard.d.ts.map +1 -1
  25. package/dist/guard.js +59 -1
  26. package/dist/guard.js.map +1 -1
  27. package/dist/index.d.ts +9 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +8 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/loop-blobs.generated.js +8 -8
  32. package/dist/loop-blobs.generated.js.map +1 -1
  33. package/dist/loop-plan.d.ts +13 -1
  34. package/dist/loop-plan.d.ts.map +1 -1
  35. package/dist/loop-plan.js +15 -1
  36. package/dist/loop-plan.js.map +1 -1
  37. package/dist/loop-render.d.ts.map +1 -1
  38. package/dist/loop-render.js +51 -6
  39. package/dist/loop-render.js.map +1 -1
  40. package/dist/loop-trace.d.ts +20 -1
  41. package/dist/loop-trace.d.ts.map +1 -1
  42. package/dist/loop-trace.js +83 -1
  43. package/dist/loop-trace.js.map +1 -1
  44. package/dist/model-recommender.d.ts +8 -0
  45. package/dist/model-recommender.d.ts.map +1 -1
  46. package/dist/model-recommender.js +31 -4
  47. package/dist/model-recommender.js.map +1 -1
  48. package/dist/qe-bridge.d.ts.map +1 -1
  49. package/dist/qe-bridge.js +9 -0
  50. package/dist/qe-bridge.js.map +1 -1
  51. package/dist/restart-advisor.d.ts +103 -0
  52. package/dist/restart-advisor.d.ts.map +1 -0
  53. package/dist/restart-advisor.js +445 -0
  54. package/dist/restart-advisor.js.map +1 -0
  55. package/dist/slop-lint.d.ts +128 -0
  56. package/dist/slop-lint.d.ts.map +1 -0
  57. package/dist/slop-lint.js +607 -0
  58. package/dist/slop-lint.js.map +1 -0
  59. package/dist/workflow-run.d.ts.map +1 -1
  60. package/dist/workflow-run.js +18 -12
  61. package/dist/workflow-run.js.map +1 -1
  62. package/package.json +19 -15
  63. package/sbom.json +277 -57
  64. package/src/cmd-usage.ts +720 -0
  65. package/src/compounding.ts +13 -0
  66. package/src/contract-checklist.ts +973 -0
  67. package/src/deadwood-allowlist.json +80 -0
  68. package/src/feature-adr-checkpoints.ts +38 -2
  69. package/src/feature-adr-routing.ts +238 -55
  70. package/src/guard.ts +79 -1
  71. package/src/index.ts +81 -1
  72. package/src/loop-blobs.generated.ts +8 -8
  73. package/src/loop-plan.ts +36 -3
  74. package/src/loop-render.ts +50 -6
  75. package/src/loop-trace.ts +91 -2
  76. package/src/model-recommender.ts +35 -4
  77. package/src/qe-bridge.ts +9 -0
  78. package/src/restart-advisor.ts +579 -0
  79. package/src/slop-lint.ts +762 -0
  80. package/src/slop-markers.json +71 -0
  81. 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,7 +422,16 @@ export function codexEffortHint(opts) {
297
422
  return '';
298
423
  }
299
424
  // ── Step-7.5 CODE landed barrier (Codex out-of-band write flush) ────────────
300
- export const DEFAULT_CODE_LANDING_MAX_WAIT_MS = 120_000;
425
+ // Widened 2026-08-28 (MEASURED, not a guess): the dz-deadwood coder job ran 46m11s end to end, and
426
+ // the slop-lint coder was independently confirmed STILL RUNNING (codex-companion status "running",
427
+ // pid alive, mid-TDD-cycle, actively applying file changes) at 16m38s elapsed — a full 8x past the
428
+ // old 120s window. The old window declared "genuinely not landed" and fed Step-8 QE a false empty
429
+ // tree, producing a grade-D report entirely about an absence that was never real. This does not fix
430
+ // the underlying conflation of TIMEOUT with CONFIRMED-ABSENCE (filed as a backlog item — the barrier
431
+ // still cannot see the codex-companion job's own liveness) — it only narrows how often the false
432
+ // negative fires for a realistic M-tier build, at the cost of a longer worst-case wait when the
433
+ // coder truly produced nothing.
434
+ export const DEFAULT_CODE_LANDING_MAX_WAIT_MS = 1_020_000;
301
435
  export const DEFAULT_CODE_LANDING_BACKOFF_MS = [
302
436
  1_000,
303
437
  2_000,
@@ -310,6 +444,17 @@ export const DEFAULT_CODE_LANDING_BACKOFF_MS = [
310
444
  20_000,
311
445
  25_000,
312
446
  25_000,
447
+ 30_000,
448
+ 30_000,
449
+ 60_000,
450
+ 60_000,
451
+ 60_000,
452
+ 90_000,
453
+ 90_000,
454
+ 120_000,
455
+ 120_000,
456
+ 120_000,
457
+ 120_000,
313
458
  ];
314
459
  export const CODE_LANDED_BARRIER_SLEEPS_SECONDS = DEFAULT_CODE_LANDING_BACKOFF_MS.map((ms) => ms / 1000);
315
460
  const CODE_LANDING_PIPELINE_PREFIXES = ['features/', '.dz/', '.agentic-qe/', 'roam/'];
@@ -1205,12 +1350,16 @@ export function codexExecCommand(input) {
1205
1350
  const modelId = String(o.modelId === undefined || o.modelId === null ? '' : o.modelId);
1206
1351
  if (!isSafeCodexId(modelId))
1207
1352
  return null;
1353
+ const reasoning = o.reasoning === undefined || o.reasoning === null || o.reasoning === '' ? 'high' : String(o.reasoning);
1354
+ if (!VALID_REASONING[reasoning])
1355
+ return null;
1208
1356
  const raw = Number(o.timeoutSeconds);
1209
1357
  const seconds = raw === raw && raw !== Infinity && raw > 0 ? Math.floor(raw) : CODEX_EXEC_TIMEOUT_SECONDS;
1210
1358
  const repo = String(o.repo === undefined || o.repo === null ? '' : o.repo);
1211
1359
  const cd = repo === '' ? '' : ' -C ' + codexSq(repo);
1212
1360
  return timeoutBinOrDefault(o.timeoutBin) + ' ' + seconds + ' codex exec' + cd
1213
- + ' -m ' + codexSq(modelId) + ' ' + codexSq(String(o.prompt === undefined || o.prompt === null ? '' : o.prompt)) + ' < /dev/null';
1361
+ + ' -m ' + codexSq(modelId) + ' -c model_reasoning_effort=' + codexSq(reasoning)
1362
+ + ' ' + codexSq(String(o.prompt === undefined || o.prompt === null ? '' : o.prompt)) + ' < /dev/null';
1214
1363
  }
1215
1364
  export function codexReviewCommand(input) {
1216
1365
  const o = input || {};