@open-agent-toolkit/cli 0.1.43 → 0.1.46

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 (94) hide show
  1. package/assets/agents/oat-reviewer.md +2 -2
  2. package/assets/config/dispatch-matrix-recommendation.json +23 -0
  3. package/assets/docs/cli-utilities/config-and-local-state.md +7 -0
  4. package/assets/docs/cli-utilities/configuration.md +104 -25
  5. package/assets/docs/cli-utilities/workflow-gates.md +79 -16
  6. package/assets/docs/contributing/code.md +10 -4
  7. package/assets/docs/contributing/hooks-and-safety.md +23 -0
  8. package/assets/docs/provider-sync/config.md +5 -1
  9. package/assets/docs/provider-sync/manifest-and-drift.md +6 -1
  10. package/assets/docs/provider-sync/providers.md +6 -3
  11. package/assets/docs/provider-sync/scope-and-surface.md +2 -1
  12. package/assets/docs/reference/oat-directory-structure.md +2 -2
  13. package/assets/docs/workflows/projects/dispatch-ceiling.md +110 -27
  14. package/assets/docs/workflows/projects/implementation-execution.md +20 -13
  15. package/assets/public-package-versions.json +4 -4
  16. package/assets/skills/oat-project-implement/SKILL.md +155 -58
  17. package/assets/skills/oat-project-plan/SKILL.md +22 -3
  18. package/assets/skills/oat-project-quick-start/SKILL.md +54 -23
  19. package/assets/templates/state.md +5 -0
  20. package/dist/commands/config/index.d.ts +6 -0
  21. package/dist/commands/config/index.d.ts.map +1 -1
  22. package/dist/commands/config/index.js +429 -20
  23. package/dist/commands/doctor/index.d.ts +6 -1
  24. package/dist/commands/doctor/index.d.ts.map +1 -1
  25. package/dist/commands/doctor/index.js +209 -19
  26. package/dist/commands/gate/index.d.ts +39 -2
  27. package/dist/commands/gate/index.d.ts.map +1 -1
  28. package/dist/commands/gate/index.js +343 -30
  29. package/dist/commands/internal/cursor-current-target.d.ts +37 -0
  30. package/dist/commands/internal/cursor-current-target.d.ts.map +1 -0
  31. package/dist/commands/internal/cursor-current-target.js +228 -0
  32. package/dist/commands/internal/index.d.ts.map +1 -1
  33. package/dist/commands/internal/index.js +2 -0
  34. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  35. package/dist/commands/project/dispatch-ceiling/index.js +487 -17
  36. package/dist/commands/providers/codex/index.d.ts +4 -0
  37. package/dist/commands/providers/codex/index.d.ts.map +1 -0
  38. package/dist/commands/providers/codex/index.js +7 -0
  39. package/dist/commands/providers/codex/materialize.d.ts +5 -0
  40. package/dist/commands/providers/codex/materialize.d.ts.map +1 -0
  41. package/dist/commands/providers/codex/materialize.js +152 -0
  42. package/dist/commands/providers/index.d.ts +2 -2
  43. package/dist/commands/providers/index.d.ts.map +1 -1
  44. package/dist/commands/providers/index.js +4 -2
  45. package/dist/commands/providers/providers.types.d.ts +23 -0
  46. package/dist/commands/providers/providers.types.d.ts.map +1 -1
  47. package/dist/commands/shared/codex-strays.d.ts.map +1 -1
  48. package/dist/commands/shared/codex-strays.js +11 -1
  49. package/dist/commands/status/index.d.ts +4 -1
  50. package/dist/commands/status/index.d.ts.map +1 -1
  51. package/dist/commands/status/index.js +1 -1
  52. package/dist/commands/sync/index.d.ts.map +1 -1
  53. package/dist/commands/sync/index.js +1 -1
  54. package/dist/commands/sync/sync.types.d.ts +4 -1
  55. package/dist/commands/sync/sync.types.d.ts.map +1 -1
  56. package/dist/config/dispatch-ceiling-preset.d.ts +4 -0
  57. package/dist/config/dispatch-ceiling-preset.d.ts.map +1 -1
  58. package/dist/config/dispatch-ceiling-preset.js +3 -0
  59. package/dist/config/dispatch-policy-options.d.ts +20 -0
  60. package/dist/config/dispatch-policy-options.d.ts.map +1 -0
  61. package/dist/config/dispatch-policy-options.js +96 -0
  62. package/dist/config/oat-config.d.ts +21 -5
  63. package/dist/config/oat-config.d.ts.map +1 -1
  64. package/dist/config/oat-config.js +118 -10
  65. package/dist/config/resolve.js +12 -0
  66. package/dist/manifest/manifest.types.d.ts +12 -12
  67. package/dist/providers/ceiling/registry.d.ts +7 -5
  68. package/dist/providers/ceiling/registry.d.ts.map +1 -1
  69. package/dist/providers/ceiling/registry.js +32 -5
  70. package/dist/providers/codex/codec/config-merge.d.ts +6 -0
  71. package/dist/providers/codex/codec/config-merge.d.ts.map +1 -1
  72. package/dist/providers/codex/codec/config-merge.js +7 -0
  73. package/dist/providers/codex/codec/materialize.d.ts +16 -0
  74. package/dist/providers/codex/codec/materialize.d.ts.map +1 -0
  75. package/dist/providers/codex/codec/materialize.js +57 -0
  76. package/dist/providers/codex/codec/shared.d.ts +1 -0
  77. package/dist/providers/codex/codec/shared.d.ts.map +1 -1
  78. package/dist/providers/codex/codec/shared.js +9 -1
  79. package/dist/providers/codex/codec/sync-extension.d.ts +7 -1
  80. package/dist/providers/codex/codec/sync-extension.d.ts.map +1 -1
  81. package/dist/providers/codex/codec/sync-extension.js +183 -46
  82. package/dist/providers/identity/availability.d.ts +45 -0
  83. package/dist/providers/identity/availability.d.ts.map +1 -0
  84. package/dist/providers/identity/availability.js +353 -0
  85. package/dist/providers/identity/family.d.ts +13 -0
  86. package/dist/providers/identity/family.d.ts.map +1 -0
  87. package/dist/providers/identity/family.js +32 -0
  88. package/dist/providers/identity/provenance.d.ts +21 -0
  89. package/dist/providers/identity/provenance.d.ts.map +1 -0
  90. package/dist/providers/identity/provenance.js +65 -0
  91. package/dist/providers/identity/stamp.d.ts +35 -0
  92. package/dist/providers/identity/stamp.d.ts.map +1 -0
  93. package/dist/providers/identity/stamp.js +171 -0
  94. package/package.json +2 -2
