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.
Files changed (191) hide show
  1. package/CHANGELOG.md +63 -0
  2. package/README.md +8 -9
  3. package/dist/{chunk-X4SSJZLY.js → chunk-25MPBIK3.js} +12 -261
  4. package/dist/chunk-25MPBIK3.js.map +1 -0
  5. package/dist/{chunk-LT5GXEB2.js → chunk-2VUYFTUB.js} +8 -7
  6. package/dist/chunk-2VUYFTUB.js.map +1 -0
  7. package/dist/chunk-4AAOOAIN.js +49 -0
  8. package/dist/chunk-4AAOOAIN.js.map +1 -0
  9. package/dist/{chunk-ARYNOWKF.js → chunk-4NDBUJHK.js} +8 -8
  10. package/dist/{chunk-S63LXWMC.js → chunk-6L75SQZU.js} +2 -2
  11. package/dist/{chunk-7REKOTKG.js → chunk-6LF4CQBY.js} +3 -3
  12. package/dist/{chunk-K5ITEA3I.js → chunk-7JNJ52CD.js} +34 -17
  13. package/dist/chunk-7JNJ52CD.js.map +1 -0
  14. package/dist/{chunk-XPDVAJDH.js → chunk-A4KDXNOT.js} +3519 -1568
  15. package/dist/chunk-A4KDXNOT.js.map +1 -0
  16. package/dist/{chunk-XHPWTL4I.js → chunk-DAIEQJIF.js} +3 -3
  17. package/dist/{chunk-UITEKY2R.js → chunk-ENGYJCTF.js} +28 -4
  18. package/dist/chunk-ENGYJCTF.js.map +1 -0
  19. package/dist/{chunk-G2JJ5BO5.js → chunk-G4JK7WVO.js} +336 -140
  20. package/dist/chunk-G4JK7WVO.js.map +1 -0
  21. package/dist/{chunk-X37UYIE3.js → chunk-G6UREQ4V.js} +436 -739
  22. package/dist/chunk-G6UREQ4V.js.map +1 -0
  23. package/dist/chunk-GE3WKJ6Q.js +375 -0
  24. package/dist/chunk-GE3WKJ6Q.js.map +1 -0
  25. package/dist/{chunk-RGYDFBCT.js → chunk-HHIYT3PI.js} +5 -5
  26. package/dist/{chunk-2CQNPLVF.js → chunk-HWEKHVLE.js} +63 -42
  27. package/dist/chunk-HWEKHVLE.js.map +1 -0
  28. package/dist/{chunk-XVAQG2CV.js → chunk-JOEA52NR.js} +10 -8
  29. package/dist/chunk-JOEA52NR.js.map +1 -0
  30. package/dist/{chunk-3WVH2CTK.js → chunk-JQRJQ4SS.js} +15 -8
  31. package/dist/chunk-JQRJQ4SS.js.map +1 -0
  32. package/dist/{chunk-DSL66I4N.js → chunk-LFWLEDUG.js} +6 -6
  33. package/dist/{chunk-B3Y3QMWI.js → chunk-MAWNVT2C.js} +4 -4
  34. package/dist/{chunk-Q4IRRVXK.js → chunk-MPTNC32T.js} +3 -3
  35. package/dist/chunk-NBXXFIIW.js +73 -0
  36. package/dist/chunk-NBXXFIIW.js.map +1 -0
  37. package/dist/{chunk-B6FSLXYN.js → chunk-PMJWD27C.js} +3 -3
  38. package/dist/{chunk-XATLUKXK.js → chunk-PNB3524H.js} +6 -6
  39. package/dist/chunk-PXWG7KFO.js +39 -0
  40. package/dist/chunk-PXWG7KFO.js.map +1 -0
  41. package/dist/{chunk-4VL3CEOS.js → chunk-QHDPLLK6.js} +3 -3
  42. package/dist/{chunk-AOTCIUGX.js → chunk-RAHSOJQ2.js} +2 -2
  43. package/dist/{chunk-PHCPUQVR.js → chunk-RZDXDMT6.js} +2 -2
  44. package/dist/{chunk-6MV4JOCL.js → chunk-SAYZWCH6.js} +2 -2
  45. package/dist/{chunk-URXIQE7I.js → chunk-SCOQ34YM.js} +2 -2
  46. package/dist/{chunk-NQXO4LR3.js → chunk-SXVHSOTH.js} +16 -11
  47. package/dist/chunk-SXVHSOTH.js.map +1 -0
  48. package/dist/{chunk-QZX7SYKU.js → chunk-T6UWJAB3.js} +2 -2
  49. package/dist/{chunk-ZVXGW2IC.js → chunk-TODCQFRB.js} +4 -6
  50. package/dist/chunk-TODCQFRB.js.map +1 -0
  51. package/dist/{chunk-YRS3QZP2.js → chunk-TTY6DPJN.js} +3 -3
  52. package/dist/{chunk-QGUP3QJF.js → chunk-TZ2YJJKK.js} +22 -18
  53. package/dist/{chunk-QGUP3QJF.js.map → chunk-TZ2YJJKK.js.map} +1 -1
  54. package/dist/chunk-V3I7VQ7N.js +356 -0
  55. package/dist/chunk-V3I7VQ7N.js.map +1 -0
  56. package/dist/{chunk-AVK2HEKB.js → chunk-V5WXIM2F.js} +2 -6
  57. package/dist/{chunk-AVK2HEKB.js.map → chunk-V5WXIM2F.js.map} +1 -1
  58. package/dist/{chunk-NKGYKOB7.js → chunk-VAPTVR3J.js} +7 -8
  59. package/dist/chunk-VAPTVR3J.js.map +1 -0
  60. package/dist/chunk-VRXX4D6K.js +31 -0
  61. package/dist/chunk-VRXX4D6K.js.map +1 -0
  62. package/dist/{chunk-ABAZMMG4.js → chunk-WVUDLKIO.js} +2 -2
  63. package/dist/{chunk-ECURHW3T.js → chunk-WWIFNPJG.js} +13 -12
  64. package/dist/chunk-WWIFNPJG.js.map +1 -0
  65. package/dist/{chunk-J27W6TXS.js → chunk-XQHZPINL.js} +8 -4
  66. package/dist/chunk-XQHZPINL.js.map +1 -0
  67. package/dist/{chunk-V5GLL2JB.js → chunk-XZEVWPX6.js} +7 -7
  68. package/dist/chunk-Y3VIRVVE.js +932 -0
  69. package/dist/chunk-Y3VIRVVE.js.map +1 -0
  70. package/dist/{chunk-NTF4UXGK.js → chunk-Y47EV74W.js} +73 -30
  71. package/dist/chunk-Y47EV74W.js.map +1 -0
  72. package/dist/chunk-ZR4YPKXQ.js +159 -0
  73. package/dist/chunk-ZR4YPKXQ.js.map +1 -0
  74. package/dist/chunk-ZXKBERQU.js +250 -0
  75. package/dist/chunk-ZXKBERQU.js.map +1 -0
  76. package/dist/commands/build/marketplace.js +17 -17
  77. package/dist/commands/build/marketplace.js.map +1 -1
  78. package/dist/commands/build/plugins.js +11 -9
  79. package/dist/commands/build/plugins.js.map +1 -1
  80. package/dist/commands/compile.js +105 -27
  81. package/dist/commands/compile.js.map +1 -1
  82. package/dist/commands/doctor.js +309 -171
  83. package/dist/commands/doctor.js.map +1 -1
  84. package/dist/commands/edit.js +503 -314
  85. package/dist/commands/edit.js.map +1 -1
  86. package/dist/commands/eject.js +40 -23
  87. package/dist/commands/eject.js.map +1 -1
  88. package/dist/commands/init.js +44 -40
  89. package/dist/commands/list.js +44 -10
  90. package/dist/commands/list.js.map +1 -1
  91. package/dist/commands/new/marketplace.js +13 -10
  92. package/dist/commands/new/marketplace.js.map +1 -1
  93. package/dist/commands/search.js +12 -10
  94. package/dist/commands/search.js.map +1 -1
  95. package/dist/commands/share.js +102 -15
  96. package/dist/commands/share.js.map +1 -1
  97. package/dist/commands/uninstall.js +231 -103
  98. package/dist/commands/uninstall.js.map +1 -1
  99. package/dist/commands/update.js +49 -20
  100. package/dist/commands/update.js.map +1 -1
  101. package/dist/components/common/prompt-confirm.js +1 -1
  102. package/dist/components/common/removal-plan-confirm.js +2 -2
  103. package/dist/components/common/select-list.js +3 -3
  104. package/dist/components/common/spinner.js +6 -3
  105. package/dist/components/wizard/category-grid.js +5 -5
  106. package/dist/components/wizard/checkbox-grid.js +4 -4
  107. package/dist/components/wizard/domain-selection.js +9 -9
  108. package/dist/components/wizard/run-wizard-session.js +29 -29
  109. package/dist/components/wizard/scroll-affordance.js +2 -2
  110. package/dist/components/wizard/skill-agent-summary.js +5 -5
  111. package/dist/components/wizard/source-grid.js +6 -6
  112. package/dist/components/wizard/stack-selection.js +8 -8
  113. package/dist/components/wizard/step-agents.js +8 -8
  114. package/dist/components/wizard/step-build.js +8 -8
  115. package/dist/components/wizard/step-confirm.js +9 -9
  116. package/dist/components/wizard/step-sources.js +9 -9
  117. package/dist/components/wizard/step-stack.js +9 -9
  118. package/dist/components/wizard/summary-panel.js +8 -8
  119. package/dist/components/wizard/toast.js +2 -2
  120. package/dist/components/wizard/wizard-layout.js +12 -12
  121. package/dist/components/wizard/wizard-tabs.js +2 -2
  122. package/dist/components/wizard/wizard.js +27 -27
  123. package/dist/hooks/init.js +69 -43
  124. package/dist/hooks/init.js.map +1 -1
  125. package/dist/load-agent-defs-3C27ENC7.js +14 -0
  126. package/dist/recompile-project-agents-DGNBCU3N.js +18 -0
  127. package/dist/{source-loader-GRBVNRAW.js → source-loader-7KZ3MD5O.js} +6 -4
  128. package/dist/src/agents/_templates/agent-body.liquid +55 -0
  129. package/dist/src/agents/_templates/agent.liquid +1 -55
  130. package/dist/src/agents/meta/agent-summoner/output.md +1 -1
  131. package/dist/src/agents/meta/agent-summoner/playbook.md +15 -7
  132. package/dist/stores/wizard-store.js +4 -4
  133. package/package.json +10 -9
  134. package/src/agents/_templates/agent-body.liquid +55 -0
  135. package/src/agents/_templates/agent.liquid +1 -55
  136. package/src/agents/meta/agent-summoner/output.md +1 -1
  137. package/src/agents/meta/agent-summoner/playbook.md +15 -7
  138. package/src/schemas/project-config.schema.json +1 -1
  139. package/src/schemas/project-source-config.schema.json +1 -1
  140. package/dist/chunk-2CQNPLVF.js.map +0 -1
  141. package/dist/chunk-3WVH2CTK.js.map +0 -1
  142. package/dist/chunk-7ANJQLPJ.js +0 -35
  143. package/dist/chunk-7ANJQLPJ.js.map +0 -1
  144. package/dist/chunk-ECURHW3T.js.map +0 -1
  145. package/dist/chunk-FLW25ORT.js +0 -130
  146. package/dist/chunk-FLW25ORT.js.map +0 -1
  147. package/dist/chunk-G2JJ5BO5.js.map +0 -1
  148. package/dist/chunk-J27W6TXS.js.map +0 -1
  149. package/dist/chunk-JCPANDNR.js +0 -35
  150. package/dist/chunk-JCPANDNR.js.map +0 -1
  151. package/dist/chunk-JIERYE6B.js +0 -480
  152. package/dist/chunk-JIERYE6B.js.map +0 -1
  153. package/dist/chunk-JU4ZGJ7A.js +0 -668
  154. package/dist/chunk-JU4ZGJ7A.js.map +0 -1
  155. package/dist/chunk-K5ITEA3I.js.map +0 -1
  156. package/dist/chunk-LT5GXEB2.js.map +0 -1
  157. package/dist/chunk-NKGYKOB7.js.map +0 -1
  158. package/dist/chunk-NQXO4LR3.js.map +0 -1
  159. package/dist/chunk-NTF4UXGK.js.map +0 -1
  160. package/dist/chunk-UITEKY2R.js.map +0 -1
  161. package/dist/chunk-X37UYIE3.js.map +0 -1
  162. package/dist/chunk-X4SSJZLY.js.map +0 -1
  163. package/dist/chunk-XPDVAJDH.js.map +0 -1
  164. package/dist/chunk-XVAQG2CV.js.map +0 -1
  165. package/dist/chunk-ZBPEFQ4G.js +0 -12
  166. package/dist/chunk-ZBPEFQ4G.js.map +0 -1
  167. package/dist/chunk-ZVXGW2IC.js.map +0 -1
  168. package/dist/load-agent-defs-7IM3GBEQ.js +0 -13
  169. package/dist/recompile-project-agents-VR4NLGOU.js +0 -17
  170. /package/dist/{chunk-ARYNOWKF.js.map → chunk-4NDBUJHK.js.map} +0 -0
  171. /package/dist/{chunk-S63LXWMC.js.map → chunk-6L75SQZU.js.map} +0 -0
  172. /package/dist/{chunk-7REKOTKG.js.map → chunk-6LF4CQBY.js.map} +0 -0
  173. /package/dist/{chunk-XHPWTL4I.js.map → chunk-DAIEQJIF.js.map} +0 -0
  174. /package/dist/{chunk-RGYDFBCT.js.map → chunk-HHIYT3PI.js.map} +0 -0
  175. /package/dist/{chunk-DSL66I4N.js.map → chunk-LFWLEDUG.js.map} +0 -0
  176. /package/dist/{chunk-B3Y3QMWI.js.map → chunk-MAWNVT2C.js.map} +0 -0
  177. /package/dist/{chunk-Q4IRRVXK.js.map → chunk-MPTNC32T.js.map} +0 -0
  178. /package/dist/{chunk-B6FSLXYN.js.map → chunk-PMJWD27C.js.map} +0 -0
  179. /package/dist/{chunk-XATLUKXK.js.map → chunk-PNB3524H.js.map} +0 -0
  180. /package/dist/{chunk-4VL3CEOS.js.map → chunk-QHDPLLK6.js.map} +0 -0
  181. /package/dist/{chunk-AOTCIUGX.js.map → chunk-RAHSOJQ2.js.map} +0 -0
  182. /package/dist/{chunk-PHCPUQVR.js.map → chunk-RZDXDMT6.js.map} +0 -0
  183. /package/dist/{chunk-6MV4JOCL.js.map → chunk-SAYZWCH6.js.map} +0 -0
  184. /package/dist/{chunk-URXIQE7I.js.map → chunk-SCOQ34YM.js.map} +0 -0
  185. /package/dist/{chunk-QZX7SYKU.js.map → chunk-T6UWJAB3.js.map} +0 -0
  186. /package/dist/{chunk-YRS3QZP2.js.map → chunk-TTY6DPJN.js.map} +0 -0
  187. /package/dist/{chunk-ABAZMMG4.js.map → chunk-WVUDLKIO.js.map} +0 -0
  188. /package/dist/{chunk-V5GLL2JB.js.map → chunk-XZEVWPX6.js.map} +0 -0
  189. /package/dist/{load-agent-defs-7IM3GBEQ.js.map → load-agent-defs-3C27ENC7.js.map} +0 -0
  190. /package/dist/{recompile-project-agents-VR4NLGOU.js.map → recompile-project-agents-DGNBCU3N.js.map} +0 -0
  191. /package/dist/{source-loader-GRBVNRAW.js.map → source-loader-7KZ3MD5O.js.map} +0 -0
