@canonical/pragma-cli 0.29.0 → 0.31.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 (410) hide show
  1. package/README.md +128 -47
  2. package/dist/pragma +0 -0
  3. package/package.json +15 -13
  4. package/src/completions/constants.ts +7 -0
  5. package/src/completions/handleQuery.test.ts +32 -0
  6. package/src/completions/handleQuery.ts +20 -1
  7. package/src/completions/queryCompletions.test.ts +5 -0
  8. package/src/completions/queryCompletions.ts +17 -3
  9. package/src/completions/querySocket.test.ts +31 -0
  10. package/src/completions/querySocket.ts +36 -9
  11. package/src/config/defaultConfig.test.ts +33 -0
  12. package/src/config/defaultConfig.ts +59 -0
  13. package/src/config/defaults.json +17 -0
  14. package/src/config/findProjectConfigPath.test.ts +53 -0
  15. package/src/config/findProjectConfigPath.ts +36 -0
  16. package/src/config/index.ts +17 -2
  17. package/src/config/mergeConfigFileUpdate.ts +58 -0
  18. package/src/config/parseConfigValues.ts +217 -0
  19. package/src/config/readConfig.test.ts +104 -13
  20. package/src/config/readConfig.ts +12 -153
  21. package/src/config/readConfigLayers.test.ts +109 -0
  22. package/src/config/readConfigLayers.ts +118 -0
  23. package/src/config/resolveGlobalConfigPath.ts +13 -0
  24. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  25. package/src/config/resolveWriteConfigPath.ts +31 -0
  26. package/src/config/types.ts +83 -1
  27. package/src/config/writeConfig.ts +25 -55
  28. package/src/constants.ts +3 -1
  29. package/src/domains/block/commands/list.ts +5 -90
  30. package/src/domains/block/commands/lookup.ts +8 -167
  31. package/src/domains/block/commands/sample.ts +2 -1
  32. package/src/domains/block/formatters/list.test.ts +3 -3
  33. package/src/domains/block/formatters/lookup.test.ts +5 -5
  34. package/src/domains/block/mcp/specs.ts +16 -196
  35. package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
  36. package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
  37. package/src/domains/block/stories.test.ts +28 -0
  38. package/src/domains/block/stories.ts +260 -0
  39. package/src/domains/config/commands/channel.test.ts +30 -3
  40. package/src/domains/config/commands/channel.ts +25 -8
  41. package/src/domains/config/commands/configScope.ts +46 -0
  42. package/src/domains/config/commands/framework.test.ts +32 -1
  43. package/src/domains/config/commands/framework.ts +26 -8
  44. package/src/domains/config/commands/show.ts +5 -45
  45. package/src/domains/config/commands/tier.test.ts +20 -2
  46. package/src/domains/config/commands/tier.ts +25 -8
  47. package/src/domains/config/commands/trace.test.ts +19 -2
  48. package/src/domains/config/commands/trace.ts +6 -3
  49. package/src/domains/config/formatters/channel.test.ts +20 -6
  50. package/src/domains/config/formatters/channel.ts +14 -11
  51. package/src/domains/config/formatters/framework.ts +14 -11
  52. package/src/domains/config/formatters/show.test.ts +27 -0
  53. package/src/domains/config/formatters/show.ts +27 -4
  54. package/src/domains/config/formatters/tier.test.ts +26 -10
  55. package/src/domains/config/formatters/tier.ts +19 -11
  56. package/src/domains/config/formatters/trace.ts +7 -7
  57. package/src/domains/config/mcp/specs.ts +33 -53
  58. package/src/domains/config/operations/show.test.ts +18 -0
  59. package/src/domains/config/operations/show.ts +7 -1
  60. package/src/domains/config/operations/types.ts +7 -0
  61. package/src/domains/config/operations/validateTier.test.ts +9 -0
  62. package/src/domains/config/operations/validateTier.ts +10 -2
  63. package/src/domains/config/stories.ts +42 -0
  64. package/src/domains/config/tasks/index.ts +1 -0
  65. package/src/domains/config/tasks/setChannel.ts +13 -28
  66. package/src/domains/config/tasks/setFramework.ts +11 -30
  67. package/src/domains/config/tasks/setTier.ts +10 -29
  68. package/src/domains/config/tasks/setTrace.ts +10 -28
  69. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  70. package/src/domains/create/generatorPack/bundled.ts +49 -0
  71. package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
  72. package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
  73. package/src/domains/create/index.ts +13 -7
  74. package/src/domains/create/mcp/specs.ts +6 -161
  75. package/src/domains/create/renderGeneratorUi.test.ts +103 -0
  76. package/src/domains/create/renderGeneratorUi.ts +65 -0
  77. package/src/domains/doctor/formatters/doctor.test.ts +50 -2
  78. package/src/domains/doctor/formatters/doctor.ts +90 -38
  79. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  80. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +133 -0
  81. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  82. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +32 -26
  83. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  84. package/src/domains/doctor/operations/checks/index.ts +1 -0
  85. package/src/domains/doctor/operations/checks.test.ts +160 -7
  86. package/src/domains/doctor/operations/runChecks.test.ts +15 -6
  87. package/src/domains/doctor/operations/runChecks.ts +2 -0
  88. package/src/domains/doctor/operations/types.ts +23 -1
  89. package/src/domains/graph/commands/inspect.test.ts +2 -2
  90. package/src/domains/graph/commands/query.test.ts +5 -3
  91. package/src/domains/graph/formatters/inspect.ts +7 -7
  92. package/src/domains/graph/formatters/query.test.ts +87 -0
  93. package/src/domains/graph/formatters/query.ts +42 -5
  94. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  95. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  96. package/src/domains/graph/operations/inspectUri.test.ts +5 -5
  97. package/src/domains/graphql/commands/build.test.ts +5 -3
  98. package/src/domains/graphql/commands/build.ts +8 -16
  99. package/src/domains/graphql/commands/check.test.ts +5 -3
  100. package/src/domains/graphql/commands/check.ts +7 -13
  101. package/src/domains/graphql/commands/index.ts +1 -0
  102. package/src/domains/graphql/commands/serve.test.ts +46 -0
  103. package/src/domains/graphql/commands/serve.ts +141 -0
  104. package/src/domains/graphql/constants.ts +10 -0
  105. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  106. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  107. package/src/domains/graphql/helpers/index.ts +2 -0
  108. package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
  109. package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
  110. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
  111. package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
  112. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  113. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  114. package/src/domains/graphql/index.ts +9 -6
  115. package/src/domains/graphql/operations/compileSchema.test.ts +23 -47
  116. package/src/domains/graphql/operations/compileSchema.ts +23 -25
  117. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  118. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  119. package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
  120. package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
  121. package/src/domains/graphql/operations/index.ts +10 -0
  122. package/src/domains/info/formatters/upgrade.test.ts +7 -4
  123. package/src/domains/info/formatters/upgrade.ts +3 -1
  124. package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
  125. package/src/domains/info/operations/collectEntityCounts.ts +123 -0
  126. package/src/domains/info/operations/collectInfo.test.ts +14 -3
  127. package/src/domains/info/operations/collectInfo.ts +12 -15
  128. package/src/domains/info/operations/index.ts +1 -0
  129. package/src/domains/info/types.ts +13 -0
  130. package/src/domains/llm/capabilities.test.ts +24 -4
  131. package/src/domains/llm/commands/capabilities.ts +12 -3
  132. package/src/domains/llm/commands/llm.ts +5 -1
  133. package/src/domains/llm/data/commandReference.ts +20 -11
  134. package/src/domains/llm/data/decisionTrees.ts +10 -5
  135. package/src/domains/llm/data/toolCatalog.ts +55 -18
  136. package/src/domains/llm/formatters/orientation.ts +5 -1
  137. package/src/domains/llm/llmCommand.test.ts +11 -3
  138. package/src/domains/llm/mcp/specs.ts +3 -4
  139. package/src/domains/llm/operations/collectContext.test.ts +64 -0
  140. package/src/domains/llm/operations/collectContext.ts +7 -17
  141. package/src/domains/modifier/commands/list.ts +5 -38
  142. package/src/domains/modifier/commands/lookup.ts +8 -79
  143. package/src/domains/modifier/commands/sample.ts +2 -1
  144. package/src/domains/modifier/mcp/specs.ts +15 -94
  145. package/src/domains/modifier/operations/lookup.ts +5 -3
  146. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
  147. package/src/domains/modifier/stories.test.ts +20 -0
  148. package/src/domains/modifier/stories.ts +86 -0
  149. package/src/domains/ontology/commands/list.test.ts +5 -3
  150. package/src/domains/ontology/commands/list.ts +5 -58
  151. package/src/domains/ontology/commands/show.test.ts +85 -9
  152. package/src/domains/ontology/commands/show.ts +6 -58
  153. package/src/domains/ontology/formatters/index.ts +1 -0
  154. package/src/domains/ontology/formatters/list.ts +37 -12
  155. package/src/domains/ontology/formatters/show.ts +361 -64
  156. package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
  157. package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
  158. package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
  159. package/src/domains/ontology/helpers/index.ts +14 -0
  160. package/src/domains/ontology/helpers/queryClasses.ts +32 -16
  161. package/src/domains/ontology/helpers/queryProperties.ts +39 -6
  162. package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
  163. package/src/domains/ontology/mcp/specs.ts +7 -84
  164. package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
  165. package/src/domains/ontology/operations/listOntologies.ts +126 -30
  166. package/src/domains/ontology/operations/showOntology.test.ts +131 -12
  167. package/src/domains/ontology/operations/showOntology.ts +289 -7
  168. package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
  169. package/src/domains/ontology/stories.test.ts +36 -0
  170. package/src/domains/ontology/stories.ts +153 -0
  171. package/src/domains/refs/operations/updateRefs.ts +2 -38
  172. package/src/domains/setup/commands/index.ts +1 -0
  173. package/src/domains/setup/commands/setup.ts +70 -0
  174. package/src/domains/setup/commands/skills.test.ts +6 -2
  175. package/src/domains/setup/commands/skills.ts +6 -2
  176. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  177. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  178. package/src/domains/setup/index.ts +7 -3
  179. package/src/domains/setup/operations/setupLsp.test.ts +3 -2
  180. package/src/domains/setup/operations/setupLsp.ts +7 -2
  181. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  182. package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
  183. package/src/domains/shared/bootStore.test.ts +82 -0
  184. package/src/domains/shared/bootStore.ts +57 -18
  185. package/src/domains/shared/compactUri.test.ts +11 -0
  186. package/src/domains/shared/compactUri.ts +10 -5
  187. package/src/domains/shared/context.ts +2 -2
  188. package/src/domains/shared/contracts.ts +11 -0
  189. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  190. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  191. package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
  192. package/src/domains/shared/extractLocalName.test.ts +3 -3
  193. package/src/domains/shared/formatters.ts +23 -2
  194. package/src/domains/shared/listTiers.ts +40 -0
  195. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  196. package/src/domains/shared/loaders/bundledLoader.ts +158 -12
  197. package/src/domains/shared/loaders/gitLoader.ts +1 -2
  198. package/src/domains/shared/loaders/index.ts +4 -1
  199. package/src/domains/shared/loaders/localLoader.ts +2 -4
  200. package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
  201. package/src/domains/shared/loaders/readPackageDir.ts +82 -9
  202. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  203. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  204. package/src/domains/shared/lookupMany.test.ts +135 -0
  205. package/src/domains/shared/lookupMany.ts +50 -2
  206. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  207. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  208. package/src/domains/shared/namespaces.ts +13 -5
  209. package/src/domains/shared/packages.ts +10 -17
  210. package/src/domains/shared/parseSampleCount.test.ts +47 -0
  211. package/src/domains/shared/parseSampleCount.ts +45 -0
  212. package/src/domains/shared/prefixes.test.ts +211 -0
  213. package/src/domains/shared/prefixes.ts +182 -15
  214. package/src/domains/shared/renderers.ts +5 -5
  215. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  216. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  217. package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
  218. package/src/domains/shared/resolveLookupExitCode.ts +42 -0
  219. package/src/domains/shared/runtime.ts +4 -63
  220. package/src/domains/shared/semanticPackage.test.ts +89 -0
  221. package/src/domains/shared/semanticPackage.ts +34 -3
  222. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  223. package/src/domains/shared/stories/compileLookupCommand.ts +102 -0
  224. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  225. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  226. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  227. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  228. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  229. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  230. package/src/domains/shared/stories/condense.test.ts +18 -0
  231. package/src/domains/shared/stories/condense.ts +15 -0
  232. package/src/domains/shared/stories/index.ts +20 -0
  233. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  234. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  235. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +151 -0
  236. package/src/domains/shared/stories/pack/applyPackFilters.ts +111 -0
  237. package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
  238. package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
  239. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +81 -0
  240. package/src/domains/shared/stories/pack/buildLookupQuery.ts +154 -0
  241. package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
  242. package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
  243. package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
  244. package/src/domains/shared/stories/pack/collectPackStories.test.ts +406 -0
  245. package/src/domains/shared/stories/pack/collectPackStories.ts +158 -0
  246. package/src/domains/shared/stories/pack/compilePackCommands.ts +50 -0
  247. package/src/domains/shared/stories/pack/compilePackStories.test.ts +751 -0
  248. package/src/domains/shared/stories/pack/compilePackStories.ts +724 -0
  249. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +55 -0
  250. package/src/domains/shared/stories/pack/index.ts +42 -0
  251. package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
  252. package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
  253. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  254. package/src/domains/shared/stories/pack/standardParity.test.ts +381 -0
  255. package/src/domains/shared/stories/pack/types.ts +224 -0
  256. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +742 -0
  257. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +859 -0
  258. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  259. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  260. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  261. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  262. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  263. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  264. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  265. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  266. package/src/domains/shared/stories/types.ts +179 -0
  267. package/src/domains/shared/suggestions/expandGlob.ts +1 -0
  268. package/src/domains/shared/suggestions/suggestNames.ts +43 -10
  269. package/src/domains/shared/types/index.ts +5 -9
  270. package/src/domains/shared/types/ontology.ts +119 -24
  271. package/src/domains/shared/types/standard.ts +9 -52
  272. package/src/domains/skill/commands/index.ts +3 -1
  273. package/src/domains/skill/commands/list.test.ts +6 -2
  274. package/src/domains/skill/commands/list.ts +5 -63
  275. package/src/domains/skill/commands/lookup.test.ts +118 -0
  276. package/src/domains/skill/commands/lookup.ts +14 -0
  277. package/src/domains/skill/formatters/index.ts +1 -0
  278. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  279. package/src/domains/skill/formatters/lookup.ts +57 -0
  280. package/src/domains/skill/helpers/resolveSkillSources.ts +0 -1
  281. package/src/domains/skill/index.ts +7 -3
  282. package/src/domains/skill/mcp/specs.ts +10 -29
  283. package/src/domains/skill/operations/discover.test.ts +61 -30
  284. package/src/domains/skill/operations/discover.ts +28 -37
  285. package/src/domains/skill/operations/index.ts +2 -1
  286. package/src/domains/skill/operations/list.test.ts +15 -7
  287. package/src/domains/skill/operations/lookup.test.ts +91 -0
  288. package/src/domains/skill/operations/lookup.ts +70 -0
  289. package/src/domains/skill/stories.test.ts +43 -0
  290. package/src/domains/skill/stories.ts +103 -0
  291. package/src/domains/skill/types.ts +8 -0
  292. package/src/domains/token/commands/lookup.test.ts +8 -0
  293. package/src/domains/token/commands/lookup.ts +12 -0
  294. package/src/domains/token/commands/sample.ts +2 -1
  295. package/src/domains/token/featureFlag.ts +17 -0
  296. package/src/domains/token/index.ts +5 -0
  297. package/src/domains/token/mcp/specs.ts +18 -2
  298. package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
  299. package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
  300. package/src/domains/trace/commands/show.ts +30 -4
  301. package/src/domains/trace/tracePlugin.test.ts +1 -1
  302. package/src/index.ts +1 -13
  303. package/src/mcp/registerResources.test.ts +382 -74
  304. package/src/mcp/registerResources.ts +5 -348
  305. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  306. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  307. package/src/mcp/resources/buildResourceList.ts +176 -0
  308. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  309. package/src/mcp/resources/classifyEntity.ts +27 -0
  310. package/src/mcp/resources/constants.ts +105 -0
  311. package/src/mcp/resources/displayPredicates.ts +62 -0
  312. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  313. package/src/mcp/resources/pickFirstValue.ts +26 -0
  314. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  315. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  316. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  317. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  318. package/src/mcp/resources/readEntity.ts +169 -0
  319. package/src/mcp/resources/registerResources.ts +126 -0
  320. package/src/mcp/resources/types.ts +128 -0
  321. package/src/mcp/tools/index.ts +22 -5
  322. package/src/mcp/tools/registerTools.test.ts +167 -34
  323. package/src/mcp/types.ts +4 -0
  324. package/src/pipeline/collectCommands.test.ts +151 -1
  325. package/src/pipeline/collectCommands.ts +35 -13
  326. package/src/pipeline/createProgram.test.ts +5 -1
  327. package/src/pipeline/createProgram.ts +2 -2
  328. package/src/pipeline/firstRun.test.ts +80 -0
  329. package/src/pipeline/firstRun.ts +99 -0
  330. package/src/pipeline/parseGlobalFlags.test.ts +102 -1
  331. package/src/pipeline/parseGlobalFlags.ts +98 -9
  332. package/src/pipeline/resolveCommandKind.test.ts +12 -0
  333. package/src/pipeline/resolveCommandKind.ts +4 -0
  334. package/src/pipeline/rootHelp.test.ts +87 -0
  335. package/src/pipeline/rootHelp.ts +265 -0
  336. package/src/pipeline/runCli.orchestration.test.ts +28 -12
  337. package/src/pipeline/runCli.test.ts +17 -4
  338. package/src/pipeline/runCli.ts +202 -25
  339. package/src/testing/cli.ts +30 -2
  340. package/src/testing/createTestMcpClient.ts +4 -1
  341. package/src/testing/dsFixtures.ts +21 -4
  342. package/src/testing/index.ts +7 -0
  343. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  344. package/src/testing/integration/cli-user-stories.test.ts +63 -0
  345. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  346. package/src/testing/integration/edge-cases.test.ts +62 -50
  347. package/src/testing/integration/executor-execution.test.ts +103 -0
  348. package/src/testing/integration/executor-golden.test.ts +145 -0
  349. package/src/testing/integration/mcp-surface.test.ts +40 -15
  350. package/src/testing/integration/parity.test.ts +136 -82
  351. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  352. package/src/testing/setupXdgIsolation.ts +26 -0
  353. package/src/testing/standardParityFixtures.ts +35 -0
  354. package/src/testing/store.ts +4 -4
  355. package/src/testing/storyFixtures.ts +63 -0
  356. package/src/domains/create/commands/component.test.ts +0 -166
  357. package/src/domains/create/commands/component.ts +0 -123
  358. package/src/domains/create/commands/index.ts +0 -3
  359. package/src/domains/create/commands/package.test.ts +0 -35
  360. package/src/domains/create/commands/package.ts +0 -21
  361. package/src/domains/create/generators.ts +0 -8
  362. package/src/domains/standard/commands/categories.test.ts +0 -70
  363. package/src/domains/standard/commands/categories.ts +0 -48
  364. package/src/domains/standard/commands/index.ts +0 -6
  365. package/src/domains/standard/commands/list.test.ts +0 -105
  366. package/src/domains/standard/commands/list.ts +0 -90
  367. package/src/domains/standard/commands/lookup.test.ts +0 -135
  368. package/src/domains/standard/commands/lookup.ts +0 -99
  369. package/src/domains/standard/commands/sample.ts +0 -57
  370. package/src/domains/standard/formatters/categories.test.ts +0 -44
  371. package/src/domains/standard/formatters/categories.ts +0 -38
  372. package/src/domains/standard/formatters/index.ts +0 -10
  373. package/src/domains/standard/formatters/list.test.ts +0 -169
  374. package/src/domains/standard/formatters/list.ts +0 -184
  375. package/src/domains/standard/formatters/lookup.test.ts +0 -95
  376. package/src/domains/standard/formatters/lookup.ts +0 -101
  377. package/src/domains/standard/formatters/sample.ts +0 -15
  378. package/src/domains/standard/formatters/types.ts +0 -31
  379. package/src/domains/standard/index.ts +0 -40
  380. package/src/domains/standard/mcp/index.ts +0 -2
  381. package/src/domains/standard/mcp/specs.ts +0 -244
  382. package/src/domains/standard/operations/categories.test.ts +0 -61
  383. package/src/domains/standard/operations/categories.ts +0 -38
  384. package/src/domains/standard/operations/index.ts +0 -6
  385. package/src/domains/standard/operations/list.test.ts +0 -75
  386. package/src/domains/standard/operations/list.ts +0 -64
  387. package/src/domains/standard/operations/lookup.test.ts +0 -58
  388. package/src/domains/standard/operations/lookup.ts +0 -151
  389. package/src/domains/standard/operations/sample.ts +0 -35
  390. package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
  391. package/src/domains/standard/orchestration/index.ts +0 -4
  392. package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
  393. package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -32
  394. package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
  395. package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
  396. package/src/domains/standard/standardConfig.ts +0 -42
  397. package/src/domains/standard/types.ts +0 -15
  398. package/src/domains/tier/commands/index.ts +0 -3
  399. package/src/domains/tier/commands/list.test.ts +0 -67
  400. package/src/domains/tier/commands/list.ts +0 -59
  401. package/src/domains/tier/formatters/index.ts +0 -3
  402. package/src/domains/tier/formatters/list.test.ts +0 -33
  403. package/src/domains/tier/formatters/list.ts +0 -45
  404. package/src/domains/tier/index.ts +0 -17
  405. package/src/domains/tier/mcp/index.ts +0 -2
  406. package/src/domains/tier/mcp/specs.ts +0 -38
  407. package/src/domains/tier/operations/index.ts +0 -3
  408. package/src/domains/tier/operations/list.test.ts +0 -32
  409. package/src/domains/tier/operations/list.ts +0 -33
  410. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -4,8 +4,7 @@ import blockSpecs from "../block/mcp/specs.js";