@@ -4,11 +4,13 @@ import { buildCommandContext, } from '../../../app/command-context.js';
4
4
  import { getFrontmatterBlock } from '../../shared/frontmatter.js';
5
5
  import { readGlobalOptions } from '../../shared/shared.utils.js';
6
6
  import { compileDispatchPolicyPreset, } from '../../../config/dispatch-ceiling-preset.js';
7
- import { resolveActiveProject, VALID_CLAUDE_DISPATCH_CEILINGS, VALID_CODEX_DISPATCH_CEILINGS, VALID_MANAGED_DISPATCH_POLICIES, } from '../../../config/oat-config.js';
7
+ import { getDispatchPolicyChoices, managedDispatchPolicyValueList, renderDispatchPolicyChoicesMarkdown, } from '../../../config/dispatch-policy-options.js';
8
+ import { resolveActiveProject, VALID_CLAUDE_DISPATCH_CEILINGS, VALID_CODEX_DISPATCH_CEILINGS, VALID_DISPATCH_MATRIX_TIERS, VALID_MANAGED_DISPATCH_POLICIES, validateDispatchRouteTarget, } from '../../../config/oat-config.js';
8
9
  import { resolveEffectiveConfig, } from '../../../config/resolve.js';
9
10
  import { resolveProjectRoot } from '../../../fs/paths.js';
10
11
  import TOML from '@iarna/toml';
11
12
  import { getCeilingAdapter, } from '../../../providers/ceiling/registry.js';
13
+ import { classifyModelFamily, } from '../../../providers/identity/family.js';
12
14
  import { Command } from 'commander';
13
15
  import YAML from 'yaml';
14
16
  const CODEX_VALUES = [
@@ -54,7 +56,7 @@ function isValidProviderValue(provider, value) {
54
56
  return (typeof value === 'string' &&
55
57
  CLAUDE_VALUES.includes(value));
56
58
  }
57
- return false;
59
+ return typeof value === 'string' && value.trim().length > 0;
58
60
  }
