agents-inc 0.164.0 → 0.165.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 +48 -0
- package/README.md +8 -9
- package/dist/{chunk-B6FSLXYN.js → chunk-42TNV2TK.js} +3 -3
- package/dist/chunk-4AAOOAIN.js +49 -0
- package/dist/chunk-4AAOOAIN.js.map +1 -0
- package/dist/{chunk-NQXO4LR3.js → chunk-4C7GL4F7.js} +16 -11
- package/dist/chunk-4C7GL4F7.js.map +1 -0
- package/dist/{chunk-Q4IRRVXK.js → chunk-57KU6SPH.js} +3 -3
- package/dist/{chunk-S63LXWMC.js → chunk-5M3PQNXI.js} +2 -2
- package/dist/{chunk-3WVH2CTK.js → chunk-5P26XBZO.js} +15 -8
- package/dist/chunk-5P26XBZO.js.map +1 -0
- package/dist/{chunk-G2JJ5BO5.js → chunk-6AIZPSEI.js} +337 -140
- package/dist/chunk-6AIZPSEI.js.map +1 -0
- package/dist/{chunk-NKGYKOB7.js → chunk-6CY6PW5X.js} +7 -8
- package/dist/chunk-6CY6PW5X.js.map +1 -0
- package/dist/{chunk-XHPWTL4I.js → chunk-6SW6SB7H.js} +3 -3
- package/dist/chunk-AAWZED5M.js +980 -0
- package/dist/chunk-AAWZED5M.js.map +1 -0
- package/dist/{chunk-UITEKY2R.js → chunk-AQXKAPCD.js} +30 -4
- package/dist/chunk-AQXKAPCD.js.map +1 -0
- package/dist/{chunk-X4SSJZLY.js → chunk-B5UUHBAM.js} +12 -261
- package/dist/chunk-B5UUHBAM.js.map +1 -0
- package/dist/{chunk-ARYNOWKF.js → chunk-BGU25XAY.js} +8 -8
- package/dist/{chunk-QGUP3QJF.js → chunk-CBMAO6OP.js} +22 -18
- package/dist/{chunk-QGUP3QJF.js.map → chunk-CBMAO6OP.js.map} +1 -1
- package/dist/{chunk-YRS3QZP2.js → chunk-CEBDGZID.js} +3 -3
- package/dist/{chunk-ABAZMMG4.js → chunk-CK5RM3DJ.js} +2 -2
- package/dist/{chunk-PHCPUQVR.js → chunk-CTUJO23Z.js} +2 -2
- package/dist/{chunk-QZX7SYKU.js → chunk-E6CHHXGY.js} +2 -2
- package/dist/{chunk-ZVXGW2IC.js → chunk-F2ZWFWTP.js} +4 -6
- package/dist/chunk-F2ZWFWTP.js.map +1 -0
- package/dist/{chunk-AVK2HEKB.js → chunk-GKPW33FP.js} +2 -6
- package/dist/{chunk-AVK2HEKB.js.map → chunk-GKPW33FP.js.map} +1 -1
- package/dist/{chunk-ECURHW3T.js → chunk-HR4YDSJK.js} +13 -12
- package/dist/chunk-HR4YDSJK.js.map +1 -0
- package/dist/{chunk-XPDVAJDH.js → chunk-IM45LLQX.js} +3599 -1558
- package/dist/chunk-IM45LLQX.js.map +1 -0
- package/dist/chunk-IVNKU4DM.js +39 -0
- package/dist/chunk-IVNKU4DM.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-RGYDFBCT.js → chunk-JV4TKXEN.js} +5 -5
- package/dist/{chunk-4VL3CEOS.js → chunk-LXXGZAD3.js} +3 -3
- package/dist/chunk-MBUGQI2M.js +73 -0
- package/dist/chunk-MBUGQI2M.js.map +1 -0
- package/dist/{chunk-URXIQE7I.js → chunk-MWDY76MT.js} +2 -2
- package/dist/{chunk-V5GLL2JB.js → chunk-P3KJCYSR.js} +7 -7
- package/dist/{chunk-6MV4JOCL.js → chunk-PSX7CT6V.js} +2 -2
- package/dist/{chunk-B3Y3QMWI.js → chunk-TRTVOVHR.js} +4 -4
- package/dist/{chunk-AOTCIUGX.js → chunk-TV5Y53R2.js} +2 -2
- package/dist/{chunk-K5ITEA3I.js → chunk-UBSSIIWD.js} +34 -17
- package/dist/chunk-UBSSIIWD.js.map +1 -0
- package/dist/chunk-UCORXS4D.js +250 -0
- package/dist/chunk-UCORXS4D.js.map +1 -0
- package/dist/chunk-UR3RRTPO.js +159 -0
- package/dist/chunk-UR3RRTPO.js.map +1 -0
- package/dist/chunk-V5CWACR5.js +375 -0
- package/dist/chunk-V5CWACR5.js.map +1 -0
- package/dist/{chunk-LT5GXEB2.js → chunk-VPJUCVU7.js} +8 -7
- package/dist/chunk-VPJUCVU7.js.map +1 -0
- package/dist/chunk-VRXX4D6K.js +31 -0
- package/dist/chunk-VRXX4D6K.js.map +1 -0
- package/dist/{chunk-2CQNPLVF.js → chunk-WIFIAQZU.js} +63 -42
- package/dist/chunk-WIFIAQZU.js.map +1 -0
- package/dist/chunk-WSGX4IWC.js +356 -0
- package/dist/chunk-WSGX4IWC.js.map +1 -0
- package/dist/{chunk-X37UYIE3.js → chunk-WTANOSXK.js} +436 -739
- package/dist/chunk-WTANOSXK.js.map +1 -0
- package/dist/{chunk-XATLUKXK.js → chunk-X2KUI7V3.js} +6 -6
- package/dist/{chunk-NTF4UXGK.js → chunk-XATIKM3P.js} +73 -30
- package/dist/chunk-XATIKM3P.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-DSL66I4N.js → chunk-Y5SNGMB7.js} +6 -6
- package/dist/{chunk-7REKOTKG.js → chunk-YNOOZFIB.js} +3 -3
- 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 +106 -27
- package/dist/commands/compile.js.map +1 -1
- package/dist/commands/doctor.js +427 -192
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/edit.js +504 -314
- package/dist/commands/edit.js.map +1 -1
- package/dist/commands/eject.js +41 -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 +103 -15
- package/dist/commands/share.js.map +1 -1
- package/dist/commands/uninstall.js +264 -106
- package/dist/commands/uninstall.js.map +1 -1
- package/dist/commands/update.js +50 -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 +59 -43
- package/dist/hooks/init.js.map +1 -1
- package/dist/load-agent-defs-JHLE6MAE.js +14 -0
- package/dist/recompile-project-agents-ZPP5NOLZ.js +18 -0
- package/dist/{source-loader-GRBVNRAW.js → source-loader-RWZAYJ7E.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-B6FSLXYN.js.map → chunk-42TNV2TK.js.map} +0 -0
- /package/dist/{chunk-Q4IRRVXK.js.map → chunk-57KU6SPH.js.map} +0 -0
- /package/dist/{chunk-S63LXWMC.js.map → chunk-5M3PQNXI.js.map} +0 -0
- /package/dist/{chunk-XHPWTL4I.js.map → chunk-6SW6SB7H.js.map} +0 -0
- /package/dist/{chunk-ARYNOWKF.js.map → chunk-BGU25XAY.js.map} +0 -0
- /package/dist/{chunk-YRS3QZP2.js.map → chunk-CEBDGZID.js.map} +0 -0
- /package/dist/{chunk-ABAZMMG4.js.map → chunk-CK5RM3DJ.js.map} +0 -0
- /package/dist/{chunk-PHCPUQVR.js.map → chunk-CTUJO23Z.js.map} +0 -0
- /package/dist/{chunk-QZX7SYKU.js.map → chunk-E6CHHXGY.js.map} +0 -0
- /package/dist/{chunk-RGYDFBCT.js.map → chunk-JV4TKXEN.js.map} +0 -0
- /package/dist/{chunk-4VL3CEOS.js.map → chunk-LXXGZAD3.js.map} +0 -0
- /package/dist/{chunk-URXIQE7I.js.map → chunk-MWDY76MT.js.map} +0 -0
- /package/dist/{chunk-V5GLL2JB.js.map → chunk-P3KJCYSR.js.map} +0 -0
- /package/dist/{chunk-6MV4JOCL.js.map → chunk-PSX7CT6V.js.map} +0 -0
- /package/dist/{chunk-B3Y3QMWI.js.map → chunk-TRTVOVHR.js.map} +0 -0
- /package/dist/{chunk-AOTCIUGX.js.map → chunk-TV5Y53R2.js.map} +0 -0
- /package/dist/{chunk-XATLUKXK.js.map → chunk-X2KUI7V3.js.map} +0 -0
- /package/dist/{chunk-DSL66I4N.js.map → chunk-Y5SNGMB7.js.map} +0 -0
- /package/dist/{chunk-7REKOTKG.js.map → chunk-YNOOZFIB.js.map} +0 -0
- /package/dist/{load-agent-defs-7IM3GBEQ.js.map → load-agent-defs-JHLE6MAE.js.map} +0 -0
- /package/dist/{recompile-project-agents-VR4NLGOU.js.map → recompile-project-agents-ZPP5NOLZ.js.map} +0 -0
- /package/dist/{source-loader-GRBVNRAW.js.map → source-loader-RWZAYJ7E.js.map} +0 -0
package/dist/commands/doctor.js
CHANGED
|
@@ -1,105 +1,189 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import {
|
|
3
|
+
unofferablePlacementsFound
|
|
4
|
+
} from "../chunk-IVNKU4DM.js";
|
|
2
5
|
import {
|
|
3
6
|
detectProject,
|
|
4
7
|
loadSource
|
|
5
|
-
} from "../chunk-
|
|
6
|
-
import "../chunk-
|
|
7
|
-
import "../chunk-
|
|
8
|
+
} from "../chunk-WTANOSXK.js";
|
|
9
|
+
import "../chunk-WIFIAQZU.js";
|
|
10
|
+
import "../chunk-42TNV2TK.js";
|
|
11
|
+
import "../chunk-UR3RRTPO.js";
|
|
8
12
|
import {
|
|
9
13
|
filterExcludedEntries,
|
|
14
|
+
listAgentFilesOf,
|
|
10
15
|
listAgentMdFiles,
|
|
11
16
|
splitAgentsByProvenance
|
|
12
|
-
} from "../chunk-
|
|
17
|
+
} from "../chunk-V5CWACR5.js";
|
|
18
|
+
import {
|
|
19
|
+
ambiguityFinding
|
|
20
|
+
} from "../chunk-MBUGQI2M.js";
|
|
13
21
|
import {
|
|
14
22
|
BaseCommand,
|
|
15
23
|
EXIT_CODES
|
|
16
|
-
} from "../chunk-
|
|
17
|
-
import "../chunk-
|
|
18
|
-
import "../chunk-
|
|
24
|
+
} from "../chunk-AAWZED5M.js";
|
|
25
|
+
import "../chunk-6CY6PW5X.js";
|
|
26
|
+
import "../chunk-CTUJO23Z.js";
|
|
19
27
|
import {
|
|
20
28
|
ConfigDefaultExportError,
|
|
29
|
+
MarketplaceManifestRequiredError,
|
|
21
30
|
MarketplaceNameRefusedError,
|
|
31
|
+
REGISTRY_IS_NOT_AN_INVENTORY,
|
|
22
32
|
SOURCE_ENV_VAR,
|
|
33
|
+
agentCodec,
|
|
23
34
|
agentYamlGenerationSchema,
|
|
35
|
+
bothSourceFoldersPresent,
|
|
24
36
|
checkMatrixHealth,
|
|
37
|
+
compiledAgentNamesTheOtherFolder,
|
|
25
38
|
configDirsInPlay,
|
|
26
39
|
declaresNoContent,
|
|
27
40
|
directoryExists,
|
|
28
41
|
discoverLocalSkills,
|
|
29
42
|
fileExists,
|
|
30
43
|
findConfigLoadFailures,
|
|
44
|
+
folderBeingRead,
|
|
31
45
|
formatZodErrors,
|
|
32
46
|
getErrorMessage,
|
|
47
|
+
getInstalledConfigPath,
|
|
33
48
|
getInstalledPluginsRegistryPath,
|
|
34
|
-
getProjectConfigPath,
|
|
35
49
|
getProjectPluginsDir,
|
|
36
50
|
getStackSkillIds,
|
|
37
51
|
getUserPluginsDir,
|
|
38
52
|
getVerifiedPluginInstallPaths,
|
|
39
53
|
glob,
|
|
54
|
+
hostCompilesAgent,
|
|
40
55
|
installBaseDir,
|
|
56
|
+
invalidResult,
|
|
41
57
|
isHomeDirectory,
|
|
42
58
|
isLocalSource,
|
|
43
59
|
isRecord,
|
|
60
|
+
layoutIsCurrent,
|
|
61
|
+
layoutIsLegacy,
|
|
44
62
|
listDirectories,
|
|
45
63
|
listRegisteredPluginInstalls,
|
|
46
64
|
loadConfig,
|
|
47
|
-
|
|
48
|
-
loadProjectSourceConfig,
|
|
65
|
+
loadInstalledConfig,
|
|
49
66
|
loadSkillsMatrixFromSource,
|
|
67
|
+
loadSourceRepoConfig,
|
|
68
|
+
mergeValidationResults,
|
|
50
69
|
metadataValidationSchema,
|
|
51
70
|
parseFrontmatter,
|
|
52
71
|
parseMarketplacePluginRef,
|
|
72
|
+
plural,
|
|
73
|
+
providerInUse,
|
|
53
74
|
readFile,
|
|
54
75
|
readForkedFromMetadata,
|
|
55
76
|
readSkillMetadata,
|
|
77
|
+
recreateConfigFrom,
|
|
78
|
+
relativeConfigPath,
|
|
56
79
|
resolveInstallPaths,
|
|
57
80
|
resolvePrimarySourceEntry,
|
|
81
|
+
resolveSourceDir,
|
|
82
|
+
scopeLabel,
|
|
83
|
+
scopeNoun,
|
|
58
84
|
skillCategoriesFileSchema,
|
|
59
85
|
skillRulesFileSchema,
|
|
86
|
+
skillsDir,
|
|
87
|
+
skillsPathPrefix,
|
|
88
|
+
sourceScopesInPlay,
|
|
60
89
|
splitMetadataValidationIssues,
|
|
61
90
|
stackConfigValidationSchema,
|
|
62
91
|
stacksConfigSchema,
|
|
92
|
+
unusedSourceFolderName,
|
|
93
|
+
validResult,
|
|
63
94
|
validateAgentFrontmatter,
|
|
64
95
|
validateAllPlugins,
|
|
65
96
|
validatePlugin,
|
|
66
97
|
validateProjectConfig,
|
|
67
98
|
validateSkillFrontmatter,
|
|
68
99
|
validateSkillMetadata
|
|
69
|
-
} from "../chunk-
|
|
100
|
+
} from "../chunk-IM45LLQX.js";
|
|
70
101
|
import {
|
|
71
102
|
matrix
|
|
72
|
-
} from "../chunk-
|
|
103
|
+
} from "../chunk-GKPW33FP.js";
|
|
73
104
|
import {
|
|
74
|
-
|
|
105
|
+
CLAUDE_DIR,
|
|
75
106
|
CLI_INVOKE_COMMAND,
|
|
76
107
|
DEFAULT_BRANDING,
|
|
77
108
|
DIRS,
|
|
78
109
|
EJECT_SOURCE,
|
|
79
|
-
LOCAL_SKILLS_PATH,
|
|
80
110
|
MARKETPLACE_JSON,
|
|
81
111
|
PLUGIN_MANIFEST_DIR,
|
|
82
112
|
SKILLS_DIR_PATH,
|
|
83
113
|
SKILL_CATEGORIES_PATH,
|
|
84
114
|
SKILL_RULES_PATH,
|
|
85
115
|
STACKS_FILE_PATH,
|
|
116
|
+
STANDARD_DIRS,
|
|
86
117
|
STANDARD_FILES,
|
|
87
118
|
UI_SYMBOLS,
|
|
88
119
|
effectivelyExcludedSkillIds
|
|
89
|
-
} from "../chunk-
|
|
120
|
+
} from "../chunk-AQXKAPCD.js";
|
|
90
121
|
import "../chunk-OI2H6M5I.js";
|
|
91
122
|
|
|
92
123
|
// src/cli/commands/doctor.ts
|
|
93
124
|
import os2 from "os";
|
|
94
|
-
import
|
|
125
|
+
import path4 from "path";
|
|
95
126
|
|
|
96
|
-
// src/cli/lib/
|
|
127
|
+
// src/cli/lib/installation/layout-findings.ts
|
|
97
128
|
import path from "path";
|
|
129
|
+
async function layoutFindingFor(scope) {
|
|
130
|
+
const { move } = scope;
|
|
131
|
+
const label = scopeLabel(scope.kind);
|
|
132
|
+
if (move === null) {
|
|
133
|
+
return { status: "pass", message: layoutIsCurrent(label, scope.relName), details: [] };
|
|
134
|
+
}
|
|
135
|
+
const whichLayout = layoutStatement(scope, move, label);
|
|
136
|
+
const faults = await faultsFor(scope, move);
|
|
137
|
+
const [worst, ...rest] = faults;
|
|
138
|
+
if (worst === void 0) return { ...whichLayout, details: [] };
|
|
139
|
+
return {
|
|
140
|
+
status: "fail",
|
|
141
|
+
message: `${label}: ${worst}`,
|
|
142
|
+
details: [...rest.map((fault) => `${label}: ${fault}`), whichLayout.message]
|
|
143
|
+
};
|
|
144
|
+
}
|
|
145
|
+
function layoutStatement(scope, move, label) {
|
|
146
|
+
if (!scope.legacy) return { status: "pass", message: layoutIsCurrent(label, move.to.relName) };
|
|
147
|
+
return { status: "warn", message: layoutIsLegacy(label, move.from.relName, move.to.relName) };
|
|
148
|
+
}
|
|
149
|
+
async function faultsFor(scope, move) {
|
|
150
|
+
const staleAgents = await compiledAgentsNamingTheOtherFolder(scope);
|
|
151
|
+
return [...rivalFolderFault(scope, move), ...staleAgents];
|
|
152
|
+
}
|
|
153
|
+
function rivalFolderFault(scope, move) {
|
|
154
|
+
if (!scope.both) return [];
|
|
155
|
+
return [
|
|
156
|
+
bothSourceFoldersPresent(move.from.relName, move.to.relName, folderBeingRead(scope, move))
|
|
157
|
+
];
|
|
158
|
+
}
|
|
159
|
+
async function compiledAgentsNamingTheOtherFolder(scope) {
|
|
160
|
+
const unused = unusedSourceFolderName(scope.root, scope.provider);
|
|
161
|
+
if (unused === null) return [];
|
|
162
|
+
const agentsDir = path.join(scope.root, CLAUDE_DIR, STANDARD_DIRS.AGENTS);
|
|
163
|
+
const files = await listAgentMdFiles(agentsDir);
|
|
164
|
+
const named = await Promise.all(
|
|
165
|
+
files.map(async (file) => ({
|
|
166
|
+
file,
|
|
167
|
+
names: await namesFolder(path.join(agentsDir, file), unused)
|
|
168
|
+
}))
|
|
169
|
+
);
|
|
170
|
+
return named.filter((entry) => entry.names).map((entry) => compiledAgentNamesTheOtherFolder(entry.file, unused));
|
|
171
|
+
}
|
|
172
|
+
async function namesFolder(agentPath, folder) {
|
|
173
|
+
try {
|
|
174
|
+
return (await readFile(agentPath)).includes(folder);
|
|
175
|
+
} catch {
|
|
176
|
+
return false;
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// src/cli/lib/source-validator.ts
|
|
181
|
+
import path2 from "path";
|
|
98
182
|
import { parse as parseYaml } from "yaml";
|
|
99
183
|
import "zod";
|
|
100
184
|
async function isSourceRepo(dir) {
|
|
101
|
-
const sourceConfig = await
|
|
102
|
-
return directoryExists(
|
|
185
|
+
const sourceConfig = await loadSourceRepoConfig(dir);
|
|
186
|
+
return directoryExists(path2.join(dir, sourceConfig?.skillsDir ?? SKILLS_DIR_PATH));
|
|
103
187
|
}
|
|
104
188
|
function isSnakeCase(key) {
|
|
105
189
|
return /[a-z]_[a-z]/.test(key);
|
|
@@ -122,11 +206,11 @@ function checkDirNameMatchesSkillId(skillId, relPath, dirName) {
|
|
|
122
206
|
}
|
|
123
207
|
];
|
|
124
208
|
}
|
|
125
|
-
async function checkSkillDirName(
|
|
126
|
-
const dirName =
|
|
127
|
-
const skillMdRelPath =
|
|
209
|
+
async function checkSkillDirName(skillsDir2, skillsDirRelPath, skillDir) {
|
|
210
|
+
const dirName = path2.basename(skillDir);
|
|
211
|
+
const skillMdRelPath = path2.join(skillsDirRelPath, skillDir, STANDARD_FILES.SKILL_MD);
|
|
128
212
|
try {
|
|
129
|
-
const content = await readFile(
|
|
213
|
+
const content = await readFile(path2.join(skillsDir2, skillDir, STANDARD_FILES.SKILL_MD));
|
|
130
214
|
const frontmatter = parseFrontmatter(content, skillMdRelPath);
|
|
131
215
|
if (frontmatter === null) {
|
|
132
216
|
return [
|
|
@@ -148,67 +232,69 @@ async function checkSkillDirName(skillsDir, skillsDirRelPath, skillDir) {
|
|
|
148
232
|
];
|
|
149
233
|
}
|
|
150
234
|
}
|
|
151
|
-
function validateSkillFilePairs(skillMdDirs, metadataDirs,
|
|
235
|
+
function validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2) {
|
|
152
236
|
const missingMetadata = [...skillMdDirs].filter((dir) => !metadataDirs.has(dir)).map((dir) => ({
|
|
153
237
|
severity: "error",
|
|
154
|
-
file:
|
|
238
|
+
file: path2.join(skillsDir2, dir),
|
|
155
239
|
message: `Missing ${STANDARD_FILES.METADATA_YAML} \u2014 skill directory has ${STANDARD_FILES.SKILL_MD} but no metadata`
|
|
156
240
|
}));
|
|
157
241
|
const missingSkillMd = [...metadataDirs].filter((dir) => !skillMdDirs.has(dir)).map((dir) => ({
|
|
158
242
|
severity: "error",
|
|
159
|
-
file:
|
|
243
|
+
file: path2.join(skillsDir2, dir),
|
|
160
244
|
message: `Missing ${STANDARD_FILES.SKILL_MD} \u2014 skill directory has ${STANDARD_FILES.METADATA_YAML} but no SKILL.md`
|
|
161
245
|
}));
|
|
162
246
|
return [...missingMetadata, ...missingSkillMd];
|
|
163
247
|
}
|
|
164
248
|
async function validateSource(sourcePath, reader = "author") {
|
|
165
|
-
const
|
|
166
|
-
const resolvedPath = path.isAbsolute(sourcePath) ? sourcePath : path.resolve(sourcePath);
|
|
249
|
+
const resolvedPath = path2.isAbsolute(sourcePath) ? sourcePath : path2.resolve(sourcePath);
|
|
167
250
|
if (!await directoryExists(resolvedPath)) {
|
|
168
|
-
|
|
169
|
-
severity: "error",
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
});
|
|
173
|
-
return buildResult(issues, 0);
|
|
251
|
+
return buildResult(
|
|
252
|
+
[{ severity: "error", file: resolvedPath, message: "Marketplace directory does not exist" }],
|
|
253
|
+
0
|
|
254
|
+
);
|
|
174
255
|
}
|
|
175
|
-
const sourceProjectConfig = await
|
|
256
|
+
const sourceProjectConfig = await loadSourceRepoConfig(resolvedPath);
|
|
176
257
|
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);
|
|
185
|
-
}
|
|
186
|
-
const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir);
|
|
187
|
-
const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir);
|
|
188
|
-
const skillMdDirs = new Set(skillMdFiles.map((f) => path.dirname(f)));
|
|
189
|
-
const metadataDirs = new Set(metadataFiles.map((f) => path.dirname(f)));
|
|
190
|
-
issues.push(...validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir));
|
|
191
|
-
const validMetadataFiles = metadataFiles.filter((f) => skillMdDirs.has(path.dirname(f)));
|
|
192
|
-
for (const metadataFile of validMetadataFiles) {
|
|
193
|
-
issues.push(...await validateOneSkill(skillsDir, skillsDirRelPath, metadataFile));
|
|
258
|
+
const skillsDir2 = path2.join(resolvedPath, skillsDirRelPath);
|
|
259
|
+
if (!await directoryExists(skillsDir2)) {
|
|
260
|
+
return buildResult(
|
|
261
|
+
[{ severity: "error", file: skillsDir2, message: "Skills directory does not exist" }],
|
|
262
|
+
0
|
|
263
|
+
);
|
|
194
264
|
}
|
|
195
|
-
|
|
265
|
+
const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir2);
|
|
266
|
+
const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir2);
|
|
267
|
+
const skillMdDirs = new Set(skillMdFiles.map((f) => path2.dirname(f)));
|
|
268
|
+
const metadataDirs = new Set(metadataFiles.map((f) => path2.dirname(f)));
|
|
269
|
+
const pairIssues = validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2);
|
|
270
|
+
const validMetadataFiles = metadataFiles.filter((f) => skillMdDirs.has(path2.dirname(f)));
|
|
271
|
+
const skillIssues = await validateSkillsInTurn(skillsDir2, skillsDirRelPath, validMetadataFiles);
|
|
272
|
+
const crossReferenceIssues = await checkCrossReferences(resolvedPath, reader);
|
|
196
273
|
const extraIssues = await Promise.all([
|
|
197
274
|
validateStacks(resolvedPath),
|
|
198
275
|
validateAgents(resolvedPath),
|
|
199
276
|
validateConfigFiles(resolvedPath)
|
|
200
277
|
]);
|
|
201
|
-
|
|
202
|
-
|
|
278
|
+
return buildResult(
|
|
279
|
+
[...pairIssues, ...skillIssues, ...crossReferenceIssues, ...extraIssues.flat()],
|
|
280
|
+
validMetadataFiles.length
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
async function validateSkillsInTurn(skillsDir2, skillsDirRelPath, metadataFiles) {
|
|
284
|
+
const issues = [];
|
|
285
|
+
for (const metadataFile of metadataFiles) {
|
|
286
|
+
issues.push(...await validateOneSkill(skillsDir2, skillsDirRelPath, metadataFile));
|
|
287
|
+
}
|
|
288
|
+
return issues;
|
|
203
289
|
}
|
|
204
290
|
function yamlParseFailure(error) {
|
|
205
291
|
return `Failed to parse YAML: ${getErrorMessage(error)}`;
|
|
206
292
|
}
|
|
207
|
-
async function validateOneSkill(
|
|
208
|
-
const relPath =
|
|
293
|
+
async function validateOneSkill(skillsDir2, skillsDirRelPath, metadataFile) {
|
|
294
|
+
const relPath = path2.join(skillsDirRelPath, metadataFile);
|
|
209
295
|
let rawMetadata;
|
|
210
296
|
try {
|
|
211
|
-
rawMetadata = parseYaml(await readFile(
|
|
297
|
+
rawMetadata = parseYaml(await readFile(path2.join(skillsDir2, metadataFile)));
|
|
212
298
|
} catch (error) {
|
|
213
299
|
return [{ severity: "error", file: relPath, message: yamlParseFailure(error) }];
|
|
214
300
|
}
|
|
@@ -216,7 +302,7 @@ async function validateOneSkill(skillsDir, skillsDirRelPath, metadataFile) {
|
|
|
216
302
|
...checkSnakeCaseKeys(rawMetadata, relPath),
|
|
217
303
|
...checkMetadataSchema(rawMetadata, relPath),
|
|
218
304
|
// Independent of metadata validity — the id lives in SKILL.md frontmatter
|
|
219
|
-
...await checkSkillDirName(
|
|
305
|
+
...await checkSkillDirName(skillsDir2, skillsDirRelPath, path2.dirname(metadataFile))
|
|
220
306
|
];
|
|
221
307
|
}
|
|
222
308
|
function checkMetadataSchema(rawMetadata, relPath) {
|
|
@@ -228,7 +314,11 @@ function checkMetadataSchema(rawMetadata, relPath) {
|
|
|
228
314
|
...warnings.map((message) => ({ severity: "warning", file: relPath, message }))
|
|
229
315
|
];
|
|
230
316
|
}
|
|
231
|
-
var MARKETPLACE_MANIFEST_PATH =
|
|
317
|
+
var MARKETPLACE_MANIFEST_PATH = path2.join(PLUGIN_MANIFEST_DIR, MARKETPLACE_JSON);
|
|
318
|
+
var MANIFEST_REQUIRED_SEVERITY = {
|
|
319
|
+
author: "warning",
|
|
320
|
+
consumer: "error"
|
|
321
|
+
};
|
|
232
322
|
async function checkCrossReferences(resolvedPath, reader) {
|
|
233
323
|
try {
|
|
234
324
|
await loadSkillsMatrixFromSource({ sourceFlag: resolvedPath, skipExtraSources: true });
|
|
@@ -236,10 +326,17 @@ async function checkCrossReferences(resolvedPath, reader) {
|
|
|
236
326
|
(healthIssue) => toSourceIssue(healthIssue, resolvedPath, reader)
|
|
237
327
|
);
|
|
238
328
|
} catch (error) {
|
|
239
|
-
return [matrixLoadFailure(error)];
|
|
329
|
+
return [matrixLoadFailure(error, reader)];
|
|
240
330
|
}
|
|
241
331
|
}
|
|
242
|
-
function matrixLoadFailure(error) {
|
|
332
|
+
function matrixLoadFailure(error, reader) {
|
|
333
|
+
if (error instanceof MarketplaceManifestRequiredError) {
|
|
334
|
+
return {
|
|
335
|
+
severity: MANIFEST_REQUIRED_SEVERITY[reader],
|
|
336
|
+
file: MARKETPLACE_MANIFEST_PATH,
|
|
337
|
+
message: getErrorMessage(error)
|
|
338
|
+
};
|
|
339
|
+
}
|
|
243
340
|
if (error instanceof MarketplaceNameRefusedError) {
|
|
244
341
|
return {
|
|
245
342
|
severity: "error",
|
|
@@ -290,7 +387,7 @@ function consumedMarketplaceMessage(marketplacePath, details) {
|
|
|
290
387
|
return `Marketplace '${marketplacePath}': ${details}. Nothing to fix here \u2014 the rule is that marketplace's, not this project's`;
|
|
291
388
|
}
|
|
292
389
|
async function validateStacks(resolvedPath) {
|
|
293
|
-
const stacksDir =
|
|
390
|
+
const stacksDir = path2.join(resolvedPath, DIRS.stacks);
|
|
294
391
|
if (!await directoryExists(stacksDir)) return [];
|
|
295
392
|
const [skillMetaIssues, configIssues] = await Promise.all([
|
|
296
393
|
validateYamlFiles({
|
|
@@ -309,7 +406,7 @@ async function validateStacks(resolvedPath) {
|
|
|
309
406
|
return [...skillMetaIssues, ...configIssues];
|
|
310
407
|
}
|
|
311
408
|
async function validateAgents(resolvedPath) {
|
|
312
|
-
const agentsDir =
|
|
409
|
+
const agentsDir = path2.join(resolvedPath, DIRS.agents);
|
|
313
410
|
if (!await directoryExists(agentsDir)) return [];
|
|
314
411
|
return validateYamlFiles({
|
|
315
412
|
baseDir: agentsDir,
|
|
@@ -326,33 +423,32 @@ async function validateConfigFiles(resolvedPath) {
|
|
|
326
423
|
]);
|
|
327
424
|
return results.flat();
|
|
328
425
|
}
|
|
329
|
-
async function validateYamlFiles(
|
|
426
|
+
async function validateYamlFiles(scan) {
|
|
330
427
|
const issues = [];
|
|
331
|
-
const files = await glob(
|
|
428
|
+
const files = await glob(scan.pattern, scan.baseDir);
|
|
332
429
|
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
|
-
);
|
|
430
|
+
issues.push(...await validateYamlFile(scan, relFile));
|
|
351
431
|
}
|
|
352
432
|
return issues;
|
|
353
433
|
}
|
|
434
|
+
async function validateYamlFile(scan, relFile) {
|
|
435
|
+
const displayPath = path2.join(scan.relBaseDir, relFile);
|
|
436
|
+
let parsed;
|
|
437
|
+
try {
|
|
438
|
+
parsed = parseYaml(await readFile(path2.join(scan.baseDir, relFile)));
|
|
439
|
+
} catch (error) {
|
|
440
|
+
return [{ severity: "error", file: displayPath, message: yamlParseFailure(error) }];
|
|
441
|
+
}
|
|
442
|
+
const result = scan.schema.safeParse(parsed);
|
|
443
|
+
if (result.success) return [];
|
|
444
|
+
return formatZodErrors(result.error).map((message) => ({
|
|
445
|
+
severity: "error",
|
|
446
|
+
file: displayPath,
|
|
447
|
+
message
|
|
448
|
+
}));
|
|
449
|
+
}
|
|
354
450
|
async function validateTsConfig(resolvedPath, relConfigPath, schema) {
|
|
355
|
-
const absPath =
|
|
451
|
+
const absPath = path2.join(resolvedPath, relConfigPath);
|
|
356
452
|
if (!await fileExists(absPath)) return [];
|
|
357
453
|
try {
|
|
358
454
|
const loaded = await loadConfig(absPath, schema);
|
|
@@ -382,7 +478,7 @@ function buildResult(issues, skillCount) {
|
|
|
382
478
|
|
|
383
479
|
// src/cli/lib/content-validator.ts
|
|
384
480
|
import os from "os";
|
|
385
|
-
import
|
|
481
|
+
import path3 from "path";
|
|
386
482
|
import { partition } from "remeda";
|
|
387
483
|
var CWD_SOURCE_NAME = "current directory";
|
|
388
484
|
var AGENT_FILE_EXTENSION = ".md";
|
|
@@ -400,8 +496,8 @@ function mergeValidations(parts) {
|
|
|
400
496
|
function displayDir(absolutePath) {
|
|
401
497
|
const home = os.homedir();
|
|
402
498
|
if (absolutePath === home) return "~";
|
|
403
|
-
if (absolutePath.startsWith(home +
|
|
404
|
-
return `~${
|
|
499
|
+
if (absolutePath.startsWith(home + path3.sep)) {
|
|
500
|
+
return `~${path3.sep}${path3.relative(home, absolutePath)}`;
|
|
405
501
|
}
|
|
406
502
|
return absolutePath;
|
|
407
503
|
}
|
|
@@ -422,13 +518,13 @@ async function validateProjectConfigFile(projectDir) {
|
|
|
422
518
|
function toUnreadableConfigIssue(failure) {
|
|
423
519
|
return {
|
|
424
520
|
severity: "error",
|
|
425
|
-
file: displayDir(failure.configPath)
|
|
521
|
+
file: `${scopeLabel(failure.scope)}'s ${displayDir(failure.configPath)}`,
|
|
426
522
|
message: `${CONFIG_UNREADABLE}: ${failure.reason}`
|
|
427
523
|
};
|
|
428
524
|
}
|
|
429
525
|
async function countExistingConfigs(projectDir) {
|
|
430
526
|
const present = await Promise.all(
|
|
431
|
-
configDirsInPlay(projectDir).map((dir) => fileExists(
|
|
527
|
+
configDirsInPlay(projectDir).map((dir) => fileExists(getInstalledConfigPath(dir)))
|
|
432
528
|
);
|
|
433
529
|
return present.filter(Boolean).length;
|
|
434
530
|
}
|
|
@@ -456,7 +552,7 @@ async function localSourcesToValidate(projectDir, registered) {
|
|
|
456
552
|
return [...local, { name: CWD_SOURCE_NAME, url: projectDir }];
|
|
457
553
|
}
|
|
458
554
|
function isCwdMarketplace(projectDir, source) {
|
|
459
|
-
return
|
|
555
|
+
return path3.resolve(projectDir, source.url) === projectDir;
|
|
460
556
|
}
|
|
461
557
|
function readerFor(projectDir, source) {
|
|
462
558
|
return isCwdMarketplace(projectDir, source) ? "author" : "consumer";
|
|
@@ -465,7 +561,7 @@ async function validateOneSource(source, reader) {
|
|
|
465
561
|
const label = `${source.name} (${source.url})`;
|
|
466
562
|
try {
|
|
467
563
|
const result = await validateSource(source.url, reader);
|
|
468
|
-
return { note: `${label} \u2014 ${result.skillCount}
|
|
564
|
+
return { note: `${label} \u2014 ${plural(result.skillCount, "skill")}`, issues: result.issues };
|
|
469
565
|
} catch (error) {
|
|
470
566
|
return {
|
|
471
567
|
note: `${label} \u2014 failed`,
|
|
@@ -525,7 +621,7 @@ async function findPluginDirectories(pluginsDir) {
|
|
|
525
621
|
const checks = await Promise.all(
|
|
526
622
|
entries.map(async (name) => ({
|
|
527
623
|
name,
|
|
528
|
-
isPlugin: await directoryExists(
|
|
624
|
+
isPlugin: await directoryExists(path3.join(pluginsDir, name, PLUGIN_MANIFEST_DIR))
|
|
529
625
|
}))
|
|
530
626
|
);
|
|
531
627
|
return checks.filter((check) => check.isPlugin).map((check) => check.name);
|
|
@@ -541,7 +637,9 @@ async function validateInstalledSkills(projectDir) {
|
|
|
541
637
|
);
|
|
542
638
|
}
|
|
543
639
|
async function configuredSkillIds(projectDir) {
|
|
544
|
-
const loaded = await Promise.all(
|
|
640
|
+
const loaded = await Promise.all(
|
|
641
|
+
configDirsInPlay(projectDir).map((dir) => loadInstalledConfig(dir))
|
|
642
|
+
);
|
|
545
643
|
return new Set(loaded.flatMap(configuredIdsOf));
|
|
546
644
|
}
|
|
547
645
|
function configuredIdsOf(loaded) {
|
|
@@ -569,10 +667,10 @@ async function listAgentFilesWithOurProvenance(agentsDir) {
|
|
|
569
667
|
const { marked } = await splitAgentsByProvenance(agentsDir);
|
|
570
668
|
return marked.map((name) => `${name}${AGENT_FILE_EXTENSION}`);
|
|
571
669
|
}
|
|
572
|
-
async function listSkillDirsWithOurProvenance(
|
|
573
|
-
const names = await listDirectories(
|
|
670
|
+
async function listSkillDirsWithOurProvenance(skillsDir2) {
|
|
671
|
+
const names = await listDirectories(skillsDir2);
|
|
574
672
|
const judged = await Promise.all(
|
|
575
|
-
names.map(async (name) => ({ name, ours: await carriesOurProvenance(
|
|
673
|
+
names.map(async (name) => ({ name, ours: await carriesOurProvenance(skillsDir2, name) }))
|
|
576
674
|
);
|
|
577
675
|
return judged.filter((entry) => entry.ours).map((entry) => entry.name);
|
|
578
676
|
}
|
|
@@ -583,40 +681,40 @@ async function listAcrossScopes(dirs, listEntries) {
|
|
|
583
681
|
async function listDisplayPaths(dir, listEntries) {
|
|
584
682
|
if (!await directoryExists(dir)) return [];
|
|
585
683
|
const entries = await listEntries(dir);
|
|
586
|
-
return entries.map((entry) => displayDir(
|
|
684
|
+
return entries.map((entry) => displayDir(path3.join(dir, entry)));
|
|
587
685
|
}
|
|
588
|
-
async function validateSkillsDirectory(
|
|
589
|
-
if (!await directoryExists(
|
|
590
|
-
const skillDirs = await listDirectories(
|
|
686
|
+
async function validateSkillsDirectory(skillsDir2, configuredIds) {
|
|
687
|
+
if (!await directoryExists(skillsDir2)) return NOTHING_VALIDATED;
|
|
688
|
+
const skillDirs = await listDirectories(skillsDir2);
|
|
591
689
|
if (skillDirs.length === 0) return NOTHING_VALIDATED;
|
|
592
690
|
const judged = await Promise.all(
|
|
593
691
|
skillDirs.map(async (name) => ({
|
|
594
692
|
name,
|
|
595
|
-
ours: await isOurSkillDirectory(
|
|
693
|
+
ours: await isOurSkillDirectory(skillsDir2, name, configuredIds)
|
|
596
694
|
}))
|
|
597
695
|
);
|
|
598
696
|
const [ours, foreign] = partition(judged, (entry) => entry.ours);
|
|
599
697
|
const results = await Promise.all(
|
|
600
698
|
ours.map(async ({ name }) => {
|
|
601
|
-
const skillDir =
|
|
699
|
+
const skillDir = path3.join(skillsDir2, name);
|
|
602
700
|
return toIssues(displayDir(skillDir), await validateInstalledSkill(skillDir));
|
|
603
701
|
})
|
|
604
702
|
);
|
|
605
703
|
return {
|
|
606
704
|
count: ours.length,
|
|
607
705
|
issues: results.flat(),
|
|
608
|
-
notes: foreign.map(({ name }) => foreignSkillNote(
|
|
706
|
+
notes: foreign.map(({ name }) => foreignSkillNote(skillsDir2, name))
|
|
609
707
|
};
|
|
610
708
|
}
|
|
611
|
-
async function isOurSkillDirectory(
|
|
709
|
+
async function isOurSkillDirectory(skillsDir2, name, configuredIds) {
|
|
612
710
|
if (configuredIds.has(name)) return true;
|
|
613
|
-
return carriesOurProvenance(
|
|
711
|
+
return carriesOurProvenance(skillsDir2, name);
|
|
614
712
|
}
|
|
615
|
-
async function carriesOurProvenance(
|
|
616
|
-
return await readForkedFromMetadata(
|
|
713
|
+
async function carriesOurProvenance(skillsDir2, name) {
|
|
714
|
+
return await readForkedFromMetadata(path3.join(skillsDir2, name)) !== null;
|
|
617
715
|
}
|
|
618
|
-
function foreignSkillNote(
|
|
619
|
-
return `${displayDir(
|
|
716
|
+
function foreignSkillNote(skillsDir2, name) {
|
|
717
|
+
return `${displayDir(path3.join(skillsDir2, name))} \u2014 not installed by this CLI and named by no configuration here: not validated`;
|
|
620
718
|
}
|
|
621
719
|
async function validateAgentsDirectory(agentsDir) {
|
|
622
720
|
if (!await directoryExists(agentsDir)) return NOTHING_VALIDATED;
|
|
@@ -624,46 +722,39 @@ async function validateAgentsDirectory(agentsDir) {
|
|
|
624
722
|
if (agentFiles.length === 0) return NOTHING_VALIDATED;
|
|
625
723
|
const results = await Promise.all(
|
|
626
724
|
agentFiles.map(async (fileName) => {
|
|
627
|
-
const agentPath =
|
|
725
|
+
const agentPath = path3.join(agentsDir, fileName);
|
|
628
726
|
return toIssues(displayDir(agentPath), await validateAgentFrontmatter(agentPath));
|
|
629
727
|
})
|
|
630
728
|
);
|
|
631
729
|
return { count: agentFiles.length, issues: results.flat(), notes: [] };
|
|
632
730
|
}
|
|
633
731
|
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 };
|
|
732
|
+
const skillMd = await validateSkillFile(
|
|
733
|
+
skillDir,
|
|
734
|
+
STANDARD_FILES.SKILL_MD,
|
|
735
|
+
validateSkillFrontmatter
|
|
736
|
+
);
|
|
737
|
+
const metadata = await validateSkillFile(
|
|
738
|
+
skillDir,
|
|
739
|
+
STANDARD_FILES.METADATA_YAML,
|
|
740
|
+
validateInstalledSkillMetadata
|
|
741
|
+
);
|
|
742
|
+
return mergeValidationResults([skillMd, metadata]);
|
|
743
|
+
}
|
|
744
|
+
async function validateSkillFile(skillDir, fileName, validate) {
|
|
745
|
+
const filePath = path3.join(skillDir, fileName);
|
|
746
|
+
if (!await fileExists(filePath)) return invalidResult(`Missing ${fileName}`);
|
|
747
|
+
return validate(filePath);
|
|
653
748
|
}
|
|
654
749
|
async function validateInstalledSkillMetadata(metadataPath) {
|
|
655
750
|
const read = await readSkillMetadata(metadataPath);
|
|
656
751
|
if (!read.usable) {
|
|
657
|
-
return {
|
|
658
|
-
valid: false,
|
|
659
|
-
errors: [`${STANDARD_FILES.METADATA_YAML}: ${read.reason}`],
|
|
660
|
-
warnings: []
|
|
661
|
-
};
|
|
752
|
+
return invalidResult(`${STANDARD_FILES.METADATA_YAML}: ${read.reason}`);
|
|
662
753
|
}
|
|
663
754
|
const metadata = read.metadata;
|
|
664
755
|
const result = validateSkillMetadata(metadata);
|
|
665
756
|
if (result.success) {
|
|
666
|
-
return
|
|
757
|
+
return validResult();
|
|
667
758
|
}
|
|
668
759
|
const { errors, warnings } = splitMetadataValidationIssues(result.error, metadata);
|
|
669
760
|
return {
|
|
@@ -675,20 +766,29 @@ async function validateInstalledSkillMetadata(metadataPath) {
|
|
|
675
766
|
|
|
676
767
|
// src/cli/commands/doctor.ts
|
|
677
768
|
import { countBy, unique } from "remeda";
|
|
678
|
-
var CONFIG_TS_REL = `${CLAUDE_SRC_DIR}/${STANDARD_FILES.CONFIG_TS}`;
|
|
679
769
|
async function resolveConfigState(detected, projectDir) {
|
|
680
|
-
if (detected?.config)
|
|
681
|
-
|
|
770
|
+
if (detected?.config) {
|
|
771
|
+
return {
|
|
772
|
+
kind: "loaded",
|
|
773
|
+
config: detected.config,
|
|
774
|
+
inherited: inheritsTheGlobalConfig(detected.installation.projectDir, projectDir)
|
|
775
|
+
};
|
|
776
|
+
}
|
|
777
|
+
const loaded = await loadInstalledConfig(projectDir);
|
|
682
778
|
if (!loaded) return { kind: "absent" };
|
|
683
|
-
return declaresNoContent(loaded.config) ? { kind: "declares-nothing", config: loaded.config } : { kind: "loaded", config: loaded.config };
|
|
779
|
+
return declaresNoContent(loaded.config) ? { kind: "declares-nothing", config: loaded.config } : { kind: "loaded", config: loaded.config, inherited: false };
|
|
780
|
+
}
|
|
781
|
+
function inheritsTheGlobalConfig(readFrom, projectDir) {
|
|
782
|
+
return isHomeDirectory(readFrom) && !isHomeDirectory(projectDir);
|
|
684
783
|
}
|
|
685
|
-
function checkConfigValid(state) {
|
|
784
|
+
function checkConfigValid(state, projectDir) {
|
|
785
|
+
const theConfig = (verdict) => configRowMessage(state, projectDir, verdict);
|
|
686
786
|
if (state.kind === "absent") {
|
|
687
787
|
return {
|
|
688
788
|
result: {
|
|
689
789
|
kind: "config",
|
|
690
790
|
status: "fail",
|
|
691
|
-
message:
|
|
791
|
+
message: theConfig("not found"),
|
|
692
792
|
details: [`Run '${CLI_INVOKE_COMMAND} init' to create a configuration`]
|
|
693
793
|
},
|
|
694
794
|
config: null
|
|
@@ -699,7 +799,7 @@ function checkConfigValid(state) {
|
|
|
699
799
|
result: {
|
|
700
800
|
kind: "config-empty",
|
|
701
801
|
status: "warn",
|
|
702
|
-
message:
|
|
802
|
+
message: theConfig("is valid but declares no skills and no agents")
|
|
703
803
|
},
|
|
704
804
|
config: state.config
|
|
705
805
|
};
|
|
@@ -711,7 +811,7 @@ function checkConfigValid(state) {
|
|
|
711
811
|
result: {
|
|
712
812
|
kind: "config",
|
|
713
813
|
status: "fail",
|
|
714
|
-
message:
|
|
814
|
+
message: theConfig("has errors"),
|
|
715
815
|
details: validation.errors
|
|
716
816
|
},
|
|
717
817
|
config: null
|
|
@@ -722,7 +822,7 @@ function checkConfigValid(state) {
|
|
|
722
822
|
result: {
|
|
723
823
|
kind: "config",
|
|
724
824
|
status: "warn",
|
|
725
|
-
message:
|
|
825
|
+
message: theConfig("has warnings"),
|
|
726
826
|
details: validation.warnings
|
|
727
827
|
},
|
|
728
828
|
config
|
|
@@ -732,11 +832,29 @@ function checkConfigValid(state) {
|
|
|
732
832
|
result: {
|
|
733
833
|
kind: "config",
|
|
734
834
|
status: "pass",
|
|
735
|
-
message:
|
|
835
|
+
message: theConfig("is valid")
|
|
736
836
|
},
|
|
737
837
|
config
|
|
738
838
|
};
|
|
739
839
|
}
|
|
840
|
+
function configRowMessage(state, projectDir, verdict) {
|
|
841
|
+
if (state.kind === "loaded" && state.inherited)
|
|
842
|
+
return `${theInheritedConfig()}, which ${verdict}`;
|
|
843
|
+
return `${theOwnConfig(projectDir)} ${verdict}`;
|
|
844
|
+
}
|
|
845
|
+
function theOwnConfig(projectDir) {
|
|
846
|
+
return `${scopeLabel(scopeOf(projectDir))}: ${configNamedFrom(projectDir)}`;
|
|
847
|
+
}
|
|
848
|
+
function theInheritedConfig() {
|
|
849
|
+
return `${scopeLabel("project")}: no ${STANDARD_FILES.CONFIG_TS}, using ${scopeNoun("global")}'s (${configNamedFrom(os2.homedir())})`;
|
|
850
|
+
}
|
|
851
|
+
function scopeOf(dir) {
|
|
852
|
+
return isHomeDirectory(dir) ? "global" : "project";
|
|
853
|
+
}
|
|
854
|
+
function configNamedFrom(root) {
|
|
855
|
+
const relPath = relativeConfigPath(root, providerInUse(root));
|
|
856
|
+
return isHomeDirectory(root) ? `~/${relPath}` : relPath;
|
|
857
|
+
}
|
|
740
858
|
async function checkSkillsResolved(config, matrix2, projectDir) {
|
|
741
859
|
const excludedIds = effectivelyExcludedSkillIds(config.skills);
|
|
742
860
|
const stackIds = config.stack ? getStackSkillIds(config.stack) : [];
|
|
@@ -765,14 +883,14 @@ async function checkSkillsResolved(config, matrix2, projectDir) {
|
|
|
765
883
|
return {
|
|
766
884
|
kind: "skills",
|
|
767
885
|
status: "fail",
|
|
768
|
-
message: `${uniqueSkills.length - missingSkills.length
|
|
886
|
+
message: `${ratio(uniqueSkills.length - missingSkills.length, uniqueSkills.length, "skill")} found`,
|
|
769
887
|
details: missingSkills.map((s) => `- ${s} (not found)`)
|
|
770
888
|
};
|
|
771
889
|
}
|
|
772
890
|
return {
|
|
773
891
|
kind: "skills",
|
|
774
892
|
status: "pass",
|
|
775
|
-
message: `${uniqueSkills.length
|
|
893
|
+
message: `${ratio(uniqueSkills.length, uniqueSkills.length, "skill")} found`
|
|
776
894
|
};
|
|
777
895
|
}
|
|
778
896
|
async function checkAgentsCompiled(config, projectDir) {
|
|
@@ -784,16 +902,28 @@ async function checkAgentsCompiled(config, projectDir) {
|
|
|
784
902
|
message: "No agents configured"
|
|
785
903
|
};
|
|
786
904
|
}
|
|
787
|
-
const
|
|
788
|
-
|
|
905
|
+
const hosts = {
|
|
906
|
+
global: providerInUse(installBaseDir(projectDir, "global")),
|
|
907
|
+
project: providerInUse(installBaseDir(projectDir, "project"))
|
|
908
|
+
};
|
|
909
|
+
const dirs = {
|
|
910
|
+
global: resolveInstallPaths(projectDir, "global").agentsDir,
|
|
911
|
+
project: resolveInstallPaths(projectDir, "project").agentsDir
|
|
912
|
+
};
|
|
789
913
|
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
|
-
})
|
|
914
|
+
agents.map((agent) => agentCompileCheck(agent, hosts, dirs))
|
|
795
915
|
);
|
|
796
|
-
const
|
|
916
|
+
const unrenderable = agentChecks.filter((c) => c.state === "unrenderable");
|
|
917
|
+
const missingAgents = agentChecks.filter((c) => c.state === "missing").map((c) => c.name);
|
|
918
|
+
if (unrenderable.length === agents.length) {
|
|
919
|
+
const hostsLeavingThemOut = unique(unrenderable.map((a) => a.provider)).join(" or ");
|
|
920
|
+
return {
|
|
921
|
+
kind: "agents",
|
|
922
|
+
status: "pass",
|
|
923
|
+
message: `No sub-agent in this configuration is carried by ${hostsLeavingThemOut}`,
|
|
924
|
+
details: unrenderable.map((a) => `- ${a.name} (left out of ${a.provider})`)
|
|
925
|
+
};
|
|
926
|
+
}
|
|
797
927
|
if (missingAgents.length > 0) {
|
|
798
928
|
return {
|
|
799
929
|
kind: "agents",
|
|
@@ -802,13 +932,24 @@ async function checkAgentsCompiled(config, projectDir) {
|
|
|
802
932
|
details: missingAgents.map((a) => `- ${a} (missing)`)
|
|
803
933
|
};
|
|
804
934
|
}
|
|
935
|
+
const renderable = agents.length - unrenderable.length;
|
|
805
936
|
return {
|
|
806
937
|
kind: "agents",
|
|
807
938
|
status: "pass",
|
|
808
|
-
message: `${
|
|
939
|
+
message: `${ratio(renderable, renderable, "agent")} compiled`
|
|
809
940
|
};
|
|
810
941
|
}
|
|
942
|
+
async function agentCompileCheck(agent, hosts, agentsDirs) {
|
|
943
|
+
const provider = hosts[agent.scope];
|
|
944
|
+
if (!hostCompilesAgent(provider, agent.name)) {
|
|
945
|
+
return { name: agent.name, provider, state: "unrenderable" };
|
|
946
|
+
}
|
|
947
|
+
const file = `${agent.name}${agentCodec(provider).extension}`;
|
|
948
|
+
const compiled = await fileExists(path4.join(agentsDirs[agent.scope], file));
|
|
949
|
+
return { name: agent.name, provider, state: compiled ? "compiled" : "missing" };
|
|
950
|
+
}
|
|
811
951
|
async function checkNoOrphans(config, projectDir) {
|
|
952
|
+
const codec = agentCodec(providerInUse(projectDir));
|
|
812
953
|
const projectAgentsDir = resolveInstallPaths(projectDir, "project").agentsDir;
|
|
813
954
|
const globalAgentsDir = resolveInstallPaths(projectDir, "global").agentsDir;
|
|
814
955
|
const scopesShareAgentsDir = isHomeDirectory(projectDir);
|
|
@@ -823,8 +964,8 @@ async function checkNoOrphans(config, projectDir) {
|
|
|
823
964
|
message: "No agents directory"
|
|
824
965
|
};
|
|
825
966
|
}
|
|
826
|
-
const projectMdFiles = projectExists ? await
|
|
827
|
-
const globalMdFiles = globalExists ? await
|
|
967
|
+
const projectMdFiles = projectExists ? await listAgentFilesOf(projectAgentsDir, codec) : [];
|
|
968
|
+
const globalMdFiles = globalExists ? await listAgentFilesOf(globalAgentsDir, codec) : [];
|
|
828
969
|
const activeProjectAgents = new Set(
|
|
829
970
|
config.agents.filter((a) => a.scope === "project" && !a.excluded).map((a) => a.name)
|
|
830
971
|
);
|
|
@@ -833,8 +974,8 @@ async function checkNoOrphans(config, projectDir) {
|
|
|
833
974
|
);
|
|
834
975
|
const knownProjectDirAgents = scopesShareAgentsDir ? /* @__PURE__ */ new Set([...activeProjectAgents, ...knownGlobalAgents]) : activeProjectAgents;
|
|
835
976
|
const orphanedFiles = [
|
|
836
|
-
...orphanedAgentNames(projectMdFiles, knownProjectDirAgents),
|
|
837
|
-
...orphanedAgentNames(globalMdFiles, knownGlobalAgents)
|
|
977
|
+
...orphanedAgentNames(projectMdFiles, knownProjectDirAgents, codec),
|
|
978
|
+
...orphanedAgentNames(globalMdFiles, knownGlobalAgents, codec)
|
|
838
979
|
];
|
|
839
980
|
if (orphanedFiles.length > 0) {
|
|
840
981
|
return {
|
|
@@ -850,8 +991,8 @@ async function checkNoOrphans(config, projectDir) {
|
|
|
850
991
|
message: "No orphaned agent files"
|
|
851
992
|
};
|
|
852
993
|
}
|
|
853
|
-
function orphanedAgentNames(mdFiles, knownAgents) {
|
|
854
|
-
return mdFiles.map((fileName) =>
|
|
994
|
+
function orphanedAgentNames(mdFiles, knownAgents, codec) {
|
|
995
|
+
return mdFiles.map((fileName) => path4.basename(fileName, codec.extension)).filter((agentName) => !knownAgents.has(agentName));
|
|
855
996
|
}
|
|
856
997
|
async function checkUnownedInstallation(projectDir) {
|
|
857
998
|
const { skills, agents } = await listInstalledArtifacts(projectDir);
|
|
@@ -865,6 +1006,9 @@ async function checkUnownedInstallation(projectDir) {
|
|
|
865
1006
|
details: [...skills, ...agents].map((artifact) => `- ${artifact}`)
|
|
866
1007
|
};
|
|
867
1008
|
}
|
|
1009
|
+
function ratio(found, total, noun) {
|
|
1010
|
+
return `${found}/${plural(total, noun)}`;
|
|
1011
|
+
}
|
|
868
1012
|
function countedArtifacts(skills, agents) {
|
|
869
1013
|
return [
|
|
870
1014
|
...skills.length > 0 ? [plural(skills.length, "skill")] : [],
|
|
@@ -882,25 +1026,31 @@ async function checkSkillsInstalled(config, projectDir) {
|
|
|
882
1026
|
};
|
|
883
1027
|
}
|
|
884
1028
|
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
|
-
})
|
|
1029
|
+
ejectSkills.map((skill) => ejectedSkillCheck(skill, projectDir))
|
|
890
1030
|
);
|
|
891
|
-
const missingSkills = skillChecks.filter((c) => !c.installed)
|
|
1031
|
+
const missingSkills = skillChecks.filter((c) => !c.installed);
|
|
892
1032
|
if (missingSkills.length > 0) {
|
|
893
1033
|
return {
|
|
894
1034
|
kind: "installed",
|
|
895
1035
|
status: "warn",
|
|
896
1036
|
message: `${plural(missingSkills.length, "skill")} missing from disk`,
|
|
897
|
-
details: missingSkills.map((s) => `- ${s} (not found in ${
|
|
1037
|
+
details: missingSkills.map((s) => `- ${s.id} (not found in ${s.lookedIn}/)`)
|
|
898
1038
|
};
|
|
899
1039
|
}
|
|
900
1040
|
return {
|
|
901
1041
|
kind: "installed",
|
|
902
1042
|
status: "pass",
|
|
903
|
-
message: `${ejectSkills.length
|
|
1043
|
+
message: `${ratio(ejectSkills.length, ejectSkills.length, "eject-mode skill")} installed`
|
|
1044
|
+
};
|
|
1045
|
+
}
|
|
1046
|
+
async function ejectedSkillCheck(skill, projectDir) {
|
|
1047
|
+
const provider = providerInUse(installBaseDir(projectDir, skill.scope));
|
|
1048
|
+
const dir = skillsDir(provider, skill.scope, projectDir);
|
|
1049
|
+
const installed = await fileExists(path4.join(dir, skill.id, STANDARD_FILES.SKILL_MD));
|
|
1050
|
+
return {
|
|
1051
|
+
id: skill.id,
|
|
1052
|
+
installed,
|
|
1053
|
+
lookedIn: skillsPathPrefix(provider, skill.scope, projectDir)
|
|
904
1054
|
};
|
|
905
1055
|
}
|
|
906
1056
|
async function checkPluginSkillsInstalled(config, projectDir) {
|
|
@@ -935,7 +1085,7 @@ async function checkPluginSkillsInstalled(config, projectDir) {
|
|
|
935
1085
|
return {
|
|
936
1086
|
kind: "plugins",
|
|
937
1087
|
status: "pass",
|
|
938
|
-
message: `${pluginSkills.length
|
|
1088
|
+
message: `${ratio(pluginSkills.length, pluginSkills.length, "plugin-mode skill")} installed`
|
|
939
1089
|
};
|
|
940
1090
|
}
|
|
941
1091
|
var MARKETPLACE_CHOSEN_BY = {
|
|
@@ -950,7 +1100,7 @@ function howItWasReached(source, isLocal) {
|
|
|
950
1100
|
}
|
|
951
1101
|
function reachedMarketplace(result) {
|
|
952
1102
|
const { source, sourceOrigin } = result.sourceConfig;
|
|
953
|
-
const skillCount =
|
|
1103
|
+
const skillCount = result.marketplaceSkillIds.size;
|
|
954
1104
|
const sourceLabel = result.isLocal ? "local" : "remote";
|
|
955
1105
|
return {
|
|
956
1106
|
kind: "source",
|
|
@@ -1069,9 +1219,6 @@ var SKIP_AFTER_CONFIG_ERROR = "Skipped \u2014 fix the content errors above first
|
|
|
1069
1219
|
function skipRestatingContent(nouns) {
|
|
1070
1220
|
return `Skipped \u2014 this row would only restate the ${nouns.join(" and ")} errors above`;
|
|
1071
1221
|
}
|
|
1072
|
-
function plural(count, word) {
|
|
1073
|
-
return `${count} ${word}${count === 1 ? "" : "s"}`;
|
|
1074
|
-
}
|
|
1075
1222
|
function formatCheckName(name) {
|
|
1076
1223
|
return name.padEnd(CHECK_WIDTH);
|
|
1077
1224
|
}
|
|
@@ -1091,13 +1238,15 @@ function formatStatus(status) {
|
|
|
1091
1238
|
}
|
|
1092
1239
|
}
|
|
1093
1240
|
}
|
|
1241
|
+
var DETAIL_INDENT = `${ROW_INDENT}${" ".repeat(CHECK_WIDTH)} `;
|
|
1094
1242
|
function formatCheckLine(name, result) {
|
|
1095
1243
|
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
|
-
);
|
|
1244
|
+
const detailLines = (result.details ?? []).flatMap(indentedDetailLines);
|
|
1099
1245
|
return [headerLine, ...detailLines];
|
|
1100
1246
|
}
|
|
1247
|
+
function indentedDetailLines(detail) {
|
|
1248
|
+
return detail.split("\n").filter((line) => line !== "").map((line) => `${DETAIL_INDENT}${line}`);
|
|
1249
|
+
}
|
|
1101
1250
|
function formatSummary(results) {
|
|
1102
1251
|
const counts = countBy(results, (r) => r.status);
|
|
1103
1252
|
const parts = [
|
|
@@ -1126,14 +1275,6 @@ var TIPS = [
|
|
|
1126
1275
|
status: "warn",
|
|
1127
1276
|
tip: ` Tip: Nothing is configured yet \u2014 run '${CLI_INVOKE_COMMAND} init' to choose skills and sub-agents`
|
|
1128
1277
|
},
|
|
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
1278
|
{
|
|
1138
1279
|
// Printed beside the config tip above, which says how to get a configuration back and
|
|
1139
1280
|
// nothing about the files that outlived the old one. Both halves are named with what they
|
|
@@ -1163,6 +1304,49 @@ function formatTips(results) {
|
|
|
1163
1304
|
(t) => t.tip
|
|
1164
1305
|
);
|
|
1165
1306
|
}
|
|
1307
|
+
async function recreateConfigTips(contentResults, projectDir) {
|
|
1308
|
+
if (!failedContentKinds(contentResults).has(CONFIG_CHECK.kind)) return [];
|
|
1309
|
+
const failures = await findConfigLoadFailures(projectDir).catch(() => []);
|
|
1310
|
+
return failures.map(
|
|
1311
|
+
(failure) => ` Tip: There is no automatic repair \u2014 recreate the configuration by running ${recreateConfigFrom(failure.scopeRoot)}`
|
|
1312
|
+
);
|
|
1313
|
+
}
|
|
1314
|
+
var LAYOUT_ROW_NAME = "Layout";
|
|
1315
|
+
function toLayoutResult(finding) {
|
|
1316
|
+
return {
|
|
1317
|
+
kind: "layout",
|
|
1318
|
+
status: finding.status,
|
|
1319
|
+
message: finding.message,
|
|
1320
|
+
details: finding.details
|
|
1321
|
+
};
|
|
1322
|
+
}
|
|
1323
|
+
async function registeredProjectsOnTheOldFolder(home) {
|
|
1324
|
+
const loaded = await loadInstalledConfig(home).catch(() => null);
|
|
1325
|
+
return (loaded?.config.projects ?? []).filter(isOnTheLegacySourceFolder);
|
|
1326
|
+
}
|
|
1327
|
+
function isOnTheLegacySourceFolder(dir) {
|
|
1328
|
+
return resolveSourceDir(dir, providerInUse(dir)).legacy;
|
|
1329
|
+
}
|
|
1330
|
+
var PLACEMENTS_ROW_NAME = "Placements Offered";
|
|
1331
|
+
async function checkOfferedPlacements(projectDir) {
|
|
1332
|
+
const findings = await everythingThatWouldStopACommandActing(projectDir);
|
|
1333
|
+
if (findings.length === 0) {
|
|
1334
|
+
return { kind: "placements", status: "pass", message: "Every configured skill is placeable" };
|
|
1335
|
+
}
|
|
1336
|
+
return {
|
|
1337
|
+
kind: "placements",
|
|
1338
|
+
status: "fail",
|
|
1339
|
+
message: `${plural(findings.length, "finding")} would stop a command acting here`,
|
|
1340
|
+
details: findings.map((finding) => `- ${finding}`)
|
|
1341
|
+
};
|
|
1342
|
+
}
|
|
1343
|
+
async function everythingThatWouldStopACommandActing(projectDir) {
|
|
1344
|
+
const [unofferable, ambiguous] = await Promise.all([
|
|
1345
|
+
unofferablePlacementsFound(projectDir),
|
|
1346
|
+
ambiguityFinding(projectDir)
|
|
1347
|
+
]);
|
|
1348
|
+
return ambiguous === null ? unofferable : [...unofferable, ambiguous];
|
|
1349
|
+
}
|
|
1166
1350
|
function skippedResult(kind) {
|
|
1167
1351
|
return { kind, status: "skip", message: "Skipped (config invalid)" };
|
|
1168
1352
|
}
|
|
@@ -1202,7 +1386,7 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1202
1386
|
const contentResults = await this.runContentChecks(projectDir);
|
|
1203
1387
|
const operationalResults = await this.runOperationalChecks(projectDir, contentResults);
|
|
1204
1388
|
const results = [...contentResults, ...operationalResults];
|
|
1205
|
-
this.printResults(results);
|
|
1389
|
+
this.printResults(results, await recreateConfigTips(contentResults, projectDir));
|
|
1206
1390
|
if (results.some((r) => r.status === "fail")) {
|
|
1207
1391
|
this.exit(EXIT_CODES.ERROR);
|
|
1208
1392
|
}
|
|
@@ -1265,12 +1449,18 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1265
1449
|
* at all, not merely no usable one.
|
|
1266
1450
|
*/
|
|
1267
1451
|
async isUninstalledSourceRepo(projectDir) {
|
|
1268
|
-
if (await fileExists(
|
|
1452
|
+
if (await fileExists(getInstalledConfigPath(projectDir))) return false;
|
|
1269
1453
|
return isSourceRepo(projectDir);
|
|
1270
1454
|
}
|
|
1271
1455
|
async runAllChecks(projectDir, configState, failedContent) {
|
|
1272
|
-
const { result: configResult, config } = checkConfigValid(configState);
|
|
1456
|
+
const { result: configResult, config } = checkConfigValid(configState, projectDir);
|
|
1273
1457
|
this.logCheck("Config Valid", configResult);
|
|
1458
|
+
const layoutResults = await this.reportLayout(projectDir);
|
|
1459
|
+
const placementsResult = await safeCheck(
|
|
1460
|
+
"placements",
|
|
1461
|
+
() => checkOfferedPlacements(projectDir)
|
|
1462
|
+
);
|
|
1463
|
+
this.logCheck(PLACEMENTS_ROW_NAME, placementsResult);
|
|
1274
1464
|
const sourceResult = await safeCheck("source", () => checkSourceReachable(projectDir));
|
|
1275
1465
|
const filteredConfig = config ? filterExcludedEntries(config) : null;
|
|
1276
1466
|
const skillsResult = await this.resolveSkillsCheck(
|
|
@@ -1291,6 +1481,8 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1291
1481
|
this.logCheck("Marketplace Reachable", sourceResult);
|
|
1292
1482
|
return [
|
|
1293
1483
|
configResult,
|
|
1484
|
+
...layoutResults,
|
|
1485
|
+
placementsResult,
|
|
1294
1486
|
skillsResult,
|
|
1295
1487
|
agentsResult,
|
|
1296
1488
|
orphansResult,
|
|
@@ -1299,6 +1491,49 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1299
1491
|
sourceResult
|
|
1300
1492
|
];
|
|
1301
1493
|
}
|
|
1494
|
+
/**
|
|
1495
|
+
* One `Layout` row per scope in play, and — at the home directory — the registered projects
|
|
1496
|
+
* still on the old folder.
|
|
1497
|
+
*
|
|
1498
|
+
* Per scope rather than per machine: a project and the global installation can be on different
|
|
1499
|
+
* layouts, and one row for the two can only be right about one of them.
|
|
1500
|
+
*
|
|
1501
|
+
* Printed right under `Config Valid`, because every row below it is read out of a config file
|
|
1502
|
+
* whose folder this row is about — a scope with two rival folders has a config the rest of the
|
|
1503
|
+
* report may have read from either one.
|
|
1504
|
+
*/
|
|
1505
|
+
async reportLayout(projectDir) {
|
|
1506
|
+
const scopes = await sourceScopesInPlay(projectDir);
|
|
1507
|
+
const rows = await Promise.all(
|
|
1508
|
+
scopes.map(async (scope) => toLayoutResult(await layoutFindingFor(scope)))
|
|
1509
|
+
);
|
|
1510
|
+
const withRegistry = await this.appendRegistryList(projectDir, rows);
|
|
1511
|
+
for (const row of withRegistry) {
|
|
1512
|
+
this.logCheck(LAYOUT_ROW_NAME, row);
|
|
1513
|
+
}
|
|
1514
|
+
return withRegistry;
|
|
1515
|
+
}
|
|
1516
|
+
/**
|
|
1517
|
+
* The registered projects still on the old folder, added to the LAST row, and only at HOME.
|
|
1518
|
+
*
|
|
1519
|
+
* At home because that is where the registry lives, and the move a legacy project needs is made
|
|
1520
|
+
* by hand in that project — a list of other people's projects printed inside one of them is a
|
|
1521
|
+
* list nobody standing there can act on. The caveat travels with it: the registry holds a
|
|
1522
|
+
* fraction of the installations on a machine, so a list printed without it reads as an
|
|
1523
|
+
* inventory.
|
|
1524
|
+
*/
|
|
1525
|
+
async appendRegistryList(projectDir, rows) {
|
|
1526
|
+
const last = rows.at(-1);
|
|
1527
|
+
if (!isHomeDirectory(projectDir) || last === void 0) return rows;
|
|
1528
|
+
const legacyProjects = await registeredProjectsOnTheOldFolder(projectDir);
|
|
1529
|
+
if (legacyProjects.length === 0) return rows;
|
|
1530
|
+
const detailed = {
|
|
1531
|
+
...last,
|
|
1532
|
+
status: last.status === "fail" ? "fail" : "warn",
|
|
1533
|
+
details: [...last.details ?? [], REGISTRY_IS_NOT_AN_INVENTORY, ...legacyProjects]
|
|
1534
|
+
};
|
|
1535
|
+
return [...rows.slice(0, -1), detailed];
|
|
1536
|
+
}
|
|
1302
1537
|
/**
|
|
1303
1538
|
* The orphan row, and which question it can answer. With a config it names the compiled
|
|
1304
1539
|
* agents that config does not; with no config at all every installed file is unowned and it
|
|
@@ -1342,10 +1577,10 @@ var Doctor = class _Doctor extends BaseCommand {
|
|
|
1342
1577
|
this.log(line);
|
|
1343
1578
|
}
|
|
1344
1579
|
}
|
|
1345
|
-
printResults(results) {
|
|
1580
|
+
printResults(results, recreateTips) {
|
|
1346
1581
|
this.log("");
|
|
1347
1582
|
this.log(formatSummary(results));
|
|
1348
|
-
const tips = formatTips(results);
|
|
1583
|
+
const tips = [...formatTips(results), ...recreateTips];
|
|
1349
1584
|
if (tips.length > 0) {
|
|
1350
1585
|
this.log("");
|
|
1351
1586
|
for (const tip of tips) {
|