@open-agent-toolkit/cli 0.1.11 → 0.1.13

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 (31) hide show
  1. package/assets/docs/cli-utilities/configuration.md +66 -11
  2. package/assets/docs/reference/oat-directory-structure.md +22 -21
  3. package/assets/docs/workflows/projects/dispatch-ceiling.md +123 -0
  4. package/assets/docs/workflows/projects/implementation-execution.md +27 -9
  5. package/assets/docs/workflows/projects/index.md +1 -0
  6. package/assets/docs/workflows/projects/lifecycle.md +1 -1
  7. package/assets/public-package-versions.json +4 -4
  8. package/assets/skills/oat-project-implement/SKILL.md +125 -31
  9. package/assets/skills/oat-project-plan/SKILL.md +28 -28
  10. package/assets/skills/oat-project-quick-start/SKILL.md +28 -27
  11. package/dist/commands/config/index.d.ts.map +1 -1
  12. package/dist/commands/config/index.js +53 -13
  13. package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
  14. package/dist/commands/project/dispatch-ceiling/index.js +126 -28
  15. package/dist/config/dispatch-ceiling-preset.d.ts +54 -0
  16. package/dist/config/dispatch-ceiling-preset.d.ts.map +1 -0
  17. package/dist/config/dispatch-ceiling-preset.js +32 -0
  18. package/dist/config/json.d.ts +2 -0
  19. package/dist/config/json.d.ts.map +1 -0
  20. package/dist/config/json.js +29 -0
  21. package/dist/config/oat-config.d.ts +9 -2
  22. package/dist/config/oat-config.d.ts.map +1 -1
  23. package/dist/config/oat-config.js +26 -18
  24. package/dist/config/resolve.d.ts.map +1 -1
  25. package/dist/config/resolve.js +5 -2
  26. package/dist/config/sync-config.d.ts.map +1 -1
  27. package/dist/config/sync-config.js +2 -1
  28. package/dist/providers/ceiling/registry.d.ts +53 -0
  29. package/dist/providers/ceiling/registry.d.ts.map +1 -0
  30. package/dist/providers/ceiling/registry.js +77 -0
  31. package/package.json +3 -2
@@ -7,6 +7,7 @@ import { resolveActiveProject, } from '../../../config/oat-config.js';
7
7
  import { resolveEffectiveConfig, } from '../../../config/resolve.js';
8
8
  import { resolveProjectRoot } from '../../../fs/paths.js';
9
9
  import TOML from '@iarna/toml';
10
+ import { getCeilingAdapter, } from '../../../providers/ceiling/registry.js';
10
11
  import { Command } from 'commander';
11
12
  import YAML from 'yaml';