59
61
  function configSourceToCeilingSource(source) {
60
62
  if (source === 'local') {
@@ -105,7 +107,7 @@ function isValidManagedPolicy(value) {
105
107
  VALID_MANAGED_DISPATCH_POLICIES.includes(value));
106
108
  }
107
109
  function validManagedPolicyList() {
108
- return VALID_MANAGED_DISPATCH_POLICIES.join(', ');
110
+ return managedDispatchPolicyValueList(', ');
109
111
  }
110
112
  function validProviderValueList(provider) {
111
113
  return providerValueOrder(provider)?.join(', ') ?? 'none';
@@ -121,6 +123,253 @@ function invalidProjectPolicyMessage(value) {
121
123
  const actual = typeof value === 'string' ? value : String(value);
122
124
  return `Invalid project dispatch policy "${actual}". Valid managed policies: ${validManagedPolicyList()}. Use mode "inherit" for host defaults.`;
123
125
  }
126
+ function normalizeProjectMatrixBareValue(provider, value) {
127
+ if (typeof value !== 'string') {
128
+ return undefined;
129
+ }
130
+ const trimmed = value.trim();
131
+ if (!trimmed) {
132
+ return undefined;
133
+ }
134
+ const order = providerValueOrder(provider);
135
+ if (order && !isValidProviderValue(provider, trimmed)) {
136
+ return undefined;
137
+ }
138
+ return trimmed;
139
+ }
140
+ function normalizeProjectMatrixRouteTarget(value) {
141
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
142
+ return undefined;
143
+ }
144
+ const record = value;
145
+ const target = {};
146
+ for (const key of ['harness', 'model', 'effort']) {
147
+ const rawValue = record[key];
148
+ if (typeof rawValue === 'string' && rawValue.trim()) {
149
+ target[key] = rawValue.trim();
150
+ }
151
+ }
152
+ return Object.keys(target).length > 0 ? target : undefined;
153
+ }
154
+ function normalizeProjectMatrixCell(provider, value) {
155
+ const bareValue = normalizeProjectMatrixBareValue(provider, value);
156
+ if (bareValue !== undefined) {
157
+ return bareValue;
158
+ }
159
+ if (!Array.isArray(value) || value.length === 0) {
160
+ return undefined;
161
+ }
162
+ const route = [];
163
+ for (const entry of value) {
164
+ const bareEntry = normalizeProjectMatrixBareValue(provider, entry);
165
+ if (bareEntry !== undefined) {
166
+ route.push(bareEntry);
167
+ continue;
168
+ }
169
+ const target = normalizeProjectMatrixRouteTarget(entry);
170
+ if (target !== undefined) {
171
+ route.push(target);
172
+ }
173
+ }
174
+ return route.length > 0 ? route : undefined;
175
+ }
176
+ function normalizeProjectMatrixProviderValue(provider, value) {
177
+ const bareValue = normalizeProjectMatrixBareValue(provider, value);
178
+ if (bareValue !== undefined) {
179
+ return bareValue;
180
+ }
181
+ if (!value || typeof value !== 'object' || Array.isArray(value)) {
182
+ return undefined;
183
+ }
184
+ const tierMap = {};
185
+ for (const [tier, rawCell] of Object.entries(value)) {
186
+ if (!VALID_DISPATCH_MATRIX_TIERS.includes(tier)) {
187
+ continue;
188
+ }
189
+ const normalized = normalizeProjectMatrixCell(provider, rawCell);
190
+ if (normalized !== undefined) {
191
+ tierMap[tier] = normalized;
192
+ }
193
+ }
194
+ return Object.keys(tierMap).length > 0 ? tierMap : undefined;
195
+ }
196
+ function readProjectDispatchMatrix(value) {
197
+ if (value === undefined || value === null) {
198
+ return { matrix: null, warnings: [] };
199
+ }
200
+ if (typeof value !== 'object' || Array.isArray(value)) {
201
+ return {
202
+ matrix: null,
203
+ warnings: [
204
+ 'Ignoring malformed oat_dispatch_policy.matrix in project state.',
205
+ ],
206
+ };
207
+ }
208
+ const matrix = {};
209
+ for (const [provider, rawProviderValue] of Object.entries(value)) {
210
+ if (!provider.trim()) {
211
+ continue;
212
+ }
213
+ const normalized = normalizeProjectMatrixProviderValue(provider, rawProviderValue);
214
+ if (normalized !== undefined) {
215
+ matrix[provider] = normalized;
216
+ }
217
+ }
218
+ if (Object.keys(matrix).length === 0) {
219
+ return {
220
+ matrix: null,
221
+ warnings: [
222
+ 'Ignoring malformed oat_dispatch_policy.matrix in project state.',
223
+ ],
224
+ };
225
+ }
226
+ return { matrix, warnings: [] };
227
+ }
228
+ function policyTier(policy) {
229
+ return typeof policy === 'string' &&
230
+ VALID_DISPATCH_MATRIX_TIERS.includes(policy)
231
+ ? policy
232
+ : null;
233
+ }
234
+ function uncappedPreferredTier(provider, role, preferredValue) {
235
+ if (provider !== 'codex' || role === 'reviewer' || preferredValue === null) {
236
+ return null;
237
+ }
238
+ if (VALID_DISPATCH_MATRIX_TIERS.includes(preferredValue)) {
239
+ return preferredValue;
240
+ }
241
+ let matchedTier = null;
242
+ for (const tier of VALID_DISPATCH_MATRIX_TIERS) {
243
+ const compiledValue = compiledPolicyValueForProvider(provider, compileDispatchPolicyPreset(tier));
244
+ if (compiledValue === preferredValue) {
245
+ matchedTier = tier;
246
+ }
247
+ }
248
+ return matchedTier;
249
+ }
250
+ function dispatchValueFromRouteTarget(target) {
251
+ const adapter = getCeilingAdapter(target.harness);
252
+ if (adapter.mechanism === 'pinned-variant') {
253
+ return target.effort ?? null;
254
+ }
255
+ if (adapter.mechanism === 'model-arg') {
256
+ return target.model ?? null;
257
+ }
258
+ return null;
259
+ }
260
+ function routeTargetFromBareValue(provider, value, routeIndex, routeLength) {
261
+ const adapter = getCeilingAdapter(provider);
262
+ const target = {
263
+ harness: provider,
264
+ crossHarness: false,
265
+ routeIndex,
266
+ routeLength,
267
+ };
268
+ if (adapter.mechanism === 'pinned-variant') {
269
+ target.effort = value;
270
+ }
271
+ else {
272
+ target.model = value;
273
+ }
274
+ return target;
275
+ }
276
+ function routeTargetFromObject(provider, target, routeIndex, routeLength) {
277
+ const harness = target.harness ?? provider;
278
+ return {
279
+ harness,
280
+ ...(target.model ? { model: target.model } : {}),
281
+ ...(target.effort ? { effort: target.effort } : {}),
282
+ crossHarness: harness !== provider,
283
+ routeIndex,
284
+ routeLength,
285
+ };
286
+ }
287
+ function resolveRouteMatrixCell(provider, route, escalationLevel, cellSource) {
288
+ const routeIndex = Math.min(escalationLevel, route.length - 1);
289
+ const entry = route[routeIndex];
290
+ if (entry === undefined) {
291
+ return null;
292
+ }
293
+ const target = typeof entry === 'string'
294
+ ? routeTargetFromBareValue(provider, entry, routeIndex, route.length)
295
+ : routeTargetFromObject(provider, entry, routeIndex, route.length);
296
+ const targetValidation = typeof entry === 'string'
297
+ ? { valid: true }
298
+ : validateDispatchRouteTarget(provider, entry);
299
+ const value = targetValidation.valid
300
+ ? typeof entry === 'string'
301
+ ? entry
302
+ : dispatchValueFromRouteTarget(target)
303
+ : null;
304
+ const warnings = targetValidation.valid
305
+ ? []
306
+ : [
307
+ `Ignoring incomplete Codex dispatch target at route index ${routeIndex}: ${targetValidation.reason}`,
308
+ ];
309
+ return {
310
+ value,
311
+ cellSource,
312
+ target,
313
+ selectionBranch: routeIndex > 0 ? 'escalation-target' : 'matrix-pinned',
314
+ warnings,
315
+ };
316
+ }
317
+ function resolveProviderCellFromValue(provider, providerValue, tier, cellSource, escalationLevel) {
318
+ if (providerValue === undefined) {
319
+ return null;
320
+ }
321
+ if (typeof providerValue === 'string') {
322
+ return {
323
+ value: providerValue,
324
+ cellSource,
325
+ target: null,
326
+ selectionBranch: 'prompt-persisted',
327
+ warnings: [],
328
+ };
329
+ }
330
+ if (tier === null) {
331
+ return null;
332
+ }
333
+ const cell = providerValue[tier];
334
+ if (cell === undefined) {
335
+ return null;
336
+ }
337
+ if (typeof cell === 'string') {
338
+ return {
339
+ value: cell,
340
+ cellSource,
341
+ target: null,
342
+ selectionBranch: 'matrix-pinned',
343
+ warnings: [],
344
+ };
345
+ }
346
+ return resolveRouteMatrixCell(provider, cell, escalationLevel, cellSource);
347
+ }
348
+ function resolveProviderMatrixCell(provider, tier, resolvedConfig, projectMatrix, escalationLevel) {
349
+ let selected = null;
350
+ const layers = [
351
+ {
352
+ source: 'user-config',
353
+ providers: resolvedConfig.user.workflow?.dispatchCeiling?.providers,
354
+ },
355
+ {
356
+ source: 'repo-config',
357
+ providers: resolvedConfig.shared.workflow?.dispatchCeiling?.providers,
358
+ },
359
+ {
360
+ source: 'local-config',
361
+ providers: resolvedConfig.local.workflow?.dispatchCeiling?.providers,
362
+ },
363
+ { source: 'project-state', providers: projectMatrix },
364
+ ];
365
+ for (const layer of layers) {
366
+ const resolved = resolveProviderCellFromValue(provider, layer.providers?.[provider], tier, layer.source, escalationLevel);
367
+ if (resolved) {
368
+ selected = resolved;
369
+ }
370
+ }
371
+ return selected;
372
+ }
124
373
  function readProjectDispatchPolicy(provider, content) {
125
374
  const frontmatter = getFrontmatterBlock(content);
126
375
  if (!frontmatter) {
@@ -135,6 +384,7 @@ function readProjectDispatchPolicy(provider, content) {
135
384
  return null;
136
385
  }
137
386
  const policyRecord = policy;
387
+ const parsedMatrix = readProjectDispatchMatrix(policyRecord['matrix']);
138
388
  const mode = policyRecord['mode'];
139
389
  if (mode === 'inherit') {
140
390
  return {
@@ -143,6 +393,11 @@ function readProjectDispatchPolicy(provider, content) {
143
393
  value: null,
144
394
  source: 'project-state',
145
395
  preset: null,
396
+ matrix: parsedMatrix.matrix,
397
+ cellSource: null,
398
+ target: null,
399
+ selectionBranch: 'inherit',
400
+ warnings: parsedMatrix.warnings,
146
401
  };
147
402
  }
148
403
  if (mode !== 'managed') {
@@ -161,6 +416,11 @@ function readProjectDispatchPolicy(provider, content) {
161
416
  value: null,
162
417
  source: 'project-state',
163
418
  preset: policyValue,
419
+ matrix: parsedMatrix.matrix,
420
+ cellSource: null,
421
+ target: null,
422
+ selectionBranch: 'prompt-persisted',
423
+ warnings: parsedMatrix.warnings,
164
424
  };
165
425
  }
166
426
  const providers = policyRecord['providers'];
@@ -183,6 +443,11 @@ function readProjectDispatchPolicy(provider, content) {
183
443
  value,
184
444
  source: 'project-state',
185
445
  preset: policyValue,
446
+ matrix: parsedMatrix.matrix,
447
+ cellSource: 'project-state',
448
+ target: null,
449
+ selectionBranch: 'prompt-persisted',
450
+ warnings: parsedMatrix.warnings,
186
451
  };
187
452
  }
188
453
  function readLegacyProjectDispatchCeiling(provider, parsed) {
@@ -208,6 +473,11 @@ function readLegacyProjectDispatchCeiling(provider, parsed) {
208
473
  value,
209
474
  source: 'project-state',
210
475
  preset: typeof presetValue === 'string' ? presetValue : null,
476
+ matrix: null,
477
+ cellSource: 'project-state',
478
+ target: null,
479
+ selectionBranch: 'prompt-persisted',
480
+ warnings: [],
211
481
  };
212
482
  }
213
483
  function readProjectDispatchCeiling(provider, content) {
@@ -274,6 +544,11 @@ function stripConfigCandidateSource(candidate) {
274
544
  value: candidate.value,
275
545
  source: candidate.source,
276
546
  preset: candidate.preset,
547
+ matrix: candidate.matrix,
548
+ cellSource: candidate.cellSource,
549
+ target: candidate.target,
550
+ selectionBranch: candidate.selectionBranch,
551
+ warnings: candidate.warnings,
277
552
  };
278
553
  }
279
554
  function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
@@ -291,6 +566,11 @@ function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
291
566
  value: null,
292
567
  source,
293
568
  preset: null,
569
+ matrix: null,
570
+ cellSource: null,
571
+ target: null,
572
+ selectionBranch: 'inherit',
573
+ warnings: [],
294
574
  configSource: modeEntry.source,
295
575
  };
296
576
  }
@@ -311,6 +591,11 @@ function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
311
591
  value: compiledPolicyValueForProvider(provider, compiled),
312
592
  source,
313
593
  preset: policy,
594
+ matrix: null,
595
+ cellSource: null,
596
+ target: null,
597
+ selectionBranch: 'prompt-persisted',
598
+ warnings: [],
314
599
  configSource,
315
600
  };
