agents-inc 0.154.0 → 0.156.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 (213) hide show
  1. package/CHANGELOG.md +55 -0
  2. package/dist/chunk-2H7UWE2G.js +246 -0
  3. package/dist/chunk-2H7UWE2G.js.map +1 -0
  4. package/dist/{chunk-LA3JPESW.js → chunk-4A4L3YIW.js} +5 -5
  5. package/dist/chunk-4G2YHZIM.js +132 -0
  6. package/dist/chunk-4G2YHZIM.js.map +1 -0
  7. package/dist/{chunk-BZ4CZBOM.js → chunk-5F2YR5XV.js} +3 -3
  8. package/dist/{chunk-CHNA5RVS.js → chunk-5OKLCVM4.js} +3 -3
  9. package/dist/{chunk-ZU6DAR4S.js → chunk-5QZAK6J7.js} +7 -7
  10. package/dist/{chunk-ZU6DAR4S.js.map → chunk-5QZAK6J7.js.map} +1 -1
  11. package/dist/{chunk-NFH6SA62.js → chunk-6RLV45HN.js} +10 -10
  12. package/dist/{chunk-NFH6SA62.js.map → chunk-6RLV45HN.js.map} +1 -1
  13. package/dist/{chunk-GUDGCFSF.js → chunk-CLFHC2DK.js} +15 -106
  14. package/dist/chunk-CLFHC2DK.js.map +1 -0
  15. package/dist/chunk-D2CAV7MG.js +61 -0
  16. package/dist/chunk-D2CAV7MG.js.map +1 -0
  17. package/dist/{chunk-UW44KSYL.js → chunk-F2AMBBIA.js} +6 -6
  18. package/dist/{chunk-UW44KSYL.js.map → chunk-F2AMBBIA.js.map} +1 -1
  19. package/dist/{chunk-QQTYMW7N.js → chunk-F5T6TV4Z.js} +206 -73
  20. package/dist/chunk-F5T6TV4Z.js.map +1 -0
  21. package/dist/{chunk-K3O3YQYG.js → chunk-G47TDUK3.js} +2 -2
  22. package/dist/{chunk-AURY6RT6.js → chunk-GS2NZ3GD.js} +8 -8
  23. package/dist/chunk-GS2NZ3GD.js.map +1 -0
  24. package/dist/{chunk-5MFJ24VM.js → chunk-H6UTKTLK.js} +104 -250
  25. package/dist/chunk-H6UTKTLK.js.map +1 -0
  26. package/dist/{chunk-MJFIO5SX.js → chunk-HG4JPXJT.js} +11 -11
  27. package/dist/chunk-HG4JPXJT.js.map +1 -0
  28. package/dist/{chunk-IXEDSKMH.js → chunk-HTYH7CKN.js} +3 -3
  29. package/dist/{chunk-JC7DBPRD.js → chunk-HXKOIWLU.js} +2 -2
  30. package/dist/chunk-HXKOIWLU.js.map +1 -0
  31. package/dist/{chunk-NSJ2S7R5.js → chunk-ILUVL7WM.js} +2 -2
  32. package/dist/{chunk-R54KIGN2.js → chunk-IO5YBKHV.js} +13 -2
  33. package/dist/chunk-IO5YBKHV.js.map +1 -0
  34. package/dist/chunk-IXRZAHZF.js +53 -0
  35. package/dist/chunk-IXRZAHZF.js.map +1 -0
  36. package/dist/chunk-JCPANDNR.js +35 -0
  37. package/dist/chunk-JCPANDNR.js.map +1 -0
  38. package/dist/{chunk-NKCY2ULI.js → chunk-LJCNUP6M.js} +2 -2
  39. package/dist/{chunk-PRJD5MMQ.js → chunk-MHRC3KRX.js} +5 -5
  40. package/dist/chunk-MHRC3KRX.js.map +1 -0
  41. package/dist/chunk-MO5FGLVS.js +142 -0
  42. package/dist/chunk-MO5FGLVS.js.map +1 -0
  43. package/dist/{chunk-Q5FLW4JG.js → chunk-OKH64JZE.js} +2 -2
  44. package/dist/{chunk-WRRP47PY.js → chunk-PCC4LBKB.js} +2 -2
  45. package/dist/chunk-PMUABBPG.js +258 -0
  46. package/dist/chunk-PMUABBPG.js.map +1 -0
  47. package/dist/{chunk-2IMYK4WN.js → chunk-PNMM5HNX.js} +5 -5
  48. package/dist/{chunk-AKQ5QISO.js → chunk-QWVXOCXO.js} +6 -6
  49. package/dist/{chunk-IPJOMAWC.js → chunk-RWOYPOMR.js} +12 -2
  50. package/dist/{chunk-IPJOMAWC.js.map → chunk-RWOYPOMR.js.map} +1 -1
  51. package/dist/{chunk-ONWEOS77.js → chunk-SY4X2LND.js} +4 -4
  52. package/dist/{chunk-XGMOXMS4.js → chunk-TBOVYLQJ.js} +2 -2
  53. package/dist/{chunk-R7JV22QK.js → chunk-TNGMF4UX.js} +20 -20
  54. package/dist/{chunk-R7JV22QK.js.map → chunk-TNGMF4UX.js.map} +1 -1
  55. package/dist/{chunk-7VZV4O6B.js → chunk-UMH2YZKG.js} +14 -5
  56. package/dist/chunk-UMH2YZKG.js.map +1 -0
  57. package/dist/{chunk-6CNRMCFM.js → chunk-VPASBA7P.js} +2 -2
  58. package/dist/{chunk-CBF4ZYV3.js → chunk-WCAUUBHO.js} +1376 -741
  59. package/dist/chunk-WCAUUBHO.js.map +1 -0
  60. package/dist/{chunk-V3ENMW7G.js → chunk-WFRTHH3A.js} +3 -3
  61. package/dist/{chunk-DRKSYJZ2.js → chunk-WTJ3AY33.js} +1 -1
  62. package/dist/{chunk-DRKSYJZ2.js.map → chunk-WTJ3AY33.js.map} +1 -1
  63. package/dist/{chunk-XMEIVWAS.js → chunk-WUQ3MTPF.js} +11 -5
  64. package/dist/{chunk-XMEIVWAS.js.map → chunk-WUQ3MTPF.js.map} +1 -1
  65. package/dist/{chunk-4VCWL2IG.js → chunk-XAXJWXI4.js} +92 -59
  66. package/dist/chunk-XAXJWXI4.js.map +1 -0
  67. package/dist/{chunk-B66SC72V.js → chunk-Y5Y7XHVU.js} +4 -4
  68. package/dist/{chunk-FMYHWKGG.js → chunk-YO2LTXB4.js} +66 -16
  69. package/dist/chunk-YO2LTXB4.js.map +1 -0
  70. package/dist/{chunk-TUQF577Z.js → chunk-ZUAZHDKP.js} +40 -41
  71. package/dist/chunk-ZUAZHDKP.js.map +1 -0
  72. package/dist/commands/build/marketplace.js +98 -121
  73. package/dist/commands/build/marketplace.js.map +1 -1
  74. package/dist/commands/build/plugins.js +8 -8
  75. package/dist/commands/compile.js +19 -24
  76. package/dist/commands/compile.js.map +1 -1
  77. package/dist/commands/doctor.js +220 -79
  78. package/dist/commands/doctor.js.map +1 -1
  79. package/dist/commands/edit.js +549 -75
  80. package/dist/commands/edit.js.map +1 -1
  81. package/dist/commands/eject.js +26 -21
  82. package/dist/commands/eject.js.map +1 -1
  83. package/dist/commands/init.js +41 -39
  84. package/dist/commands/list.js +11 -11
  85. package/dist/commands/list.js.map +1 -1
  86. package/dist/commands/new/marketplace.js +243 -0
  87. package/dist/commands/new/marketplace.js.map +1 -0
  88. package/dist/commands/search.js +16 -14
  89. package/dist/commands/search.js.map +1 -1
  90. package/dist/commands/share.js +67 -0
  91. package/dist/commands/share.js.map +1 -0
  92. package/dist/commands/uninstall.js +61 -74
  93. package/dist/commands/uninstall.js.map +1 -1
  94. package/dist/commands/update.js +9 -9
  95. package/dist/commands/update.js.map +1 -1
  96. package/dist/components/common/prompt-confirm.js +1 -1
  97. package/dist/components/common/removal-plan-confirm.js +10 -0
  98. package/dist/components/common/select-list.js +2 -2
  99. package/dist/components/wizard/category-grid.js +5 -5
  100. package/dist/components/wizard/checkbox-grid.js +5 -5
  101. package/dist/components/wizard/domain-selection.js +11 -11
  102. package/dist/components/wizard/run-wizard-session.js +29 -28
  103. package/dist/components/wizard/scroll-affordance.js +2 -2
  104. package/dist/components/wizard/skill-agent-summary.js +6 -6
  105. package/dist/components/wizard/source-grid.js +6 -6
  106. package/dist/components/wizard/stack-selection.js +8 -8
  107. package/dist/components/wizard/step-agents.js +10 -11
  108. package/dist/components/wizard/step-build.js +9 -9
  109. package/dist/components/wizard/step-confirm.js +10 -10
  110. package/dist/components/wizard/step-sources.js +10 -10
  111. package/dist/components/wizard/step-stack.js +9 -9
  112. package/dist/components/wizard/summary-panel.js +9 -9
  113. package/dist/components/wizard/toast.js +2 -2
  114. package/dist/components/wizard/wizard-layout.js +14 -14
  115. package/dist/components/wizard/wizard-tabs.js +2 -2
  116. package/dist/components/wizard/wizard.js +27 -27
  117. package/dist/copy-local-skills-NSUTBVLJ.js +14 -0
  118. package/dist/hooks/init.js +42 -62
  119. package/dist/hooks/init.js.map +1 -1
  120. package/dist/load-agent-defs-X4E57I2U.js +15 -0
  121. package/dist/{loader-JIZDTR4G.js → loader-G6BFG2VA.js} +3 -3
  122. package/dist/recompile-project-agents-JE4MM72I.js +20 -0
  123. package/dist/{source-loader-OE6EJFPU.js → source-loader-3DL3NYUX.js} +7 -5
  124. package/dist/source-loader-3DL3NYUX.js.map +1 -0
  125. package/dist/src/agents/developer/ai-developer/critical-reminders.md +1 -1
  126. package/dist/src/agents/developer/ai-developer/critical-requirements.md +1 -1
  127. package/dist/src/agents/developer/ai-developer/identity.md +1 -1
  128. package/dist/src/agents/developer/ai-developer/playbook.md +1 -1
  129. package/dist/src/agents/developer/api-developer/identity.md +1 -1
  130. package/dist/src/agents/developer/cli-developer/identity.md +1 -1
  131. package/dist/src/agents/developer/web-developer/identity.md +1 -1
  132. package/dist/src/agents/meta/codex-keeper/identity.md +1 -1
  133. package/dist/src/agents/meta/convention-keeper/identity.md +1 -1
  134. package/dist/src/agents/meta/skill-summoner/identity.md +1 -1
  135. package/dist/src/agents/researcher/ai-researcher/identity.md +1 -1
  136. package/dist/src/agents/researcher/api-researcher/identity.md +1 -1
  137. package/dist/src/agents/researcher/cli-researcher/identity.md +1 -1
  138. package/dist/src/agents/researcher/web-researcher/identity.md +1 -1
  139. package/dist/src/agents/reviewer/reviewer/playbook.md +1 -1
  140. package/dist/src/agents/tester/ai-tester/identity.md +1 -1
  141. package/dist/src/agents/tester/api-tester/critical-reminders.md +1 -1
  142. package/dist/src/agents/tester/api-tester/critical-requirements.md +1 -1
  143. package/dist/src/agents/tester/api-tester/identity.md +2 -2
  144. package/dist/src/agents/tester/cli-tester/identity.md +1 -1
  145. package/dist/src/agents/tester/web-tester/identity.md +3 -3
  146. package/dist/stores/wizard-store.js +7 -5
  147. package/package.json +10 -7
  148. package/src/agents/developer/ai-developer/critical-reminders.md +1 -1
  149. package/src/agents/developer/ai-developer/critical-requirements.md +1 -1
  150. package/src/agents/developer/ai-developer/identity.md +1 -1
  151. package/src/agents/developer/ai-developer/playbook.md +1 -1
  152. package/src/agents/developer/api-developer/identity.md +1 -1
  153. package/src/agents/developer/cli-developer/identity.md +1 -1
  154. package/src/agents/developer/web-developer/identity.md +1 -1
  155. package/src/agents/meta/codex-keeper/identity.md +1 -1
  156. package/src/agents/meta/convention-keeper/identity.md +1 -1
  157. package/src/agents/meta/skill-summoner/identity.md +1 -1
  158. package/src/agents/researcher/ai-researcher/identity.md +1 -1
  159. package/src/agents/researcher/api-researcher/identity.md +1 -1
  160. package/src/agents/researcher/cli-researcher/identity.md +1 -1
  161. package/src/agents/researcher/web-researcher/identity.md +1 -1
  162. package/src/agents/reviewer/reviewer/playbook.md +1 -1
  163. package/src/agents/tester/ai-tester/identity.md +1 -1
  164. package/src/agents/tester/api-tester/critical-reminders.md +1 -1
  165. package/src/agents/tester/api-tester/critical-requirements.md +1 -1
  166. package/src/agents/tester/api-tester/identity.md +2 -2
  167. package/src/agents/tester/cli-tester/identity.md +1 -1
  168. package/src/agents/tester/web-tester/identity.md +3 -3
  169. package/src/schemas/custom-metadata.schema.json +3 -0
  170. package/src/schemas/metadata.schema.json +3 -0
  171. package/src/schemas/project-config.schema.json +4 -8
  172. package/src/schemas/project-source-config.schema.json +2 -2
  173. package/dist/chunk-4VCWL2IG.js.map +0 -1
  174. package/dist/chunk-5MFJ24VM.js.map +0 -1
  175. package/dist/chunk-7VZV4O6B.js.map +0 -1
  176. package/dist/chunk-AURY6RT6.js.map +0 -1
  177. package/dist/chunk-CBF4ZYV3.js.map +0 -1
  178. package/dist/chunk-FMYHWKGG.js.map +0 -1
  179. package/dist/chunk-GUDGCFSF.js.map +0 -1
  180. package/dist/chunk-JC7DBPRD.js.map +0 -1
  181. package/dist/chunk-MJFIO5SX.js.map +0 -1
  182. package/dist/chunk-OJJLYHM3.js +0 -34
  183. package/dist/chunk-OJJLYHM3.js.map +0 -1
  184. package/dist/chunk-PRJD5MMQ.js.map +0 -1
  185. package/dist/chunk-QQTYMW7N.js.map +0 -1
  186. package/dist/chunk-QUDQTO3O.js +0 -41
  187. package/dist/chunk-QUDQTO3O.js.map +0 -1
  188. package/dist/chunk-R54KIGN2.js.map +0 -1
  189. package/dist/chunk-TUQF577Z.js.map +0 -1
  190. package/dist/copy-local-skills-WVHJFRPK.js +0 -14
  191. package/dist/load-agent-defs-5OT54J5M.js +0 -15
  192. package/dist/recompile-project-agents-VREEPBDV.js +0 -20
  193. /package/dist/{chunk-LA3JPESW.js.map → chunk-4A4L3YIW.js.map} +0 -0
  194. /package/dist/{chunk-BZ4CZBOM.js.map → chunk-5F2YR5XV.js.map} +0 -0
  195. /package/dist/{chunk-CHNA5RVS.js.map → chunk-5OKLCVM4.js.map} +0 -0
  196. /package/dist/{chunk-K3O3YQYG.js.map → chunk-G47TDUK3.js.map} +0 -0
  197. /package/dist/{chunk-IXEDSKMH.js.map → chunk-HTYH7CKN.js.map} +0 -0
  198. /package/dist/{chunk-NSJ2S7R5.js.map → chunk-ILUVL7WM.js.map} +0 -0
  199. /package/dist/{chunk-NKCY2ULI.js.map → chunk-LJCNUP6M.js.map} +0 -0
  200. /package/dist/{chunk-Q5FLW4JG.js.map → chunk-OKH64JZE.js.map} +0 -0
  201. /package/dist/{chunk-WRRP47PY.js.map → chunk-PCC4LBKB.js.map} +0 -0
  202. /package/dist/{chunk-2IMYK4WN.js.map → chunk-PNMM5HNX.js.map} +0 -0
  203. /package/dist/{chunk-AKQ5QISO.js.map → chunk-QWVXOCXO.js.map} +0 -0
  204. /package/dist/{chunk-ONWEOS77.js.map → chunk-SY4X2LND.js.map} +0 -0
  205. /package/dist/{chunk-XGMOXMS4.js.map → chunk-TBOVYLQJ.js.map} +0 -0
  206. /package/dist/{chunk-6CNRMCFM.js.map → chunk-VPASBA7P.js.map} +0 -0
  207. /package/dist/{chunk-V3ENMW7G.js.map → chunk-WFRTHH3A.js.map} +0 -0
  208. /package/dist/{chunk-B66SC72V.js.map → chunk-Y5Y7XHVU.js.map} +0 -0
  209. /package/dist/{copy-local-skills-WVHJFRPK.js.map → components/common/removal-plan-confirm.js.map} +0 -0
  210. /package/dist/{load-agent-defs-5OT54J5M.js.map → copy-local-skills-NSUTBVLJ.js.map} +0 -0
  211. /package/dist/{loader-JIZDTR4G.js.map → load-agent-defs-X4E57I2U.js.map} +0 -0
  212. /package/dist/{recompile-project-agents-VREEPBDV.js.map → loader-G6BFG2VA.js.map} +0 -0
  213. /package/dist/{source-loader-OE6EJFPU.js.map → recompile-project-agents-JE4MM72I.js.map} +0 -0