@@ -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-X37UYIE3.js";
6
- import "../chunk-2CQNPLVF.js";
7
- import "../chunk-B6FSLXYN.js";
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-JU4ZGJ7A.js";
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-JIERYE6B.js";
17
- import "../chunk-NKGYKOB7.js";
18
- import "../chunk-PHCPUQVR.js";
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
- loadProjectConfigFromDir,
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-XPDVAJDH.js";
93
+ } from "../chunk-A4KDXNOT.js";
70
94
  import {
71
95
  matrix
72
- } from "../chunk-AVK2HEKB.js";
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-UITEKY2R.js";
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 loadProjectSourceConfig(dir);
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(skillsDir, skillsDirRelPath, skillDir) {
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(skillsDir, skillDir, STANDARD_FILES.SKILL_MD));
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, skillsDir) {
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(skillsDir, dir),
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(skillsDir, dir),
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
- issues.push({
169
- severity: "error",
170
- file: resolvedPath,
171
- message: "Marketplace directory does not exist"
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 loadProjectSourceConfig(resolvedPath);
194
+ const sourceProjectConfig = await loadSourceRepoConfig(resolvedPath);
176
195
  const skillsDirRelPath = sourceProjectConfig?.skillsDir ?? SKILLS_DIR_PATH;
177
- const skillsDir = path.join(resolvedPath, skillsDirRelPath);
178
- if (!await directoryExists(skillsDir)) {
179
- issues.push({
180
- severity: "error",
181
- file: skillsDir,
182
- message: "Skills directory does not exist"
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}`, skillsDir);
187
- const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir);
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
- issues.push(...validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir));
207
+ const pairIssues = validateSkillFilePairs(skillMdDirs, metadataDirs, skillsDir2);
191
208
  const validMetadataFiles = metadataFiles.filter((f) => skillMdDirs.has(path.dirname(f)));
192
- for (const metadataFile of validMetadataFiles) {
193
- issues.push(...await validateOneSkill(skillsDir, skillsDirRelPath, metadataFile));
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
- issues.push(...extraIssues.flat());
202
- return buildResult(issues, validMetadataFiles.length);
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(skillsDir, skillsDirRelPath, metadataFile) {
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(skillsDir, metadataFile)));
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(skillsDir, skillsDirRelPath, path.dirname(metadataFile))
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(opts) {
364
+ async function validateYamlFiles(scan) {
330
365
  const issues = [];
331
- const files = await glob(opts.pattern, opts.baseDir);
366
+ const files = await glob(scan.pattern, scan.baseDir);
332
367
  for (const relFile of files) {
333
- const absPath = path.join(opts.baseDir, relFile);
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(getProjectConfigPath(dir)))
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} skills`, issues: result.issues };
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(configDirsInPlay(projectDir).map(loadProjectConfigFromDir));
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(skillsDir) {
573
- const names = await listDirectories(skillsDir);
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(skillsDir, name) }))
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(skillsDir, configuredIds) {
589
- if (!await directoryExists(skillsDir)) return NOTHING_VALIDATED;
590
- const skillDirs = await listDirectories(skillsDir);
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(skillsDir, name, configuredIds)
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(skillsDir, name);
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(skillsDir, name))
644
+ notes: foreign.map(({ name }) => foreignSkillNote(skillsDir2, name))
609
645
  };
610
646
  }
