@open-agent-toolkit/cli 0.1.42 → 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/agents/oat-phase-implementer.md +11 -7
- package/assets/agents/oat-reviewer.md +6 -4
- package/assets/config/dispatch-matrix-recommendation.json +23 -0
- package/assets/docs/cli-utilities/configuration.md +95 -40
- 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/reference/oat-directory-structure.md +26 -24
- package/assets/docs/workflows/projects/dispatch-ceiling.md +193 -80
- package/assets/docs/workflows/projects/implementation-execution.md +68 -45
- package/assets/docs/workflows/projects/index.md +2 -2
- package/assets/docs/workflows/projects/lifecycle.md +17 -2
- package/assets/public-package-versions.json +4 -4
- package/assets/skills/oat-project-implement/SKILL.md +232 -100
- package/assets/skills/oat-project-plan/SKILL.md +80 -30
- package/assets/skills/oat-project-plan-writing/SKILL.md +10 -10
- package/assets/skills/oat-project-quick-start/SKILL.md +80 -30
- package/assets/templates/plan.md +4 -4
- package/assets/templates/state.md +12 -3
- 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 +432 -11
- 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 +646 -56
- 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 +25 -6
- package/dist/config/oat-config.d.ts.map +1 -1
- package/dist/config/oat-config.js +123 -11
- package/dist/config/resolve.d.ts.map +1 -1
- package/dist/config/resolve.js +16 -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 +22 -1
- package/dist/providers/codex/codec/sync-extension.js +1 -1
- 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',
|
|
@@ -43,6 +54,8 @@ const KEY_ORDER = [
|
|
|
43
54
|
'workflow.autoArtifactReview.plan',
|
|
44
55
|
'workflow.autoArtifactReview.analysis',
|
|
45
56
|
'workflow.designMode',
|
|
57
|
+
'workflow.dispatchPolicy.mode',
|
|
58
|
+
'workflow.dispatchPolicy.policy',
|
|
46
59
|
'workflow.dispatchCeiling.preset',
|
|
47
60
|
'workflow.dispatchCeiling.providers.codex',
|
|
48
61
|
'workflow.dispatchCeiling.providers.claude',
|
|
@@ -454,7 +467,29 @@ const CONFIG_CATALOG = [
|
|
|
454
467
|
defaultValue: 'unset',
|
|
455
468
|
mutability: 'read/write',
|
|
456
469
|
owningCommand: 'oat config set workflow.dispatchCeiling.preset <value>',
|
|
457
|
-
description: 'Provider-neutral ceiling preset that compiles to concrete per-provider values at write time. balanced → Codex: high, Claude: sonnet; maximum → Codex: xhigh, Claude: opus; cost-conscious → Codex: medium, Claude: sonnet. Preset provenance only; runtime dispatch reads concrete providers values. Resolution: local > shared > user > default.',
|
|
470
|
+
description: 'Legacy compatibility alias for capped managed dispatch policies. Provider-neutral ceiling preset that compiles to concrete per-provider values at write time. balanced → Codex: high, Claude: sonnet; maximum → Codex: xhigh, Claude: opus; cost-conscious → Codex: medium, Claude: sonnet. Preset provenance only; runtime dispatch reads concrete providers values. Resolution: local > shared > user > default.',
|
|
471
|
+
},
|
|
472
|
+
{
|
|
473
|
+
key: 'workflow.dispatchPolicy.mode',
|
|
474
|
+
group: 'Workflow Preferences (3-layer: local > shared > user)',
|
|
475
|
+
file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
|
|
476
|
+
scope: 'workflow',
|
|
477
|
+
type: 'managed | inherit',
|
|
478
|
+
defaultValue: 'unset',
|
|
479
|
+
mutability: 'read/write',
|
|
480
|
+
owningCommand: 'oat config set workflow.dispatchPolicy.mode <value>',
|
|
481
|
+
description: 'Dispatch policy mode. "managed" means OAT selects model/effort from workflow.dispatchPolicy.policy; "inherit" means OAT leaves dispatch controls to the host/provider defaults. Set workflow.dispatchPolicy.policy to choose a managed policy. Resolution: local > shared > user > default.',
|
|
482
|
+
},
|
|
483
|
+
{
|
|
484
|
+
key: 'workflow.dispatchPolicy.policy',
|
|
485
|
+
group: 'Workflow Preferences (3-layer: local > shared > user)',
|
|
486
|
+
file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
|
|
487
|
+
scope: 'workflow',
|
|
488
|
+
type: 'economy | balanced | high | frontier | uncapped',
|
|
489
|
+
defaultValue: 'unset',
|
|
490
|
+
mutability: 'read/write',
|
|
491
|
+
owningCommand: 'oat config set workflow.dispatchPolicy.policy <value>',
|
|
492
|
+
description: 'Managed dispatch policy. economy, balanced, high, and frontier are capped managed policies; uncapped keeps OAT-managed preferred selection without provider caps. Setting this key writes workflow.dispatchPolicy.mode=managed. Resolution: local > shared > user > default.',
|
|
458
493
|
},
|
|
459
494
|
{
|
|
460
495
|
key: 'workflow.dispatchCeiling.providers.codex',
|
|
@@ -472,7 +507,7 @@ const CONFIG_CATALOG = [
|
|
|
472
507
|
group: 'Workflow Preferences (3-layer: local > shared > user)',
|
|
473
508
|
file: '.oat/config.local.json | .oat/config.json | ~/.oat/config.json',
|
|
474
509
|
scope: 'workflow',
|
|
475
|
-
type: 'haiku | sonnet | opus',
|
|
510
|
+
type: 'haiku | sonnet | opus | fable',
|
|
476
511
|
defaultValue: 'unset',
|
|
477
512
|
mutability: 'read/write',
|
|
478
513
|
owningCommand: 'oat config set workflow.dispatchCeiling.providers.claude <value>',
|
|
@@ -523,10 +558,49 @@ const DEFAULT_DEPENDENCIES = {
|
|
|
523
558
|
writeUserConfig,
|
|
524
559
|
resolveProjectsRoot,
|
|
525
560
|
resolveEffectiveConfig,
|
|
561
|
+
resolveAssetsRoot,
|
|
562
|
+
readFile: (path) => readFileDefault(path, 'utf8'),
|
|
563
|
+
confirmAction,
|
|
564
|
+
validateMatrixCell,
|
|
526
565
|
processEnv: process.env,
|
|
527
566
|
};
|
|
528
567
|
function isConfigKey(value) {
|
|
529
|
-
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;
|
|
530
604
|
}
|
|
531
605
|
function normalizeSharedRoot(value) {
|
|
532
606
|
const trimmed = value.trim();
|
|
@@ -540,6 +614,14 @@ const WORKFLOW_ENUM_VALUES = {
|
|
|
540
614
|
'workflow.postImplementSequence': ['wait', 'summary', 'pr', 'docs-pr'],
|
|
541
615
|
'workflow.reviewExecutionModel': ['subagent', 'inline', 'fresh-session'],
|
|
542
616
|
'workflow.designMode': ['collaborative', 'selective', 'draft'],
|
|
617
|
+
'workflow.dispatchPolicy.mode': ['managed', 'inherit'],
|
|
618
|
+
'workflow.dispatchPolicy.policy': [
|
|
619
|
+
'economy',
|
|
620
|
+
'balanced',
|
|
621
|
+
'high',
|
|
622
|
+
'frontier',
|
|
623
|
+
'uncapped',
|
|
624
|
+
],
|
|
543
625
|
'workflow.dispatchCeiling.preset': ['balanced', 'maximum', 'cost-conscious'],
|
|
544
626
|
'workflow.dispatchCeiling.providers.codex': [
|
|
545
627
|
'low',
|
|
@@ -547,8 +629,22 @@ const WORKFLOW_ENUM_VALUES = {
|
|
|
547
629
|
'high',
|
|
548
630
|
'xhigh',
|
|
549
631
|
],
|
|
550
|
-
'workflow.dispatchCeiling.providers.claude': [
|
|
632
|
+
'workflow.dispatchCeiling.providers.claude': [
|
|
633
|
+
'haiku',
|
|
634
|
+
'sonnet',
|
|
635
|
+
'opus',
|
|
636
|
+
'fable',
|
|
637
|
+
],
|
|
551
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
|
+
}
|
|
552
648
|
const WORKFLOW_BOOLEAN_KEYS = new Set([
|
|
553
649
|
'workflow.archiveOnComplete',
|
|
554
650
|
'workflow.createPrOnComplete',
|
|
@@ -629,10 +725,123 @@ function parseWorkflowValue(key, rawValue) {
|
|
|
629
725
|
}
|
|
630
726
|
return normalized;
|
|
631
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
|
+
}
|
|
632
740
|
throw new Error(`Unknown workflow key: ${key}`);
|
|
633
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
|
+
}
|
|
634
817
|
function applyWorkflowValue(workflow, key, value) {
|
|
635
818
|
const subKey = key.slice('workflow.'.length);
|
|
819
|
+
if (subKey === 'dispatchPolicy.mode') {
|
|
820
|
+
const mode = value;
|
|
821
|
+
if (mode === 'inherit') {
|
|
822
|
+
return {
|
|
823
|
+
...workflow,
|
|
824
|
+
dispatchPolicy: { mode },
|
|
825
|
+
};
|
|
826
|
+
}
|
|
827
|
+
const policy = workflow.dispatchPolicy?.policy;
|
|
828
|
+
if (!policy) {
|
|
829
|
+
throw new Error('Cannot set workflow.dispatchPolicy.mode to managed without an existing workflow.dispatchPolicy.policy. Set workflow.dispatchPolicy.policy <economy|balanced|high|frontier|uncapped> instead.');
|
|
830
|
+
}
|
|
831
|
+
return {
|
|
832
|
+
...workflow,
|
|
833
|
+
dispatchPolicy: { mode, policy },
|
|
834
|
+
};
|
|
835
|
+
}
|
|
836
|
+
if (subKey === 'dispatchPolicy.policy') {
|
|
837
|
+
return {
|
|
838
|
+
...workflow,
|
|
839
|
+
dispatchPolicy: {
|
|
840
|
+
mode: 'managed',
|
|
841
|
+
policy: value,
|
|
842
|
+
},
|
|
843
|
+
};
|
|
844
|
+
}
|
|
636
845
|
if (subKey === 'dispatchCeiling.preset') {
|
|
637
846
|
// Presets compile to concrete per-provider values at write time so the
|
|
638
847
|
// resolver (which reads only providers.*) gets a usable ceiling. The preset
|
|
@@ -651,15 +860,37 @@ function applyWorkflowValue(workflow, key, value) {
|
|
|
651
860
|
},
|
|
652
861
|
};
|
|
653
862
|
}
|
|
654
|
-
if (
|
|
655
|
-
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
|
+
}
|
|
656
887
|
return {
|
|
657
888
|
...workflow,
|
|
658
889
|
dispatchCeiling: {
|
|
659
890
|
...workflow.dispatchCeiling,
|
|
660
891
|
providers: {
|
|
661
|
-
...
|
|
662
|
-
[
|
|
892
|
+
...providers,
|
|
893
|
+
[provider]: value,
|
|
663
894
|
},
|
|
664
895
|
},
|
|
665
896
|
};
|
|
@@ -706,12 +937,31 @@ async function getConfigValue(repoRoot, userConfigDir, key, dependencies) {
|
|
|
706
937
|
source: entry.source,
|
|
707
938
|
};
|
|
708
939
|
}
|
|
709
|
-
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) {
|
|
710
950
|
validateSurfaceForKey(key, surface);
|
|
711
951
|
const effectiveSurface = surface === 'auto' ? defaultSurfaceForKey(key) : surface;
|
|
712
952
|
if (isWorkflowKey(key)) {
|
|
713
953
|
const parsedValue = parseWorkflowValue(key, rawValue);
|
|
714
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
|
+
}
|
|
715
965
|
if (effectiveSurface === 'user') {
|
|
716
966
|
const userConfig = await dependencies.readUserConfig(userConfigDir);
|
|
717
967
|
await dependencies.writeUserConfig(userConfigDir, {
|
|
@@ -889,6 +1139,109 @@ async function setConfigValue(repoRoot, userConfigDir, key, rawValue, surface, d
|
|
|
889
1139
|
source: 'shared',
|
|
890
1140
|
};
|
|
891
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
|
+
}
|
|
892
1245
|
function formatList(values) {
|
|
893
1246
|
const keyWidth = Math.max('Key'.length, ...values.map((item) => item.key.length));
|
|
894
1247
|
const sourceWidth = Math.max('Source'.length, ...values.map((item) => item.source.length));
|
|
@@ -981,7 +1334,7 @@ async function runSet(keyArg, rawValue, surface, context, dependencies) {
|
|
|
981
1334
|
}
|
|
982
1335
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
983
1336
|
const userConfigDir = join(context.home, '.oat');
|
|
984
|
-
const result = await setConfigValue(repoRoot, userConfigDir, keyArg, rawValue, surface, dependencies);
|
|
1337
|
+
const result = await setConfigValue(repoRoot, userConfigDir, keyArg, rawValue, surface, dependencies, context.logger.warn);
|
|
985
1338
|
if (context.json) {
|
|
986
1339
|
context.logger.json({
|
|
987
1340
|
status: 'ok',
|
|
@@ -1004,12 +1357,42 @@ async function runSet(keyArg, rawValue, surface, context, dependencies) {
|
|
|
1004
1357
|
process.exitCode = 1;
|
|
1005
1358
|
}
|
|
1006
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
|
+
}
|
|
1007
1390
|
async function runList(context, dependencies) {
|
|
1008
1391
|
try {
|
|
1009
1392
|
const repoRoot = await dependencies.resolveProjectRoot(context.cwd);
|
|
1010
1393
|
const userConfigDir = join(context.home, '.oat');
|
|
1011
1394
|
const values = [];
|
|
1012
|
-
for (const key of
|
|
1395
|
+
for (const key of await listConfigKeys(repoRoot, userConfigDir, dependencies)) {
|
|
1013
1396
|
values.push(await getConfigValue(repoRoot, userConfigDir, key, dependencies));
|
|
1014
1397
|
}
|
|
1015
1398
|
if (context.json) {
|
|
@@ -1119,6 +1502,44 @@ export function createConfigCommand(overrides = {}) {
|
|
|
1119
1502
|
}
|
|
1120
1503
|
process.exitCode = 1;
|
|
1121
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
|
+
}
|
|
1122
1543
|
}))
|
|
1123
1544
|
.addCommand(new Command('list')
|
|
1124
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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"}
|