@open-agent-toolkit/cli 0.1.41 → 0.1.43
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/assets/agents/oat-phase-implementer.md +13 -9
- package/assets/agents/oat-reviewer.md +16 -12
- package/assets/docs/cli-utilities/configuration.md +51 -35
- package/assets/docs/cli-utilities/workflow-gates.md +55 -0
- package/assets/docs/reference/cli-reference.md +1 -1
- package/assets/docs/reference/oat-directory-structure.md +26 -24
- package/assets/docs/workflows/projects/artifacts.md +22 -0
- package/assets/docs/workflows/projects/dispatch-ceiling.md +128 -85
- package/assets/docs/workflows/projects/hill-checkpoints.md +2 -0
- package/assets/docs/workflows/projects/implementation-execution.md +70 -46
- package/assets/docs/workflows/projects/index.md +2 -2
- package/assets/docs/workflows/projects/lifecycle.md +17 -2
- package/assets/docs/workflows/projects/reviews.md +17 -0
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +256 -95
- package/assets/skills/oat-project-plan/SKILL.md +60 -29
- package/assets/skills/oat-project-plan-writing/SKILL.md +10 -10
- package/assets/skills/oat-project-quick-start/SKILL.md +60 -29
- package/assets/skills/oat-project-review-provide/SKILL.md +25 -13
- package/assets/skills/oat-project-review-receive/SKILL.md +20 -10
- package/assets/skills/oat-review-provide/SKILL.md +14 -12
- package/assets/skills/oat-review-provide/references/review-artifact-template.md +1 -1
- package/assets/templates/plan.md +4 -4
- package/assets/templates/state.md +7 -3
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +66 -3
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +15 -3
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +296 -49
- package/dist/commands/review/latest.d.ts.map +1 -1
- package/dist/commands/review/latest.js +5 -2
- package/dist/commands/shared/frontmatter.d.ts +11 -0
- package/dist/commands/shared/frontmatter.d.ts.map +1 -1
- package/dist/commands/shared/frontmatter.js +15 -0
- package/dist/config/dispatch-ceiling-preset.d.ts +37 -1
- package/dist/config/dispatch-ceiling-preset.d.ts.map +1 -1
- package/dist/config/dispatch-ceiling-preset.js +20 -0
- package/dist/config/oat-config.d.ts +10 -1
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +20 -1
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +4 -0
- package/dist/providers/ceiling/registry.d.ts.map +1 -1
- package/dist/providers/ceiling/registry.js +6 -1
- package/dist/providers/codex/codec/sync-extension.js +1 -1
- package/package.json +2 -2
|
@@ -3,7 +3,8 @@ import { isAbsolute, join } from 'node:path';
|
|
|
3
3
|
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
|
-
import {
|
|
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
8
|
import { resolveEffectiveConfig, } from '../../../config/resolve.js';
|
|
8
9
|
import { resolveProjectRoot } from '../../../fs/paths.js';
|
|
9
10
|
import TOML from '@iarna/toml';
|
|
@@ -11,15 +12,10 @@ import { getCeilingAdapter, } from '../../../providers/ceiling/registry.js';
|
|
|
11
12
|
import { Command } from 'commander';
|
|
12
13
|
import YAML from 'yaml';
|
|
13
14
|
const CODEX_VALUES = [
|
|
14
|
-
|
|
15
|
-
'medium',
|
|
16
|
-
'high',
|
|
17
|
-
'xhigh',
|
|
15
|
+
...VALID_CODEX_DISPATCH_CEILINGS,
|
|
18
16
|
];
|
|
19
17
|
const CLAUDE_VALUES = [
|
|
20
|
-
|
|
21
|
-
'sonnet',
|
|
22
|
-
'opus',
|
|
18
|
+
...VALID_CLAUDE_DISPATCH_CEILINGS,
|
|
23
19
|
];
|
|
24
20
|
const DEFAULT_DEPENDENCIES = {
|
|
25
21
|
buildCommandContext,
|
|
@@ -104,7 +100,28 @@ function providerLabel(provider) {
|
|
|
104
100
|
function resolveTargetProjectPath(repoRoot, projectPath) {
|
|
105
101
|
return isAbsolute(projectPath) ? projectPath : join(repoRoot, projectPath);
|
|
106
102
|
}
|
|
107
|
-
function
|
|
103
|
+
function isValidManagedPolicy(value) {
|
|
104
|
+
return (typeof value === 'string' &&
|
|
105
|
+
VALID_MANAGED_DISPATCH_POLICIES.includes(value));
|
|
106
|
+
}
|
|
107
|
+
function validManagedPolicyList() {
|
|
108
|
+
return VALID_MANAGED_DISPATCH_POLICIES.join(', ');
|
|
109
|
+
}
|
|
110
|
+
function validProviderValueList(provider) {
|
|
111
|
+
return providerValueOrder(provider)?.join(', ') ?? 'none';
|
|
112
|
+
}
|
|
113
|
+
function compiledPolicyValueForProvider(provider, compiled) {
|
|
114
|
+
if (!('providers' in compiled)) {
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
const value = compiled.providers[provider];
|
|
118
|
+
return isValidProviderValue(provider, value) ? value : null;
|
|
119
|
+
}
|
|
120
|
+
function invalidProjectPolicyMessage(value) {
|
|
121
|
+
const actual = typeof value === 'string' ? value : String(value);
|
|
122
|
+
return `Invalid project dispatch policy "${actual}". Valid managed policies: ${validManagedPolicyList()}. Use mode "inherit" for host defaults.`;
|
|
123
|
+
}
|
|
124
|
+
function readProjectDispatchPolicy(provider, content) {
|
|
108
125
|
const frontmatter = getFrontmatterBlock(content);
|
|
109
126
|
if (!frontmatter) {
|
|
110
127
|
return null;
|
|
@@ -113,6 +130,62 @@ function readProjectDispatchCeiling(provider, content) {
|
|
|
113
130
|
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
114
131
|
return null;
|
|
115
132
|
}
|
|
133
|
+
const policy = parsed['oat_dispatch_policy'];
|
|
134
|
+
if (!policy || typeof policy !== 'object' || Array.isArray(policy)) {
|
|
135
|
+
return null;
|
|
136
|
+
}
|
|
137
|
+
const policyRecord = policy;
|
|
138
|
+
const mode = policyRecord['mode'];
|
|
139
|
+
if (mode === 'inherit') {
|
|
140
|
+
return {
|
|
141
|
+
mode,
|
|
142
|
+
policy: null,
|
|
143
|
+
value: null,
|
|
144
|
+
source: 'project-state',
|
|
145
|
+
preset: null,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
if (mode !== 'managed') {
|
|
149
|
+
const actual = typeof mode === 'string' ? mode : String(mode);
|
|
150
|
+
throw new Error(`Invalid project dispatch policy mode "${actual}". Valid modes: managed, inherit.`);
|
|
151
|
+
}
|
|
152
|
+
const policyValue = policyRecord['policy'];
|
|
153
|
+
if (!isValidManagedPolicy(policyValue)) {
|
|
154
|
+
throw new Error(invalidProjectPolicyMessage(policyValue));
|
|
155
|
+
}
|
|
156
|
+
const compiled = compileDispatchPolicyPreset(policyValue);
|
|
157
|
+
if (policyValue === 'uncapped') {
|
|
158
|
+
return {
|
|
159
|
+
mode,
|
|
160
|
+
policy: policyValue,
|
|
161
|
+
value: null,
|
|
162
|
+
source: 'project-state',
|
|
163
|
+
preset: policyValue,
|
|
164
|
+
};
|
|
165
|
+
}
|
|
166
|
+
const providers = policyRecord['providers'];
|
|
167
|
+
const explicitValue = providers && typeof providers === 'object' && !Array.isArray(providers)
|
|
168
|
+
? providers[provider]
|
|
169
|
+
: null;
|
|
170
|
+
const providerOrder = providerValueOrder(provider);
|
|
171
|
+
if (providerOrder &&
|
|
172
|
+
explicitValue !== null &&
|
|
173
|
+
explicitValue !== undefined &&
|
|
174
|
+
!isValidProviderValue(provider, explicitValue)) {
|
|
175
|
+
throw new Error(`Invalid project dispatch policy provider value "${String(explicitValue)}" for ${provider}. Valid values: ${validProviderValueList(provider)}.`);
|
|
176
|
+
}
|
|
177
|
+
const value = isValidProviderValue(provider, explicitValue)
|
|
178
|
+
? explicitValue
|
|
179
|
+
: compiledPolicyValueForProvider(provider, compiled);
|
|
180
|
+
return {
|
|
181
|
+
mode,
|
|
182
|
+
policy: policyValue,
|
|
183
|
+
value,
|
|
184
|
+
source: 'project-state',
|
|
185
|
+
preset: policyValue,
|
|
186
|
+
};
|
|
187
|
+
}
|
|
188
|
+
function readLegacyProjectDispatchCeiling(provider, parsed) {
|
|
116
189
|
const ceiling = parsed['oat_dispatch_ceiling'];
|
|
117
190
|
if (!ceiling || typeof ceiling !== 'object' || Array.isArray(ceiling)) {
|
|
118
191
|
return null;
|
|
@@ -130,21 +203,37 @@ function readProjectDispatchCeiling(provider, content) {
|
|
|
130
203
|
}
|
|
131
204
|
const presetValue = ceilingRecord['preset'];
|
|
132
205
|
return {
|
|
206
|
+
mode: 'managed',
|
|
207
|
+
policy: 'legacy-ceiling',
|
|
133
208
|
value,
|
|
209
|
+
source: 'project-state',
|
|
134
210
|
preset: typeof presetValue === 'string' ? presetValue : null,
|
|
135
211
|
};
|
|
136
212
|
}
|
|
213
|
+
function readProjectDispatchCeiling(provider, content) {
|
|
214
|
+
const frontmatter = getFrontmatterBlock(content);
|
|
215
|
+
if (!frontmatter) {
|
|
216
|
+
return null;
|
|
217
|
+
}
|
|
218
|
+
const parsed = YAML.parse(frontmatter);
|
|
219
|
+
if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
|
|
220
|
+
return null;
|
|
221
|
+
}
|
|
222
|
+
return (readProjectDispatchPolicy(provider, content) ??
|
|
223
|
+
readLegacyProjectDispatchCeiling(provider, parsed));
|
|
224
|
+
}
|
|
137
225
|
async function resolveProjectStateCeiling(provider, projectPath, dependencies) {
|
|
138
226
|
if (!projectPath) {
|
|
139
227
|
return null;
|
|
140
228
|
}
|
|
229
|
+
let content;
|
|
141
230
|
try {
|
|
142
|
-
|
|
143
|
-
return readProjectDispatchCeiling(provider, content);
|
|
231
|
+
content = await dependencies.readFile(join(projectPath, 'state.md'));
|
|
144
232
|
}
|
|
145
233
|
catch {
|
|
146
234
|
return null;
|
|
147
235
|
}
|
|
236
|
+
return readProjectDispatchCeiling(provider, content);
|
|
148
237
|
}
|
|
149
238
|
async function resolveProjectPath(repoRoot, dependencies, options) {
|
|
150
239
|
if (options.projectPath) {
|
|
@@ -156,20 +245,115 @@ async function resolveProjectPath(repoRoot, dependencies, options) {
|
|
|
156
245
|
}
|
|
157
246
|
return join(repoRoot, activeProject.path);
|
|
158
247
|
}
|
|
159
|
-
function
|
|
248
|
+
function isConfigCandidateSource(source) {
|
|
249
|
+
return source !== undefined && source !== 'default';
|
|
250
|
+
}
|
|
251
|
+
function configSourcePrecedence(source) {
|
|
252
|
+
switch (source) {
|
|
253
|
+
case 'env':
|
|
254
|
+
return 4;
|
|
255
|
+
case 'local':
|
|
256
|
+
return 3;
|
|
257
|
+
case 'shared':
|
|
258
|
+
return 2;
|
|
259
|
+
case 'user':
|
|
260
|
+
return 1;
|
|
261
|
+
case 'default':
|
|
262
|
+
return 0;
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
function lowerPrecedenceConfigSource(left, right) {
|
|
266
|
+
return configSourcePrecedence(left) <= configSourcePrecedence(right)
|
|
267
|
+
? left
|
|
268
|
+
: right;
|
|
269
|
+
}
|
|
270
|
+
function stripConfigCandidateSource(candidate) {
|
|
271
|
+
return {
|
|
272
|
+
mode: candidate.mode,
|
|
273
|
+
policy: candidate.policy,
|
|
274
|
+
value: candidate.value,
|
|
275
|
+
source: candidate.source,
|
|
276
|
+
preset: candidate.preset,
|
|
277
|
+
};
|
|
278
|
+
}
|
|
279
|
+
function readResolvedConfigPolicyCandidate(provider, resolvedConfig) {
|
|
280
|
+
const modeEntry = resolvedConfig.resolved['workflow.dispatchPolicy.mode'];
|
|
281
|
+
const policyEntry = resolvedConfig.resolved['workflow.dispatchPolicy.policy'];
|
|
282
|
+
if (modeEntry?.value === 'inherit' &&
|
|
283
|
+
isConfigCandidateSource(modeEntry.source)) {
|
|
284
|
+
const source = configSourceToCeilingSource(modeEntry.source);
|
|
285
|
+
if (source === null) {
|
|
286
|
+
return null;
|
|
287
|
+
}
|
|
288
|
+
return {
|
|
289
|
+
mode: 'inherit',
|
|
290
|
+
policy: null,
|
|
291
|
+
value: null,
|
|
292
|
+
source,
|
|
293
|
+
preset: null,
|
|
294
|
+
configSource: modeEntry.source,
|
|
295
|
+
};
|
|
296
|
+
}
|
|
297
|
+
if (modeEntry?.value === 'managed' &&
|
|
298
|
+
isValidManagedPolicy(policyEntry?.value) &&
|
|
299
|
+
isConfigCandidateSource(modeEntry.source) &&
|
|
300
|
+
isConfigCandidateSource(policyEntry?.source)) {
|
|
301
|
+
const policy = policyEntry.value;
|
|
302
|
+
const compiled = compileDispatchPolicyPreset(policy);
|
|
303
|
+
const configSource = lowerPrecedenceConfigSource(modeEntry.source, policyEntry.source);
|
|
304
|
+
const source = configSourceToCeilingSource(configSource);
|
|
305
|
+
if (source === null) {
|
|
306
|
+
return null;
|
|
307
|
+
}
|
|
308
|
+
return {
|
|
309
|
+
mode: 'managed',
|
|
310
|
+
policy,
|
|
311
|
+
value: compiledPolicyValueForProvider(provider, compiled),
|
|
312
|
+
source,
|
|
313
|
+
preset: policy,
|
|
314
|
+
configSource,
|
|
315
|
+
};
|
|
316
|
+
}
|
|
317
|
+
return null;
|
|
318
|
+
}
|
|
319
|
+
function readResolvedLegacyConfigCeilingCandidate(provider, resolvedConfig) {
|
|
160
320
|
// Read the concrete per-provider value from the nested key. The flat
|
|
161
321
|
// `workflow.dispatchCeiling.<provider>` shape was removed in p01; never read
|
|
162
322
|
// the preset label for dispatch.
|
|
163
323
|
const entry = resolvedConfig.resolved[`workflow.dispatchCeiling.providers.${provider}`];
|
|
164
|
-
|
|
165
|
-
|
|
324
|
+
if (!entry ||
|
|
325
|
+
!isConfigCandidateSource(entry.source) ||
|
|
326
|
+
!isValidProviderValue(provider, entry.value)) {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
const source = configSourceToCeilingSource(entry.source);
|
|
330
|
+
if (source === null) {
|
|
166
331
|
return null;
|
|
167
332
|
}
|
|
168
333
|
return {
|
|
334
|
+
mode: 'managed',
|
|
335
|
+
policy: 'legacy-ceiling',
|
|
169
336
|
value: entry.value,
|
|
170
337
|
source,
|
|
338
|
+
preset: null,
|
|
339
|
+
configSource: entry.source,
|
|
171
340
|
};
|
|
172
341
|
}
|
|
342
|
+
function readResolvedConfigCeiling(provider, resolvedConfig) {
|
|
343
|
+
const policyCandidate = readResolvedConfigPolicyCandidate(provider, resolvedConfig);
|
|
344
|
+
const legacyCandidate = readResolvedLegacyConfigCeilingCandidate(provider, resolvedConfig);
|
|
345
|
+
if (!policyCandidate) {
|
|
346
|
+
return legacyCandidate ? stripConfigCandidateSource(legacyCandidate) : null;
|
|
347
|
+
}
|
|
348
|
+
if (!legacyCandidate) {
|
|
349
|
+
return stripConfigCandidateSource(policyCandidate);
|
|
350
|
+
}
|
|
351
|
+
const winner = configSourcePrecedence(policyCandidate.configSource) >=
|
|
352
|
+
configSourcePrecedence(legacyCandidate.configSource)
|
|
353
|
+
? policyCandidate
|
|
354
|
+
: legacyCandidate;
|
|
355
|
+
return stripConfigCandidateSource(winner);
|
|
356
|
+
}
|
|
173
357
|
function normalizeRole(value) {
|
|
174
358
|
return value === 'reviewer' ? 'reviewer' : 'implementer';
|
|
175
359
|
}
|
|
@@ -200,32 +384,67 @@ function normalizePreferredValue(provider, value) {
|
|
|
200
384
|
}
|
|
201
385
|
return normalized;
|
|
202
386
|
}
|
|
203
|
-
function selectDispatchValue(provider, role,
|
|
387
|
+
function selectDispatchValue(provider, role, policy, preferredValue) {
|
|
388
|
+
const baseSelection = {
|
|
389
|
+
role,
|
|
390
|
+
policyMode: policy.mode,
|
|
391
|
+
policy: policy.policy,
|
|
392
|
+
};
|
|
393
|
+
if (policy.mode === 'inherit') {
|
|
394
|
+
return {
|
|
395
|
+
...baseSelection,
|
|
396
|
+
preferredValue: null,
|
|
397
|
+
selectedValue: null,
|
|
398
|
+
capped: false,
|
|
399
|
+
selectionMode: 'inherit-default',
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
if (policy.policy === 'uncapped') {
|
|
403
|
+
return {
|
|
404
|
+
...baseSelection,
|
|
405
|
+
preferredValue: role === 'reviewer' ? null : preferredValue,
|
|
406
|
+
selectedValue: role === 'reviewer' ? null : preferredValue,
|
|
407
|
+
capped: false,
|
|
408
|
+
selectionMode: role === 'reviewer' ? 'no-review-target' : 'uncapped',
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
if (policy.value === null) {
|
|
412
|
+
return {
|
|
413
|
+
...baseSelection,
|
|
414
|
+
preferredValue: null,
|
|
415
|
+
selectedValue: null,
|
|
416
|
+
capped: false,
|
|
417
|
+
selectionMode: 'capped',
|
|
418
|
+
};
|
|
419
|
+
}
|
|
204
420
|
if (role === 'reviewer' || preferredValue === null) {
|
|
205
421
|
return {
|
|
206
|
-
|
|
422
|
+
...baseSelection,
|
|
207
423
|
preferredValue,
|
|
208
|
-
selectedValue:
|
|
424
|
+
selectedValue: policy.value,
|
|
209
425
|
capped: false,
|
|
426
|
+
selectionMode: role === 'reviewer' ? 'review-target' : 'capped',
|
|
210
427
|
};
|
|
211
428
|
}
|
|
212
429
|
const order = providerValueOrder(provider);
|
|
213
430
|
const preferredIndex = order?.indexOf(preferredValue) ?? -1;
|
|
214
|
-
const ceilingIndex = order?.indexOf(
|
|
431
|
+
const ceilingIndex = order?.indexOf(policy.value) ?? -1;
|
|
215
432
|
if (!order || preferredIndex < 0 || ceilingIndex < 0) {
|
|
216
433
|
return {
|
|
217
|
-
|
|
434
|
+
...baseSelection,
|
|
218
435
|
preferredValue,
|
|
219
|
-
selectedValue:
|
|
436
|
+
selectedValue: policy.value,
|
|
220
437
|
capped: false,
|
|
438
|
+
selectionMode: 'capped',
|
|
221
439
|
};
|
|
222
440
|
}
|
|
223
441
|
const selectedIndex = Math.min(preferredIndex, ceilingIndex);
|
|
224
442
|
return {
|
|
225
|
-
|
|
443
|
+
...baseSelection,
|
|
226
444
|
preferredValue,
|
|
227
445
|
selectedValue: order[selectedIndex],
|
|
228
446
|
capped: preferredIndex > ceilingIndex,
|
|
447
|
+
selectionMode: 'capped',
|
|
229
448
|
};
|
|
230
449
|
}
|
|
231
450
|
/**
|
|
@@ -233,9 +452,9 @@ function selectDispatchValue(provider, role, ceilingValue, preferredValue) {
|
|
|
233
452
|
* the enforcement mode, mechanism, dispatch args, and verify-on-upgrade flag.
|
|
234
453
|
* Mode is computed here at call time — it is never read from persisted state.
|
|
235
454
|
*/
|
|
236
|
-
function buildProviderResolution(provider,
|
|
455
|
+
function buildProviderResolution(provider, policy, role, orchestratorTier, preferredValue) {
|
|
237
456
|
const adapter = getCeilingAdapter(provider);
|
|
238
|
-
if (
|
|
457
|
+
if (policy === null) {
|
|
239
458
|
return {
|
|
240
459
|
value: null,
|
|
241
460
|
mode: adapter.supportsCeiling ? 'advisory' : 'unsupported',
|
|
@@ -247,14 +466,19 @@ function buildProviderResolution(provider, value, role, orchestratorTier, prefer
|
|
|
247
466
|
preferredValue,
|
|
248
467
|
selectedValue: null,
|
|
249
468
|
capped: false,
|
|
469
|
+
selectionMode: 'unresolved',
|
|
470
|
+
policyMode: null,
|
|
471
|
+
policy: null,
|
|
250
472
|
},
|
|
251
473
|
};
|
|
252
474
|
}
|
|
253
|
-
const selection = selectDispatchValue(provider, role,
|
|
254
|
-
const dispatchValue = selection.selectedValue
|
|
255
|
-
const dispatchArgs =
|
|
256
|
-
|
|
257
|
-
|
|
475
|
+
const selection = selectDispatchValue(provider, role, policy, preferredValue);
|
|
476
|
+
const dispatchValue = selection.selectedValue;
|
|
477
|
+
const dispatchArgs = dispatchValue
|
|
478
|
+
? adapter.compileToDispatchArgs(dispatchValue, role, {
|
|
479
|
+
orchestratorTier,
|
|
480
|
+
})
|
|
481
|
+
: null;
|
|
258
482
|
let mode;
|
|
259
483
|
if (!adapter.supportsCeiling) {
|
|
260
484
|
mode = 'unsupported';
|
|
@@ -266,13 +490,15 @@ function buildProviderResolution(provider, value, role, orchestratorTier, prefer
|
|
|
266
490
|
mode = 'advisory';
|
|
267
491
|
}
|
|
268
492
|
return {
|
|
269
|
-
value,
|
|
493
|
+
value: policy.value,
|
|
270
494
|
mode,
|
|
271
495
|
mechanism: adapter.mechanism,
|
|
272
496
|
dispatchArgs,
|
|
273
|
-
verifyOnDispatch:
|
|
274
|
-
|
|
275
|
-
|
|
497
|
+
verifyOnDispatch: dispatchValue
|
|
498
|
+
? adapter.verifyOnDispatch(dispatchValue, {
|
|
499
|
+
orchestratorTier,
|
|
500
|
+
})
|
|
501
|
+
: false,
|
|
276
502
|
selection,
|
|
277
503
|
};
|
|
278
504
|
}
|
|
@@ -305,7 +531,7 @@ async function resolveCodexProviderDefaultEffort(repoRoot, context, dependencies
|
|
|
305
531
|
}
|
|
306
532
|
function blockMessage(provider) {
|
|
307
533
|
const label = providerLabel(provider);
|
|
308
|
-
return `BLOCKED: ${label} dispatch
|
|
534
|
+
return `BLOCKED: ${label} dispatch policy is unresolved in non-interactive mode.\nSet workflow.dispatchPolicy.mode/workflow.dispatchPolicy.policy, workflow.dispatchCeiling.providers.${provider}, oat_dispatch_policy, or legacy oat_dispatch_ceiling.`;
|
|
309
535
|
}
|
|
310
536
|
function isNonInteractiveEnv(env) {
|
|
311
537
|
return env['OAT_NON_INTERACTIVE'] === '1';
|
|
@@ -325,7 +551,7 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
325
551
|
: 'not-applicable';
|
|
326
552
|
const preferredValue = normalizePreferredValue(provider, options.preferred);
|
|
327
553
|
const resolvedValue = await resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies);
|
|
328
|
-
const providerResolution = buildProviderResolution(provider, resolvedValue
|
|
554
|
+
const providerResolution = buildProviderResolution(provider, resolvedValue, role, orchestratorTier, preferredValue);
|
|
329
555
|
const providers = {
|
|
330
556
|
[provider]: providerResolution,
|
|
331
557
|
};
|
|
@@ -334,6 +560,8 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
334
560
|
status: 'resolved',
|
|
335
561
|
provider,
|
|
336
562
|
value: resolvedValue.value,
|
|
563
|
+
policyMode: resolvedValue.mode,
|
|
564
|
+
policy: resolvedValue.policy,
|
|
337
565
|
source: resolvedValue.source,
|
|
338
566
|
preset: resolvedValue.preset,
|
|
339
567
|
unresolved: false,
|
|
@@ -350,6 +578,8 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
350
578
|
status: shouldBlock ? 'blocked' : 'unresolved',
|
|
351
579
|
provider,
|
|
352
580
|
value: null,
|
|
581
|
+
policyMode: null,
|
|
582
|
+
policy: null,
|
|
353
583
|
source: null,
|
|
354
584
|
preset: null,
|
|
355
585
|
unresolved: true,
|
|
@@ -368,37 +598,51 @@ async function resolveDispatchCeiling(context, dependencies, options) {
|
|
|
368
598
|
async function resolveCeilingValue(provider, resolvedConfig, projectPath, dependencies) {
|
|
369
599
|
const configCeiling = readResolvedConfigCeiling(provider, resolvedConfig);
|
|
370
600
|
if (configCeiling) {
|
|
371
|
-
return
|
|
372
|
-
value: configCeiling.value,
|
|
373
|
-
source: configCeiling.source,
|
|
374
|
-
preset: null,
|
|
375
|
-
};
|
|
601
|
+
return configCeiling;
|
|
376
602
|
}
|
|
377
603
|
const projectCeiling = await resolveProjectStateCeiling(provider, projectPath, dependencies);
|
|
378
604
|
if (projectCeiling) {
|
|
379
|
-
return
|
|
380
|
-
value: projectCeiling.value,
|
|
381
|
-
source: 'project-state',
|
|
382
|
-
preset: projectCeiling.preset,
|
|
383
|
-
};
|
|
605
|
+
return projectCeiling;
|
|
384
606
|
}
|
|
385
607
|
return null;
|
|
386
608
|
}
|
|
609
|
+
function policyLabel(policy) {
|
|
610
|
+
if (policy === 'legacy-ceiling') {
|
|
611
|
+
return 'legacy capped';
|
|
612
|
+
}
|
|
613
|
+
return policy ?? 'inherit host defaults';
|
|
614
|
+
}
|
|
387
615
|
function writeHumanResolution(context, resolution) {
|
|
388
616
|
const label = providerLabel(resolution.provider);
|
|
389
617
|
if (resolution.status === 'blocked' && resolution.message) {
|
|
390
618
|
context.logger.error(resolution.message);
|
|
391
619
|
return;
|
|
392
620
|
}
|
|
393
|
-
context.logger.info(`${label} dispatch
|
|
621
|
+
context.logger.info(`${label} dispatch policy: ${policyLabel(resolution.policy)}`);
|
|
622
|
+
context.logger.info(`Resolved cap: ${resolution.value ?? 'none'}`);
|
|
394
623
|
context.logger.info(`Source: ${sourceLabel(resolution.source)}`);
|
|
395
624
|
const providerResolution = resolution.providers[resolution.provider];
|
|
396
625
|
if (providerResolution) {
|
|
397
626
|
context.logger.info(`Mode: ${providerResolution.mode} (${providerResolution.mechanism})`);
|
|
627
|
+
context.logger.info(`Selection: ${providerResolution.selection.selectionMode}`);
|
|
398
628
|
}
|
|
399
629
|
if (resolution.provider === 'codex') {
|
|
400
630
|
context.logger.info(`Codex provider default effort: ${resolution.providerDefaultEffort}`);
|
|
401
|
-
|
|
631
|
+
if (providerResolution?.selection.selectionMode === 'inherit-default') {
|
|
632
|
+
context.logger.info('Note: OAT will not select a Codex effort; base/unpinned roles resolve through the provider default.');
|
|
633
|
+
}
|
|
634
|
+
else if (providerResolution?.selection.selectionMode === 'uncapped') {
|
|
635
|
+
context.logger.info('Note: OAT will use the preferred pinned Codex variant with no configured cap. Actual host support for upward effort selection must be verified by the dispatching host.');
|
|
636
|
+
}
|
|
637
|
+
else if (providerResolution?.selection.selectionMode === 'review-target') {
|
|
638
|
+
context.logger.info('Note: Reviewer dispatch uses the configured target from the resolved capped policy.');
|
|
639
|
+
}
|
|
640
|
+
else if (providerResolution?.selection.selectionMode === 'no-review-target') {
|
|
641
|
+
context.logger.info('Note: Managed uncapped reviewer dispatch has no configured target; use the base/unpinned reviewer fallback.');
|
|
642
|
+
}
|
|
643
|
+
else {
|
|
644
|
+
context.logger.info(`Note: OAT will use pinned subagent variants up to ${resolution.value ?? 'the resolved policy cap'}. Base/unpinned roles resolve through the provider default.`);
|
|
645
|
+
}
|
|
402
646
|
if (providerResolution?.selection.selectedValue &&
|
|
403
647
|
providerResolution.selection.preferredValue) {
|
|
404
648
|
context.logger.info(`Selected Codex effort: ${providerResolution.selection.selectedValue}`);
|
|
@@ -406,6 +650,9 @@ function writeHumanResolution(context, resolution) {
|
|
|
406
650
|
}
|
|
407
651
|
else {
|
|
408
652
|
context.logger.info('Effort axis: not-applicable');
|
|
653
|
+
if (providerResolution?.selection.selectionMode === 'inherit-default') {
|
|
654
|
+
context.logger.info('Note: OAT will not select a Claude model; Task dispatch inherits host/provider behavior.');
|
|
655
|
+
}
|
|
409
656
|
if (providerResolution?.selection.selectedValue &&
|
|
410
657
|
providerResolution.selection.preferredValue) {
|
|
411
658
|
context.logger.info(`Selected dispatch value: ${providerResolution.selection.selectedValue}`);
|
|
@@ -441,12 +688,12 @@ export function createProjectDispatchCeilingCommand(overrides = {}) {
|
|
|
441
688
|
};
|
|
442
689
|
const command = new Command('dispatch-ceiling').description('Resolve OAT project dispatch ceiling metadata');
|
|
443
690
|
command.addCommand(new Command('resolve')
|
|
444
|
-
.description('Resolve dispatch
|
|
691
|
+
.description('Resolve dispatch policy for a provider')
|
|
445
692
|
.requiredOption('--provider <provider>', 'Provider name: codex or claude are enforced; any other provider resolves as advisory (unsupported)')
|
|
446
693
|
.option('--role <role>', 'Dispatch role for variant compilation: implementer (default) or reviewer')
|
|
447
694
|
.option('--orchestrator-tier <tier>', 'Orchestrator tier, used to flag verify-on-upgrade for above-orchestrator requests')
|
|
448
|
-
.option('--preferred <value>', 'Preferred implementer/fix dispatch value before
|
|
449
|
-
.option('--project-path <path>', 'Read project-state
|
|
695
|
+
.option('--preferred <value>', 'Preferred implementer/fix dispatch value before applying the resolved policy')
|
|
696
|
+
.option('--project-path <path>', 'Read project-state policy from an explicit project path')
|
|
450
697
|
.option('--preflight', 'Treat unresolved non-interactive resolution as an implementation block')
|
|
451
698
|
.option('--non-interactive', 'Force non-interactive block behavior when the ceiling is unresolved')
|
|
452
699
|
.option('--json', 'Output machine-readable JSON')
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../src/commands/review/latest.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;
|
|
1
|
+
{"version":3,"file":"latest.d.ts","sourceRoot":"","sources":["../../../src/commands/review/latest.ts"],"names":[],"mappings":"AAGA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,EAAsB,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAE7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,MAAM,gBAAgB,GAAG,SAAS,GAAG,OAAO,CAAC;AAEnD,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,IAAI,EAAE,gBAAgB,CAAC;IACvB,QAAQ,EAAE,OAAO,CAAC;IAClB,UAAU,EAAE,OAAO,CAAC;CACrB;AAQD,MAAM,WAAW,uBAAuB;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,GAAG,IAAI,CAAC;CAC7B;AAED,UAAU,wBAAwB;IAChC,mBAAmB,EAAE,CAAC,OAAO,EAAE,aAAa,KAAK,cAAc,CAAC;IAChE,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,OAAO,CAAC,MAAM,CAAC,CAAC;IACrD,kBAAkB,EAAE,CAAC,QAAQ,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;CACnE;AAsID,wBAAsB,gBAAgB,CACpC,OAAO,EAAE,uBAAuB,GAC/B,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAqF9B;AA+CD,wBAAgB,yBAAyB,CACvC,SAAS,GAAE,OAAO,CAAC,wBAAwB,CAAM,GAChD,OAAO,CAkBT"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { readdir, readFile } from 'node:fs/promises';
|
|
2
2
|
import { isAbsolute, join, relative } from 'node:path';
|
|
3
3
|
import { buildCommandContext, } from '../../app/command-context.js';
|
|
4
|
-
import { getFrontmatterBlock, getFrontmatterField, } from '../shared/frontmatter.js';
|
|
4
|
+
import { getFrontmatterBlock, getFrontmatterField, parseGeneratedTime, } from '../shared/frontmatter.js';
|
|
5
5
|
import { readGlobalOptions } from '../shared/shared.utils.js';
|
|
6
6
|
import { readOatLocalConfig } from '../../config/oat-config.js';
|
|
7
7
|
import { normalizeToPosixPath, resolveProjectRoot } from '../../fs/paths.js';
|
|
@@ -72,7 +72,7 @@ async function readReviewCandidate(repoRoot, relativePath, kind, priority, archi
|
|
|
72
72
|
if (!generatedAt) {
|
|
73
73
|
return null;
|
|
74
74
|
}
|
|
75
|
-
const generatedTime =
|
|
75
|
+
const generatedTime = parseGeneratedTime(generatedAt);
|
|
76
76
|
if (Number.isNaN(generatedTime)) {
|
|
77
77
|
return null;
|
|
78
78
|
}
|
|
@@ -99,6 +99,9 @@ function sortReviewCandidates(a, b) {
|
|
|
99
99
|
if (a.lifecycleRank !== b.lifecycleRank) {
|
|
100
100
|
return b.lifecycleRank - a.lifecycleRank;
|
|
101
101
|
}
|
|
102
|
+
// Same scope, same generated time: fall back to a stable path order. Review
|
|
103
|
+
// artifacts carry a seconds-precision `oat_generated_at`, so distinct re-gate
|
|
104
|
+
// rounds differ on generatedTime above and never reach this branch.
|
|
102
105
|
return a.path.localeCompare(b.path);
|
|
103
106
|
}
|
|
104
107
|
export async function findLatestReview(options) {
|
|
@@ -9,6 +9,17 @@ export declare function isProjectStatePhase(value: string): value is ProjectStat
|
|
|
9
9
|
export declare function isProjectStateFrontmatterField(value: string): value is ProjectStateFrontmatterField;
|
|
10
10
|
export declare function getFrontmatterBlock(content: string): string | null;
|
|
11
11
|
export declare function getFrontmatterField(frontmatter: string, field: string): string | null;
|
|
12
|
+
/**
|
|
13
|
+
* Parse an `oat_generated_at` value into a comparable epoch millisecond time.
|
|
14
|
+
*
|
|
15
|
+
* Review artifacts should carry a UTC, `Z`-suffixed timestamp
|
|
16
|
+
* (`YYYY-MM-DDTHH:MM:SSZ`). A bare date (`YYYY-MM-DD`) already parses as UTC,
|
|
17
|
+
* but a datetime with no timezone designator parses as *local* time, which
|
|
18
|
+
* mis-orders artifacts written by agents in different timezones. Treat such a
|
|
19
|
+
* value as UTC by appending `Z` so ordering is timezone-independent regardless
|
|
20
|
+
* of what the writer emitted. Returns `NaN` for unparseable input.
|
|
21
|
+
*/
|
|
22
|
+
export declare function parseGeneratedTime(value: string): number;
|
|
12
23
|
export declare function parseFrontmatterField(filePath: string, field: string): Promise<string>;
|
|
13
24
|
export declare function getSkillVersion(skillDir: string): Promise<string | null>;
|
|
14
25
|
export declare function getAgentVersion(agentPath: string): Promise<string | null>;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/frontmatter.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAE/E,eAAO,MAAM,oBAAoB,gFAOvB,CAAC;AAEX,eAAO,MAAM,gCAAgC,sgBA2BnC,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAKf;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
|
|
1
|
+
{"version":3,"file":"frontmatter.d.ts","sourceRoot":"","sources":["../../../src/commands/shared/frontmatter.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,mBAAmB,6CAA8C,CAAC;AAE/E,eAAO,MAAM,oBAAoB,gFAOvB,CAAC;AAEX,eAAO,MAAM,gCAAgC,sgBA2BnC,CAAC;AAEX,MAAM,MAAM,gBAAgB,GAAG,CAAC,OAAO,mBAAmB,CAAC,CAAC,MAAM,CAAC,CAAC;AACpE,MAAM,MAAM,iBAAiB,GAAG,CAAC,OAAO,oBAAoB,CAAC,CAAC,MAAM,CAAC,CAAC;AACtE,MAAM,MAAM,4BAA4B,GACtC,CAAC,OAAO,gCAAgC,CAAC,CAAC,MAAM,CAAC,CAAC;AAEpD,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,gBAAgB,CAE3E;AAED,wBAAgB,mBAAmB,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,IAAI,iBAAiB,CAE7E;AAED,wBAAgB,8BAA8B,CAC5C,KAAK,EAAE,MAAM,GACZ,KAAK,IAAI,4BAA4B,CAIvC;AAED,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAGlE;AAED,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,MAAM,EACnB,KAAK,EAAE,MAAM,GACZ,MAAM,GAAG,IAAI,CAKf;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAIxD;AAED,wBAAsB,qBAAqB,CACzC,QAAQ,EAAE,MAAM,EAChB,KAAK,EAAE,MAAM,GACZ,OAAO,CAAC,MAAM,CAAC,CASjB;AAED,wBAAsB,eAAe,CACnC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAQxB;AAED,wBAAsB,eAAe,CACnC,SAAS,EAAE,MAAM,GAChB,OAAO,CAAC,MAAM,GAAG,IAAI,CAAC,CAGxB"}
|
|
@@ -57,6 +57,21 @@ export function getFrontmatterField(frontmatter, field) {
|
|
|
57
57
|
return null;
|
|
58
58
|
return match[1].replace(/\s*#.*$/, '').trim();
|
|
59
59
|
}
|
|
60
|
+
/**
|
|
61
|
+
* Parse an `oat_generated_at` value into a comparable epoch millisecond time.
|
|
62
|
+
*
|
|
63
|
+
* Review artifacts should carry a UTC, `Z`-suffixed timestamp
|
|
64
|
+
* (`YYYY-MM-DDTHH:MM:SSZ`). A bare date (`YYYY-MM-DD`) already parses as UTC,
|
|
65
|
+
* but a datetime with no timezone designator parses as *local* time, which
|
|
66
|
+
* mis-orders artifacts written by agents in different timezones. Treat such a
|
|
67
|
+
* value as UTC by appending `Z` so ordering is timezone-independent regardless
|
|
68
|
+
* of what the writer emitted. Returns `NaN` for unparseable input.
|
|
69
|
+
*/
|
|
70
|
+
export function parseGeneratedTime(value) {
|
|
71
|
+
const hasTime = value.includes('T');
|
|
72
|
+
const hasZone = /(?:[zZ]|[+-]\d{2}:?\d{2})$/.test(value);
|
|
73
|
+
return Date.parse(hasTime && !hasZone ? `${value}Z` : value);
|
|
74
|
+
}
|
|
60
75
|
export async function parseFrontmatterField(filePath, field) {
|
|
61
76
|
try {
|
|
62
77
|
const content = await readFile(filePath, 'utf8');
|