4
4
  import blockEmptyError from "../block/orchestration/blockEmptyError.js";
5
5
  import modifierSpecs from "../modifier/mcp/specs.js";
6
6
  import modifierEmptyError from "../modifier/orchestration/modifierEmptyError.js";
7
- import standardSpecs from "../standard/mcp/specs.js";
8
- import standardEmptyError from "../standard/orchestration/standardEmptyError.js";
7
+ import { TOKEN_READ_SURFACE_ENABLED } from "../token/featureFlag.js";
9
8
  import tokenSpecs from "../token/mcp/specs.js";
10
9
  import tokenEmptyError from "../token/orchestration/tokenEmptyError.js";
11
10
  import type { FilterConfig } from "./types/index.js";
@@ -40,36 +39,34 @@ const cases: ReadonlyArray<{
40
39
  toolNames: new Set(blockSpecs.map((s) => s.name)),
41
40
  storeEmpty: false,
42
41
  },
43
- {
44
- domain: "token (category active)",
45
- error: tokenEmptyError("color"),
46
- toolNames: new Set(tokenSpecs.map((s) => s.name)),
47
- storeEmpty: false,
48
- },
49
- {
50
- domain: "token (store-empty)",
51
- error: tokenEmptyError(),
52
- toolNames: new Set(tokenSpecs.map((s) => s.name)),
53
- storeEmpty: true,
54
- },
42
+ // The token read surface (and with it the paths that raise
43
+ // tokenEmptyError) is gated behind the token feature flag; its recovery
44
+ // hints can only point at registered tools while the surface is live.
45
+ ...(TOKEN_READ_SURFACE_ENABLED
46
+ ? [
47
+ {
48
+ domain: "token (category active)",
49
+ error: tokenEmptyError("color"),
50
+ toolNames: new Set(tokenSpecs.map((s) => s.name)),
51
+ storeEmpty: false,
52
+ },
53
+ {
54
+ domain: "token (store-empty)",
55
+ error: tokenEmptyError(),
56
+ toolNames: new Set(tokenSpecs.map((s) => s.name)),
57
+ storeEmpty: true,
58
+ },
59
+ ]
60
+ : []),
55
61
  {
56
62
  domain: "modifier (store-empty)",
57
63
  error: modifierEmptyError(),
58
64
  toolNames: new Set(modifierSpecs.map((s) => s.name)),
59
65
  storeEmpty: true,
60
66
  },