316
601
  }
@@ -336,6 +621,11 @@ function readResolvedLegacyConfigCeilingCandidate(provider, resolvedConfig) {
336
621
  value: entry.value,
337
622
  source,
338
623
  preset: null,
624
+ matrix: null,
625
+ cellSource: source,
626
+ target: null,
627
+ selectionBranch: 'prompt-persisted',
628
+ warnings: [],
339
629
  configSource: entry.source,
340
630
  };
341
631
  }
@@ -384,11 +674,28 @@ function normalizePreferredValue(provider, value) {
384
674
  }
385
675
  return normalized;
386
676
  }
677
+ function normalizeEscalationLevel(value) {
678
+ if (value === undefined) {
679
+ return 0;
680
+ }
681
+ const normalized = value.trim();
682
+ if (!/^\d+$/.test(normalized)) {
683
+ throw new Error(`Invalid escalation level "${value}". Use a non-negative integer.`);
684
+ }
685
+ return Number.parseInt(normalized, 10);
686
+ }
687
+ function selectionFamily(provider, value, target) {
688
+ return value
689
+ ? classifyModelFamily({ value, providerId: target?.harness ?? provider })
690
+ : 'unknown';
691
+ }
387
692
  function selectDispatchValue(provider, role, policy, preferredValue) {
388
693
  const baseSelection = {
389
694
  role,
390
695
  policyMode: policy.mode,
391
696
  policy: policy.policy,
697
+ cellSource: policy.cellSource,
698
+ target: policy.target,
392
699
  };
393
700
  if (policy.mode === 'inherit') {
394
701
  return {
@@ -397,15 +704,30 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
397
704
  selectedValue: null,
398
705
  capped: false,
399
706
  selectionMode: 'inherit-default',
707
+ selectionBranch: 'inherit',
708
+ family: 'unknown',
709
+ target: null,
400
710
  };
401
711
  }
402
712
  if (policy.policy === 'uncapped') {
713
+ const targetValue = policy.target
714
+ ? dispatchValueFromRouteTarget(policy.target)
715
+ : null;
716
+ const selectedValue = role === 'reviewer' ? null : (targetValue ?? preferredValue);
717
+ const target = role === 'reviewer' || !targetValue ? null : policy.target;
403
718
  return {
404
719
  ...baseSelection,
405
720
  preferredValue: role === 'reviewer' ? null : preferredValue,
406
- selectedValue: role === 'reviewer' ? null : preferredValue,
721
+ selectedValue,
407
722
  capped: false,
408
723
  selectionMode: role === 'reviewer' ? 'no-review-target' : 'uncapped',
724
+ selectionBranch: target
725
+ ? policy.selectionBranch
726
+ : selectedValue
727
+ ? 'prompt-persisted'
728
+ : 'unresolved',
729
+ family: selectionFamily(provider, selectedValue, target),
730
+ target,
409
731
  };
410
732
  }
411
733
  if (policy.value === null) {
@@ -414,7 +736,10 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
414
736
  preferredValue: null,
415
737
  selectedValue: null,
416
738
  capped: false,
417
- selectionMode: 'capped',
739
+ selectionMode: policy.target ? 'unresolved' : 'capped',
740
+ selectionBranch: policy.selectionBranch,
741
+ family: 'unknown',
742
+ target: policy.target,
418
743
  };
419
744
  }