611
- async function isOurSkillDirectory(skillsDir, name, configuredIds) {
647
+ async function isOurSkillDirectory(skillsDir2, name, configuredIds) {
612
648
  if (configuredIds.has(name)) return true;
613
- return carriesOurProvenance(skillsDir, name);
649
+ return carriesOurProvenance(skillsDir2, name);
614
650
  }
615
- async function carriesOurProvenance(skillsDir, name) {
616
- return await readForkedFromMetadata(path2.join(skillsDir, name)) !== null;
651
+ async function carriesOurProvenance(skillsDir2, name) {
652
+ return await readForkedFromMetadata(path2.join(skillsDir2, name)) !== null;
617
653
  }
618
- function foreignSkillNote(skillsDir, name) {
619
- return `${displayDir(path2.join(skillsDir, name))} \u2014 not installed by this CLI and named by no configuration here: not validated`;
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 skillMdPath = path2.join(skillDir, STANDARD_FILES.SKILL_MD);
635
- const metadataPath = path2.join(skillDir, STANDARD_FILES.METADATA_YAML);
636
- const errors = [];
637
- const warnings = [];
638
- if (!await fileExists(skillMdPath)) {
639
- errors.push(`Missing ${STANDARD_FILES.SKILL_MD}`);
640
- } else {
641
- const frontmatterResult = await validateSkillFrontmatter(skillMdPath);
642
- errors.push(...frontmatterResult.errors);
643
- warnings.push(...frontmatterResult.warnings);
644
- }
645
- if (!await fileExists(metadataPath)) {
646
- errors.push(`Missing ${STANDARD_FILES.METADATA_YAML}`);
647
- } else {
648
- const metadataResult = await validateInstalledSkillMetadata(metadataPath);
649
- errors.push(...metadataResult.errors);
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 { valid: true, errors: [], warnings: [] };
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) return { kind: "loaded", config: detected.config };
681
- const loaded = await loadProjectConfigFromDir(projectDir);
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: `${CONFIG_TS_REL} not found`,
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: `${CONFIG_TS_REL} is valid but declares no skills and no agents`
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: `${CONFIG_TS_REL} has errors`,
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: `${CONFIG_TS_REL} has warnings`,
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: `${CONFIG_TS_REL} is valid`
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}/${uniqueSkills.length} skills found`,
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}/${uniqueSkills.length} skills found`
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 projectAgentsDir = resolveInstallPaths(projectDir, "project").agentsDir;
788
- const globalAgentsDir = resolveInstallPaths(projectDir, "global").agentsDir;
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(async (agent) => {
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 missingAgents = agentChecks.filter((c) => !c.compiled).map((c) => c.name);
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: `${agents.length}/${agents.length} agents compiled`
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 listAgentMdFiles(projectAgentsDir) : [];
827
- const globalMdFiles = globalExists ? await listAgentMdFiles(globalAgentsDir) : [];
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) => fileName.replace(/\.md$/, "")).filter((agentName) => !knownAgents.has(agentName));
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(async (skill) => {
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).map((c) => c.id);
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 ${LOCAL_SKILLS_PATH}/)`)
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}/${ejectSkills.length} eject-mode skills installed`
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}/${pluginSkills.length} plugin-mode skills installed`
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 = Object.keys(matrix.skills).length;
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 ?? []).map(
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(getProjectConfigPath(projectDir))) return false;
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) {