61
- {
62
- domain: "standard (filter active)",
63
- error: standardEmptyError({ category: "react" }),
64
- toolNames: new Set(standardSpecs.map((s) => s.name)),
65
- storeEmpty: false,
66
- },
67
- {
68
- domain: "standard (store-empty)",
69
- error: standardEmptyError({}),
70
- toolNames: new Set(standardSpecs.map((s) => s.name)),
71
- storeEmpty: true,
72
- },
67
+ // `standard` left this contract with its domain: the bundled pack has no
68
+ // emptyError hook (a pinned PARITY_GAP), so an empty standard list renders
69
+ // zero rows instead of an EMPTY_RESULTS recovery.
73
70
  ];
74
71
 
75
72
  describe("empty-error recovery contract (cross-domain)", () => {
@@ -1,6 +1,6 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import extractLocalName from "./extractLocalName.js";
3
- import { PREFIX_MAP } from "./prefixes.js";
3
+ import { DEFAULT_PREFIX_MAP } from "./prefixes.js";
4
4
 
5
5
  describe("extractLocalName", () => {
6
6
  it("extracts after hash", () => {
@@ -8,7 +8,7 @@ describe("extractLocalName", () => {
8
8
  });
9
9
 
10
10
  it("extracts after last slash", () => {
11
- expect(extractLocalName(`${PREFIX_MAP.ds}global`)).toBe("global");
11
+ expect(extractLocalName(`${DEFAULT_PREFIX_MAP.ds}global`)).toBe("global");
12
12
  });
13
13
 
14
14
  it("returns full string when no separator", () => {
@@ -16,6 +16,6 @@ describe("extractLocalName", () => {
16
16
  });
17
17
 
18
18
  it("handles nested path", () => {
19
- expect(extractLocalName(`${PREFIX_MAP.ds}apps/lxd`)).toBe("lxd");
19
+ expect(extractLocalName(`${DEFAULT_PREFIX_MAP.ds}apps/lxd`)).toBe("lxd");
20
20
  });
21
21
  });
@@ -70,13 +70,34 @@ export function renderLookupResults<TResult, TInput>(
70
70
 
71
71
  const parts = result.results.map((item) => formatOne(mapResult(item)));
72
72
  if (result.errors.length > 0) {
73
+ const heading = ctx.globalFlags.llm ? "**Errors:**" : "Errors:";
73
74
  parts.push(
74
75
  [
75
- "Errors:",
76
- ...result.errors.map((error) => `- ${error.query}: ${error.message}`),
76
+ heading,
77
+ ...result.errors.flatMap((error) => formatErrorLines(error)),
77
78
  ].join("\n"),
78
79
  );
79
80
  }
80
81
 
81
82
  return parts.join("\n\n");
82
83
  }
84
+
85
+ /**
86
+ * Render one lookup error as display lines: the query and message, plus any
87
+ * fuzzy-match suggestions carried on the error (which the JSON mode surfaces
88
+ * but the text modes previously dropped).
89
+ *
90
+ * @param error - A single lookup error entry.
91
+ * @returns One or more display lines for this error.
92
+ */
93
+ function formatErrorLines(error: {
94
+ query: string;
95
+ message: string;
96
+ suggestions?: readonly string[];
97
+ }): string[] {
98
+ const lines = [`- ${error.query}: ${error.message}`];
99
+ if (error.suggestions && error.suggestions.length > 0) {
100
+ lines.push(` Did you mean: ${error.suggestions.join(", ")}?`);
101
+ }
102
+ return lines;
103
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * List the tiers defined in the loaded ontology, ordered by name.
3
+ *
4
+ * Shared operation: the `tier list` CLI/MCP surface is served by the bundled
5
+ * `tier` story pack, but `config tier` validation still needs the tier set
6
+ * programmatically, and this stays part of the package's public API. Kept in
7
+ * the shared layer so it has no dependency on any DS-specific domain module.
8
+ *
9
+ * @param store - ke store to query.
10
+ * @returns Array of tier entries, empty when none exist.
11
+ * @note Impure — queries the ke store.
12
+ */
13
+
14
+ import type { Store, URI } from "@canonical/ke";
15
+ import { buildQuery } from "./buildQuery.js";
16
+ import { P } from "./prefixes.js";
17
+ import type { TierEntry } from "./types/index.js";
18
+
19
+ export async function listTiers(store: Store): Promise<TierEntry[]> {
20
+ const result = await store.query(
21
+ buildQuery(`
22
+ SELECT ?tier ?name
23
+ WHERE {
24
+ ?tier a ${P.ds}Tier ;
25
+ ${P.ds}name ?name .
26
+ }
27
+ ORDER BY ?name
28
+ `),
29
+ );
30
+
31
+ if (result.type !== "select") return [];
32
+
33
+ return result.bindings.map((b) => ({
34
+ uri: (b.tier ?? "") as URI,
35
+ path: b.name ?? "",
36
+ depth: 0,
37
+ }));
38
+ }
39
+
40
+ export default listTiers;
@@ -0,0 +1,248 @@
1
+ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
2
+ import type { PackageRef } from "../../refs/operations/parseRef.js";
3
+ import createBundledLoader, {
4
+ resetBundledLoaderCache,
5
+ } from "./bundledLoader.js";
6
+
7
+ const ref: PackageRef = { kind: "npm", pkg: "@canonical/design-system" };
8
+
9
+ /** Minimal stand-in for a Bun embedded-file blob. */
10
+ interface FakeBlob {
11
+ readonly name: string;
12
+ text(): Promise<string>;
13
+ }
14
+
15
+ const blob = (name: string, content: string): FakeBlob => ({
16
+ name,
17
+ text: async () => content,
18
+ });
19
+
20
+ const corruptBlob = (name: string): FakeBlob => ({
21
+ name,
22
+ text: async () => {
23
+ throw new Error("embedded blob unreadable");
24
+ },
25
+ });
26
+
27
+ const TTL = "@prefix ds: <https://example.test/ds#> .";
28
+
29
+ function stubEmbeddedFiles(blobs: readonly FakeBlob[]): void {
30
+ (globalThis as { Bun?: unknown }).Bun = { embeddedFiles: blobs };
31
+ }
32
+
33
+ const hadBun = typeof (globalThis as { Bun?: unknown }).Bun !== "undefined";
34
+ const originalBun = (globalThis as { Bun?: unknown }).Bun;
35
+
36
+ let stderrSpy: ReturnType<typeof vi.spyOn>;
37
+
38
+ beforeEach(() => {
39
+ resetBundledLoaderCache();
40
+ stderrSpy = vi.spyOn(process.stderr, "write").mockImplementation(() => true);
41
+ });
42
+
43
+ afterEach(() => {
44
+ resetBundledLoaderCache();
45
+ if (hadBun) {
46
+ (globalThis as { Bun?: unknown }).Bun = originalBun;
47
+ } else {
48
+ delete (globalThis as { Bun?: unknown }).Bun;
49
+ }
50
+ stderrSpy.mockRestore();
51
+ });
52
+
53
+ const stderrText = (): string =>
54
+ stderrSpy.mock.calls.map((call) => String(call[0])).join("");
55
+
56
+ describe("createBundledLoader", () => {
57
+ it("returns undefined outside a compiled Bun binary", async () => {
58
+ if (!hadBun) {
59
+ delete (globalThis as { Bun?: unknown }).Bun;
60
+ const resolved = await createBundledLoader().resolve(ref);
61
+ expect(resolved).toBeUndefined();
62
+ }
63
+ });
64
+
65
+ it("bundles all embedded TTL and reads the manifest version", async () => {
66
+ stubEmbeddedFiles([
67
+ blob("button-a1b2c3d4.ttl", TTL),
68
+ blob("package-a1b2c3d4.json", JSON.stringify({ version: "1.2.3" })),
69
+ ]);
70
+
71
+ const resolved = await createBundledLoader().resolve(ref);
72
+
73
+ expect(resolved).toBeDefined();
74
+ expect(resolved?.source).toBe("bundled");
75
+ expect(resolved?.version).toBe("1.2.3");
76
+ expect(resolved?.graphs).toEqual([
77
+ {
78
+ path: "(bundled)/button-a1b2c3d4.ttl",
79
+ content: TTL,
80
+ format: "turtle",
81
+ },
82
+ ]);
83
+ });
84
+
85
+ it("accepts a plain package.json basename", async () => {
86
+ stubEmbeddedFiles([
87
+ blob("button.ttl", TTL),
88
+ blob("package.json", JSON.stringify({ version: "2.0.0" })),
89
+ ]);
90
+
91
+ const resolved = await createBundledLoader().resolve(ref);
92
+ expect(resolved?.version).toBe("2.0.0");
93
+ });
94
+
95
+ it("warns and skips a corrupt TTL blob instead of silently dropping it", async () => {
96
+ stubEmbeddedFiles([
97
+ blob("good-a1b2c3d4.ttl", TTL),
98
+ corruptBlob("bad-a1b2c3d4.ttl"),
99
+ ]);
100
+
101
+ const resolved = await createBundledLoader().resolve(ref);
102
+
103
+ expect(resolved?.graphs).toHaveLength(1);
104
+ expect(stderrText()).toContain("bad-a1b2c3d4.ttl");
105
+ expect(stderrText()).toContain("embedded blob unreadable");
106
+ });
107
+
108
+ it("serves embedded story JSON as story-file entries", async () => {
109
+ stubEmbeddedFiles([
110
+ blob("button-a1b2c3d4.ttl", TTL),
111
+ blob("package-a1b2c3d4.json", JSON.stringify({ version: "1.2.3" })),
112
+ blob("colors-fyskc7pf.json", JSON.stringify({ noun: "color" })),
113
+ ]);
114
+
115
+ const resolved = await createBundledLoader().resolve(ref);
116
+
117
+ expect(resolved?.stories).toEqual([
118
+ {
119
+ path: "(bundled)/colors-fyskc7pf.json",
120
+ definition: { noun: "color" },
121
+ },
122
+ ]);
123
+ expect(resolved?.version).toBe("1.2.3");
124
+ });
125
+
126
+ it("does not serve package manifests as stories", async () => {
127
+ stubEmbeddedFiles([
128
+ blob("button.ttl", TTL),
129
+ blob("package.json", JSON.stringify({ version: "2.0.0" })),
130
+ blob("package-deadbeef.json", JSON.stringify({ version: "3.0.0" })),
131
+ ]);
132
+
133
+ const resolved = await createBundledLoader().resolve(ref);
134
+
135
+ expect(resolved?.stories).toEqual([]);
136
+ });
137
+
138
+ it("warns and skips a story blob with invalid JSON instead of crashing", async () => {
139
+ stubEmbeddedFiles([
140
+ blob("button.ttl", TTL),
141
+ blob("good-a1b2c3d4.json", JSON.stringify({ noun: "color" })),
142
+ blob("broken-a1b2c3d4.json", "{ not json"),
143
+ ]);
144
+
145
+ const resolved = await createBundledLoader().resolve(ref);
146
+
147
+ expect(resolved?.stories).toHaveLength(1);
148
+ expect(resolved?.stories.at(0)?.definition).toEqual({ noun: "color" });
149
+ expect(stderrText()).toContain("broken-a1b2c3d4.json");
150
+ });
151
+
152
+ it("warns and skips an unreadable story blob", async () => {
153
+ stubEmbeddedFiles([
154
+ blob("button.ttl", TTL),
155
+ corruptBlob("story-a1b2c3d4.json"),
156
+ ]);
157
+
158
+ const resolved = await createBundledLoader().resolve(ref);
159
+
160
+ expect(resolved?.stories).toEqual([]);
161
+ expect(stderrText()).toContain("story-a1b2c3d4.json");
162
+ expect(stderrText()).toContain("embedded blob unreadable");
163
+ });
164
+
165
+ it("resolves a stories-only bundle with no TTL graphs", async () => {
166
+ stubEmbeddedFiles([
167
+ blob("colors-fyskc7pf.json", JSON.stringify({ noun: "color" })),
168
+ ]);
169
+
170
+ const resolved = await createBundledLoader().resolve(ref);
171
+
172
+ expect(resolved).toBeDefined();
173
+ expect(resolved?.graphs).toEqual([]);
174
+ expect(resolved?.stories).toHaveLength(1);
175
+ });
176
+
177
+ it("returns undefined when no TTL or story content is embedded", async () => {
178
+ stubEmbeddedFiles([
179
+ blob("template-a1b2c3d4.ejs", "<%= name %>"),
180
+ blob("skill-a1b2c3d4.md", "# Skill"),
181
+ ]);
182
+
183
+ const resolved = await createBundledLoader().resolve(ref);
184
+
185
+ expect(resolved).toBeUndefined();
186
+ });
187
+
188
+ it("warns on a corrupt package manifest and falls back to 0.0.0", async () => {
189
+ stubEmbeddedFiles([
190
+ blob("button.ttl", TTL),
191
+ blob("package-deadbeef.json", "{ not json"),
192
+ ]);
193
+
194
+ const resolved = await createBundledLoader().resolve(ref);
195
+
196
+ expect(resolved?.version).toBe("0.0.0");
197
+ expect(stderrText()).toContain("package-deadbeef.json");
198
+ });
199
+
200
+ it("rejects JSON blobs whose basename is not a package manifest", async () => {
201
+ stubEmbeddedFiles([
202
+ blob("button.ttl", TTL),
203
+ blob("mypackage.json", JSON.stringify({ version: "9.9.9" })),
204
+ blob("package-lock.json", JSON.stringify({ version: "8.8.8" })),
205
+ blob("packages.json", JSON.stringify({ version: "7.7.7" })),
206
+ ]);
207
+
208
+ const resolved = await createBundledLoader().resolve(ref);
209
+ expect(resolved?.version).toBe("0.0.0");
210
+ });
211
+
212
+ it("warns on an invalid semver version and keeps looking", async () => {
213
+ stubEmbeddedFiles([
214
+ blob("button.ttl", TTL),
215
+ blob("package-aaaaaaaa.json", JSON.stringify({ version: "not-semver" })),
216
+ blob("package-bbbbbbbb.json", JSON.stringify({ version: "3.4.5" })),
217
+ ]);
218
+
219
+ const resolved = await createBundledLoader().resolve(ref);
220
+
221
+ expect(resolved?.version).toBe("3.4.5");
222
+ expect(stderrText()).toContain('invalid semver "not-semver"');
223
+ });
224
+
225
+ it("caches the resolved package until the cache is reset", async () => {
226
+ stubEmbeddedFiles([
227
+ blob("button.ttl", TTL),
228
+ blob("package.json", JSON.stringify({ version: "1.0.0" })),
229
+ ]);
230
+
231
+ const first = await createBundledLoader().resolve(ref);
232
+ expect(first?.version).toBe("1.0.0");
233
+
234
+ // New fixture without reset — cached result still returned.
235
+ stubEmbeddedFiles([
236
+ blob("other.ttl", TTL),
237
+ blob("package.json", JSON.stringify({ version: "5.0.0" })),
238
+ ]);
239
+ const second = await createBundledLoader().resolve(ref);
240
+ expect(second).toBe(first);
241
+
242
+ // After reset the loader re-reads the embedded files.
243
+ resetBundledLoaderCache();
244
+ const third = await createBundledLoader().resolve(ref);
245
+ expect(third?.version).toBe("5.0.0");
246
+ expect(third).not.toBe(first);
247
+ });
248
+ });
@@ -1,23 +1,32 @@
1
1
  /**
2
2
  * Bundled package loader for compiled binaries.
3
3
  *
4
- * Resolves semantic content from `Bun.embeddedFiles` — TTL and skill
5
- * files embedded into the compiled binary via the build command.
4
+ * Resolves semantic content from `Bun.embeddedFiles` — the TTL graphs
5
+ * and story-pack JSON embedded into the compiled binary via the build
6
+ * command. Skills are not embedded; this loader reports `skills: []`.
6
7
  *
7
8
  * Blob names are hashed by bun (e.g. `button-a1b2c3d4.ttl`), losing
8
9
  * directory and package association. The loader therefore returns a
9
- * single SemanticPackage containing ALL embedded TTL content. RDF
10
- * stores deduplicate triples, so this is safe when combined with
11
- * higher-precedence loaders that resolve individual packages.
10
+ * single SemanticPackage containing ALL embedded TTL and story content,
11
+ * and the same cached package is returned for every ref this loader
12
+ * resolves.
13
+ * Note that repeated loads are NOT fully deduplicated by the store:
14
+ * Oxigraph applies set semantics to ground triples, but blank nodes are
15
+ * re-minted on every parse, so content loaded both here and by a
16
+ * higher-precedence loader can produce duplicated blank-node subgraphs.
17
+ * The orchestrator's first-loader-wins precedence (local > git >
18
+ * bundled) is what keeps individual packages from double-loading.
12
19
  *
13
20
  * @note Impure — reads embedded blobs.
14
21
  */
15
22
 
23
+ import { basename } from "node:path";
16
24
  import type { PackageRef } from "../../refs/operations/parseRef.js";
17
25
  import type {
18
26
  GraphContent,
19
27
  PackageLoader,
20
28
  SemanticPackage,
29
+ StoryFileEntry,
21
30
  } from "../semanticPackage.js";
22
31
 
23
32
  // ---------------------------------------------------------------------------
@@ -27,6 +36,45 @@ import type {
27
36
  /** Cached result — embedded content is immutable, no need to re-read. */
28
37
  let cached: SemanticPackage | undefined;
29
38
 
39
+ /**
40
+ * Reset the module-level cache. Test-only: lets suites exercise the loader
41
+ * against different `Bun.embeddedFiles` fixtures without module isolation.
42
+ */
43
+ export function resetBundledLoaderCache(): void {
44
+ cached = undefined;
45
+ }
46
+
47
+ // ---------------------------------------------------------------------------
48
+ // Validation
49
+ // ---------------------------------------------------------------------------
50
+
51
+ /**
52
+ * Embedded package manifest basename: exactly `package.json`, or
53
+ * `package-<hash>.json` where `<hash>` is bun's content hash suffix.
54
+ */
55
+ const PACKAGE_MANIFEST_PATTERN = /^package(?:-[0-9a-f]{6,64})?\.json$/i;
56
+
57
+ /**
58
+ * Semver 2.0.0 validation (semver.org's suggested pattern).
59
+ */
60
+ const SEMVER_PATTERN =
61
+ /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:-((?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(?:\.(?:0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*))?(?:\+([0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/;
62
+
63
+ /**
64
+ * Whether a blob name is an embedded package manifest — strict basename
65
+ * match, so unrelated JSON blobs (e.g. `mypackage.json`) are not misread.
66
+ */
67
+ function isPackageManifest(name: string): boolean {
68
+ return PACKAGE_MANIFEST_PATTERN.test(basename(name));
69
+ }
70
+
71
+ /**
72
+ * Emit a loader warning on stderr (safe for both CLI and MCP stdio use).
73
+ */
74
+ function warn(message: string): void {
75
+ process.stderr.write(`Warning: bundled loader: ${message}\n`);
76
+ }
77
+
30
78
  // ---------------------------------------------------------------------------
31
79
  // Public API
32
80
  // ---------------------------------------------------------------------------
@@ -44,16 +92,20 @@ export default function createBundledLoader(): PackageLoader {
44
92
  if (!blobs?.length) return undefined;
45
93
 
46
94
  const graphs = await readEmbeddedGraphs(blobs);
47
- if (graphs.length === 0) return undefined;
95
+ const stories = await readEmbeddedStories(blobs);
96
+ if (graphs.length === 0 && stories.length === 0) return undefined;
48
97
 
49
98
  const version = await readEmbeddedVersion(blobs);
99
+ const prefixes = await readEmbeddedPrefixes(blobs);
50
100
 
51
101
  cached = {
52
102
  name: "(bundled)",
53
103
  version,
54
104
  source: "bundled",
55
105
  graphs,
106
+ ...(prefixes ? { prefixes } : {}),
56
107
  skills: [],
108
+ stories,
57
109
  };
58
110
 
59
111
  return cached;
@@ -80,28 +132,122 @@ async function readEmbeddedGraphs(
80
132
  content,
81
133
  format: "turtle",
82
134
  });
83
- } catch {}
135
+ } catch (error) {
136
+ const reason = error instanceof Error ? error.message : String(error);
137
+ warn(`skipping unreadable embedded graph "${blob.name}": ${reason}`);
138
+ }
84
139
  }
85
140
 
86
141
  return graphs;
87
142
  }
88
143
 
144
+ // ---------------------------------------------------------------------------
145
+ // Story reading
146
+ // ---------------------------------------------------------------------------
147
+
148
+ /**
149
+ * Read story-pack definitions from embedded `.json` blobs.
150
+ *
151
+ * The build's story-assets plugin makes `stories/*.json` the only JSON
152
+ * embedded as file assets (see storyAssetsPlugin.ts), so every `.json`
153
+ * blob not named like a package manifest is a story file. Mirrors the
154
+ * local loader: a blob that cannot be read or parsed warns on stderr
155
+ * and is skipped — one bad file cannot break boot. Definitions are
156
+ * shape-validated later by the story-pack compiler.
157
+ */
158
+ async function readEmbeddedStories(
159
+ blobs: ReadonlyArray<Blob & { name: string }>,
160
+ ): Promise<StoryFileEntry[]> {
161
+ const stories: StoryFileEntry[] = [];
162
+
163
+ for (const blob of blobs) {
164
+ if (!blob.name.endsWith(".json") || isPackageManifest(blob.name)) continue;
165
+
166
+ try {
167
+ stories.push({
168
+ path: `(bundled)/${blob.name}`,
169
+ definition: JSON.parse(await blob.text()),
170
+ });
171
+ } catch (error) {
172
+ const reason = error instanceof Error ? error.message : String(error);
173
+ warn(`skipping invalid embedded story "${blob.name}": ${reason}`);
174
+ }
175
+ }
176
+
177
+ return stories;
178
+ }
179
+
89
180
  // ---------------------------------------------------------------------------
90
181
  // Version reading
91
182
  // ---------------------------------------------------------------------------
92
183
 
184
+ /**
185
+ * Aggregate `pragma.prefixes` declared across embedded package manifests.
186
+ *
187
+ * The bundled loader collapses every embedded package into one aggregate
188
+ * SemanticPackage, so their prefix declarations are merged here (later
189
+ * manifests win on key collisions). Only string→string entries are kept;
190
+ * malformed shapes are ignored so one bad manifest cannot break boot.
191
+ *
192
+ * @returns The merged prefix map, or `undefined` when none is declared.
193
+ */
194
+ async function readEmbeddedPrefixes(
195
+ blobs: ReadonlyArray<Blob & { name: string }>,
196
+ ): Promise<Readonly<Record<string, string>> | undefined> {
197
+ const prefixes: Record<string, string> = {};
198
+
199
+ for (const blob of blobs) {
200
+ if (!isPackageManifest(blob.name)) continue;
201
+
202
+ try {
203
+ const parsed = JSON.parse(await blob.text()) as { pragma?: unknown };
204
+ const pragma = parsed.pragma;
205
+ if (typeof pragma !== "object" || pragma === null) continue;
206
+ const raw = (pragma as { prefixes?: unknown }).prefixes;
207
+ if (typeof raw !== "object" || raw === null) continue;
208
+ for (const [name, namespace] of Object.entries(raw)) {
209
+ if (typeof namespace !== "string") continue;
210
+ // These collapse into one aggregate package, so a cross-manifest
211
+ // collision would never reach resolvePrefixes' warning — surface it
212
+ // here, naming the manifest, instead of silently last-winning.
213
+ if (name in prefixes && prefixes[name] !== namespace) {
214
+ warn(
215
+ `embedded manifest "${blob.name}" overrides prefix "${name}" ` +
216
+ `(was <${prefixes[name]}>, now <${namespace}>)`,
217
+ );
218
+ }
219
+ prefixes[name] = namespace;
220
+ }
221
+ } catch (error) {
222
+ const reason = error instanceof Error ? error.message : String(error);
223
+ warn(`skipping prefixes in embedded manifest "${blob.name}": ${reason}`);
224
+ }
225
+ }
226
+
227
+ return Object.keys(prefixes).length > 0 ? prefixes : undefined;
228
+ }
229
+
93
230
  async function readEmbeddedVersion(
94
231
  blobs: ReadonlyArray<Blob & { name: string }>,
95
232
  ): Promise<string> {
96
233
  for (const blob of blobs) {
97
- if (!blob.name.includes("package") || !blob.name.endsWith(".json"))
98
- continue;
234
+ if (!isPackageManifest(blob.name)) continue;
99
235
 
100
236
  try {
101
237
  const text = await blob.text();
102
- const parsed = JSON.parse(text) as { version?: string };
103
- if (parsed.version) return parsed.version;
104
- } catch {}
238
+ const parsed = JSON.parse(text) as { version?: unknown };
239
+ if (typeof parsed.version !== "string") continue;
240
+ if (!SEMVER_PATTERN.test(parsed.version)) {
241
+ warn(
242
+ `ignoring invalid semver "${parsed.version}" in embedded manifest "${blob.name}"`,
243
+ );
244
+ continue;
245
+ }
246
+ return parsed.version;
247
+ } catch (error) {
248
+ const reason = error instanceof Error ? error.message : String(error);
249
+ warn(`skipping corrupt embedded manifest "${blob.name}": ${reason}`);
250
+ }
105
251
  }
106
252
 
107
253
  return "0.0.0";
@@ -24,8 +24,7 @@ export default function createGitLoader(): PackageLoader {
24
24
  const dir = gitCacheDir(ref.pkg, ref.ref);
25
25
  if (!existsSync(dir)) return undefined;
26
26
 
27
- const { version, graphs, skills } = readPackageDir(dir);
28
- return { name: ref.pkg, version, source: "git", graphs, skills };
27
+ return { name: ref.pkg, source: "git", ...readPackageDir(dir) };
29
28
  },
30
29
  };
31
30
  }
@@ -1,6 +1,9 @@
1
1
  /** @module Semantic package loaders — local, git, and bundled resolution. */
2
2
 
3
- export { default as createBundledLoader } from "./bundledLoader.js";
3
+ export {
4
+ default as createBundledLoader,
5
+ resetBundledLoaderCache,
6
+ } from "./bundledLoader.js";
4
7
  export { default as createGitLoader } from "./gitLoader.js";
5
8
  export { default as createLocalLoader } from "./localLoader.js";
6
9
  export { default as readPackageDir } from "./readPackageDir.js";