@@ -1,11 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  import {
3
- AGENT_NAMES,
4
- DOMAINS,
5
3
  EFFORT_NAMES,
6
4
  GlobalPairWriteViolation,
7
5
  MODEL_NAMES,
8
- SKILL_IDS,
9
6
  agentFrontmatterValidationSchema,
10
7
  assertGateToken,
11
8
  copy,
@@ -17,7 +14,11 @@ import {
17
14
  formatZodIssues,
18
15
  getErrorMessage,
19
16
  glob,
17
+ isAgentName,
20
18
  isPathWithin,
19
+ isRecord,
20
+ isSkillAssignment,
21
+ isSkillId,
21
22
  listDirectories,
22
23
  loadAllAgents,
23
24
  loadPluginSkills,
@@ -48,15 +49,17 @@ import {
48
49
  warnUnknownFields,
49
50
  withGateToken,
50
51
  writeFile
51
- } from "./chunk-4VCWL2IG.js";
52
+ } from "./chunk-XAXJWXI4.js";
52
53
  import {
53
54
  BUILT_IN_MATRIX,
54
55
  allSkills,
56
+ byCategoryDeclarationOrder,
57
+ getCategoryDomain,
55
58
  getSkillById,
56
59
  hasSkill,
57
60
  initializeMatrix,
58
61
  matrix
59
- } from "./chunk-IPJOMAWC.js";
62
+ } from "./chunk-RWOYPOMR.js";
60
63
  import {
61
64
  typedEntries,
62
65
  typedFromEntries,
@@ -94,6 +97,7 @@ import {
94
97
  PLUGIN_MANIFEST_DIR,
95
98
  PLUGIN_MANIFEST_FILE,
96
99
  PROJECT_ROOT,
100
+ PUBLIC_CATALOGUE_PACKAGE,
97
101
  SCHEMA_PATHS,
98
102
  SKILLS_DIR_PATH,
99
103
  SKILL_CATEGORIES_PATH,
@@ -103,8 +107,9 @@ import {
103
107
  STANDARD_DIRS,
104
108
  STANDARD_FILES,
105
109
  YAML_FORMATTING,
110
+ editorConfigUrl,
106
111
  marketplaceManifestPath
107
- } from "./chunk-R54KIGN2.js";
112
+ } from "./chunk-IO5YBKHV.js";
108
113
  import {
109
114
  defaultCategories,
110
115
  defaultRules,
@@ -113,8 +118,8 @@ import {
113
118
 
114
119
  // src/cli/lib/loading/source-loader.ts
115
120
  import os15 from "os";
116
- import { unique as unique5 } from "remeda";
117
- import path26 from "path";
121
+ import { unique as unique7 } from "remeda";
122
+ import path28 from "path";
118
123
 
119
124
  // src/cli/lib/installation/is-home-directory.ts
120
125
  import fs from "fs";
@@ -137,17 +142,15 @@ var METADATA_KEYS = {
137
142
  USAGE_GUIDANCE: "usageGuidance"
138
143
  };
139
144
  var LOCAL_DEFAULTS = {
140
- // Boundary cast: deliberately outside the generated Category union — scaffold
141
- // placeholder written into starter files for the user to replace
142
- CATEGORY: "dummy-category",
143
- AUTHOR: "@dummy-author",
144
- DOMAIN: "dummy"
145
+ AUTHOR: "@dummy-author"
145
146
  };
146
147
  var SKILL_CONTENT_FILES = [STANDARD_FILES.SKILL_MD, STANDARD_FILES.REFERENCE_MD];
147
148
  var SKILL_CONTENT_DIRS = [STANDARD_DIRS.EXAMPLES, STANDARD_DIRS.SCRIPTS];
148
149
 
149
150
  // src/cli/lib/configuration/config.ts
150
151
  import os3 from "os";
152
+ import path3 from "path";
153
+ import { z } from "zod";
151
154
 
152
155
  // src/cli/lib/configuration/config-loader.ts
153
156
  import path from "path";
@@ -157,12 +160,41 @@ var CONFIG_EXPORTS_PATH = path.resolve(
157
160
  path.dirname(fileURLToPath(import.meta.url)),
158
161
  "../../config-exports.ts"
159
162
  );
163
+ var ConfigSchemaError = class extends Error {
164
+ constructor(configPath, issues) {
165
+ super(`Config validation failed at '${configPath}': ${issues}`);
166
+ this.configPath = configPath;
167
+ this.issues = issues;
168
+ this.name = "ConfigSchemaError";
169
+ }
170
+ configPath;
171
+ issues;
172
+ };
173
+ var ConfigDefaultExportError = class extends Error {
174
+ constructor(configPath) {
175
+ super(`Config at '${configPath}' has no default export`);
176
+ this.configPath = configPath;
177
+ this.name = "ConfigDefaultExportError";
178
+ }
179
+ configPath;
180
+ };
181
+ var ES_MODULE_MARKER = "__esModule";
182
+ function unwrapDefaultExport(namespace) {
183
+ if (isRecord(namespace) && namespace.default !== void 0) return namespace.default;
184
+ return namespace;
185
+ }
186
+ function declaresNoDefaultExport(namespace) {
187
+ return isRecord(namespace) && ES_MODULE_MARKER in namespace && !("default" in namespace);
188
+ }
189
+ function exportsNothing(value) {
190
+ return value == null || typeof value === "object" && Object.keys(value).length === 0;
191
+ }
160
192
  async function loadConfig(configPath, schema) {
161
193
  if (!await fileExists(configPath)) {
162
194
  verbose(`Config not found at ${configPath}`);
163
195
  return null;
164
196
  }
165
- let raw;
197
+ let namespace;
166
198
  try {
167
199
  const jiti = createJiti(import.meta.url, {
168
200
  moduleCache: false,
@@ -177,22 +209,24 @@ async function loadConfig(configPath, schema) {
177
209
  "@agents-inc/cli/config": CONFIG_EXPORTS_PATH
178
210
  }
179
211
  });
180
- raw = await jiti.import(configPath, { default: true });
212
+ namespace = await jiti.import(configPath);
181
213
  } catch (error) {
182
214
  throw new Error(`Failed to load config from '${configPath}': ${getErrorMessage(error)}`, {
183
215
  cause: error
184
216
  });
185
217
  }
186
- if (raw == null || typeof raw === "object" && Object.keys(raw).length === 0) {
187
- verbose(`Config at ${configPath} has no default export`);
218
+ const raw = unwrapDefaultExport(namespace);
219
+ if (exportsNothing(raw)) {
220
+ verbose(`Config at ${configPath} exports nothing`);
188
221
  return null;
189
222
  }
223
+ if (declaresNoDefaultExport(namespace)) {
224
+ throw new ConfigDefaultExportError(configPath);
225
+ }
190
226
  if (schema) {
191
227
  const result = schema.safeParse(raw);
192
228
  if (!result.success) {
193
- throw new Error(
194
- `Config validation failed at '${configPath}': ${formatZodIssues(result.error.issues)}`
195
- );
229
+ throw new ConfigSchemaError(configPath, formatZodIssues(result.error.issues));
196
230
  }
197
231
  return result.data;
198
232
  }
@@ -219,7 +253,7 @@ function resolveInstallPaths(projectDir, scope = "project") {
219
253
 
220
254
  // src/cli/lib/configuration/config.ts
221
255
  var DEFAULT_SOURCE = `${GITHUB_SOURCE.GITHUB_PREFIX}agents-inc/skills`;
222
- var SOURCE_ENV_VAR = "CC_SOURCE";
256
+ var SOURCE_ENV_VAR = "CC_MARKETPLACE";
223
257
  async function loadSourceConfig(dir, scope) {
224
258
  const scopeLabel = scope === "project" ? "Project" : "Global";
225
259
  const configPath = getProjectConfigPath(dir);
@@ -231,7 +265,9 @@ async function loadSourceConfig(dir, scope) {
231
265
  try {
232
266
  data = await loadConfig(configPath, projectSourceConfigSchema);
233
267
  } catch (error) {
234
- verbose(`Failed to load ${scope} source config at ${configPath}: ${getErrorMessage(error)}`);
268
+ if (error instanceof ConfigSchemaError || error instanceof ConfigDefaultExportError)
269
+ throw error;
270
+ verbose(`Failed to load ${scope} config at ${configPath}: ${getErrorMessage(error)}`);
235
271
  return null;
236
272
  }
237
273
  if (!data) return null;
@@ -258,33 +294,33 @@ async function loadOwnProjectSourceConfig(projectDir) {
258
294
  async function resolveSource(request) {
259
295
  const { caller, flag, projectDir } = request;
260
296
  const effective = await loadEffectiveSourceConfig(projectDir);
261
- const marketplace = effective?.config.marketplace;
262
- const marketplaceLabel = marketplace !== void 0 && { marketplace };
297
+ const marketplaceName = effective?.config.marketplaceName;
298
+ const marketplaceLabel = marketplaceName !== void 0 && { marketplace: marketplaceName };
263
299
  if (flag !== void 0) {
264
300
  assertNamedSourceUsable(flag);
265
- verbose(`Source named by this run: ${flag}`);
301
+ verbose(`Marketplace named by this run: ${flag}`);
266
302
  return { source: flag, sourceOrigin: "flag", ...marketplaceLabel };
267
303
  }
268
304
  const envSource = caller === "init" ? readEnvSource() : void 0;
269
305
  if (envSource !== void 0) {
270
306
  return { source: envSource, sourceOrigin: "env", ...marketplaceLabel };
271
307
  }
272
- if (effective?.config.source) {
273
- verbose(`Source from ${effective.origin} config: ${effective.config.source}`);
308
+ if (effective?.config.marketplace) {
309
+ verbose(`Marketplace from ${effective.origin} config: ${effective.config.marketplace}`);
274
310
  return {
275
- source: effective.config.source,
311
+ source: effective.config.marketplace,
276
312
  sourceOrigin: effective.origin,
277
313
  ...marketplaceLabel
278
314
  };
279
315
  }
280
- verbose(`Using default source: ${DEFAULT_SOURCE}`);
316
+ verbose(`Using default marketplace: ${DEFAULT_SOURCE}`);
281
317
  return { source: DEFAULT_SOURCE, sourceOrigin: "default", ...marketplaceLabel };
282
318
  }
283
- var NAMED_SOURCE_LABEL = "The source";
319
+ var NAMED_SOURCE_LABEL = "The marketplace";
284
320
  function assertNamedSourceUsable(flag) {
285
321
  if (flag.trim() === "") {
286
322
  throw new Error(
287
- `${NAMED_SOURCE_LABEL} cannot be empty. Provide a valid source: a local directory path or a git repository URL (e.g., './my-skills' or 'https://github.com/user/repo')`
323
+ `${NAMED_SOURCE_LABEL} cannot be empty. Provide a valid marketplace: a local directory path or a git repository URL (e.g., './my-skills' or 'https://github.com/user/repo')`
288
324
  );
289
325
  }
290
326
  validateSourceFormat(flag.trim(), NAMED_SOURCE_LABEL);
@@ -294,19 +330,19 @@ function readEnvSource() {
294
330
  if (!envValue) return void 0;
295
331
  const trimmed = envValue.trim();
296
332
  if (trimmed === "") {
297
- warn(`${SOURCE_ENV_VAR} is set but empty \u2014 ignoring and falling back to next source.`);
333
+ warn(`${SOURCE_ENV_VAR} is set but empty \u2014 ignoring and falling back to the next rung.`);
298
334
  return void 0;
299
335
  }
300
336
  try {
301
337
  validateSourceFormat(trimmed, SOURCE_ENV_VAR);
302
338
  } catch (error) {
303
339
  warn(
304
- `${SOURCE_ENV_VAR} has an invalid value \u2014 ignoring and falling back to next source.
340
+ `${SOURCE_ENV_VAR} has an invalid value \u2014 ignoring and falling back to the next rung.
305
341
  ${getErrorMessage(error)}`
306
342
  );
307
343
  return void 0;
308
344
  }
309
- verbose(`Source from ${SOURCE_ENV_VAR} env var: ${trimmed}`);
345
+ verbose(`Marketplace from ${SOURCE_ENV_VAR} env var: ${trimmed}`);
310
346
  return trimmed;
311
347
  }
312
348
  async function resolvePrimarySourceEntry(projectDir) {
@@ -340,7 +376,7 @@ function validateSourceFormat(source, flagName) {
340
376
  throw new Error(
341
377
  `${flagName} contains invalid characters.
342
378
 
343
- Source values must not contain null bytes.
379
+ Marketplace values must not contain null bytes.
344
380
  Examples:
345
381
  ${flagName} ./my-skills
346
382
  ${flagName} github:user/repo`
@@ -350,7 +386,7 @@ Examples:
350
386
  throw new Error(
351
387
  `${flagName} value is too long (${source.length} characters, max ${MAX_SOURCE_LENGTH}).
352
388
 
353
- Provide a shorter source path or URL.
389
+ Provide a shorter marketplace path or URL.
354
390
  Examples:
355
391
  ${flagName} ./my-skills
356
392
  ${flagName} github:user/repo`
@@ -379,7 +415,7 @@ Examples:
379
415
  throw new Error(
380
416
  `${flagName} contains path traversal in URL: "${source}"
381
417
 
382
- Remote source URLs must not contain '..' sequences.
418
+ Remote marketplace URLs must not contain '..' sequences.
383
419
  Examples:
384
420
  ${flagName} github:user/repo
385
421
  ${flagName} https://github.com/user/repo`
@@ -411,7 +447,7 @@ Examples:
411
447
  throw new Error(
412
448
  `${flagName} points to a private or reserved IP address: "${source}"
413
449
 
414
- Source URLs must not target private network addresses.
450
+ Marketplace URLs must not target private network addresses.
415
451
  Use a public hostname instead.
416
452
  Examples:
417
453
  ${flagName} https://github.com/user/repo
@@ -424,7 +460,7 @@ function validateGitShorthand(source, repoPath, flagName) {
424
460
  throw new Error(
425
461
  `${flagName} has an invalid repository reference: "${source}"
426
462
 
427
- Git shorthand sources require an owner/repo format.
463
+ Git shorthand marketplaces require an owner/repo format.
428
464
  Examples:
429
465
  ${flagName} github:user/repo
430
466
  ${flagName} gh:organization/skills`
@@ -437,7 +473,7 @@ function validateLocalPath(source, flagName) {
437
473
  throw new Error(
438
474
  `${flagName} contains invalid characters: "${source}"
439
475
 
440
- Source paths must not contain control characters.
476
+ Marketplace paths must not contain control characters.
441
477
  Examples:
442
478
  ${flagName} ./my-skills
443
479
  ${flagName} /home/user/skills`
@@ -459,6 +495,24 @@ Examples:
459
495
  function isDefaultSource(source) {
460
496
  return source === DEFAULT_SOURCE;
461
497
  }
498
+ var packageIdentitySchema = z.object({ name: z.string() });
499
+ async function isPublicCatalogueCheckout(basePath) {
500
+ const raw = await readFileOptional(path3.join(basePath, STANDARD_FILES.PACKAGE_JSON));
501
+ return declaredPackageName(raw) === PUBLIC_CATALOGUE_PACKAGE;
502
+ }
503
+ function declaredPackageName(raw) {
504
+ let json;
505
+ try {
506
+ json = JSON.parse(raw);
507
+ } catch {
508
+ return null;
509
+ }
510
+ const parsed = packageIdentitySchema.safeParse(json);
511
+ return parsed.success ? parsed.data.name : null;
512
+ }
513
+ async function offersBuiltInStacks(basePath, source) {
514
+ return isDefaultSource(source) || await isPublicCatalogueCheckout(basePath);
515
+ }
462
516
  function isLocalSource(source) {
463
517
  if (source.startsWith("/") || source.startsWith(".")) {
464
518
  return true;
@@ -467,7 +521,7 @@ function isLocalSource(source) {
467
521
  if (!hasRemoteProtocol) {
468
522
  if (source.includes("..") || source.includes("~")) {
469
523
  throw new Error(
470
- `Invalid source path: ${source}. Path traversal patterns like '..' and '~' are not allowed for security reasons. Use absolute paths or remote URLs instead (e.g., '/home/user/skills' or 'https://github.com/user/repo').`
524
+ `Invalid marketplace path: ${source}. Path traversal patterns like '..' and '~' are not allowed for security reasons. Use absolute paths or remote URLs instead (e.g., '/home/user/skills' or 'https://github.com/user/repo').`
471
525
  );
472
526
  }
473
527
  }
@@ -478,7 +532,7 @@ function isLocalSource(source) {
478
532
  import { groupBy as groupBy2, indexBy, partition } from "remeda";
479
533
 
480
534
  // ../matrix/src/vendor/generated/source-types.ts
481
- var SKILL_IDS2 = [
535
+ var SKILL_IDS = [
482
536
  "ai-infrastructure-huggingface-inference",
483
537
  "ai-infrastructure-litellm",
484
538
  "ai-infrastructure-modal",
@@ -822,7 +876,7 @@ var CATEGORIES = [
822
876
  "web-ui-kit",
823
877
  "web-utilities"
824
878
  ];
825
- var DOMAINS2 = [
879
+ var DOMAINS = [
826
880
  "ai",
827
881
  "api",
828
882
  "cli",
@@ -833,7 +887,7 @@ var DOMAINS2 = [
833
887
  "shared",
834
888
  "web"
835
889
  ];
836
- var AGENT_NAMES2 = [
890
+ var AGENT_NAMES = [
837
891
  "agent-summoner",
838
892
  "ai-developer",
839
893
  "ai-researcher",
@@ -10517,89 +10571,68 @@ var BUILT_IN_MATRIX2 = {
10517
10571
  generatedAt: "build"
10518
10572
  };
10519
10573
 
10520
- // ../matrix/src/schema.ts
10521
- import { z } from "zod";
10522
-
10523
- // ../matrix/src/vendor/matrix.ts
10524
- var MODEL_NAMES2 = ["sonnet", "opus", "haiku", "fable", "inherit"];
10525
-
10526
- // ../matrix/src/schema.ts
10527
- var DomainIdSchema = z.enum(DOMAINS2);
10528
- var SkillIdSchema = z.enum(SKILL_IDS2);
10529
- var CategorySchema = z.enum(CATEGORIES);
10530
- var AgentNameSchema = z.enum(AGENT_NAMES2);
10531
- var ModelNameSchema = z.enum(MODEL_NAMES2);
10532
- var CategoryDefinitionSchema = z.object({
10533
- id: CategorySchema,
10534
- displayName: z.string(),
10535
- description: z.string(),
10574
+ // ../matrix/src/built-in-matrix.ts
10575
+ import { z as z2 } from "zod";
10576
+ var domainIdSchema = z2.enum(DOMAINS);
10577
+ var skillIdSchema = z2.enum(SKILL_IDS);
10578
+ var categorySchema = z2.enum(CATEGORIES);
10579
+ var agentNameSchema = z2.enum(AGENT_NAMES);
10580
+ var categoryDefinitionSchema = z2.object({
10581
+ id: categorySchema,
10582
+ displayName: z2.string(),
10583
+ description: z2.string(),
10536
10584
  // Optional in the CLI's type. Present on all 89 built-in categories today; a category without
10537
10585
  // one cannot be placed in the UI, so the read model drops it rather than failing the whole boot.
10538
- domain: DomainIdSchema.optional(),
10539
- exclusive: z.boolean(),
10540
- required: z.boolean(),
10541
- order: z.number()
10586
+ domain: domainIdSchema.exactOptional(),
10587
+ exclusive: z2.boolean(),
10588
+ required: z2.boolean(),
10589
+ order: z2.number()
10542
10590
  });
10543
- var SkillRelationSchema = z.object({
10544
- skillId: SkillIdSchema,
10545
- reason: z.string()
10591
+ var skillRelationSchema = z2.object({
10592
+ skillId: skillIdSchema,
10593
+ reason: z2.string()
10546
10594
  });
10547
- var SkillRequirementSchema = z.object({
10548
- skillIds: z.array(SkillIdSchema),
10549
- needsAny: z.boolean(),
10550
- reason: z.string()
10595
+ var skillRequirementSchema = z2.object({
10596
+ skillIds: z2.array(skillIdSchema),
10597
+ needsAny: z2.boolean(),
10598
+ reason: z2.string()
10551
10599
  });
10552
- var ResolvedSkillSchema = z.object({
10553
- id: SkillIdSchema,
10554
- slug: z.string(),
10555
- displayName: z.string(),
10556
- description: z.string(),
10557
- category: CategorySchema,
10558
- conflictsWith: z.array(SkillRelationSchema),
10559
- discourages: z.array(SkillRelationSchema),
10560
- requires: z.array(SkillRequirementSchema)
10600
+ var resolvedSkillSchema = z2.object({
10601
+ id: skillIdSchema,
10602
+ slug: z2.string(),
10603
+ displayName: z2.string(),
10604
+ description: z2.string(),
10605
+ category: categorySchema,
10606
+ conflictsWith: z2.array(skillRelationSchema),
10607
+ discourages: z2.array(skillRelationSchema),
10608
+ requires: z2.array(skillRequirementSchema)
10561
10609
  });
10562
- var ResolvedStackSchema = z.object({
10563
- id: z.string(),
10564
- name: z.string(),
10565
- description: z.string(),
10610
+ var resolvedStackSchema = z2.object({
10611
+ id: z2.string(),
10612
+ name: z2.string(),
10613
+ description: z2.string(),
10566
10614
  // agent id → category id → skill ids. Partial because a stack names the few
10567
10615
  // agents it staffs, not the whole roster; the category keys are the one level
10568
10616
  // nothing here reads, so they stay unvalidated strings.
10569
- skills: z.partialRecord(
10570
- AgentNameSchema,
10571
- z.record(z.string(), z.array(SkillIdSchema))
10617
+ skills: z2.partialRecord(
10618
+ agentNameSchema,
10619
+ z2.record(z2.string(), z2.array(skillIdSchema))
10572
10620
  ),
10573
- allSkillIds: z.array(SkillIdSchema),
10574
- philosophy: z.string(),
10621
+ allSkillIds: z2.array(skillIdSchema),
10622
+ philosophy: z2.string(),
10575
10623
  // Never populated by the CLI today; the stack rail groups by it when it appears.
10576
- group: z.string().optional()
10624
+ group: z2.string().exactOptional()
10577
10625
  });
10578
- var MatrixSchema = z.object({
10626
+ var builtInMatrixSchema = z2.object({
10579
10627
  // Stamped into every seed payload so a consumer can explain skipped ids.
10580
- version: z.string(),
10581
- categories: z.record(z.string(), CategoryDefinitionSchema),
10582
- skills: z.record(z.string(), ResolvedSkillSchema),
10583
- suggestedStacks: z.array(ResolvedStackSchema)
10584
- });
10585
- var AgentDefinitionSchema = z.object({
10586
- id: AgentNameSchema,
10587
- title: z.string(),
10588
- description: z.string(),
10589
- model: ModelNameSchema.optional(),
10590
- tools: z.array(z.string()),
10591
- // Not narrowed here: which roles are sayable is authored in the read model's
10592
- // preload table, which reads this file — so the check lives where the list
10593
- // does, in `flavorOf`, rather than being imported back into the boundary.
10594
- flavor: z.string()
10628
+ version: z2.string(),
10629
+ categories: z2.record(z2.string(), categoryDefinitionSchema),
10630
+ skills: z2.record(z2.string(), resolvedSkillSchema),
10631
+ suggestedStacks: z2.array(resolvedStackSchema)
10595
10632
  });
10596
- var AgentDefinitionsSchema = z.record(
10597
- z.string(),
10598
- AgentDefinitionSchema
10599
- );
10600
10633
 
10601
10634
  // ../matrix/src/read-model/source.ts
10602
- var MATRIX = MatrixSchema.parse(BUILT_IN_MATRIX2);
10635
+ var MATRIX = builtInMatrixSchema.parse(BUILT_IN_MATRIX2);
10603
10636
  var MATRIX_VERSION = MATRIX.version;
10604
10637
 
10605
10638
  // ../matrix/src/read-model/domains.ts
@@ -10636,7 +10669,7 @@ var DOMAIN_DESCRIPTIONS = {
10636
10669
  meta: "Design patterns, code review, and research methodology",
10637
10670
  shared: "Shared utilities and methodology"
10638
10671
  };
10639
- var DOMAIN_IDS = new Set(DOMAINS2);
10672
+ var DOMAIN_IDS = new Set(DOMAINS);
10640
10673
  var isDomain = (domainId) => DOMAIN_IDS.has(domainId);
10641
10674
  var agentDomainOf = (agentId) => {
10642
10675
  const prefix = agentId.split("-")[0];
@@ -10664,7 +10697,7 @@ var toCatalogSkill = (skill, domainId) => ({
10664
10697
  }))
10665
10698
  });
10666
10699
  var placeableCategories = (categories) => Object.values(categories).filter(
10667
- (category) => category.domain !== void 0
10700
+ (category) => category.domain !== void 0 && isDomain(category.domain)
10668
10701
  );
10669
10702
  var byDomainThenAuthoredOrder = (a, b) => compareDomains(a.domain, b.domain) || a.order - b.order;
10670
10703
  var toCatalogCategory = (category, skills) => ({
@@ -10686,27 +10719,29 @@ var toCatalogDomain = (id, categories) => ({
10686
10719
  0
10687
10720
  )
10688
10721
  });
10689
- var buildCatalog = () => {
10722
+ var buildCatalog = (matrix2) => {
10690
10723
  const skillsByCategory = groupBy(
10691
- Object.values(MATRIX.skills),
10724
+ Object.values(matrix2.skills),
10692
10725
  (skill) => skill.category
10693
10726
  );
10694
- const categories = placeableCategories(MATRIX.categories).sort(byDomainThenAuthoredOrder).map(
10727
+ const categories = placeableCategories(matrix2.categories).sort(byDomainThenAuthoredOrder).map(
10695
10728
  (category) => toCatalogCategory(category, skillsByCategory.get(category.id) ?? [])
10696
10729
  );
10697
10730
  const domains = [...groupBy(categories, (category) => category.domainId)].sort(([a], [b]) => compareDomains(a, b)).map(([id, domainCategories]) => toCatalogDomain(id, domainCategories));
10698
10731
  const allSkills2 = categories.flatMap((category) => category.skills);
10699
10732
  return {
10700
10733
  domains,
10701
- skillsById: indexById(allSkills2),
10702
- categoriesById: indexById(categories),
10734
+ skillsById: byId(allSkills2),
10735
+ categoriesById: byId(categories),
10703
10736
  skillCount: allSkills2.length
10704
10737
  };
10705
10738
  };
10706
- var CATALOG = buildCatalog();
10707
- var CATALOGUED_IDS = new Set(SKILL_IDS2);
10708
- var isSkillId = (skillId) => CATALOGUED_IDS.has(skillId);
10709
- var skillById = (skillId) => isSkillId(skillId) ? CATALOG.skillsById[skillId] : void 0;
10739
+ var byId = (items) => Object.fromEntries(items.map((item) => [item.id, item]));
10740
+ var CATALOG = buildCatalog(MATRIX);
10741
+ var createSkillLookup = (catalog) => (skillId) => catalog.skillsById[skillId];
10742
+ var skillById = createSkillLookup(CATALOG);
10743
+ var SHIPPED_IDS = new Set(SKILL_IDS);
10744
+ var isShippedSkillId = (skillId) => SHIPPED_IDS.has(skillId);
10710
10745
 
10711
10746
  // ../matrix/src/generated/agents.ts
10712
10747
  var AGENT_DEFINITIONS = {
@@ -10874,6 +10909,31 @@ var AGENT_DEFINITIONS = {
10874
10909
  }
10875
10910
  };
10876
10911
 
10912
+ // ../matrix/src/built-in-agents.ts
10913
+ import { z as z3 } from "zod";
10914
+
10915
+ // ../matrix/src/vendor/matrix.ts
10916
+ var MODEL_NAMES2 = ["sonnet", "opus", "haiku", "fable", "inherit"];
10917
+
10918
+ // ../matrix/src/built-in-agents.ts
10919
+ var agentNameSchema2 = z3.enum(AGENT_NAMES);
10920
+ var modelNameSchema = z3.enum(MODEL_NAMES2);
10921
+ var agentDefinitionSchema = z3.object({
10922
+ id: agentNameSchema2,
10923
+ title: z3.string(),
10924
+ description: z3.string(),
10925
+ model: modelNameSchema.exactOptional(),
10926
+ tools: z3.array(z3.string()),
10927
+ // Not narrowed here: which roles are sayable is authored in the read model's
10928
+ // preload table — so the check lives where the list does, in `flavorOf`,
10929
+ // rather than the list being imported back into the boundary.
10930
+ flavor: z3.string()
10931
+ });
10932
+ var agentDefinitionsSchema = z3.record(
10933
+ z3.string(),
10934
+ agentDefinitionSchema
10935
+ );
10936
+
10877
10937
  // ../matrix/src/read-model/preload-defaults.ts
10878
10938
  var ROLE_FLAVORS = [
10879
10939
  "developer",
@@ -11036,7 +11096,7 @@ var PRELOAD_DEFAULTS = {
11036
11096
  // Shared — carried across every implementation domain
11037
11097
  "shared-security-auth-security": ["developer", "researcher"]
11038
11098
  };
11039
- var KNOWN_SKILL_IDS = new Set(SKILL_IDS2);
11099
+ var KNOWN_SKILL_IDS = new Set(SKILL_IDS);
11040
11100
  var KNOWN_AGENT_IDS = new Set(Object.keys(AGENT_DEFINITIONS));
11041
11101
  var LISTABLE_FLAVORS = new Set(ROLE_FLAVORS);
11042
11102
  var assertKnownSkill = (skillId) => {
@@ -11100,7 +11160,7 @@ var toSubAgentGroup = (domainId, agents) => ({
11100
11160
  });
11101
11161
  var buildSubAgentGroups = () => {
11102
11162
  const agents = Object.values(
11103
- AgentDefinitionsSchema.parse(AGENT_DEFINITIONS)
11163
+ agentDefinitionsSchema.parse(AGENT_DEFINITIONS)
11104
11164
  ).map(toSubAgent);
11105
11165
  return [...groupBy(agents, (agent) => agent.domainId)].sort(([a], [b]) => compareDomains(a, b)).map(([domainId, domainAgents]) => toSubAgentGroup(domainId, domainAgents));
11106
11166
  };
@@ -11108,7 +11168,9 @@ var SUB_AGENT_GROUPS = buildSubAgentGroups();
11108
11168
  var SUB_AGENTS_BY_ID = indexById(
11109
11169
  SUB_AGENT_GROUPS.flatMap((group) => group.agents)
11110
11170
  );
11111
- var ROSTER_IDS = new Set(AGENT_NAMES2);
11171
+ var ROSTER_IDS = new Set(AGENT_NAMES);
11172
+ var isAgentName2 = (agentId) => ROSTER_IDS.has(agentId);
11173
+ var subAgentById = (agentId) => isAgentName2(agentId) ? SUB_AGENTS_BY_ID[agentId] : void 0;
11112
11174
 
11113
11175
  // ../matrix/src/read-model/assignment-defaults.ts
11114
11176
  var ROSTER = SUB_AGENT_GROUPS.flatMap(
@@ -11129,40 +11191,82 @@ var isRoleCraftFor = (skill, agent) => {
11129
11191
  const craft = CRAFT_CATEGORIES_BY_FLAVOR[agent.flavor];
11130
11192
  return craft !== void 0 && craft.includes(skill.categoryId);
11131
11193
  };
11132
- var metaSkillReach = (defaults, skill) => {
11133
- const rowFlavors = new Set(defaults[skill.id] ?? []);
11194
+ var metaSkillReach = (skill, rowFlavors) => {
11195
+ const listedFlavors = new Set(rowFlavors);
11134
11196
  return ROSTER.filter(
11135
- (agent) => rowFlavors.has(agent.flavor) || isRoleCraftFor(skill, agent)
11197
+ (agent) => listedFlavors.has(agent.flavor) || isRoleCraftFor(skill, agent)
11136
11198
  );
11137
11199
  };
11138
- var targetsOf = (defaults, skill) => {
11139
- if (skill.domainId === "meta") return metaSkillReach(defaults, skill);
11200
+ var targetsOf = (skill, rowFlavors) => {
11201
+ if (skill.domainId === "meta") return metaSkillReach(skill, rowFlavors);
11140
11202
  if (skill.domainId === "shared") return NON_META_ROSTER;
11141
11203
  return implementationDomainReach(skill.domainId);
11142
11204
  };
11205
+ var taxonomyOf = (skill) => typeof skill === "string" ? skillById(skill) : skill;
11206
+ var shippedIdOf = (catalogued) => catalogued !== void 0 && isShippedSkillId(catalogued.id) ? catalogued.id : void 0;
11207
+ var rowFlavorsOf = (defaults, shippedId) => shippedId === void 0 ? [] : defaults[shippedId] ?? [];
11143
11208
  var createAssignmentResolver = (defaults) => {
11144
11209
  const resolveLoad = createLoadStateResolver(defaults);
11145
- return (skillId) => {
11146
- const skill = skillById(skillId);
11147
- if (!skill) return [];
11148
- return targetsOf(defaults, skill).map((agent) => ({
11149
- agentId: agent.id,
11150
- load: resolveLoad({ skillId: skill.id, agentId: agent.id })
11151
- }));
11210
+ return (skill) => {
11211
+ const taxonomy = taxonomyOf(skill);
11212
+ if (!taxonomy) return [];
11213
+ const shippedId = shippedIdOf(skillById(taxonomy.id));
11214
+ const loadOn = (agentId) => shippedId === void 0 ? "lazy" : resolveLoad({ skillId: shippedId, agentId });
11215
+ return targetsOf(taxonomy, rowFlavorsOf(defaults, shippedId)).map(
11216
+ (agent) => ({ agentId: agent.id, load: loadOn(agent.id) })
11217
+ );
11152
11218
  };
11153
11219
  };
11154
11220
  var resolveAssignment = createAssignmentResolver(PRELOAD_DEFAULTS);
11155
11221
 
11156
11222
  // ../matrix/src/read-model/stacks.ts
11157
- var parsedStacks = MATRIX.suggestedStacks;
11158
- var STACKS = parsedStacks.map((stack) => ({
11223
+ var toCatalogStack = (stack) => ({
11159
11224
  id: stack.id,
11160
11225
  name: stack.name,
11161
11226
  description: stack.description,
11162
11227
  philosophy: stack.philosophy,
11163
11228
  ...stack.group !== void 0 && { group: stack.group },
11164
11229
  skillCount: stack.allSkillIds.length
11165
- }));
11230
+ });
11231
+ var buildStacks = (matrix2) => matrix2.suggestedStacks.map(toCatalogStack);
11232
+ var STACKS = buildStacks(MATRIX);
11233
+ var rosteredAgents = (stack) => Object.keys(stack.skills).map((agentId) => subAgentById(agentId)).filter((agent) => agent !== void 0).map((agent) => agent.id);
11234
+ var toAgentsBySkill = (stack) => {
11235
+ const agentsBySkill = {};
11236
+ for (const agentId of rosteredAgents(stack)) {
11237
+ for (const skillId of Object.values(stack.skills[agentId] ?? {}).flat()) {
11238
+ const agents = agentsBySkill[skillId] ??= [];
11239
+ if (!agents.includes(agentId)) agents.push(agentId);
11240
+ }
11241
+ }
11242
+ return agentsBySkill;
11243
+ };
11244
+ var toLoadByAgent = (skillId) => new Map(
11245
+ resolveAssignment(skillId).map(({ agentId, load }) => [agentId, load])
11246
+ );
11247
+ var toAssignmentsBySkill = (stack) => Object.fromEntries(
11248
+ Object.entries(toAgentsBySkill(stack)).map(([skillId, agentIds]) => {
11249
+ const loadByAgent = toLoadByAgent(skillId);
11250
+ return [
11251
+ skillId,
11252
+ agentIds.map((agentId) => ({
11253
+ agentId,
11254
+ load: loadByAgent.get(agentId) ?? "lazy"
11255
+ }))
11256
+ ];
11257
+ })
11258
+ );
11259
+ var createStackExpander = (matrix2) => (stackId) => {
11260
+ const stack = matrix2.suggestedStacks.find(
11261
+ (candidate) => candidate.id === stackId
11262
+ );
11263
+ if (!stack) return void 0;
11264
+ return {
11265
+ skillIds: stack.allSkillIds,
11266
+ assignmentsBySkill: toAssignmentsBySkill(stack)
11267
+ };
11268
+ };
11269
+ var expandStack = createStackExpander(MATRIX);
11166
11270
 
11167
11271
  // ../matrix/src/read-model/selection-defaults.ts
11168
11272
  var DEFAULT_SELECTION_OPTIONS = {
@@ -11301,8 +11405,8 @@ var createSelectionSemantics = (catalog) => {
11301
11405
  };
11302
11406
  };
11303
11407
  };
11304
- var shippedFacts = () => ({
11305
- skills: Object.values(MATRIX.skills).map((skill) => ({
11408
+ var catalogFactsOf = (matrix2) => ({
11409
+ skills: Object.values(matrix2.skills).map((skill) => ({
11306
11410
  id: skill.id,
11307
11411
  categoryId: skill.category,
11308
11412
  conflictsWith: skill.conflictsWith.map((relation) => relation.skillId),
@@ -11316,10 +11420,10 @@ var shippedFacts = () => ({
11316
11420
  }))
11317
11421
  })),
11318
11422
  exclusiveCategoryIds: new Set(
11319
- Object.values(MATRIX.categories).filter((category) => category.exclusive).map((category) => category.id)
11423
+ Object.values(matrix2.categories).filter((category) => category.exclusive).map((category) => category.id)
11320
11424
  )
11321
11425
  });
11322
- var judgeSelection = createSelectionSemantics(shippedFacts());
11426
+ var judgeSelection = createSelectionSemantics(catalogFactsOf(MATRIX));
11323
11427
 
11324
11428
  // ../matrix/src/contract/selection-scenarios.ts
11325
11429
  var DISCOURAGED_PAIRS = Object.values(CATALOG.skillsById).flatMap(
@@ -11327,82 +11431,185 @@ var DISCOURAGED_PAIRS = Object.values(CATALOG.skillsById).flatMap(
11327
11431
  );
11328
11432
 
11329
11433
  // ../matrix/src/seed.ts
11330
- import { z as z2 } from "zod";
11331
- var SEED_VERSION = 3;
11332
- var seedModelSchema = z2.enum(["opus", "fable", "sonnet", "haiku"]);
11333
- var seedEffortSchema = z2.enum([
11434
+ import { z as z4 } from "zod";
11435
+ var SEED_VERSION = 5;
11436
+ var seedModelSchema = z4.enum(["opus", "fable", "sonnet", "haiku"]);
11437
+ var seedEffortSchema = z4.enum([
11334
11438
  "low",
11335
11439
  "medium",
11336
11440
  "high",
11337
11441
  "xhigh",
11338
11442
  "max"
11339
11443
  ]);
11340
- var seedLoadStateSchema = z2.enum(["lazy", "preloaded"]);
11341
- var seedSkillSchema = z2.object({
11342
- install: z2.enum(["plugin", "eject"]),
11343
- scope: z2.enum(["project", "global"]),
11444
+ var seedLoadStateSchema = z4.enum(["lazy", "preloaded"]);
11445
+ var seedSkillSchema = z4.object({
11446
+ install: z4.enum(["plugin", "eject"]),
11447
+ scope: z4.enum(["project", "global"]),
11344
11448
  // Sub-agent id → load state; presence is assignment. Per (agent, skill),
11345
11449
  // matching the granularity of the CLI's stack config.
11346
- assignments: z2.record(z2.string(), seedLoadStateSchema)
11450
+ assignments: z4.record(z4.string(), seedLoadStateSchema)
11347
11451
  });
11348
- var seedAgentSchema = z2.object({
11349
- on: z2.boolean().optional(),
11350
- model: seedModelSchema.optional(),
11351
- effort: seedEffortSchema.optional(),
11452
+ var seedAgentSchema = z4.object({
11453
+ on: z4.boolean().exactOptional(),
11454
+ model: seedModelSchema.exactOptional(),
11455
+ effort: seedEffortSchema.exactOptional(),
11352
11456
  // Where this agent's front-matter is written: the project, or the user's own
11353
11457
  // ~/.claude. Absent means the shared selection default — `global`, spelled
11354
11458
  // once in `DEFAULT_SELECTION_OPTIONS` — so the resting choice never travels,
11355
11459
  // exactly as a resting model does not.
11356
- scope: z2.enum(["project", "global"]).optional()
11460
+ scope: z4.enum(["project", "global"]).exactOptional()
11357
11461
  });
11358
- var seedPayloadSchema = z2.object({
11359
- v: z2.literal(SEED_VERSION),
11462
+ var MAX_EXTERNAL_SKILL_BYTES = 262144;
11463
+ var SKILL_MANIFEST = "SKILL.md";
11464
+ var treeBytes = (files) => {
11465
+ const encoder = new TextEncoder();
11466
+ return Object.values(files).reduce(
11467
+ (total, text) => total + encoder.encode(text).length,
11468
+ 0
11469
+ );
11470
+ };
11471
+ var seedSkillTreeSchema = z4.record(z4.string().min(1), z4.string()).refine((files) => SKILL_MANIFEST in files, {
11472
+ message: `a skill directory must hold ${SKILL_MANIFEST}`
11473
+ }).refine((files) => treeBytes(files) <= MAX_EXTERNAL_SKILL_BYTES, {
11474
+ message: `a skill directory may not exceed ${String(MAX_EXTERNAL_SKILL_BYTES)} bytes`
11475
+ });
11476
+ var seedExternalSkillSchema = z4.object({
11477
+ displayName: z4.string().min(1),
11478
+ description: z4.string(),
11479
+ // A category of the catalogue this payload names — the placement the user
11480
+ // confirmed. Without it the skill has nowhere to render and no sub-agent
11481
+ // reach, which is the whole difference between a catalogue entry and an
11482
+ // orphan section.
11483
+ categoryId: z4.string().min(1),
11484
+ // GitHub's own `owner/name`.
11485
+ repo: z4.string().min(1),
11486
+ // The skill's DIRECTORY within it — `skills/docx`, never the SKILL.md.
11487
+ path: z4.string().min(1),
11488
+ files: seedSkillTreeSchema
11489
+ });
11490
+ var seedPayloadSchema = z4.object({
11491
+ v: z4.literal(SEED_VERSION),
11360
11492
  // Diagnostics only. A mismatch with the consumer's matrix must not fail the
11361
11493
  // decode — it explains why some ids were skipped.
11362
- matrixVersion: z2.string(),
11363
- stackId: z2.string().nullable(),
11494
+ matrixVersion: z4.string(),
11495
+ stackId: z4.string().nullable(),
11496
+ // Where the skills below are fetched from, in the form `--marketplace` takes
11497
+ // one: `github:acme/skills`, a URL, a local path. The ref alone — the name
11498
+ // its manifest gives it is read from the fetched marketplace.json, so
11499
+ // carrying that too would let a payload disagree with the repository it
11500
+ // names. Absent is the default public catalogue, which is why a payload that
11501
+ // installs from it looks exactly as it did before this field existed.
11502
+ marketplace: z4.string().exactOptional(),
11364
11503
  // Sparse — presence is selection, exactly like the web store. `remembered`
11365
11504
  // is deliberately absent: deselected setup never leaves the browser.
11366
- skills: z2.record(z2.string(), seedSkillSchema),
11505
+ skills: z4.record(z4.string(), seedSkillSchema),
11506
+ // Skill id → its whole directory, for the ids above that no catalogue can
11507
+ // resolve. Keyed by the same id `skills` uses, so a selection reads one map
11508
+ // whichever kind of skill it names. Absent is the ordinary case — a payload
11509
+ // built from the catalogue alone — which is why one looks exactly as it did
11510
+ // before content travelled. Content is the expensive part of a payload, so an
11511
+ // added skill nobody selected has no entry here either.
11512
+ external: z4.record(z4.string(), seedExternalSkillSchema).exactOptional(),
11367
11513
  // Sparse for the same reason: an agent resting on its own catalogue model
11368
11514
  // with no pin has nothing to say, so it has no entry. Presence is a
11369
11515
  // statement, not an install: what installs is decided by assignments and
11370
11516
  // `on: true` alone, so a derived-off agent can still travel its overrides.
11371
11517
  // Only an agent pinned *off* is omitted outright — with the assignment rows
11372
11518
  // naming it, since the sharer's own counts exclude them.
11373
- agents: z2.record(z2.string(), seedAgentSchema)
11519
+ agents: z4.record(z4.string(), seedAgentSchema)
11520
+ });
11521
+
11522
+ // ../matrix/src/matrix-schema.ts
11523
+ import { z as z5 } from "zod";
11524
+ var idSchema = z5.string().min(1);
11525
+ var matrixCategorySchema = z5.object({
11526
+ id: idSchema,
11527
+ displayName: z5.string(),
11528
+ description: z5.string(),
11529
+ // Optional in the CLI's type. A category without one cannot be placed in the
11530
+ // UI, so a consumer drops it rather than failing the whole catalogue.
11531
+ domain: z5.string().exactOptional(),
11532
+ exclusive: z5.boolean(),
11533
+ required: z5.boolean(),
11534
+ order: z5.number()
11535
+ });
11536
+ var skillRelationSchema2 = z5.object({
11537
+ skillId: idSchema,
11538
+ reason: z5.string()
11539
+ });
11540
+ var skillRequirementSchema2 = z5.object({
11541
+ skillIds: z5.array(idSchema),
11542
+ needsAny: z5.boolean(),
11543
+ reason: z5.string()
11544
+ });
11545
+ var matrixSkillSchema = z5.object({
11546
+ id: idSchema,
11547
+ slug: z5.string(),
11548
+ displayName: z5.string(),
11549
+ description: z5.string(),
11550
+ category: idSchema,
11551
+ conflictsWith: z5.array(skillRelationSchema2),
11552
+ discourages: z5.array(skillRelationSchema2),
11553
+ requires: z5.array(skillRequirementSchema2)
11554
+ });
11555
+ var matrixStackSchema = z5.object({
11556
+ id: idSchema,
11557
+ name: z5.string(),
11558
+ description: z5.string(),
11559
+ // agent id → category id → skill ids. A stack names the few agents it staffs
11560
+ // rather than the whole roster, and the agent roster is as marketplace-owned
11561
+ // as the skills are, so neither level is narrowed.
11562
+ skills: z5.record(z5.string(), z5.record(z5.string(), z5.array(idSchema))),
11563
+ allSkillIds: z5.array(idSchema),
11564
+ philosophy: z5.string(),
11565
+ // Never populated by the CLI today; a stack rail groups by it when it appears.
11566
+ group: z5.string().exactOptional()
11567
+ });
11568
+ var matrixSchema = z5.object({
11569
+ version: z5.string(),
11570
+ categories: z5.record(z5.string(), matrixCategorySchema),
11571
+ skills: z5.record(z5.string(), matrixSkillSchema),
11572
+ suggestedStacks: z5.array(matrixStackSchema)
11374
11573
  });
11375
11574
 
11376
11575
  // ../matrix/src/skill-index.ts
11377
- import { z as z3 } from "zod";
11378
- var skillIndexEntrySchema = z3.object({
11576
+ import { z as z6 } from "zod";
11577
+ var skillIndexEntrySchema = z6.object({
11379
11578
  // The skill's own name — its frontmatter `name` where there is one, its
11380
11579
  // directory name where there is not. Never empty: a skill with neither is
11381
11580
  // not addressable and does not make it into the index.
11382
- name: z3.string().min(1),
11581
+ name: z6.string().min(1),
11383
11582
  // Frontmatter `description`, or the SKILL.md's first heading. Empty when the
11384
11583
  // file offers neither, which is a thin result rather than an invalid one.
11385
- description: z3.string(),
11584
+ description: z6.string(),
11386
11585
  // GitHub's own `owner/name`, so a result can be attributed and fetched
11387
11586
  // without a second lookup.
11388
- repo: z3.string().min(1),
11587
+ repo: z6.string().min(1),
11389
11588
  // The skill DIRECTORY within that repo — `skills/docx`, not
11390
11589
  // `skills/docx/SKILL.md`. A skill is a directory in every consumer of this
11391
11590
  // index, and the file is only how the directory proves itself.
11392
- path: z3.string().min(1),
11591
+ path: z6.string().min(1),
11393
11592
  // The repository's star count when the index was built. A property of the
11394
11593
  // repo rather than the skill, repeated per entry because the dialog ranks
11395
11594
  // and badges individual results.
11396
- stars: z3.number().int().nonnegative()
11595
+ stars: z6.number().int().nonnegative(),
11596
+ // How much the skill's whole DIRECTORY weighs — the sum over its blobs of the
11597
+ // sizes GitHub reported for them in the tree listing the crawl already made,
11598
+ // never a download. Repeated per entry because the cap it is read against is
11599
+ // per skill: `MAX_EXTERNAL_SKILL_BYTES` decides whether one directory can
11600
+ // ride a shared link, and without this number that decision cannot be made
11601
+ // until the consumer has listed the repository itself. Zero is a real answer,
11602
+ // for a directory GitHub reported no sizes for at all.
11603
+ bytes: z6.number().int().nonnegative()
11397
11604
  });
11398
- var skillIndexSchema = z3.object({
11605
+ var skillIndexSchema = z6.object({
11399
11606
  // When this index was assembled, as an ISO 8601 instant. The worker's own
11400
11607
  // freshness check reads it, and it is served so a caller can say how old the
11401
11608
  // list is rather than having to guess.
11402
- builtAt: z3.iso.datetime(),
11403
- skills: z3.array(skillIndexEntrySchema)
11609
+ builtAt: z6.iso.datetime(),
11610
+ skills: z6.array(skillIndexEntrySchema)
11404
11611
  });
11405
- var skillIndexFreshnessSchema = z3.enum(["fresh", "stale"]);
11612
+ var skillIndexFreshnessSchema = z6.enum(["fresh", "stale"]);
11406
11613
 
11407
11614
  // src/cli/lib/configuration/scope-predicates.ts
11408
11615
  function isActiveAt(entry, scope) {
@@ -11431,25 +11638,8 @@ function effectivelyExcludedSkillIds(skills) {
11431
11638
  return new Set(skills.filter((s) => s.excluded && !activeIds.has(s.id)).map((s) => s.id));
11432
11639
  }
11433
11640
 
11434
- // src/cli/utils/type-guards.ts
11435
- function isRecord(value) {
11436
- return typeof value === "object" && value !== null && !Array.isArray(value);
11437
- }
11438
- function isDomain2(value) {
11439
- return DOMAINS.includes(value);
11440
- }
11441
- function isAgentName(value) {
11442
- return AGENT_NAMES.includes(value);
11443
- }
11444
- function isSkillId2(value) {
11445
- return SKILL_IDS.includes(value);
11446
- }
11447
- function isSkillAssignment(value) {
11448
- if (typeof value !== "object" || value === null) return false;
11449
- return typeof value.id === "string";
11450
- }
11451
-
11452
11641
  // src/cli/lib/configuration/config-generator.ts
11642
+ var MATRIX_SAMPLE_SIZE = 5;
11453
11643
  function extractCategoryFromPath(categoryPath) {
11454
11644
  if (categoryPath === LOCAL_PSEUDO_CATEGORY) return void 0;
11455
11645
  return categoryPath;
@@ -11463,15 +11653,22 @@ function priorLoadState(existingStack, agent, category, skillId) {
11463
11653
  return prior.preloaded === true ? "preloaded" : "lazy";
11464
11654
  }
11465
11655
  function mappedLoadState(skillId, agent) {
11466
- if (!isSkillId2(skillId) || !isAgentName(agent)) return "lazy";
11656
+ if (!isSkillId(skillId) || !isAgentName(agent)) return "lazy";
11467
11657
  return resolveLoadState({ skillId, agentId: agent });
11468
11658
  }
11469
- function isRelevantPair(skillId, agent) {
11470
- return resolveAssignment(skillId).some((target) => target.agentId === agent);
11659
+ function taxonomyOrIdOf(skillId, category) {
11660
+ const domainId = getCategoryDomain(category);
11661
+ if (domainId === void 0) return skillId;
11662
+ return { id: skillId, domainId, categoryId: category };
11663
+ }
11664
+ function isRelevantPair(skillId, category, agent) {
11665
+ return resolveAssignment(taxonomyOrIdOf(skillId, category)).some(
11666
+ (target) => target.agentId === agent
11667
+ );
11471
11668
  }
11472
11669
  function isPreservedOrRelevant(agent, category, skillId, inputs) {
11473
11670
  const hasPriorEntry = priorLoadState(inputs.existingStack, agent, category, skillId) !== void 0;
11474
- return hasPriorEntry || isRelevantPair(skillId, agent);
11671
+ return hasPriorEntry || isRelevantPair(skillId, category, agent);
11475
11672
  }
11476
11673
  function getScopeOrThrow(map, key, kind) {
11477
11674
  const scope = map.get(key);
@@ -11511,12 +11708,8 @@ function toStackAssignment(id, agent, category, inputs) {
11511
11708
  return load === "preloaded" ? { id, preloaded: true } : { id };
11512
11709
  }
11513
11710
  function inCanonicalCategoryOrder(activeSkillsByCategory) {
11514
- const declarationRank = new Map(
11515
- typedKeys(matrix.categories).map((category, rank) => [category, rank])
11516
- );
11517
- const afterEveryDeclared = declarationRank.size;
11518
- const rankOf = (category) => declarationRank.get(category) ?? afterEveryDeclared;
11519
- return [...activeSkillsByCategory].sort(([a], [b]) => rankOf(a) - rankOf(b));
11711
+ const byDeclaration = byCategoryDeclarationOrder();
11712
+ return [...activeSkillsByCategory].sort(([a], [b]) => byDeclaration(a, b));
11520
11713
  }
11521
11714
  function buildAgentStack(agent, inputs) {
11522
11715
  const agentStack = {};
@@ -11541,28 +11734,31 @@ function buildStackForSelection(inputs) {
11541
11734
  return result;
11542
11735
  }
11543
11736
  function resolveValidSkills(selectedSkillIds, skillConfigs) {
11544
- const looked = selectedSkillIds.map((skillId) => {
11545
- const skill = matrix.skills[skillId];
11546
- if (!skill) warn(`Skill '${skillId}' NOT FOUND in matrix`, { suppressInTest: true });
11547
- return { skillId, skill };
11548
- });
11737
+ const looked = selectedSkillIds.map((skillId) => ({ skillId, skill: matrix.skills[skillId] }));
11549
11738
  const found = looked.filter(
11550
11739
  (entry) => entry.skill != null
11551
11740
  );
11552
- const skippedCount = looked.length - found.length;
11553
- if (skippedCount > 0) {
11554
- const matrixSample = typedKeys(matrix.skills).slice(0, 5).join(", ");
11555
- warn(
11556
- `${skippedCount}/${selectedSkillIds.length} skills not found in matrix. Matrix keys sample: [${matrixSample}]`,
11557
- { suppressInTest: true }
11558
- );
11559
- }
11741
+ const absentSkillIds = looked.filter((entry) => entry.skill == null).map((entry) => entry.skillId);
11742
+ reportAbsentSkills(absentSkillIds);
11560
11743
  const excludedSkillIds = effectivelyExcludedSkillIds(skillConfigs);
11561
11744
  const validSkills = found.map(({ skillId, skill }) => ({
11562
11745
  skillId,
11563
11746
  category: extractCategoryFromPath(skill.category)
11564
11747
  })).filter((entry) => entry.category != null).filter((entry) => !excludedSkillIds.has(entry.skillId));
11565
- return { validSkills, foundCount: found.length, skippedCount };
11748
+ return { validSkills, foundCount: found.length, skippedCount: absentSkillIds.length };
11749
+ }
11750
+ function reportAbsentSkills(absentSkillIds) {
11751
+ if (absentSkillIds.length === 0) return;
11752
+ for (const skillId of absentSkillIds) {
11753
+ warn(absentSkillWarning(skillId), { suppressInTest: true });
11754
+ }
11755
+ const matrixSample = typedKeys(matrix.skills).slice(0, MATRIX_SAMPLE_SIZE).join(", ");
11756
+ verbose(
11757
+ `${absentSkillIds.length} skills absent from the matrix. Matrix keys sample: [${matrixSample}]`
11758
+ );
11759
+ }
11760
+ function absentSkillWarning(skillId) {
11761
+ return `Skill '${skillId}' is not in this marketplace \u2014 it stays in the configuration and no sub-agent is given it. Run '${CLI_INVOKE_COMMAND} update' to refresh the marketplace, or remove it with '${CLI_INVOKE_COMMAND} edit'.`;
11566
11762
  }
11567
11763
  function buildSkillScopeMap(skillConfigs) {
11568
11764
  const [excludedConfigs, activeConfigs] = partition(skillConfigs, (s) => Boolean(s.excluded));
@@ -11632,7 +11828,7 @@ function generateProjectConfigFromSkills(name, selectedSkillIds, options) {
11632
11828
  scopeEligibilityGained: options.scopeEligibilityGained
11633
11829
  }
11634
11830
  });
11635
- const skills = options?.skillConfigs ?? selectedSkillIds.map((id) => ({ id, scope: "project", source: EJECT_SOURCE }));
11831
+ const skills = options?.skillConfigs ?? selectedSkillIds.map((id) => ({ id, scope: "project", origin: EJECT_SOURCE }));
11636
11832
  const activeAgentConfigs = resolveActiveAgentConfigs(agentList, options?.agentConfigs);
11637
11833
  const excludedAgentConfigs = agentConfigs.filter((ac) => ac.excluded);
11638
11834
  const finalAgentConfigs = [...activeAgentConfigs, ...excludedAgentConfigs];
@@ -11707,7 +11903,7 @@ function splitConfigByScope(config) {
11707
11903
  name: GLOBAL_CONFIG_NAME,
11708
11904
  agents: globalAgents,
11709
11905
  skills: globalSkills,
11710
- ...Object.keys(globalStack).length > 0 && { stack: globalStack },
11906
+ stack: globalStack,
11711
11907
  ...config.selectedDomains !== void 0 && { selectedDomains: config.selectedDomains }
11712
11908
  };
11713
11909
  const projectConfig = {
@@ -11715,7 +11911,7 @@ function splitConfigByScope(config) {
11715
11911
  name: config.name,
11716
11912
  agents: projectAgents,
11717
11913
  skills: projectSkills,
11718
- ...Object.keys(projectStack).length > 0 && { stack: projectStack }
11914
+ stack: projectStack
11719
11915
  };
11720
11916
  return { global: globalConfig, project: projectConfig };
11721
11917
  }
@@ -11725,10 +11921,10 @@ import { indexBy as indexBy2, uniqueBy as uniqueBy2 } from "remeda";
11725
11921
 
11726
11922
  // src/cli/lib/configuration/project-config.ts
11727
11923
  import os4 from "os";
11728
- import path4 from "path";
11924
+ import path5 from "path";
11729
11925
 
11730
11926
  // src/cli/lib/stacks/stacks-loader.ts
11731
- import path3 from "path";
11927
+ import path4 from "path";
11732
11928
  import { flatMap, groupBy as groupBy3, mapValues, partition as partition2, pipe, unique, uniqueBy } from "remeda";
11733
11929
  var stacksCache = /* @__PURE__ */ new Map();
11734
11930
  function normalizeAgentConfig(agentConfig) {
@@ -11788,7 +11984,7 @@ async function loadStacks(configDir, stacksFile) {
11788
11984
  const cacheKey = `${configDir}:${resolvedStacksFile}`;
11789
11985
  const cached = stacksCache.get(cacheKey);
11790
11986
  if (cached) return cached;
11791
- const stacksPath = path3.join(configDir, resolvedStacksFile);
11987
+ const stacksPath = path4.join(configDir, resolvedStacksFile);
11792
11988
  try {
11793
11989
  const raw = await loadConfig(stacksPath, stacksConfigSchema);
11794
11990
  if (raw == null) {
@@ -11819,8 +12015,10 @@ async function loadStackById(stackId, configDir, source) {
11819
12015
  verbose(`Found stack: ${stack.name} (${stackId})`);
11820
12016
  return stack;
11821
12017
  }
11822
- if (!isDefaultSource(source)) {
11823
- verbose(`Source '${source}' does not ship stack '${stackId}', and gets no built-in stand-in`);
12018
+ if (!await offersBuiltInStacks(configDir, source)) {
12019
+ verbose(
12020
+ `Marketplace '${source}' does not ship stack '${stackId}', and gets no built-in stand-in`
12021
+ );
11824
12022
  return null;
11825
12023
  }
11826
12024
  const defaultStack = defaultStacks.find((s) => s.id === stackId) ?? null;
@@ -11907,7 +12105,7 @@ async function loadProjectConfigFromDir(projectDir) {
11907
12105
  }
11908
12106
  const config = {
11909
12107
  ...parsed,
11910
- name: parsed.name ?? path4.basename(projectDir),
12108
+ name: parsed.name ?? path5.basename(projectDir),
11911
12109
  skills: parsed.skills ?? [],
11912
12110
  agents: parsed.agents ?? [],
11913
12111
  ...normalizedStack && { stack: normalizedStack }
@@ -11976,8 +12174,8 @@ function mergeConfigs(newConfig, existingConfig, options) {
11976
12174
  if (existingConfig.description) {
11977
12175
  merged.description = existingConfig.description;
11978
12176
  }
11979
- if (existingConfig.source && !newConfig.source) {
11980
- merged.source = existingConfig.source;
12177
+ if (existingConfig.marketplace && !newConfig.marketplace) {
12178
+ merged.marketplace = existingConfig.marketplace;
11981
12179
  }
11982
12180
  if (existingConfig.agents.length > 0) {
11983
12181
  const newAgentsByKey = indexBy2(merged.agents, agentKey);
@@ -12026,8 +12224,8 @@ function mergeConfigs(newConfig, existingConfig, options) {
12026
12224
  if (existingConfig.agentsSource) {
12027
12225
  merged.agentsSource = existingConfig.agentsSource;
12028
12226
  }
12029
- if (existingConfig.marketplace) {
12030
- merged.marketplace = existingConfig.marketplace;
12227
+ if (existingConfig.marketplaceName) {
12228
+ merged.marketplaceName = existingConfig.marketplaceName;
12031
12229
  }
12032
12230
  if (existingConfig.projects && !newConfig.projects) {
12033
12231
  merged.projects = existingConfig.projects;
@@ -12069,12 +12267,26 @@ async function mergeWithExistingConfig(newConfig, context) {
12069
12267
  }
12070
12268
 
12071
12269
  // src/cli/lib/configuration/config-types-writer.ts
12072
- import path5 from "path";
12270
+ import path6 from "path";
12073
12271
  import { groupBy as groupBy4, unique as unique2 } from "remeda";
12272
+
12273
+ // src/cli/utils/string.ts
12274
+ function truncateText(text, maxLength) {
12275
+ if (text.length <= maxLength) return text;
12276
+ return text.slice(0, maxLength - 1) + "\u2026";
12277
+ }
12278
+ function toTitleCase(kebabCase) {
12279
+ return kebabCase.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
12280
+ }
12281
+ function bytewise(a, b) {
12282
+ return a < b ? -1 : a > b ? 1 : 0;
12283
+ }
12284
+
12285
+ // src/cli/lib/configuration/config-types-writer.ts
12074
12286
  var MULTI_LINE_THRESHOLD = 6;
12075
12287
  var EMPTY_UNION_TYPE = "never";
12076
12288
  async function getGlobalConfigTypesPath() {
12077
- const globalConfigTypesPath = path5.join(
12289
+ const globalConfigTypesPath = path6.join(
12078
12290
  GLOBAL_INSTALL_ROOT,
12079
12291
  CLAUDE_SRC_DIR,
12080
12292
  STANDARD_FILES.CONFIG_TYPES_TS
@@ -12085,10 +12297,10 @@ async function getGlobalConfigTypesPath() {
12085
12297
  return null;
12086
12298
  }
12087
12299
  function computeGlobalTypesImportPath(projectDir) {
12088
- const projectClaudeSrc = path5.join(projectDir, CLAUDE_SRC_DIR);
12089
- const globalClaudeSrc = path5.join(GLOBAL_INSTALL_ROOT, CLAUDE_SRC_DIR);
12090
- const relativePath = path5.relative(projectClaudeSrc, globalClaudeSrc);
12091
- return relativePath.split(path5.sep).join("/");
12300
+ const projectClaudeSrc = path6.join(projectDir, CLAUDE_SRC_DIR);
12301
+ const globalClaudeSrc = path6.join(GLOBAL_INSTALL_ROOT, CLAUDE_SRC_DIR);
12302
+ const relativePath = path6.relative(projectClaudeSrc, globalClaudeSrc);
12303
+ return relativePath.split(path6.sep).join("/");
12092
12304
  }
12093
12305
  var STACK_AGENT_CONFIG_INLINE_THRESHOLD = 3;
12094
12306
  function formatLiteralUnion(members) {
@@ -12099,7 +12311,7 @@ var PROJECT_CONFIG_TYPES_BEFORE = `export type InstallMode = "eject" | "plugin"
12099
12311
  export type SkillConfig = {
12100
12312
  id: SkillId;
12101
12313
  scope: "project" | "global";
12102
- source: string;
12314
+ origin: string;
12103
12315
  excluded?: boolean;
12104
12316
  };
12105
12317
 
@@ -12123,7 +12335,7 @@ var PROJECT_CONFIG_INTERFACE_AFTER = `export interface ProjectConfig {
12123
12335
  /** Per-agent configuration with scope */
12124
12336
  agents: AgentScopeConfig[];
12125
12337
 
12126
- /** Per-skill configuration with scope and source */
12338
+ /** Per-skill configuration with scope and provenance */
12127
12339
  skills: SkillConfig[];
12128
12340
 
12129
12341
  /** Author handle (e.g., "@vince") */
@@ -12132,12 +12344,12 @@ var PROJECT_CONFIG_INTERFACE_AFTER = `export interface ProjectConfig {
12132
12344
  /** Stack configuration: agent -> category -> skill assignment */
12133
12345
  stack?: Partial<Record<ProjectAgentName, StackAgentConfig>>;
12134
12346
 
12135
- /** Skills source path or URL */
12136
- source?: string;
12137
-
12138
- /** Marketplace identifier for plugin installation */
12347
+ /** The marketplace this install reads skills from, as a path or URL */
12139
12348
  marketplace?: string;
12140
12349
 
12350
+ /** The name that marketplace's manifest gives it, which plugins are registered under */
12351
+ marketplaceName?: string;
12352
+
12141
12353
  /** Agents source path or URL (when agents come from a different source than skills) */
12142
12354
  agentsSource?: string;
12143
12355
 
@@ -12184,7 +12396,7 @@ function generateStackAgentConfig(skillsByCategory, matrix2) {
12184
12396
  if (skillsByCategory.size === 0) {
12185
12397
  return STACK_AGENT_CONFIG_LOOSE_LINE;
12186
12398
  }
12187
- const properties = [...skillsByCategory.entries()].sort(([a], [b]) => a.localeCompare(b)).map(([category, skills]) => {
12399
+ const properties = [...skillsByCategory.entries()].sort(([a], [b]) => bytewise(a, b)).map(([category, skills]) => {
12188
12400
  const arraySuffix = matrix2.categories[category]?.exclusive === true ? "" : "[]";
12189
12401
  return ` "${category}"?: SkillAssignment<${formatSkillUnion(skills)}>${arraySuffix};`;
12190
12402
  });
@@ -12202,22 +12414,27 @@ function buildSkillsByCategory(skillIds, categories, matrix2) {
12202
12414
  ])
12203
12415
  );
12204
12416
  }
12205
- function collectCustomDomains(matrix2, customCategorySet, extraDomains) {
12206
- const categories = typedKeys(matrix2.categories).map((key) => ({ key, domain: matrix2.categories[key]?.domain })).filter((c) => c.domain !== void 0);
12207
- const marketplaceDomains = new Set(
12208
- categories.filter((c) => !customCategorySet.has(c.key)).map((c) => c.domain)
12209
- );
12210
- const customOnlyDomains = categories.filter((c) => customCategorySet.has(c.key)).map((c) => c.domain).filter((domain) => !marketplaceDomains.has(domain));
12211
- return /* @__PURE__ */ new Set([...customOnlyDomains, ...extraDomains]);
12417
+ function isUndeclaredCategory(category, matrix2) {
12418
+ return matrix2.categories[category] === void 0;
12419
+ }
12420
+ function isCustomSkill(skillId, matrix2) {
12421
+ const declared = matrix2.skills[skillId];
12422
+ return declared === void 0 || declared.custom === true;
12423
+ }
12424
+ function isCustomAgent(agentName, declared, flaggedCustom) {
12425
+ return !declared.has(agentName) || flaggedCustom.has(agentName);
12426
+ }
12427
+ function isCustomDomain(domain, declared) {
12428
+ return !declared.has(domain);
12212
12429
  }
12213
12430
  async function regenerateConfigTypes(projectDir, backgroundData, extras) {
12214
12431
  if (isHomeDirectory(projectDir)) {
12215
12432
  throw new GlobalPairWriteViolation(
12216
- path5.join(projectDir, CLAUDE_SRC_DIR, STANDARD_FILES.CONFIG_TYPES_TS)
12433
+ path6.join(projectDir, CLAUDE_SRC_DIR, STANDARD_FILES.CONFIG_TYPES_TS)
12217
12434
  );
12218
12435
  }
12219
12436
  const data = await backgroundData;
12220
- const claudeSrcDir = path5.join(projectDir, CLAUDE_SRC_DIR);
12437
+ const claudeSrcDir = path6.join(projectDir, CLAUDE_SRC_DIR);
12221
12438
  const isProjectScope = !isHomeDirectory(projectDir);
12222
12439
  const globalConfigTypes = isProjectScope ? await getGlobalConfigTypesPath() : null;
12223
12440
  const loadedConfig = await loadProjectConfigFromDir(projectDir);
@@ -12245,7 +12462,7 @@ async function regenerateConfigTypes(projectDir, backgroundData, extras) {
12245
12462
  loadedConfig?.config
12246
12463
  );
12247
12464
  }
12248
- const configTypesPath = path5.join(claudeSrcDir, STANDARD_FILES.CONFIG_TYPES_TS);
12465
+ const configTypesPath = path6.join(claudeSrcDir, STANDARD_FILES.CONFIG_TYPES_TS);
12249
12466
  await writeFile(configTypesPath, source);
12250
12467
  verbose(`Regenerated ${STANDARD_FILES.CONFIG_TYPES_TS}`);
12251
12468
  }
@@ -12277,20 +12494,22 @@ function generateConfigTypesSource(matrix2, agentNames, customAgentNames = [], e
12277
12494
  domains = unique2([...extractDomains(matrix2), ...extraDomainsArr]).sort();
12278
12495
  categories = unique2([...typedKeys(matrix2.categories), ...extraCategoriesArr]).sort();
12279
12496
  }
12280
- const customSkillSet = /* @__PURE__ */ new Set([
12281
- ...extraSkillIds,
12282
- ...typedKeys(matrix2.skills).filter((id) => matrix2.skills[id]?.custom === true)
12283
- ]);
12284
- const customAgentSet = /* @__PURE__ */ new Set([...customAgentNames, ...extraAgentNamesArr]);
12285
- const customCategorySet = /* @__PURE__ */ new Set([
12286
- ...extraCategoriesArr,
12287
- ...typedKeys(matrix2.skills).map((id) => matrix2.skills[id]).filter((skill) => skill?.custom === true).map((skill) => skill.category).filter(isNonLocalCategory)
12288
- ]);
12289
- const customDomainSet = collectCustomDomains(matrix2, customCategorySet, extraDomainsArr);
12290
- const skillIdLine = formatMaybeSectionedUnion(skillIds, (id) => customSkillSet.has(id));
12291
- const agentNameLine = formatMaybeSectionedUnion(sortedAgents, (name) => customAgentSet.has(name));
12292
- const domainLine = formatMaybeSectionedUnion(domains, (d) => customDomainSet.has(d));
12293
- const categoryLine = formatMaybeSectionedUnion(categories, (s) => customCategorySet.has(s));
12497
+ const declaredAgents = new Set(agentNames);
12498
+ const flaggedCustomAgents = new Set(customAgentNames);
12499
+ const declaredDomains = new Set(extractDomains(matrix2));
12500
+ const skillIdLine = formatMaybeSectionedUnion(skillIds, (id) => isCustomSkill(id, matrix2));
12501
+ const agentNameLine = formatMaybeSectionedUnion(
12502
+ sortedAgents,
12503
+ (name) => isCustomAgent(name, declaredAgents, flaggedCustomAgents)
12504
+ );
12505
+ const domainLine = formatMaybeSectionedUnion(
12506
+ domains,
12507
+ (domain) => isCustomDomain(domain, declaredDomains)
12508
+ );
12509
+ const categoryLine = formatMaybeSectionedUnion(
12510
+ categories,
12511
+ (category) => isUndeclaredCategory(category, matrix2)
12512
+ );
12294
12513
  const selectedAgents = config?.agents ? activeAgentNames(config.agents) : [];
12295
12514
  const selectedAgentNameLine = selectedAgents.length > 0 ? formatUnion(selectedAgents) : "AgentName";
12296
12515
  const projectScopedAgents = config?.agents ? activeProjectAgentNames(config.agents) : [];
@@ -12400,12 +12619,12 @@ function formatExtendedUnion(globalTypeName, projectMembers) {
12400
12619
  }
12401
12620
 
12402
12621
  // src/cli/lib/skills/skill-metadata.ts
12403
- import path7 from "path";
12622
+ import path8 from "path";
12404
12623
  import { parse as parseYaml, stringify as stringifyYaml } from "yaml";
12405
12624
 
12406
12625
  // src/cli/lib/versioning.ts
12407
12626
  import { createHash } from "crypto";
12408
- import path6 from "path";
12627
+ import path7 from "path";
12409
12628
  var ISO_DATE_LENGTH = 10;
12410
12629
  function getCurrentDate() {
12411
12630
  return (/* @__PURE__ */ new Date()).toISOString().slice(0, ISO_DATE_LENGTH);
@@ -12422,18 +12641,18 @@ async function computeFileHash(filePath) {
12422
12641
  async function computeSkillFolderHash(skillPath) {
12423
12642
  const contents = [];
12424
12643
  for (const fileName of SKILL_CONTENT_FILES) {
12425
- const filePath = path6.join(skillPath, fileName);
12644
+ const filePath = path7.join(skillPath, fileName);
12426
12645
  if (await fileExists(filePath)) {
12427
12646
  const content = await readFile(filePath);
12428
12647
  contents.push(`${fileName}:${content}`);
12429
12648
  }
12430
12649
  }
12431
12650
  for (const dirName of SKILL_CONTENT_DIRS) {
12432
- const dirPath = path6.join(skillPath, dirName);
12651
+ const dirPath = path7.join(skillPath, dirName);
12433
12652
  if (await fileExists(dirPath)) {
12434
12653
  const files = await glob("**/*", dirPath);
12435
12654
  for (const file of files.sort()) {
12436
- const filePath = path6.join(dirPath, file);
12655
+ const filePath = path7.join(dirPath, file);
12437
12656
  const content = await readFile(filePath);
12438
12657
  contents.push(`${dirName}/${file}:${content}`);
12439
12658
  }
@@ -12516,7 +12735,7 @@ async function readForkedFromMetadata(skillDir) {
12516
12735
  return metadata?.forkedFrom ?? null;
12517
12736
  }
12518
12737
  async function readLocalSkillMetadata(skillDir) {
12519
- const metadataPath = path7.join(skillDir, STANDARD_FILES.METADATA_YAML);
12738
+ const metadataPath = path8.join(skillDir, STANDARD_FILES.METADATA_YAML);
12520
12739
  if (!await fileExists(metadataPath)) {
12521
12740
  return null;
12522
12741
  }
@@ -12528,8 +12747,8 @@ async function readLocalSkillMetadata(skillDir) {
12528
12747
  }
12529
12748
  return result.data;
12530
12749
  }
12531
- async function injectForkedFromMetadata(destPath, skillId, contentHash, source) {
12532
- const metadataPath = path7.join(destPath, STANDARD_FILES.METADATA_YAML);
12750
+ async function injectForkedFromMetadata(destPath, skillId, contentHash, origin = {}) {
12751
+ const metadataPath = path8.join(destPath, STANDARD_FILES.METADATA_YAML);
12533
12752
  const rawContent = await readFile(metadataPath);
12534
12753
  const { yamlContent } = stripYamlSchemaComment(rawContent);
12535
12754
  const parseResult = localSkillMetadataSchema.safeParse(parseYaml(yamlContent));
@@ -12542,7 +12761,8 @@ async function injectForkedFromMetadata(destPath, skillId, contentHash, source)
12542
12761
  skillId,
12543
12762
  contentHash,
12544
12763
  date: getCurrentDate(),
12545
- ...source ? { source } : {}
12764
+ ...origin.source !== void 0 && { source: origin.source },
12765
+ ...origin.path !== void 0 && { path: origin.path }
12546
12766
  }
12547
12767
  };
12548
12768
  await writeMetadataYaml(metadataPath, metadata, `${yamlSchemaComment(SCHEMA_PATHS.metadata)}
@@ -12554,7 +12774,7 @@ async function writeMetadataYaml(filePath, metadata, schemaComment = "") {
12554
12774
  }
12555
12775
 
12556
12776
  // src/cli/lib/skills/skill-copier.ts
12557
- import path8 from "path";
12777
+ import path9 from "path";
12558
12778
  var NULL_BYTE_PATTERN2 = /\0/;
12559
12779
  function validateSkillPath(resolvedPath, expectedParent, skillPath) {
12560
12780
  if (NULL_BYTE_PATTERN2.test(skillPath)) {
@@ -12562,28 +12782,30 @@ function validateSkillPath(resolvedPath, expectedParent, skillPath) {
12562
12782
  }
12563
12783
  if (!isPathWithin(resolvedPath, expectedParent)) {
12564
12784
  throw new Error(
12565
- `Invalid skill path: '${skillPath}' escapes expected directory '${path8.resolve(expectedParent)}'`
12785
+ `Invalid skill path: '${skillPath}' escapes expected directory '${path9.resolve(expectedParent)}'`
12566
12786
  );
12567
12787
  }
12568
12788
  }
12569
12789
  function resolveSkillPath(basePath, skillPath) {
12570
- const resolved = path8.join(basePath, skillPath);
12790
+ const resolved = path9.join(basePath, skillPath);
12571
12791
  validateSkillPath(resolved, basePath, skillPath);
12572
12792
  return resolved;
12573
12793
  }
12574
12794
  function getSkillSourcePath(skill, rootDir) {
12575
- const srcDir = path8.join(rootDir, SOURCE_SRC_DIR);
12795
+ const srcDir = path9.join(rootDir, SOURCE_SRC_DIR);
12576
12796
  return resolveSkillPath(srcDir, skill.path);
12577
12797
  }
12578
12798
  async function generateSkillHash(skillSourcePath) {
12579
- const skillMdPath = path8.join(skillSourcePath, STANDARD_FILES.SKILL_MD);
12799
+ const skillMdPath = path9.join(skillSourcePath, STANDARD_FILES.SKILL_MD);
12580
12800
  return computeFileHash(skillMdPath);
12581
12801
  }
12582
12802
  async function copySkillTo(skill, sourcePath, destPath, source) {
12583
12803
  const contentHash = await generateSkillHash(sourcePath);
12584
- await ensureDir(path8.dirname(destPath));
12804
+ await ensureDir(path9.dirname(destPath));
12585
12805
  await copy(sourcePath, destPath);
12586
- await injectForkedFromMetadata(destPath, skill.id, contentHash, source);
12806
+ await injectForkedFromMetadata(destPath, skill.id, contentHash, {
12807
+ ...source !== void 0 && { source }
12808
+ });
12587
12809
  return { skillId: skill.id, contentHash, sourcePath, destPath };
12588
12810
  }
12589
12811
  async function resolveLocalCopiedSkill(skill, localPath) {
@@ -12596,6 +12818,32 @@ async function resolveLocalCopiedSkill(skill, localPath) {
12596
12818
  local: true
12597
12819
  };
12598
12820
  }
12821
+ async function copyEachSkill(selectedSkillIds, copyOne) {
12822
+ const outcomes = await Promise.all(
12823
+ selectedSkillIds.map(async (skillId) => attemptCopy(skillId, copyOne))
12824
+ );
12825
+ const failures = outcomes.filter(isCopyFailure);
12826
+ if (failures.length > 0) throw new Error(copyFailureMessage(failures, selectedSkillIds.length));
12827
+ return outcomes.filter(isCopySuccess).map((outcome) => outcome.copied);
12828
+ }
12829
+ async function attemptCopy(skillId, copyOne) {
12830
+ try {
12831
+ return { skillId, copied: await copyOne(skillId) };
12832
+ } catch (error) {
12833
+ return { skillId, problem: getErrorMessage(error) };
12834
+ }
12835
+ }
12836
+ function isCopyFailure(outcome) {
12837
+ return "problem" in outcome;
12838
+ }
12839
+ function isCopySuccess(outcome) {
12840
+ return "copied" in outcome;
12841
+ }
12842
+ function copyFailureMessage(failures, attempted) {
12843
+ const lines = failures.map((failure) => ` ${failure.skillId}: ${failure.problem}`);
12844
+ return `Could not copy ${failures.length} of ${attempted} skills:
12845
+ ${lines.join("\n")}`;
12846
+ }
12599
12847
  function getFlattenedSkillDestPath(skill, localSkillsDir) {
12600
12848
  return resolveSkillPath(localSkillsDir, skill.id);
12601
12849
  }
@@ -12608,34 +12856,30 @@ async function copySkillToLocalFlattened(skill, localSkillsDir, sourceResult) {
12608
12856
  );
12609
12857
  }
12610
12858
  async function copySkillsToLocalFlattened(selectedSkillIds, localSkillsDir, sourceResult, sourceSelections) {
12611
- const results = await Promise.all(
12612
- selectedSkillIds.map(async (skillId) => {
12613
- const skill = getSkillById(skillId);
12614
- const selectedSource = sourceSelections?.[skillId];
12615
- const userSelectedRemote = selectedSource && selectedSource !== EJECT_SOURCE;
12616
- if (skill.local && skill.localPath && !userSelectedRemote) {
12617
- const destPath = getFlattenedSkillDestPath(skill, localSkillsDir);
12618
- const alreadyInPlace = path8.resolve(skill.localPath) === path8.resolve(destPath);
12619
- if (alreadyInPlace) {
12620
- return resolveLocalCopiedSkill(skill, skill.localPath);
12621
- }
12622
- const contentHash = await generateSkillHash(skill.localPath);
12623
- await ensureDir(path8.dirname(destPath));
12624
- await copy(skill.localPath, destPath);
12625
- return { skillId: skill.id, sourcePath: skill.localPath, destPath, contentHash };
12626
- }
12859
+ return copyEachSkill(selectedSkillIds, async (skillId) => {
12860
+ const skill = getSkillById(skillId);
12861
+ const selectedSource = sourceSelections?.[skillId];
12862
+ const userSelectedRemote = selectedSource && selectedSource !== EJECT_SOURCE;
12863
+ if (!skill.local || !skill.localPath || userSelectedRemote) {
12627
12864
  return copySkillToLocalFlattened(skill, localSkillsDir, sourceResult);
12628
- })
12629
- );
12630
- return results;
12865
+ }
12866
+ const destPath = getFlattenedSkillDestPath(skill, localSkillsDir);
12867
+ if (path9.resolve(skill.localPath) === path9.resolve(destPath)) {
12868
+ return resolveLocalCopiedSkill(skill, skill.localPath);
12869
+ }
12870
+ const contentHash = await generateSkillHash(skill.localPath);
12871
+ await ensureDir(path9.dirname(destPath));
12872
+ await copy(skill.localPath, destPath);
12873
+ return { skillId: skill.id, sourcePath: skill.localPath, destPath, contentHash };
12874
+ });
12631
12875
  }
12632
12876
 
12633
12877
  // src/cli/lib/skills/skill-plugin-compiler.ts
12634
- import path21 from "path";
12878
+ import path23 from "path";
12635
12879
  import { parse as parseYaml2 } from "yaml";
12636
12880
 
12637
12881
  // src/cli/lib/plugins/plugin-manifest.ts
12638
- import path9 from "path";
12882
+ import path10 from "path";
12639
12883
  var PLUGIN_DIR_NAME = PLUGIN_MANIFEST_DIR;
12640
12884
  var PLUGIN_MANIFEST_FILE2 = STANDARD_FILES.PLUGIN_JSON;
12641
12885
  var AGENT_PLUGIN_PREFIX = "agent-";
@@ -12666,8 +12910,8 @@ function generateAgentPluginManifest(options) {
12666
12910
  };
12667
12911
  }
12668
12912
  async function writePluginManifest(outputDir, manifest) {
12669
- const pluginDir = path9.join(outputDir, PLUGIN_DIR_NAME);
12670
- const manifestPath = path9.join(pluginDir, PLUGIN_MANIFEST_FILE2);
12913
+ const pluginDir = path10.join(outputDir, PLUGIN_DIR_NAME);
12914
+ const manifestPath = path10.join(pluginDir, PLUGIN_MANIFEST_FILE2);
12671
12915
  await ensureDir(pluginDir);
12672
12916
  const content = JSON.stringify(manifest, null, 2);
12673
12917
  await writeFile(manifestPath, content);
@@ -12676,20 +12920,20 @@ async function writePluginManifest(outputDir, manifest) {
12676
12920
 
12677
12921
  // src/cli/lib/plugins/plugin-finder.ts
12678
12922
  import os5 from "os";
12679
- import path10 from "path";
12923
+ import path11 from "path";
12680
12924
  import { zip } from "remeda";
12681
12925
  function getUserPluginsDir() {
12682
- return path10.join(os5.homedir(), CLAUDE_DIR, PLUGINS_SUBDIR);
12926
+ return path11.join(os5.homedir(), CLAUDE_DIR, PLUGINS_SUBDIR);
12683
12927
  }
12684
12928
  function getProjectPluginsDir(projectDir) {
12685
12929
  const dir = projectDir ?? process.cwd();
12686
- return path10.join(dir, CLAUDE_DIR, PLUGINS_SUBDIR);
12930
+ return path11.join(dir, CLAUDE_DIR, PLUGINS_SUBDIR);
12687
12931
  }
12688
12932
  function getPluginAgentsDir(pluginDir) {
12689
- return path10.join(pluginDir, "agents");
12933
+ return path11.join(pluginDir, "agents");
12690
12934
  }
12691
12935
  function getPluginManifestPath(pluginDir) {
12692
- return path10.join(pluginDir, PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE);
12936
+ return path11.join(pluginDir, PLUGIN_MANIFEST_DIR, PLUGIN_MANIFEST_FILE);
12693
12937
  }
12694
12938
  async function readPluginManifest(pluginDir) {
12695
12939
  const manifestPath = getPluginManifestPath(pluginDir);
@@ -12728,7 +12972,7 @@ import { readdir } from "fs/promises";
12728
12972
 
12729
12973
  // src/cli/lib/installation/installation.ts
12730
12974
  import os6 from "os";
12731
- import path11 from "path";
12975
+ import path12 from "path";
12732
12976
  var INSTALL_MODE_LABELS = {
12733
12977
  plugin: "Plugin",
12734
12978
  mixed: "Mixed",
@@ -12743,8 +12987,8 @@ function declaresNoContent(config) {
12743
12987
  }
12744
12988
  function deriveInstallMode(skills) {
12745
12989
  if (skills.length === 0) return "eject";
12746
- const hasEject = skills.some((s) => s.source === EJECT_SOURCE);
12747
- const hasPlugin = skills.some((s) => s.source !== EJECT_SOURCE);
12990
+ const hasEject = skills.some((s) => s.origin === EJECT_SOURCE);
12991
+ const hasPlugin = skills.some((s) => s.origin !== EJECT_SOURCE);
12748
12992
  if (hasEject && hasPlugin) return "mixed";
12749
12993
  return hasEject ? "eject" : "plugin";
12750
12994
  }
@@ -12764,10 +13008,10 @@ async function detectInstallationInDir(dir) {
12764
13008
  return {
12765
13009
  mode,
12766
13010
  configPath,
12767
- agentsDir: path11.join(dir, CLAUDE_DIR, STANDARD_DIRS.AGENTS),
13011
+ agentsDir: path12.join(dir, CLAUDE_DIR, STANDARD_DIRS.AGENTS),
12768
13012
  // Mixed mode has local skills in .claude/skills/ and plugins in cache;
12769
13013
  // use .claude/skills/ as the primary skillsDir (same as eject mode)
12770
- skillsDir: path11.join(dir, CLAUDE_DIR, mode === "plugin" ? PLUGINS_SUBDIR : "skills"),
13014
+ skillsDir: path12.join(dir, CLAUDE_DIR, mode === "plugin" ? PLUGINS_SUBDIR : "skills"),
12771
13015
  projectDir: dir
12772
13016
  };
12773
13017
  }
@@ -12785,26 +13029,49 @@ async function detectInstallation(projectDir = process.cwd()) {
12785
13029
 
12786
13030
  // src/cli/lib/installation/local-installer.ts
12787
13031
  import os13 from "os";
12788
- import path18 from "path";
12789
- import { unique as unique4 } from "remeda";
13032
+ import path20 from "path";
13033
+ import { unique as unique5 } from "remeda";
12790
13034
 
12791
13035
  // src/cli/lib/loading/source-fetcher.ts
12792
13036
  import { createHash as createHash2 } from "crypto";
12793
13037
  import { downloadTemplate } from "giget";
12794
13038
  import os7 from "os";
12795
- import path12 from "path";
13039
+ import path13 from "path";
13040
+
13041
+ // src/cli/lib/validate-kebab-name.ts
13042
+ import { unique as unique3 } from "remeda";
13043
+ var KEBAB_CASE_CHARACTER = /[a-z0-9-]/;
13044
+ function charactersOutsideKebabCase(name) {
13045
+ return unique3([...name].filter((character) => !KEBAB_CASE_CHARACTER.test(character)));
13046
+ }
13047
+ function validateKebabCaseName(name, noun) {
13048
+ if (!name || name.trim() === "") {
13049
+ return `${noun} name is required`;
13050
+ }
13051
+ if (!KEBAB_CASE_PATTERN.test(name)) {
13052
+ return `${noun} name must be kebab-case (lowercase letters, numbers, and hyphens, starting with a letter)`;
13053
+ }
13054
+ return null;
13055
+ }
12796
13056
 
12797
13057
  // src/cli/utils/messages.ts
12798
13058
  var ERROR_MESSAGES = {
12799
13059
  UNKNOWN_ERROR: "Unknown error occurred",
12800
13060
  UNKNOWN_ERROR_SHORT: "Unknown error",
12801
13061
  NO_INSTALLATION: `No installation found. Run '${CLI_INVOKE_COMMAND} init' first to set up ${DEFAULT_BRANDING.NAME}`,
12802
- NO_SKILLS_FOUND: "No skills found",
12803
- VALIDATION_FAILED: "Validation failed",
12804
- FAILED_RESOLVE_SOURCE: "Failed to resolve source",
13062
+ FAILED_RESOLVE_SOURCE: "Failed to resolve marketplace",
12805
13063
  FAILED_LOAD_AGENT_PARTIALS: "Failed to load agent partials",
12806
13064
  FAILED_COMPILE_AGENTS: "Failed to compile agents",
12807
- CLAUDE_CLI_NOT_FOUND: `Claude CLI not found \u2014 '${CLI_INVOKE_COMMAND} update' refreshes marketplaces through it. Install Claude Code first: https://claude.ai/code`
13065
+ CLAUDE_CLI_NOT_FOUND: `Claude CLI not found \u2014 '${CLI_INVOKE_COMMAND} update' refreshes marketplaces through it. Install Claude Code first: https://claude.ai/code`,
13066
+ /**
13067
+ * What `compile` refuses with when every pass discovered zero skills. It is reached only
13068
+ * after an installation was detected, so the state it describes is a configuration with
13069
+ * nothing installed under it — the same state `doctor` reports as `config-empty` and
13070
+ * names `init` for, because `init` on a config that declares nothing opens the wizard
13071
+ * rather than the dashboard. `edit` is the wrong half of the pair here: it modifies the
13072
+ * currently installed skills, and there are none to modify.
13073
+ */
13074
+ NO_SKILLS_TO_COMPILE: `No skills found. Run '${CLI_INVOKE_COMMAND} init' to choose skills, or add your own under ${LOCAL_SKILLS_PATH}/.`
12808
13075
  };
12809
13076
  var SUCCESS_MESSAGES = {
12810
13077
  UNINSTALL_COMPLETE: "Uninstall complete!",
@@ -12814,23 +13081,29 @@ var SUCCESS_MESSAGES = {
12814
13081
  var STATUS_MESSAGES = {
12815
13082
  INSTALLING_PLUGINS: "Installing skill plugins...",
12816
13083
  LOADING_SKILLS: "Loading skills...",
12817
- LOADING_MARKETPLACE_SOURCE: "Loading marketplace source...",
13084
+ LOADING_MARKETPLACE_SOURCE: "Loading marketplace...",
12818
13085
  RECOMPILING_AGENTS: "Recompiling agents...",
12819
13086
  COMPILING_AGENTS: "Compiling agents...",
12820
13087
  DISCOVERING_SKILLS: "Discovering skills...",
12821
- RESOLVING_SOURCE: "Resolving source...",
12822
- RESOLVING_MARKETPLACE_SOURCE: "Resolving marketplace source...",
13088
+ RESOLVING_SOURCE: "Resolving marketplace...",
13089
+ RESOLVING_MARKETPLACE_SOURCE: "Resolving marketplace...",
12823
13090
  LOADING_AGENT_PARTIALS: "Loading agent partials...",
12824
13091
  FETCHING_REPOSITORY: "Fetching repository...",
12825
13092
  COPYING_SKILLS: "Copying skills...",
12826
- UPDATING_PLUGIN_SKILLS: "Updating plugin skills...",
12827
13093
  /**
12828
- * Printed when revalidation found the remote source had moved on. It is the
13094
+ * Printed when revalidation found the remote marketplace had moved on. It is the
12829
13095
  * only warning a user gets that this load costs a download rather than a
12830
13096
  * cache read, so it goes out before the download starts, not after.
12831
13097
  */
12832
13098
  MARKETPLACE_HAS_NEWER_CONTENT: "Marketplace has newer content \u2014 fetching the update..."
12833
13099
  };
13100
+ var AGENT_PARTIAL_FILES = [
13101
+ STANDARD_FILES.IDENTITY_MD,
13102
+ STANDARD_FILES.PLAYBOOK_MD,
13103
+ STANDARD_FILES.CRITICAL_REQUIREMENTS_MD,
13104
+ STANDARD_FILES.CRITICAL_REMINDERS_MD,
13105
+ STANDARD_FILES.OUTPUT_MD
13106
+ ];
12834
13107
  var INFO_MESSAGES = {
12835
13108
  NO_CHANGES_MADE: "No changes made.",
12836
13109
  RUN_COMPILE: `Run '${CLI_INVOKE_COMMAND} compile' to include imported skills in your agents.`,
@@ -12845,7 +13118,9 @@ var INFO_MESSAGES = {
12845
13118
  * their work — this line says so once, as information rather than a warning.
12846
13119
  */
12847
13120
  EJECTED_SKILLS_USER_OWNED: `Ejected skills are yours to own \u2014 '${CLI_INVOKE_COMMAND} update' does not change them.`,
12848
- NO_PLUGIN_MARKETPLACES: "No plugin marketplaces are configured \u2014 nothing to refresh."
13121
+ NO_PLUGIN_MARKETPLACES: "No plugin marketplaces are configured \u2014 nothing to refresh.",
13122
+ /** Printed under the destination `eject agent-partials` just named, so it names files only. */
13123
+ AGENT_PARTIALS_CUSTOMIZABLE: `Each sub-agent directory there holds ${AGENT_PARTIAL_FILES.join(", ")} \u2014 edit those to customize that sub-agent.`
12849
13124
  };
12850
13125
  function pluginsInstalled(count) {
12851
13126
  return `Installed ${count} skill plugins`;
@@ -12885,6 +13160,31 @@ function marketplacesRefreshFailed(marketplaces) {
12885
13160
  const subject = marketplaces.length === 1 ? "marketplace" : "marketplaces";
12886
13161
  return `${marketplaces.length} ${subject} could not be updated: ${marketplaces.join(", ")}`;
12887
13162
  }
13163
+ function marketplaceOwnerHasNoName(packageJsonPath) {
13164
+ return [
13165
+ `A marketplace's owner must have a name, and no name could be read from 'author' in ${packageJsonPath}.`,
13166
+ `Nothing was written: a ${MARKETPLACE_JSON} whose 'owner.name' is empty is refused by this CLI's own reader, so the build would have reported success for a marketplace nobody can install from.`,
13167
+ `Set 'author' in that file \u2014 either "Jane Doe <jane@example.com>" or { "name": "Jane Doe" } \u2014 and build again.`
13168
+ ].join("\n\n");
13169
+ }
13170
+ function marketplaceHasNoVersion(packageJsonPath) {
13171
+ return [
13172
+ `A marketplace must have a version, and 'version' in ${packageJsonPath} is empty.`,
13173
+ `Nothing was written: a ${MARKETPLACE_JSON} whose 'version' is empty is refused by this CLI's own reader, so the build would have reported success for a marketplace nobody can install from.`,
13174
+ `Set 'version' in that file \u2014 "0.1.0" if this is its first release \u2014 and build again.`
13175
+ ].join("\n\n");
13176
+ }
13177
+ function marketplaceNameNotPublishable(name, packageJsonPath) {
13178
+ const offenders = charactersOutsideKebabCase(name);
13179
+ return [
13180
+ `Marketplace name '${name}', read from 'name' in ${packageJsonPath}, is not a name a marketplace may publish under.`,
13181
+ ...offenders.length > 0 ? [
13182
+ `It carries ${offenders.map((character) => `'${character}'`).join(" and ")}, which a marketplace name may not.`
13183
+ ] : [],
13184
+ `A marketplace name is kebab-case: lowercase letters, numbers and hyphens, starting with a letter.`,
13185
+ `Publish under a name of your own instead: '${CLI_INVOKE_COMMAND} build marketplace --name <your-marketplace>'.`
13186
+ ].join("\n\n");
13187
+ }
12888
13188
  function configTypesRefreshFailed(reason) {
12889
13189
  return `Could not refresh ${STANDARD_FILES.CONFIG_TYPES_TS} \u2014 type unions may be stale: ${reason}`;
12890
13190
  }
@@ -12918,17 +13218,94 @@ function skillMetadataUnusableError(entries) {
12918
13218
  function savedSkillMetadataUnusableError(entries) {
12919
13219
  return [
12920
13220
  metadataUnusableOpening(entries),
12921
- `A config entry naming a skill this file cannot describe is dropped as one this source does not carry \u2014 and a file that can be repaired must not cost an install its record.`,
13221
+ `A config entry naming a skill this file cannot describe is dropped as one this marketplace does not carry \u2014 and a file that can be repaired must not cost an install its record.`,
12922
13222
  METADATA_UNUSABLE_WAY_OUT
12923
13223
  ].join("\n");
12924
13224
  }
12925
13225
  var SHARED_CONFIG_GREENFIELD_HINT = "installing a shared configuration is a fresh setup, not a merge";
13226
+ function sharedConfigDestinations(id) {
13227
+ return [
13228
+ ` Install it: ${CLI_INVOKE_COMMAND} init --from ${id}`,
13229
+ ` Open it: ${editorConfigUrl(id)}`
13230
+ ];
13231
+ }
12926
13232
  function sharedConfigExistingInstall(configPath) {
12927
13233
  return `An installation already exists at ${configPath}. Run '${CLI_INVOKE_COMMAND} uninstall' first \u2014 ${SHARED_CONFIG_GREENFIELD_HINT}.`;
12928
13234
  }
12929
13235
  function sharedConfigGlobalInstall(configPath) {
12930
13236
  return `This configuration installs global-scoped content, and a global installation already exists at ${configPath}. Run '${CLI_INVOKE_COMMAND} uninstall' from your home directory first \u2014 ${SHARED_CONFIG_GREENFIELD_HINT}.`;
12931
13237
  }
13238
+ function sharedConfigProjectScopeAtHome(skillIds, agentNames) {
13239
+ return [
13240
+ "This configuration cannot be installed here: the home directory is the global scope, and a global installation holds only global-scoped content \u2014 so these project-scoped entries have nowhere to be written:",
13241
+ ...skillIds.map((id) => ` skill ${id} (scope: project)`),
13242
+ ...agentNames.map((name) => ` sub-agent ${name} (scope: project)`),
13243
+ "Run this from inside a project directory, or re-share it with each entry above at global scope."
13244
+ ].join("\n");
13245
+ }
13246
+ function skippedUnknownSkills(skillIds) {
13247
+ return `Skipped ${skillIds.length} skill(s) this catalog does not know: ${skillIds.join(", ")}`;
13248
+ }
13249
+ function skippedUnknownAgents(agentNames) {
13250
+ return `Skipped ${agentNames.length} unknown sub-agent(s): ${agentNames.join(", ")}`;
13251
+ }
13252
+ function carriedSkillsWritten(skillIds) {
13253
+ return `Wrote ${skillIds.length} skill(s) this configuration carries: ${skillIds.join(", ")}`;
13254
+ }
13255
+ var SHARED_CONFIG_APPLY = {
13256
+ PREVIEW_HEADING: "Applying this configuration will remove:",
13257
+ SKILLS_HEADING: "Skills:",
13258
+ AGENTS_HEADING: "Sub-agents:",
13259
+ /**
13260
+ * The two headings a PROJECT run prints instead, for entries that live at global scope. They
13261
+ * exist only there: at the home directory every entry is global, so a heading saying so would
13262
+ * label the whole list with the one fact the location already states.
13263
+ */
13264
+ GLOBAL_SKILLS_HEADING: "Skills installed globally:",
13265
+ GLOBAL_AGENTS_HEADING: "Sub-agents installed globally:",
13266
+ /** Printed in place of the sections when the payload takes nothing away. */
13267
+ NOTHING_REMOVED: "Nothing is removed \u2014 this configuration only adds and re-tunes.",
13268
+ CONFIRM: "Apply this configuration?"
13269
+ };
13270
+ function sharedConfigNeedsTerminal(id) {
13271
+ return [
13272
+ `Applying configuration '${id}' removes whatever it leaves out, so it has to be confirmed \u2014 and there is no terminal here to confirm it at.`,
13273
+ `Run '${CLI_INVOKE_COMMAND} edit --from ${id}' from a terminal, or '${CLI_INVOKE_COMMAND} init --from ${id}' in a clean directory, which installs without removing anything.`
13274
+ ].join("\n");
13275
+ }
13276
+ var SHARED_CONFIG_ONE_DIRECTION = "--ui and --from are the two directions of the same round trip: --ui hands this installation to the editor, and --from applies one back. Run one, then the other.";
13277
+ function globallyInstalledRemoved(otherProjects) {
13278
+ return [
13279
+ "These are installed globally, and a global install is shared by every project on this machine.",
13280
+ ...globalRemovalReach(otherProjects),
13281
+ `To keep them, answer no and re-share the configuration with them included.`
13282
+ ].join("\n");
13283
+ }
13284
+ function globalRemovalReach(otherProjects) {
13285
+ if (otherProjects.length === 0) {
13286
+ return [
13287
+ "No other project is registered here, so nothing else changes today \u2014 a project set up later inherits whatever the global install holds then."
13288
+ ];
13289
+ }
13290
+ return [
13291
+ `Also affects ${otherProjects.length} other registered project(s):`,
13292
+ ...otherProjects.map((projectDir) => ` ${projectDir}`)
13293
+ ];
13294
+ }
13295
+ function authoredHereKept(skillIds) {
13296
+ return [
13297
+ "Kept \u2014 written here rather than installed, so a shared configuration never carried them:",
13298
+ ...skillIds.map((id) => ` skill ${id}`),
13299
+ `Remove them with '${CLI_INVOKE_COMMAND} edit'.`
13300
+ ].join("\n");
13301
+ }
13302
+ function unplaceableKept(skillIds) {
13303
+ return [
13304
+ "Kept \u2014 named by this configuration, and this catalogue cannot place them:",
13305
+ ...skillIds.map((id) => ` skill ${id}`),
13306
+ `Run '${CLI_INVOKE_COMMAND} update' to refresh this installation's marketplace, then apply the configuration again.`
13307
+ ].join("\n");
13308
+ }
12932
13309
  function configUnreadableError(configLoadFailure2) {
12933
13310
  return [
12934
13311
  configLoadFailure2,
@@ -12940,8 +13317,10 @@ function configUnreadableError(configLoadFailure2) {
12940
13317
  function registeredProjectsUpdateFailed(reason) {
12941
13318
  return `Could not update registered projects \u2014 their configs may still reference the uninstalled global content: ${reason}`;
12942
13319
  }
12943
- function compiledAgentsKept(agentsDir) {
12944
- return `Kept compiled agents in ${agentsDir}/ \u2014 identifying which of them this CLI compiled needs the configuration, and this run has none it could read.`;
13320
+ function unmarkedAgentsKept(agentsDir, count) {
13321
+ const subject = count === 1 ? "agent" : "agents";
13322
+ const object = count === 1 ? "it" : "them";
13323
+ return `Kept ${count} ${subject} in ${agentsDir}/ \u2014 no ${DEFAULT_PLUGIN_NAME} marker, so this CLI did not compile ${object}.`;
12945
13324
  }
12946
13325
  var UNINSTALL_PLAN = {
12947
13326
  PREVIEW_HEADING: "The following will be removed:",
@@ -12950,7 +13329,7 @@ var UNINSTALL_PLAN = {
12950
13329
  CONFIG_HEADING: "Config:"
12951
13330
  };
12952
13331
  function localSkillsRemoval(skillsDir) {
12953
- return `${skillsDir}/ (matching sources)`;
13332
+ return `${skillsDir}/ (matching the marketplace)`;
12954
13333
  }
12955
13334
  function compiledAgentsRemoval(agentsDir) {
12956
13335
  return `${agentsDir}/ (CLI-compiled)`;
@@ -12968,7 +13347,7 @@ function sanitizeSourceForCache(source) {
12968
13347
  }
12969
13348
  function getCacheDir(source) {
12970
13349
  const sanitized = sanitizeSourceForCache(source) || "unknown";
12971
- return path12.join(CACHE_DIR, "sources", sanitized);
13350
+ return path13.join(CACHE_DIR, "sources", sanitized);
12972
13351
  }
12973
13352
  async function fetchFromSource(source, options = {}) {
12974
13353
  const { subdir } = options;
@@ -12978,19 +13357,19 @@ async function fetchFromSource(source, options = {}) {
12978
13357
  return fetchFromRemoteSource(source, subdir);
12979
13358
  }
12980
13359
  async function fetchFromLocalSource(source, subdir) {
12981
- const fullPath = subdir ? path12.join(source, subdir) : source;
12982
- const absolutePath = path12.isAbsolute(fullPath) ? fullPath : path12.resolve(process.cwd(), fullPath);
13360
+ const fullPath = subdir ? path13.join(source, subdir) : source;
13361
+ const absolutePath = path13.isAbsolute(fullPath) ? fullPath : path13.resolve(process.cwd(), fullPath);
12983
13362
  if (!await directoryExists(absolutePath)) {
12984
13363
  throw new Error(
12985
- `Local source not found: '${absolutePath}'
13364
+ `Local marketplace not found: '${absolutePath}'
12986
13365
 
12987
- Nothing is at that path, and a local source must be a directory holding a skills marketplace.
13366
+ Nothing is at that path, and a local marketplace must be a directory holding skills.
12988
13367
 
12989
13368
  Check it for a typo, or name a marketplace that exists:
12990
- --source ${DEFAULT_SOURCE}`
13369
+ --marketplace ${DEFAULT_SOURCE}`
12991
13370
  );
12992
13371
  }
12993
- verbose(`Using local source: ${absolutePath}`);
13372
+ verbose(`Using local marketplace: ${absolutePath}`);
12994
13373
  return {
12995
13374
  path: absolutePath,
12996
13375
  fromCache: false,
@@ -13009,8 +13388,8 @@ function getGigetCacheDir(source) {
13009
13388
  return void 0;
13010
13389
  }
13011
13390
  const templateName = uriMatch.groups.repo.replace("/", "-").replace(/[^\da-z-]/gi, "-");
13012
- const gigetCacheRoot = process.env.XDG_CACHE_HOME ? path12.resolve(process.env.XDG_CACHE_HOME, "giget") : path12.resolve(os7.homedir(), ".cache", "giget");
13013
- return path12.join(gigetCacheRoot, providerName, templateName);
13391
+ const gigetCacheRoot = process.env.XDG_CACHE_HOME ? path13.resolve(process.env.XDG_CACHE_HOME, "giget") : path13.resolve(os7.homedir(), ".cache", "giget");
13392
+ return path13.join(gigetCacheRoot, providerName, templateName);
13014
13393
  }
13015
13394
  async function clearGigetCache(source) {
13016
13395
  const gigetDir = getGigetCacheDir(source);
@@ -13029,14 +13408,14 @@ async function fetchFromRemoteSource(source, subdir) {
13029
13408
  const verdict = await revalidateCachedCopy(cacheDir, source);
13030
13409
  const cached = { path: cacheDir, fromCache: true, source: fullSource };
13031
13410
  if (verdict === "current" || verdict === "unreachable") {
13032
- verbose(`Using cached source: ${cacheDir}`);
13411
+ verbose(`Using cached marketplace: ${cacheDir}`);
13033
13412
  return cached;
13034
13413
  }
13035
13414
  announceRefetch(verdict);
13036
13415
  await clearGigetCache(source);
13037
13416
  await remove(cacheDir);
13038
13417
  }
13039
- await ensureDir(path12.dirname(cacheDir));
13418
+ await ensureDir(path13.dirname(cacheDir));
13040
13419
  try {
13041
13420
  const result = await downloadTemplate(fullSource, {
13042
13421
  dir: cacheDir,
@@ -13111,7 +13490,7 @@ async function readFetchRecord(cacheDir) {
13111
13490
  if (!raw) return void 0;
13112
13491
  const parsed = sourceRevalidationSchema.safeParse(parseJsonOrUndefined(raw));
13113
13492
  if (!parsed.success) {
13114
- verbose(`Unusable fetch record at ${fetchRecordPath(cacheDir)} \u2014 re-fetching the source`);
13493
+ verbose(`Unusable fetch record at ${fetchRecordPath(cacheDir)} \u2014 re-fetching the marketplace`);
13115
13494
  return void 0;
13116
13495
  }
13117
13496
  return parsed.data;
@@ -13179,23 +13558,25 @@ If you're behind a corporate proxy, you may need to set:
13179
13558
  }
13180
13559
  return new Error(`Failed to fetch ${source}: ${message}`);
13181
13560
  }
13561
+ var MarketplaceManifestAbsentError = class extends Error {
13562
+ };
13182
13563
  async function fetchMarketplace(source) {
13183
13564
  const result = await fetchFromSource(source, {
13184
13565
  subdir: ""
13185
13566
  // Root of repo
13186
13567
  });
13187
13568
  const marketplacePath = marketplaceManifestPath(result.path);
13188
- if (!await directoryExists(path12.dirname(marketplacePath))) {
13189
- throw new Error(
13190
- `Marketplace not found for source: ${source}
13569
+ if (!await fileExists(marketplacePath)) {
13570
+ throw new MarketplaceManifestAbsentError(
13571
+ `Marketplace not found at: ${source}
13191
13572
 
13192
13573
  The .claude-plugin/marketplace.json file is missing from this repository.
13193
13574
 
13194
13575
  Possible causes:
13195
- - The source URL may be incorrect
13576
+ - The marketplace URL may be incorrect
13196
13577
  - The repository may not have a marketplace configured
13197
13578
 
13198
- To create a marketplace, add a .claude-plugin/marketplace.json file to your source repository.`
13579
+ To create a marketplace, add a .claude-plugin/marketplace.json file to your marketplace repository.`
13199
13580
  );
13200
13581
  }
13201
13582
  const content = await readFileSafe(marketplacePath, MAX_MARKETPLACE_FILE_SIZE);
@@ -13255,22 +13636,26 @@ Too many plugins: ${marketplace.plugins.length} (limit: ${MAX_MARKETPLACE_PLUGIN
13255
13636
 
13256
13637
  // src/cli/lib/loading/multi-source-loader.ts
13257
13638
  import os8 from "os";
13639
+ function isLocalOnlySkill(skill) {
13640
+ return skill?.availableSources !== void 0 && !skill.availableSources.some((source) => source.type !== "local");
13641
+ }
13258
13642
  function addAvailableSource(skill, source) {
13259
13643
  skill.availableSources = skill.availableSources ?? [];
13260
13644
  skill.availableSources.push(source);
13261
13645
  }
13262
- async function loadSkillsFromAllSources(primaryMatrix, sourceConfig, projectDir, marketplace) {
13646
+ async function loadSkillsFromAllSources(primaryMatrix, sourceConfig, projectDir, marketplace, unbackedSkillIds = /* @__PURE__ */ new Set()) {
13263
13647
  const resolvedMarketplace = marketplace ?? sourceConfig.marketplace;
13264
13648
  const primarySourceName = resolvedMarketplace ?? DEFAULT_PUBLIC_SOURCE_NAME;
13265
13649
  const primarySourceType = isDefaultSource(sourceConfig.source) ? "public" : "private";
13266
- tagPrimarySourceSkills(primaryMatrix, primarySourceName, primarySourceType);
13650
+ tagPrimarySourceSkills(primaryMatrix, primarySourceName, primarySourceType, unbackedSkillIds);
13267
13651
  tagLocalSkills(primaryMatrix);
13268
13652
  await tagPluginSkills(primaryMatrix, projectDir, primarySourceName, primarySourceType);
13269
13653
  setActiveSources(primaryMatrix);
13270
13654
  }
13271
- function tagPrimarySourceSkills(matrix2, sourceName, sourceType) {
13272
- for (const [, skill] of typedEntries(matrix2.skills)) {
13655
+ function tagPrimarySourceSkills(matrix2, sourceName, sourceType, unbackedSkillIds) {
13656
+ for (const [id, skill] of typedEntries(matrix2.skills)) {
13273
13657
  if (!skill) continue;
13658
+ if (unbackedSkillIds.has(id)) continue;
13274
13659
  const source = {
13275
13660
  name: sourceName,
13276
13661
  type: sourceType,
@@ -13409,9 +13794,60 @@ async function resolveAgents(agents, skills, compileConfig, _projectRoot) {
13409
13794
 
13410
13795
  // src/cli/lib/compiler.ts
13411
13796
  import { Liquid } from "liquidjs";
13412
- import path13 from "path";
13797
+ import path15 from "path";
13413
13798
  import { pipe as pipe2, flatMap as flatMap2, filter, uniqueBy as uniqueBy3 } from "remeda";
13414
13799
 
13800
+ // src/cli/lib/agents/agent-provenance.ts
13801
+ import path14 from "path";
13802
+ import { z as z7 } from "zod";
13803
+ var MARKER_OPEN = `<!-- Generated by ${DEFAULT_PLUGIN_NAME} v`;
13804
+ var MARKER_NOTICE = " \u2014 do not edit; compile rewrites this file";
13805
+ var MARKER_CLOSE = " -->";
13806
+ var FRONTMATTER_FENCE = "---";
13807
+ var PACKAGE_JSON_FILE = "package.json";
13808
+ var ownPackageJsonSchema = z7.object({ version: z7.string().min(1) });
13809
+ function provenanceMarker(version) {
13810
+ return `${MARKER_OPEN}${version}${MARKER_NOTICE}${MARKER_CLOSE}`;
13811
+ }
13812
+ function isProvenanceMarker(line) {
13813
+ return line.startsWith(MARKER_OPEN) && line.endsWith(MARKER_CLOSE);
13814
+ }
13815
+ function bodyStartIndex(lines) {
13816
+ if (lines[0] !== FRONTMATTER_FENCE) return 0;
13817
+ const closingFence = lines.indexOf(FRONTMATTER_FENCE, 1);
13818
+ if (closingFence === -1) return 0;
13819
+ return closingFence + 1;
13820
+ }
13821
+ function hasProvenanceMarker(content) {
13822
+ const lines = content.split("\n");
13823
+ const firstBodyLine = lines[bodyStartIndex(lines)];
13824
+ return firstBodyLine !== void 0 && isProvenanceMarker(firstBodyLine);
13825
+ }
13826
+ function stampProvenanceMarker(content, version) {
13827
+ const lines = content.split("\n");
13828
+ const bodyStart = bodyStartIndex(lines);
13829
+ const replacedLines = hasProvenanceMarker(content) ? 1 : 0;
13830
+ return [
13831
+ ...lines.slice(0, bodyStart),
13832
+ provenanceMarker(version),
13833
+ ...lines.slice(bodyStart + replacedLines)
13834
+ ].join("\n");
13835
+ }
13836
+ var ownVersion;
13837
+ function cliVersion() {
13838
+ ownVersion ??= readOwnVersion();
13839
+ return ownVersion;
13840
+ }
13841
+ async function readOwnVersion() {
13842
+ const manifestPath = path14.join(PROJECT_ROOT, PACKAGE_JSON_FILE);
13843
+ const raw = JSON.parse(await readFile(manifestPath));
13844
+ const parsed = ownPackageJsonSchema.safeParse(raw);
13845
+ if (!parsed.success) {
13846
+ throw new Error(`Could not read this CLI's own version from ${manifestPath}`);
13847
+ }
13848
+ return parsed.data.version;
13849
+ }
13850
+
13415
13851
  // src/cli/lib/validation-result.ts
13416
13852
  function validResult() {
13417
13853
  return { valid: true, errors: [], warnings: [] };
@@ -13490,24 +13926,24 @@ function sanitizeCompiledAgentData(data) {
13490
13926
  async function readAgentFiles(name, agent, projectRoot) {
13491
13927
  const agentSourceRoot = agent.sourceRoot || projectRoot;
13492
13928
  const agentBaseDir = agent.agentBaseDir || DIRS.agents;
13493
- const agentDir = path13.join(agentSourceRoot, agentBaseDir, agent.path || name);
13494
- const identity = await readFile(path13.join(agentDir, STANDARD_FILES.IDENTITY_MD));
13495
- const playbook = await readFile(path13.join(agentDir, STANDARD_FILES.PLAYBOOK_MD));
13929
+ const agentDir = path15.join(agentSourceRoot, agentBaseDir, agent.path || name);
13930
+ const identity = await readFile(path15.join(agentDir, STANDARD_FILES.IDENTITY_MD));
13931
+ const playbook = await readFile(path15.join(agentDir, STANDARD_FILES.PLAYBOOK_MD));
13496
13932
  const criticalRequirementsTop = await readFileOptional(
13497
- path13.join(agentDir, STANDARD_FILES.CRITICAL_REQUIREMENTS_MD),
13933
+ path15.join(agentDir, STANDARD_FILES.CRITICAL_REQUIREMENTS_MD),
13498
13934
  ""
13499
13935
  );
13500
13936
  const criticalReminders = await readFileOptional(
13501
- path13.join(agentDir, STANDARD_FILES.CRITICAL_REMINDERS_MD),
13937
+ path15.join(agentDir, STANDARD_FILES.CRITICAL_REMINDERS_MD),
13502
13938
  ""
13503
13939
  );
13504
13940
  const agentPath = agent.path || name;
13505
13941
  const parts = agentPath.split("/");
13506
13942
  const category = parts[0] || name;
13507
- const categoryDir = path13.join(agentSourceRoot, agentBaseDir, category);
13508
- let output = await readFileOptional(path13.join(agentDir, STANDARD_FILES.OUTPUT_MD), "");
13943
+ const categoryDir = path15.join(agentSourceRoot, agentBaseDir, category);
13944
+ let output = await readFileOptional(path15.join(agentDir, STANDARD_FILES.OUTPUT_MD), "");
13509
13945
  if (!output) {
13510
- output = await readFileOptional(path13.join(categoryDir, STANDARD_FILES.OUTPUT_MD), "");
13946
+ output = await readFileOptional(path15.join(categoryDir, STANDARD_FILES.OUTPUT_MD), "");
13511
13947
  }
13512
13948
  return { identity, playbook, output, criticalRequirementsTop, criticalReminders };
13513
13949
  }
@@ -13528,26 +13964,30 @@ function buildAgentTemplateContext(name, agent, files, mapSkill = (skill) => ski
13528
13964
  preloadedSkillIds
13529
13965
  };
13530
13966
  }
13967
+ async function renderCompiledAgent(engine, data) {
13968
+ const rendered = await engine.renderFile("agent", sanitizeCompiledAgentData(data));
13969
+ return stampProvenanceMarker(rendered, await cliVersion());
13970
+ }
13531
13971
  async function createLiquidEngine(projectDir) {
13532
13972
  const roots = [];
13533
13973
  if (projectDir) {
13534
- const srcTemplatesDir = path13.join(
13974
+ const srcTemplatesDir = path15.join(
13535
13975
  projectDir,
13536
13976
  CLAUDE_SRC_DIR,
13537
13977
  STANDARD_DIRS.AGENTS,
13538
- path13.basename(DIRS.templates)
13978
+ path15.basename(DIRS.templates)
13539
13979
  );
13540
13980
  if (await directoryExists(srcTemplatesDir)) {
13541
13981
  roots.push(srcTemplatesDir);
13542
13982
  verbose(`Using local templates from: ${srcTemplatesDir}`);
13543
13983
  }
13544
- const legacyTemplatesDir = path13.join(projectDir, CLAUDE_DIR, STANDARD_DIRS.TEMPLATES);
13984
+ const legacyTemplatesDir = path15.join(projectDir, CLAUDE_DIR, STANDARD_DIRS.TEMPLATES);
13545
13985
  if (await directoryExists(legacyTemplatesDir)) {
13546
13986
  roots.push(legacyTemplatesDir);
13547
13987
  verbose(`Using legacy templates from: ${legacyTemplatesDir}`);
13548
13988
  }
13549
13989
  }
13550
- roots.push(path13.join(PROJECT_ROOT, DIRS.templates));
13990
+ roots.push(path15.join(PROJECT_ROOT, DIRS.templates));
13551
13991
  return new Liquid({
13552
13992
  root: roots,
13553
13993
  extname: ".liquid",
@@ -13566,12 +14006,12 @@ async function compileAgentForPlugin(name, agent, fallbackRoot, engine) {
13566
14006
  ...skill,
13567
14007
  ...pluginRefFor(skill)
13568
14008
  }));
13569
- return engine.renderFile("agent", sanitizeCompiledAgentData(data));
14009
+ return renderCompiledAgent(engine, data);
13570
14010
  }
13571
14011
 
13572
14012
  // src/cli/lib/agents/write-compiled-agents.ts
13573
14013
  import os9 from "os";
13574
- import path14 from "path";
14014
+ import path16 from "path";
13575
14015
  async function holdsExactly(filePath, content) {
13576
14016
  return await fileExists(filePath) && await readFile(filePath) === content;
13577
14017
  }
@@ -13584,7 +14024,7 @@ async function writeCompiledAgentsByScope(params) {
13584
14024
  const output = await compileAgentForPlugin(name, agent, params.sourcePath, params.engine);
13585
14025
  const scope = params.agentScopeMap?.get(name) ?? UNROUTED_AGENT_SCOPE;
13586
14026
  const targetDir = scope === "global" ? globalAgentsDir : params.projectAgentsDir;
13587
- const targetPath = path14.join(targetDir, `${name}.md`);
14027
+ const targetPath = path16.join(targetDir, `${name}.md`);
13588
14028
  const rewritten = !await holdsExactly(targetPath, output);
13589
14029
  if (rewritten) await writeFile(targetPath, output);
13590
14030
  outcomes.push({ name, ok: true, scope, targetDir, rewritten });
@@ -13597,11 +14037,11 @@ async function writeCompiledAgentsByScope(params) {
13597
14037
 
13598
14038
  // src/cli/lib/config-gate/index.ts
13599
14039
  import os12 from "os";
13600
- import path17 from "path";
14040
+ import path19 from "path";
13601
14041
 
13602
14042
  // src/cli/lib/configuration/config-writer.ts
13603
14043
  import os10 from "os";
13604
- import path15 from "path";
14044
+ import path17 from "path";
13605
14045
  var EXTRACTED_FIELDS = /* @__PURE__ */ new Set(["skills", "agents", "stack", "selectedDomains"]);
13606
14046
  var CANONICAL_FIELD_ORDER = [
13607
14047
  "name",
@@ -13611,8 +14051,8 @@ var CANONICAL_FIELD_ORDER = [
13611
14051
  "author",
13612
14052
  "selectedDomains",
13613
14053
  "stack",
13614
- "source",
13615
14054
  "marketplace",
14055
+ "marketplaceName",
13616
14056
  "agentsSource",
13617
14057
  "projects"
13618
14058
  ];
@@ -13624,6 +14064,20 @@ function canonicalizeFieldOrder(cleaned) {
13624
14064
  const passthrough = Object.entries(cleaned).filter(([key]) => !CANONICAL_FIELDS.has(key));
13625
14065
  return Object.fromEntries([...inSchemaOrder, ...passthrough]);
13626
14066
  }
14067
+ function withKeysOrderedBy(record, compareKeys) {
14068
+ return Object.fromEntries(Object.entries(record).sort(([a], [b]) => compareKeys(a, b)));
14069
+ }
14070
+ function compareNamesInCodeUnitOrder(a, b) {
14071
+ if (a === b) return 0;
14072
+ return a < b ? -1 : 1;
14073
+ }
14074
+ function canonicalizeStackOrder(stack) {
14075
+ const byDeclaration = byCategoryDeclarationOrder();
14076
+ const withCategoriesInOrder = Object.entries(stack).map(
14077
+ ([agent, categories]) => [agent, withKeysOrderedBy(categories, byDeclaration)]
14078
+ );
14079
+ return withKeysOrderedBy(Object.fromEntries(withCategoriesInOrder), compareNamesInCodeUnitOrder);
14080
+ }
13627
14081
  function renderEntryLine(entry) {
13628
14082
  return ` ${JSON.stringify(entry)},`;
13629
14083
  }
@@ -13644,9 +14098,10 @@ function cleanForEmission(config, options) {
13644
14098
  delete cleaned.projects;
13645
14099
  }
13646
14100
  if (cleaned.stack) {
13647
- cleaned.stack = compactStackAssignments(
14101
+ const compacted = compactStackAssignments(
13648
14102
  cleaned.stack
13649
14103
  );
14104
+ cleaned.stack = canonicalizeStackOrder(compacted);
13650
14105
  }
13651
14106
  return canonicalizeFieldOrder(cleaned);
13652
14107
  }
@@ -13923,7 +14378,7 @@ function compactStackAssignments(stack) {
13923
14378
  );
13924
14379
  }
13925
14380
  function getGlobalConfigImportPath() {
13926
- return path15.join(os10.homedir(), CLAUDE_SRC_DIR);
14381
+ return path17.join(os10.homedir(), CLAUDE_SRC_DIR);
13927
14382
  }
13928
14383
  function generateBlankGlobalConfigSource() {
13929
14384
  return `import type { ProjectConfig } from "./config-types";
@@ -13992,7 +14447,7 @@ function diffSkills(prev, next) {
13992
14447
  for (const [id, afterEntry] of after) {
13993
14448
  const beforeEntry = before.get(id);
13994
14449
  if (!beforeEntry || isDeepEqual(beforeEntry, afterEntry)) continue;
13995
- if (beforeEntry.source !== afterEntry.source) sourceChanged.push(id);
14450
+ if (beforeEntry.origin !== afterEntry.origin) sourceChanged.push(id);
13996
14451
  else otherChanged.push(id);
13997
14452
  }
13998
14453
  return { added, removed, sourceChanged, otherChanged };
@@ -14066,16 +14521,16 @@ var NO_AGENTS = Promise.resolve({});
14066
14521
 
14067
14522
  // src/cli/lib/config-gate/pair-writer.ts
14068
14523
  import os11 from "os";
14069
- import path16 from "path";
14524
+ import path18 from "path";
14070
14525
  function globalPairPaths() {
14071
- const dir = path16.join(os11.homedir(), CLAUDE_SRC_DIR);
14526
+ const dir = path18.join(os11.homedir(), CLAUDE_SRC_DIR);
14072
14527
  return {
14073
- configPath: path16.join(dir, STANDARD_FILES.CONFIG_TS),
14074
- typesPath: path16.join(dir, STANDARD_FILES.CONFIG_TYPES_TS)
14528
+ configPath: path18.join(dir, STANDARD_FILES.CONFIG_TS),
14529
+ typesPath: path18.join(dir, STANDARD_FILES.CONFIG_TYPES_TS)
14075
14530
  };
14076
14531
  }
14077
14532
  function typesPathFor(configPath) {
14078
- return path16.join(path16.dirname(configPath), STANDARD_FILES.CONFIG_TYPES_TS);
14533
+ return path18.join(path18.dirname(configPath), STANDARD_FILES.CONFIG_TYPES_TS);
14079
14534
  }
14080
14535
  async function writeIfChanged(filePath, source) {
14081
14536
  assertGateToken(filePath);
@@ -14124,16 +14579,16 @@ async function ensureBlankPair() {
14124
14579
  verbose("Global config already exists, skipping blank creation");
14125
14580
  return false;
14126
14581
  }
14127
- await ensureDir(path16.dirname(configPath));
14582
+ await ensureDir(path18.dirname(configPath));
14128
14583
  await writeIfChanged(configPath, generateBlankGlobalConfigSource());
14129
14584
  await writeIfChanged(typesPath, generateBlankGlobalConfigTypesSource());
14130
- verbose(`Created blank global config at ${path16.dirname(configPath)}`);
14585
+ verbose(`Created blank global config at ${path18.dirname(configPath)}`);
14131
14586
  return true;
14132
14587
  }
14133
14588
 
14134
14589
  // src/cli/lib/config-gate/propagate.ts
14135
14590
  import fs2 from "fs";
14136
- import { isDeepEqual as isDeepEqual2, unique as unique3 } from "remeda";
14591
+ import { isDeepEqual as isDeepEqual2, unique as unique4 } from "remeda";
14137
14592
  async function writeConfigFile(config, configPath, options) {
14138
14593
  const source = generateConfigSource(config, options);
14139
14594
  await writeFile(configPath, source);
@@ -14197,9 +14652,9 @@ function mergeGlobalConfigs(existing, incoming) {
14197
14652
  const mergedSelectedDomains = [
14198
14653
  .../* @__PURE__ */ new Set([...existing.selectedDomains ?? [], ...incoming.selectedDomains ?? []])
14199
14654
  ];
14655
+ const mergedMarketplaceName = existing.marketplaceName ?? incoming.marketplaceName;
14200
14656
  const mergedMarketplace = existing.marketplace ?? incoming.marketplace;
14201
- const mergedSource = existing.source ?? incoming.source;
14202
- const changed = newSkills.length > 0 || newAgents.length > 0 || stackChanged || !isDeepEqual2(existing.selectedDomains ?? [], mergedSelectedDomains) || mergedMarketplace !== existing.marketplace || mergedSource !== existing.source;
14657
+ const changed = newSkills.length > 0 || newAgents.length > 0 || stackChanged || !isDeepEqual2(existing.selectedDomains ?? [], mergedSelectedDomains) || mergedMarketplaceName !== existing.marketplaceName || mergedMarketplace !== existing.marketplace;
14203
14658
  return {
14204
14659
  config: {
14205
14660
  ...existing,
@@ -14207,8 +14662,8 @@ function mergeGlobalConfigs(existing, incoming) {
14207
14662
  agents: mergedAgents,
14208
14663
  stack: mergedStack,
14209
14664
  selectedDomains: mergedSelectedDomains,
14210
- ...mergedMarketplace !== void 0 && { marketplace: mergedMarketplace },
14211
- ...mergedSource !== void 0 && { source: mergedSource }
14665
+ ...mergedMarketplaceName !== void 0 && { marketplaceName: mergedMarketplaceName },
14666
+ ...mergedMarketplace !== void 0 && { marketplace: mergedMarketplace }
14212
14667
  },
14213
14668
  changed
14214
14669
  };
@@ -14362,7 +14817,7 @@ function inlinedProjectView(reconciledSplit, effectiveGlobal) {
14362
14817
  ...reconciledSplit,
14363
14818
  skills: [...effectiveGlobal.skills, ...reconciledSplit.skills],
14364
14819
  agents: [...effectiveGlobal.agents, ...reconciledSplit.agents],
14365
- selectedDomains: unique3([
14820
+ selectedDomains: unique4([
14366
14821
  ...effectiveGlobal.selectedDomains ?? [],
14367
14822
  ...reconciledSplit.selectedDomains ?? []
14368
14823
  ]),
@@ -14426,12 +14881,8 @@ async function pruneGlobalEntriesFromRegisteredProjects(globalConfig, matrix2, a
14426
14881
  };
14427
14882
  return propagateGlobalChangesToProjects(emptiedGlobal, matrix2, agents);
14428
14883
  }
14429
- async function resolveEffectiveGlobalConfig(globalSplit, existingGlobalConfig, projectDir) {
14430
- const hasGlobalItems = globalSplit.skills.length > 0 || globalSplit.agents.length > 0;
14431
- const merged = !hasGlobalItems ? {
14432
- config: existingGlobalConfig ?? { name: GLOBAL_CONFIG_NAME, skills: [], agents: [] },
14433
- changed: false
14434
- } : existingGlobalConfig ? mergeGlobalConfigs(existingGlobalConfig, globalSplit) : { config: globalSplit, changed: true };
14884
+ async function resolveEffectiveGlobalConfig(globalSplit, existingGlobalConfig, projectDir, authority) {
14885
+ const merged = authority === "all" ? matchGlobalToSession(globalSplit, existingGlobalConfig) : addSessionToGlobal(globalSplit, existingGlobalConfig);
14435
14886
  const registration = await registerProjectPath(merged.config, projectDir);
14436
14887
  return {
14437
14888
  config: registration.config,
@@ -14439,6 +14890,22 @@ async function resolveEffectiveGlobalConfig(globalSplit, existingGlobalConfig, p
14439
14890
  changed: merged.changed || registration.changed
14440
14891
  };
14441
14892
  }
14893
+ function addSessionToGlobal(globalSplit, existingGlobalConfig) {
14894
+ const hasGlobalItems = globalSplit.skills.length > 0 || globalSplit.agents.length > 0;
14895
+ if (!hasGlobalItems) {
14896
+ return {
14897
+ config: existingGlobalConfig ?? { name: GLOBAL_CONFIG_NAME, skills: [], agents: [] },
14898
+ changed: false
14899
+ };
14900
+ }
14901
+ if (!existingGlobalConfig) return { config: globalSplit, changed: true };
14902
+ return mergeGlobalConfigs(existingGlobalConfig, globalSplit);
14903
+ }
14904
+ function matchGlobalToSession(globalSplit, existingGlobalConfig) {
14905
+ if (!existingGlobalConfig) return { config: globalSplit, changed: true };
14906
+ const config = mergeConfigs(globalSplit, existingGlobalConfig, { authoritativeScope: "all" });
14907
+ return { config, changed: !isDeepEqual2(config, existingGlobalConfig) };
14908
+ }
14442
14909
  function buildConfigTypesBackgroundData(matrix2, agents) {
14443
14910
  const agentNames = typedKeys(agents);
14444
14911
  const customAgentNames = agentNames.filter((name) => agents[name]?.custom === true);
@@ -14450,13 +14917,13 @@ function stackCategories(stack) {
14450
14917
  function buildProjectTypesExtras(finalConfig, matrix2) {
14451
14918
  const activeSkills = finalConfig.skills.filter((s) => !s.excluded);
14452
14919
  const activeAgents = finalConfig.agents.filter((a) => !a.excluded);
14453
- const extraSkillIds = unique3(activeSkills.map((s) => s.id));
14454
- const extraAgentNames = unique3(activeAgents.map((a) => a.name));
14455
- const extraCategories = unique3([
14920
+ const extraSkillIds = unique4(activeSkills.map((s) => s.id));
14921
+ const extraAgentNames = unique4(activeAgents.map((a) => a.name));
14922
+ const extraCategories = unique4([
14456
14923
  ...deriveCategories(extraSkillIds, matrix2),
14457
14924
  ...stackCategories(finalConfig.stack)
14458
14925
  ]);
14459
- const extraDomains = unique3([
14926
+ const extraDomains = unique4([
14460
14927
  ...deriveDomains(extraCategories, matrix2),
14461
14928
  ...finalConfig.selectedDomains ?? []
14462
14929
  ]);
@@ -14472,7 +14939,7 @@ var NOTHING_RECOMPILED = {
14472
14939
  };
14473
14940
  async function recompilePropagated(projectDirs) {
14474
14941
  if (projectDirs.length === 0) return NOTHING_RECOMPILED;
14475
- const { recompilePropagatedProjectAgents } = await import("./recompile-project-agents-VREEPBDV.js");
14942
+ const { recompilePropagatedProjectAgents } = await import("./recompile-project-agents-JE4MM72I.js");
14476
14943
  return recompilePropagatedProjectAgents(projectDirs);
14477
14944
  }
14478
14945
 
@@ -14502,7 +14969,7 @@ async function applyConsequences(globalConfig, changes, deps, currentProjectDir)
14502
14969
  function lazyGateDeps(projectDir) {
14503
14970
  return {
14504
14971
  loadMatrix: async () => {
14505
- const { loadSkillsMatrixFromSource: loadSkillsMatrixFromSource2 } = await import("./source-loader-OE6EJFPU.js");
14972
+ const { loadSkillsMatrixFromSource: loadSkillsMatrixFromSource2 } = await import("./source-loader-3DL3NYUX.js");
14506
14973
  const { matrix: matrix2 } = await loadSkillsMatrixFromSource2({
14507
14974
  projectDir,
14508
14975
  skipExtraSources: true,
@@ -14511,8 +14978,8 @@ function lazyGateDeps(projectDir) {
14511
14978
  return matrix2;
14512
14979
  },
14513
14980
  loadAgents: async () => {
14514
- const { loadAgentDefs } = await import("./load-agent-defs-5OT54J5M.js");
14515
- return (await loadAgentDefs({ projectDir })).agents;
14981
+ const { loadAgentDefs } = await import("./load-agent-defs-X4E57I2U.js");
14982
+ return (await loadAgentDefs()).agents;
14516
14983
  }
14517
14984
  };
14518
14985
  }
@@ -14539,13 +15006,14 @@ async function writeFromProjectContext(args, homeDir, deps) {
14539
15006
  const effective = await resolveEffectiveGlobalConfig(
14540
15007
  globalConfig,
14541
15008
  existingGlobal?.config,
14542
- projectDir
15009
+ projectDir,
15010
+ args.authoritativeScope
14543
15011
  );
14544
15012
  const effectiveGlobalConfig = effective.config;
14545
15013
  const changes = classifyGlobalChange(existingGlobal?.config, effectiveGlobalConfig);
14546
15014
  let globalWritten = false;
14547
15015
  if (effective.changed) {
14548
- await ensureDir(path17.dirname(globalConfigPath));
15016
+ await ensureDir(path19.dirname(globalConfigPath));
14549
15017
  globalWritten = await writeGlobalPair(effectiveGlobalConfig, globalConfigPath, matrix2, agents);
14550
15018
  verbose(`Updated global config at ${globalConfigPath}`);
14551
15019
  } else {
@@ -14564,7 +15032,7 @@ async function writeFromProjectContext(args, homeDir, deps) {
14564
15032
  );
14565
15033
  const hasProjectItems = reconciledProjectConfig.skills.length > 0 || reconciledProjectConfig.agents.length > 0;
14566
15034
  if (args.projectInstallationExists || hasProjectItems) {
14567
- await ensureDir(path17.dirname(projectConfigPath));
15035
+ await ensureDir(path19.dirname(projectConfigPath));
14568
15036
  await writeProjectConfigPair(
14569
15037
  projectDir,
14570
15038
  reconciledProjectConfig,
@@ -14620,9 +15088,9 @@ async function reconcileTypesFromDisk(projectDir, config, deps, options) {
14620
15088
  }
14621
15089
  function withMigratedSource(skill, migratedSources) {
14622
15090
  if (!isActiveAt(skill, "global")) return skill;
14623
- const source = migratedSources.get(skill.id);
14624
- if (source === void 0 || source === skill.source) return skill;
14625
- return { ...skill, source };
15091
+ const origin = migratedSources.get(skill.id);
15092
+ if (origin === void 0 || origin === skill.origin) return skill;
15093
+ return { ...skill, origin };
14626
15094
  }
14627
15095
  function applyMigratedGlobalSources(globalSkills, migratedSources) {
14628
15096
  const skills = globalSkills.map((skill) => withMigratedSource(skill, migratedSources));
@@ -14654,7 +15122,7 @@ function applyMutation(mutation, current) {
14654
15122
  return filtered.length === projects.length ? null : { ...current, projects: filtered };
14655
15123
  }
14656
15124
  case "set-source":
14657
- return { ...current, source: mutation.source };
15125
+ return { ...current, marketplace: mutation.source };
14658
15126
  default: {
14659
15127
  const _exhaustive = mutation;
14660
15128
  return _exhaustive;
@@ -14711,7 +15179,7 @@ async function writeProjectPartial(projectDir, partial, options = {}) {
14711
15179
  throw new GlobalPairWriteViolation(getProjectConfigPath(projectDir));
14712
15180
  }
14713
15181
  const configPath = getProjectConfigPath(projectDir);
14714
- await ensureDir(path17.join(projectDir, CLAUDE_SRC_DIR));
15182
+ await ensureDir(path19.join(projectDir, CLAUDE_SRC_DIR));
14715
15183
  await writeFile(
14716
15184
  configPath,
14717
15185
  generateConfigSource(fillRequiredFields(withNormalizedStack(partial), options.fallbackName))
@@ -14722,7 +15190,7 @@ async function writeProjectPartial(projectDir, partial, options = {}) {
14722
15190
  function computeNewlyAddedSkillIds(currentSkills, priorSkills) {
14723
15191
  const priorActiveIds = new Set((priorSkills ?? []).filter((s) => !s.excluded).map((s) => s.id));
14724
15192
  const currentActiveIds = currentSkills.filter((s) => !s.excluded).map((s) => s.id);
14725
- return unique4(currentActiveIds.filter((id) => !priorActiveIds.has(id)));
15193
+ return unique5(currentActiveIds.filter((id) => !priorActiveIds.has(id)));
14726
15194
  }
14727
15195
  function computeScopeEligibilityGained(currentSkills, currentAgents, priorSkills, priorAgents) {
14728
15196
  const priorSkillScope = activeSkillScopeMap(priorSkills);
@@ -14747,7 +15215,7 @@ function resolveStackProperty(generated, assigned) {
14747
15215
  return generated && assigned ? assigned : generated;
14748
15216
  }
14749
15217
  function configNameFor(projectDir) {
14750
- return isHomeDirectory(projectDir) ? DEFAULT_PLUGIN_NAME : path18.basename(projectDir);
15218
+ return isHomeDirectory(projectDir) ? DEFAULT_PLUGIN_NAME : path20.basename(projectDir);
14751
15219
  }
14752
15220
  async function loadCuratedStack(projectDir, projectStack) {
14753
15221
  const globalStack = isHomeDirectory(projectDir) ? void 0 : await loadGlobalStack();
@@ -14757,7 +15225,7 @@ async function loadGlobalStack() {
14757
15225
  return (await loadProjectConfigFromDir(os13.homedir()))?.config.stack;
14758
15226
  }
14759
15227
  async function buildEjectConfig(wizardResult, sourceResult, projectDir) {
14760
- const skillIds = unique4(wizardResult.skills.map((s) => s.id));
15228
+ const skillIds = unique5(wizardResult.skills.map((s) => s.id));
14761
15229
  verbose(
14762
15230
  `buildEjectConfig: selectedStackId='${wizardResult.selectedStackId}', skills=[${skillIds.join(", ")}], selectedAgents=[${wizardResult.selectedAgents.join(", ")}]`
14763
15231
  );
@@ -14816,12 +15284,12 @@ function setConfigMetadata(config, wizardResult, sourceResult, sourceFlag) {
14816
15284
  result.selectedDomains = wizardResult.selectedDomains;
14817
15285
  }
14818
15286
  if (sourceFlag) {
14819
- result.source = sourceFlag;
15287
+ result.marketplace = sourceFlag;
14820
15288
  } else if (sourceResult.sourceConfig.source) {
14821
- result.source = sourceResult.sourceConfig.source;
15289
+ result.marketplace = sourceResult.sourceConfig.source;
14822
15290
  }
14823
15291
  if (sourceResult.marketplace) {
14824
- result.marketplace = sourceResult.marketplace;
15292
+ result.marketplaceName = sourceResult.marketplace;
14825
15293
  }
14826
15294
  return result;
14827
15295
  }
@@ -14846,7 +15314,7 @@ function buildCompileAgents(config, agents) {
14846
15314
  const globalSkillIds = new Set(
14847
15315
  config.skills.filter((s) => isActiveAt(s, "global")).map((s) => s.id)
14848
15316
  );
14849
- const sourceById = new Map(config.skills.map((s) => [s.id, s.source]));
15317
+ const sourceById = new Map(config.skills.map((s) => [s.id, s.origin]));
14850
15318
  const buildAgentCompileEntry = (agentConfig) => {
14851
15319
  const tuning = {
14852
15320
  ...agentConfig.model !== void 0 && { model: agentConfig.model },
@@ -14872,7 +15340,7 @@ function buildAgentScopeMap(config) {
14872
15340
 
14873
15341
  // src/cli/utils/exec.ts
14874
15342
  import { spawn } from "child_process";
14875
- import { z as z4 } from "zod";
15343
+ import { z as z8 } from "zod";
14876
15344
  import os14 from "os";
14877
15345
  var MAX_PLUGIN_PATH_LENGTH = 1024;
14878
15346
  var MAX_PLUGIN_NAME_LENGTH = 256;
@@ -14901,20 +15369,20 @@ Plugin paths may only contain alphanumeric characters, dashes, underscores, dots
14901
15369
  }
14902
15370
  function validateMarketplaceSource(source) {
14903
15371
  if (!source || source.trim().length === 0) {
14904
- throw new Error("Marketplace source must not be empty.");
15372
+ throw new Error("Marketplace must not be empty.");
14905
15373
  }
14906
15374
  if (source.length > MAX_MARKETPLACE_SOURCE_LENGTH) {
14907
15375
  throw new Error(
14908
- `Marketplace source is too long (${source.length} characters, max ${MAX_MARKETPLACE_SOURCE_LENGTH}).`
15376
+ `Marketplace is too long (${source.length} characters, max ${MAX_MARKETPLACE_SOURCE_LENGTH}).`
14909
15377
  );
14910
15378
  }
14911
15379
  if (CONTROL_CHAR_PATTERN.test(source)) {
14912
- throw new Error("Marketplace source contains invalid control characters.");
15380
+ throw new Error("Marketplace contains invalid control characters.");
14913
15381
  }
14914
15382
  if (!SAFE_PLUGIN_PATH_PATTERN.test(source)) {
14915
15383
  throw new Error(
14916
- `Marketplace source contains invalid characters: "${source}"
14917
- Source may only contain alphanumeric characters, dashes, underscores, dots, slashes, @, and colons.`
15384
+ `Marketplace contains invalid characters: "${source}"
15385
+ Marketplaces may only contain alphanumeric characters, dashes, underscores, dots, slashes, @, and colons.`
14918
15386
  );
14919
15387
  }
14920
15388
  }
@@ -14967,11 +15435,15 @@ async function execCommand(command, args, options) {
14967
15435
  function resolvePluginCwd(scope, projectDir) {
14968
15436
  return scope === "user" ? os14.homedir() : projectDir;
14969
15437
  }
14970
- async function claudePluginInstall(pluginPath, scope, projectDir) {
15438
+ function configDirEnv(options) {
15439
+ if (options?.configDir === void 0) return {};
15440
+ return { env: { CLAUDE_CONFIG_DIR: options.configDir } };
15441
+ }
15442
+ async function claudePluginInstall(pluginPath, scope, projectDir, options) {
14971
15443
  validatePluginPath(pluginPath);
14972
15444
  const cwd = resolvePluginCwd(scope, projectDir);
14973
15445
  const args = ["plugin", "install", pluginPath, "--scope", scope];
14974
- const result = await execCommand("claude", args, { cwd });
15446
+ const result = await execCommand("claude", args, { cwd, ...configDirEnv(options) });
14975
15447
  if (result.exitCode !== 0) {
14976
15448
  const errorMessage = result.stderr || result.stdout || "Unknown error";
14977
15449
  throw new Error(`Plugin installation failed: ${errorMessage.trim()}`);
@@ -14985,17 +15457,21 @@ async function isClaudeCLIAvailable() {
14985
15457
  return false;
14986
15458
  }
14987
15459
  }
14988
- var marketplaceInfoListSchema = z4.array(
14989
- z4.object({
14990
- name: z4.string(),
14991
- source: z4.string(),
14992
- repo: z4.string().exactOptional(),
14993
- path: z4.string().exactOptional()
15460
+ var marketplaceInfoListSchema = z8.array(
15461
+ z8.object({
15462
+ name: z8.string(),
15463
+ source: z8.string(),
15464
+ repo: z8.string().exactOptional(),
15465
+ path: z8.string().exactOptional()
14994
15466
  })
14995
15467
  );
14996
- async function claudePluginMarketplaceList() {
15468
+ async function claudePluginMarketplaceList(options) {
14997
15469
  try {
14998
- const result = await execCommand("claude", ["plugin", "marketplace", "list", "--json"], {});
15470
+ const result = await execCommand(
15471
+ "claude",
15472
+ ["plugin", "marketplace", "list", "--json"],
15473
+ configDirEnv(options)
15474
+ );
14999
15475
  if (result.exitCode !== 0) {
15000
15476
  return [];
15001
15477
  }
@@ -15016,16 +15492,16 @@ async function claudePluginMarketplaceList() {
15016
15492
  return [];
15017
15493
  }
15018
15494
  }
15019
- async function claudePluginMarketplaceExists(name) {
15020
- const marketplaces = await claudePluginMarketplaceList();
15495
+ async function claudePluginMarketplaceExists(name, options) {
15496
+ const marketplaces = await claudePluginMarketplaceList(options);
15021
15497
  return marketplaces.some((m) => m.name === name);
15022
15498
  }
15023
- async function claudePluginMarketplaceAdd(source) {
15499
+ async function claudePluginMarketplaceAdd(source, options) {
15024
15500
  validateMarketplaceSource(source);
15025
15501
  const args = ["plugin", "marketplace", "add", source];
15026
15502
  let result;
15027
15503
  try {
15028
- result = await execCommand("claude", args, {});
15504
+ result = await execCommand("claude", args, configDirEnv(options));
15029
15505
  } catch (err) {
15030
15506
  throw new Error(`Failed to add marketplace: ${getErrorMessage(err)}`, { cause: err });
15031
15507
  }
@@ -15037,12 +15513,12 @@ async function claudePluginMarketplaceAdd(source) {
15037
15513
  throw new Error(`Failed to add marketplace: ${errorMessage.trim()}`);
15038
15514
  }
15039
15515
  }
15040
- async function claudePluginMarketplaceUpdate(name) {
15516
+ async function claudePluginMarketplaceUpdate(name, options) {
15041
15517
  validatePluginName(name);
15042
15518
  const args = ["plugin", "marketplace", "update", name];
15043
15519
  let result;
15044
15520
  try {
15045
- result = await execCommand("claude", args, {});
15521
+ result = await execCommand("claude", args, configDirEnv(options));
15046
15522
  } catch (err) {
15047
15523
  throw new Error(`Failed to update marketplace: ${getErrorMessage(err)}`, { cause: err });
15048
15524
  }
@@ -15051,11 +15527,11 @@ async function claudePluginMarketplaceUpdate(name) {
15051
15527
  throw new Error(`Failed to update marketplace: ${errorMessage.trim()}`);
15052
15528
  }
15053
15529
  }
15054
- async function claudePluginUninstall(pluginName, scope, projectDir) {
15530
+ async function claudePluginUninstall(pluginName, scope, projectDir, options) {
15055
15531
  validatePluginName(pluginName);
15056
15532
  const cwd = resolvePluginCwd(scope, projectDir);
15057
15533
  const args = ["plugin", "uninstall", pluginName, "--scope", scope];
15058
- const result = await execCommand("claude", args, { cwd });
15534
+ const result = await execCommand("claude", args, { cwd, ...configDirEnv(options) });
15059
15535
  if (result.exitCode !== 0) {
15060
15536
  const errorMessage = result.stderr || result.stdout || "Unknown error";
15061
15537
  if (errorMessage.includes("not installed") || errorMessage.includes("not found")) {
@@ -15064,19 +15540,22 @@ async function claudePluginUninstall(pluginName, scope, projectDir) {
15064
15540
  throw new Error(`Plugin uninstall failed: ${errorMessage.trim()}`);
15065
15541
  }
15066
15542
  }
15067
- async function claudePluginUninstallBestEffort(pluginRef, primaryScope, projectDir) {
15543
+ async function claudePluginUninstallBestEffort(pluginRef, primaryScope, projectDir, options) {
15068
15544
  const fallbackScope = primaryScope === "project" ? "user" : "project";
15069
15545
  try {
15070
- await claudePluginUninstall(pluginRef, primaryScope, projectDir);
15546
+ await claudePluginUninstall(pluginRef, primaryScope, projectDir, options);
15071
15547
  } catch {
15072
15548
  }
15073
15549
  try {
15074
- await claudePluginUninstall(pluginRef, fallbackScope, projectDir);
15550
+ await claudePluginUninstall(pluginRef, fallbackScope, projectDir, options);
15075
15551
  } catch {
15076
15552
  }
15077
15553
  }
15078
15554
 
15079
15555
  // src/cli/lib/installation/mode-migrator.ts
15556
+ function ejectCopyFailureError(failedCount) {
15557
+ return `Failed to copy ${failedCount} skill(s) for eject. Eject intent could not be honored. Check that the destination skills directory is writable, then run '${CLI_INVOKE_COMMAND} edit' again \u2014 or switch the affected skills back to plugin mode.`;
15558
+ }
15080
15559
  function detectMigrations(oldSkills, newSkills) {
15081
15560
  const toEject = [];
15082
15561
  const toPlugin = [];
@@ -15087,13 +15566,13 @@ function detectMigrations(oldSkills, newSkills) {
15087
15566
  if (!oldSkill) continue;
15088
15567
  const migration = {
15089
15568
  id: newSkill.id,
15090
- oldSource: oldSkill.source,
15091
- newSource: newSkill.source,
15569
+ oldSource: oldSkill.origin,
15570
+ newSource: newSkill.origin,
15092
15571
  oldScope: oldSkill.scope,
15093
15572
  newScope: newSkill.scope
15094
15573
  };
15095
- const wasEject = oldSkill.source === EJECT_SOURCE;
15096
- const isEject = newSkill.source === EJECT_SOURCE;
15574
+ const wasEject = oldSkill.origin === EJECT_SOURCE;
15575
+ const isEject = newSkill.origin === EJECT_SOURCE;
15097
15576
  if (wasEject && !isEject) {
15098
15577
  toPlugin.push(migration);
15099
15578
  } else if (!wasEject && isEject) {
@@ -15106,48 +15585,17 @@ function detectMigrations(oldSkills, newSkills) {
15106
15585
  return { toEject, toPlugin, scopeChanges };
15107
15586
  }
15108
15587
  async function executeMigration(plan, projectDir, sourceResult) {
15109
- const warnings = [];
15110
- const ejectedSkills = [];
15111
15588
  const pluginInstalls = { installed: [], failed: [] };
15112
- if (plan.toEject.length > 0) {
15113
- try {
15114
- const scopes = ["project", "global"];
15115
- for (const scope of scopes) {
15116
- const migrations = plan.toEject.filter(
15117
- (m) => scope === "global" ? m.newScope === "global" : m.newScope !== "global"
15118
- );
15119
- if (migrations.length === 0) continue;
15120
- const copied = await copySkillsToLocalFlattened(
15121
- migrations.map((m) => m.id),
15122
- resolveInstallPaths(projectDir, scope).skillsDir,
15123
- sourceResult
15124
- );
15125
- ejectedSkills.push(...copied.map((skill) => skill.skillId));
15126
- }
15127
- if (!sourceResult.marketplace) {
15128
- warnings.push(
15129
- ...plan.toEject.filter((m) => !(m.oldScope === "global" && m.newScope === "project")).map((m) => `Could not uninstall plugin for ${m.id}: no marketplace configured`)
15130
- );
15131
- } else {
15132
- for (const migration of plan.toEject) {
15133
- if (migration.oldScope === "global" && migration.newScope === "project") {
15134
- verbose(`Keeping global plugin for ${migration.id} (migrated to project-eject)`);
15135
- continue;
15136
- }
15137
- const pluginScope = toClaudePluginScope(migration.oldScope);
15138
- const pluginRef = buildMarketplacePluginRef(migration.id, sourceResult.marketplace);
15139
- await claudePluginUninstall(pluginRef, pluginScope, projectDir);
15140
- verbose(`Uninstalled plugin for ${migration.id}`);
15141
- }
15142
- }
15143
- } catch (error) {
15144
- warnings.push(`Could not copy skills for eject: ${getErrorMessage(error)}`);
15145
- }
15146
- }
15589
+ const ejectCopies = await copyMigratedSkillsToLocal(plan.toEject, projectDir, sourceResult);
15590
+ const warnings = await uninstallMigratedPlugins(
15591
+ migrationsWhoseCopyLanded(plan.toEject, ejectCopies),
15592
+ projectDir,
15593
+ sourceResult
15594
+ );
15147
15595
  if (plan.toPlugin.length > 0) {
15148
15596
  if (!sourceResult.marketplace) {
15149
15597
  throw new Error(
15150
- `Cannot install skills as plugins: marketplace could not be resolved from source '${sourceResult.sourceConfig.source}'. Plugin install mode requires a marketplace \u2014 fix the source or switch the affected skills to eject mode.`
15598
+ `Cannot install skills as plugins: marketplace could not be resolved from '${sourceResult.sourceConfig.source}'. Plugin install mode requires a marketplace \u2014 fix the marketplace or switch the affected skills to eject mode.`
15151
15599
  );
15152
15600
  }
15153
15601
  for (const migration of plan.toPlugin) {
@@ -15164,7 +15612,50 @@ async function executeMigration(plan, projectDir, sourceResult) {
15164
15612
  await deleteEjectedWorkingCopy(migration, projectDir);
15165
15613
  }
15166
15614
  }
15167
- return { ejectedSkills, pluginInstalls, warnings };
15615
+ return { ejectCopies, pluginInstalls, warnings };
15616
+ }
15617
+ async function copyMigratedSkillsToLocal(migrations, projectDir, sourceResult) {
15618
+ const copies = { copied: [], failed: [] };
15619
+ for (const migration of migrations) {
15620
+ const { skillsDir } = resolveInstallPaths(projectDir, migration.newScope);
15621
+ try {
15622
+ const copied = await copySkillsToLocalFlattened([migration.id], skillsDir, sourceResult);
15623
+ copies.copied.push(...copied.map((skill) => skill.skillId));
15624
+ } catch (error) {
15625
+ copies.failed.push({ id: migration.id, error: getErrorMessage(error) });
15626
+ }
15627
+ }
15628
+ return copies;
15629
+ }
15630
+ function migrationsWhoseCopyLanded(migrations, copies) {
15631
+ const landed = new Set(copies.copied);
15632
+ return migrations.filter((migration) => landed.has(migration.id));
15633
+ }
15634
+ function keepsGlobalPluginRegistration(migration) {
15635
+ return migration.oldScope === "global" && migration.newScope === "project";
15636
+ }
15637
+ async function uninstallMigratedPlugins(migrations, projectDir, sourceResult) {
15638
+ const warnings = [];
15639
+ for (const migration of migrations) {
15640
+ if (keepsGlobalPluginRegistration(migration)) {
15641
+ verbose(`Keeping global plugin for ${migration.id} (migrated to project-eject)`);
15642
+ continue;
15643
+ }
15644
+ if (!sourceResult.marketplace) {
15645
+ warnings.push(`Could not uninstall plugin for ${migration.id}: no marketplace configured`);
15646
+ continue;
15647
+ }
15648
+ const pluginScope = toClaudePluginScope(migration.oldScope);
15649
+ const pluginRef = buildMarketplacePluginRef(migration.id, sourceResult.marketplace);
15650
+ try {
15651
+ await claudePluginUninstall(pluginRef, pluginScope, projectDir);
15652
+ } catch (error) {
15653
+ warnings.push(`Could not uninstall plugin for ${migration.id}: ${getErrorMessage(error)}`);
15654
+ continue;
15655
+ }
15656
+ verbose(`Uninstalled plugin for ${migration.id}`);
15657
+ }
15658
+ return warnings;
15168
15659
  }
15169
15660
  async function deleteEjectedWorkingCopy(migration, projectDir) {
15170
15661
  if (migration.oldScope === "global" && migration.newScope === "project") {
@@ -15175,28 +15666,34 @@ async function deleteEjectedWorkingCopy(migration, projectDir) {
15175
15666
  }
15176
15667
 
15177
15668
  // src/cli/lib/plugins/plugin-settings.ts
15178
- import path19 from "path";
15179
- import { z as z5 } from "zod";
15180
- var pluginSettingsSchema = z5.object({
15181
- enabledPlugins: z5.record(z5.string(), z5.unknown()).optional()
15669
+ import path21 from "path";
15670
+ import { z as z9 } from "zod";
15671
+ var pluginSettingsSchema = z9.object({
15672
+ // The one genuine optional here: a settings.json with no plugin enabled omits the
15673
+ // key entirely, and `getEnabledPluginKeys` says so in its own diagnostic rather than
15674
+ // defaulting it away. `lastUpdated` and `gitCommitSha` used to sit beside the fields
15675
+ // below on the same footing and were decoration — declared, never read, and stripped
15676
+ // by `z.object` either way.
15677
+ enabledPlugins: z9.record(z9.string(), z9.unknown()).exactOptional()
15182
15678
  }).passthrough();
15183
- var pluginInstallationSchema = z5.object({
15184
- scope: z5.enum(["user", "project", "local"]),
15185
- projectPath: z5.string().optional(),
15186
- installPath: z5.string(),
15187
- version: z5.string(),
15188
- installedAt: z5.string(),
15189
- lastUpdated: z5.string().optional(),
15190
- gitCommitSha: z5.string().optional()
15191
- });
15192
- var installedPluginsSchema = z5.object({
15193
- version: z5.number(),
15194
- plugins: z5.record(z5.string(), z5.array(pluginInstallationSchema))
15679
+ var installationFields = {
15680
+ installPath: z9.string(),
15681
+ version: z9.string(),
15682
+ installedAt: z9.string()
15683
+ };
15684
+ var pluginInstallationSchema = z9.discriminatedUnion("scope", [
15685
+ z9.object({ scope: z9.literal("project"), projectPath: z9.string(), ...installationFields }),
15686
+ z9.object({ scope: z9.literal("user"), ...installationFields }),
15687
+ z9.object({ scope: z9.literal("local"), ...installationFields })
15688
+ ]);
15689
+ var installedPluginsSchema = z9.object({
15690
+ version: z9.number(),
15691
+ plugins: z9.record(z9.string(), z9.array(pluginInstallationSchema))
15195
15692
  }).passthrough();
15196
15693
  var SETTINGS_FILE = STANDARD_FILES.SETTINGS_JSON;
15197
15694
  var INSTALLED_PLUGINS_FILE = "installed_plugins.json";
15198
15695
  function getInstalledPluginsRegistryPath(pluginsDir) {
15199
- return path19.join(pluginsDir, INSTALLED_PLUGINS_FILE);
15696
+ return path21.join(pluginsDir, INSTALLED_PLUGINS_FILE);
15200
15697
  }
15201
15698
  async function listRegisteredPluginInstalls(pluginsDir) {
15202
15699
  const registryPath = getInstalledPluginsRegistryPath(pluginsDir);
@@ -15214,7 +15711,7 @@ async function listRegisteredPluginInstalls(pluginsDir) {
15214
15711
  });
15215
15712
  }
15216
15713
  async function getEnabledPluginKeys(projectDir) {
15217
- const settingsPath = path19.join(projectDir, CLAUDE_DIR, SETTINGS_FILE);
15714
+ const settingsPath = path21.join(projectDir, CLAUDE_DIR, SETTINGS_FILE);
15218
15715
  if (!await fileExists(settingsPath)) {
15219
15716
  verbose(`No settings.json found at '${settingsPath}'`);
15220
15717
  return [];
@@ -15357,8 +15854,7 @@ async function getInstallationInfo() {
15357
15854
  skillCount,
15358
15855
  agentCount: sumCounts(agentDirCounts),
15359
15856
  configPath: installation.configPath,
15360
- agentDirs: agentDirCounts.filter(hasEntries).map((scopeCount) => scopeCount.dir),
15361
- skillsDir: installation.skillsDir
15857
+ agentDirs: agentDirCounts.filter(hasEntries).map((scopeCount) => scopeCount.dir)
15362
15858
  };
15363
15859
  }
15364
15860
  function installedScopes(installation) {
@@ -15427,7 +15923,7 @@ function formatInstallationDisplay(info) {
15427
15923
  }
15428
15924
 
15429
15925
  // src/cli/lib/plugins/plugin-validator.ts
15430
- import path20 from "path";
15926
+ import path22 from "path";
15431
15927
  import fg from "fast-glob";
15432
15928
  var PLUGIN_DIR = PLUGIN_MANIFEST_DIR;
15433
15929
  var PLUGIN_MANIFEST = STANDARD_FILES.PLUGIN_JSON;
@@ -15444,15 +15940,15 @@ async function validatePluginStructure(pluginPath) {
15444
15940
  if (!await directoryExists(pluginPath)) {
15445
15941
  return invalidResult(`Plugin directory does not exist: ${pluginPath}`);
15446
15942
  }
15447
- const pluginDir = path20.join(pluginPath, PLUGIN_DIR);
15943
+ const pluginDir = path22.join(pluginPath, PLUGIN_DIR);
15448
15944
  if (!await directoryExists(pluginDir)) {
15449
15945
  errors.push(`Missing ${PLUGIN_DIR}/ directory`);
15450
15946
  }
15451
- const manifestPath = path20.join(pluginDir, PLUGIN_MANIFEST);
15947
+ const manifestPath = path22.join(pluginDir, PLUGIN_MANIFEST);
15452
15948
  if (!await fileExists(manifestPath)) {
15453
15949
  errors.push(`Missing ${PLUGIN_DIR}/${PLUGIN_MANIFEST}`);
15454
15950
  }
15455
- const readmePath = path20.join(pluginPath, STANDARD_FILES.README_MD);
15951
+ const readmePath = path22.join(pluginPath, STANDARD_FILES.README_MD);
15456
15952
  if (!await fileExists(readmePath)) {
15457
15953
  warnings.push("Missing README.md (recommended for documentation)");
15458
15954
  }
@@ -15496,15 +15992,15 @@ async function validatePluginManifest(manifestPath) {
15496
15992
  if (!manifest.description) {
15497
15993
  warnings.push("Missing description field (recommended for discoverability)");
15498
15994
  }
15499
- const pluginDir = path20.dirname(path20.dirname(manifestPath));
15995
+ const pluginDir = path22.dirname(path22.dirname(manifestPath));
15500
15996
  if (manifest.skills && typeof manifest.skills === "string") {
15501
- const skillsPath = path20.join(pluginDir, manifest.skills);
15997
+ const skillsPath = path22.join(pluginDir, manifest.skills);
15502
15998
  if (!await directoryExists(skillsPath)) {
15503
15999
  errors.push(`Skills path does not exist: ${manifest.skills}`);
15504
16000
  }
15505
16001
  }
15506
16002
  if (manifest.agents && typeof manifest.agents === "string") {
15507
- const agentsPath = path20.join(pluginDir, manifest.agents);
16003
+ const agentsPath = path22.join(pluginDir, manifest.agents);
15508
16004
  if (!await directoryExists(agentsPath)) {
15509
16005
  errors.push(`Agents path does not exist: ${manifest.agents}`);
15510
16006
  }
@@ -15571,7 +16067,7 @@ function prefixResult(result, prefix) {
15571
16067
  };
15572
16068
  }
15573
16069
  async function validatePluginSkillFiles(pluginPath, skillsRelPath) {
15574
- const skillsDir = path20.join(pluginPath, skillsRelPath);
16070
+ const skillsDir = path22.join(pluginPath, skillsRelPath);
15575
16071
  if (!await directoryExists(skillsDir)) return validResult();
15576
16072
  const files = await fg(`**/${STANDARD_FILES.SKILL_MD}`, { cwd: skillsDir, absolute: true });
15577
16073
  if (files.length === 0) {
@@ -15583,13 +16079,13 @@ async function validatePluginSkillFiles(pluginPath, skillsRelPath) {
15583
16079
  }
15584
16080
  const results = await Promise.all(
15585
16081
  files.map(
15586
- async (f) => prefixResult(await validateSkillFrontmatter(f), path20.relative(pluginPath, f))
16082
+ async (f) => prefixResult(await validateSkillFrontmatter(f), path22.relative(pluginPath, f))
15587
16083
  )
15588
16084
  );
15589
16085
  return mergeValidationResults(results);
15590
16086
  }
15591
16087
  async function validatePluginAgentFiles(pluginPath, agentsRelPath) {
15592
- const agentsDir = path20.join(pluginPath, agentsRelPath);
16088
+ const agentsDir = path22.join(pluginPath, agentsRelPath);
15593
16089
  if (!await directoryExists(agentsDir)) return validResult();
15594
16090
  const files = await fg("*.md", { cwd: agentsDir, absolute: true });
15595
16091
  if (files.length === 0) {
@@ -15601,7 +16097,7 @@ async function validatePluginAgentFiles(pluginPath, agentsRelPath) {
15601
16097
  }
15602
16098
  const results = await Promise.all(
15603
16099
  files.map(
15604
- async (f) => prefixResult(await validateAgentFrontmatter(f), path20.relative(pluginPath, f))
16100
+ async (f) => prefixResult(await validateAgentFrontmatter(f), path22.relative(pluginPath, f))
15605
16101
  )
15606
16102
  );
15607
16103
  return mergeValidationResults(results);
@@ -15643,7 +16139,7 @@ async function validateAllPlugins(pluginsDir) {
15643
16139
  const dirChecks = await Promise.all(
15644
16140
  allDirs.map(async (dirName) => ({
15645
16141
  dirName,
15646
- isPlugin: await directoryExists(path20.join(pluginsDir, dirName, PLUGIN_DIR))
16142
+ isPlugin: await directoryExists(path22.join(pluginsDir, dirName, PLUGIN_DIR))
15647
16143
  }))
15648
16144
  );
15649
16145
  const pluginDirs = dirChecks.filter((c) => c.isPlugin).map((c) => c.dirName);
@@ -15662,7 +16158,7 @@ async function validateAllPlugins(pluginsDir) {
15662
16158
  };
15663
16159
  }
15664
16160
  for (const pluginName of pluginDirs) {
15665
- const pluginPath = path20.join(pluginsDir, pluginName);
16161
+ const pluginPath = path22.join(pluginsDir, pluginName);
15666
16162
  const result = await validatePlugin(pluginPath);
15667
16163
  results.push({ name: pluginName, result });
15668
16164
  }
@@ -15685,7 +16181,7 @@ function sanitizeSkillName(name) {
15685
16181
  return name.replace(/\+/g, "-");
15686
16182
  }
15687
16183
  async function readSkillMetadata2(skillPath) {
15688
- const metadataPath = path21.join(skillPath, STANDARD_FILES.METADATA_YAML);
16184
+ const metadataPath = path23.join(skillPath, STANDARD_FILES.METADATA_YAML);
15689
16185
  if (!await fileExists(metadataPath)) {
15690
16186
  return null;
15691
16187
  }
@@ -15729,8 +16225,8 @@ This skill is automatically available when installed.
15729
16225
  }
15730
16226
  async function compileSkillPlugin(options) {
15731
16227
  const { skillPath, outputDir, skillName: overrideName } = options;
15732
- const dirBasename = path21.basename(skillPath);
15733
- const skillMdPath = path21.join(skillPath, STANDARD_FILES.SKILL_MD);
16228
+ const dirBasename = path23.basename(skillPath);
16229
+ const skillMdPath = path23.join(skillPath, STANDARD_FILES.SKILL_MD);
15734
16230
  if (!await fileExists(skillMdPath)) {
15735
16231
  throw new Error(
15736
16232
  `Skill '${dirBasename}' is missing required ${STANDARD_FILES.SKILL_MD} file. Expected at: ${skillMdPath}`
@@ -15746,8 +16242,8 @@ async function compileSkillPlugin(options) {
15746
16242
  const skillName = overrideName ?? sanitizeSkillName(frontmatter.name);
15747
16243
  verbose(`Compiling skill plugin: ${skillName} from ${skillPath}`);
15748
16244
  const metadata = await readSkillMetadata2(skillPath);
15749
- const pluginDir = path21.join(outputDir, skillName);
15750
- const skillsDir = path21.join(pluginDir, "skills", skillName);
16245
+ const pluginDir = path23.join(outputDir, skillName);
16246
+ const skillsDir = path23.join(pluginDir, "skills", skillName);
15751
16247
  await ensureDir(pluginDir);
15752
16248
  await ensureDir(skillsDir);
15753
16249
  const newHash = await computeSkillFolderHash(skillPath);
@@ -15766,26 +16262,26 @@ async function compileSkillPlugin(options) {
15766
16262
  await writePluginManifest(pluginDir, manifest);
15767
16263
  await writeContentHash(pluginDir, contentHash, getPluginManifestPath);
15768
16264
  verbose(` Wrote plugin.json for ${skillName} (v${version})`);
15769
- await writeFile(path21.join(skillsDir, STANDARD_FILES.SKILL_MD), skillMdContent);
16265
+ await writeFile(path23.join(skillsDir, STANDARD_FILES.SKILL_MD), skillMdContent);
15770
16266
  verbose(` Copied ${STANDARD_FILES.SKILL_MD}`);
15771
16267
  for (const fileName of SKILL_CONTENT_FILES) {
15772
16268
  if (fileName === STANDARD_FILES.SKILL_MD) continue;
15773
- const sourcePath = path21.join(skillPath, fileName);
16269
+ const sourcePath = path23.join(skillPath, fileName);
15774
16270
  if (await fileExists(sourcePath)) {
15775
16271
  const content = await readFile(sourcePath);
15776
- await writeFile(path21.join(skillsDir, fileName), content);
16272
+ await writeFile(path23.join(skillsDir, fileName), content);
15777
16273
  verbose(` Copied ${fileName}`);
15778
16274
  }
15779
16275
  }
15780
16276
  for (const dirName of SKILL_CONTENT_DIRS) {
15781
- const sourceDir = path21.join(skillPath, dirName);
16277
+ const sourceDir = path23.join(skillPath, dirName);
15782
16278
  if (await fileExists(sourceDir)) {
15783
- await copy(sourceDir, path21.join(skillsDir, dirName));
16279
+ await copy(sourceDir, path23.join(skillsDir, dirName));
15784
16280
  verbose(` Copied ${dirName}/`);
15785
16281
  }
15786
16282
  }
15787
16283
  const readme = generateReadme(skillName, frontmatter);
15788
- await writeFile(path21.join(pluginDir, STANDARD_FILES.README_MD), readme);
16284
+ await writeFile(path23.join(pluginDir, STANDARD_FILES.README_MD), readme);
15789
16285
  verbose(" Generated README.md");
15790
16286
  return {
15791
16287
  pluginPath: pluginDir,
@@ -15798,7 +16294,7 @@ async function compileAllSkillPlugins(skillsDir, outputDir) {
15798
16294
  const failed = [];
15799
16295
  const skillMdFiles = await glob(`**/${STANDARD_FILES.SKILL_MD}`, skillsDir);
15800
16296
  for (const skillMdFile of skillMdFiles) {
15801
- const skillPath = path21.join(skillsDir, path21.dirname(skillMdFile));
16297
+ const skillPath = path23.join(skillsDir, path23.dirname(skillMdFile));
15802
16298
  try {
15803
16299
  const result = await compileSkillPlugin({
15804
16300
  skillPath,
@@ -15808,7 +16304,7 @@ async function compileAllSkillPlugins(skillsDir, outputDir) {
15808
16304
  log(` [OK] ${result.skillName}`);
15809
16305
  } catch (error) {
15810
16306
  const errorMessage = getErrorMessage(error);
15811
- const dirBasename = path21.basename(skillPath);
16307
+ const dirBasename = path23.basename(skillPath);
15812
16308
  failed.push(dirBasename);
15813
16309
  warn(`Failed to compile skill from '${dirBasename}': ${errorMessage}`);
15814
16310
  }
@@ -15824,9 +16320,9 @@ Compiled ${results.length} skill plugins:`);
15824
16320
  }
15825
16321
 
15826
16322
  // src/cli/lib/skills/local-skill-loader.ts
15827
- import path22 from "path";
16323
+ import path24 from "path";
15828
16324
  async function discoverLocalSkills(projectDir) {
15829
- const localSkillsPath = path22.join(projectDir, LOCAL_SKILLS_PATH);
16325
+ const localSkillsPath = path24.join(projectDir, LOCAL_SKILLS_PATH);
15830
16326
  if (!await directoryExists(localSkillsPath)) {
15831
16327
  verbose(`Local skills directory not found: ${localSkillsPath}`);
15832
16328
  return null;
@@ -15843,9 +16339,9 @@ async function discoverLocalSkills(projectDir) {
15843
16339
  };
15844
16340
  }
15845
16341
  async function extractLocalSkill(localSkillsPath, skillDirName) {
15846
- const skillDir = path22.join(localSkillsPath, skillDirName);
15847
- const metadataPath = path22.join(skillDir, STANDARD_FILES.METADATA_YAML);
15848
- const skillMdPath = path22.join(skillDir, STANDARD_FILES.SKILL_MD);
16342
+ const skillDir = path24.join(localSkillsPath, skillDirName);
16343
+ const metadataPath = path24.join(skillDir, STANDARD_FILES.METADATA_YAML);
16344
+ const skillMdPath = path24.join(skillDir, STANDARD_FILES.SKILL_MD);
15849
16345
  if (!await fileExists(metadataPath)) {
15850
16346
  verbose(`Skipping local skill '${skillDirName}': No metadata.yaml found`);
15851
16347
  return null;
@@ -15862,6 +16358,12 @@ async function extractLocalSkill(localSkillsPath, skillDirName) {
15862
16358
  return null;
15863
16359
  }
15864
16360
  const metadata = read.metadata;
16361
+ if (metadata.category === LOCAL_PSEUDO_CATEGORY) {
16362
+ warn(
16363
+ `Skipping local skill '${skillDirName}': ${METADATA_KEYS.CATEGORY} '${LOCAL_PSEUDO_CATEGORY}' is a placeholder, not a real category, so no sub-agent can be given this skill. Set ${METADATA_KEYS.CATEGORY} in ${metadataPath} to a real one.`
16364
+ );
16365
+ return null;
16366
+ }
15865
16367
  const skillMdContent = await readFile(skillMdPath);
15866
16368
  const frontmatter = parseFrontmatter(skillMdContent, skillMdPath);
15867
16369
  if (!frontmatter) {
@@ -15869,7 +16371,7 @@ async function extractLocalSkill(localSkillsPath, skillDirName) {
15869
16371
  return null;
15870
16372
  }
15871
16373
  const relativePath = `${LOCAL_SKILLS_PATH}/${skillDirName}/`;
15872
- const absolutePath = path22.join(localSkillsPath, skillDirName) + path22.sep;
16374
+ const absolutePath = path24.join(localSkillsPath, skillDirName) + path24.sep;
15873
16375
  const skillId = frontmatter.name;
15874
16376
  const extracted = {
15875
16377
  id: skillId,
@@ -15891,7 +16393,7 @@ async function extractLocalSkill(localSkillsPath, skillDirName) {
15891
16393
  }
15892
16394
 
15893
16395
  // src/cli/lib/skills/unresolved-skill-entries.ts
15894
- import path23 from "path";
16396
+ import path25 from "path";
15895
16397
  async function findUnusableSavedSkillMetadata(unresolvedIds, savedSkills, projectDir) {
15896
16398
  const classified = await classifyUnresolvedSkills(unresolvedIds, savedSkills, projectDir);
15897
16399
  return classified.flatMap(({ fate }) => fate.kind === "unusable-metadata" ? [fate.detail] : []);
@@ -15914,10 +16416,10 @@ async function classifyUnresolvedSkills(unresolvedIds, savedSkills, projectDir)
15914
16416
  );
15915
16417
  }
15916
16418
  async function classifySavedSkill(id, saved, projectDir) {
15917
- if (saved?.source !== EJECT_SOURCE) return { kind: "dropped-by-source" };
15918
- const skillDir = path23.join(resolveInstallPaths(projectDir, saved.scope).skillsDir, id);
16419
+ if (saved?.origin !== EJECT_SOURCE) return { kind: "dropped-by-source" };
16420
+ const skillDir = path25.join(resolveInstallPaths(projectDir, saved.scope).skillsDir, id);
15919
16421
  if (!await directoryExists(skillDir)) return { kind: "files-gone", skillDir };
15920
- const metadataPath = path23.join(skillDir, STANDARD_FILES.METADATA_YAML);
16422
+ const metadataPath = path25.join(skillDir, STANDARD_FILES.METADATA_YAML);
15921
16423
  if (!await fileExists(metadataPath)) return { kind: "not-installed-there", skillDir };
15922
16424
  const read = await readSkillMetadata(metadataPath);
15923
16425
  if (!read.usable) {
@@ -15930,7 +16432,7 @@ async function classifySavedSkill(id, saved, projectDir) {
15930
16432
  return { kind: "unplaceable-category", skillDir, category: read.metadata.category };
15931
16433
  }
15932
16434
  async function skillMdNames(skillDir, id) {
15933
- const skillMdPath = path23.join(skillDir, STANDARD_FILES.SKILL_MD);
16435
+ const skillMdPath = path25.join(skillDir, STANDARD_FILES.SKILL_MD);
15934
16436
  if (!await fileExists(skillMdPath)) return false;
15935
16437
  const frontmatter = parseFrontmatter(await readFile(skillMdPath), skillMdPath);
15936
16438
  return frontmatter?.name === id;
@@ -15955,7 +16457,7 @@ function removalReason(id, fate, sourceLabel) {
15955
16457
  }
15956
16458
 
15957
16459
  // src/cli/lib/skills/local-skill-mover.ts
15958
- import path24 from "path";
16460
+ import path26 from "path";
15959
16461
  function validateSkillId(skillId) {
15960
16462
  return !(skillId.length === 0 || skillId.includes("\0") || skillId.includes("..") || skillId.includes("/") || skillId.includes("\\"));
15961
16463
  }
@@ -15964,8 +16466,8 @@ async function deleteLocalSkill(projectDir, skillId) {
15964
16466
  warn(`Invalid skill ID for deletion: '${skillId}'`);
15965
16467
  return;
15966
16468
  }
15967
- const skillPath = path24.resolve(path24.join(projectDir, LOCAL_SKILLS_PATH, skillId));
15968
- const skillsDir = path24.resolve(path24.join(projectDir, LOCAL_SKILLS_PATH));
16469
+ const skillPath = path26.resolve(path26.join(projectDir, LOCAL_SKILLS_PATH, skillId));
16470
+ const skillsDir = path26.resolve(path26.join(projectDir, LOCAL_SKILLS_PATH));
15969
16471
  if (!isPathWithin(skillPath, skillsDir)) {
15970
16472
  warn(`Skill ID '${skillId}' resolves outside the skills directory.`);
15971
16473
  return;
@@ -15985,10 +16487,10 @@ async function migrateLocalSkillScope(skillId, fromScope, projectDir) {
15985
16487
  const toScope = fromScope === "global" ? "project" : "global";
15986
16488
  const fromBaseDir = installBaseDir(projectDir, fromScope);
15987
16489
  const toBaseDir = installBaseDir(projectDir, toScope);
15988
- const fromPath = path24.resolve(path24.join(fromBaseDir, LOCAL_SKILLS_PATH, skillId));
15989
- const toPath = path24.resolve(path24.join(toBaseDir, LOCAL_SKILLS_PATH, skillId));
15990
- const fromSkillsDir = path24.resolve(path24.join(fromBaseDir, LOCAL_SKILLS_PATH));
15991
- const toSkillsDir = path24.resolve(path24.join(toBaseDir, LOCAL_SKILLS_PATH));
16490
+ const fromPath = path26.resolve(path26.join(fromBaseDir, LOCAL_SKILLS_PATH, skillId));
16491
+ const toPath = path26.resolve(path26.join(toBaseDir, LOCAL_SKILLS_PATH, skillId));
16492
+ const fromSkillsDir = path26.resolve(path26.join(fromBaseDir, LOCAL_SKILLS_PATH));
16493
+ const toSkillsDir = path26.resolve(path26.join(toBaseDir, LOCAL_SKILLS_PATH));
15992
16494
  if (!isPathWithin(fromPath, fromSkillsDir)) {
15993
16495
  warn(`Skill ID '${skillId}' resolves outside the source skills directory.`);
15994
16496
  return;
@@ -16015,21 +16517,94 @@ async function migrateLocalSkillScope(skillId, fromScope, projectDir) {
16015
16517
 
16016
16518
  // src/cli/lib/matrix/matrix-loader.ts
16017
16519
  import { parse as parseYaml3 } from "yaml";
16018
- import path25 from "path";
16019
-
16020
- // src/cli/lib/matrix/skill-resolution.ts
16021
- import { uniqueBy as uniqueBy4 } from "remeda";
16022
-
16023
- // src/cli/utils/string.ts
16024
- function truncateText(text, maxLength) {
16025
- if (text.length <= maxLength) return text;
16026
- return text.slice(0, maxLength - 1) + "\u2026";
16520
+ import path27 from "path";
16521
+ async function loadSkillCategories(configPath) {
16522
+ const data = await loadConfig(configPath, skillCategoriesFileSchema);
16523
+ if (!data) {
16524
+ throw new Error(`Invalid skill categories at '${configPath}': failed to load or validate`);
16525
+ }
16526
+ verbose(`Loaded skill categories: ${configPath}`);
16527
+ return data.categories;
16027
16528
  }
16028
- function toTitleCase(kebabCase) {
16029
- return kebabCase.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
16529
+ async function loadSkillRules(configPath) {
16530
+ const data = await loadConfig(configPath, skillRulesFileSchema);
16531
+ if (!data) {
16532
+ throw new Error(`Invalid skill rules at '${configPath}': failed to load or validate`);
16533
+ }
16534
+ const config = {
16535
+ version: data.version,
16536
+ relationships: data.relationships ?? {
16537
+ conflicts: [],
16538
+ discourages: [],
16539
+ requires: [],
16540
+ alternatives: []
16541
+ }
16542
+ };
16543
+ verbose(`Loaded skill rules: ${configPath}`);
16544
+ return config;
16545
+ }
16546
+ async function extractAllSkills(skillsDir) {
16547
+ const skills = [];
16548
+ const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir);
16549
+ for (const metadataFile of metadataFiles) {
16550
+ const skillDir = path27.dirname(metadataFile);
16551
+ const skillMdPath = path27.join(skillsDir, skillDir, STANDARD_FILES.SKILL_MD);
16552
+ const metadataPath = path27.join(skillsDir, metadataFile);
16553
+ if (!await fileExists(skillMdPath)) {
16554
+ verbose(`Skipping ${metadataFile}: No ${STANDARD_FILES.SKILL_MD} found`);
16555
+ continue;
16556
+ }
16557
+ const metadataContent = await readFile(metadataPath);
16558
+ let rawMetadata;
16559
+ try {
16560
+ rawMetadata = parseYaml3(metadataContent);
16561
+ } catch (error) {
16562
+ warn(
16563
+ `Skipping '${metadataFile}': unparseable ${STANDARD_FILES.METADATA_YAML} at ${metadataPath} \u2014 ${getErrorMessage(error)}`
16564
+ );
16565
+ continue;
16566
+ }
16567
+ const metadataResult = matrixRawMetadataSchema.safeParse(rawMetadata);
16568
+ if (!metadataResult.success) {
16569
+ warn(
16570
+ `Skipping '${metadataFile}': invalid metadata.yaml \u2014 ${formatZodIssues(metadataResult.error.issues)}`
16571
+ );
16572
+ continue;
16573
+ }
16574
+ const metadata = metadataResult.data;
16575
+ const skillMdContent = await readFile(skillMdPath);
16576
+ const frontmatter = parseFrontmatter(skillMdContent, skillMdPath);
16577
+ if (!frontmatter) {
16578
+ verbose(`Skipping ${metadataFile}: Invalid SKILL.md frontmatter`);
16579
+ continue;
16580
+ }
16581
+ if (!metadata.displayName) {
16582
+ throw new Error(
16583
+ `Skill at ${metadataFile} is missing required '${METADATA_KEYS.DISPLAY_NAME}' field in metadata.yaml`
16584
+ );
16585
+ }
16586
+ const skillId = frontmatter.name;
16587
+ const extracted = {
16588
+ id: skillId,
16589
+ directoryPath: skillDir,
16590
+ description: metadata.cliDescription || frontmatter.description,
16591
+ ...metadata.usageGuidance !== void 0 && { usageGuidance: metadata.usageGuidance },
16592
+ category: metadata.category,
16593
+ author: metadata.author,
16594
+ path: `skills/${skillDir}/`,
16595
+ domain: metadata.domain,
16596
+ displayName: metadata.displayName,
16597
+ slug: metadata.slug,
16598
+ ...metadata.custom === true ? { custom: true } : {}
16599
+ };
16600
+ skills.push(extracted);
16601
+ verbose(`Extracted skill: ${skillId}`);
16602
+ }
16603
+ return skills;
16030
16604
  }
16031
16605
 
16032
16606
  // src/cli/lib/matrix/skill-resolution.ts
16607
+ import { unique as unique6, uniqueBy as uniqueBy4 } from "remeda";
16033
16608
  var AUTO_SYNTH_ORDER = 999;
16034
16609
  function synthesizeCategory(category, domain) {
16035
16610
  verbose(
@@ -16046,21 +16621,36 @@ function synthesizeCategory(category, domain) {
16046
16621
  order: AUTO_SYNTH_ORDER
16047
16622
  };
16048
16623
  }
16624
+ function claimSlug(slugMap, slug, id) {
16625
+ const incumbent = slugMap.slugToId[slug];
16626
+ if (incumbent === id) return;
16627
+ if (incumbent !== void 0) {
16628
+ warn(`Duplicate slug '${slug}': already mapped to '${incumbent}', ignoring '${id}'`);
16629
+ return;
16630
+ }
16631
+ slugMap.slugToId[slug] = id;
16632
+ slugMap.idToSlug[id] = slug;
16633
+ }
16049
16634
  function buildSlugMap(skills) {
16050
- const slugToId = {};
16051
- const idToSlug = {};
16635
+ const slugMap = { slugToId: {}, idToSlug: {} };
16636
+ for (const skill of skills) {
16637
+ claimSlug(slugMap, skill.slug, skill.id);
16638
+ }
16639
+ return slugMap;
16640
+ }
16641
+ function buildResolvedSkillMap(skills, categories, relationships, slugMap) {
16642
+ const resolvedSkills = {};
16052
16643
  for (const skill of skills) {
16053
- const existingId = slugToId[skill.slug];
16054
- if (existingId) {
16644
+ const existing = resolvedSkills[skill.id];
16645
+ if (existing) {
16055
16646
  warn(
16056
- `Duplicate slug '${skill.slug}': already mapped to '${existingId}', ignoring '${skill.id}'`
16647
+ `Duplicate skill id '${skill.id}': already resolved from '${existing.path}', ignoring '${skill.path}'`
16057
16648
  );
16058
16649
  continue;
16059
16650
  }
16060
- slugToId[skill.slug] = skill.id;
16061
- idToSlug[skill.id] = skill.slug;
16651
+ resolvedSkills[skill.id] = buildResolvedSkill(skill, categories, relationships, slugMap);
16062
16652
  }
16063
- return { slugToId, idToSlug };
16653
+ return resolvedSkills;
16064
16654
  }
16065
16655
  function resolveToCanonicalId(slug, slugToId, context) {
16066
16656
  const slugResult = slugToId[slug];
@@ -16071,13 +16661,48 @@ function resolveToCanonicalId(slug, slugToId, context) {
16071
16661
  warn(`Unresolved slug '${slug}'${location} \u2014 skipping`);
16072
16662
  return null;
16073
16663
  }
16664
+ function slugsNamedByRules(relationships) {
16665
+ return [
16666
+ ...relationships.conflicts.flatMap((rule) => rule.skills),
16667
+ ...relationships.discourages.flatMap((rule) => rule.skills),
16668
+ ...relationships.requires.flatMap((rule) => [rule.skill, ...rule.needs]),
16669
+ ...relationships.alternatives.flatMap((rule) => rule.skills)
16670
+ ];
16671
+ }
16672
+ function collectUnresolvedSlugs(relationships, slugToId) {
16673
+ return unique6(slugsNamedByRules(relationships).filter((slug) => slugToId[slug] === void 0));
16674
+ }
16675
+ function mergeRelationships(source, defaults) {
16676
+ return {
16677
+ conflicts: [...source.conflicts, ...defaults.conflicts],
16678
+ discourages: [...source.discourages, ...defaults.discourages],
16679
+ requires: [...source.requires, ...defaults.requires],
16680
+ alternatives: [...source.alternatives, ...defaults.alternatives]
16681
+ };
16682
+ }
16683
+ var MIN_RELATABLE_GROUP_MEMBERS = 2;
16684
+ function narrowGroupsToSlugs(rules, shipped) {
16685
+ return rules.map((rule) => ({ ...rule, skills: rule.skills.filter((slug) => shipped.has(slug)) })).filter((rule) => rule.skills.length >= MIN_RELATABLE_GROUP_MEMBERS);
16686
+ }
16687
+ function narrowRequirementsToSlugs(rules, shipped) {
16688
+ return rules.filter((rule) => shipped.has(rule.skill)).map((rule) => ({ ...rule, needs: rule.needs.filter((slug) => shipped.has(slug)) })).filter((rule) => rule.needs.length > 0);
16689
+ }
16690
+ function narrowToShippedSlugs(rules, shipped) {
16691
+ return {
16692
+ conflicts: narrowGroupsToSlugs(rules.conflicts, shipped),
16693
+ discourages: narrowGroupsToSlugs(rules.discourages, shipped),
16694
+ requires: narrowRequirementsToSlugs(rules.requires, shipped),
16695
+ alternatives: narrowGroupsToSlugs(rules.alternatives, shipped)
16696
+ };
16697
+ }
16698
+ function relationshipsForSource(skills, sourceRules) {
16699
+ const shipped = new Set(skills.map((skill) => skill.slug));
16700
+ const builtIn = narrowToShippedSlugs(defaultRules.relationships, shipped);
16701
+ return sourceRules ? mergeRelationships(sourceRules.relationships, builtIn) : builtIn;
16702
+ }
16074
16703
  function mergeMatrixWithSkills(categories, relationships, skills) {
16075
16704
  const slugMap = buildSlugMap(skills);
16076
- const resolvedSkills = {};
16077
- for (const skill of skills) {
16078
- const resolved = buildResolvedSkill(skill, categories, relationships, slugMap);
16079
- resolvedSkills[skill.id] = resolved;
16080
- }
16705
+ const resolvedSkills = buildResolvedSkillMap(skills, categories, relationships, slugMap);
16081
16706
  const synthesizedCategories = { ...categories };
16082
16707
  for (const skill of skills) {
16083
16708
  if (skill.category === LOCAL_PSEUDO_CATEGORY) continue;
@@ -16087,12 +16712,14 @@ function mergeMatrixWithSkills(categories, relationships, skills) {
16087
16712
  verbose(`Auto-synthesized category '${skill.category}' for skill '${skill.id}'`);
16088
16713
  }
16089
16714
  }
16715
+ const unresolvedSlugs = collectUnresolvedSlugs(relationships, slugMap.slugToId);
16090
16716
  const merged = {
16091
16717
  version: DEFAULT_VERSION,
16092
16718
  categories: synthesizedCategories,
16093
16719
  skills: resolvedSkills,
16094
16720
  suggestedStacks: [],
16095
16721
  slugMap,
16722
+ ...unresolvedSlugs.length > 0 && { unresolvedSlugs },
16096
16723
  generatedAt: (/* @__PURE__ */ new Date()).toISOString()
16097
16724
  };
16098
16725
  return merged;
@@ -16100,6 +16727,11 @@ function mergeMatrixWithSkills(categories, relationships, skills) {
16100
16727
  function resolveSlugsOrSkip(slugs, resolve, context) {
16101
16728
  return slugs.map((slug) => resolve(slug, context)).filter((id) => id !== null);
16102
16729
  }
16730
+ function resolveEveryNeed(needs, resolve) {
16731
+ if (needs.length === 0) return null;
16732
+ const resolved = resolveSlugsOrSkip(needs, resolve, "requires.needs");
16733
+ return resolved.length === needs.length ? resolved : null;
16734
+ }
16103
16735
  function collectSymmetricGroupMembers(rules, skillId, resolve, context) {
16104
16736
  const members = rules.flatMap((rule) => {
16105
16737
  const resolved = resolveSlugsOrSkip(rule.skills, resolve, context);
@@ -16131,8 +16763,8 @@ function resolveRelationships(skillId, relationships, resolve) {
16131
16763
  for (const rule of relationships.requires) {
16132
16764
  const ruleSkillId = resolve(rule.skill, "requires.skill");
16133
16765
  if (ruleSkillId !== skillId) continue;
16134
- const resolvedNeeds = resolveSlugsOrSkip(rule.needs, resolve, "requires.needs");
16135
- if (resolvedNeeds.length === 0) continue;
16766
+ const resolvedNeeds = resolveEveryNeed(rule.needs, resolve);
16767
+ if (!resolvedNeeds) continue;
16136
16768
  requires.push({
16137
16769
  skillIds: resolvedNeeds,
16138
16770
  needsAny: rule.needsAny ?? false,
@@ -16164,84 +16796,6 @@ function buildResolvedSkill(skill, _categories, relationships, slugMap) {
16164
16796
  };
16165
16797
  }
16166
16798
 
16167
- // src/cli/lib/matrix/matrix-loader.ts
16168
- async function loadSkillCategories(configPath) {
16169
- const data = await loadConfig(configPath, skillCategoriesFileSchema);
16170
- if (!data) {
16171
- throw new Error(`Invalid skill categories at '${configPath}': failed to load or validate`);
16172
- }
16173
- verbose(`Loaded skill categories: ${configPath}`);
16174
- return data.categories;
16175
- }
16176
- async function loadSkillRules(configPath) {
16177
- const data = await loadConfig(configPath, skillRulesFileSchema);
16178
- if (!data) {
16179
- throw new Error(`Invalid skill rules at '${configPath}': failed to load or validate`);
16180
- }
16181
- const config = {
16182
- version: data.version,
16183
- relationships: data.relationships ?? {
16184
- conflicts: [],
16185
- discourages: [],
16186
- requires: [],
16187
- alternatives: []
16188
- }
16189
- };
16190
- verbose(`Loaded skill rules: ${configPath}`);
16191
- return config;
16192
- }
16193
- async function extractAllSkills(skillsDir) {
16194
- const skills = [];
16195
- const metadataFiles = await glob(`**/${STANDARD_FILES.METADATA_YAML}`, skillsDir);
16196
- for (const metadataFile of metadataFiles) {
16197
- const skillDir = path25.dirname(metadataFile);
16198
- const skillMdPath = path25.join(skillsDir, skillDir, STANDARD_FILES.SKILL_MD);
16199
- const metadataPath = path25.join(skillsDir, metadataFile);
16200
- if (!await fileExists(skillMdPath)) {
16201
- verbose(`Skipping ${metadataFile}: No ${STANDARD_FILES.SKILL_MD} found`);
16202
- continue;
16203
- }
16204
- const metadataContent = await readFile(metadataPath);
16205
- const rawMetadata = parseYaml3(metadataContent);
16206
- const metadataResult = matrixRawMetadataSchema.safeParse(rawMetadata);
16207
- if (!metadataResult.success) {
16208
- warn(
16209
- `Skipping '${metadataFile}': invalid metadata.yaml \u2014 ${formatZodIssues(metadataResult.error.issues)}`
16210
- );
16211
- continue;
16212
- }
16213
- const metadata = metadataResult.data;
16214
- const skillMdContent = await readFile(skillMdPath);
16215
- const frontmatter = parseFrontmatter(skillMdContent, skillMdPath);
16216
- if (!frontmatter) {
16217
- verbose(`Skipping ${metadataFile}: Invalid SKILL.md frontmatter`);
16218
- continue;
16219
- }
16220
- if (!metadata.displayName) {
16221
- throw new Error(
16222
- `Skill at ${metadataFile} is missing required '${METADATA_KEYS.DISPLAY_NAME}' field in metadata.yaml`
16223
- );
16224
- }
16225
- const skillId = frontmatter.name;
16226
- const extracted = {
16227
- id: skillId,
16228
- directoryPath: skillDir,
16229
- description: metadata.cliDescription || frontmatter.description,
16230
- ...metadata.usageGuidance !== void 0 && { usageGuidance: metadata.usageGuidance },
16231
- category: metadata.category,
16232
- author: metadata.author,
16233
- path: `skills/${skillDir}/`,
16234
- domain: metadata.domain,
16235
- displayName: metadata.displayName,
16236
- slug: metadata.slug,
16237
- ...metadata.custom === true ? { custom: true } : {}
16238
- };
16239
- skills.push(extracted);
16240
- verbose(`Extracted skill: ${skillId}`);
16241
- }
16242
- return skills;
16243
- }
16244
-
16245
16799
  // src/cli/lib/matrix/matrix-resolver.ts
16246
16800
  import { groupBy as groupBy5 } from "remeda";
16247
16801
  function getLabel(skill) {
@@ -18045,6 +18599,7 @@ function checkMatrixHealth(matrix2) {
18045
18599
  ...checkCategoryDomains(matrix2),
18046
18600
  ...checkSkillCategories(matrix2),
18047
18601
  ...checkSkillRelationRefs(matrix2),
18602
+ ...checkUnresolvedRuleSlugs(matrix2),
18048
18603
  ...checkAuditVerdictContradictions(matrix2),
18049
18604
  ...checkUnauditedSkills(matrix2)
18050
18605
  ];
@@ -18093,6 +18648,13 @@ function checkSkillRelationRefs(matrix2) {
18093
18648
  )
18094
18649
  );
18095
18650
  }
18651
+ function checkUnresolvedRuleSlugs(matrix2) {
18652
+ return (matrix2.unresolvedSlugs ?? []).map((slug) => ({
18653
+ severity: "error",
18654
+ finding: "rule-unresolved-slug",
18655
+ details: `Unresolved slug '${slug}' in the relationship rules \u2014 no skill carries it, so every rule naming it states nothing`
18656
+ }));
18657
+ }
18096
18658
  function isExclusiveCategory3(matrix2, category) {
18097
18659
  if (category === LOCAL_PSEUDO_CATEGORY) return false;
18098
18660
  const definition = matrix2.categories[category];
@@ -18125,12 +18687,12 @@ function checkUnauditedSkills(matrix2) {
18125
18687
  return typedEntries(matrix2.skills).filter(
18126
18688
  ([skillId, skill]) => (
18127
18689
  // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- typedEntries/Object.entries launders the `| undefined` a Partial<Record> admits out of its result type, so this guard reads as dead while still covering an explicitly-undefined slot
18128
- skill !== void 0 && !skill.local && auditEntryFor(skillId) === void 0
18690
+ skill !== void 0 && !skill.local && isSkillId(skillId) && auditEntryFor(skillId) === void 0
18129
18691
  )
18130
18692
  ).map(([skillId]) => ({
18131
18693
  severity: "warning",
18132
18694
  finding: "skill-unaudited",
18133
- details: `Skill '${skillId}' has no audit verdict \u2014 source-provided skills are outside the built-in audit manifest`
18695
+ details: `Skill '${skillId}' has no audit verdict \u2014 the built-in audit manifest is missing an entry it is meant to carry`
18134
18696
  }));
18135
18697
  }
18136
18698
 
@@ -18148,6 +18710,7 @@ async function loadSkillsMatrixFromSource(options = {}) {
18148
18710
  verbose(`Loading skills from source: ${source}`);
18149
18711
  const result = await resolveBaseResult(source, sourceConfig, devMode, matrixOnly);
18150
18712
  const resolvedProjectDir = projectDir || process.cwd();
18713
+ const marketplaceSkillIds = new Set(typedKeys(result.matrix.skills));
18151
18714
  const homeDir = os15.homedir();
18152
18715
  if (!isHomeDirectory(resolvedProjectDir)) {
18153
18716
  result.matrix = await mergeDiscoveredLocalSkills(result.matrix, homeDir, "global");
@@ -18158,23 +18721,34 @@ async function loadSkillsMatrixFromSource(options = {}) {
18158
18721
  result.matrix,
18159
18722
  sourceConfig,
18160
18723
  resolvedProjectDir,
18161
- result.marketplace
18724
+ result.marketplace,
18725
+ idsOutsideMarketplace(result.matrix, marketplaceSkillIds)
18162
18726
  );
18163
18727
  }
18164
18728
  checkMatrixHealth(result.matrix);
18165
18729
  initializeMatrix(result.matrix);
18166
18730
  return result;
18167
18731
  }
18732
+ function idsOutsideMarketplace(matrix2, marketplaceSkillIds) {
18733
+ return new Set(typedKeys(matrix2.skills).filter((id) => !marketplaceSkillIds.has(id)));
18734
+ }
18735
+ function copyOfBuiltInMatrix() {
18736
+ return {
18737
+ ...BUILT_IN_MATRIX,
18738
+ skills: { ...BUILT_IN_MATRIX.skills },
18739
+ categories: { ...BUILT_IN_MATRIX.categories },
18740
+ suggestedStacks: [...BUILT_IN_MATRIX.suggestedStacks],
18741
+ slugMap: {
18742
+ slugToId: { ...BUILT_IN_MATRIX.slugMap.slugToId },
18743
+ idToSlug: { ...BUILT_IN_MATRIX.slugMap.idToSlug }
18744
+ }
18745
+ };
18746
+ }
18168
18747
  async function resolveBaseResult(source, sourceConfig, devMode, matrixOnly) {
18169
18748
  if (isDefaultSource(source) && !devMode) {
18170
18749
  const sourcePath = matrixOnly ? "" : (await fetchFromSource(source)).path;
18171
18750
  return {
18172
- matrix: {
18173
- ...BUILT_IN_MATRIX,
18174
- skills: { ...BUILT_IN_MATRIX.skills },
18175
- categories: { ...BUILT_IN_MATRIX.categories },
18176
- suggestedStacks: [...BUILT_IN_MATRIX.suggestedStacks]
18177
- },
18751
+ matrix: copyOfBuiltInMatrix(),
18178
18752
  sourceConfig,
18179
18753
  sourcePath,
18180
18754
  isLocal: false,
@@ -18184,44 +18758,40 @@ async function resolveBaseResult(source, sourceConfig, devMode, matrixOnly) {
18184
18758
  const isLocal = isLocalSource(source) || devMode === true;
18185
18759
  return isLocal ? loadFromLocal(source, sourceConfig) : loadFromRemote(source, sourceConfig);
18186
18760
  }
18187
- async function resolveMarketplaceLabels(source, sourceConfig) {
18761
+ async function readManifestState(source) {
18188
18762
  try {
18189
- const marketplaceResult = await fetchMarketplace(source);
18190
- const marketplace = sourceConfig.marketplace ?? marketplaceResult.marketplace.name;
18191
- verbose(`Using marketplace name from marketplace.json: ${marketplace}`);
18192
- return { marketplace };
18193
- } catch {
18194
- verbose(`Source does not have a marketplace.json \u2014 using source name as label`);
18195
- return sourceConfig.marketplace === void 0 ? {} : { marketplace: sourceConfig.marketplace };
18763
+ const { marketplace } = await fetchMarketplace(source);
18764
+ return { kind: "named", name: marketplace.name };
18765
+ } catch (error) {
18766
+ if (error instanceof MarketplaceManifestAbsentError) return { kind: "absent" };
18767
+ return { kind: "unreadable", reason: getErrorMessage(error) };
18196
18768
  }
18197
18769
  }
18198
- function mergeRelationships(source, defaults) {
18199
- return {
18200
- conflicts: [...source.conflicts, ...defaults.conflicts],
18201
- discourages: [...source.discourages, ...defaults.discourages],
18202
- requires: [...source.requires, ...defaults.requires],
18203
- alternatives: [...source.alternatives, ...defaults.alternatives]
18204
- };
18205
- }
18206
- var MIN_RELATABLE_GROUP_MEMBERS = 2;
18207
- function narrowGroupsToSlugs(rules, shipped) {
18208
- return rules.map((rule) => ({ ...rule, skills: rule.skills.filter((slug) => shipped.has(slug)) })).filter((rule) => rule.skills.length >= MIN_RELATABLE_GROUP_MEMBERS);
18209
- }
18210
- function narrowRequirementsToSlugs(rules, shipped) {
18211
- return rules.filter((rule) => shipped.has(rule.skill)).map((rule) => ({ ...rule, needs: rule.needs.filter((slug) => shipped.has(slug)) })).filter((rule) => rule.needs.length > 0);
18770
+ function configuredLabel(sourceConfig) {
18771
+ return sourceConfig.marketplace === void 0 ? {} : { marketplace: sourceConfig.marketplace };
18212
18772
  }
18213
- function narrowToShippedSlugs(rules, shipped) {
18214
- return {
18215
- conflicts: narrowGroupsToSlugs(rules.conflicts, shipped),
18216
- discourages: narrowGroupsToSlugs(rules.discourages, shipped),
18217
- requires: narrowRequirementsToSlugs(rules.requires, shipped),
18218
- alternatives: narrowGroupsToSlugs(rules.alternatives, shipped)
18219
- };
18220
- }
18221
- function relationshipsForSource(sourceRules, skills) {
18222
- const shipped = new Set(skills.map((skill) => skill.slug));
18223
- const builtIn = narrowToShippedSlugs(defaultRules.relationships, shipped);
18224
- return sourceRules ? mergeRelationships(sourceRules.relationships, builtIn) : builtIn;
18773
+ async function resolveMarketplaceLabels(source, sourceConfig) {
18774
+ const state = await readManifestState(source);
18775
+ switch (state.kind) {
18776
+ case "named": {
18777
+ const marketplace = sourceConfig.marketplace ?? state.name;
18778
+ verbose(`Using marketplace name from ${MARKETPLACE_JSON}: ${marketplace}`);
18779
+ return { marketplace };
18780
+ }
18781
+ case "absent":
18782
+ verbose(`Marketplace has no ${MARKETPLACE_JSON} \u2014 using its ref as the label`);
18783
+ return configuredLabel(sourceConfig);
18784
+ case "unreadable":
18785
+ warn(
18786
+ `Marketplace has a ${MARKETPLACE_JSON} this CLI cannot read, so its ref is the label instead:
18787
+ ${state.reason}`
18788
+ );
18789
+ return configuredLabel(sourceConfig);
18790
+ default: {
18791
+ const exhaustive = state;
18792
+ return exhaustive;
18793
+ }
18794
+ }
18225
18795
  }
18226
18796
  async function mergeDiscoveredLocalSkills(matrix2, dir, label) {
18227
18797
  const discovered = await discoverLocalSkills(dir);
@@ -18258,12 +18828,15 @@ async function loadFromRemote(source, sourceConfig) {
18258
18828
  ...labels
18259
18829
  };
18260
18830
  }
18831
+ async function loadMarketplaceMatrix(marketplaceDir) {
18832
+ return loadAndMergeFromBasePath(marketplaceDir, marketplaceDir);
18833
+ }
18261
18834
  async function loadAndMergeFromBasePath(basePath, source) {
18262
18835
  const sourceProjectConfig = await loadProjectSourceConfig(basePath);
18263
18836
  const skillsDirRelPath = sourceProjectConfig?.skillsDir ?? SKILLS_DIR_PATH;
18264
18837
  const stacksRelFile = sourceProjectConfig?.stacksFile;
18265
- const sourceCategoriesPath = path26.join(basePath, SKILL_CATEGORIES_PATH);
18266
- const sourceRulesPath = path26.join(basePath, SKILL_RULES_PATH);
18838
+ const sourceCategoriesPath = path28.join(basePath, SKILL_CATEGORIES_PATH);
18839
+ const sourceRulesPath = path28.join(basePath, SKILL_RULES_PATH);
18267
18840
  const hasSourceCategories = await fileExists(sourceCategoriesPath);
18268
18841
  const hasSourceRules = await fileExists(sourceRulesPath);
18269
18842
  const sourceCategories = hasSourceCategories ? await loadSkillCategories(sourceCategoriesPath) : void 0;
@@ -18282,10 +18855,11 @@ async function loadAndMergeFromBasePath(basePath, source) {
18282
18855
  } else {
18283
18856
  verbose(`Matrix from CLI only (source has no categories/rules files)`);
18284
18857
  }
18285
- const skillsDir = path26.join(basePath, skillsDirRelPath);
18858
+ const skillsDir = path28.join(basePath, skillsDirRelPath);
18286
18859
  verbose(`Skills from source: ${skillsDir}`);
18287
18860
  const skills = await extractAllSkills(skillsDir);
18288
- const relationships = relationshipsForSource(sourceRules, skills);
18861
+ await refuseCatalogueCollisions(basePath, source, skills);
18862
+ const relationships = relationshipsForSource(skills, sourceRules);
18289
18863
  const mergedMatrix = mergeMatrixWithSkills(categories, relationships, skills);
18290
18864
  initializeMatrix(mergedMatrix);
18291
18865
  const stacks = await resolveOfferedStacks(basePath, stacksRelFile, source);
@@ -18303,18 +18877,38 @@ async function loadAndMergeFromBasePath(basePath, source) {
18303
18877
  }
18304
18878
  return mergedMatrix;
18305
18879
  }
18880
+ var CATALOGUE_SKILL_IDS = new Set(typedKeys(BUILT_IN_MATRIX.skills));
18881
+ var MAX_REPORTED_COLLISIONS = 10;
18882
+ async function refuseCatalogueCollisions(basePath, source, skills) {
18883
+ const collidingIds = skills.map((skill) => skill.id).filter((id) => CATALOGUE_SKILL_IDS.has(id));
18884
+ if (collidingIds.length === 0) return;
18885
+ if (await isPublicCatalogueCheckout(basePath)) return;
18886
+ throw new Error(catalogueCollisionError(collidingIds, source));
18887
+ }
18888
+ function catalogueCollisionError(collidingIds, source) {
18889
+ const listed = collidingIds.slice(0, MAX_REPORTED_COLLISIONS).map((id) => ` ${id}`);
18890
+ const unlisted = collidingIds.length - listed.length;
18891
+ return [
18892
+ `Marketplace '${source}' ships ${collidingIds.length} skill id(s) the public catalogue already owns:`,
18893
+ ...listed,
18894
+ ...unlisted > 0 ? [` ... and ${unlisted} more`] : [],
18895
+ `A skill id is the directory the skill installs into, so these would shadow the public catalogue's own skills. Every id must carry its marketplace's name as a namespace \u2014 '<marketplace>-<id>'. Rename each skill and the 'name' in its SKILL.md, then re-run 'build marketplace', which refuses the same ids before they are published.`
18896
+ ].join("\n");
18897
+ }
18306
18898
  async function resolveOfferedStacks(basePath, stacksFile, source) {
18307
18899
  const sourceStacks = await loadStacks(basePath, stacksFile);
18308
18900
  if (sourceStacks.length > 0) {
18309
18901
  verbose(`Offering the ${sourceStacks.length} stacks the source ships`);
18310
18902
  return sourceStacks;
18311
18903
  }
18312
- if (!isDefaultSource(source)) {
18313
- verbose(`Source '${source}' ships no stacks, and gets no built-in stand-in \u2014 offering none`);
18314
- return [];
18904
+ if (await offersBuiltInStacks(basePath, source)) {
18905
+ verbose(
18906
+ `The public catalogue ships no stacks \u2014 offering the ${defaultStacks.length} built-in stacks`
18907
+ );
18908
+ return defaultStacks;
18315
18909
  }
18316
- verbose(`Default source ships no stacks \u2014 offering the ${defaultStacks.length} built-in stacks`);
18317
- return defaultStacks;
18910
+ verbose(`Marketplace '${source}' ships no stacks, and gets no built-in stand-in \u2014 offering none`);
18911
+ return [];
18318
18912
  }
18319
18913
  function convertStackToResolvedStack(stack) {
18320
18914
  const agentConfigs = typedKeys(stack.agents).flatMap((agentId) => {
@@ -18326,7 +18920,7 @@ function convertStackToResolvedStack(stack) {
18326
18920
  ({ agentId, agentConfig }) => [agentId, resolveStackAgentSkills(agentConfig)]
18327
18921
  )
18328
18922
  );
18329
- const allSkillIds = unique5(
18923
+ const allSkillIds = unique7(
18330
18924
  agentConfigs.flatMap(
18331
18925
  ({ agentConfig }) => resolveAgentConfigToSkills(agentConfig).map((ref) => ref.id)
18332
18926
  )
@@ -18358,12 +18952,23 @@ function extractSourceName(source) {
18358
18952
  const firstSegment = withoutUrl.split("/")[0];
18359
18953
  return firstSegment || source;
18360
18954
  }
18955
+ function declaresCategory(matrix2, category) {
18956
+ return category !== LOCAL_PSEUDO_CATEGORY && matrix2.categories[category] !== void 0;
18957
+ }
18958
+ function undeclaredCategoryRefusal(metadata, category) {
18959
+ const metadataPath = path28.join(metadata.path, STANDARD_FILES.METADATA_YAML);
18960
+ return `Skipping local skill '${metadata.id}': ${METADATA_KEYS.CATEGORY} '${category}' is not one this installation declares, so the skill belongs in no grid tab and can be given to no sub-agent. Set ${METADATA_KEYS.CATEGORY} in ${metadataPath} to a category that already exists \u2014 a skill is placed in the taxonomy, it does not extend it.`;
18961
+ }
18361
18962
  function mergeLocalSkillsIntoMatrix(matrix2, localResult) {
18362
18963
  for (const metadata of localResult.skills) {
18363
18964
  const existingSkill = matrix2.skills[metadata.id];
18364
18965
  const category = existingSkill?.category ?? metadata.category;
18365
18966
  const slug = existingSkill?.slug ?? metadata.slug;
18366
18967
  const displayName = existingSkill?.displayName ?? metadata.displayName;
18968
+ if (metadata.custom === true && !declaresCategory(matrix2, category)) {
18969
+ warn(undeclaredCategoryRefusal(metadata, category));
18970
+ continue;
18971
+ }
18367
18972
  const resolvedSkill = {
18368
18973
  id: metadata.id,
18369
18974
  slug,
@@ -18382,6 +18987,7 @@ function mergeLocalSkillsIntoMatrix(matrix2, localResult) {
18382
18987
  ...metadata.custom !== void 0 && { custom: metadata.custom }
18383
18988
  };
18384
18989
  matrix2.skills[metadata.id] = resolvedSkill;
18990
+ claimSlug(matrix2.slugMap, slug, metadata.id);
18385
18991
  if (category !== LOCAL_PSEUDO_CATEGORY && !matrix2.categories[category]) {
18386
18992
  matrix2.categories[category] = {
18387
18993
  id: category,
@@ -18400,6 +19006,7 @@ function mergeLocalSkillsIntoMatrix(matrix2, localResult) {
18400
19006
  }
18401
19007
 
18402
19008
  export {
19009
+ validateKebabCaseName,
18403
19010
  ERROR_MESSAGES,
18404
19011
  SUCCESS_MESSAGES,
18405
19012
  STATUS_MESSAGES,
@@ -18414,17 +19021,31 @@ export {
18414
19021
  marketplacesRefreshed,
18415
19022
  marketplaceRefreshFailed,
18416
19023
  marketplacesRefreshFailed,
19024
+ marketplaceOwnerHasNoName,
19025
+ marketplaceHasNoVersion,
19026
+ marketplaceNameNotPublishable,
18417
19027
  configTypesRefreshFailed,
18418
19028
  registeredProjectsUpdated,
18419
19029
  registeredProjectUpdateSkipped,
18420
19030
  skillMetadataUnusableDetail,
18421
19031
  skillMetadataUnusableError,
18422
19032
  savedSkillMetadataUnusableError,
19033
+ sharedConfigDestinations,
18423
19034
  sharedConfigExistingInstall,
18424
19035
  sharedConfigGlobalInstall,
19036
+ sharedConfigProjectScopeAtHome,
19037
+ skippedUnknownSkills,
19038
+ skippedUnknownAgents,
19039
+ carriedSkillsWritten,
19040
+ SHARED_CONFIG_APPLY,
19041
+ sharedConfigNeedsTerminal,
19042
+ SHARED_CONFIG_ONE_DIRECTION,
19043
+ globallyInstalledRemoved,
19044
+ authoredHereKept,
19045
+ unplaceableKept,
18425
19046
  configUnreadableError,
18426
19047
  registeredProjectsUpdateFailed,
18427
- compiledAgentsKept,
19048
+ unmarkedAgentsKept,
18428
19049
  UNINSTALL_PLAN,
18429
19050
  localSkillsRemoval,
18430
19051
  compiledAgentsRemoval,
@@ -18434,9 +19055,7 @@ export {
18434
19055
  activeAgentNames,
18435
19056
  activeAgentScopeMap,
18436
19057
  effectivelyExcludedSkillIds,
18437
- isRecord,
18438
- isDomain2 as isDomain,
18439
- isAgentName,
19058
+ ConfigDefaultExportError,
18440
19059
  loadConfig,
18441
19060
  installBaseDir,
18442
19061
  getProjectConfigPath,
@@ -18454,10 +19073,22 @@ export {
18454
19073
  findConfigLoadFailures,
18455
19074
  loadProjectConfig,
18456
19075
  validateProjectConfig,
19076
+ INSTALL_MODE_LABELS,
19077
+ INSTALL_MODE_DESCRIPTIONS,
19078
+ declaresNoContent,
19079
+ deriveInstallMode,
19080
+ detectProjectInstallation,
19081
+ detectGlobalInstallation,
19082
+ detectInstallation,
18457
19083
  computeStringHash,
19084
+ computeFileHash,
18458
19085
  determinePluginVersion,
18459
19086
  writeContentHash,
18460
19087
  readForkedFromMetadata,
19088
+ readLocalSkillMetadata,
19089
+ injectForkedFromMetadata,
19090
+ writeMetadataYaml,
19091
+ validateSkillPath,
18461
19092
  copySkillsToLocalFlattened,
18462
19093
  generateAgentPluginManifest,
18463
19094
  writePluginManifest,
@@ -18469,39 +19100,56 @@ export {
18469
19100
  buildMarketplacePluginRef,
18470
19101
  parseMarketplacePluginRef,
18471
19102
  toClaudePluginScope,
19103
+ MATRIX_VERSION,
18472
19104
  DOMAIN_ORDER,
18473
19105
  DOMAIN_DESCRIPTIONS,
18474
19106
  DEFAULT_SELECTION_OPTIONS,
19107
+ SEED_VERSION,
19108
+ seedModelSchema,
19109
+ seedExternalSkillSchema,
18475
19110
  seedPayloadSchema,
18476
19111
  isScopePairCompatible,
18477
- fetchFromSource,
18478
- fetchMarketplace,
18479
19112
  truncateText,
18480
19113
  toTitleCase,
19114
+ fetchFromSource,
19115
+ fetchMarketplace,
19116
+ isLocalOnlySkill,
19117
+ claimSlug,
18481
19118
  getUnmetRequiredBy,
18482
19119
  getCellState,
18483
19120
  validateSelection,
18484
19121
  getAvailableSkills,
18485
19122
  checkMatrixHealth,
18486
19123
  loadSkillsMatrixFromSource,
19124
+ loadMarketplaceMatrix,
18487
19125
  convertStackToResolvedStack,
18488
19126
  extractSourceName,
18489
19127
  mergeLocalSkillsIntoMatrix,
18490
- INSTALL_MODE_LABELS,
18491
- INSTALL_MODE_DESCRIPTIONS,
18492
- declaresNoContent,
18493
- deriveInstallMode,
18494
- detectProjectInstallation,
18495
- detectGlobalInstallation,
18496
- detectInstallation,
18497
- resolveAgents,
18498
- createLiquidEngine,
18499
- writeCompiledAgentsByScope,
18500
19128
  getInstalledPluginsRegistryPath,
18501
19129
  listRegisteredPluginInstalls,
18502
19130
  getVerifiedPluginInstallPaths,
18503
19131
  discoverAllPluginSkills,
18504
19132
  listPluginNames,
19133
+ getInstallationInfo,
19134
+ formatInstallationDisplay,
19135
+ validateSkillFrontmatter,
19136
+ validateAgentFrontmatter,
19137
+ validatePlugin,
19138
+ validateAllPlugins,
19139
+ compileSkillPlugin,
19140
+ compileAllSkillPlugins,
19141
+ printCompilationSummary,
19142
+ discoverLocalSkills,
19143
+ findUnusableSavedSkillMetadata,
19144
+ unresolvedSkillRemovalReasons,
19145
+ deleteLocalSkill,
19146
+ migrateLocalSkillScope,
19147
+ resolveAgents,
19148
+ hasProvenanceMarker,
19149
+ createLiquidEngine,
19150
+ writeCompiledAgentsByScope,
19151
+ normalizeProjectPath,
19152
+ execCommand,
18505
19153
  claudePluginInstall,
18506
19154
  isClaudeCLIAvailable,
18507
19155
  claudePluginMarketplaceExists,
@@ -18520,21 +19168,8 @@ export {
18520
19168
  buildAndMergeConfig,
18521
19169
  buildCompileAgents,
18522
19170
  buildAgentScopeMap,
19171
+ ejectCopyFailureError,
18523
19172
  detectMigrations,
18524
- executeMigration,
18525
- getInstallationInfo,
18526
- formatInstallationDisplay,
18527
- validateSkillFrontmatter,
18528
- validateAgentFrontmatter,
18529
- validatePlugin,
18530
- validateAllPlugins,
18531
- compileSkillPlugin,
18532
- compileAllSkillPlugins,
18533
- printCompilationSummary,
18534
- discoverLocalSkills,
18535
- findUnusableSavedSkillMetadata,
18536
- unresolvedSkillRemovalReasons,
18537
- deleteLocalSkill,
18538
- migrateLocalSkillScope
19173
+ executeMigration
18539
19174
  };
18540
- //# sourceMappingURL=chunk-CBF4ZYV3.js.map
19175
+ //# sourceMappingURL=chunk-WCAUUBHO.js.map