420
745
  if (role === 'reviewer' || preferredValue === null) {
@@ -424,6 +749,8 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
424
749
  selectedValue: policy.value,
425
750
  capped: false,
426
751
  selectionMode: role === 'reviewer' ? 'review-target' : 'capped',
752
+ selectionBranch: policy.selectionBranch,
753
+ family: selectionFamily(provider, policy.value, policy.target),
427
754
  };
428
755
  }
429
756
  const order = providerValueOrder(provider);
@@ -436,17 +763,57 @@ function selectDispatchValue(provider, role, policy, preferredValue) {
436
763
  selectedValue: policy.value,
437
764
  capped: false,
438
765
  selectionMode: 'capped',
766
+ selectionBranch: policy.selectionBranch,
767
+ family: selectionFamily(provider, policy.value, policy.target),
439
768
  };
440
769
  }
441
770
  const selectedIndex = Math.min(preferredIndex, ceilingIndex);
771
+ const selectedValue = order[selectedIndex];
442
772
  return {
443
773
  ...baseSelection,
444
774
  preferredValue,
445
- selectedValue: order[selectedIndex],
775
+ selectedValue,
446
776
  capped: preferredIndex > ceilingIndex,
447
777
  selectionMode: 'capped',
778
+ selectionBranch: policy.selectionBranch,
779
+ family: selectionFamily(provider, selectedValue, selectedValue === policy.value ? policy.target : null),
780
+ target: selectedValue === policy.value ? policy.target : null,
448
781
  };
