agents-inc 0.164.0 → 0.166.0
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 +63 -0
- package/README.md +8 -9
- package/dist/{chunk-X4SSJZLY.js → chunk-25MPBIK3.js} +12 -261
- package/dist/chunk-25MPBIK3.js.map +1 -0
- package/dist/{chunk-LT5GXEB2.js → chunk-2VUYFTUB.js} +8 -7
- package/dist/chunk-2VUYFTUB.js.map +1 -0
- package/dist/chunk-4AAOOAIN.js +49 -0
- package/dist/chunk-4AAOOAIN.js.map +1 -0
- package/dist/{chunk-ARYNOWKF.js → chunk-4NDBUJHK.js} +8 -8
- package/dist/{chunk-S63LXWMC.js → chunk-6L75SQZU.js} +2 -2
- package/dist/{chunk-7REKOTKG.js → chunk-6LF4CQBY.js} +3 -3
- package/dist/{chunk-K5ITEA3I.js → chunk-7JNJ52CD.js} +34 -17
- package/dist/chunk-7JNJ52CD.js.map +1 -0
- package/dist/{chunk-XPDVAJDH.js → chunk-A4KDXNOT.js} +3519 -1568
- package/dist/chunk-A4KDXNOT.js.map +1 -0
- package/dist/{chunk-XHPWTL4I.js → chunk-DAIEQJIF.js} +3 -3
- package/dist/{chunk-UITEKY2R.js → chunk-ENGYJCTF.js} +28 -4
- package/dist/chunk-ENGYJCTF.js.map +1 -0
- package/dist/{chunk-G2JJ5BO5.js → chunk-G4JK7WVO.js} +336 -140
- package/dist/chunk-G4JK7WVO.js.map +1 -0
- package/dist/{chunk-X37UYIE3.js → chunk-G6UREQ4V.js} +436 -739
- package/dist/chunk-G6UREQ4V.js.map +1 -0
- package/dist/chunk-GE3WKJ6Q.js +375 -0
- package/dist/chunk-GE3WKJ6Q.js.map +1 -0
- package/dist/{chunk-RGYDFBCT.js → chunk-HHIYT3PI.js} +5 -5
- package/dist/{chunk-2CQNPLVF.js → chunk-HWEKHVLE.js} +63 -42
- package/dist/chunk-HWEKHVLE.js.map +1 -0
- package/dist/{chunk-XVAQG2CV.js → chunk-JOEA52NR.js} +10 -8
- package/dist/chunk-JOEA52NR.js.map +1 -0
- package/dist/{chunk-3WVH2CTK.js → chunk-JQRJQ4SS.js} +15 -8
- package/dist/chunk-JQRJQ4SS.js.map +1 -0
- package/dist/{chunk-DSL66I4N.js → chunk-LFWLEDUG.js} +6 -6
- package/dist/{chunk-B3Y3QMWI.js → chunk-MAWNVT2C.js} +4 -4
- package/dist/{chunk-Q4IRRVXK.js → chunk-MPTNC32T.js} +3 -3
- package/dist/chunk-NBXXFIIW.js +73 -0
- package/dist/chunk-NBXXFIIW.js.map +1 -0
- package/dist/{chunk-B6FSLXYN.js → chunk-PMJWD27C.js} +3 -3
- package/dist/{chunk-XATLUKXK.js → chunk-PNB3524H.js} +6 -6
- package/dist/chunk-PXWG7KFO.js +39 -0
- package/dist/chunk-PXWG7KFO.js.map +1 -0
- package/dist/{chunk-4VL3CEOS.js → chunk-QHDPLLK6.js} +3 -3
- package/dist/{chunk-AOTCIUGX.js → chunk-RAHSOJQ2.js} +2 -2
- package/dist/{chunk-PHCPUQVR.js → chunk-RZDXDMT6.js} +2 -2
- package/dist/{chunk-6MV4JOCL.js → chunk-SAYZWCH6.js} +2 -2
- package/dist/{chunk-URXIQE7I.js → chunk-SCOQ34YM.js} +2 -2
- package/dist/{chunk-NQXO4LR3.js → chunk-SXVHSOTH.js} +16 -11
- package/dist/chunk-SXVHSOTH.js.map +1 -0
- package/dist/{chunk-QZX7SYKU.js → chunk-T6UWJAB3.js} +2 -2
- package/dist/{chunk-ZVXGW2IC.js → chunk-TODCQFRB.js} +4 -6
- package/dist/chunk-TODCQFRB.js.map +1 -0
- package/dist/{chunk-YRS3QZP2.js → chunk-TTY6DPJN.js} +3 -3
- package/dist/{chunk-QGUP3QJF.js → chunk-TZ2YJJKK.js} +22 -18
- package/dist/{chunk-QGUP3QJF.js.map → chunk-TZ2YJJKK.js.map} +1 -1
- package/dist/chunk-V3I7VQ7N.js +356 -0
- package/dist/chunk-V3I7VQ7N.js.map +1 -0
- package/dist/{chunk-AVK2HEKB.js → chunk-V5WXIM2F.js} +2 -6
- package/dist/{chunk-AVK2HEKB.js.map → chunk-V5WXIM2F.js.map} +1 -1
- package/dist/{chunk-NKGYKOB7.js → chunk-VAPTVR3J.js} +7 -8
- package/dist/chunk-VAPTVR3J.js.map +1 -0
- package/dist/chunk-VRXX4D6K.js +31 -0
- package/dist/chunk-VRXX4D6K.js.map +1 -0
- package/dist/{chunk-ABAZMMG4.js → chunk-WVUDLKIO.js} +2 -2
- package/dist/{chunk-ECURHW3T.js → chunk-WWIFNPJG.js} +13 -12
- package/dist/chunk-WWIFNPJG.js.map +1 -0
- package/dist/{chunk-J27W6TXS.js → chunk-XQHZPINL.js} +8 -4
- package/dist/chunk-XQHZPINL.js.map +1 -0
- package/dist/{chunk-V5GLL2JB.js → chunk-XZEVWPX6.js} +7 -7
- package/dist/chunk-Y3VIRVVE.js +932 -0
- package/dist/chunk-Y3VIRVVE.js.map +1 -0
- package/dist/{chunk-NTF4UXGK.js → chunk-Y47EV74W.js} +73 -30
- package/dist/chunk-Y47EV74W.js.map +1 -0
- package/dist/chunk-ZR4YPKXQ.js +159 -0
- package/dist/chunk-ZR4YPKXQ.js.map +1 -0
- package/dist/chunk-ZXKBERQU.js +250 -0
- package/dist/chunk-ZXKBERQU.js.map +1 -0
- package/dist/commands/build/marketplace.js +17 -17
- package/dist/commands/build/marketplace.js.map +1 -1
- package/dist/commands/build/plugins.js +11 -9
- package/dist/commands/build/plugins.js.map +1 -1
- package/dist/commands/compile.js +105 -27
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/doctor.js +309 -171
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/edit.js +503 -314
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/eject.js +40 -23
- package/dist/commands/eject.js.map +1 -1
- package/dist/commands/init.js +44 -40
- package/dist/commands/list.js +44 -10
- package/dist/commands/list.js.map +1 -1
- package/dist/commands/new/marketplace.js +13 -10
- package/dist/commands/new/marketplace.js.map +1 -1
- package/dist/commands/search.js +12 -10
- package/dist/commands/search.js.map +1 -1
- package/dist/commands/share.js +102 -15
- package/dist/commands/share.js.map +1 -1
- package/dist/commands/uninstall.js +231 -103
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +49 -20
- package/dist/commands/update.js.map +1 -1
- package/dist/components/common/prompt-confirm.js +1 -1
- package/dist/components/common/removal-plan-confirm.js +2 -2
- package/dist/components/common/select-list.js +3 -3
- package/dist/components/common/spinner.js +6 -3
- package/dist/components/wizard/category-grid.js +5 -5
- package/dist/components/wizard/checkbox-grid.js +4 -4
- package/dist/components/wizard/domain-selection.js +9 -9
- package/dist/components/wizard/run-wizard-session.js +29 -29
- package/dist/components/wizard/scroll-affordance.js +2 -2
- package/dist/components/wizard/skill-agent-summary.js +5 -5
- package/dist/components/wizard/source-grid.js +6 -6
- package/dist/components/wizard/stack-selection.js +8 -8
- package/dist/components/wizard/step-agents.js +8 -8
- package/dist/components/wizard/step-build.js +8 -8
- package/dist/components/wizard/step-confirm.js +9 -9
- package/dist/components/wizard/step-sources.js +9 -9
- package/dist/components/wizard/step-stack.js +9 -9
- package/dist/components/wizard/summary-panel.js +8 -8
- package/dist/components/wizard/toast.js +2 -2
- package/dist/components/wizard/wizard-layout.js +12 -12
- package/dist/components/wizard/wizard-tabs.js +2 -2
- package/dist/components/wizard/wizard.js +27 -27
- package/dist/hooks/init.js +69 -43
- package/dist/hooks/init.js.map +1 -1
- package/dist/load-agent-defs-3C27ENC7.js +14 -0
- package/dist/recompile-project-agents-DGNBCU3N.js +18 -0
- package/dist/{source-loader-GRBVNRAW.js → source-loader-7KZ3MD5O.js} +6 -4
- package/dist/src/agents/_templates/agent-body.liquid +55 -0
- package/dist/src/agents/_templates/agent.liquid +1 -55
- package/dist/src/agents/meta/agent-summoner/output.md +1 -1
- package/dist/src/agents/meta/agent-summoner/playbook.md +15 -7
- package/dist/stores/wizard-store.js +4 -4
- package/package.json +10 -9
- package/src/agents/_templates/agent-body.liquid +55 -0
- package/src/agents/_templates/agent.liquid +1 -55
- package/src/agents/meta/agent-summoner/output.md +1 -1
- package/src/agents/meta/agent-summoner/playbook.md +15 -7
- package/src/schemas/project-config.schema.json +1 -1
- package/src/schemas/project-source-config.schema.json +1 -1
- package/dist/chunk-2CQNPLVF.js.map +0 -1
- package/dist/chunk-3WVH2CTK.js.map +0 -1
- package/dist/chunk-7ANJQLPJ.js +0 -35
- package/dist/chunk-7ANJQLPJ.js.map +0 -1
- package/dist/chunk-ECURHW3T.js.map +0 -1
- package/dist/chunk-FLW25ORT.js +0 -130
- package/dist/chunk-FLW25ORT.js.map +0 -1
- package/dist/chunk-G2JJ5BO5.js.map +0 -1
- package/dist/chunk-J27W6TXS.js.map +0 -1
- package/dist/chunk-JCPANDNR.js +0 -35
- package/dist/chunk-JCPANDNR.js.map +0 -1
- package/dist/chunk-JIERYE6B.js +0 -480
- package/dist/chunk-JIERYE6B.js.map +0 -1
- package/dist/chunk-JU4ZGJ7A.js +0 -668
- package/dist/chunk-JU4ZGJ7A.js.map +0 -1
- package/dist/chunk-K5ITEA3I.js.map +0 -1
- package/dist/chunk-LT5GXEB2.js.map +0 -1
- package/dist/chunk-NKGYKOB7.js.map +0 -1
- package/dist/chunk-NQXO4LR3.js.map +0 -1
- package/dist/chunk-NTF4UXGK.js.map +0 -1
- package/dist/chunk-UITEKY2R.js.map +0 -1
- package/dist/chunk-X37UYIE3.js.map +0 -1
- package/dist/chunk-X4SSJZLY.js.map +0 -1
- package/dist/chunk-XPDVAJDH.js.map +0 -1
- package/dist/chunk-XVAQG2CV.js.map +0 -1
- package/dist/chunk-ZBPEFQ4G.js +0 -12
- package/dist/chunk-ZBPEFQ4G.js.map +0 -1
- package/dist/chunk-ZVXGW2IC.js.map +0 -1
- package/dist/load-agent-defs-7IM3GBEQ.js +0 -13
- package/dist/recompile-project-agents-VR4NLGOU.js +0 -17
- /package/dist/{chunk-ARYNOWKF.js.map → chunk-4NDBUJHK.js.map} +0 -0
- /package/dist/{chunk-S63LXWMC.js.map → chunk-6L75SQZU.js.map} +0 -0
- /package/dist/{chunk-7REKOTKG.js.map → chunk-6LF4CQBY.js.map} +0 -0
- /package/dist/{chunk-XHPWTL4I.js.map → chunk-DAIEQJIF.js.map} +0 -0
- /package/dist/{chunk-RGYDFBCT.js.map → chunk-HHIYT3PI.js.map} +0 -0
- /package/dist/{chunk-DSL66I4N.js.map → chunk-LFWLEDUG.js.map} +0 -0
- /package/dist/{chunk-B3Y3QMWI.js.map → chunk-MAWNVT2C.js.map} +0 -0
- /package/dist/{chunk-Q4IRRVXK.js.map → chunk-MPTNC32T.js.map} +0 -0
- /package/dist/{chunk-B6FSLXYN.js.map → chunk-PMJWD27C.js.map} +0 -0
- /package/dist/{chunk-XATLUKXK.js.map → chunk-PNB3524H.js.map} +0 -0
- /package/dist/{chunk-4VL3CEOS.js.map → chunk-QHDPLLK6.js.map} +0 -0
- /package/dist/{chunk-AOTCIUGX.js.map → chunk-RAHSOJQ2.js.map} +0 -0
- /package/dist/{chunk-PHCPUQVR.js.map → chunk-RZDXDMT6.js.map} +0 -0
- /package/dist/{chunk-6MV4JOCL.js.map → chunk-SAYZWCH6.js.map} +0 -0
- /package/dist/{chunk-URXIQE7I.js.map → chunk-SCOQ34YM.js.map} +0 -0
- /package/dist/{chunk-QZX7SYKU.js.map → chunk-T6UWJAB3.js.map} +0 -0
- /package/dist/{chunk-YRS3QZP2.js.map → chunk-TTY6DPJN.js.map} +0 -0
- /package/dist/{chunk-ABAZMMG4.js.map → chunk-WVUDLKIO.js.map} +0 -0
- /package/dist/{chunk-V5GLL2JB.js.map → chunk-XZEVWPX6.js.map} +0 -0
- /package/dist/{load-agent-defs-7IM3GBEQ.js.map → load-agent-defs-3C27ENC7.js.map} +0 -0
- /package/dist/{recompile-project-agents-VR4NLGOU.js.map → recompile-project-agents-DGNBCU3N.js.map} +0 -0
- /package/dist/{source-loader-GRBVNRAW.js.map → source-loader-7KZ3MD5O.js.map} +0 -0
package/dist/commands/doctor.js
CHANGED
|
@@ -1,25 +1,35 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
unofferablePlacementsFound
|
|
4
|
+
} from "../chunk-PXWG7KFO.js";
|
|
2
5
|
import {
|
|
3
6
|
detectProject,
|
|
4
7
|
loadSource
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-G6UREQ4V.js";
|
|
9
|
+
import "../chunk-HWEKHVLE.js";
|
|
10
|
+
import "../chunk-PMJWD27C.js";
|
|
11
|
+
import "../chunk-ZR4YPKXQ.js";
|
|
8
12
|
import {
|
|
9
13
|
filterExcludedEntries,
|
|
14
|
+
listAgentFilesOf,
|
|
10
15
|
listAgentMdFiles,
|
|
11
16
|
splitAgentsByProvenance
|
|
12
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-GE3WKJ6Q.js";
|
|
18
|
+
import {
|
|
19
|
+
ambiguityFinding
|
|
20
|
+
} from "../chunk-NBXXFIIW.js";
|
|
13
21
|
import {
|
|
14
22
|
BaseCommand,
|
|
15
23
|
EXIT_CODES
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import "../chunk-
|
|
18
|
-
import "../chunk-
|
|
24
|
+
} from "../chunk-Y3VIRVVE.js";
|
|
25
|
+
import "../chunk-VAPTVR3J.js";
|
|
26
|
+
import "../chunk-RZDXDMT6.js";
|
|
19
27
|
import {
|
|
20
28
|
ConfigDefaultExportError,
|
|
29
|
+
MarketplaceManifestRequiredError,
|
|
21
30
|
MarketplaceNameRefusedError,
|
|
22
31
|
SOURCE_ENV_VAR,
|
|
32
|
+
agentCodec,
|
|
23
33
|
agentYamlGenerationSchema,
|
|
24
34
|
checkMatrixHealth,
|
|
25
35
|
configDirsInPlay,
|
|
@@ -30,53 +40,65 @@ import {
|
|
|
30
40
|
findConfigLoadFailures,
|
|
31
41
|
formatZodErrors,
|
|
32
42
|
getErrorMessage,
|
|
43
|
+
getInstalledConfigPath,
|
|
33
44
|
getInstalledPluginsRegistryPath,
|
|
34
|
-
getProjectConfigPath,
|
|
35
45
|
getProjectPluginsDir,
|
|
36
46
|
getStackSkillIds,
|
|
37
47
|
getUserPluginsDir,
|
|
38
48
|
getVerifiedPluginInstallPaths,
|
|
39
49
|
glob,
|
|
50
|
+
hostCompilesAgent,
|
|
40
51
|
installBaseDir,
|
|
52
|
+
invalidResult,
|
|
41
53
|
isHomeDirectory,
|
|
42
54
|
isLocalSource,
|
|
43
55
|
isRecord,
|
|
56
|
+
layoutIsCurrent,
|
|
44
57
|
listDirectories,
|
|
45
58
|
listRegisteredPluginInstalls,
|
|
46
59
|
loadConfig,
|
|
47
|
-
|
|
48
|
-
loadProjectSourceConfig,
|
|
60
|
+
loadInstalledConfig,
|
|
49
61
|
loadSkillsMatrixFromSource,
|
|
62
|
+
loadSourceRepoConfig,
|
|
63
|
+
mergeValidationResults,
|
|
50
64
|
metadataValidationSchema,
|
|
51
65
|
parseFrontmatter,
|
|
52
66
|
parseMarketplacePluginRef,
|
|
67
|
+
plural,
|
|
68
|
+
providerInUse,
|
|
53
69
|
readFile,
|
|
54
70
|
readForkedFromMetadata,
|
|
55
71
|
readSkillMetadata,
|
|
72
|
+
recreateConfigFrom,
|
|
73
|
+
relativeConfigPath,
|
|
56
74
|
resolveInstallPaths,
|
|
57
75
|
resolvePrimarySourceEntry,
|
|
76
|
+
scopeLabel,
|
|
77
|
+
scopeNoun,
|
|
58
78
|
skillCategoriesFileSchema,
|
|
59
79
|
skillRulesFileSchema,
|
|
80
|
+
skillsDir,
|
|
81
|
+
skillsPathPrefix,
|
|
82
|
+
sourceScopesInPlay,
|
|
60
83
|
splitMetadataValidationIssues,
|
|
61
84
|
stackConfigValidationSchema,
|
|
62
85
|
stacksConfigSchema,
|
|
86
|
+
validResult,
|
|
63
87
|
validateAgentFrontmatter,
|
|
64
88
|
validateAllPlugins,
|
|
65
89
|
validatePlugin,
|
|
66
90
|
validateProjectConfig,
|
|
67
91
|
validateSkillFrontmatter,
|
|
68
92
|
validateSkillMetadata
|
|
69
|
-
} from "../chunk-
|
|
93
|
+
} from "../chunk-A4KDXNOT.js";
|
|
70
94
|
import {
|
|
71
95
|
matrix
|
|
72
|
-
} from "../chunk-
|
|
96
|
+
} from "../chunk-V5WXIM2F.js";
|
|
73
97
|
import {
|
|
74
|
-
CLAUDE_SRC_DIR,
|
|
75
98
|
CLI_INVOKE_COMMAND,
|
|
76
99
|
DEFAULT_BRANDING,
|
|
77
100
|
DIRS,
|
|
78
101
|
EJECT_SOURCE,
|
|
79
|
-
LOCAL_SKILLS_PATH,
|
|
80
102
|
MARKETPLACE_JSON,
|
|
81
103
|
PLUGIN_MANIFEST_DIR,
|
|
82
104
|
SKILLS_DIR_PATH,
|
|
@@ -86,7 +108,7 @@ import {
|
|
|
86
108
|
STANDARD_FILES,
|
|
87
109
|
UI_SYMBOLS,
|
|
88
110
|
effectivelyExcludedSkillIds
|
|
89
|
-
} from "../chunk-
|
|
111
|
+
} from "../chunk-ENGYJCTF.js";
|
|
90
112
|
import "../chunk-OI2H6M5I.js";
|
|
91
113
|
|
|
92
114
|
// src/cli/commands/doctor.ts
|
|
@@ -98,7 +120,7 @@ import path from "path";
|
|
|
98
120
|
import { parse as parseYaml } from "yaml";
|
|
99
121
|
import "zod";
|
|
100
122
|
async function isSourceRepo(dir) {
|
|
101
|
-
const sourceConfig = await
|
|
123
|
+
const sourceConfig = await loadSourceRepoConfig(dir);
|
|
102
124
|
return directoryExists(path.join(dir, sourceConfig?.skillsDir ?? SKILLS_DIR_PATH));
|
|
103
125
|
}
|
|
104
126
|
function isSnakeCase(key) {
|
|
@@ -122,11 +144,11 @@ function checkDirNameMatchesSkillId(skillId, relPath, dirName) {
|
|
|
122
144
|
}
|
|
123
145
|
];
|
|
124
146
|
}
|
|
125
|
-
async function checkSkillDirName(
|
|
147
|
+
async function checkSkillDirName(skillsDir2, skillsDirRelPath, skillDir) {
|
|
126
148
|
const dirName = path.basename(skillDir);
|
|
127
149
|
const skillMdRelPath = path.join(skillsDirRelPath, skillDir, STANDARD_FILES.SKILL_MD);
|
|
128
150
|
try {
|
|
129
|
-
const content = await readFile(path.join(
|
|
151
|
+
const content = await readFile(path.join(skillsDir2, skillDir, STANDARD_FILES.SKILL_MD));
|
|
130
152
|
const frontmatter = parseFrontmatter(content, skillMdRelPath);
|
|
131
153
|
if (frontmatter === null) {
|
|
132
154
|
return [
|
|
@@ -148,67 +170,69 @@ async function checkSkillDirName(skillsDir, skillsDirRelPath, skillDir) {
|
|
|
148
170
|
];
|
|
149
171
|
}
|
|
150
172
|
}
|
|
151
|
-
function validateSkillFilePairs(skillMdDirs, metadataDirs,
|
|
173
|
+
function validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2) {
|
|
152
174
|
const missingMetadata = [...skillMdDirs].filter((dir) => !metadataDirs.has(dir)).map((dir) => ({
|
|
153
175
|
severity: "error",
|
|
154
|
-
file: path.join(
|
|
176
|
+
file: path.join(skillsDir2, dir),
|
|
155
177
|
message: `Missing ${STANDARD_FILES.METADATA_YAML} \u2014 skill directory has ${STANDARD_FILES.SKILL_MD} but no metadata`
|
|
156
178
|
}));
|
|
157
179
|
const missingSkillMd = [...metadataDirs].filter((dir) => !skillMdDirs.has(dir)).map((dir) => ({
|
|
158
180
|
severity: "error",
|
|
159
|
-
file: path.join(
|
|
181
|
+
file: path.join(skillsDir2, dir),
|
|
160
182
|
message: `Missing ${STANDARD_FILES.SKILL_MD} \u2014 skill directory has ${STANDARD_FILES.METADATA_YAML} but no SKILL.md`
|
|
161
183
|
}));
|
|
162
184
|
return [...missingMetadata, ...missingSkillMd];
|
|
163
185
|
}
|
|
164
186
|
async function validateSource(sourcePath, reader = "author") {
|
|
165
|
-
const issues = [];
|
|
166
187
|
const resolvedPath = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(sourcePath);
|
|
167
188
|
if (!await directoryExists(resolvedPath)) {
|
|
168
|
-
|
|
169
|
-
severity: "error",
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
});
|
|
173
|
-
return buildResult(issues, 0);
|
|
189
|
+
return buildResult(
|
|
190
|
+
[{ severity: "error", file: resolvedPath, message: "Marketplace directory does not exist" }],
|
|
191
|
+
0
|
|
192
|
+
);
|
|
174
193
|
}
|
|
175
|
-
const sourceProjectConfig = await
|
|
194
|
+
const sourceProjectConfig = await loadSourceRepoConfig(resolvedPath);
|
|
176
195
|
const skillsDirRelPath = sourceProjectConfig?.skillsDir ?? SKILLS_DIR_PATH;
|
|
177
|
-
const
|
|
178
|
-
if (!await directoryExists(
|
|
179
|
-
|
|
180
|
-
severity: "error",
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
});
|
|
184
|
-
return buildResult(issues, 0);
|
|
196
|
+
const skillsDir2 = path.join(resolvedPath, skillsDirRelPath);
|
|
197
|
+
if (!await directoryExists(skillsDir2)) {
|
|
198
|
+
return buildResult(
|
|
199
|
+
[{ severity: "error", file: skillsDir2, message: "Skills directory does not exist" }],
|
|
200
|
+
0
|
|
201
|
+
);
|
|
185
202
|
}
|
|
186
|
-
const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`,
|
|
187
|
-
const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`,
|
|
203
|
+
const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
|
|
204
|
+
const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir2);
|
|
188
205
|
const skillMdDirs = new Set(skillMdFiles.map((f) => path.dirname(f)));
|
|
189
206
|
const metadataDirs = new Set(metadataFiles.map((f) => path.dirname(f)));
|
|
190
|
-
|
|
207
|
+
const pairIssues = validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2);
|
|
191
208
|
const validMetadataFiles = metadataFiles.filter((f) => skillMdDirs.has(path.dirname(f)));
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
}
|
|
195
|
-
issues.push(...await checkCrossReferences(resolvedPath, reader));
|
|
209
|
+
const skillIssues = await validateSkillsInTurn(skillsDir2, skillsDirRelPath, validMetadataFiles);
|
|
210
|
+
const crossReferenceIssues = await checkCrossReferences(resolvedPath, reader);
|
|
196
211
|
const extraIssues = await Promise.all([
|
|
197
212
|
validateStacks(resolvedPath),
|
|
198
213
|
validateAgents(resolvedPath),
|
|
199
214
|
validateConfigFiles(resolvedPath)
|
|
200
215
|
]);
|
|
201
|
-
|
|
202
|
-
|
|
216
|
+
return buildResult(
|
|
217
|
+
[...pairIssues, ...skillIssues, ...crossReferenceIssues, ...extraIssues.flat()],
|
|
218
|
+
validMetadataFiles.length
|
|
219
|
+
);
|
|
220
|
+
}
|
|
221
|
+
async function validateSkillsInTurn(skillsDir2, skillsDirRelPath, metadataFiles) {
|
|
222
|
+
const issues = [];
|
|
223
|
+
for (const metadataFile of metadataFiles) {
|
|
224
|
+
issues.push(...await validateOneSkill(skillsDir2, skillsDirRelPath, metadataFile));
|
|
225
|
+
}
|
|
226
|
+
return issues;
|
|
203
227
|
}
|
|
204
228
|
function yamlParseFailure(error) {
|
|
205
229
|
return `Failed to parse YAML: ${getErrorMessage(error)}`;
|
|
206
230
|
}
|
|
207
|
-
async function validateOneSkill(
|
|
231
|
+
async function validateOneSkill(skillsDir2, skillsDirRelPath, metadataFile) {
|
|
208
232
|
const relPath = path.join(skillsDirRelPath, metadataFile);
|
|
209
233
|
let rawMetadata;
|
|
210
234
|
try {
|
|
211
|
-
rawMetadata = parseYaml(await readFile(path.join(
|
|
235
|
+
rawMetadata = parseYaml(await readFile(path.join(skillsDir2, metadataFile)));
|
|
212
236
|
} catch (error) {
|
|
213
237
|
return [{ severity: "error", file: relPath, message: yamlParseFailure(error) }];
|
|
214
238
|
}
|
|
@@ -216,7 +240,7 @@ async function validateOneSkill(skillsDir, skillsDirRelPath, metadataFile) {
|
|
|
216
240
|
...checkSnakeCaseKeys(rawMetadata, relPath),
|
|
217
241
|
...checkMetadataSchema(rawMetadata, relPath),
|
|
218
242
|
// Independent of metadata validity — the id lives in SKILL.md frontmatter
|
|
219
|
-
...await checkSkillDirName(
|
|
243
|
+
...await checkSkillDirName(skillsDir2, skillsDirRelPath, path.dirname(metadataFile))
|
|
220
244
|
];
|
|
221
245
|
}
|
|
222
246
|
function checkMetadataSchema(rawMetadata, relPath) {
|
|
@@ -229,6 +253,10 @@ function checkMetadataSchema(rawMetadata, relPath) {
|
|
|
229
253
|
];
|
|
230
254
|
}
|
|
231
255
|
var MARKETPLACE_MANIFEST_PATH = path.join(PLUGIN_MANIFEST_DIR, MARKETPLACE_JSON);
|
|
256
|
+
var MANIFEST_REQUIRED_SEVERITY = {
|
|
257
|
+
author: "warning",
|
|
258
|
+
consumer: "error"
|
|
259
|
+
};
|
|
232
260
|
async function checkCrossReferences(resolvedPath, reader) {
|
|
233
261
|
try {
|
|
234
262
|
await loadSkillsMatrixFromSource({ sourceFlag: resolvedPath, skipExtraSources: true });
|
|
@@ -236,10 +264,17 @@ async function checkCrossReferences(resolvedPath, reader) {
|
|
|
236
264
|
(healthIssue) => toSourceIssue(healthIssue, resolvedPath, reader)
|
|
237
265
|
);
|
|
238
266
|
} catch (error) {
|
|
239
|
-
return [matrixLoadFailure(error)];
|
|
267
|
+
return [matrixLoadFailure(error, reader)];
|
|
240
268
|
}
|
|
241
269
|
}
|
|
242
|
-
function matrixLoadFailure(error) {
|
|
270
|
+
function matrixLoadFailure(error, reader) {
|
|
271
|
+
if (error instanceof MarketplaceManifestRequiredError) {
|
|
272
|
+
return {
|
|
273
|
+
severity: MANIFEST_REQUIRED_SEVERITY[reader],
|
|
274
|
+
file: MARKETPLACE_MANIFEST_PATH,
|
|
275
|
+
message: getErrorMessage(error)
|
|
276
|
+
};
|
|
277
|
+
}
|
|
243
278
|
if (error instanceof MarketplaceNameRefusedError) {
|
|
244
279
|
return {
|
|
245
280
|
severity: "error",
|
|
@@ -326,31 +361,30 @@ async function validateConfigFiles(resolvedPath) {
|
|
|
326
361
|
]);
|
|
327
362
|
return results.flat();
|
|
328
363
|
}
|
|
329
|
-
async function validateYamlFiles(
|
|
364
|
+
async function validateYamlFiles(scan) {
|
|
330
365
|
const issues = [];
|
|
331
|
-
const files = await glob(
|
|
366
|
+
const files = await glob(scan.pattern, scan.baseDir);
|
|
332
367
|
for (const relFile of files) {
|
|
333
|
-
|
|
334
|
-
const displayPath = path.join(opts.relBaseDir, relFile);
|
|
335
|
-
let parsed;
|
|
336
|
-
try {
|
|
337
|
-
parsed = parseYaml(await readFile(absPath));
|
|
338
|
-
} catch (error) {
|
|
339
|
-
issues.push({ severity: "error", file: displayPath, message: yamlParseFailure(error) });
|
|
340
|
-
continue;
|
|
341
|
-
}
|
|
342
|
-
const result = opts.schema.safeParse(parsed);
|
|
343
|
-
if (result.success) continue;
|
|
344
|
-
issues.push(
|
|
345
|
-
...formatZodErrors(result.error).map((message) => ({
|
|
346
|
-
severity: "error",
|
|
347
|
-
file: displayPath,
|
|
348
|
-
message
|
|
349
|
-
}))
|
|
350
|
-
);
|
|
368
|
+
issues.push(...await validateYamlFile(scan, relFile));
|
|
351
369
|
}
|
|
352
370
|
return issues;
|
|
353
371
|
}
|
|
372
|
+
async function validateYamlFile(scan, relFile) {
|
|
373
|
+
const displayPath = path.join(scan.relBaseDir, relFile);
|
|
374
|
+
let parsed;
|
|
375
|
+
try {
|
|
376
|
+
parsed = parseYaml(await readFile(path.join(scan.baseDir, relFile)));
|
|
377
|
+
} catch (error) {
|
|
378
|
+
return [{ severity: "error", file: displayPath, message: yamlParseFailure(error) }];
|
|
379
|
+
}
|
|
380
|
+
const result = scan.schema.safeParse(parsed);
|
|
381
|
+
if (result.success) return [];
|
|
382
|
+
return formatZodErrors(result.error).map((message) => ({
|
|
383
|
+
severity: "error",
|
|
384
|
+
file: displayPath,
|
|
385
|
+
message
|
|
386
|
+
}));
|
|
387
|
+
}
|
|
354
388
|
async function validateTsConfig(resolvedPath, relConfigPath, schema) {
|
|
355
389
|
const absPath = path.join(resolvedPath, relConfigPath);
|
|
356
390
|
if (!await fileExists(absPath)) return [];
|
|
@@ -422,13 +456,13 @@ async function validateProjectConfigFile(projectDir) {
|
|
|
422
456
|
function toUnreadableConfigIssue(failure) {
|
|
423
457
|
return {
|
|
424
458
|
severity: "error",
|
|
425
|
-
file: displayDir(failure.configPath)
|
|
459
|
+
file: `${scopeLabel(failure.scope)}'s ${displayDir(failure.configPath)}`,
|
|
426
460
|
message: `${CONFIG_UNREADABLE}: ${failure.reason}`
|
|
427
461
|
};
|
|
428
462
|
}
|
|
429
463
|
async function countExistingConfigs(projectDir) {
|
|
430
464
|
const present = await Promise.all(
|
|
431
|
-
configDirsInPlay(projectDir).map((dir) => fileExists(
|
|
465
|
+
configDirsInPlay(projectDir).map((dir) => fileExists(getInstalledConfigPath(dir)))
|
|
432
466
|
);
|
|
433
467
|
return present.filter(Boolean).length;
|
|
434
468
|
}
|
|
@@ -465,7 +499,7 @@ async function validateOneSource(source, reader) {
|
|
|
465
499
|
const label = `${source.name} (${source.url})`;
|
|
466
500
|
try {
|
|
467
501
|
const result = await validateSource(source.url, reader);
|
|
468
|
-
return { note: `${label} \u2014 ${result.skillCount}
|
|
502
|
+
return { note: `${label} \u2014 ${plural(result.skillCount, "skill")}`, issues: result.issues };
|
|
469
503
|
} catch (error) {
|
|
470
504
|
return {
|
|
471
505
|
note: `${label} \u2014 failed`,
|
|
@@ -541,7 +575,9 @@ async function validateInstalledSkills(projectDir) {
|
|
|
541
575
|
);
|
|
542
576
|
}
|
|
543
577
|
async function configuredSkillIds(projectDir) {
|
|
544
|
-
const loaded = await Promise.all(
|
|
578
|
+
const loaded = await Promise.all(
|
|
579
|
+
configDirsInPlay(projectDir).map((dir) => loadInstalledConfig(dir))
|
|
580
|
+
);
|
|
545
581
|
return new Set(loaded.flatMap(configuredIdsOf));
|
|
546
582
|
}
|
|
547
583
|
function configuredIdsOf(loaded) {
|
|
@@ -569,10 +605,10 @@ async function listAgentFilesWithOurProvenance(agentsDir) {
|
|
|
569
605
|
const { marked } = await splitAgentsByProvenance(agentsDir);
|
|
570
606
|
return marked.map((name) => `${name}${AGENT_FILE_EXTENSION}`);
|
|
571
607
|
}
|
|
572
|
-
async function listSkillDirsWithOurProvenance(
|
|
573
|
-
const names = await listDirectories(
|
|
608
|
+
async function listSkillDirsWithOurProvenance(skillsDir2) {
|
|
609
|
+
const names = await listDirectories(skillsDir2);
|
|
574
610
|
const judged = await Promise.all(
|
|
575
|
-
names.map(async (name) => ({ name, ours: await carriesOurProvenance(
|
|
611
|
+
names.map(async (name) => ({ name, ours: await carriesOurProvenance(skillsDir2, name) }))
|
|
576
612
|
);
|
|
577
613
|
return judged.filter((entry) => entry.ours).map((entry) => entry.name);
|
|
578
614
|
}
|
|
@@ -585,38 +621,38 @@ async function listDisplayPaths(dir, listEntries) {
|
|
|
585
621
|
const entries = await listEntries(dir);
|
|
586
622
|
return entries.map((entry) => displayDir(path2.join(dir, entry)));
|
|
587
623
|
}
|
|
588
|
-
async function validateSkillsDirectory(
|
|
589
|
-
if (!await directoryExists(
|
|
590
|
-
const skillDirs = await listDirectories(
|
|
624
|
+
async function validateSkillsDirectory(skillsDir2, configuredIds) {
|
|
625
|
+
if (!await directoryExists(skillsDir2)) return NOTHING_VALIDATED;
|
|
626
|
+
const skillDirs = await listDirectories(skillsDir2);
|
|
591
627
|
if (skillDirs.length === 0) return NOTHING_VALIDATED;
|
|
592
628
|
const judged = await Promise.all(
|
|
593
629
|
skillDirs.map(async (name) => ({
|
|
594
630
|
name,
|
|
595
|
-
ours: await isOurSkillDirectory(
|
|
631
|
+
ours: await isOurSkillDirectory(skillsDir2, name, configuredIds)
|
|
596
632
|
}))
|
|
597
633
|
);
|
|
598
634
|
const [ours, foreign] = partition(judged, (entry) => entry.ours);
|
|
599
635
|
const results = await Promise.all(
|
|
600
636
|
ours.map(async ({ name }) => {
|
|
601
|
-
const skillDir = path2.join(
|
|
637
|
+
const skillDir = path2.join(skillsDir2, name);
|
|
602
638
|
return toIssues(displayDir(skillDir), await validateInstalledSkill(skillDir));
|
|
603
639
|
})
|
|
604
640
|
);
|
|
605
641
|
return {
|
|
606
642
|
count: ours.length,
|
|
607
643
|
issues: results.flat(),
|
|
608
|
-
notes: foreign.map(({ name }) => foreignSkillNote(
|
|
644
|
+
notes: foreign.map(({ name }) => foreignSkillNote(skillsDir2, name))
|
|
609
645
|
};
|
|
610
646
|
}
|
|
611
|
-
async function isOurSkillDirectory(
|
|
647
|
+
async function isOurSkillDirectory(skillsDir2, name, configuredIds) {
|
|
612
648
|
if (configuredIds.has(name)) return true;
|
|
613
|
-
return carriesOurProvenance(
|
|
649
|
+
return carriesOurProvenance(skillsDir2, name);
|
|
614
650
|
}
|
|
615
|
-
async function carriesOurProvenance(
|
|
616
|
-
return await readForkedFromMetadata(path2.join(
|
|
651
|
+
async function carriesOurProvenance(skillsDir2, name) {
|
|
652
|
+
return await readForkedFromMetadata(path2.join(skillsDir2, name)) !== null;
|
|
617
653
|
}
|
|
618
|
-
function foreignSkillNote(
|
|
619
|
-
return `${displayDir(path2.join(
|
|
654
|
+
function foreignSkillNote(skillsDir2, name) {
|
|
655
|
+
return `${displayDir(path2.join(skillsDir2, name))} \u2014 not installed by this CLI and named by no configuration here: not validated`;
|
|
620
656
|
}
|
|
621
657
|
async function validateAgentsDirectory(agentsDir) {
|
|
622
658
|
if (!await directoryExists(agentsDir)) return NOTHING_VALIDATED;
|
|
@@ -631,39 +667,32 @@ async function validateAgentsDirectory(agentsDir) {
|
|
|
631
667
|
return { count: agentFiles.length, issues: results.flat(), notes: [] };
|
|
632
668
|
}
|
|
633
669
|
async function validateInstalledSkill(skillDir) {
|
|
634
|
-
const
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
warnings.push(...metadataResult.warnings);
|
|
651
|
-
}
|
|
652
|
-
return { valid: errors.length === 0, errors, warnings };
|
|
670
|
+
const skillMd = await validateSkillFile(
|
|
671
|
+
skillDir,
|
|
672
|
+
STANDARD_FILES.SKILL_MD,
|
|
673
|
+
validateSkillFrontmatter
|
|
674
|
+
);
|
|
675
|
+
const metadata = await validateSkillFile(
|
|
676
|
+
skillDir,
|
|
677
|
+
STANDARD_FILES.METADATA_YAML,
|
|
678
|
+
validateInstalledSkillMetadata
|
|
679
|
+
);
|
|
680
|
+
return mergeValidationResults([skillMd, metadata]);
|
|
681
|
+
}
|
|
682
|
+
async function validateSkillFile(skillDir, fileName, validate) {
|
|
683
|
+
const filePath = path2.join(skillDir, fileName);
|
|
684
|
+
if (!await fileExists(filePath)) return invalidResult(`Missing ${fileName}`);
|
|
685
|
+
return validate(filePath);
|
|
653
686
|
}
|
|
654
687
|
async function validateInstalledSkillMetadata(metadataPath) {
|
|
655
688
|
const read = await readSkillMetadata(metadataPath);
|
|
656
689
|
if (!read.usable) {
|
|
657
|
-
return {
|
|
658
|
-
valid: false,
|
|
659
|
-
errors: [`${STANDARD_FILES.METADATA_YAML}: ${read.reason}`],
|
|
660
|
-
warnings: []
|
|
661
|
-
};
|
|
690
|
+
return invalidResult(`${STANDARD_FILES.METADATA_YAML}: ${read.reason}`);
|
|
662
691
|
}
|
|
663
692
|
const metadata = read.metadata;
|
|
664
693
|
const result = validateSkillMetadata(metadata);
|
|
665
694
|
if (result.success) {
|
|
666
|
-
return
|
|
695
|
+
return validResult();
|
|
667
696
|
}
|
|
668
697
|
const { errors, warnings } = splitMetadataValidationIssues(result.error, metadata);
|
|
669
698
|
return {
|
|
@@ -675,20 +704,29 @@ async function validateInstalledSkillMetadata(metadataPath) {
|
|
|
675
704
|
|
|
676
705
|
// src/cli/commands/doctor.ts
|
|
677
706
|
import { countBy, unique } from "remeda";
|
|
678
|
-
var CONFIG_TS_REL = `${CLAUDE_SRC_DIR}/${STANDARD_FILES.CONFIG_TS}`;
|
|
679
707
|
async function resolveConfigState(detected, projectDir) {
|
|
680
|
-
if (detected?.config)
|
|
681
|
-
|
|
708
|
+
if (detected?.config) {
|
|
709
|
+
return {
|
|
710
|
+
kind: "loaded",
|
|
711
|
+
config: detected.config,
|
|
712
|
+
inherited: inheritsTheGlobalConfig(detected.installation.projectDir, projectDir)
|
|
713
|
+
};
|
|
714
|
+
}
|
|
715
|
+
const loaded = await loadInstalledConfig(projectDir);
|
|
682
716
|
if (!loaded) return { kind: "absent" };
|
|
683
|
-
return declaresNoContent(loaded.config) ? { kind: "declares-nothing", config: loaded.config } : { kind: "loaded", config: loaded.config };
|
|
717
|
+
return declaresNoContent(loaded.config) ? { kind: "declares-nothing", config: loaded.config } : { kind: "loaded", config: loaded.config, inherited: false };
|
|
718
|
+
}
|
|
719
|
+
function inheritsTheGlobalConfig(readFrom, projectDir) {
|
|
720
|
+
return isHomeDirectory(readFrom) && !isHomeDirectory(projectDir);
|
|
684
721
|
}
|
|
685
|
-
function checkConfigValid(state) {
|
|
722
|
+
function checkConfigValid(state, projectDir) {
|
|
723
|
+
const theConfig = (verdict) => configRowMessage(state, projectDir, verdict);
|
|
686
724
|
if (state.kind === "absent") {
|
|
687
725
|
return {
|
|
688
726
|
result: {
|
|
689
727
|
kind: "config",
|
|
690
728
|
status: "fail",
|
|
691
|
-
message:
|
|
729
|
+
message: theConfig("not found"),
|
|
692
730
|
details: [`Run '${CLI_INVOKE_COMMAND} init' to create a configuration`]
|
|
693
731
|
},
|
|
694
732
|
config: null
|
|
@@ -699,7 +737,7 @@ function checkConfigValid(state) {
|
|
|
699
737
|
result: {
|
|
700
738
|
kind: "config-empty",
|
|
701
739
|
status: "warn",
|
|
702
|
-
message:
|
|
740
|
+
message: theConfig("is valid but declares no skills and no agents")
|
|
703
741
|
},
|
|
704
742
|
config: state.config
|
|
705
743
|
};
|
|
@@ -711,7 +749,7 @@ function checkConfigValid(state) {
|
|
|
711
749
|
result: {
|
|
712
750
|
kind: "config",
|
|
713
751
|
status: "fail",
|
|
714
|
-
message:
|
|
752
|
+
message: theConfig("has errors"),
|
|
715
753
|
details: validation.errors
|
|
716
754
|
},
|
|
717
755
|
config: null
|
|
@@ -722,7 +760,7 @@ function checkConfigValid(state) {
|
|
|
722
760
|
result: {
|
|
723
761
|
kind: "config",
|
|
724
762
|
status: "warn",
|
|
725
|
-
message:
|
|
763
|
+
message: theConfig("has warnings"),
|
|
726
764
|
details: validation.warnings
|
|
727
765
|
},
|
|
728
766
|
config
|
|
@@ -732,11 +770,29 @@ function checkConfigValid(state) {
|
|
|
732
770
|
result: {
|
|
733
771
|
kind: "config",
|
|
734
772
|
status: "pass",
|
|
735
|
-
message:
|
|
773
|
+
message: theConfig("is valid")
|
|
736
774
|
},
|
|
737
775
|
config
|
|
738
776
|
};
|
|
739
777
|
}
|
|
778
|
+
function configRowMessage(state, projectDir, verdict) {
|
|
779
|
+
if (state.kind === "loaded" && state.inherited)
|
|
780
|
+
return `${theInheritedConfig()}, which ${verdict}`;
|
|
781
|
+
return `${theOwnConfig(projectDir)} ${verdict}`;
|
|
782
|
+
}
|
|
783
|
+
function theOwnConfig(projectDir) {
|
|
784
|
+
return `${scopeLabel(scopeOf(projectDir))}: ${configNamedFrom(projectDir)}`;
|
|
785
|
+
}
|
|
786
|
+
function theInheritedConfig() {
|
|
787
|
+
return `${scopeLabel("project")}: no ${STANDARD_FILES.CONFIG_TS}, using ${scopeNoun("global")}'s (${configNamedFrom(os2.homedir())})`;
|
|
788
|
+
}
|
|
789
|
+
function scopeOf(dir) {
|
|
790
|
+
return isHomeDirectory(dir) ? "global" : "project";
|
|
791
|
+
}
|
|
792
|
+
function configNamedFrom(root) {
|
|
793
|
+
const relPath = relativeConfigPath(root, providerInUse(root));
|
|
794
|
+
return isHomeDirectory(root) ? `~/${relPath}` : relPath;
|
|
795
|
+
}
|
|
740
796
|
async function checkSkillsResolved(config, matrix2, projectDir) {
|
|
741
797
|
const excludedIds = effectivelyExcludedSkillIds(config.skills);
|
|
742
798
|
const stackIds = config.stack ? getStackSkillIds(config.stack) : [];
|
|
@@ -765,14 +821,14 @@ async function checkSkillsResolved(config, matrix2, projectDir) {
|
|
|
765
821
|
return {
|
|
766
822
|
kind: "skills",
|
|
767
823
|
status: "fail",
|
|
768
|
-
message: `${uniqueSkills.length - missingSkills.length
|
|
824
|
+
message: `${ratio(uniqueSkills.length - missingSkills.length, uniqueSkills.length, "skill")} found`,
|
|
769
825
|
details: missingSkills.map((s) => `- ${s} (not found)`)
|
|
770
826
|
};
|
|
771
827
|
}
|
|
772
828
|
return {
|
|
773
829
|
kind: "skills",
|
|
774
830
|
status: "pass",
|
|
775
|
-
message: `${uniqueSkills.length
|
|
831
|
+
message: `${ratio(uniqueSkills.length, uniqueSkills.length, "skill")} found`
|
|
776
832
|
};
|
|
777
833
|
}
|
|
778
834
|
async function checkAgentsCompiled(config, projectDir) {
|
|
@@ -784,16 +840,28 @@ async function checkAgentsCompiled(config, projectDir) {
|
|
|
784
840
|
message: "No agents configured"
|
|
785
841
|
};
|
|
786
842
|
}
|
|
787
|
-
const
|
|
788
|
-
|
|
843
|
+
const hosts = {
|
|
844
|
+
global: providerInUse(installBaseDir(projectDir, "global")),
|
|
845
|
+
project: providerInUse(installBaseDir(projectDir, "project"))
|
|
846
|
+
};
|
|
847
|
+
const dirs = {
|
|
848
|
+
global: resolveInstallPaths(projectDir, "global").agentsDir,
|
|
849
|
+
project: resolveInstallPaths(projectDir, "project").agentsDir
|
|
850
|
+
};
|
|
789
851
|
const agentChecks = await Promise.all(
|
|
790
|
-
agents.map(
|
|
791
|
-
const agentsDir = agent.scope === "global" ? globalAgentsDir : projectAgentsDir;
|
|
792
|
-
const agentPath = path3.join(agentsDir, `${agent.name}.md`);
|
|
793
|
-
return { name: agent.name, compiled: await fileExists(agentPath) };
|
|
794
|
-
})
|
|
852
|
+
agents.map((agent) => agentCompileCheck(agent, hosts, dirs))
|
|
795
853
|
);
|
|
796
|
-
const
|
|
854
|
+
const unrenderable = agentChecks.filter((c) => c.state === "unrenderable");
|
|
855
|
+
const missingAgents = agentChecks.filter((c) => c.state === "missing").map((c) => c.name);
|
|
856
|
+
if (unrenderable.length === agents.length) {
|
|
857
|
+
const hostsLeavingThemOut = unique(unrenderable.map((a) => a.provider)).join(" or ");
|
|
858
|
+
return {
|
|
859
|
+
kind: "agents",
|
|
860
|
+
status: "pass",
|
|
861
|
+
message: `No sub-agent in this configuration is carried by ${hostsLeavingThemOut}`,
|
|
862
|
+
details: unrenderable.map((a) => `- ${a.name} (left out of ${a.provider})`)
|
|
863
|
+
};
|
|
864
|
+
}
|
|
797
865
|
if (missingAgents.length > 0) {
|
|
798
866
|
return {
|
|
799
867
|
kind: "agents",
|
|
@@ -802,13 +870,24 @@ async function checkAgentsCompiled(config, projectDir) {
|
|
|
802
870
|
details: missingAgents.map((a) => `- ${a} (missing)`)
|
|
803
871
|
};
|
|
804
872
|
}
|
|
873
|
+
const renderable = agents.length - unrenderable.length;
|
|
805
874
|
return {
|
|
806
875
|
kind: "agents",
|
|
807
876
|
status: "pass",
|
|
808
|
-
message: `${
|
|
877
|
+
message: `${ratio(renderable, renderable, "agent")} compiled`
|
|
809
878
|
};
|
|
810
879
|
}
|
|
880
|
+
async function agentCompileCheck(agent, hosts, agentsDirs) {
|
|
881
|
+
const provider = hosts[agent.scope];
|
|
882
|
+
if (!hostCompilesAgent(provider, agent.name)) {
|
|
883
|
+
return { name: agent.name, provider, state: "unrenderable" };
|
|
884
|
+
}
|
|
885
|
+
const file = `${agent.name}${agentCodec(provider).extension}`;
|
|
886
|
+
const compiled = await fileExists(path3.join(agentsDirs[agent.scope], file));
|
|
887
|
+
return { name: agent.name, provider, state: compiled ? "compiled" : "missing" };
|
|
888
|
+
}
|
|
811
889
|
async function checkNoOrphans(config, projectDir) {
|
|
890
|
+
const codec = agentCodec(providerInUse(projectDir));
|
|
812
891
|
const projectAgentsDir = resolveInstallPaths(projectDir, "project").agentsDir;
|
|
813
892
|
const globalAgentsDir = resolveInstallPaths(projectDir, "global").agentsDir;
|
|
814
893
|
const scopesShareAgentsDir = isHomeDirectory(projectDir);
|
|
@@ -823,8 +902,8 @@ async function checkNoOrphans(config, projectDir) {
|
|
|
823
902
|
message: "No agents directory"
|
|
824
903
|
};
|
|
825
904
|
}
|
|
826
|
-
const projectMdFiles = projectExists ? await
|
|
827
|
-
const globalMdFiles = globalExists ? await
|
|
905
|
+
const projectMdFiles = projectExists ? await listAgentFilesOf(projectAgentsDir, codec) : [];
|
|
906
|
+
const globalMdFiles = globalExists ? await listAgentFilesOf(globalAgentsDir, codec) : [];
|
|
828
907
|
const activeProjectAgents = new Set(
|
|
829
908
|
config.agents.filter((a) => a.scope === "project" && !a.excluded).map((a) => a.name)
|
|
830
909
|
);
|
|
@@ -833,8 +912,8 @@ async function checkNoOrphans(config, projectDir) {
|
|
|
833
912
|
);
|
|
834
913
|
const knownProjectDirAgents = scopesShareAgentsDir ? /* @__PURE__ */ new Set([...activeProjectAgents, ...knownGlobalAgents]) : activeProjectAgents;
|
|
835
914
|
const orphanedFiles = [
|
|
836
|
-
...orphanedAgentNames(projectMdFiles, knownProjectDirAgents),
|
|
837
|
-
...orphanedAgentNames(globalMdFiles, knownGlobalAgents)
|
|
915
|
+
...orphanedAgentNames(projectMdFiles, knownProjectDirAgents, codec),
|
|
916
|
+
...orphanedAgentNames(globalMdFiles, knownGlobalAgents, codec)
|
|
838
917
|
];
|
|
839
918
|
if (orphanedFiles.length > 0) {
|
|
840
919
|
return {
|
|
@@ -850,8 +929,8 @@ async function checkNoOrphans(config, projectDir) {
|
|
|
850
929
|
message: "No orphaned agent files"
|
|
851
930
|
};
|
|
852
931
|
}
|
|
853
|
-
function orphanedAgentNames(mdFiles, knownAgents) {
|
|
854
|
-
return mdFiles.map((fileName) =>
|
|
932
|
+
function orphanedAgentNames(mdFiles, knownAgents, codec) {
|
|
933
|
+
return mdFiles.map((fileName) => path3.basename(fileName, codec.extension)).filter((agentName) => !knownAgents.has(agentName));
|
|
855
934
|
}
|
|
856
935
|
async function checkUnownedInstallation(projectDir) {
|
|
857
936
|
const { skills, agents } = await listInstalledArtifacts(projectDir);
|
|
@@ -865,6 +944,9 @@ async function checkUnownedInstallation(projectDir) {
|
|
|
865
944
|
details: [...skills, ...agents].map((artifact) => `- ${artifact}`)
|
|
866
945
|
};
|
|
867
946
|
}
|
|
947
|
+
function ratio(found, total, noun) {
|
|
948
|
+
return `${found}/${plural(total, noun)}`;
|
|
949
|
+
}
|
|
868
950
|
function countedArtifacts(skills, agents) {
|
|
869
951
|
return [
|
|
870
952
|
...skills.length > 0 ? [plural(skills.length, "skill")] : [],
|
|
@@ -882,25 +964,31 @@ async function checkSkillsInstalled(config, projectDir) {
|
|
|
882
964
|
};
|
|
883
965
|
}
|
|
884
966
|
const skillChecks = await Promise.all(
|
|
885
|
-
ejectSkills.map(
|
|
886
|
-
const baseDir = installBaseDir(projectDir, skill.scope);
|
|
887
|
-
const skillMdPath = path3.join(baseDir, LOCAL_SKILLS_PATH, skill.id, STANDARD_FILES.SKILL_MD);
|
|
888
|
-
return { id: skill.id, installed: await fileExists(skillMdPath) };
|
|
889
|
-
})
|
|
967
|
+
ejectSkills.map((skill) => ejectedSkillCheck(skill, projectDir))
|
|
890
968
|
);
|
|
891
|
-
const missingSkills = skillChecks.filter((c) => !c.installed)
|
|
969
|
+
const missingSkills = skillChecks.filter((c) => !c.installed);
|
|
892
970
|
if (missingSkills.length > 0) {
|
|
893
971
|
return {
|
|
894
972
|
kind: "installed",
|
|
895
973
|
status: "warn",
|
|
896
974
|
message: `${plural(missingSkills.length, "skill")} missing from disk`,
|
|
897
|
-
details: missingSkills.map((s) => `- ${s} (not found in ${
|
|
975
|
+
details: missingSkills.map((s) => `- ${s.id} (not found in ${s.lookedIn}/)`)
|
|
898
976
|
};
|
|
899
977
|
}
|
|
900
978
|
return {
|
|
901
979
|
kind: "installed",
|
|
902
980
|
status: "pass",
|
|
903
|
-
message: `${ejectSkills.length
|
|
981
|
+
message: `${ratio(ejectSkills.length, ejectSkills.length, "eject-mode skill")} installed`
|
|
982
|
+
};
|
|
983
|
+
}
|
|
984
|
+
async function ejectedSkillCheck(skill, projectDir) {
|
|
985
|
+
const provider = providerInUse(installBaseDir(projectDir, skill.scope));
|
|
986
|
+
const dir = skillsDir(provider, skill.scope, projectDir);
|
|
987
|
+
const installed = await fileExists(path3.join(dir, skill.id, STANDARD_FILES.SKILL_MD));
|
|
988
|
+
return {
|
|
989
|
+
id: skill.id,
|
|
990
|
+
installed,
|
|
991
|
+
lookedIn: skillsPathPrefix(provider, skill.scope, projectDir)
|
|
904
992
|
};
|
|
905
993
|
}
|
|
906
994
|
async function checkPluginSkillsInstalled(config, projectDir) {
|
|
@@ -935,7 +1023,7 @@ async function checkPluginSkillsInstalled(config, projectDir) {
|
|
|
935
1023
|
return {
|
|
936
1024
|
kind: "plugins",
|
|
937
1025
|
status: "pass",
|
|
938
|
-
message: `${pluginSkills.length
|
|
1026
|
+
message: `${ratio(pluginSkills.length, pluginSkills.length, "plugin-mode skill")} installed`
|
|
939
1027
|
};
|
|
940
1028
|
}
|
|
941
1029
|
var MARKETPLACE_CHOSEN_BY = {
|
|
@@ -950,7 +1038,7 @@ function howItWasReached(source, isLocal) {
|
|
|
950
1038
|
}
|
|
951
1039
|
function reachedMarketplace(result) {
|
|
952
1040
|
const { source, sourceOrigin } = result.sourceConfig;
|
|
953
|
-
const skillCount =
|
|
1041
|
+
const skillCount = result.marketplaceSkillIds.size;
|
|
954
1042
|
const sourceLabel = result.isLocal ? "local" : "remote";
|
|
955
1043
|
return {
|
|
956
1044
|
kind: "source",
|
|
@@ -1069,9 +1157,6 @@ var SKIP_AFTER_CONFIG_ERROR = "Skipped \u2014 fix the content errors above first
|
|
|
1069
1157
|
function skipRestatingContent(nouns) {
|
|
1070
1158
|
return `Skipped \u2014 this row would only restate the ${nouns.join(" and ")} errors above`;
|
|
1071
1159
|
}
|
|
1072
|
-
function plural(count, word) {
|
|
1073
|
-
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
1074
|
-
}
|
|
1075
1160
|
function formatCheckName(name) {
|
|
1076
1161
|
return name.padEnd(CHECK_WIDTH);
|
|
1077
1162
|
}
|
|
@@ -1091,13 +1176,15 @@ function formatStatus(status) {
|
|
|
1091
1176
|
}
|
|
1092
1177
|
}
|
|
1093
1178
|
}
|
|
1179
|
+
var DETAIL_INDENT = `${ROW_INDENT}${" ".repeat(CHECK_WIDTH)} `;
|
|
1094
1180
|
function formatCheckLine(name, result) {
|
|
1095
1181
|
const headerLine = `${ROW_INDENT}${formatCheckName(name)}${formatStatus(result.status)} ${result.message}`;
|
|
1096
|
-
const detailLines = (result.details ?? []).
|
|
1097
|
-
(detail) => `${ROW_INDENT}${" ".repeat(CHECK_WIDTH)} ${detail}`
|
|
1098
|
-
);
|
|
1182
|
+
const detailLines = (result.details ?? []).flatMap(indentedDetailLines);
|
|
1099
1183
|
return [headerLine, ...detailLines];
|
|
1100
1184
|
}
|
|
1185
|
+
function indentedDetailLines(detail) {
|
|
1186
|
+
return detail.split("\n").filter((line) => line !== "").map((line) => `${DETAIL_INDENT}${line}`);
|
|
1187
|
+
}
|
|
1101
1188
|
function formatSummary(results) {
|
|
1102
1189
|
const counts = countBy(results, (r) => r.status);
|
|
1103
1190
|
const parts = [
|
|
@@ -1126,14 +1213,6 @@ var TIPS = [
|
|
|
1126
1213
|
status: "warn",
|
|
1127
1214
|
tip: ` Tip: Nothing is configured yet \u2014 run '${CLI_INVOKE_COMMAND} init' to choose skills and sub-agents`
|
|
1128
1215
|
},
|
|
1129
|
-
{
|
|
1130
|
-
// The one remedy that applies to a config that cannot be read. `init` does not clear such a
|
|
1131
|
-
// file — it refuses it — so the tip above would send the reader in a circle; this is the same
|
|
1132
|
-
// way out `edit` and `init` themselves name, worded the same way.
|
|
1133
|
-
kind: "content-config",
|
|
1134
|
-
status: "fail",
|
|
1135
|
-
tip: ` Tip: There is no automatic repair \u2014 recreate the configuration: '${CLI_INVOKE_COMMAND} uninstall' still works on a config it cannot read, then '${CLI_INVOKE_COMMAND} init'`
|
|
1136
|
-
},
|
|
1137
1216
|
{
|
|
1138
1217
|
// Printed beside the config tip above, which says how to get a configuration back and
|
|
1139
1218
|
// nothing about the files that outlived the old one. Both halves are named with what they
|
|
@@ -1163,6 +1242,41 @@ function formatTips(results) {
|
|
|
1163
1242
|
(t) => t.tip
|
|
1164
1243
|
);
|
|
1165
1244
|
}
|
|
1245
|
+
async function recreateConfigTips(contentResults, projectDir) {
|
|
1246
|
+
if (!failedContentKinds(contentResults).has(CONFIG_CHECK.kind)) return [];
|
|
1247
|
+
const failures = await findConfigLoadFailures(projectDir).catch(() => []);
|
|
1248
|
+
return failures.map(
|
|
1249
|
+
(failure) => ` Tip: There is no automatic repair \u2014 recreate the configuration by running ${recreateConfigFrom(failure.scopeRoot)}`
|
|
1250
|
+
);
|
|
1251
|
+
}
|
|
1252
|
+
var LAYOUT_ROW_NAME = "Layout";
|
|
1253
|
+
function toLayoutResult(scope) {
|
|
1254
|
+
return {
|
|
1255
|
+
kind: "layout",
|
|
1256
|
+
status: "pass",
|
|
1257
|
+
message: layoutIsCurrent(scopeLabel(scope.kind), scope.relName)
|
|
1258
|
+
};
|
|
1259
|
+
}
|
|
1260
|
+
var PLACEMENTS_ROW_NAME = "Placements Offered";
|
|
1261
|
+
async function checkOfferedPlacements(projectDir) {
|
|
1262
|
+
const findings = await everythingThatWouldStopACommandActing(projectDir);
|
|
1263
|
+
if (findings.length === 0) {
|
|
1264
|
+
return { kind: "placements", status: "pass", message: "Every configured skill is placeable" };
|
|
1265
|
+
}
|
|
1266
|
+
return {
|
|
1267
|
+
kind: "placements",
|
|
1268
|
+
status: "fail",
|
|
1269
|
+
message: `${plural(findings.length, "finding")} would stop a command acting here`,
|
|
1270
|
+
details: findings.map((finding) => `- ${finding}`)
|
|
1271
|
+
};
|
|
1272
|
+
}
|
|
1273
|
+
async function everythingThatWouldStopACommandActing(projectDir) {
|
|
1274
|
+
const [unofferable, ambiguous] = await Promise.all([
|
|
1275
|
+
unofferablePlacementsFound(projectDir),
|
|
1276
|
+
ambiguityFinding(projectDir)
|
|
1277
|
+
]);
|
|
1278
|
+
return ambiguous === null ? unofferable : [...unofferable, ambiguous];
|
|
1279
|
+
}
|
|
1166
1280
|
function skippedResult(kind) {
|
|
1167
1281
|
return { kind, status: "skip", message: "Skipped (config invalid)" };
|
|
1168
1282
|
}
|
|
@@ -1202,7 +1316,7 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1202
1316
|
const contentResults = await this.runContentChecks(projectDir);
|
|
1203
1317
|
const operationalResults = await this.runOperationalChecks(projectDir, contentResults);
|
|
1204
1318
|
const results = [...contentResults, ...operationalResults];
|
|
1205
|
-
this.printResults(results);
|
|
1319
|
+
this.printResults(results, await recreateConfigTips(contentResults, projectDir));
|
|
1206
1320
|
if (results.some((r) => r.status === "fail")) {
|
|
1207
1321
|
this.exit(EXIT_CODES.ERROR);
|
|
1208
1322
|
}
|
|
@@ -1265,12 +1379,18 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1265
1379
|
* at all, not merely no usable one.
|
|
1266
1380
|
*/
|
|
1267
1381
|
async isUninstalledSourceRepo(projectDir) {
|
|
1268
|
-
if (await fileExists(
|
|
1382
|
+
if (await fileExists(getInstalledConfigPath(projectDir))) return false;
|
|
1269
1383
|
return isSourceRepo(projectDir);
|
|
1270
1384
|
}
|
|
1271
1385
|
async runAllChecks(projectDir, configState, failedContent) {
|
|
1272
|
-
const { result: configResult, config } = checkConfigValid(configState);
|
|
1386
|
+
const { result: configResult, config } = checkConfigValid(configState, projectDir);
|
|
1273
1387
|
this.logCheck("Config Valid", configResult);
|
|
1388
|
+
const layoutResults = await this.reportLayout(projectDir);
|
|
1389
|
+
const placementsResult = await safeCheck(
|
|
1390
|
+
"placements",
|
|
1391
|
+
() => checkOfferedPlacements(projectDir)
|
|
1392
|
+
);
|
|
1393
|
+
this.logCheck(PLACEMENTS_ROW_NAME, placementsResult);
|
|
1274
1394
|
const sourceResult = await safeCheck("source", () => checkSourceReachable(projectDir));
|
|
1275
1395
|
const filteredConfig = config ? filterExcludedEntries(config) : null;
|
|
1276
1396
|
const skillsResult = await this.resolveSkillsCheck(
|
|
@@ -1291,6 +1411,8 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1291
1411
|
this.logCheck("Marketplace Reachable", sourceResult);
|
|
1292
1412
|
return [
|
|
1293
1413
|
configResult,
|
|
1414
|
+
...layoutResults,
|
|
1415
|
+
placementsResult,
|
|
1294
1416
|
skillsResult,
|
|
1295
1417
|
agentsResult,
|
|
1296
1418
|
orphansResult,
|
|
@@ -1299,6 +1421,22 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1299
1421
|
sourceResult
|
|
1300
1422
|
];
|
|
1301
1423
|
}
|
|
1424
|
+
/**
|
|
1425
|
+
* One `Layout` row per scope in play.
|
|
1426
|
+
*
|
|
1427
|
+
* Per scope rather than per machine: a project and the global installation are two
|
|
1428
|
+
* installations, and one row for the two can only name one of their folders.
|
|
1429
|
+
*
|
|
1430
|
+
* Printed right under `Config Valid`, because every row below it is read out of a config file
|
|
1431
|
+
* in the folder this row names.
|
|
1432
|
+
*/
|
|
1433
|
+
async reportLayout(projectDir) {
|
|
1434
|
+
const rows = (await sourceScopesInPlay(projectDir)).map(toLayoutResult);
|
|
1435
|
+
for (const row of rows) {
|
|
1436
|
+
this.logCheck(LAYOUT_ROW_NAME, row);
|
|
1437
|
+
}
|
|
1438
|
+
return rows;
|
|
1439
|
+
}
|
|
1302
1440
|
/**
|
|
1303
1441
|
* The orphan row, and which question it can answer. With a config it names the compiled
|
|
1304
1442
|
* agents that config does not; with no config at all every installed file is unowned and it
|
|
@@ -1342,10 +1480,10 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1342
1480
|
this.log(line);
|
|
1343
1481
|
}
|
|
1344
1482
|
}
|
|
1345
|
-
printResults(results) {
|
|
1483
|
+
printResults(results, recreateTips) {
|
|
1346
1484
|
this.log("");
|
|
1347
1485
|
this.log(formatSummary(results));
|
|
1348
|
-
const tips = formatTips(results);
|
|
1486
|
+
const tips = [...formatTips(results), ...recreateTips];
|
|
1349
1487
|
if (tips.length > 0) {
|
|
1350
1488
|
this.log("");
|
|
1351
1489
|
for (const tip of tips) {
|