@open-agent-toolkit/cli 0.1.43 → 0.1.45
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/config/dispatch-matrix-recommendation.json +23 -0
- package/assets/docs/cli-utilities/configuration.md +59 -20
- package/assets/docs/cli-utilities/workflow-gates.md +79 -16
- package/assets/docs/contributing/code.md +10 -4
- package/assets/docs/contributing/hooks-and-safety.md +23 -0
- package/assets/docs/workflows/projects/dispatch-ceiling.md +80 -10
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +53 -11
- package/assets/skills/oat-project-plan/SKILL.md +22 -3
- package/assets/skills/oat-project-quick-start/SKILL.md +22 -3
- package/assets/templates/state.md +5 -0
- package/dist/commands/config/index.d.ts +6 -0
- package/dist/commands/config/index.d.ts.map +1 -1
- package/dist/commands/config/index.js +366 -8
- package/dist/commands/doctor/index.d.ts +6 -1
- package/dist/commands/doctor/index.d.ts.map +1 -1
- package/dist/commands/doctor/index.js +138 -4
- package/dist/commands/gate/index.d.ts +39 -2
- package/dist/commands/gate/index.d.ts.map +1 -1
- package/dist/commands/gate/index.js +343 -30
- package/dist/commands/internal/cursor-current-target.d.ts +37 -0
- package/dist/commands/internal/cursor-current-target.d.ts.map +1 -0
- package/dist/commands/internal/cursor-current-target.js +228 -0
- package/dist/commands/internal/index.d.ts.map +1 -1
- package/dist/commands/internal/index.js +2 -0
- package/dist/commands/project/dispatch-ceiling/index.d.ts.map +1 -1
- package/dist/commands/project/dispatch-ceiling/index.js +359 -16
- package/dist/config/oat-config.d.ts +15 -5
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +103 -10
- package/dist/config/resolve.js +12 -0
- package/dist/manifest/manifest.types.d.ts +12 -12
- package/dist/providers/ceiling/registry.d.ts.map +1 -1
- package/dist/providers/ceiling/registry.js +16 -0
- package/dist/providers/identity/availability.d.ts +22 -0
- package/dist/providers/identity/availability.d.ts.map +1 -0
- package/dist/providers/identity/availability.js +119 -0
- package/dist/providers/identity/family.d.ts +13 -0
- package/dist/providers/identity/family.d.ts.map +1 -0
- package/dist/providers/identity/family.js +32 -0
- package/dist/providers/identity/provenance.d.ts +21 -0
- package/dist/providers/identity/provenance.d.ts.map +1 -0
- package/dist/providers/identity/provenance.js +65 -0
- package/dist/providers/identity/stamp.d.ts +35 -0
- package/dist/providers/identity/stamp.d.ts.map +1 -0
- package/dist/providers/identity/stamp.js +171 -0
- package/package.json +2 -2
|
@@ -1,13 +1,24 @@
|
|
|
1
|
+
import { readFile as readFileDefault } from 'node:fs/promises';
|
|
1
2
|
import { join } from 'node:path';
|
|
2
3
|
import { buildCommandContext } from '../../app/command-context.js';
|
|
3
4
|
import { resolveProjectsRoot } from '../shared/oat-paths.js';
|
|
5
|
+
import { confirmAction, } from '../shared/shared.prompts.js';
|
|
4
6
|
import { readGlobalOptions } from '../shared/shared.utils.js';
|
|
5
7
|
import { compileDispatchCeilingPreset } from '../../config/dispatch-ceiling-preset.js';
|
|
6
8
|
import { readOatConfig, readOatLocalConfig, readUserConfig, writeOatConfig, writeOatLocalConfig, writeUserConfig, } from '../../config/oat-config.js';
|
|
7
9
|
import { resolveEffectiveConfig, } from '../../config/resolve.js';
|
|
10
|
+
import { resolveAssetsRoot } from '../../fs/assets.js';
|
|
8
11
|
import { resolveProjectRoot } from '../../fs/paths.js';
|
|
12
|
+
import { validateMatrixCell, } from '../../providers/identity/availability.js';
|
|
9
13
|
import { Command } from 'commander';
|
|
10
14
|
import { createConfigDumpCommand } from './dump.js';
|
|
15
|
+
const DISPATCH_CEILING_PROVIDER_KEY_PREFIX = 'workflow.dispatchCeiling.providers.';
|
|
16
|
+
const DISPATCH_MATRIX_TIERS = [
|
|
17
|
+
'economy',
|
|
18
|
+
'balanced',
|
|
19
|
+
'high',
|
|
20
|
+
'frontier',
|
|
21
|
+
];
|
|
11
22
|
const KEY_ORDER = [
|
|
12
23
|
'activeIdea',
|
|
13
24
|
'activeProject',
|
|
@@ -547,10 +558,49 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
547
558
|
writeUserConfig,
|
|
548
559
|
resolveProjectsRoot,
|
|
549
560
|
resolveEffectiveConfig,
|
|
561
|
+
resolveAssetsRoot,
|
|
562
|
+
readFile: (path) => readFileDefault(path, 'utf8'),
|
|
563
|
+
confirmAction,
|
|
564
|
+
validateMatrixCell,
|
|
550
565
|
processEnv: process.env,
|
|
551
566
|
};
|
|
552
567
|
function isConfigKey(value) {
|
|
553
|
-
return KEY_ORDER.includes(value)
|
|
568
|
+
return (KEY_ORDER.includes(value) ||
|
|
569
|
+
isDispatchCeilingProviderKey(value));
|
|
570
|
+
}
|
|
571
|
+
function isDispatchCeilingProviderKey(value) {
|
|
572
|
+
return (value.startsWith(DISPATCH_CEILING_PROVIDER_KEY_PREFIX) &&
|
|
573
|
+
value.slice(DISPATCH_CEILING_PROVIDER_KEY_PREFIX.length).trim().length > 0);
|
|
574
|
+
}
|
|
575
|
+
function isDispatchMatrixTier(value) {
|
|
576
|
+
return DISPATCH_MATRIX_TIERS.includes(value);
|
|
577
|
+
}
|
|
578
|
+
function parseDispatchCeilingProviderConfigKey(key) {
|
|
579
|
+
const suffix = key.slice(DISPATCH_CEILING_PROVIDER_KEY_PREFIX.length).trim();
|
|
580
|
+
const parts = suffix.split('.').map((part) => part.trim());
|
|
581
|
+
const invalidMessage = `Invalid config key ${key}: expected workflow.dispatchCeiling.providers.<provider> or workflow.dispatchCeiling.providers.<provider>.<tier> with tier one of ${DISPATCH_MATRIX_TIERS.join(' | ')}.`;
|
|
582
|
+
if (parts.some((part) => part.length === 0)) {
|
|
583
|
+
throw new Error(invalidMessage);
|
|
584
|
+
}
|
|
585
|
+
if (parts.length === 1) {
|
|
586
|
+
const provider = parts[0];
|
|
587
|
+
if (!provider) {
|
|
588
|
+
throw new Error(invalidMessage);
|
|
589
|
+
}
|
|
590
|
+
return { provider };
|
|
591
|
+
}
|
|
592
|
+
const tier = parts[parts.length - 1];
|
|
593
|
+
if (!tier || !isDispatchMatrixTier(tier)) {
|
|
594
|
+
throw new Error(invalidMessage);
|
|
595
|
+
}
|
|
596
|
+
const provider = parts.slice(0, -1).join('.').trim();
|
|
597
|
+
if (!provider) {
|
|
598
|
+
throw new Error(invalidMessage);
|
|
599
|
+
}
|
|
600
|
+
return { provider, tier };
|
|
601
|
+
}
|
|
602
|
+
function providerNameFromConfigKey(key) {
|
|
603
|
+
return parseDispatchCeilingProviderConfigKey(key).provider;
|
|
554
604
|
}
|
|
555
605
|
function normalizeSharedRoot(value) {
|
|
556
606
|
const trimmed = value.trim();
|
|
@@ -586,6 +636,15 @@ const WORKFLOW_ENUM_VALUES = {
|
|
|
586
636
|
'fable',
|
|
587
637
|
],
|
|
588
638
|
};
|
|
639
|
+
function closedDispatchProviderValues(provider) {
|
|
640
|
+
if (provider === 'codex') {
|
|
641
|
+
return WORKFLOW_ENUM_VALUES['workflow.dispatchCeiling.providers.codex'];
|
|
642
|
+
}
|
|
643
|
+
if (provider === 'claude') {
|
|
644
|
+
return WORKFLOW_ENUM_VALUES['workflow.dispatchCeiling.providers.claude'];
|
|
645
|
+
}
|
|
646
|
+
return undefined;
|
|
647
|
+
}
|
|
589
648
|
const WORKFLOW_BOOLEAN_KEYS = new Set([
|
|
590
649
|
'workflow.archiveOnComplete',
|
|
591
650
|
'workflow.createPrOnComplete',
|
|
@@ -666,8 +725,95 @@ function parseWorkflowValue(key, rawValue) {
|
|
|
666
725
|
}
|
|
667
726
|
return normalized;
|
|
668
727
|
}
|
|
728
|
+
if (isDispatchCeilingProviderKey(key)) {
|
|
729
|
+
const { provider } = parseDispatchCeilingProviderConfigKey(key);
|
|
730
|
+
const normalized = rawValue.trim();
|
|
731
|
+
if (normalized.length === 0) {
|
|
732
|
+
throw new Error(`Invalid value for ${key}: provider values cannot be empty`);
|
|
733
|
+
}
|
|
734
|
+
const closedValues = closedDispatchProviderValues(provider);
|
|
735
|
+
if (closedValues && !closedValues.includes(normalized)) {
|
|
736
|
+
throw new Error(`Invalid value for ${key}: expected one of ${closedValues.join(' | ')}, got '${rawValue}'`);
|
|
737
|
+
}
|
|
738
|
+
return normalized;
|
|
739
|
+
}
|
|
669
740
|
throw new Error(`Unknown workflow key: ${key}`);
|
|
670
741
|
}
|
|
742
|
+
function dispatchProviderAvailabilityWarning(key, value, availability) {
|
|
743
|
+
return matrixCellAvailabilityWarning(key, value, availability);
|
|
744
|
+
}
|
|
745
|
+
const DISPATCH_MATRIX_RECOMMENDATION_ASSET = join('config', 'dispatch-matrix-recommendation.json');
|
|
746
|
+
function isRecord(value) {
|
|
747
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
748
|
+
}
|
|
749
|
+
function matrixCellAvailabilityWarning(path, value, availability) {
|
|
750
|
+
if (availability === 'valid') {
|
|
751
|
+
return null;
|
|
752
|
+
}
|
|
753
|
+
if (availability === 'unknown-value') {
|
|
754
|
+
return `${path} value '${value}' was not recognized by the provider availability oracle; saving anyway.`;
|
|
755
|
+
}
|
|
756
|
+
return `${path} value '${value}' could not be validated because the provider availability oracle is unavailable; saving anyway.`;
|
|
757
|
+
}
|
|
758
|
+
function parseDispatchMatrixRecommendation(raw) {
|
|
759
|
+
const parsed = JSON.parse(raw);
|
|
760
|
+
if (!isRecord(parsed)) {
|
|
761
|
+
throw new Error('Dispatch matrix recommendation asset must be an object.');
|
|
762
|
+
}
|
|
763
|
+
const version = typeof parsed.version === 'string' ? parsed.version.trim() : '';
|
|
764
|
+
if (!version) {
|
|
765
|
+
throw new Error('Dispatch matrix recommendation asset is missing a version.');
|
|
766
|
+
}
|
|
767
|
+
if (!isRecord(parsed.providers)) {
|
|
768
|
+
throw new Error('Dispatch matrix recommendation asset is missing providers.');
|
|
769
|
+
}
|
|
770
|
+
return {
|
|
771
|
+
version,
|
|
772
|
+
providers: parsed.providers,
|
|
773
|
+
};
|
|
774
|
+
}
|
|
775
|
+
function isRouteTarget(entry) {
|
|
776
|
+
return isRecord(entry);
|
|
777
|
+
}
|
|
778
|
+
function addDispatchMatrixCellRefs(refs, provider, path, cell) {
|
|
779
|
+
if (typeof cell === 'string') {
|
|
780
|
+
refs.push({ provider, value: cell, path });
|
|
781
|
+
return;
|
|
782
|
+
}
|
|
783
|
+
for (const [index, entry] of cell.entries()) {
|
|
784
|
+
const entryPath = `${path}[${index}]`;
|
|
785
|
+
if (typeof entry === 'string') {
|
|
786
|
+
refs.push({ provider, value: entry, path: entryPath });
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
if (!isRouteTarget(entry)) {
|
|
790
|
+
continue;
|
|
791
|
+
}
|
|
792
|
+
if (entry.model) {
|
|
793
|
+
refs.push({ provider, value: entry.model, path: `${entryPath}.model` });
|
|
794
|
+
}
|
|
795
|
+
if (entry.effort) {
|
|
796
|
+
refs.push({ provider, value: entry.effort, path: `${entryPath}.effort` });
|
|
797
|
+
}
|
|
798
|
+
}
|
|
799
|
+
}
|
|
800
|
+
function collectDispatchMatrixCellRefs(providers) {
|
|
801
|
+
const refs = [];
|
|
802
|
+
for (const [provider, providerValue] of Object.entries(providers)) {
|
|
803
|
+
const providerPath = `workflow.dispatchCeiling.providers.${provider}`;
|
|
804
|
+
if (typeof providerValue === 'string') {
|
|
805
|
+
refs.push({ provider, value: providerValue, path: providerPath });
|
|
806
|
+
continue;
|
|
807
|
+
}
|
|
808
|
+
for (const [tier, cell] of Object.entries(providerValue)) {
|
|
809
|
+
if (cell === undefined) {
|
|
810
|
+
continue;
|
|
811
|
+
}
|
|
812
|
+
addDispatchMatrixCellRefs(refs, provider, `${providerPath}.${tier}`, cell);
|
|
813
|
+
}
|
|
814
|
+
}
|
|
815
|
+
return refs;
|
|
816
|
+
}
|
|
671
817
|
function applyWorkflowValue(workflow, key, value) {
|
|
672
818
|
const subKey = key.slice('workflow.'.length);
|
|
673
819
|
if (subKey === 'dispatchPolicy.mode') {
|
|
@@ -714,15 +860,37 @@ function applyWorkflowValue(workflow, key, value) {
|
|
|
714
860
|
},
|
|
715
861
|
};
|
|
716
862
|
}
|
|
717
|
-
if (
|
|
718
|
-
const
|
|
863
|
+
if (isDispatchCeilingProviderKey(key)) {
|
|
864
|
+
const { provider, tier } = parseDispatchCeilingProviderConfigKey(key);
|
|
865
|
+
const providers = workflow.dispatchCeiling?.providers ?? {};
|
|
866
|
+
if (tier) {
|
|
867
|
+
const existingProviderValue = providers[provider];
|
|
868
|
+
const existingTierMap = existingProviderValue &&
|
|
869
|
+
typeof existingProviderValue === 'object' &&
|
|
870
|
+
!Array.isArray(existingProviderValue)
|
|
871
|
+
? existingProviderValue
|
|
872
|
+
: {};
|
|
873
|
+
return {
|
|
874
|
+
...workflow,
|
|
875
|
+
dispatchCeiling: {
|
|
876
|
+
...workflow.dispatchCeiling,
|
|
877
|
+
providers: {
|
|
878
|
+
...providers,
|
|
879
|
+
[provider]: {
|
|
880
|
+
...existingTierMap,
|
|
881
|
+
[tier]: value,
|
|
882
|
+
},
|
|
883
|
+
},
|
|
884
|
+
},
|
|
885
|
+
};
|
|
886
|
+
}
|
|
719
887
|
return {
|
|
720
888
|
...workflow,
|
|
721
889
|
dispatchCeiling: {
|
|
722
890
|
...workflow.dispatchCeiling,
|
|
723
891
|
providers: {
|
|
724
|
-
...
|
|
725
|
-
[
|
|
892
|
+
...providers,
|
|
893
|
+
[provider]: value,
|
|
726
894
|
},
|
|
727
895
|
},
|
|
728
896
|
};
|
|
@@ -769,12 +937,31 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies) {
|
|
|
769
937
|
source: entry.source,
|
|
770
938
|
};
|
|
771
939
|
}
|
|
772
|
-
async function
|
|
940
|
+
async function listConfigKeys(repoRoot, userConfigDir, dependencies) {
|
|
941
|
+
const resolved = await dependencies.resolveEffectiveConfig(repoRoot, userConfigDir, dependencies.processEnv);
|
|
942
|
+
const staticKeys = new Set(KEY_ORDER);
|
|
943
|
+
const dynamicProviderKeys = Object.keys(resolved.resolved)
|
|
944
|
+
.filter(isDispatchCeilingProviderKey)
|
|
945
|
+
.filter((key) => !staticKeys.has(key))
|
|
946
|
+
.sort();
|
|
947
|
+
return [...KEY_ORDER, ...dynamicProviderKeys];
|
|
948
|
+
}
|
|
949
|
+
async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, dependencies, warn) {
|
|
773
950
|
validateSurfaceForKey(key, surface);
|
|
774
951
|
const effectiveSurface = surface === 'auto' ? defaultSurfaceForKey(key) : surface;
|
|
775
952
|
if (isWorkflowKey(key)) {
|
|
776
953
|
const parsedValue = parseWorkflowValue(key, rawValue);
|
|
777
954
|
const displayValue = typeof parsedValue === 'boolean' ? String(parsedValue) : parsedValue;
|
|
955
|
+
if (typeof parsedValue === 'string' && isDispatchCeilingProviderKey(key)) {
|
|
956
|
+
const availability = await dependencies.validateMatrixCell(providerNameFromConfigKey(key), parsedValue, {
|
|
957
|
+
cwd: repoRoot,
|
|
958
|
+
env: dependencies.processEnv,
|
|
959
|
+
});
|
|
960
|
+
const warning = dispatchProviderAvailabilityWarning(key, parsedValue, availability);
|
|
961
|
+
if (warning) {
|
|
962
|
+
warn(warning);
|
|
963
|
+
}
|
|
964
|
+
}
|
|
778
965
|
if (effectiveSurface === 'user') {
|
|
779
966
|
const userConfig = await dependencies.readUserConfig(userConfigDir);
|
|
780
967
|
await dependencies.writeUserConfig(userConfigDir, {
|
|
@@ -952,6 +1139,109 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
952
1139
|
source: 'shared',
|
|
953
1140
|
};
|
|
954
1141
|
}
|
|
1142
|
+
function hasExistingDispatchMatrix(config) {
|
|
1143
|
+
const providers = config.workflow?.dispatchCeiling?.providers;
|
|
1144
|
+
return providers !== undefined && Object.keys(providers).length > 0;
|
|
1145
|
+
}
|
|
1146
|
+
async function loadDispatchMatrixRecommendation(dependencies) {
|
|
1147
|
+
const assetsRoot = await dependencies.resolveAssetsRoot();
|
|
1148
|
+
const assetPath = join(assetsRoot, DISPATCH_MATRIX_RECOMMENDATION_ASSET);
|
|
1149
|
+
return parseDispatchMatrixRecommendation(await dependencies.readFile(assetPath));
|
|
1150
|
+
}
|
|
1151
|
+
async function validateRecommendationCells(repoRoot, recommendation, dependencies, warn) {
|
|
1152
|
+
for (const ref of collectDispatchMatrixCellRefs(recommendation.providers)) {
|
|
1153
|
+
const closedValues = closedDispatchProviderValues(ref.provider);
|
|
1154
|
+
if (closedValues && !closedValues.includes(ref.value)) {
|
|
1155
|
+
throw new Error(`Invalid value for ${ref.path}: expected one of ${closedValues.join(' | ')}, got '${ref.value}'`);
|
|
1156
|
+
}
|
|
1157
|
+
let availability;
|
|
1158
|
+
try {
|
|
1159
|
+
availability = await dependencies.validateMatrixCell(ref.provider, ref.value, {
|
|
1160
|
+
cwd: repoRoot,
|
|
1161
|
+
env: dependencies.processEnv,
|
|
1162
|
+
});
|
|
1163
|
+
}
|
|
1164
|
+
catch {
|
|
1165
|
+
availability = 'unvalidated';
|
|
1166
|
+
}
|
|
1167
|
+
const warning = matrixCellAvailabilityWarning(ref.path, ref.value, availability);
|
|
1168
|
+
if (warning) {
|
|
1169
|
+
warn(warning);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
}
|
|
1173
|
+
function applyDispatchMatrixRecommendation(workflow, recommendation) {
|
|
1174
|
+
return {
|
|
1175
|
+
...(workflow ?? {}),
|
|
1176
|
+
dispatchCeiling: {
|
|
1177
|
+
...workflow?.dispatchCeiling,
|
|
1178
|
+
recommendationVersion: recommendation.version,
|
|
1179
|
+
providers: recommendation.providers,
|
|
1180
|
+
},
|
|
1181
|
+
};
|
|
1182
|
+
}
|
|
1183
|
+
async function confirmDispatchMatrixOverwrite(context, dependencies, source, yes) {
|
|
1184
|
+
if (yes) {
|
|
1185
|
+
return;
|
|
1186
|
+
}
|
|
1187
|
+
if (!context.interactive) {
|
|
1188
|
+
throw new Error('Dispatch matrix already exists; rerun interactively or pass --yes to replace it.');
|
|
1189
|
+
}
|
|
1190
|
+
const shouldReplace = await dependencies.confirmAction(`Replace existing dispatch matrix in ${source} config?`, { interactive: context.interactive });
|
|
1191
|
+
if (!shouldReplace) {
|
|
1192
|
+
throw new Error('Dispatch matrix adoption cancelled; existing matrix unchanged.');
|
|
1193
|
+
}
|
|
1194
|
+
}
|
|
1195
|
+
async function adoptDispatchMatrixRecommendation(repoRoot, userConfigDir, options, context, dependencies) {
|
|
1196
|
+
const source = options.surface === 'auto' ? 'local' : options.surface;
|
|
1197
|
+
const recommendation = await loadDispatchMatrixRecommendation(dependencies);
|
|
1198
|
+
if (source === 'user') {
|
|
1199
|
+
const userConfig = await dependencies.readUserConfig(userConfigDir);
|
|
1200
|
+
if (hasExistingDispatchMatrix(userConfig)) {
|
|
1201
|
+
await confirmDispatchMatrixOverwrite(context, dependencies, source, options.yes);
|
|
1202
|
+
}
|
|
1203
|
+
await validateRecommendationCells(repoRoot, recommendation, dependencies, context.logger.warn);
|
|
1204
|
+
await dependencies.writeUserConfig(userConfigDir, {
|
|
1205
|
+
...userConfig,
|
|
1206
|
+
workflow: applyDispatchMatrixRecommendation(userConfig.workflow, recommendation),
|
|
1207
|
+
});
|
|
1208
|
+
return {
|
|
1209
|
+
key: 'workflow.dispatchCeiling.providers',
|
|
1210
|
+
value: recommendation.version,
|
|
1211
|
+
source,
|
|
1212
|
+
};
|
|
1213
|
+
}
|
|
1214
|
+
if (source === 'local') {
|
|
1215
|
+
const localConfig = await dependencies.readOatLocalConfig(repoRoot);
|
|
1216
|
+
if (hasExistingDispatchMatrix(localConfig)) {
|
|
1217
|
+
await confirmDispatchMatrixOverwrite(context, dependencies, source, options.yes);
|
|
1218
|
+
}
|
|
1219
|
+
await validateRecommendationCells(repoRoot, recommendation, dependencies, context.logger.warn);
|
|
1220
|
+
await dependencies.writeOatLocalConfig(repoRoot, {
|
|
1221
|
+
...localConfig,
|
|
1222
|
+
workflow: applyDispatchMatrixRecommendation(localConfig.workflow, recommendation),
|
|
1223
|
+
});
|
|
1224
|
+
return {
|
|
1225
|
+
key: 'workflow.dispatchCeiling.providers',
|
|
1226
|
+
value: recommendation.version,
|
|
1227
|
+
source,
|
|
1228
|
+
};
|
|
1229
|
+
}
|
|
1230
|
+
const sharedConfig = await dependencies.readOatConfig(repoRoot);
|
|
1231
|
+
if (hasExistingDispatchMatrix(sharedConfig)) {
|
|
1232
|
+
await confirmDispatchMatrixOverwrite(context, dependencies, source, options.yes);
|
|
1233
|
+
}
|
|
1234
|
+
await validateRecommendationCells(repoRoot, recommendation, dependencies, context.logger.warn);
|
|
1235
|
+
await dependencies.writeOatConfig(repoRoot, {
|
|
1236
|
+
...sharedConfig,
|
|
1237
|
+
workflow: applyDispatchMatrixRecommendation(sharedConfig.workflow, recommendation),
|
|
1238
|
+
});
|
|
1239
|
+
return {
|
|
1240
|
+
key: 'workflow.dispatchCeiling.providers',
|
|
1241
|
+
value: recommendation.version,
|
|
1242
|
+
source,
|
|
1243
|
+
};
|
|
1244
|
+
}
|
|
955
1245
|
function formatList(values) {
|
|
956
1246
|
const keyWidth = Math.max('Key'.length, ...values.map((item) => item.key.length));
|
|
957
1247
|
const sourceWidth = Math.max('Source'.length, ...values.map((item) => item.source.length));
|
|
@@ -1044,7 +1334,7 @@ async function runSet(keyArg, rawValue, surface, context, dependencies) {
|
|
|
1044
1334
|
}
|
|
1045
1335
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
1046
1336
|
const userConfigDir = join(context.home, '.oat');
|
|
1047
|
-
const result = await setConfigValue(repoRoot, userConfigDir, keyArg, rawValue, surface, dependencies);
|
|
1337
|
+
const result = await setConfigValue(repoRoot, userConfigDir, keyArg, rawValue, surface, dependencies, context.logger.warn);
|
|
1048
1338
|
if (context.json) {
|
|
1049
1339
|
context.logger.json({
|
|
1050
1340
|
status: 'ok',
|
|
@@ -1067,12 +1357,42 @@ async function runSet(keyArg, rawValue, surface, context, dependencies) {
|
|
|
1067
1357
|
process.exitCode = 1;
|
|
1068
1358
|
}
|
|
1069
1359
|
}
|
|
1360
|
+
async function runAdopt(templateArg, options, context, dependencies) {
|
|
1361
|
+
try {
|
|
1362
|
+
if (templateArg !== 'dispatch-matrix') {
|
|
1363
|
+
throw new Error(`Unknown config adoption template: ${templateArg}`);
|
|
1364
|
+
}
|
|
1365
|
+
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
1366
|
+
const userConfigDir = join(context.home, '.oat');
|
|
1367
|
+
const result = await adoptDispatchMatrixRecommendation(repoRoot, userConfigDir, options, context, dependencies);
|
|
1368
|
+
if (context.json) {
|
|
1369
|
+
context.logger.json({
|
|
1370
|
+
status: 'ok',
|
|
1371
|
+
...result,
|
|
1372
|
+
});
|
|
1373
|
+
}
|
|
1374
|
+
else {
|
|
1375
|
+
context.logger.info(`Adopted dispatch matrix recommendation ${result.value} to ${result.source} config.`);
|
|
1376
|
+
}
|
|
1377
|
+
process.exitCode = 0;
|
|
1378
|
+
}
|
|
1379
|
+
catch (error) {
|
|
1380
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1381
|
+
if (context.json) {
|
|
1382
|
+
context.logger.json({ status: 'error', message });
|
|
1383
|
+
}
|
|
1384
|
+
else {
|
|
1385
|
+
context.logger.error(message);
|
|
1386
|
+
}
|
|
1387
|
+
process.exitCode = 1;
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1070
1390
|
async function runList(context, dependencies) {
|
|
1071
1391
|
try {
|
|
1072
1392
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
1073
1393
|
const userConfigDir = join(context.home, '.oat');
|
|
1074
1394
|
const values = [];
|
|
1075
|
-
for (const key of
|
|
1395
|
+
for (const key of await listConfigKeys(repoRoot, userConfigDir, dependencies)) {
|
|
1076
1396
|
values.push(await getConfigValue(repoRoot, userConfigDir, key, dependencies));
|
|
1077
1397
|
}
|
|
1078
1398
|
if (context.json) {
|
|
@@ -1182,6 +1502,44 @@ export function createConfigCommand(overrides = {}) {
|
|
|
1182
1502
|
}
|
|
1183
1503
|
process.exitCode = 1;
|
|
1184
1504
|
}
|
|
1505
|
+
}))
|
|
1506
|
+
.addCommand(new Command('adopt')
|
|
1507
|
+
.description('Adopt a bundled OAT config recommendation')
|
|
1508
|
+
.argument('<template>', 'Recommendation template to adopt')
|
|
1509
|
+
.option('--shared', 'Write to the shared repo config (.oat/config.json)')
|
|
1510
|
+
.option('--local', 'Write to the repo-local config (.oat/config.local.json)')
|
|
1511
|
+
.option('--user', 'Write to the user-level config (~/.oat/config.json)')
|
|
1512
|
+
.option('--yes', 'Replace an existing adopted matrix without prompting')
|
|
1513
|
+
.action(async (template, options, command) => {
|
|
1514
|
+
const context = dependencies.buildCommandContext(readGlobalOptions(command));
|
|
1515
|
+
try {
|
|
1516
|
+
const flagsPresent = [
|
|
1517
|
+
options.shared,
|
|
1518
|
+
options.local,
|
|
1519
|
+
options.user,
|
|
1520
|
+
].filter(Boolean).length;
|
|
1521
|
+
if (flagsPresent > 1) {
|
|
1522
|
+
throw new Error('--shared, --local, and --user flags are mutually exclusive; pass at most one.');
|
|
1523
|
+
}
|
|
1524
|
+
let surface = 'auto';
|
|
1525
|
+
if (options.shared)
|
|
1526
|
+
surface = 'shared';
|
|
1527
|
+
else if (options.local)
|
|
1528
|
+
surface = 'local';
|
|
1529
|
+
else if (options.user)
|
|
1530
|
+
surface = 'user';
|
|
1531
|
+
await runAdopt(template, { surface, yes: options.yes === true }, context, dependencies);
|
|
1532
|
+
}
|
|
1533
|
+
catch (error) {
|
|
1534
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
1535
|
+
if (context.json) {
|
|
1536
|
+
context.logger.json({ status: 'error', message });
|
|
1537
|
+
}
|
|
1538
|
+
else {
|
|
1539
|
+
context.logger.error(message);
|
|
1540
|
+
}
|
|
1541
|
+
process.exitCode = 1;
|
|
1542
|
+
}
|
|
1185
1543
|
}))
|
|
1186
1544
|
.addCommand(new Command('list')
|
|
1187
1545
|
.description('List resolved OAT config values with sources')
|
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
import { type CommandContext, type GlobalOptions } from '../../app/command-context.js';
|
|
2
2
|
import { type PjmDoctorOptions } from '../pjm/doctor.js';
|
|
3
|
-
import { type OatConfig } from '../../config/oat-config.js';
|
|
3
|
+
import { type OatConfig, type OatLocalConfig, type UserConfig } from '../../config/oat-config.js';
|
|
4
4
|
import { type Manifest } from '../../manifest/index.js';
|
|
5
|
+
import { type MatrixCellAvailability, type ValidateMatrixCellOptions } from '../../providers/identity/availability.js';
|
|
5
6
|
import type { ConcreteScope } from '../../shared/types.js';
|
|
6
7
|
import { type DoctorCheck } from '../../ui/output.js';
|
|
7
8
|
import { Command } from 'commander';
|
|
@@ -19,6 +20,10 @@ interface DoctorDependencies {
|
|
|
19
20
|
readFile: (path: string) => Promise<string>;
|
|
20
21
|
resolveAssetsRoot: () => Promise<string>;
|
|
21
22
|
readOatConfig: (repoRoot: string) => Promise<OatConfig>;
|
|
23
|
+
readOatLocalConfig: (repoRoot: string) => Promise<OatLocalConfig>;
|
|
24
|
+
readUserConfig: (userConfigDir: string) => Promise<UserConfig>;
|
|
25
|
+
validateMatrixCell: (provider: string, value: string, options: ValidateMatrixCellOptions) => Promise<MatrixCellAvailability>;
|
|
26
|
+
processEnv: NodeJS.ProcessEnv;
|
|
22
27
|
runPjmDoctorChecks: (repoRoot: string, options?: PjmDoctorOptions) => Promise<DoctorCheck[]>;
|
|
23
28
|
checkSkillVersions: (scopeRoot: string, assetsRoot: string, pathExists: (path: string) => Promise<boolean>) => Promise<SkillVersionReport>;
|
|
24
29
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"AAWA,OAAO,EAEL,KAAK,cAAc,EACnB,KAAK,aAAa,EACnB,MAAM,sBAAsB,CAAC;AAE9B,OAAO,EAGL,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAO9B,OAAO,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/commands/doctor/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,7 +7,7 @@ import { createPjmDisabledCheck, runPjmDoctorChecks, } from '../pjm/doctor.js';
|
|
|
7
7
|
import { getSkillVersion } from '../shared/frontmatter.js';
|
|
8
8
|
import { withScopeOption } from '../shared/scope-option.js';
|
|
9
9
|
import { readGlobalOptions, resolveConcreteScopes, } from '../shared/shared.utils.js';
|
|
10
|
-
import { readOatConfig } from '../../config/oat-config.js';
|
|
10
|
+
import { readOatConfig, readOatLocalConfig, readUserConfig, } from '../../config/oat-config.js';
|
|
11
11
|
import { resolveAssetsRoot } from '../../fs/assets.js';
|
|
12
12
|
import { resolveProjectRoot, resolveScopeRoot } from '../../fs/paths.js';
|
|
13
13
|
import TOML from '@iarna/toml';
|
|
@@ -17,6 +17,7 @@ import { codexAdapter } from '../../providers/codex/index.js';
|
|
|
17
17
|
import { copilotAdapter } from '../../providers/copilot/index.js';
|
|
18
18
|
import { cursorAdapter } from '../../providers/cursor/index.js';
|
|
19
19
|
import { geminiAdapter } from '../../providers/gemini/index.js';
|
|
20
|
+
import { validateMatrixCell, } from '../../providers/identity/availability.js';
|
|
20
21
|
import { formatDoctorResults } from '../../ui/output.js';
|
|
21
22
|
import { Command } from 'commander';
|
|
22
23
|
async function pathExistsDefault(path) {
|
|
@@ -129,13 +130,137 @@ function createDependencies() {
|
|
|
129
130
|
readFile: async (path) => readFile(path, 'utf8'),
|
|
130
131
|
resolveAssetsRoot,
|
|
131
132
|
readOatConfig,
|
|
133
|
+
readOatLocalConfig,
|
|
134
|
+
readUserConfig,
|
|
135
|
+
validateMatrixCell,
|
|
136
|
+
processEnv: process.env,
|
|
132
137
|
runPjmDoctorChecks,
|
|
133
138
|
// Default binding remains self-contained, but still honors the caller-
|
|
134
139
|
// provided pathExists dependency from runChecksForScope when available.
|
|
135
140
|
checkSkillVersions: (scopeRoot, assetsRoot, pathExists = pathExistsDefault) => checkSkillVersionsDefault(scopeRoot, assetsRoot, pathExists),
|
|
136
141
|
};
|
|
137
142
|
}
|
|
138
|
-
|
|
143
|
+
function isRouteTarget(entry) {
|
|
144
|
+
return typeof entry === 'object' && entry !== null && !Array.isArray(entry);
|
|
145
|
+
}
|
|
146
|
+
function addDispatchMatrixCellRefs(refs, layer, provider, path, cell) {
|
|
147
|
+
if (typeof cell === 'string') {
|
|
148
|
+
refs.push({ layer, provider, value: cell, path });
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
for (const [index, entry] of cell.entries()) {
|
|
152
|
+
const entryPath = `${path}[${index}]`;
|
|
153
|
+
if (typeof entry === 'string') {
|
|
154
|
+
refs.push({ layer, provider, value: entry, path: entryPath });
|
|
155
|
+
continue;
|
|
156
|
+
}
|
|
157
|
+
if (!isRouteTarget(entry)) {
|
|
158
|
+
continue;
|
|
159
|
+
}
|
|
160
|
+
if (entry.model) {
|
|
161
|
+
refs.push({
|
|
162
|
+
layer,
|
|
163
|
+
provider,
|
|
164
|
+
value: entry.model,
|
|
165
|
+
path: `${entryPath}.model`,
|
|
166
|
+
});
|
|
167
|
+
}
|
|
168
|
+
if (entry.effort) {
|
|
169
|
+
refs.push({
|
|
170
|
+
layer,
|
|
171
|
+
provider,
|
|
172
|
+
value: entry.effort,
|
|
173
|
+
path: `${entryPath}.effort`,
|
|
174
|
+
});
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
function collectDispatchMatrixCellRefs(layers) {
|
|
179
|
+
const refs = [];
|
|
180
|
+
for (const { layer, config } of layers) {
|
|
181
|
+
const providers = config.workflow?.dispatchCeiling?.providers ?? {};
|
|
182
|
+
for (const [provider, providerValue] of Object.entries(providers)) {
|
|
183
|
+
const providerPath = `workflow.dispatchCeiling.providers.${provider}`;
|
|
184
|
+
if (typeof providerValue === 'string') {
|
|
185
|
+
refs.push({
|
|
186
|
+
layer,
|
|
187
|
+
provider,
|
|
188
|
+
value: providerValue,
|
|
189
|
+
path: providerPath,
|
|
190
|
+
});
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
193
|
+
const tierMap = providerValue;
|
|
194
|
+
for (const [tier, cell] of Object.entries(tierMap)) {
|
|
195
|
+
if (cell === undefined) {
|
|
196
|
+
continue;
|
|
197
|
+
}
|
|
198
|
+
addDispatchMatrixCellRefs(refs, layer, provider, `${providerPath}.${tier}`, cell);
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
return refs;
|
|
203
|
+
}
|
|
204
|
+
function formatDispatchMatrixIssueList(issues) {
|
|
205
|
+
return issues
|
|
206
|
+
.map((issue) => `${issue.path}=${issue.value} (${issue.layer} config)`)
|
|
207
|
+
.join(', ');
|
|
208
|
+
}
|
|
209
|
+
async function createDispatchMatrixDoctorCheck(scopeRoot, layers, dependencies) {
|
|
210
|
+
const refs = collectDispatchMatrixCellRefs(layers);
|
|
211
|
+
if (refs.length === 0) {
|
|
212
|
+
return {
|
|
213
|
+
name: 'project:dispatch_matrix',
|
|
214
|
+
description: 'Dispatch matrix cell availability',
|
|
215
|
+
status: 'pass',
|
|
216
|
+
message: 'No configured dispatch matrix cells found in user, shared, or local config layers.',
|
|
217
|
+
};
|
|
218
|
+
}
|
|
219
|
+
const issues = [];
|
|
220
|
+
for (const ref of refs) {
|
|
221
|
+
let availability;
|
|
222
|
+
try {
|
|
223
|
+
availability = await dependencies.validateMatrixCell(ref.provider, ref.value, {
|
|
224
|
+
cwd: scopeRoot,
|
|
225
|
+
env: dependencies.processEnv,
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
catch {
|
|
229
|
+
availability = 'unvalidated';
|
|
230
|
+
}
|
|
231
|
+
if (availability !== 'valid') {
|
|
232
|
+
issues.push({ ...ref, availability });
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
if (issues.length === 0) {
|
|
236
|
+
return {
|
|
237
|
+
name: 'project:dispatch_matrix',
|
|
238
|
+
description: 'Dispatch matrix cell availability',
|
|
239
|
+
status: 'pass',
|
|
240
|
+
message: `All configured dispatch matrix cells are available: ${formatDispatchMatrixIssueList(refs)}.`,
|
|
241
|
+
};
|
|
242
|
+
}
|
|
243
|
+
const unknown = issues.filter((issue) => issue.availability === 'unknown-value');
|
|
244
|
+
const unvalidated = issues.filter((issue) => issue.availability === 'unvalidated');
|
|
245
|
+
const messageParts = [];
|
|
246
|
+
if (unknown.length > 0) {
|
|
247
|
+
messageParts.push(`Unknown dispatch matrix cells: ${formatDispatchMatrixIssueList(unknown)}`);
|
|
248
|
+
}
|
|
249
|
+
if (unvalidated.length > 0) {
|
|
250
|
+
messageParts.push(`Unvalidated dispatch matrix cells: ${formatDispatchMatrixIssueList(unvalidated)}`);
|
|
251
|
+
}
|
|
252
|
+
const hasUnknown = unknown.length > 0;
|
|
253
|
+
return {
|
|
254
|
+
name: 'project:dispatch_matrix',
|
|
255
|
+
description: 'Dispatch matrix cell availability',
|
|
256
|
+
status: hasUnknown ? 'warn' : 'pass',
|
|
257
|
+
message: messageParts.join('. '),
|
|
258
|
+
fix: hasUnknown
|
|
259
|
+
? 'Run `oat config set workflow.dispatchCeiling.providers.<provider> <value>` with an available value, or refresh provider assets with `oat sync --scope project`.'
|
|
260
|
+
: undefined,
|
|
261
|
+
};
|
|
262
|
+
}
|
|
263
|
+
async function runChecksForScope(scope, scopeRoot, userConfigDir, dependencies) {
|
|
139
264
|
const checks = [];
|
|
140
265
|
const skillsPath = join(scopeRoot, '.agents', 'skills');
|
|
141
266
|
const agentsPath = join(scopeRoot, '.agents', 'agents');
|
|
@@ -345,7 +470,16 @@ async function runChecksForScope(scope, scopeRoot, dependencies) {
|
|
|
345
470
|
}
|
|
346
471
|
}
|
|
347
472
|
const repoReferenceRoot = join(scopeRoot, '.oat', 'repo');
|
|
348
|
-
const config = await
|
|
473
|
+
const [userConfig, config, localConfig] = await Promise.all([
|
|
474
|
+
dependencies.readUserConfig(userConfigDir),
|
|
475
|
+
dependencies.readOatConfig(scopeRoot),
|
|
476
|
+
dependencies.readOatLocalConfig(scopeRoot),
|
|
477
|
+
]);
|
|
478
|
+
checks.push(await createDispatchMatrixDoctorCheck(scopeRoot, [
|
|
479
|
+
{ layer: 'user', config: userConfig },
|
|
480
|
+
{ layer: 'shared', config },
|
|
481
|
+
{ layer: 'local', config: localConfig },
|
|
482
|
+
], dependencies));
|
|
349
483
|
const projectManagementEnabled = config.tools?.['project-management'] === true;
|
|
350
484
|
if (projectManagementEnabled) {
|
|
351
485
|
checks.push(...(await dependencies.runPjmDoctorChecks(repoReferenceRoot, {
|
|
@@ -362,7 +496,7 @@ async function runDoctorCommand(context, dependencies) {
|
|
|
362
496
|
const checks = [];
|
|
363
497
|
for (const scope of resolveConcreteScopes(context.scope)) {
|
|
364
498
|
const scopeRoot = await dependencies.resolveScopeRoot(scope, context);
|
|
365
|
-
const scopeChecks = await runChecksForScope(scope, scopeRoot, dependencies);
|
|
499
|
+
const scopeChecks = await runChecksForScope(scope, scopeRoot, join(context.home, '.oat'), dependencies);
|
|
366
500
|
checks.push(...scopeChecks);
|
|
367
501
|
}
|
|
368
502
|
if (context.json) {
|