449
782
  }
783
+ function hasCodexVariantDispatchArgs(dispatchArgs) {
784
+ return (dispatchArgs !== null &&
785
+ 'variant' in dispatchArgs &&
786
+ typeof dispatchArgs.variant === 'string' &&
787
+ dispatchArgs.variant.length > 0);
788
+ }
789
+ function hasModelDispatchArgs(dispatchArgs) {
790
+ return (dispatchArgs !== null &&
791
+ 'model' in dispatchArgs &&
792
+ typeof dispatchArgs.model === 'string' &&
793
+ dispatchArgs.model.length > 0);
794
+ }
795
+ function modelAxis(selection, dispatchArgs) {
796
+ if (selection.target?.model && dispatchArgs) {
797
+ return `selected:${selection.target.model}`;
798
+ }
799
+ if (hasModelDispatchArgs(dispatchArgs)) {
800
+ return `selected:${dispatchArgs.model}`;
801
+ }
802
+ if (selection.selectionMode === 'inherit-default') {
803
+ return 'inherited';
804
+ }
805
+ return 'unresolved';
806
+ }
807
+ function codexEffortAxis(selection, dispatchArgs) {
808
+ if (hasCodexVariantDispatchArgs(dispatchArgs) && selection.target?.effort) {
809
+ return `selected:${selection.target.effort}`;
810
+ }
811
+ if (selection.selectionMode === 'inherit-default' ||
812
+ selection.selectionMode === 'no-review-target') {
813
+ return 'provider-default';
814
+ }
815
+ return 'unresolved';
816
+ }
450
817
  /**
451
818
  * Join a resolved ceiling value with the active provider's adapter to compute
452
819
  * the enforcement mode, mechanism, dispatch args, and verify-on-upgrade flag.
@@ -460,13 +827,21 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
460
827
  mode: adapter.supportsCeiling ? 'advisory' : 'unsupported',
461
828
  mechanism: adapter.mechanism,
462
829
  dispatchArgs: null,
830
+ modelAxis: 'unresolved',
831
+ effortAxis: provider === 'codex' ? 'provider-default' : 'not-applicable',
463
832
  verifyOnDispatch: false,
833
+ cellSource: null,
834
+ target: null,
464
835
  selection: {
465
836
  role,
466
837
  preferredValue,
467
838
  selectedValue: null,
468
839
  capped: false,
469
840
  selectionMode: 'unresolved',
841
+ selectionBranch: 'unresolved',
842
+ family: 'unknown',
843
+ target: null,
844
+ cellSource: null,
470
845
  policyMode: null,
471
846
  policy: null,
472
847
  },
@@ -474,9 +849,11 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
474
849
  }
475
850
  const selection = selectDispatchValue(provider, role, policy, preferredValue);
476
851
  const dispatchValue = selection.selectedValue;
477
- const dispatchArgs = dispatchValue
852
+ const isCrossHarness = selection.target?.crossHarness === true;
853
+ const dispatchArgs = dispatchValue && !isCrossHarness
478
854
  ? adapter.compileToDispatchArgs(dispatchValue, role, {
479
855
  orchestratorTier,
856
+ target: selection.target,
480
857
  })
481
858
  : null;
482
859
  let mode;
@@ -494,11 +871,18 @@ function buildProviderResolution(provider, policy, role, orchestratorTier, prefe
494
871
  mode,
495
872
  mechanism: adapter.mechanism,
496
873
  dispatchArgs,
497
- verifyOnDispatch: dispatchValue
874
+ modelAxis: modelAxis(selection, dispatchArgs),
875
+ effortAxis: provider === 'codex'
876
+ ? codexEffortAxis(selection, dispatchArgs)
877
+ : 'not-applicable',
878
+ verifyOnDispatch: dispatchValue && !isCrossHarness
498
879
  ? adapter.verifyOnDispatch(dispatchValue, {
499
880
  orchestratorTier,
881
+ target: selection.target,
500
882
  })
501
883
  : false,
884
+ cellSource: policy.cellSource,
885
+ target: selection.target,
502
886
  selection,
503
887
  };
504
888
  }
@@ -550,7 +934,11 @@ async function resolveDispatchCeiling(context, dependencies, options) {
550
934
  ? await resolveCodexProviderDefaultEffort(repoRoot, context, dependencies)
551
935
  : 'not-applicable';
552
936
  const preferredValue = normalizePreferredValue(provider, options.preferred);
553
- const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies);
937
+ const escalationLevel = normalizeEscalationLevel(options.escalationLevel);
938
+ const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel, role, preferredValue);
939
+ for (const warning of resolvedValue?.warnings ?? []) {
940
+ context.logger.warn(warning);
941
+ }
554
942
  const providerResolution = buildProviderResolution(provider, resolvedValue, role, orchestratorTier, preferredValue);
555
943
  const providers = {
556
944
  [provider]: providerResolution,
@@ -567,6 +955,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
567
955
  unresolved: false,
568
956
  projectPath,
569
957
  providerDefaultEffort,
958
+ matrix: resolvedValue.matrix,
570
959
  providers,
571
960
  };
572
961
  }
@@ -585,6 +974,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
585
974
  unresolved: true,
586
975
  projectPath,
587
976
  providerDefaultEffort,
977
+ matrix: null,
588
978
  providers,
589
979
  message,
590
980
  };
@@ -595,16 +985,50 @@ async function resolveDispatchCeiling(context, dependencies, options) {
595
985
  * Never reads the preset label for dispatch — the preset is surfaced as
596
986
  * provenance only.
597
987
  */
