agents-inc 0.170.0 → 0.170.2
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/CHANGELOG.md +17 -0
- package/dist/{chunk-BMRUNMPV.js → chunk-2YA4DDRM.js} +35 -35
- package/dist/chunk-2YA4DDRM.js.map +1 -0
- package/dist/{chunk-44TE3RKH.js → chunk-3ED3KHHD.js} +5 -4
- package/dist/chunk-3ED3KHHD.js.map +1 -0
- package/dist/{chunk-3G7X2WKI.js → chunk-5BNX73OU.js} +18 -38
- package/dist/chunk-5BNX73OU.js.map +1 -0
- package/dist/{chunk-7RDMTR64.js → chunk-66AEVERM.js} +785 -256
- package/dist/chunk-66AEVERM.js.map +1 -0
- package/dist/{chunk-PJWGTNOX.js → chunk-7C3PFRBU.js} +144 -186
- package/dist/chunk-7C3PFRBU.js.map +1 -0
- package/dist/{chunk-52HDHJ7I.js → chunk-AUNRHWH4.js} +32 -21
- package/dist/chunk-AUNRHWH4.js.map +1 -0
- package/dist/{chunk-KOLDW6SF.js → chunk-BBDGYJHX.js} +2 -2
- package/dist/chunk-BBDGYJHX.js.map +1 -0
- package/dist/{chunk-SZYQISA2.js → chunk-ELV5WDS3.js} +43 -29
- package/dist/chunk-ELV5WDS3.js.map +1 -0
- package/dist/{chunk-MQ6CGZNJ.js → chunk-GAKVZNTX.js} +1879 -2019
- package/dist/chunk-GAKVZNTX.js.map +1 -0
- package/dist/chunk-GEMW5RQ2.js +1169 -0
- package/dist/chunk-GEMW5RQ2.js.map +1 -0
- package/dist/{chunk-NQW25DCF.js → chunk-GHHJYNAN.js} +1 -7
- package/dist/chunk-GHHJYNAN.js.map +1 -0
- package/dist/{chunk-WTWMGH2C.js → chunk-HACRR422.js} +2 -2
- package/dist/chunk-HACRR422.js.map +1 -0
- package/dist/{chunk-F2BHT5C4.js → chunk-IWY5IR4F.js} +129 -151
- package/dist/chunk-IWY5IR4F.js.map +1 -0
- package/dist/{chunk-JFLVEFFP.js → chunk-KIK5EZYD.js} +1 -1
- package/dist/chunk-KIK5EZYD.js.map +1 -0
- package/dist/{chunk-USVBYQSQ.js → chunk-O4LN3HFI.js} +16 -6
- package/dist/chunk-O4LN3HFI.js.map +1 -0
- package/dist/{chunk-RIPSGNLU.js → chunk-OCAVW6N5.js} +3 -3
- package/dist/chunk-OCAVW6N5.js.map +1 -0
- package/dist/{chunk-4AAOOAIN.js → chunk-P6CYS4JK.js} +2 -2
- package/dist/chunk-P6CYS4JK.js.map +1 -0
- package/dist/chunk-PLFANHRZ.js +35 -0
- package/dist/chunk-PLFANHRZ.js.map +1 -0
- package/dist/{chunk-6ZRH5RHC.js → chunk-PMUZR4XO.js} +4 -4
- package/dist/chunk-PMUZR4XO.js.map +1 -0
- package/dist/{chunk-F5EBEATU.js → chunk-PSUTNOQ2.js} +34 -46
- package/dist/chunk-PSUTNOQ2.js.map +1 -0
- package/dist/chunk-RIK45Q4I.js +103 -0
- package/dist/chunk-RIK45Q4I.js.map +1 -0
- package/dist/{chunk-HED2DAXZ.js → chunk-THO7LO5B.js} +5 -5
- package/dist/chunk-THO7LO5B.js.map +1 -0
- package/dist/{chunk-OYJQUA3D.js → chunk-YGDWHDOY.js} +31 -13
- package/dist/chunk-YGDWHDOY.js.map +1 -0
- package/dist/{chunk-NCWDK6L7.js → chunk-YXKTY5WM.js} +254 -379
- package/dist/chunk-YXKTY5WM.js.map +1 -0
- package/dist/{chunk-NEZ4OAOL.js → chunk-ZEGM3MVR.js} +3 -5
- package/dist/chunk-ZEGM3MVR.js.map +1 -0
- package/dist/commands/build/marketplace.js +46 -46
- package/dist/commands/build/marketplace.js.map +1 -1
- package/dist/commands/build/plugins.js +25 -28
- package/dist/commands/build/plugins.js.map +1 -1
- package/dist/commands/compile.js +87 -178
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/doctor.js +510 -360
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/edit.js +451 -357
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/eject.js +144 -125
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/init.js +20 -27
- package/dist/commands/list.js +39 -55
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/new/marketplace.js +6 -11
- package/dist/commands/new/marketplace.js.map +1 -1
- package/dist/commands/search.js +35 -32
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/share.js +17 -77
- package/dist/commands/share.js.map +1 -1
- package/dist/commands/uninstall.js +159 -222
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +9 -32
- package/dist/commands/update.js.map +1 -1
- package/dist/components/common/prompt-confirm.js +2 -2
- package/dist/components/common/removal-plan-confirm.js +2 -2
- package/dist/components/common/scroll-affordance.js +2 -2
- package/dist/components/common/select-list.js +2 -2
- package/dist/components/common/skill-agent-summary.js +4 -4
- package/dist/components/common/spinner.js +2 -4
- package/dist/components/common/summary-panel.js +5 -5
- package/dist/components/common/summary-panel.js.map +1 -1
- package/dist/config-exports.js +7 -3
- package/dist/config-exports.js.map +1 -1
- package/dist/hooks/init.js +18 -19
- package/dist/hooks/init.js.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/load-agent-defs-6QUUMGHK.js +14 -0
- package/dist/recompile-project-agents-I33KQJFB.js +65 -0
- package/dist/recompile-project-agents-I33KQJFB.js.map +1 -0
- package/dist/{source-loader-C5SQXZXI.js → source-loader-TTJVW2ER.js} +5 -5
- package/dist/src/agents/developer/ai-developer/playbook.md +2 -5
- package/dist/src/agents/developer/api-developer/playbook.md +2 -5
- package/dist/src/agents/developer/cli-developer/playbook.md +2 -5
- package/dist/src/agents/developer/web-developer/playbook.md +2 -5
- package/dist/src/agents/meta/agent-summoner/output.md +2 -2
- package/dist/src/agents/meta/agent-summoner/playbook.md +1 -13
- package/dist/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
- package/dist/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
- package/dist/src/agents/tester/ai-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/ai-tester/playbook.md +2 -0
- package/dist/src/agents/tester/api-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/api-tester/playbook.md +2 -0
- package/dist/src/agents/tester/cli-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/cli-tester/playbook.md +2 -0
- package/dist/src/agents/tester/web-tester/critical-requirements.md +1 -2
- package/dist/src/agents/tester/web-tester/playbook.md +2 -0
- package/package.json +9 -8
- package/src/agents/developer/ai-developer/playbook.md +2 -5
- package/src/agents/developer/api-developer/playbook.md +2 -5
- package/src/agents/developer/cli-developer/playbook.md +2 -5
- package/src/agents/developer/web-developer/playbook.md +2 -5
- package/src/agents/meta/agent-summoner/output.md +2 -2
- package/src/agents/meta/agent-summoner/playbook.md +1 -13
- package/src/agents/meta/codex-keeper/critical-requirements.md +2 -5
- package/src/agents/meta/convention-keeper/critical-requirements.md +2 -4
- package/src/agents/tester/ai-tester/critical-requirements.md +1 -2
- package/src/agents/tester/ai-tester/playbook.md +2 -0
- package/src/agents/tester/api-tester/critical-requirements.md +1 -2
- package/src/agents/tester/api-tester/playbook.md +2 -0
- package/src/agents/tester/cli-tester/critical-requirements.md +1 -2
- package/src/agents/tester/cli-tester/playbook.md +2 -0
- package/src/agents/tester/web-tester/critical-requirements.md +1 -2
- package/src/agents/tester/web-tester/playbook.md +2 -0
- package/src/schemas/project-config.schema.json +0 -3
- package/src/schemas/project-source-config.schema.json +0 -6
- package/dist/chunk-3G7X2WKI.js.map +0 -1
- package/dist/chunk-44TE3RKH.js.map +0 -1
- package/dist/chunk-4AAOOAIN.js.map +0 -1
- package/dist/chunk-52HDHJ7I.js.map +0 -1
- package/dist/chunk-6HNXYYVF.js +0 -373
- package/dist/chunk-6HNXYYVF.js.map +0 -1
- package/dist/chunk-6ZRH5RHC.js.map +0 -1
- package/dist/chunk-7RDMTR64.js.map +0 -1
- package/dist/chunk-BMRUNMPV.js.map +0 -1
- package/dist/chunk-CLPUWOSA.js +0 -46
- package/dist/chunk-CLPUWOSA.js.map +0 -1
- package/dist/chunk-F2BHT5C4.js.map +0 -1
- package/dist/chunk-F5EBEATU.js.map +0 -1
- package/dist/chunk-HED2DAXZ.js.map +0 -1
- package/dist/chunk-JFLVEFFP.js.map +0 -1
- package/dist/chunk-KOLDW6SF.js.map +0 -1
- package/dist/chunk-MQ6CGZNJ.js.map +0 -1
- package/dist/chunk-NCWDK6L7.js.map +0 -1
- package/dist/chunk-NEZ4OAOL.js.map +0 -1
- package/dist/chunk-NIHUOWE4.js +0 -106
- package/dist/chunk-NIHUOWE4.js.map +0 -1
- package/dist/chunk-NQW25DCF.js.map +0 -1
- package/dist/chunk-OYJQUA3D.js.map +0 -1
- package/dist/chunk-PJWGTNOX.js.map +0 -1
- package/dist/chunk-RIPSGNLU.js.map +0 -1
- package/dist/chunk-SZYQISA2.js.map +0 -1
- package/dist/chunk-USVBYQSQ.js.map +0 -1
- package/dist/chunk-V3KTSP3Q.js +0 -940
- package/dist/chunk-V3KTSP3Q.js.map +0 -1
- package/dist/chunk-WTWMGH2C.js.map +0 -1
- package/dist/load-agent-defs-YMDWBFDP.js +0 -14
- package/dist/recompile-project-agents-SZ76WNTW.js +0 -18
- package/dist/source-loader-C5SQXZXI.js.map +0 -1
- /package/dist/{load-agent-defs-YMDWBFDP.js.map → load-agent-defs-6QUUMGHK.js.map} +0 -0
- /package/dist/{recompile-project-agents-SZ76WNTW.js.map → source-loader-TTJVW2ER.js.map} +0 -0
package/dist/commands/doctor.js
CHANGED
|
@@ -1,51 +1,45 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
-
import
|
|
3
|
-
unofferablePlacementsFound
|
|
4
|
-
} from "../chunk-WTWMGH2C.js";
|
|
5
|
-
import {
|
|
6
|
-
detectProject,
|
|
7
|
-
loadSource
|
|
8
|
-
} from "../chunk-7RDMTR64.js";
|
|
9
|
-
import "../chunk-F2BHT5C4.js";
|
|
10
|
-
import "../chunk-HED2DAXZ.js";
|
|
11
|
-
import "../chunk-3G7X2WKI.js";
|
|
2
|
+
import "../chunk-5BNX73OU.js";
|
|
12
3
|
import {
|
|
13
4
|
filterExcludedEntries,
|
|
14
5
|
listAgentFilesOf,
|
|
15
6
|
listAgentMdFiles,
|
|
16
7
|
splitAgentsByProvenance
|
|
17
|
-
} from "../chunk-
|
|
8
|
+
} from "../chunk-2YA4DDRM.js";
|
|
18
9
|
import {
|
|
19
10
|
ambiguityFinding
|
|
20
|
-
} from "../chunk-
|
|
11
|
+
} from "../chunk-OCAVW6N5.js";
|
|
21
12
|
import {
|
|
22
13
|
BaseCommand,
|
|
23
14
|
EXIT_CODES
|
|
24
|
-
} from "../chunk-
|
|
25
|
-
import "../chunk-CLPUWOSA.js";
|
|
15
|
+
} from "../chunk-GEMW5RQ2.js";
|
|
26
16
|
import {
|
|
27
17
|
ConfigDefaultExportError,
|
|
18
|
+
ConfigLoadError,
|
|
28
19
|
MarketplaceManifestRequiredError,
|
|
29
20
|
MarketplaceNameRefusedError,
|
|
21
|
+
MarketplaceRefusedError,
|
|
30
22
|
SOURCE_ENV_VAR,
|
|
31
23
|
agentCodec,
|
|
32
24
|
agentYamlGenerationSchema,
|
|
25
|
+
agentsDir,
|
|
33
26
|
configDirsInPlay,
|
|
34
27
|
declaresNoContent,
|
|
28
|
+
detectInstallations,
|
|
35
29
|
directoryExists,
|
|
36
|
-
discoverLocalSkills,
|
|
37
30
|
fileExists,
|
|
38
31
|
findConfigLoadFailures,
|
|
39
32
|
formatZodErrors,
|
|
40
33
|
getErrorMessage,
|
|
41
34
|
getInstalledConfigPath,
|
|
42
35
|
getInstalledPluginsRegistryPath,
|
|
36
|
+
getPluginManifestPath,
|
|
43
37
|
getProjectPluginsDir,
|
|
44
38
|
getStackSkillIds,
|
|
45
39
|
getUserPluginsDir,
|
|
46
|
-
getVerifiedPluginInstallPaths,
|
|
47
40
|
glob,
|
|
48
41
|
hostCompilesAgent,
|
|
42
|
+
hostFor,
|
|
49
43
|
installBaseDir,
|
|
50
44
|
invalidResult,
|
|
51
45
|
isHomeDirectory,
|
|
@@ -53,9 +47,11 @@ import {
|
|
|
53
47
|
isRecord,
|
|
54
48
|
layoutIsCurrent,
|
|
55
49
|
listDirectories,
|
|
50
|
+
listPluginDirNames,
|
|
56
51
|
listRegisteredPluginInstalls,
|
|
57
52
|
loadConfig,
|
|
58
53
|
loadInstalledConfig,
|
|
54
|
+
loadProjectConfigFromDir,
|
|
59
55
|
loadSkillsMatrixFromSource,
|
|
60
56
|
loadSourceRepoConfig,
|
|
61
57
|
matrixHealthIssues,
|
|
@@ -63,17 +59,19 @@ import {
|
|
|
63
59
|
metadataValidationSchema,
|
|
64
60
|
parseFrontmatter,
|
|
65
61
|
parseMarketplacePluginRef,
|
|
66
|
-
|
|
62
|
+
providerArgument,
|
|
67
63
|
providerInUse,
|
|
68
64
|
readFile,
|
|
69
65
|
readForkedFromMetadata,
|
|
70
66
|
readSkillMetadata,
|
|
71
67
|
recreateConfigFrom,
|
|
68
|
+
refuseUnofferedPlacements,
|
|
72
69
|
relativeConfigPath,
|
|
73
70
|
resolveInstallPaths,
|
|
74
71
|
resolvePrimarySourceEntry,
|
|
75
72
|
scopeLabel,
|
|
76
73
|
scopeNoun,
|
|
74
|
+
scopeRootsInPlay,
|
|
77
75
|
skillCategoriesFileSchema,
|
|
78
76
|
skillRulesFileSchema,
|
|
79
77
|
skillsDir,
|
|
@@ -82,17 +80,20 @@ import {
|
|
|
82
80
|
splitMetadataValidationIssues,
|
|
83
81
|
stackConfigValidationSchema,
|
|
84
82
|
stacksConfigSchema,
|
|
83
|
+
typedValues,
|
|
85
84
|
validResult,
|
|
86
85
|
validateAgentFrontmatter,
|
|
87
86
|
validateAllPlugins,
|
|
88
87
|
validatePlugin,
|
|
89
88
|
validateProjectConfig,
|
|
90
89
|
validateSkillFrontmatter,
|
|
91
|
-
validateSkillMetadata
|
|
92
|
-
|
|
90
|
+
validateSkillMetadata,
|
|
91
|
+
verbose
|
|
92
|
+
} from "../chunk-GAKVZNTX.js";
|
|
93
93
|
import {
|
|
94
|
-
matrix
|
|
95
|
-
|
|
94
|
+
matrix,
|
|
95
|
+
plural
|
|
96
|
+
} from "../chunk-AUNRHWH4.js";
|
|
96
97
|
import {
|
|
97
98
|
CLI_INVOKE_COMMAND,
|
|
98
99
|
DEFAULT_BRANDING,
|
|
@@ -107,12 +108,106 @@ import {
|
|
|
107
108
|
STANDARD_FILES,
|
|
108
109
|
UI_SYMBOLS,
|
|
109
110
|
effectivelyExcludedSkillIds
|
|
110
|
-
} from "../chunk-
|
|
111
|
-
import "../chunk-
|
|
111
|
+
} from "../chunk-7C3PFRBU.js";
|
|
112
|
+
import "../chunk-GHHJYNAN.js";
|
|
112
113
|
|
|
113
114
|
// src/cli/commands/doctor.ts
|
|
114
|
-
import
|
|
115
|
+
import os3 from "os";
|
|
116
|
+
import path4 from "path";
|
|
117
|
+
|
|
118
|
+
// src/cli/lib/doctor/installation-reads.ts
|
|
119
|
+
import os from "os";
|
|
120
|
+
function hostsOnDisk(projectDir) {
|
|
121
|
+
return { project: providerInUse(projectDir), global: providerInUse(os.homedir()) };
|
|
122
|
+
}
|
|
123
|
+
function hostsOf(provider) {
|
|
124
|
+
return { project: provider, global: provider };
|
|
125
|
+
}
|
|
126
|
+
async function providersInPlay(projectDir) {
|
|
127
|
+
const found = await detectInstallations(projectDir);
|
|
128
|
+
const inTheProject = found.filter((installation) => installation.scope === "project");
|
|
129
|
+
const inPlay = inTheProject.length > 0 ? inTheProject : found;
|
|
130
|
+
return inPlay.map((installation) => installation.provider);
|
|
131
|
+
}
|
|
132
|
+
async function resolveConfigState({
|
|
133
|
+
projectDir,
|
|
134
|
+
hosts
|
|
135
|
+
}) {
|
|
136
|
+
try {
|
|
137
|
+
return await readConfigState(projectDir, hosts);
|
|
138
|
+
} catch (error) {
|
|
139
|
+
if (error instanceof ConfigLoadError) return { kind: "unreadable", failure: error };
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
async function readConfigState(projectDir, hosts) {
|
|
144
|
+
const own = await loadProjectConfigFromDir(projectDir, hosts.project);
|
|
145
|
+
if (own && !declaresNoContent(own.config)) {
|
|
146
|
+
return { kind: "loaded", config: own.config, inherited: false };
|
|
147
|
+
}
|
|
148
|
+
const inherited = isHomeDirectory(projectDir) ? null : await loadProjectConfigFromDir(os.homedir(), hosts.global);
|
|
149
|
+
if (inherited && !declaresNoContent(inherited.config)) {
|
|
150
|
+
return { kind: "loaded", config: inherited.config, inherited: true };
|
|
151
|
+
}
|
|
152
|
+
return own ? { kind: "declares-nothing", config: own.config } : { kind: "absent" };
|
|
153
|
+
}
|
|
154
|
+
async function installedPluginSkillIds(baseDir, provider) {
|
|
155
|
+
const plugins = await verifiedPlugins(baseDir, provider);
|
|
156
|
+
return new Set(plugins.map((plugin) => parseMarketplacePluginRef(plugin.pluginKey)));
|
|
157
|
+
}
|
|
158
|
+
async function verifiedPlugins(baseDir, provider) {
|
|
159
|
+
const enabled = (await pluginsListedBy(provider, baseDir)).filter((plugin) => plugin.enabled);
|
|
160
|
+
const onDisk = await Promise.all(
|
|
161
|
+
enabled.map((plugin) => fileExists(getPluginManifestPath(plugin.installPath)))
|
|
162
|
+
);
|
|
163
|
+
return enabled.filter((_, index) => onDisk[index] === true);
|
|
164
|
+
}
|
|
165
|
+
async function pluginsListedBy(provider, baseDir) {
|
|
166
|
+
try {
|
|
167
|
+
return await hostFor(provider).listPlugins(baseDir);
|
|
168
|
+
} catch (error) {
|
|
169
|
+
verbose(`Failed to list installed plugins: ${getErrorMessage(error)}`);
|
|
170
|
+
return [];
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
async function unofferablePlacements({
|
|
174
|
+
projectDir,
|
|
175
|
+
hosts
|
|
176
|
+
}) {
|
|
177
|
+
const projectFirst = [...scopeRootsInPlay(projectDir)].reverse();
|
|
178
|
+
const findings = await Promise.all(
|
|
179
|
+
projectFirst.map(({ kind, root }) => unofferablePlacementIn(root, hosts[kind]))
|
|
180
|
+
);
|
|
181
|
+
return findings.filter((finding) => finding !== null);
|
|
182
|
+
}
|
|
183
|
+
async function unofferablePlacementIn(root, provider) {
|
|
184
|
+
const skills = await configuredSkillsAt(root, provider);
|
|
185
|
+
if (skills === null) return null;
|
|
186
|
+
try {
|
|
187
|
+
refuseUnofferedPlacements(skills, hostFor(provider));
|
|
188
|
+
return null;
|
|
189
|
+
} catch (error) {
|
|
190
|
+
return getErrorMessage(error);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
async function configuredSkillsAt(root, provider) {
|
|
194
|
+
try {
|
|
195
|
+
const loaded = await loadProjectConfigFromDir(root, provider);
|
|
196
|
+
return loaded?.config.skills ?? null;
|
|
197
|
+
} catch (error) {
|
|
198
|
+
if (error instanceof ConfigLoadError) return null;
|
|
199
|
+
throw error;
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// src/cli/lib/doctor/ownership.ts
|
|
115
204
|
import path3 from "path";
|
|
205
|
+
import { groupBy } from "remeda";
|
|
206
|
+
|
|
207
|
+
// src/cli/lib/content-validator.ts
|
|
208
|
+
import os2 from "os";
|
|
209
|
+
import path2 from "path";
|
|
210
|
+
import { partition } from "remeda";
|
|
116
211
|
|
|
117
212
|
// src/cli/lib/source-validator.ts
|
|
118
213
|
import path from "path";
|
|
@@ -199,12 +294,7 @@ async function validateSource(sourcePath, reader = "author") {
|
|
|
199
294
|
0
|
|
200
295
|
);
|
|
201
296
|
}
|
|
202
|
-
const
|
|
203
|
-
const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir2);
|
|
204
|
-
const skillMdDirs = new Set(skillMdFiles.map((f) => path.dirname(f)));
|
|
205
|
-
const metadataDirs = new Set(metadataFiles.map((f) => path.dirname(f)));
|
|
206
|
-
const pairIssues = validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2);
|
|
207
|
-
const validMetadataFiles = metadataFiles.filter((f) => skillMdDirs.has(path.dirname(f)));
|
|
297
|
+
const { pairIssues, validMetadataFiles } = await pairSkillFiles(skillsDir2);
|
|
208
298
|
const skillIssues = await validateSkillsInTurn(skillsDir2, skillsDirRelPath, validMetadataFiles);
|
|
209
299
|
const crossReferenceIssues = await checkCrossReferences(resolvedPath, reader);
|
|
210
300
|
const extraIssues = await Promise.all([
|
|
@@ -217,6 +307,16 @@ async function validateSource(sourcePath, reader = "author") {
|
|
|
217
307
|
validMetadataFiles.length
|
|
218
308
|
);
|
|
219
309
|
}
|
|
310
|
+
async function pairSkillFiles(skillsDir2) {
|
|
311
|
+
const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
|
|
312
|
+
const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir2);
|
|
313
|
+
const skillMdDirs = new Set(skillMdFiles.map((f) => path.dirname(f)));
|
|
314
|
+
const metadataDirs = new Set(metadataFiles.map((f) => path.dirname(f)));
|
|
315
|
+
return {
|
|
316
|
+
pairIssues: validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2),
|
|
317
|
+
validMetadataFiles: metadataFiles.filter((f) => skillMdDirs.has(path.dirname(f)))
|
|
318
|
+
};
|
|
319
|
+
}
|
|
220
320
|
async function validateSkillsInTurn(skillsDir2, skillsDirRelPath, metadataFiles) {
|
|
221
321
|
const issues = [];
|
|
222
322
|
for (const metadataFile of metadataFiles) {
|
|
@@ -343,10 +443,10 @@ async function validateStacks(resolvedPath) {
|
|
|
343
443
|
return [...skillMetaIssues, ...configIssues];
|
|
344
444
|
}
|
|
345
445
|
async function validateAgents(resolvedPath) {
|
|
346
|
-
const
|
|
347
|
-
if (!await directoryExists(
|
|
446
|
+
const agentsDir2 = path.join(resolvedPath, DIRS.agents);
|
|
447
|
+
if (!await directoryExists(agentsDir2)) return [];
|
|
348
448
|
return validateYamlFiles({
|
|
349
|
-
baseDir:
|
|
449
|
+
baseDir: agentsDir2,
|
|
350
450
|
relBaseDir: DIRS.agents,
|
|
351
451
|
pattern: `**/${STANDARD_FILES.AGENT_METADATA_YAML}`,
|
|
352
452
|
schema: agentYamlGenerationSchema
|
|
@@ -414,11 +514,7 @@ function buildResult(issues, skillCount) {
|
|
|
414
514
|
}
|
|
415
515
|
|
|
416
516
|
// src/cli/lib/content-validator.ts
|
|
417
|
-
import os from "os";
|
|
418
|
-
import path2 from "path";
|
|
419
|
-
import { partition } from "remeda";
|
|
420
517
|
var CWD_SOURCE_NAME = "current directory";
|
|
421
|
-
var AGENT_FILE_EXTENSION = ".md";
|
|
422
518
|
var NOTHING_VALIDATED = { count: 0, issues: [], notes: [] };
|
|
423
519
|
function mergeValidations(parts) {
|
|
424
520
|
return parts.reduce(
|
|
@@ -431,7 +527,7 @@ function mergeValidations(parts) {
|
|
|
431
527
|
);
|
|
432
528
|
}
|
|
433
529
|
function displayDir(absolutePath) {
|
|
434
|
-
const home =
|
|
530
|
+
const home = os2.homedir();
|
|
435
531
|
if (absolutePath === home) return "~";
|
|
436
532
|
if (absolutePath.startsWith(home + path2.sep)) {
|
|
437
533
|
return `~${path2.sep}${path2.relative(home, absolutePath)}`;
|
|
@@ -519,7 +615,7 @@ async function validatePluginsDirectory(pluginsDir) {
|
|
|
519
615
|
const registryValidation = await validateRegistryPlugins(pluginsDir);
|
|
520
616
|
if (registryValidation !== void 0) return registryValidation;
|
|
521
617
|
}
|
|
522
|
-
const pluginDirs = await
|
|
618
|
+
const pluginDirs = await listPluginDirNames(pluginsDir);
|
|
523
619
|
if (pluginDirs.length === 0) return NOTHING_VALIDATED;
|
|
524
620
|
const { results } = await validateAllPlugins(pluginsDir);
|
|
525
621
|
return {
|
|
@@ -553,16 +649,6 @@ async function validateRegistryPlugins(pluginsDir) {
|
|
|
553
649
|
);
|
|
554
650
|
return { count: installs.length, issues: results.flat(), notes: [] };
|
|
555
651
|
}
|
|
556
|
-
async function findPluginDirectories(pluginsDir) {
|
|
557
|
-
const entries = await listDirectories(pluginsDir);
|
|
558
|
-
const checks = await Promise.all(
|
|
559
|
-
entries.map(async (name) => ({
|
|
560
|
-
name,
|
|
561
|
-
isPlugin: await directoryExists(path2.join(pluginsDir, name, PLUGIN_MANIFEST_DIR))
|
|
562
|
-
}))
|
|
563
|
-
);
|
|
564
|
-
return checks.filter((check) => check.isPlugin).map((check) => check.name);
|
|
565
|
-
}
|
|
566
652
|
async function validateInstalledSkills(projectDir) {
|
|
567
653
|
const configuredIds = await configuredSkillIds(projectDir);
|
|
568
654
|
return mergeValidations(
|
|
@@ -593,27 +679,26 @@ function installedDirs(projectDir, key) {
|
|
|
593
679
|
if (isHomeDirectory(projectDir)) return [global];
|
|
594
680
|
return [global, resolveInstallPaths(projectDir, "project")[key]];
|
|
595
681
|
}
|
|
596
|
-
async function
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
return { skills, agents };
|
|
682
|
+
async function skillDirsWithOurProvenanceIn(skillsDir2) {
|
|
683
|
+
return listDisplayPaths(skillsDir2, listSkillDirsWithOurProvenance);
|
|
684
|
+
}
|
|
685
|
+
async function agentFilesWithOurProvenanceIn(agentsDir2, codec) {
|
|
686
|
+
return listDisplayPaths(agentsDir2, (dir) => listAgentFilesWithOurProvenance(dir, codec));
|
|
602
687
|
}
|
|
603
|
-
async function listAgentFilesWithOurProvenance(
|
|
604
|
-
const { marked } = await splitAgentsByProvenance(
|
|
605
|
-
return marked.map((name) => `${name}${
|
|
688
|
+
async function listAgentFilesWithOurProvenance(agentsDir2, codec) {
|
|
689
|
+
const { marked } = await splitAgentsByProvenance(agentsDir2, codec);
|
|
690
|
+
return marked.map((name) => `${name}${codec.extension}`);
|
|
606
691
|
}
|
|
607
692
|
async function listSkillDirsWithOurProvenance(skillsDir2) {
|
|
608
|
-
const
|
|
609
|
-
|
|
610
|
-
|
|
693
|
+
const [ours] = await partitionByVerdict(
|
|
694
|
+
await listDirectories(skillsDir2),
|
|
695
|
+
(name) => carriesOurProvenance(skillsDir2, name)
|
|
611
696
|
);
|
|
612
|
-
return
|
|
697
|
+
return ours;
|
|
613
698
|
}
|
|
614
|
-
async function
|
|
615
|
-
const
|
|
616
|
-
return
|
|
699
|
+
async function partitionByVerdict(names, verdict) {
|
|
700
|
+
const verdicts = await Promise.all(names.map(verdict));
|
|
701
|
+
return partition(names, (_, index) => verdicts[index] === true);
|
|
617
702
|
}
|
|
618
703
|
async function listDisplayPaths(dir, listEntries) {
|
|
619
704
|
if (!await directoryExists(dir)) return [];
|
|
@@ -624,15 +709,12 @@ async function validateSkillsDirectory(skillsDir2, configuredIds) {
|
|
|
624
709
|
if (!await directoryExists(skillsDir2)) return NOTHING_VALIDATED;
|
|
625
710
|
const skillDirs = await listDirectories(skillsDir2);
|
|
626
711
|
if (skillDirs.length === 0) return NOTHING_VALIDATED;
|
|
627
|
-
const
|
|
628
|
-
skillDirs
|
|
629
|
-
|
|
630
|
-
ours: await isOurSkillDirectory(skillsDir2, name, configuredIds)
|
|
631
|
-
}))
|
|
712
|
+
const [ours, foreign] = await partitionByVerdict(
|
|
713
|
+
skillDirs,
|
|
714
|
+
(name) => isOurSkillDirectory(skillsDir2, name, configuredIds)
|
|
632
715
|
);
|
|
633
|
-
const [ours, foreign] = partition(judged, (entry) => entry.ours);
|
|
634
716
|
const results = await Promise.all(
|
|
635
|
-
ours.map(async (
|
|
717
|
+
ours.map(async (name) => {
|
|
636
718
|
const skillDir = path2.join(skillsDir2, name);
|
|
637
719
|
return toIssues(displayDir(skillDir), await validateInstalledSkill(skillDir));
|
|
638
720
|
})
|
|
@@ -640,7 +722,7 @@ async function validateSkillsDirectory(skillsDir2, configuredIds) {
|
|
|
640
722
|
return {
|
|
641
723
|
count: ours.length,
|
|
642
724
|
issues: results.flat(),
|
|
643
|
-
notes: foreign.map((
|
|
725
|
+
notes: foreign.map((name) => foreignSkillNote(skillsDir2, name))
|
|
644
726
|
};
|
|
645
727
|
}
|
|
646
728
|
async function isOurSkillDirectory(skillsDir2, name, configuredIds) {
|
|
@@ -653,29 +735,23 @@ async function carriesOurProvenance(skillsDir2, name) {
|
|
|
653
735
|
function foreignSkillNote(skillsDir2, name) {
|
|
654
736
|
return `${displayDir(path2.join(skillsDir2, name))} \u2014 not installed by this CLI and named by no configuration here: not validated`;
|
|
655
737
|
}
|
|
656
|
-
async function validateAgentsDirectory(
|
|
657
|
-
if (!await directoryExists(
|
|
658
|
-
const agentFiles = await listAgentMdFiles(
|
|
738
|
+
async function validateAgentsDirectory(agentsDir2) {
|
|
739
|
+
if (!await directoryExists(agentsDir2)) return NOTHING_VALIDATED;
|
|
740
|
+
const agentFiles = await listAgentMdFiles(agentsDir2);
|
|
659
741
|
if (agentFiles.length === 0) return NOTHING_VALIDATED;
|
|
660
742
|
const results = await Promise.all(
|
|
661
743
|
agentFiles.map(async (fileName) => {
|
|
662
|
-
const agentPath = path2.join(
|
|
744
|
+
const agentPath = path2.join(agentsDir2, fileName);
|
|
663
745
|
return toIssues(displayDir(agentPath), await validateAgentFrontmatter(agentPath));
|
|
664
746
|
})
|
|
665
747
|
);
|
|
666
748
|
return { count: agentFiles.length, issues: results.flat(), notes: [] };
|
|
667
749
|
}
|
|
668
750
|
async function validateInstalledSkill(skillDir) {
|
|
669
|
-
const skillMd = await
|
|
670
|
-
skillDir,
|
|
671
|
-
STANDARD_FILES.
|
|
672
|
-
|
|
673
|
-
);
|
|
674
|
-
const metadata = await validateSkillFile(
|
|
675
|
-
skillDir,
|
|
676
|
-
STANDARD_FILES.METADATA_YAML,
|
|
677
|
-
validateInstalledSkillMetadata
|
|
678
|
-
);
|
|
751
|
+
const [skillMd, metadata] = await Promise.all([
|
|
752
|
+
validateSkillFile(skillDir, STANDARD_FILES.SKILL_MD, validateSkillFrontmatter),
|
|
753
|
+
validateSkillFile(skillDir, STANDARD_FILES.METADATA_YAML, validateInstalledSkillMetadata)
|
|
754
|
+
]);
|
|
679
755
|
return mergeValidationResults([skillMd, metadata]);
|
|
680
756
|
}
|
|
681
757
|
async function validateSkillFile(skillDir, fileName, validate) {
|
|
@@ -701,25 +777,100 @@ async function validateInstalledSkillMetadata(metadataPath) {
|
|
|
701
777
|
};
|
|
702
778
|
}
|
|
703
779
|
|
|
704
|
-
// src/cli/
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
780
|
+
// src/cli/lib/doctor/ownership.ts
|
|
781
|
+
async function judgeOwnership(state, config, installation) {
|
|
782
|
+
const agentFolders = agentFoldersToJudge(state, config, installation);
|
|
783
|
+
const skillFolders = skillFoldersToJudge(state, config, installation);
|
|
784
|
+
const [unowned, orphans] = await Promise.all([
|
|
785
|
+
unownedIn(agentFolders, skillFolders),
|
|
786
|
+
orphansIn(agentFolders, skillFolders)
|
|
787
|
+
]);
|
|
788
|
+
return { unowned, orphans };
|
|
789
|
+
}
|
|
790
|
+
function agentFoldersToJudge(state, config, { projectDir, hosts }) {
|
|
791
|
+
return oncePerFolder(
|
|
792
|
+
scopesToJudge(state, hosts).map((scope) => ({
|
|
793
|
+
dir: agentsDir(hosts[scope], scope, projectDir),
|
|
794
|
+
codec: agentCodec(hosts[scope]),
|
|
795
|
+
claimed: claimed(state, scope, config && agentNamesClaimedIn(config, scope))
|
|
796
|
+
}))
|
|
797
|
+
);
|
|
798
|
+
}
|
|
799
|
+
function skillFoldersToJudge(state, config, { projectDir, hosts }) {
|
|
800
|
+
return oncePerFolder(
|
|
801
|
+
scopesToJudge(state, hosts).map((scope) => ({
|
|
802
|
+
dir: skillsDir(hosts[scope], scope, projectDir),
|
|
803
|
+
claimed: claimed(state, scope, config && skillIdsClaimedIn(config, scope))
|
|
804
|
+
}))
|
|
805
|
+
);
|
|
717
806
|
}
|
|
718
|
-
function
|
|
719
|
-
|
|
807
|
+
function scopesToJudge(state, hosts) {
|
|
808
|
+
const ownConfig = state.kind !== "absent" && !inherits(state);
|
|
809
|
+
return ownConfig && hosts.global !== hosts.project ? ["project"] : ["project", "global"];
|
|
720
810
|
}
|
|
721
|
-
function
|
|
722
|
-
|
|
811
|
+
function inherits(state) {
|
|
812
|
+
return state.kind === "loaded" && state.inherited;
|
|
813
|
+
}
|
|
814
|
+
function claimed(state, scope, names) {
|
|
815
|
+
if (names === null || state.kind === "absent") return null;
|
|
816
|
+
if (inherits(state) && scope === "project") return null;
|
|
817
|
+
return new Set(names);
|
|
818
|
+
}
|
|
819
|
+
function agentNamesClaimedIn(config, scope) {
|
|
820
|
+
return config.agents.filter((agent) => claimsAt(agent, scope)).map((agent) => agent.name);
|
|
821
|
+
}
|
|
822
|
+
function skillIdsClaimedIn(config, scope) {
|
|
823
|
+
return config.skills.filter((skill) => claimsAt(skill, scope)).map((skill) => skill.id);
|
|
824
|
+
}
|
|
825
|
+
function claimsAt(entry, scope) {
|
|
826
|
+
return entry.scope === scope && (scope === "global" || !entry.excluded);
|
|
827
|
+
}
|
|
828
|
+
function oncePerFolder(folders) {
|
|
829
|
+
return typedValues(groupBy(folders, (folder) => folder.dir)).map(
|
|
830
|
+
(sharing) => sharing.reduce(joinClaims)
|
|
831
|
+
);
|
|
832
|
+
}
|
|
833
|
+
function joinClaims(into, from) {
|
|
834
|
+
if (into.claimed === null) return from;
|
|
835
|
+
if (from.claimed === null) return into;
|
|
836
|
+
return { ...into, claimed: /* @__PURE__ */ new Set([...into.claimed, ...from.claimed]) };
|
|
837
|
+
}
|
|
838
|
+
async function unownedIn(agentFolders, skillFolders) {
|
|
839
|
+
const [skills, agents] = await Promise.all([
|
|
840
|
+
Promise.all(
|
|
841
|
+
skillFolders.filter(isUnclaimed).map(({ dir }) => skillDirsWithOurProvenanceIn(dir))
|
|
842
|
+
),
|
|
843
|
+
Promise.all(
|
|
844
|
+
agentFolders.filter(isUnclaimed).map(({ dir, codec }) => agentFilesWithOurProvenanceIn(dir, codec))
|
|
845
|
+
)
|
|
846
|
+
]);
|
|
847
|
+
return { skills: skills.flat(), agents: agents.flat() };
|
|
848
|
+
}
|
|
849
|
+
async function orphansIn(agentFolders, skillFolders) {
|
|
850
|
+
const [skills, agents] = await Promise.all([
|
|
851
|
+
Promise.all(skillFolders.map(unclaimedSkillDirs)),
|
|
852
|
+
Promise.all(agentFolders.map(unclaimedAgentFiles))
|
|
853
|
+
]);
|
|
854
|
+
return { skills: skills.flat(), agents: agents.flat() };
|
|
855
|
+
}
|
|
856
|
+
function isUnclaimed(folder) {
|
|
857
|
+
return folder.claimed === null;
|
|
858
|
+
}
|
|
859
|
+
async function unclaimedSkillDirs({ dir, claimed: claimed2 }) {
|
|
860
|
+
if (claimed2 === null) return [];
|
|
861
|
+
const ours = await listSkillDirsWithOurProvenance(dir);
|
|
862
|
+
return ours.filter((name) => !claimed2.has(name));
|
|
863
|
+
}
|
|
864
|
+
async function unclaimedAgentFiles({ dir, codec, claimed: claimed2 }) {
|
|
865
|
+
if (claimed2 === null || !await directoryExists(dir)) return [];
|
|
866
|
+
const files = await listAgentFilesOf(dir, codec);
|
|
867
|
+
return files.filter((file) => !claimed2.has(path3.basename(file, codec.extension)));
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
// src/cli/commands/doctor.ts
|
|
871
|
+
import { countBy, unique } from "remeda";
|
|
872
|
+
function checkConfigValid(state, installation) {
|
|
873
|
+
const theConfig = (verdict) => configRowMessage(state, installation, verdict);
|
|
723
874
|
if (state.kind === "absent") {
|
|
724
875
|
return {
|
|
725
876
|
result: {
|
|
@@ -731,6 +882,17 @@ function checkConfigValid(state, projectDir) {
|
|
|
731
882
|
config: null
|
|
732
883
|
};
|
|
733
884
|
}
|
|
885
|
+
if (state.kind === "unreadable") {
|
|
886
|
+
return {
|
|
887
|
+
result: {
|
|
888
|
+
kind: "config",
|
|
889
|
+
status: "fail",
|
|
890
|
+
message: theConfig("exists but could not be loaded"),
|
|
891
|
+
details: [state.failure.reason]
|
|
892
|
+
},
|
|
893
|
+
config: null
|
|
894
|
+
};
|
|
895
|
+
}
|
|
734
896
|
if (state.kind === "declares-nothing") {
|
|
735
897
|
return {
|
|
736
898
|
result: {
|
|
@@ -774,30 +936,30 @@ function checkConfigValid(state, projectDir) {
|
|
|
774
936
|
config
|
|
775
937
|
};
|
|
776
938
|
}
|
|
777
|
-
function configRowMessage(state, projectDir, verdict) {
|
|
939
|
+
function configRowMessage(state, { projectDir, hosts }, verdict) {
|
|
778
940
|
if (state.kind === "loaded" && state.inherited)
|
|
779
|
-
return `${theInheritedConfig()}, which ${verdict}`;
|
|
780
|
-
|
|
941
|
+
return `${theInheritedConfig(hosts.global)}, which ${verdict}`;
|
|
942
|
+
if (state.kind === "unreadable") {
|
|
943
|
+
const { scopeRoot, scope } = state.failure;
|
|
944
|
+
return `${theConfigAt(scopeRoot, hosts[scope])} ${verdict}`;
|
|
945
|
+
}
|
|
946
|
+
return `${theConfigAt(projectDir, hosts.project)} ${verdict}`;
|
|
781
947
|
}
|
|
782
|
-
function
|
|
783
|
-
return `${scopeLabel(scopeOf(
|
|
948
|
+
function theConfigAt(root, provider) {
|
|
949
|
+
return `${scopeLabel(scopeOf(root))}: ${configNamedFrom(root, provider)}`;
|
|
784
950
|
}
|
|
785
|
-
function theInheritedConfig() {
|
|
786
|
-
return `${scopeLabel("project")}: no ${STANDARD_FILES.CONFIG_TS}, using ${scopeNoun("global")}'s (${configNamedFrom(
|
|
951
|
+
function theInheritedConfig(provider) {
|
|
952
|
+
return `${scopeLabel("project")}: no ${STANDARD_FILES.CONFIG_TS}, using ${scopeNoun("global")}'s (${configNamedFrom(os3.homedir(), provider)})`;
|
|
787
953
|
}
|
|
788
954
|
function scopeOf(dir) {
|
|
789
955
|
return isHomeDirectory(dir) ? "global" : "project";
|
|
790
956
|
}
|
|
791
|
-
function configNamedFrom(root) {
|
|
792
|
-
const relPath = relativeConfigPath(root,
|
|
957
|
+
function configNamedFrom(root, provider) {
|
|
958
|
+
const relPath = relativeConfigPath(root, provider);
|
|
793
959
|
return isHomeDirectory(root) ? `~/${relPath}` : relPath;
|
|
794
960
|
}
|
|
795
|
-
|
|
796
|
-
const
|
|
797
|
-
const stackIds = config.stack ? getStackSkillIds(config.stack) : [];
|
|
798
|
-
const uniqueSkills = unique([...stackIds, ...config.skills.map((s) => s.id)]).filter(
|
|
799
|
-
(id) => !excludedIds.has(id)
|
|
800
|
-
);
|
|
961
|
+
function checkSkillsResolved(config, matrix2) {
|
|
962
|
+
const uniqueSkills = skillIdsToResolve(config);
|
|
801
963
|
if (uniqueSkills.length === 0) {
|
|
802
964
|
return {
|
|
803
965
|
kind: "skills",
|
|
@@ -805,17 +967,7 @@ async function checkSkillsResolved(config, matrix2, projectDir) {
|
|
|
805
967
|
message: "No skills configured"
|
|
806
968
|
};
|
|
807
969
|
}
|
|
808
|
-
const
|
|
809
|
-
discoverLocalSkills(projectDir),
|
|
810
|
-
!isHomeDirectory(projectDir) ? discoverLocalSkills(os2.homedir()) : null
|
|
811
|
-
]);
|
|
812
|
-
const localSkillIds = /* @__PURE__ */ new Set([
|
|
813
|
-
...localResult?.skills.map((s) => s.id) ?? [],
|
|
814
|
-
...globalResult?.skills.map((s) => s.id) ?? []
|
|
815
|
-
]);
|
|
816
|
-
const missingSkills = uniqueSkills.filter(
|
|
817
|
-
(skillId) => !(skillId in matrix2.skills) && !localSkillIds.has(skillId)
|
|
818
|
-
);
|
|
970
|
+
const missingSkills = uniqueSkills.filter((skillId) => !(skillId in matrix2.skills));
|
|
819
971
|
if (missingSkills.length > 0) {
|
|
820
972
|
return {
|
|
821
973
|
kind: "skills",
|
|
@@ -830,29 +982,27 @@ async function checkSkillsResolved(config, matrix2, projectDir) {
|
|
|
830
982
|
message: `${ratio(uniqueSkills.length, uniqueSkills.length, "skill")} found`
|
|
831
983
|
};
|
|
832
984
|
}
|
|
833
|
-
|
|
834
|
-
const
|
|
835
|
-
|
|
985
|
+
function skillIdsToResolve(config) {
|
|
986
|
+
const excludedIds = effectivelyExcludedSkillIds(config.skills);
|
|
987
|
+
const stackIds = config.stack ? getStackSkillIds(config.stack) : [];
|
|
988
|
+
return unique([...stackIds, ...config.skills.map((s) => s.id)]).filter(
|
|
989
|
+
(id) => !excludedIds.has(id)
|
|
990
|
+
);
|
|
991
|
+
}
|
|
992
|
+
async function checkAgentsCompiled(config, installation) {
|
|
993
|
+
if (config.agents.length === 0) {
|
|
836
994
|
return {
|
|
837
995
|
kind: "agents",
|
|
838
996
|
status: "pass",
|
|
839
997
|
message: "No agents configured"
|
|
840
998
|
};
|
|
841
999
|
}
|
|
842
|
-
const hosts = {
|
|
843
|
-
global: providerInUse(installBaseDir(projectDir, "global")),
|
|
844
|
-
project: providerInUse(installBaseDir(projectDir, "project"))
|
|
845
|
-
};
|
|
846
|
-
const dirs = {
|
|
847
|
-
global: resolveInstallPaths(projectDir, "global").agentsDir,
|
|
848
|
-
project: resolveInstallPaths(projectDir, "project").agentsDir
|
|
849
|
-
};
|
|
850
1000
|
const agentChecks = await Promise.all(
|
|
851
|
-
agents.map((agent) => agentCompileCheck(agent,
|
|
1001
|
+
config.agents.map((agent) => agentCompileCheck(agent, installation))
|
|
852
1002
|
);
|
|
853
1003
|
const unrenderable = agentChecks.filter((c) => c.state === "unrenderable");
|
|
854
1004
|
const missingAgents = agentChecks.filter((c) => c.state === "missing").map((c) => c.name);
|
|
855
|
-
if (unrenderable.length === agents.length) {
|
|
1005
|
+
if (unrenderable.length === config.agents.length) {
|
|
856
1006
|
const hostsLeavingThemOut = unique(unrenderable.map((a) => a.provider)).join(" or ");
|
|
857
1007
|
return {
|
|
858
1008
|
kind: "agents",
|
|
@@ -865,96 +1015,71 @@ async function checkAgentsCompiled(config, projectDir) {
|
|
|
865
1015
|
return {
|
|
866
1016
|
kind: "agents",
|
|
867
1017
|
status: "warn",
|
|
868
|
-
message:
|
|
1018
|
+
message: needRecompilation(missingAgents.length),
|
|
869
1019
|
details: missingAgents.map((a) => `- ${a} (missing)`)
|
|
870
1020
|
};
|
|
871
1021
|
}
|
|
872
|
-
const renderable = agents.length - unrenderable.length;
|
|
1022
|
+
const renderable = config.agents.length - unrenderable.length;
|
|
873
1023
|
return {
|
|
874
1024
|
kind: "agents",
|
|
875
1025
|
status: "pass",
|
|
876
1026
|
message: `${ratio(renderable, renderable, "agent")} compiled`
|
|
877
1027
|
};
|
|
878
1028
|
}
|
|
879
|
-
|
|
1029
|
+
function needRecompilation(count) {
|
|
1030
|
+
return `${plural(count, "agent")} ${count === 1 ? "needs" : "need"} recompilation`;
|
|
1031
|
+
}
|
|
1032
|
+
async function agentCompileCheck(agent, { projectDir, hosts }) {
|
|
880
1033
|
const provider = hosts[agent.scope];
|
|
881
1034
|
if (!hostCompilesAgent(provider, agent.name)) {
|
|
882
1035
|
return { name: agent.name, provider, state: "unrenderable" };
|
|
883
1036
|
}
|
|
884
1037
|
const file = `${agent.name}${agentCodec(provider).extension}`;
|
|
885
|
-
const compiled = await fileExists(
|
|
1038
|
+
const compiled = await fileExists(path4.join(agentsDir(provider, agent.scope, projectDir), file));
|
|
886
1039
|
return { name: agent.name, provider, state: compiled ? "compiled" : "missing" };
|
|
887
1040
|
}
|
|
888
|
-
async function checkNoOrphans(config,
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
const [projectExists, globalExists] = await Promise.all([
|
|
894
|
-
directoryExists(projectAgentsDir),
|
|
895
|
-
!scopesShareAgentsDir ? directoryExists(globalAgentsDir) : false
|
|
896
|
-
]);
|
|
897
|
-
if (!projectExists && !globalExists) {
|
|
1041
|
+
async function checkNoOrphans(state, config, installation) {
|
|
1042
|
+
return ownershipResult(state, await judgeOwnership(state, config, installation));
|
|
1043
|
+
}
|
|
1044
|
+
function ownershipResult(state, { unowned, orphans }) {
|
|
1045
|
+
if (unowned.skills.length > 0 || unowned.agents.length > 0) {
|
|
898
1046
|
return {
|
|
899
|
-
kind: "orphans",
|
|
900
|
-
status: "
|
|
901
|
-
message:
|
|
1047
|
+
kind: "orphans-unowned",
|
|
1048
|
+
status: "fail",
|
|
1049
|
+
message: `${countedArtifacts(unowned.skills, unowned.agents)} installed here, and no configuration declares them`,
|
|
1050
|
+
details: [...unowned.skills, ...unowned.agents, ...orphanLines(orphans)].map(
|
|
1051
|
+
(line) => `- ${line}`
|
|
1052
|
+
)
|
|
902
1053
|
};
|
|
903
1054
|
}
|
|
904
|
-
|
|
905
|
-
const globalMdFiles = globalExists ? await listAgentFilesOf(globalAgentsDir, codec) : [];
|
|
906
|
-
const activeProjectAgents = new Set(
|
|
907
|
-
config.agents.filter((a) => a.scope === "project" && !a.excluded).map((a) => a.name)
|
|
908
|
-
);
|
|
909
|
-
const knownGlobalAgents = new Set(
|
|
910
|
-
config.agents.filter((a) => a.scope === "global").map((a) => a.name)
|
|
911
|
-
);
|
|
912
|
-
const knownProjectDirAgents = scopesShareAgentsDir ? /* @__PURE__ */ new Set([...activeProjectAgents, ...knownGlobalAgents]) : activeProjectAgents;
|
|
913
|
-
const orphanedFiles = [
|
|
914
|
-
...orphanedAgentNames(projectMdFiles, knownProjectDirAgents, codec),
|
|
915
|
-
...orphanedAgentNames(globalMdFiles, knownGlobalAgents, codec)
|
|
916
|
-
];
|
|
917
|
-
if (orphanedFiles.length > 0) {
|
|
1055
|
+
if (orphans.skills.length > 0 || orphans.agents.length > 0) {
|
|
918
1056
|
return {
|
|
919
1057
|
kind: "orphans",
|
|
920
1058
|
status: "warn",
|
|
921
|
-
message:
|
|
922
|
-
details:
|
|
1059
|
+
message: countedOrphans(orphans),
|
|
1060
|
+
details: orphanLines(orphans).map((line) => `- ${line}`)
|
|
923
1061
|
};
|
|
924
1062
|
}
|
|
925
|
-
return
|
|
926
|
-
|
|
927
|
-
status: "pass",
|
|
928
|
-
message: "No orphaned agent files"
|
|
929
|
-
};
|
|
1063
|
+
if (state.kind === "absent") return skippedResult("orphans");
|
|
1064
|
+
return { kind: "orphans", status: "pass", message: "No orphaned skills or agent files" };
|
|
930
1065
|
}
|
|
931
|
-
function
|
|
932
|
-
return
|
|
1066
|
+
function orphanLines({ skills, agents }) {
|
|
1067
|
+
return [...skills, ...agents].map((name) => `${name} (not in config)`);
|
|
933
1068
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
if (skills.length === 0 && agents.length === 0) return skippedResult("orphans");
|
|
937
|
-
return {
|
|
938
|
-
kind: "orphans-unowned",
|
|
939
|
-
status: "fail",
|
|
940
|
-
// The per-line "(not in config)" the other verdict repeats is said once here instead:
|
|
941
|
-
// with no configuration it is true of every line, and there are as many lines as files.
|
|
942
|
-
message: `${countedArtifacts(skills, agents)} installed here, and no configuration declares them`,
|
|
943
|
-
details: [...skills, ...agents].map((artifact) => `- ${artifact}`)
|
|
944
|
-
};
|
|
1069
|
+
function countedOrphans({ skills, agents }) {
|
|
1070
|
+
return nonZeroCounts([skills.length, "orphaned skill"], [agents.length, "orphaned agent file"]);
|
|
945
1071
|
}
|
|
946
1072
|
function ratio(found, total, noun) {
|
|
947
1073
|
return `${found}/${plural(total, noun)}`;
|
|
948
1074
|
}
|
|
949
1075
|
function countedArtifacts(skills, agents) {
|
|
950
|
-
return [
|
|
951
|
-
...skills.length > 0 ? [plural(skills.length, "skill")] : [],
|
|
952
|
-
...agents.length > 0 ? [plural(agents.length, "agent")] : []
|
|
953
|
-
].join(" and ");
|
|
1076
|
+
return nonZeroCounts([skills.length, "skill"], [agents.length, "agent"]);
|
|
954
1077
|
}
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
1078
|
+
function nonZeroCounts(...counts) {
|
|
1079
|
+
return counts.filter(([count]) => count > 0).map(([count, noun]) => plural(count, noun)).join(" and ");
|
|
1080
|
+
}
|
|
1081
|
+
async function checkSkillsInstalled(config, installation) {
|
|
1082
|
+
const ejectSkills = config.skills.filter((s) => s.origin === EJECT_SOURCE);
|
|
958
1083
|
if (ejectSkills.length === 0) {
|
|
959
1084
|
return {
|
|
960
1085
|
kind: "installed",
|
|
@@ -963,7 +1088,7 @@ async function checkSkillsInstalled(config, projectDir) {
|
|
|
963
1088
|
};
|
|
964
1089
|
}
|
|
965
1090
|
const skillChecks = await Promise.all(
|
|
966
|
-
ejectSkills.map((skill) => ejectedSkillCheck(skill,
|
|
1091
|
+
ejectSkills.map((skill) => ejectedSkillCheck(skill, installation))
|
|
967
1092
|
);
|
|
968
1093
|
const missingSkills = skillChecks.filter((c) => !c.installed);
|
|
969
1094
|
if (missingSkills.length > 0) {
|
|
@@ -980,17 +1105,17 @@ async function checkSkillsInstalled(config, projectDir) {
|
|
|
980
1105
|
message: `${ratio(ejectSkills.length, ejectSkills.length, "eject-mode skill")} installed`
|
|
981
1106
|
};
|
|
982
1107
|
}
|
|
983
|
-
async function ejectedSkillCheck(skill, projectDir) {
|
|
984
|
-
const provider =
|
|
1108
|
+
async function ejectedSkillCheck(skill, { projectDir, hosts }) {
|
|
1109
|
+
const provider = hosts[skill.scope];
|
|
985
1110
|
const dir = skillsDir(provider, skill.scope, projectDir);
|
|
986
|
-
const installed = await fileExists(
|
|
1111
|
+
const installed = await fileExists(path4.join(dir, skill.id, STANDARD_FILES.SKILL_MD));
|
|
987
1112
|
return {
|
|
988
1113
|
id: skill.id,
|
|
989
1114
|
installed,
|
|
990
1115
|
lookedIn: skillsPathPrefix(provider, skill.scope, projectDir)
|
|
991
1116
|
};
|
|
992
1117
|
}
|
|
993
|
-
async function checkPluginSkillsInstalled(config,
|
|
1118
|
+
async function checkPluginSkillsInstalled(config, installation) {
|
|
994
1119
|
const pluginSkills = config.skills.filter((s) => s.origin !== EJECT_SOURCE);
|
|
995
1120
|
if (pluginSkills.length === 0) {
|
|
996
1121
|
return {
|
|
@@ -999,18 +1124,11 @@ async function checkPluginSkillsInstalled(config, projectDir) {
|
|
|
999
1124
|
message: "No plugin-mode skills configured"
|
|
1000
1125
|
};
|
|
1001
1126
|
}
|
|
1002
|
-
const
|
|
1003
|
-
const
|
|
1004
|
-
|
|
1005
|
-
const installedIds = new Set(
|
|
1006
|
-
(await getVerifiedPluginInstallPaths(baseDir)).map(
|
|
1007
|
-
(plugin) => parseMarketplacePluginRef(plugin.pluginKey)
|
|
1008
|
-
)
|
|
1009
|
-
);
|
|
1010
|
-
return pluginSkills.filter((skill) => installBaseDir(projectDir, skill.scope) === baseDir).filter((skill) => !installedIds.has(skill.id)).map((skill) => skill.id);
|
|
1011
|
-
})
|
|
1127
|
+
const scopes = unique(pluginSkills.map((s) => s.scope));
|
|
1128
|
+
const missingPerScope = await Promise.all(
|
|
1129
|
+
scopes.map((scope) => pluginSkillsMissingAt(scope, pluginSkills, installation))
|
|
1012
1130
|
);
|
|
1013
|
-
const missingSkills =
|
|
1131
|
+
const missingSkills = missingPerScope.flat();
|
|
1014
1132
|
if (missingSkills.length > 0) {
|
|
1015
1133
|
return {
|
|
1016
1134
|
kind: "plugins",
|
|
@@ -1025,6 +1143,13 @@ async function checkPluginSkillsInstalled(config, projectDir) {
|
|
|
1025
1143
|
message: `${ratio(pluginSkills.length, pluginSkills.length, "plugin-mode skill")} installed`
|
|
1026
1144
|
};
|
|
1027
1145
|
}
|
|
1146
|
+
async function pluginSkillsMissingAt(scope, pluginSkills, { projectDir, hosts }) {
|
|
1147
|
+
const installedIds = await installedPluginSkillIds(
|
|
1148
|
+
installBaseDir(projectDir, scope),
|
|
1149
|
+
hosts[scope]
|
|
1150
|
+
);
|
|
1151
|
+
return pluginSkills.filter((skill) => skill.scope === scope).filter((skill) => !installedIds.has(skill.id)).map((skill) => skill.id);
|
|
1152
|
+
}
|
|
1028
1153
|
var MARKETPLACE_CHOSEN_BY = {
|
|
1029
1154
|
flag: "named by this run",
|
|
1030
1155
|
env: `named by ${SOURCE_ENV_VAR}`,
|
|
@@ -1037,29 +1162,37 @@ function howItWasReached(source, isLocal) {
|
|
|
1037
1162
|
}
|
|
1038
1163
|
function reachedMarketplace(result) {
|
|
1039
1164
|
const { source, sourceOrigin } = result.sourceConfig;
|
|
1040
|
-
const
|
|
1041
|
-
const
|
|
1165
|
+
const available = `${plural(result.marketplaceSkillIds.size, "skill")} available`;
|
|
1166
|
+
const chosenBy = MARKETPLACE_CHOSEN_BY[sourceOrigin];
|
|
1167
|
+
if (result.unreachable) {
|
|
1168
|
+
return {
|
|
1169
|
+
kind: "source",
|
|
1170
|
+
status: "warn",
|
|
1171
|
+
message: `Could not reach ${source} \u2014 read its cached copy instead`,
|
|
1172
|
+
details: [available, `The copy at ${result.sourcePath} may be out of date \u2014 ${chosenBy}`]
|
|
1173
|
+
};
|
|
1174
|
+
}
|
|
1042
1175
|
return {
|
|
1043
1176
|
kind: "source",
|
|
1044
1177
|
status: "pass",
|
|
1045
|
-
message: `Connected to ${
|
|
1046
|
-
details: [
|
|
1047
|
-
`${plural(skillCount, "skill")} available`,
|
|
1048
|
-
`${howItWasReached(source, result.isLocal)} \u2014 ${MARKETPLACE_CHOSEN_BY[sourceOrigin]}`
|
|
1049
|
-
]
|
|
1178
|
+
message: `Connected to ${result.isLocal ? "local" : "remote"}: ${result.sourcePath}`,
|
|
1179
|
+
details: [available, `${howItWasReached(source, result.isLocal)} \u2014 ${chosenBy}`]
|
|
1050
1180
|
};
|
|
1051
1181
|
}
|
|
1052
|
-
async function checkSourceReachable(
|
|
1182
|
+
async function checkSourceReachable({
|
|
1183
|
+
projectDir,
|
|
1184
|
+
hosts
|
|
1185
|
+
}) {
|
|
1053
1186
|
try {
|
|
1054
|
-
|
|
1055
|
-
|
|
1187
|
+
return reachedMarketplace(
|
|
1188
|
+
await loadSkillsMatrixFromSource({ projectDir, provider: hosts.project })
|
|
1189
|
+
);
|
|
1056
1190
|
} catch (error) {
|
|
1057
|
-
const message = getErrorMessage(error);
|
|
1058
1191
|
return {
|
|
1059
1192
|
kind: "source",
|
|
1060
1193
|
status: "fail",
|
|
1061
|
-
message: "Failed to load marketplace",
|
|
1062
|
-
details: [
|
|
1194
|
+
message: refusedOr(error, "Failed to load marketplace"),
|
|
1195
|
+
details: [getErrorMessage(error)]
|
|
1063
1196
|
};
|
|
1064
1197
|
}
|
|
1065
1198
|
}
|
|
@@ -1074,11 +1207,8 @@ var CONTENT_CHECKS = [
|
|
|
1074
1207
|
kind: "content-sources",
|
|
1075
1208
|
name: "Marketplaces",
|
|
1076
1209
|
noun: "marketplace",
|
|
1077
|
-
// The registered marketplaces are the ones config.ts names.
|
|
1078
1210
|
readsConfig: true,
|
|
1079
|
-
// A
|
|
1080
|
-
// id is resolved against, so the skills row would call ids "not found" that this row has
|
|
1081
|
-
// already accounted for.
|
|
1211
|
+
// A broken marketplace leaves skills out of the matrix ids resolve against.
|
|
1082
1212
|
blocks: ["skills"],
|
|
1083
1213
|
run: validateRegisteredSources
|
|
1084
1214
|
},
|
|
@@ -1087,9 +1217,7 @@ var CONTENT_CHECKS = [
|
|
|
1087
1217
|
name: "Plugins",
|
|
1088
1218
|
noun: "plugin",
|
|
1089
1219
|
readsConfig: false,
|
|
1090
|
-
//
|
|
1091
|
-
// plugin-mode skill would read "no enabled plugin found" — the registry's finding, once per
|
|
1092
|
-
// configured skill, wearing the row's words.
|
|
1220
|
+
// An unparseable registry resolves as no installs at all.
|
|
1093
1221
|
blocks: ["plugins"],
|
|
1094
1222
|
run: validateInstalledPlugins
|
|
1095
1223
|
},
|
|
@@ -1097,12 +1225,9 @@ var CONTENT_CHECKS = [
|
|
|
1097
1225
|
kind: "content-skills",
|
|
1098
1226
|
name: "Skills",
|
|
1099
1227
|
noun: "skill",
|
|
1100
|
-
//
|
|
1101
|
-
// its directories are this installation's is a question only the config can answer for the
|
|
1102
|
-
// ones carrying no provenance marker. A config nobody can read leaves that unanswerable.
|
|
1228
|
+
// Only the config says which unmarked shared-skills directories are this installation's.
|
|
1103
1229
|
readsConfig: true,
|
|
1104
|
-
// `extractLocalSkill`
|
|
1105
|
-
// found" from the skills row would be this finding re-worded.
|
|
1230
|
+
// `extractLocalSkill` drops a skill with unusable metadata.
|
|
1106
1231
|
blocks: ["skills"],
|
|
1107
1232
|
run: validateInstalledSkills
|
|
1108
1233
|
},
|
|
@@ -1111,8 +1236,7 @@ var CONTENT_CHECKS = [
|
|
|
1111
1236
|
name: "Agents",
|
|
1112
1237
|
noun: "agent",
|
|
1113
1238
|
readsConfig: false,
|
|
1114
|
-
//
|
|
1115
|
-
// `No Orphans` reads the names of the files that are — broken frontmatter changes neither.
|
|
1239
|
+
// No operational row reads agent file contents.
|
|
1116
1240
|
blocks: [],
|
|
1117
1241
|
run: validateInstalledAgents
|
|
1118
1242
|
}
|
|
@@ -1197,58 +1321,77 @@ var TIPS = [
|
|
|
1197
1321
|
{
|
|
1198
1322
|
kind: "agents",
|
|
1199
1323
|
status: "warn",
|
|
1200
|
-
|
|
1324
|
+
line: (provider) => ` Tip: Run '${commandActingOn(provider, "compile")}' to generate missing agent files`
|
|
1201
1325
|
},
|
|
1202
1326
|
{
|
|
1203
1327
|
kind: "config",
|
|
1204
1328
|
status: "fail",
|
|
1205
|
-
|
|
1329
|
+
line: () => ` Tip: Run '${CLI_INVOKE_COMMAND} init' to create or fix configuration`
|
|
1206
1330
|
},
|
|
1207
1331
|
{
|
|
1208
|
-
// The remedy for a valid config with nothing in it. Not the tip above: there is nothing to
|
|
1209
|
-
// create and nothing to fix. `init` on this config opens the editor rather than the dashboard
|
|
1210
|
-
// — that is what the detection returning null is FOR — so it is the way to fill it in.
|
|
1211
1332
|
kind: "config-empty",
|
|
1212
1333
|
status: "warn",
|
|
1213
|
-
|
|
1334
|
+
line: () => ` Tip: Nothing is configured yet \u2014 run '${CLI_INVOKE_COMMAND} init' to choose skills and sub-agents`
|
|
1214
1335
|
},
|
|
1215
1336
|
{
|
|
1216
|
-
// Printed beside the config tip above, which says how to get a configuration back and
|
|
1217
|
-
// nothing about the files that outlived the old one. Both halves are named with what they
|
|
1218
|
-
// actually do: `uninstall` matches skill directories by their own `forked-from` metadata and
|
|
1219
|
-
// compiled agents by the marker each one carries, so neither needs the configuration that is
|
|
1220
|
-
// gone — which is why the row can name both and this tip can promise both.
|
|
1221
1337
|
kind: "orphans-unowned",
|
|
1222
1338
|
status: "fail",
|
|
1223
|
-
|
|
1339
|
+
line: (provider) => ` Tip: Nothing declares the files above \u2014 '${commandActingOn(provider, "init --from <id>")}' writes a configuration that can own them again, or '${commandActingOn(provider, "uninstall")}' removes them, the compiled agents included: each file listed carries this CLI's own provenance, which is what that command reads when there is no configuration left`
|
|
1224
1340
|
},
|
|
1225
1341
|
{
|
|
1226
1342
|
kind: "skills",
|
|
1227
1343
|
status: "fail",
|
|
1228
|
-
|
|
1344
|
+
line: () => " Tip: Check skill IDs in config match available skills"
|
|
1229
1345
|
},
|
|
1230
1346
|
{
|
|
1231
1347
|
kind: "installed",
|
|
1232
1348
|
status: "warn",
|
|
1233
|
-
// No command
|
|
1234
|
-
|
|
1235
|
-
// skill and it litters a plugin-mode project with local skill directories.
|
|
1236
|
-
tip: " Tip: Re-eject the missing skills from the marketplace to restore their files"
|
|
1349
|
+
// No command named: `eject skills --force` copies every skill, and only at project scope.
|
|
1350
|
+
line: () => " Tip: Re-eject the missing skills from the marketplace to restore their files"
|
|
1237
1351
|
}
|
|
1238
1352
|
];
|
|
1353
|
+
function commandActingOn(provider, command) {
|
|
1354
|
+
return `${CLI_INVOKE_COMMAND} ${command}${providerArgument(provider)}`;
|
|
1355
|
+
}
|
|
1239
1356
|
function formatTips(results) {
|
|
1240
|
-
return TIPS.
|
|
1241
|
-
|
|
1242
|
-
|
|
1357
|
+
return unique(TIPS.flatMap((tip) => linesOf(tip, results)));
|
|
1358
|
+
}
|
|
1359
|
+
function linesOf(tip, results) {
|
|
1360
|
+
return results.filter((result) => answers(tip, result)).map((result) => tip.line(result.provider));
|
|
1361
|
+
}
|
|
1362
|
+
function answers(tip, result) {
|
|
1363
|
+
return result.kind === tip.kind && result.status === tip.status;
|
|
1243
1364
|
}
|
|
1244
1365
|
async function recreateConfigTips(contentResults, projectDir) {
|
|
1245
1366
|
if (!failedContentKinds(contentResults).has(CONFIG_CHECK.kind)) return [];
|
|
1246
1367
|
const failures = await findConfigLoadFailures(projectDir).catch(() => []);
|
|
1247
1368
|
return failures.map(
|
|
1248
|
-
(failure) => ` Tip: There is no automatic repair \u2014 recreate the configuration by running ${recreateConfigFrom(failure
|
|
1369
|
+
(failure) => ` Tip: There is no automatic repair \u2014 recreate the configuration by running ${recreateConfigFrom(failure)}`
|
|
1249
1370
|
);
|
|
1250
1371
|
}
|
|
1372
|
+
var INSTALLATIONS_ROW_NAME = "Installations";
|
|
1373
|
+
async function checkInstallationsInPlay(projectDir, providers) {
|
|
1374
|
+
const finding = await ambiguityFinding(projectDir);
|
|
1375
|
+
return {
|
|
1376
|
+
kind: "installations",
|
|
1377
|
+
status: "fail",
|
|
1378
|
+
message: `${providers.length} installations here, each checked below`,
|
|
1379
|
+
details: finding === null ? [] : [`- ${finding}`]
|
|
1380
|
+
};
|
|
1381
|
+
}
|
|
1382
|
+
function installationHeading(provider) {
|
|
1383
|
+
return `${SECTION_INDENT}The ${provider} installation`;
|
|
1384
|
+
}
|
|
1385
|
+
var SOURCE_REPO_ROW_NAME = "Marketplace Repository";
|
|
1386
|
+
async function uninstalledSourceRepo(projectDir) {
|
|
1387
|
+
if (!await isSourceRepo(projectDir)) return null;
|
|
1388
|
+
return { kind: "source-repo", status: "skip", message: SKIP_NO_INSTALLATION };
|
|
1389
|
+
}
|
|
1251
1390
|
var LAYOUT_ROW_NAME = "Layout";
|
|
1391
|
+
async function checkLayout({ projectDir, hosts }) {
|
|
1392
|
+
const scopes = await sourceScopesInPlay(projectDir, ({ kind }) => hosts[kind]);
|
|
1393
|
+
return scopes.map(toLayoutResult);
|
|
1394
|
+
}
|
|
1252
1395
|
function toLayoutResult(scope) {
|
|
1253
1396
|
return {
|
|
1254
1397
|
kind: "layout",
|
|
@@ -1257,8 +1400,8 @@ function toLayoutResult(scope) {
|
|
|
1257
1400
|
};
|
|
1258
1401
|
}
|
|
1259
1402
|
var PLACEMENTS_ROW_NAME = "Placements Offered";
|
|
1260
|
-
async function checkOfferedPlacements(
|
|
1261
|
-
const findings = await
|
|
1403
|
+
async function checkOfferedPlacements(installation) {
|
|
1404
|
+
const findings = await unofferablePlacements(installation);
|
|
1262
1405
|
if (findings.length === 0) {
|
|
1263
1406
|
return { kind: "placements", status: "pass", message: "Every configured skill is placeable" };
|
|
1264
1407
|
}
|
|
@@ -1269,13 +1412,6 @@ async function checkOfferedPlacements(projectDir) {
|
|
|
1269
1412
|
details: findings.map((finding) => `- ${finding}`)
|
|
1270
1413
|
};
|
|
1271
1414
|
}
|
|
1272
|
-
async function everythingThatWouldStopACommandActing(projectDir) {
|
|
1273
|
-
const [unofferable, ambiguous] = await Promise.all([
|
|
1274
|
-
unofferablePlacementsFound(projectDir),
|
|
1275
|
-
ambiguityFinding(projectDir)
|
|
1276
|
-
]);
|
|
1277
|
-
return ambiguous === null ? unofferable : [...unofferable, ambiguous];
|
|
1278
|
-
}
|
|
1279
1415
|
function skippedResult(kind) {
|
|
1280
1416
|
return { kind, status: "skip", message: "Skipped (config invalid)" };
|
|
1281
1417
|
}
|
|
@@ -1296,13 +1432,31 @@ function contentBlockedResult(row, failedContent) {
|
|
|
1296
1432
|
function runContentCheck(check, projectDir) {
|
|
1297
1433
|
return safeCheck(check.kind, async () => toContentResult(check, await check.run(projectDir)));
|
|
1298
1434
|
}
|
|
1435
|
+
async function runGatedContentCheck(check, projectDir, configUnreadable) {
|
|
1436
|
+
if (check.readsConfig && configUnreadable) return skippedContentResult(check.kind);
|
|
1437
|
+
return runContentCheck(check, projectDir);
|
|
1438
|
+
}
|
|
1439
|
+
async function flatMapInTurn(items, step) {
|
|
1440
|
+
return items.reduce(
|
|
1441
|
+
async (earlier, item) => [...await earlier, ...await step(item)],
|
|
1442
|
+
Promise.resolve([])
|
|
1443
|
+
);
|
|
1444
|
+
}
|
|
1299
1445
|
async function safeCheck(kind, fn) {
|
|
1300
1446
|
try {
|
|
1301
1447
|
return await fn();
|
|
1302
1448
|
} catch (error) {
|
|
1303
|
-
return {
|
|
1449
|
+
return {
|
|
1450
|
+
kind,
|
|
1451
|
+
status: "fail",
|
|
1452
|
+
message: refusedOr(error, "Check threw"),
|
|
1453
|
+
details: [getErrorMessage(error)]
|
|
1454
|
+
};
|
|
1304
1455
|
}
|
|
1305
1456
|
}
|
|
1457
|
+
function refusedOr(error, otherwise) {
|
|
1458
|
+
return error instanceof MarketplaceRefusedError ? "Refused" : otherwise;
|
|
1459
|
+
}
|
|
1306
1460
|
var Doctor = class _Doctor extends BaseCommand {
|
|
1307
1461
|
static summary = "Diagnose common configuration issues";
|
|
1308
1462
|
static description = `Run diagnostic checks on your ${DEFAULT_BRANDING.NAME} configuration to identify issues with config validity, skill resolution, agent compilation, and marketplace connectivity.`;
|
|
@@ -1327,12 +1481,6 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1327
1481
|
this.log(`${SECTION_INDENT}Checking configuration health...`);
|
|
1328
1482
|
this.log("");
|
|
1329
1483
|
}
|
|
1330
|
-
/**
|
|
1331
|
-
* The config is checked first and alone. A check that reads it to know what to validate would
|
|
1332
|
-
* otherwise ask a file already reported as unreadable — and asking is what emitted the loader's
|
|
1333
|
-
* own failure line once per read, spliced between these rows. Everything that walks installed
|
|
1334
|
-
* content on disk still runs: it says something true whatever the config is in.
|
|
1335
|
-
*/
|
|
1336
1484
|
async runContentChecks(projectDir) {
|
|
1337
1485
|
this.log(`${SECTION_INDENT}${SECTION_CONTENT}`);
|
|
1338
1486
|
const configResult = await runContentCheck(CONFIG_CHECK, projectDir);
|
|
@@ -1340,7 +1488,7 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1340
1488
|
const rows = await Promise.all(
|
|
1341
1489
|
CONTENT_CHECKS.map(async (check) => ({
|
|
1342
1490
|
name: check.name,
|
|
1343
|
-
result:
|
|
1491
|
+
result: await runGatedContentCheck(check, projectDir, configUnreadable)
|
|
1344
1492
|
}))
|
|
1345
1493
|
);
|
|
1346
1494
|
const allRows = [{ name: CONFIG_CHECK.name, result: configResult }, ...rows];
|
|
@@ -1349,13 +1497,6 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1349
1497
|
}
|
|
1350
1498
|
return allRows.map((row) => row.result);
|
|
1351
1499
|
}
|
|
1352
|
-
/**
|
|
1353
|
-
* The whole layer stands down for the two findings that leave every row with nothing to say: a
|
|
1354
|
-
* config nobody can read — every row is read out of it, so all of them would be cascades of that
|
|
1355
|
-
* one file — and a marketplace repository with nothing installed, where a marketplace author has
|
|
1356
|
-
* no install for the layer to describe. Every other content error is scoped to the rows that
|
|
1357
|
-
* read what it is about; {@link GatedContentCheck} says which those are, per pass.
|
|
1358
|
-
*/
|
|
1359
1500
|
async runOperationalChecks(projectDir, contentResults) {
|
|
1360
1501
|
this.log("");
|
|
1361
1502
|
this.log(`${SECTION_INDENT}${SECTION_OPERATIONAL}`);
|
|
@@ -1364,48 +1505,78 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1364
1505
|
this.log(`${ROW_INDENT}${SKIP_AFTER_CONFIG_ERROR}`);
|
|
1365
1506
|
return [];
|
|
1366
1507
|
}
|
|
1367
|
-
const
|
|
1368
|
-
if (
|
|
1369
|
-
this.
|
|
1508
|
+
const providers = await providersInPlay(projectDir);
|
|
1509
|
+
if (providers.length > 1) {
|
|
1510
|
+
return this.checkEachInstallation(projectDir, providers, failedContent);
|
|
1511
|
+
}
|
|
1512
|
+
return this.checkTheInstallation(projectDir, failedContent);
|
|
1513
|
+
}
|
|
1514
|
+
async checkTheInstallation(projectDir, failedContent) {
|
|
1515
|
+
const installation = { projectDir, hosts: hostsOnDisk(projectDir) };
|
|
1516
|
+
const configState = await resolveConfigState(installation);
|
|
1517
|
+
if (configState.kind === "absent") {
|
|
1518
|
+
const sourceRepo = await safeCheck("source-repo", () => uninstalledSourceRepo(projectDir));
|
|
1519
|
+
if (sourceRepo !== null) return this.reportSourceRepo(sourceRepo);
|
|
1520
|
+
}
|
|
1521
|
+
return this.runAllChecks(installation, configState, failedContent);
|
|
1522
|
+
}
|
|
1523
|
+
/** A repository nothing is installed in skips the layer; one whose own config breaks is a row. */
|
|
1524
|
+
reportSourceRepo(sourceRepo) {
|
|
1525
|
+
if (sourceRepo.status === "skip") {
|
|
1526
|
+
this.log(`${ROW_INDENT}${sourceRepo.message}`);
|
|
1370
1527
|
return [];
|
|
1371
1528
|
}
|
|
1372
|
-
|
|
1373
|
-
return
|
|
1374
|
-
}
|
|
1375
|
-
/**
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1385
|
-
|
|
1529
|
+
this.logCheck(SOURCE_REPO_ROW_NAME, sourceRepo);
|
|
1530
|
+
return [sourceRepo];
|
|
1531
|
+
}
|
|
1532
|
+
/** In turn rather than at once: each block prints as it runs, and each load seats the matrix. */
|
|
1533
|
+
async checkEachInstallation(projectDir, providers, failedContent) {
|
|
1534
|
+
const installations = await safeCheck(
|
|
1535
|
+
"installations",
|
|
1536
|
+
() => checkInstallationsInPlay(projectDir, providers)
|
|
1537
|
+
);
|
|
1538
|
+
this.logCheck(INSTALLATIONS_ROW_NAME, installations);
|
|
1539
|
+
const blocks = await flatMapInTurn(
|
|
1540
|
+
providers,
|
|
1541
|
+
(provider) => this.checkOneOf(projectDir, provider, failedContent)
|
|
1542
|
+
);
|
|
1543
|
+
return [installations, ...blocks];
|
|
1544
|
+
}
|
|
1545
|
+
async checkOneOf(projectDir, provider, failedContent) {
|
|
1546
|
+
this.log("");
|
|
1547
|
+
this.log(installationHeading(provider));
|
|
1548
|
+
const installation = { projectDir, hosts: hostsOf(provider) };
|
|
1549
|
+
const results = await this.runAllChecks(
|
|
1550
|
+
installation,
|
|
1551
|
+
await resolveConfigState(installation),
|
|
1552
|
+
failedContent
|
|
1553
|
+
);
|
|
1554
|
+
return results.map((result) => ({ ...result, provider }));
|
|
1555
|
+
}
|
|
1556
|
+
async runAllChecks(installation, configState, failedContent) {
|
|
1557
|
+
const { result: configResult, config } = checkConfigValid(configState, installation);
|
|
1386
1558
|
this.logCheck("Config Valid", configResult);
|
|
1387
|
-
const layoutResults = await this.reportLayout(
|
|
1559
|
+
const layoutResults = await this.reportLayout(installation);
|
|
1388
1560
|
const placementsResult = await safeCheck(
|
|
1389
1561
|
"placements",
|
|
1390
|
-
() => checkOfferedPlacements(
|
|
1562
|
+
() => checkOfferedPlacements(installation)
|
|
1391
1563
|
);
|
|
1392
1564
|
this.logCheck(PLACEMENTS_ROW_NAME, placementsResult);
|
|
1393
|
-
const sourceResult = await safeCheck("source", () => checkSourceReachable(
|
|
1565
|
+
const sourceResult = await safeCheck("source", () => checkSourceReachable(installation));
|
|
1394
1566
|
const filteredConfig = config ? filterExcludedEntries(config) : null;
|
|
1395
|
-
const skillsResult = await this.resolveSkillsCheck(
|
|
1396
|
-
config,
|
|
1397
|
-
sourceResult,
|
|
1398
|
-
projectDir,
|
|
1399
|
-
failedContent
|
|
1400
|
-
);
|
|
1567
|
+
const skillsResult = await this.resolveSkillsCheck(config, sourceResult, failedContent);
|
|
1401
1568
|
this.logCheck("Skills Resolved", skillsResult);
|
|
1402
|
-
const agentsResult =
|
|
1569
|
+
const agentsResult = await this.resolveAgentsCheck(filteredConfig, installation);
|
|
1403
1570
|
this.logCheck("Agents Compiled", agentsResult);
|
|
1404
|
-
const orphansResult = await this.resolveOrphansCheck(config, configState,
|
|
1571
|
+
const orphansResult = await this.resolveOrphansCheck(config, configState, installation);
|
|
1405
1572
|
this.logCheck("No Orphans", orphansResult);
|
|
1406
|
-
const installedResult =
|
|
1573
|
+
const installedResult = await this.resolveInstalledCheck(filteredConfig, installation);
|
|
1407
1574
|
this.logCheck("Skills Installed", installedResult);
|
|
1408
|
-
const pluginsResult = await this.resolvePluginsCheck(
|
|
1575
|
+
const pluginsResult = await this.resolvePluginsCheck(
|
|
1576
|
+
filteredConfig,
|
|
1577
|
+
installation,
|
|
1578
|
+
failedContent
|
|
1579
|
+
);
|
|
1409
1580
|
this.logCheck("Plugins Installed", pluginsResult);
|
|
1410
1581
|
this.logCheck("Marketplace Reachable", sourceResult);
|
|
1411
1582
|
return [
|
|
@@ -1420,59 +1591,38 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1420
1591
|
sourceResult
|
|
1421
1592
|
];
|
|
1422
1593
|
}
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
*
|
|
1426
|
-
* Per scope rather than per machine: a project and the global installation are two
|
|
1427
|
-
* installations, and one row for the two can only name one of their folders.
|
|
1428
|
-
*
|
|
1429
|
-
* Printed right under `Config Valid`, because every row below it is read out of a config file
|
|
1430
|
-
* in the folder this row names.
|
|
1431
|
-
*/
|
|
1432
|
-
async reportLayout(projectDir) {
|
|
1433
|
-
const rows = (await sourceScopesInPlay(projectDir)).map(toLayoutResult);
|
|
1594
|
+
async reportLayout(installation) {
|
|
1595
|
+
const rows = await checkLayout(installation);
|
|
1434
1596
|
for (const row of rows) {
|
|
1435
1597
|
this.logCheck(LAYOUT_ROW_NAME, row);
|
|
1436
1598
|
}
|
|
1437
1599
|
return rows;
|
|
1438
1600
|
}
|
|
1439
|
-
/**
|
|
1440
|
-
|
|
1441
|
-
|
|
1442
|
-
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
*/
|
|
1446
|
-
async resolveOrphansCheck(config, configState, projectDir) {
|
|
1447
|
-
if (config) return safeCheck("orphans", () => checkNoOrphans(config, projectDir));
|
|
1448
|
-
if (configState.kind === "absent") {
|
|
1449
|
-
return safeCheck("orphans", () => checkUnownedInstallation(projectDir));
|
|
1450
|
-
}
|
|
1451
|
-
return skippedResult("orphans");
|
|
1452
|
-
}
|
|
1453
|
-
/**
|
|
1454
|
-
* The skills row, and the three states in which it cannot be computed: no config to read the
|
|
1455
|
-
* skills out of, content it resolves against that the layer above has already reported broken,
|
|
1456
|
-
* and a marketplace that never loaded — so there is no populated matrix to resolve them
|
|
1457
|
-
* against, and every configured skill would be reported "not found".
|
|
1458
|
-
*/
|
|
1459
|
-
async resolveSkillsCheck(config, sourceResult, projectDir, failedContent) {
|
|
1601
|
+
/** Skips on a config the checks rejected: ownership can't be judged from a rejected file. */
|
|
1602
|
+
async resolveOrphansCheck(config, configState, installation) {
|
|
1603
|
+
if (!config && configState.kind !== "absent") return skippedResult("orphans");
|
|
1604
|
+
return safeCheck("orphans", () => checkNoOrphans(configState, config, installation));
|
|
1605
|
+
}
|
|
1606
|
+
async resolveSkillsCheck(config, sourceResult, failedContent) {
|
|
1460
1607
|
if (!config) return skippedResult("skills");
|
|
1461
1608
|
const blocked = contentBlockedResult("skills", failedContent);
|
|
1462
1609
|
if (blocked) return blocked;
|
|
1463
1610
|
if (sourceResult.status === "fail") return sourceUnreachableSkillsResult();
|
|
1464
|
-
return safeCheck("skills", () => checkSkillsResolved(config, matrix
|
|
1465
|
-
}
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1611
|
+
return safeCheck("skills", () => checkSkillsResolved(config, matrix));
|
|
1612
|
+
}
|
|
1613
|
+
async resolveAgentsCheck(config, installation) {
|
|
1614
|
+
if (!config) return skippedResult("agents");
|
|
1615
|
+
return safeCheck("agents", () => checkAgentsCompiled(config, installation));
|
|
1616
|
+
}
|
|
1617
|
+
async resolveInstalledCheck(config, installation) {
|
|
1618
|
+
if (!config) return skippedResult("installed");
|
|
1619
|
+
return safeCheck("installed", () => checkSkillsInstalled(config, installation));
|
|
1620
|
+
}
|
|
1621
|
+
async resolvePluginsCheck(config, installation, failedContent) {
|
|
1472
1622
|
if (!config) return skippedResult("plugins");
|
|
1473
1623
|
const blocked = contentBlockedResult("plugins", failedContent);
|
|
1474
1624
|
if (blocked) return blocked;
|
|
1475
|
-
return safeCheck("plugins", () => checkPluginSkillsInstalled(config,
|
|
1625
|
+
return safeCheck("plugins", () => checkPluginSkillsInstalled(config, installation));
|
|
1476
1626
|
}
|
|
1477
1627
|
logCheck(name, result) {
|
|
1478
1628
|
for (const line of formatCheckLine(name, result)) {
|