12
13
  const CODEX_VALUES = [
@@ -38,18 +39,26 @@ const DEFAULT_DEPENDENCIES = {
38
39
  processEnv: process.env,
39
40
  };
40
41
  function normalizeProvider(value) {
41
- if (value === 'codex' || value === 'claude') {
42
- return value;
42
+ // Provider-neutral: accept any provider name. Unknown providers do not throw;
43
+ // they resolve through the fallback advisory adapter as `mode: unsupported`.
44
+ const normalized = value?.trim();
45
+ if (!normalized) {
46
+ throw new Error('Provider is required.');
43
47
  }
44
- throw new Error('Invalid provider. Expected one of: codex, claude.');
48
+ return normalized;
45
49
  }
46
50
  function isValidProviderValue(provider, value) {
51
+ // Typed concrete-value validation only applies to providers with a value
52
+ // enum. Unknown providers have no concrete ceiling value (always advisory).
47
53
  if (provider === 'codex') {
48
54
  return (typeof value === 'string' &&
49
55
  CODEX_VALUES.includes(value));
50
56
  }
51
- return (typeof value === 'string' &&
52
- CLAUDE_VALUES.includes(value));
57
+ if (provider === 'claude') {
58
+ return (typeof value === 'string' &&
59
+ CLAUDE_VALUES.includes(value));
60
+ }
61
+ return false;
53
62
  }
54
63
  function configSourceToCeilingSource(source) {
55
64
  if (source === 'local') {
@@ -83,7 +92,14 @@ function sourceLabel(source) {
83
92
  }
84
93
  }
85
94
  function providerLabel(provider) {
86
- return provider === 'codex' ? 'Codex' : 'Claude';
95
+ if (provider === 'codex') {
96
+ return 'Codex';
97
+ }
98
+ if (provider === 'claude') {
99
+ return 'Claude';
100
+ }
101
+ // Unknown providers: title-case the raw name for human-readable output.
102
+ return provider.charAt(0).toUpperCase() + provider.slice(1);
87
103
  }
88
104
  function resolveTargetProjectPath(repoRoot, projectPath) {
89
105
  return isAbsolute(projectPath) ? projectPath : join(repoRoot, projectPath);
@@ -102,11 +118,21 @@ function readProjectDispatchCeiling(provider, content) {
102
118
  return null;
103
119
  }
104
120
  const ceilingRecord = ceiling;
105
- if (ceilingRecord['provider'] !== provider) {
121
+ // Clean break: read concrete per-provider values only. The preset label is
122
+ // provenance and is never used to drive dispatch.
123
+ const providers = ceilingRecord['providers'];
124
+ if (!providers || typeof providers !== 'object' || Array.isArray(providers)) {
125
+ return null;
126
+ }
127
+ const value = providers[provider];
128
+ if (!isValidProviderValue(provider, value)) {
106
129
  return null;
107
130
  }
108
- const value = ceilingRecord['value'];
109
- return isValidProviderValue(provider, value) ? value : null;
131
+ const presetValue = ceilingRecord['preset'];
132
+ return {
133
+ value,
134
+ preset: typeof presetValue === 'string' ? presetValue : null,
135
+ };
110
136
  }
111
137
  async function resolveProjectStateCeiling(provider, projectPath, dependencies) {
112
138
  if (!projectPath) {
@@ -131,7 +157,10 @@ async function resolveProjectPath(repoRoot, dependencies, options) {
131
157
  return join(repoRoot, activeProject.path);
132
158
  }
133
159
  function readResolvedConfigCeiling(provider, resolvedConfig) {
134
- const entry = resolvedConfig.resolved[`workflow.dispatchCeiling.${provider}`];
160
+ // Read the concrete per-provider value from the nested key. The flat
161
+ // `workflow.dispatchCeiling.<provider>` shape was removed in p01; never read
162
+ // the preset label for dispatch.
163
+ const entry = resolvedConfig.resolved[`workflow.dispatchCeiling.providers.${provider}`];
135
164
  const source = entry ? configSourceToCeilingSource(entry.source) : null;
136
165
  if (!entry || !source || !isValidProviderValue(provider, entry.value)) {
137
166
  return null;
@@ -141,6 +170,46 @@ function readResolvedConfigCeiling(provider, resolvedConfig) {
141
170
  source,
142
171
  };
143
172
  }
173
+ function normalizeRole(value) {
174
+ return value === 'reviewer' ? 'reviewer' : 'implementer';
175
+ }
176
+ /**
177
+ * Join a resolved ceiling value with the active provider's adapter to compute
178
+ * the enforcement mode, mechanism, dispatch args, and verify-on-upgrade flag.
179
+ * Mode is computed here at call time — it is never read from persisted state.
180
+ */
181
+ function buildProviderResolution(provider, value, role, orchestratorTier) {
182
+ const adapter = getCeilingAdapter(provider);
183
+ if (value === null) {
184
+ return {
185
+ value: null,
186
+ mode: adapter.supportsCeiling ? 'advisory' : 'unsupported',
187
+ mechanism: adapter.mechanism,
188
+ dispatchArgs: null,
189
+ verifyOnDispatch: false,
190
+ };
191
+ }
192
+ const dispatchArgs = adapter.compileToDispatchArgs(value, role, {
193
+ orchestratorTier,
194
+ });
195
+ let mode;
196
+ if (!adapter.supportsCeiling) {
197
+ mode = 'unsupported';
198
+ }
199
+ else if (dispatchArgs) {
200
+ mode = 'enforced';
201
+ }
202
+ else {
203
+ mode = 'advisory';
204
+ }
205
+ return {
206
+ value,
207
+ mode,
208
+ mechanism: adapter.mechanism,
209
+ dispatchArgs,
210
+ verifyOnDispatch: adapter.verifyOnDispatch(value, { orchestratorTier }),
211
+ };
212
+ }
144
213
  function readCodexDefaultFromToml(content) {
145
214
  let parsed;
146
215
  try {
@@ -170,13 +239,15 @@ async function resolveCodexProviderDefaultEffort(repoRoot, context, dependencies
170
239
  }
171
240
  function blockMessage(provider) {
172
241
  const label = providerLabel(provider);
173
- return `BLOCKED: ${label} dispatch ceiling is unresolved in non-interactive mode.\nSet workflow.dispatchCeiling.${provider} in .oat/config.json or oat_dispatch_ceiling in project state.`;
242
+ return `BLOCKED: ${label} dispatch ceiling is unresolved in non-interactive mode.\nSet workflow.dispatchCeiling.providers.${provider} in .oat/config.json or oat_dispatch_ceiling in project state.`;
174
243
  }
175
244
  function isNonInteractiveEnv(env) {
176
245
  return env['OAT_NON_INTERACTIVE'] === '1';
177
246
  }
178
247
  async function resolveDispatchCeiling(context, dependencies, options) {
179
248
  const provider = normalizeProvider(options.provider);
249
+ const role = normalizeRole(options.role);
250
+ const orchestratorTier = options.orchestratorTier;
180
251
  const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
181
252
  const userConfigDir = join(context.home, '.oat');
182
253
  const [resolvedConfig, projectPath] = await Promise.all([
@@ -186,28 +257,22 @@ async function resolveDispatchCeiling(context, dependencies, options) {
186
257
  const providerDefaultEffort = provider === 'codex'
187
258
  ? await resolveCodexProviderDefaultEffort(repoRoot, context, dependencies)
188
259
  : 'not-applicable';
189
- const configCeiling = readResolvedConfigCeiling(provider, resolvedConfig);
190
- if (configCeiling) {
191
- return {
192
- status: 'resolved',
193
- provider,
194
- value: configCeiling.value,
195
- source: configCeiling.source,
196
- unresolved: false,
197
- projectPath,
198
- providerDefaultEffort,
199
- };
200
- }
201
- const projectCeiling = await resolveProjectStateCeiling(provider, projectPath, dependencies);
202
- if (projectCeiling) {
260
+ const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies);
261
+ const providerResolution = buildProviderResolution(provider, resolvedValue?.value ?? null, role, orchestratorTier);
262
+ const providers = {
263
+ [provider]: providerResolution,
264
+ };
265
+ if (resolvedValue) {
203
266
  return {
204
267
  status: 'resolved',
205
268
  provider,
206
- value: projectCeiling,
207
- source: 'project-state',
269
+ value: resolvedValue.value,
270
+ source: resolvedValue.source,
271
+ preset: resolvedValue.preset,
208
272
  unresolved: false,
209
273
  projectPath,
210
274
  providerDefaultEffort,
275
+ providers,
211
276
  };
212
277
  }
213
278
  const shouldBlock = options.nonInteractive === true ||
@@ -219,12 +284,39 @@ async function resolveDispatchCeiling(context, dependencies, options) {
219
284
  provider,
220
285
  value: null,
221
286
  source: null,
287
+ preset: null,
222
288
  unresolved: true,
223
289
  projectPath,
224
290
  providerDefaultEffort,
291
+ providers,
225
292
  message,
226
293
  };
227
294
  }
295
+ /**
296
+ * Resolve the concrete per-provider ceiling value, applying config precedence
297
+ * (local > shared > user, via `resolveEffectiveConfig`) before project state.
298
+ * Never reads the preset label for dispatch — the preset is surfaced as
299
+ * provenance only.
300
+ */
301
+ async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies) {
302
+ const configCeiling = readResolvedConfigCeiling(provider, resolvedConfig);
303
+ if (configCeiling) {
304
+ return {
305
+ value: configCeiling.value,
306
+ source: configCeiling.source,
307
+ preset: null,
308
+ };
309
+ }
310
+ const projectCeiling = await resolveProjectStateCeiling(provider, projectPath, dependencies);
311
+ if (projectCeiling) {
312
+ return {
313
+ value: projectCeiling.value,
314
+ source: 'project-state',
315
+ preset: projectCeiling.preset,
316
+ };
317
+ }
318
+ return null;
319
+ }
228
320
  function writeHumanResolution(context, resolution) {
229
321
  const label = providerLabel(resolution.provider);
230
322
  if (resolution.status === 'blocked' && resolution.message) {
@@ -233,6 +325,10 @@ function writeHumanResolution(context, resolution) {
233
325
  }
234
326
  context.logger.info(`${label} dispatch ceiling: ${resolution.value ?? 'unresolved'}`);
235
327
  context.logger.info(`Source: ${sourceLabel(resolution.source)}`);
328
+ const providerResolution = resolution.providers[resolution.provider];
329
+ if (providerResolution) {
330
+ context.logger.info(`Mode: ${providerResolution.mode} (${providerResolution.mechanism})`);
331
+ }
236
332
  if (resolution.provider === 'codex') {
237
333
  context.logger.info(`Codex provider default effort: ${resolution.providerDefaultEffort}`);
238
334
  context.logger.info(`Note: OAT will use pinned subagent variants up to ${resolution.value ?? 'the resolved ceiling'}. Base/unpinned roles resolve through the provider default.`);
@@ -271,7 +367,9 @@ export function createProjectDispatchCeilingCommand(overrides = {}) {
271
367
  const command = new Command('dispatch-ceiling').description('Resolve OAT project dispatch ceiling metadata');
272
368
  command.addCommand(new Command('resolve')
273
369
  .description('Resolve dispatch ceiling for a provider')
274
- .requiredOption('--provider <provider>', 'Provider: codex or claude')
370
+ .requiredOption('--provider <provider>', 'Provider name: codex or claude are enforced; any other provider resolves as advisory (unsupported)')
371
+ .option('--role <role>', 'Dispatch role for variant compilation: implementer (default) or reviewer')
372
+ .option('--orchestrator-tier <tier>', 'Orchestrator tier, used to flag verify-on-upgrade for above-orchestrator requests')
275
373
  .option('--project-path <path>', 'Read project-state ceiling from an explicit project path')
276
374
  .option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
277
375
  .option('--non-interactive', 'Force non-interactive block behavior when the ceiling is unresolved')
@@ -0,0 +1,54 @@
1
+ import type { WorkflowClaudeDispatchCeiling, WorkflowCodexDispatchCeiling, WorkflowDispatchCeiling, WorkflowDispatchCeilingPreset } from './oat-config.js';
2
+ /**
3
+ * Fixed mapping table: preset → concrete per-provider values.
4
+ * This is the single authority for preset compilation; the resolver and skills
5
+ * must not re-map these values.
6
+ *
7
+ * Design notes:
8
+ * - cost-conscious holds Claude at `sonnet` (no Haiku reviewers by default).
9
+ * - The table is `as const` to preserve literal types for downstream consumers.
10
+ */
11
+ export declare const DISPATCH_CEILING_PRESETS: {
12
+ readonly balanced: {
13
+ readonly codex: "high";
14
+ readonly claude: "sonnet";
15
+ };
16
+ readonly maximum: {
17
+ readonly codex: "xhigh";
18
+ readonly claude: "opus";
19
+ };
20
+ readonly 'cost-conscious': {
21
+ readonly codex: "medium";
22
+ readonly claude: "sonnet";
23
+ };
24
+ };
25
+ /**
26
+ * Result of compiling a preset selection.
27
+ * Both `preset` (provenance label) and `providers` (concrete values) are present.
28
+ */
29
+ export interface PresetCompileResult {
30
+ preset: WorkflowDispatchCeilingPreset;
31
+ providers: {
32
+ codex: WorkflowCodexDispatchCeiling;
33
+ claude: WorkflowClaudeDispatchCeiling;
34
+ };
35
+ }
36
+ /**
37
+ * Result of compiling an advanced/manual per-provider selection.
38
+ * No `preset` key — advanced selections store only `providers`.
39
+ */
40
+ export interface AdvancedCompileResult {
41
+ providers: WorkflowDispatchCeiling['providers'] & object;
42
+ }
43
+ /**
44
+ * Compile a preset label into concrete per-provider values.
45
+ * The returned `preset` field is provenance only; runtime dispatch reads
46
+ * only `providers`.
47
+ */
48
+ export declare function compileDispatchCeilingPreset(preset: WorkflowDispatchCeilingPreset): PresetCompileResult;
49
+ /**
50
+ * Pass through advanced/manual per-provider values with no `preset` key.
51
+ * Advanced selections do not set a preset so the persisted shape omits it.
52
+ */
53
+ export declare function compileAdvancedDispatchCeiling(providers: WorkflowDispatchCeiling['providers'] & object): AdvancedCompileResult;
54
+ //# sourceMappingURL=dispatch-ceiling-preset.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"dispatch-ceiling-preset.d.ts","sourceRoot":"","sources":["../../src/config/dispatch-ceiling-preset.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EACV,6BAA6B,EAC7B,4BAA4B,EAC5B,uBAAuB,EACvB,6BAA6B,EAC9B,MAAM,cAAc,CAAC;AAEtB;;;;;;;;GAQG;AACH,eAAO,MAAM,wBAAwB;;;;;;;;;;;;;CAOpC,CAAC;AAEF;;;GAGG;AACH,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,6BAA6B,CAAC;IACtC,SAAS,EAAE;QACT,KAAK,EAAE,4BAA4B,CAAC;QACpC,MAAM,EAAE,6BAA6B,CAAC;KACvC,CAAC;CACH;AAED;;;GAGG;AACH,MAAM,WAAW,qBAAqB;IACpC,SAAS,EAAE,uBAAuB,CAAC,WAAW,CAAC,GAAG,MAAM,CAAC;CAC1D;AAED;;;;GAIG;AACH,wBAAgB,4BAA4B,CAC1C,MAAM,EAAE,6BAA6B,GACpC,mBAAmB,CAKrB;AAED;;;GAGG;AACH,wBAAgB,8BAA8B,CAC5C,SAAS,EAAE,uBAAuB,CAAC,WAAW,CAAC,GAAG,MAAM,GACvD,qBAAqB,CAEvB"}
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Fixed mapping table: preset → concrete per-provider values.
3
+ * This is the single authority for preset compilation; the resolver and skills
4
+ * must not re-map these values.
5
+ *
6
+ * Design notes:
7
+ * - cost-conscious holds Claude at `sonnet` (no Haiku reviewers by default).
8
+ * - The table is `as const` to preserve literal types for downstream consumers.
9
+ */
10
+ export const DISPATCH_CEILING_PRESETS = {
11
+ balanced: { codex: 'high', claude: 'sonnet' },
12
+ maximum: { codex: 'xhigh', claude: 'opus' },
13
+ 'cost-conscious': { codex: 'medium', claude: 'sonnet' },
14
+ };
15
+ /**
16
+ * Compile a preset label into concrete per-provider values.
17
+ * The returned `preset` field is provenance only; runtime dispatch reads
18
+ * only `providers`.
19
+ */
20
+ export function compileDispatchCeilingPreset(preset) {
21
+ return {
22
+ preset,
23
+ providers: { ...DISPATCH_CEILING_PRESETS[preset] },
24
+ };
25
+ }
26
+ /**
27
+ * Pass through advanced/manual per-provider values with no `preset` key.
28
+ * Advanced selections do not set a preset so the persisted shape omits it.
29
+ */
30
+ export function compileAdvancedDispatchCeiling(providers) {
31
+ return { providers };
32
+ }
@@ -0,0 +1,2 @@
1
+ export declare function parseJsonConfig(raw: string, configPath: string): unknown;
2
+ //# sourceMappingURL=json.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"json.d.ts","sourceRoot":"","sources":["../../src/config/json.ts"],"names":[],"mappings":"AAYA,wBAAgB,eAAe,CAAC,GAAG,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,GAAG,OAAO,CAcxE"}
@@ -0,0 +1,29 @@
1
+ import { parse as parseJsonc, printParseErrorCode, } from 'jsonc-parser';
2
+ const JSON_CONFIG_PARSE_OPTIONS = {
3
+ allowTrailingComma: true,
4
+ disallowComments: true,
5
+ };
6
+ export function parseJsonConfig(raw, configPath) {
7
+ const errors = [];
8
+ const parsed = parseJsonc(raw, errors, JSON_CONFIG_PARSE_OPTIONS);
9
+ if (errors.length > 0) {
10
+ const details = errors
11
+ .map((error) => formatParseError(raw, error))
12
+ .join('; ');
13
+ throw new SyntaxError(`Config at ${configPath} is not valid JSON: ${details}`);
14
+ }
15
+ return parsed;
16
+ }
17
+ function formatParseError(raw, error) {
18
+ const location = offsetToLineColumn(raw, error.offset);
19
+ return `${printParseErrorCode(error.error)} at ${location.line}:${location.column}`;
20
+ }
21
+ function offsetToLineColumn(raw, offset) {
22
+ const prefix = raw.slice(0, offset);
23
+ const lines = prefix.split('\n');
24
+ const lastLine = lines[lines.length - 1] ?? '';
25
+ return {
26
+ line: lines.length,
27
+ column: lastLine.length + 1,
28
+ };
29
+ }
@@ -22,9 +22,13 @@ export type WorkflowReviewExecutionModel = 'subagent' | 'inline' | 'fresh-sessio
22
22
  export type WorkflowDesignMode = 'collaborative' | 'selective' | 'draft';
23
23
  export type WorkflowCodexDispatchCeiling = 'low' | 'medium' | 'high' | 'xhigh';
24
24
  export type WorkflowClaudeDispatchCeiling = 'haiku' | 'sonnet' | 'opus';
25
+ export type WorkflowDispatchCeilingPreset = 'balanced' | 'maximum' | 'cost-conscious';
25
26
  export interface WorkflowDispatchCeiling {
26
- codex?: WorkflowCodexDispatchCeiling;
27
- claude?: WorkflowClaudeDispatchCeiling;
27
+ preset?: WorkflowDispatchCeilingPreset;
28
+ providers?: {
29
+ codex?: WorkflowCodexDispatchCeiling;
30
+ claude?: WorkflowClaudeDispatchCeiling;
31
+ };
28
32
  }
29
33
  export interface OatWorkflowConfig {
30
34
  hillCheckpointDefault?: WorkflowHillCheckpointDefault;
@@ -37,6 +41,9 @@ export interface OatWorkflowConfig {
37
41
  designMode?: WorkflowDesignMode;
38
42
  dispatchCeiling?: WorkflowDispatchCeiling;
39
43
  }
44
+ export declare const VALID_CODEX_DISPATCH_CEILINGS: readonly WorkflowCodexDispatchCeiling[];
45
+ export declare const VALID_CLAUDE_DISPATCH_CEILINGS: readonly WorkflowClaudeDispatchCeiling[];
46
+ export declare const VALID_DISPATCH_CEILING_PRESETS: readonly WorkflowDispatchCeilingPreset[];
40
47
  export type OatToolsConfig = Partial<Record<'core' | 'ideas' | 'docs' | 'workflows' | 'utility' | 'project-management' | 'research' | 'brainstorm', boolean>>;
41
48
  export interface OatConfig {
42
49
  version: number;
@@ -1 +1 @@
1
- {"version":3,"file":"oat-config.d.ts","sourceRoot":"","sources":["../../src/config/oat-config.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"oat-config.d.ts","sourceRoot":"","sources":["../../src/config/oat-config.ts"],"names":[],"mappings":"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"}
@@ -3,6 +3,7 @@ import { readFile } from 'node:fs/promises';
3
3
  import { basename, isAbsolute, join, relative, resolve, sep } from 'node:path';
4
4
  import { atomicWriteJson, dirExists, fileExists } from '../fs/io.js';
5
5
  import { normalizeToPosixPath } from '../fs/paths.js';
6
+ import { parseJsonConfig } from './json.js';
6
7
  const VALID_HILL_CHECKPOINT_DEFAULTS = ['every', 'final'];
7
8
  const VALID_POST_IMPLEMENT_SEQUENCES = ['wait', 'summary', 'pr', 'docs-pr'];
8
9
  const VALID_REVIEW_EXECUTION_MODELS = [
@@ -15,13 +16,9 @@ const VALID_DESIGN_MODES = [
15
16
  'selective',
16
17
  'draft',
17
18
  ];
18
- const VALID_CODEX_DISPATCH_CEILINGS = [
19
- 'low',
20
- 'medium',
21
- 'high',
22
- 'xhigh',
23
- ];
24
- const VALID_CLAUDE_DISPATCH_CEILINGS = ['haiku', 'sonnet', 'opus'];
19
+ export const VALID_CODEX_DISPATCH_CEILINGS = ['low', 'medium', 'high', 'xhigh'];
20
+ export const VALID_CLAUDE_DISPATCH_CEILINGS = ['haiku', 'sonnet', 'opus'];
21
+ export const VALID_DISPATCH_CEILING_PRESETS = ['balanced', 'maximum', 'cost-conscious'];
25
22
  function normalizeWorkflowConfig(parsed) {
26
23
  if (!isRecord(parsed)) {
27
24
  return undefined;
@@ -60,15 +57,26 @@ function normalizeWorkflowConfig(parsed) {
60
57
  }
61
58
  if (isRecord(parsed.dispatchCeiling)) {
62
59
  const dispatchCeiling = {};
63
- if (typeof parsed.dispatchCeiling.codex === 'string' &&
64
- VALID_CODEX_DISPATCH_CEILINGS.includes(parsed.dispatchCeiling.codex)) {
65
- dispatchCeiling.codex = parsed.dispatchCeiling
66
- .codex;
60
+ if (typeof parsed.dispatchCeiling.preset === 'string' &&
61
+ VALID_DISPATCH_CEILING_PRESETS.includes(parsed.dispatchCeiling.preset)) {
62
+ dispatchCeiling.preset = parsed.dispatchCeiling
63
+ .preset;
67
64
  }
68
- if (typeof parsed.dispatchCeiling.claude === 'string' &&
69
- VALID_CLAUDE_DISPATCH_CEILINGS.includes(parsed.dispatchCeiling.claude)) {
70
- dispatchCeiling.claude = parsed.dispatchCeiling
71
- .claude;
65
+ if (isRecord(parsed.dispatchCeiling.providers)) {
66
+ const providers = {};
67
+ if (typeof parsed.dispatchCeiling.providers.codex === 'string' &&
68
+ VALID_CODEX_DISPATCH_CEILINGS.includes(parsed.dispatchCeiling.providers.codex)) {
69
+ providers.codex = parsed.dispatchCeiling.providers
70
+ .codex;
71
+ }
72
+ if (typeof parsed.dispatchCeiling.providers.claude === 'string' &&
73
+ VALID_CLAUDE_DISPATCH_CEILINGS.includes(parsed.dispatchCeiling.providers.claude)) {
74
+ providers.claude = parsed.dispatchCeiling.providers
75
+ .claude;
76
+ }
77
+ if (Object.keys(providers).length > 0) {
78
+ dispatchCeiling.providers = providers;
79
+ }
72
80
  }
73
81
  if (Object.keys(dispatchCeiling).length > 0) {
74
82
  next.dispatchCeiling = dispatchCeiling;
@@ -284,7 +292,7 @@ export async function readOatConfig(repoRoot) {
284
292
  const configPath = getConfigPath(repoRoot);
285
293
  try {
286
294
  const raw = await readFile(configPath, 'utf8');
287
- return normalizeOatConfig(JSON.parse(raw));
295
+ return normalizeOatConfig(parseJsonConfig(raw, configPath));
288
296
  }
289
297
  catch (error) {
290
298
  if (isMissingFileError(error)) {
@@ -297,7 +305,7 @@ export async function readOatLocalConfig(repoRoot) {
297
305
  const configPath = getLocalConfigPath(repoRoot);
298
306
  try {
299
307
  const raw = await readFile(configPath, 'utf8');
300
- return normalizeOatLocalConfig(repoRoot, JSON.parse(raw));
308
+ return normalizeOatLocalConfig(repoRoot, parseJsonConfig(raw, configPath));
301
309
  }
302
310
  catch (error) {
303
311
  if (isMissingFileError(error)) {
@@ -377,7 +385,7 @@ export async function readUserConfig(userConfigDir) {
377
385
  const configPath = getUserConfigPath(userConfigDir);
378
386
  try {
379
387
  const raw = await readFile(configPath, 'utf8');
380
- return normalizeUserConfig(JSON.parse(raw));
388
+ return normalizeUserConfig(parseJsonConfig(raw, configPath));
381
389
  }
382
390
  catch (error) {
383
391
  if (isMissingFileError(error)) {
@@ -1 +1 @@
1
- {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,oBAAoB,GAC5B,QAAQ,GACR,OAAO,GACP,MAAM,GACN,KAAK,GACL,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,kCAAkC;IACjD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAChE;AAwED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,SAAS,GAAE,OAAO,CAAC,kCAAkC,CAAM,GAC1D,OAAO,CAAC,cAAc,CAAC,CAgFzB"}
1
+ {"version":3,"file":"resolve.d.ts","sourceRoot":"","sources":["../../src/config/resolve.ts"],"names":[],"mappings":"AAAA,OAAO,EAIL,KAAK,SAAS,EACd,KAAK,cAAc,EACnB,KAAK,UAAU,EAChB,MAAM,cAAc,CAAC;AAEtB,MAAM,MAAM,oBAAoB,GAC5B,QAAQ,GACR,OAAO,GACP,MAAM,GACN,KAAK,GACL,SAAS,CAAC;AAEd,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,OAAO,CAAC;IACf,MAAM,EAAE,oBAAoB,CAAC;CAC9B;AAED,MAAM,WAAW,cAAc;IAC7B,MAAM,EAAE,SAAS,CAAC;IAClB,KAAK,EAAE,cAAc,CAAC;IACtB,IAAI,EAAE,UAAU,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CAAC;CAC5C;AAED,MAAM,WAAW,kCAAkC;IACjD,aAAa,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,SAAS,CAAC,CAAC;IACxD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IAClE,cAAc,EAAE,CAAC,aAAa,EAAE,MAAM,KAAK,OAAO,CAAC,UAAU,CAAC,CAAC;CAChE;AA2ED,wBAAsB,sBAAsB,CAC1C,QAAQ,EAAE,MAAM,EAChB,aAAa,EAAE,MAAM,EACrB,GAAG,GAAE,MAAM,CAAC,UAAwB,EACpC,SAAS,GAAE,OAAO,CAAC,kCAAkC,CAAM,GAC1D,OAAO,CAAC,cAAc,CAAC,CAgFzB"}
@@ -54,8 +54,11 @@ const DEFAULT_WORKFLOW_CONFIG = {
54
54
  autoNarrowReReviewScope: null,
55
55
  designMode: null,
56
56
  dispatchCeiling: {
57
- codex: null,
58
- claude: null,
57
+ preset: null,
58
+ providers: {
59
+ codex: null,
60
+ claude: null,
61
+ },
59
62
  },
60
63
  },
61
64
  };
@@ -1 +1 @@
1
- {"version":3,"file":"sync-config.d.ts","sourceRoot":"","sources":["../../src/config/sync-config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,oBAAoB;;;;;;;;;EAGxB,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI3B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEtE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,GAAG;IAC1D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,UAIjC,CAAC;AAoDF,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,UAAgC,GACzC,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,UAAU,CAAC,CAWrB;AAED,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,UAAU,CAAC,CAcrB"}
1
+ {"version":3,"file":"sync-config.d.ts","sourceRoot":"","sources":["../../src/config/sync-config.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAIxB,QAAA,MAAM,oBAAoB;;;;;;;;;EAGxB,CAAC;AAEH,eAAO,MAAM,gBAAgB;;;;;;;;;;;;;;;;;;;;;;;;;;;EAI3B,CAAC;AAEH,MAAM,MAAM,kBAAkB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,oBAAoB,CAAC,CAAC;AAEtE,MAAM,MAAM,UAAU,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,gBAAgB,CAAC,GAAG;IAC1D,SAAS,EAAE,MAAM,CAAC,MAAM,EAAE,kBAAkB,CAAC,CAAC;CAC/C,CAAC;AAEF,eAAO,MAAM,mBAAmB,EAAE,UAIjC,CAAC;AAoDF,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,QAAQ,GAAE,UAAgC,GACzC,OAAO,CAAC,UAAU,CAAC,CA4BrB;AAED,wBAAsB,cAAc,CAClC,UAAU,EAAE,MAAM,EAClB,MAAM,EAAE,UAAU,GACjB,OAAO,CAAC,UAAU,CAAC,CAWrB;AAED,wBAAsB,kBAAkB,CACtC,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,EACpB,OAAO,EAAE,OAAO,GACf,OAAO,CAAC,UAAU,CAAC,CAcrB"}
@@ -3,6 +3,7 @@ import { CliError } from '../errors/index.js';
3
3
  import { atomicWriteJson } from '../fs/io.js';
4
4
  import { SyncStrategySchema } from '../shared/types.js';
5
5
  import { z } from 'zod';
6
+ import { parseJsonConfig } from './json.js';
6
7
  const ProviderConfigSchema = z.object({
7
8
  strategy: SyncStrategySchema.optional(),
8
9
  enabled: z.boolean().optional(),
@@ -37,7 +38,7 @@ function normalizeConfig(config, defaults) {
37
38
  function parseSyncConfig(raw, configPath) {
38
39
  let parsed;
39
40
  try {
40
- parsed = JSON.parse(raw);
41
+ parsed = parseJsonConfig(raw, configPath);
41
42
  }
42
43
  catch {
43
44
  throw new CliError(`Sync config at ${configPath} is not valid JSON. Fix the file and retry.`);
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Provider ceiling adapter registry.
3
+ *
4
+ * Each adapter is the single source of truth for *what a provider can do* with a
5
+ * dispatch ceiling: whether it can enforce one, by what mechanism, the valid
6
+ * value set, and how to compile a ceiling value into dispatch arguments for a
7
+ * given role. The resolver (`oat project dispatch-ceiling resolve`) joins stored
8
+ * ceiling intent with these adapters to decide enforced/advisory/unsupported and
9
+ * to produce concrete dispatch args — skills never re-implement this logic.
10
+ *
11
+ * Codex enforces via sync-time pinned role variants
12
+ * (`oat-phase-implementer-<effort>` / `oat-reviewer-<effort>`); the adapter only
13
+ * *consumes* those existing names and never generates new variant files. Claude
14
+ * enforces via the per-call Task `model` argument (no variant files). Every other
15
+ * provider is advisory by default.
16
+ */
17
+ export type EnforcementMechanism = 'pinned-variant' | 'model-arg' | 'none';
18
+ export type CeilingRole = 'implementer' | 'reviewer';
19
+ export interface CeilingCompileContext {
20
+ /** The orchestrator's own tier, used to detect above-orchestrator upgrades. */
21
+ orchestratorTier?: string;
22
+ }
23
+ export type CeilingDispatchArgs = {
24
+ variant: string;
25
+ } | {
26
+ model: string;
27
+ } | null;
28
+ export interface ProviderCeilingAdapter {
29
+ provider: string;
30
+ supportsCeiling: boolean;
31
+ validValues: string[];
32
+ mechanism: EnforcementMechanism;
33
+ /**
34
+ * Compile a ceiling value into dispatch args for the given role, or `null`
35
+ * when the value is invalid or the provider is advisory/unsupported.
36
+ */
37
+ compileToDispatchArgs(value: string, role: CeilingRole, ctx: CeilingCompileContext): CeilingDispatchArgs;
38
+ /**
39
+ * Whether dispatch must verify the actual model/tier before reporting
40
+ * `enforced`. Only the upgrade path (requested tier > orchestrator tier) needs
41
+ * verification; cap-down and lateral requests never do.
42
+ */
43
+ verifyOnDispatch(value: string, ctx: CeilingCompileContext): boolean;
44
+ }
45
+ /** Claude tier order, low → high, for above-orchestrator comparison. */
46
+ export declare const CLAUDE_TIER_ORDER: readonly string[];
47
+ /**
48
+ * Look up the ceiling adapter for a provider. Unknown providers fall back to an
49
+ * advisory no-op adapter (`supportsCeiling: false`, `mechanism: 'none'`,
50
+ * `compileToDispatchArgs → null`).
51
+ */
52
+ export declare function getCeilingAdapter(provider: string): ProviderCeilingAdapter;
53
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../../src/providers/ceiling/registry.ts"],"names":[],"mappings":"AAKA;;;;;;;;;;;;;;;GAeG;AAEH,MAAM,MAAM,oBAAoB,GAAG,gBAAgB,GAAG,WAAW,GAAG,MAAM,CAAC;AAE3E,MAAM,MAAM,WAAW,GAAG,aAAa,GAAG,UAAU,CAAC;AAErD,MAAM,WAAW,qBAAqB;IACpC,+EAA+E;IAC/E,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,MAAM,mBAAmB,GAC3B;IAAE,OAAO,EAAE,MAAM,CAAA;CAAE,GACnB;IAAE,KAAK,EAAE,MAAM,CAAA;CAAE,GACjB,IAAI,CAAC;AAET,MAAM,WAAW,sBAAsB;IACrC,QAAQ,EAAE,MAAM,CAAC;IACjB,eAAe,EAAE,OAAO,CAAC;IACzB,WAAW,EAAE,MAAM,EAAE,CAAC;IACtB,SAAS,EAAE,oBAAoB,CAAC;IAChC;;;OAGG;IACH,qBAAqB,CACnB,KAAK,EAAE,MAAM,EACb,IAAI,EAAE,WAAW,EACjB,GAAG,EAAE,qBAAqB,GACzB,mBAAmB,CAAC;IACvB;;;;OAIG;IACH,gBAAgB,CAAC,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,qBAAqB,GAAG,OAAO,CAAC;CACtE;AAMD,wEAAwE;AACxE,eAAO,MAAM,iBAAiB,EAAE,SAAS,MAAM,EAAgC,CAAC;AA0EhF;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,GAAG,sBAAsB,CAE1E"}