598
- async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies) {
988
+ async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies, escalationLevel, role, preferredValue) {
599
989
  const configCeiling = readResolvedConfigCeiling(provider, resolvedConfig);
600
- if (configCeiling) {
601
- return configCeiling;
602
- }
603
990
  const projectCeiling = await resolveProjectStateCeiling(provider, projectPath, dependencies);
604
- if (projectCeiling) {
605
- return projectCeiling;
991
+ const baseCeiling = configCeiling ?? projectCeiling;
992
+ if (!baseCeiling) {
993
+ return null;
606
994
  }
607
- return null;
995
+ if (baseCeiling.mode === 'inherit') {
996
+ return baseCeiling;
997
+ }
998
+ if (baseCeiling.policy === 'uncapped') {
999
+ const preferredTier = uncappedPreferredTier(provider, role, preferredValue);
1000
+ if (preferredTier) {
1001
+ const matrixCell = resolveProviderMatrixCell(provider, preferredTier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel);
1002
+ if (matrixCell) {
1003
+ return {
1004
+ ...baseCeiling,
1005
+ cellSource: matrixCell.cellSource,
1006
+ target: matrixCell.target,
1007
+ selectionBranch: matrixCell.selectionBranch,
1008
+ warnings: [...baseCeiling.warnings, ...matrixCell.warnings],
1009
+ };
1010
+ }
1011
+ }
1012
+ return baseCeiling;
1013
+ }
1014
+ const tier = policyTier(baseCeiling.policy);
1015
+ const matrixCell = resolveProviderMatrixCell(provider, tier, resolvedConfig, projectCeiling?.matrix ?? null, escalationLevel);
1016
+ if (matrixCell) {
1017
+ return {
1018
+ ...baseCeiling,
1019
+ value: matrixCell.value,
1020
+ cellSource: matrixCell.cellSource,
1021
+ target: matrixCell.target,
1022
+ selectionBranch: matrixCell.selectionBranch,
1023
+ warnings: [...baseCeiling.warnings, ...matrixCell.warnings],
1024
+ };
1025
+ }
1026
+ if (baseCeiling.policy !== 'legacy-ceiling' &&
1027
+ baseCeiling.value === null &&
1028
+ providerValueOrder(provider) === null) {
1029
+ return null;
1030
+ }
1031
+ return baseCeiling;
608
1032
  }
609
1033
  function policyLabel(policy) {
610
1034
  if (policy === 'legacy-ceiling') {
@@ -625,6 +1049,23 @@ function writeHumanResolution(context, resolution) {
625
1049
  if (providerResolution) {
626
1050
  context.logger.info(`Mode: ${providerResolution.mode} (${providerResolution.mechanism})`);
627
1051
  context.logger.info(`Selection: ${providerResolution.selection.selectionMode}`);
1052
+ if (providerResolution.target) {
1053
+ const details = [
1054
+ `harness=${providerResolution.target.harness}`,
1055
+ providerResolution.target.model
1056
+ ? `model=${providerResolution.target.model}`
1057
+ : null,
1058
+ providerResolution.target.effort
1059
+ ? `effort=${providerResolution.target.effort}`
1060
+ : null,
1061
+ ]
1062
+ .filter(Boolean)
1063
+ .join(' ');
1064
+ const suffix = providerResolution.target.crossHarness
1065
+ ? ' (cross-harness target; native dispatch args deferred)'
1066
+ : '';
1067
+ context.logger.info(`Dispatch target: ${details}${suffix}`);
1068
+ }
628
1069
  }
629
1070
  if (resolution.provider === 'codex') {
630
1071
  context.logger.info(`Codex provider default effort: ${resolution.providerDefaultEffort}`);
@@ -681,18 +1122,47 @@ async function runDispatchCeilingResolve(context, dependencies, options) {
681
1122
  process.exitCode = 1;
682
1123
  }
683
1124
  }
1125
+ async function runDispatchCeilingChoices(context, options) {
1126
+ const choices = getDispatchPolicyChoices();
1127
+ const format = (options.format ?? 'markdown').trim().toLowerCase();
1128
+ if (context.json || options.json) {
1129
+ context.logger.json({ status: 'ok', choices });
1130
+ process.exitCode = 0;
1131
+ return;
1132
+ }
1133
+ if (format !== 'markdown') {
1134
+ context.logger.error(`Invalid choices format "${options.format}". Valid formats: markdown.`);
1135
+ process.exitCode = 1;
1136
+ return;
1137
+ }
1138
+ context.logger.info(renderDispatchPolicyChoicesMarkdown(choices));
1139
+ process.exitCode = 0;
1140
+ }
684
1141
  export function createProjectDispatchCeilingCommand(overrides = {}) {
685
1142
  const dependencies = {
686
1143
  ...DEFAULT_DEPENDENCIES,
687
1144
  ...overrides,
688
1145
  };
689
1146
  const command = new Command('dispatch-ceiling').description('Resolve OAT project dispatch ceiling metadata');
1147
+ command.addCommand(new Command('choices')
1148
+ .description('Print canonical dispatch policy choices')
1149
+ .option('--format <format>', 'Output format: markdown', 'markdown')
1150
+ .option('--json', 'Output machine-readable JSON')
1151
+ .action(async (options, cmd) => {
1152
+ const globalOptions = readGlobalOptions(cmd);
1153
+ const context = dependencies.buildCommandContext({
1154
+ ...globalOptions,
1155
+ json: globalOptions.json === true || options.json === true,
1156
+ });
1157
+ await runDispatchCeilingChoices(context, options);
1158
+ }));
690
1159
  command.addCommand(new Command('resolve')
691
1160
  .description('Resolve dispatch policy for a provider')
692
- .requiredOption('--provider <provider>', 'Provider name: codex or claude are enforced; any other provider resolves as advisory (unsupported)')
1161
+ .requiredOption('--provider <provider>', 'Provider name: codex, claude, or cursor are enforced; unregistered providers resolve as unsupported advisory')
693
1162
  .option('--role <role>', 'Dispatch role for variant compilation: implementer (default) or reviewer')
694
1163
  .option('--orchestrator-tier <tier>', 'Orchestrator tier, used to flag verify-on-upgrade for above-orchestrator requests')
695
1164
  .option('--preferred <value>', 'Preferred implementer/fix dispatch value before applying the resolved policy')
1165
+ .option('--escalation-level <level>', 'Ordered route entry to select; 0 is the floor and higher values advance through escalation targets')
696
1166
  .option('--project-path <path>', 'Read project-state policy from an explicit project path')
697
1167
  .option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
698
1168
  .option('--non-interactive', 'Force non-interactive block behavior when the ceiling is unresolved')