@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
@@ -1,5 +1,4 @@
1
- import { readFileSync } from "node:fs";
2
- import { createStore } from "@canonical/ke";
1
+ import { createStore, type InlineSource } from "@canonical/ke";
3
2
  import {
4
3
  CompilationError,
5
4
  type CompilerResult,
@@ -9,15 +8,14 @@ import {
9
8
  hashSources,
10
9
  } from "@canonical/ke-graphql";
11
10
  import { PragmaError } from "#error";
12
- import { resolveSourceFiles } from "../helpers/index.js";
13
11
 
14
12
  export interface CompileSchemaOptions {
15
- /** TTL file paths or glob patterns, resolved from `cwd`. */
16
- readonly sources: readonly string[];
13
+ /** In-memory TTL sources (content + provenance path) to compile. */
14
+ readonly sources: readonly InlineSource[];
17
15
  /** Prefix map handed to both the ke store and the compiler. */
18
16
  readonly prefixes: Readonly<Record<string, string>>;
19
- /** Working directory for resolving relative source paths. */
20
- readonly cwd: string;
17
+ /** Working directory, passed through to the store (unused for inline). */
18
+ readonly cwd?: string;
21
19
  }
22
20
 
23
21
  export interface CompileSchemaResult {
@@ -25,7 +23,7 @@ export interface CompileSchemaResult {
25
23
  readonly status: "ok" | "failed";
26
24
  /** All compiler diagnostics, including those carried by a CompilationError. */
27
25
  readonly diagnostics: readonly Diagnostic[];
28
- /** Resolved absolute TTL file paths that were loaded. */
26
+ /** Provenance paths of the sources that were loaded. */
29
27
  readonly files: readonly string[];
30
28
  /** Fingerprint of the raw source contents (matches ke's onLoad hashing). */
31
29
  readonly sourcesHash: string;
@@ -34,42 +32,42 @@ export interface CompileSchemaResult {
34
32
  }
35
33
 
36
34
  /**
37
- * Compile TTL sources into a GraphQL schema via the ke-graphql pipeline.
35
+ * Compile in-memory TTL sources into a GraphQL schema via the ke-graphql
36
+ * pipeline.
38
37
  *
39
- * Resolves source patterns, hashes the raw file contents (the exact bytes
40
- * ke loads, so the artifact's `sourcesHash` matches what
41
- * `createSchemaPlugin` computes from ke's onLoad), boots a throwaway ke
42
- * store, and runs the compiler. A {@link CompilationError} (schema
43
- * composition failed) is converted into a `"failed"` result carrying its
44
- * diagnostics; the store is always disposed.
38
+ * Hashes the raw source contents (the exact bytes ke loads, so the
39
+ * artifact's `sourcesHash` matches what `createSchemaPlugin` computes from
40
+ * ke's onLoad), boots a throwaway ke store, and runs the compiler. A
41
+ * {@link CompilationError} (schema composition failed) is converted into a
42
+ * `"failed"` result carrying its diagnostics; the store is always disposed.
45
43
  *
46
- * @note Impure — reads filesystem and boots a ke store (WASM + TTL).
44
+ * @note Impure — boots a ke store (WASM + TTL).
47
45
  *
48
- * @param options - Sources, prefixes, and working directory.
46
+ * @param options - In-memory sources and prefixes.
49
47
  * @returns The compile outcome with diagnostics, files, and sources hash.
50
- * @throws PragmaError with code `INVALID_INPUT` when no sources resolve.
48
+ * @throws PragmaError with code `INVALID_INPUT` when given no sources.
51
49
  * @throws PragmaError with code `STORE_ERROR` when the store fails to load.
52
50
  */
53
51
  export default async function compileSchema(
54
52
  options: CompileSchemaOptions,
55
53
  ): Promise<CompileSchemaResult> {
56
- const files = resolveSourceFiles(options.sources, options.cwd);
54
+ const { sources } = options;
57
55
 
58
- if (files.length === 0) {
59
- throw PragmaError.invalidInput("sources", options.sources.join(", "), {
56
+ if (sources.length === 0) {
57
+ throw PragmaError.invalidInput("sources", "(empty)", {
60
58
  recovery: {
61
- message: "Provide at least one existing TTL file or glob pattern.",
59
+ message: "Provide at least one TTL source.",
62
60
  },
63
61
  });
64
62
  }
65
63
 
66
- const contents = files.map((file) => readFileSync(file, "utf-8"));
67
- const sourcesHash = hashSources(contents);
64
+ const sourcesHash = hashSources(sources.map((source) => source.content));
65
+ const files = sources.map((source) => source.path ?? "inline");
68
66
 
69
67
  let store: Awaited<ReturnType<typeof createStore>>;
70
68
  try {
71
69
  store = await createStore({
72
- sources: [...files],
70
+ sources: [...sources],
73
71
  prefixes: options.prefixes,
74
72
  cwd: options.cwd,
75
73
  });
@@ -0,0 +1,50 @@
1
+ import { afterEach, describe, expect, it } from "vitest";
2
+ import { EX_NAMESPACE, GRAPHQL_CLEAN_TTL } from "#testing";
3
+ import { DEFAULT_PREFIX_MAP } from "../../shared/prefixes.js";
4
+ import createSchemaServer, { type SchemaServer } from "./createSchemaServer.js";
5
+
6
+ const PREFIXES = { ...DEFAULT_PREFIX_MAP, ex: EX_NAMESPACE };
7
+
8
+ describe("createSchemaServer", () => {
9
+ let server: SchemaServer | undefined;
10
+
11
+ afterEach(() => {
12
+ server?.dispose();
13
+ server = undefined;
14
+ });
15
+
16
+ it("compiles inline sources and serves an introspectable schema", async () => {
17
+ server = await createSchemaServer({
18
+ sources: [
19
+ { content: GRAPHQL_CLEAN_TTL, format: "turtle", path: "clean.ttl" },
20
+ ],
21
+ prefixes: PREFIXES,
22
+ });
23
+
24
+ const response = await server.handler(
25
+ new Request("http://localhost/graphql", {
26
+ method: "POST",
27
+ headers: { "content-type": "application/json" },
28
+ body: JSON.stringify({
29
+ query: "{ __schema { queryType { name } } }",
30
+ }),
31
+ }),
32
+ );
33
+
34
+ expect(response.status).toBe(200);
35
+ const body = (await response.json()) as {
36
+ data?: { __schema?: { queryType?: { name?: string } } };
37
+ };
38
+ expect(body.data?.__schema?.queryType?.name).toBe("Query");
39
+ expect(Array.isArray(server.diagnostics)).toBe(true);
40
+ });
41
+
42
+ it("throws a STORE_ERROR on invalid TTL", async () => {
43
+ await expect(
44
+ createSchemaServer({
45
+ sources: [{ content: "not turtle .", format: "turtle", path: "x.ttl" }],
46
+ prefixes: PREFIXES,
47
+ }),
48
+ ).rejects.toMatchObject({ code: "STORE_ERROR" });
49
+ });
50
+ });
@@ -0,0 +1,75 @@
1
+ /**
2
+ * Boot a persistent ke store over in-memory TTL sources with the
3
+ * ke-graphql schema plugin attached, and build the `/http` fetch handler
4
+ * (GraphiQL + GraphQL, CORS, incremental delivery) over the compiled
5
+ * schema. The caller owns the returned `dispose`.
6
+ *
7
+ * @note Impure — boots a ke store (WASM + TTL) and compiles the schema.
8
+ */
9
+
10
+ import { createStore, type InlineSource, type Plugin } from "@canonical/ke";
11
+ import {
12
+ createSchemaPlugin,
13
+ type Diagnostic,
14
+ type SchemaPluginApi,
15
+ } from "@canonical/ke-graphql";
16
+ import { createGraphQLHandler } from "@canonical/ke-graphql/http";
17
+ import { PragmaError } from "#error";
18
+
19
+ export interface CreateSchemaServerOptions {
20
+ /** In-memory TTL sources to load into the store. */
21
+ readonly sources: readonly InlineSource[];
22
+ /** Prefix map handed to both the ke store and the compiler. */
23
+ readonly prefixes: Readonly<Record<string, string>>;
24
+ }
25
+
26
+ export interface SchemaServer {
27
+ /** Runtime-neutral fetch handler over the compiled schema. */
28
+ readonly handler: ReturnType<typeof createGraphQLHandler>;
29
+ /** Compiler diagnostics produced while building the schema. */
30
+ readonly diagnostics: readonly Diagnostic[];
31
+ /** Releases the underlying ke store. */
32
+ readonly dispose: () => void;
33
+ }
34
+
35
+ export default async function createSchemaServer(
36
+ options: CreateSchemaServerOptions,
37
+ ): Promise<SchemaServer> {
38
+ const plugin = createSchemaPlugin({ incremental: true });
39
+
40
+ let store: Awaited<ReturnType<typeof createStore>>;
41
+ try {
42
+ store = await createStore({
43
+ sources: [...options.sources],
44
+ prefixes: options.prefixes,
45
+ // biome-ignore lint/suspicious/noExplicitAny: Plugin generic variance
46
+ plugins: [plugin as Plugin<any>],
47
+ });
48
+ } catch (error) {
49
+ throw PragmaError.storeError(
50
+ error instanceof Error ? error.message : String(error),
51
+ { recovery: { message: "Check the TTL syntax of the sources." } },
52
+ );
53
+ }
54
+
55
+ const api = store.api<SchemaPluginApi>("ke-graphql");
56
+ if (!api) {
57
+ store.dispose();
58
+ throw PragmaError.storeError("ke-graphql plugin did not register its API", {
59
+ recovery: { message: "This is an internal error; please report it." },
60
+ });
61
+ }
62
+
63
+ const handler = createGraphQLHandler(api.schema, {
64
+ context: () => api.createContext(store),
65
+ graphiql: true,
66
+ cors: true,
67
+ incremental: true,
68
+ });
69
+
70
+ return {
71
+ handler,
72
+ diagnostics: api.diagnostics,
73
+ dispose: () => store.dispose(),
74
+ };
75
+ }
@@ -0,0 +1,167 @@
1
+ import {
2
+ existsSync,
3
+ mkdirSync,
4
+ mkdtempSync,
5
+ readFileSync,
6
+ rmSync,
7
+ writeFileSync,
8
+ } from "node:fs";
9
+ import { tmpdir } from "node:os";
10
+ import { join } from "node:path";
11
+ import { afterAll, afterEach, beforeAll, describe, expect, it } from "vitest";
12
+ import { EX_NAMESPACE, GRAPHQL_CLEAN_TTL, GRAPHQL_FATAL_TTL } from "#testing";
13
+ import { DEFAULT_PREFIX_MAP } from "../../shared/prefixes.js";
14
+ import type { SemanticPackage } from "../../shared/semanticPackage.js";
15
+ import ensureSchemaArtifact, {
16
+ resolveSchemaArtifactPath,
17
+ } from "./ensureSchemaArtifact.js";
18
+
19
+ const PREFIXES = { ...DEFAULT_PREFIX_MAP, ex: EX_NAMESPACE };
20
+
21
+ let dataDir: string;
22
+ let cwd: string;
23
+ let originalXdgData: string | undefined;
24
+
25
+ beforeAll(() => {
26
+ dataDir = mkdtempSync(join(tmpdir(), "pragma-schema-artifact-data-"));
27
+ cwd = mkdtempSync(join(tmpdir(), "pragma-schema-artifact-cwd-"));
28
+ originalXdgData = process.env.XDG_DATA_HOME;
29
+ process.env.XDG_DATA_HOME = dataDir;
30
+ // The global test setup disables artifact emission; this suite tests it.
31
+ delete process.env.PRAGMA_SCHEMA_ARTIFACT;
32
+ });
33
+
34
+ afterAll(() => {
35
+ if (originalXdgData === undefined) {
36
+ delete process.env.XDG_DATA_HOME;
37
+ } else {
38
+ process.env.XDG_DATA_HOME = originalXdgData;
39
+ }
40
+ process.env.PRAGMA_SCHEMA_ARTIFACT = "off";
41
+ rmSync(dataDir, { recursive: true, force: true });
42
+ rmSync(cwd, { recursive: true, force: true });
43
+ });
44
+
45
+ afterEach(() => {
46
+ delete process.env.PRAGMA_SCHEMA_ARTIFACT;
47
+ rmSync(join(dataDir, "pragma"), { recursive: true, force: true });
48
+ });
49
+
50
+ function makeRuntime(ttl: string): Parameters<typeof ensureSchemaArtifact>[0] {
51
+ const pkg: SemanticPackage = {
52
+ name: "@test/ontology",
53
+ version: "1.0.0",
54
+ source: "local",
55
+ graphs: [{ path: "ontology.ttl", content: ttl, format: "turtle" }],
56
+ skills: [],
57
+ stories: [],
58
+ };
59
+ return {
60
+ cwd,
61
+ packages: [pkg],
62
+ store: { prefixes: PREFIXES } as never,
63
+ };
64
+ }
65
+
66
+ describe("resolveSchemaArtifactPath", () => {
67
+ it("uses the XDG data dir when no project config is in tree", () => {
68
+ expect(resolveSchemaArtifactPath(cwd)).toBe(
69
+ join(dataDir, "pragma", "schema.graphql"),
70
+ );
71
+ });
72
+
73
+ it("uses .pragma/ beside the project config when one is in tree", () => {
74
+ const project = mkdtempSync(join(tmpdir(), "pragma-schema-project-"));
75
+ try {
76
+ writeFileSync(join(project, "pragma.config.json"), "{}", "utf-8");
77
+ const nested = join(project, "src");
78
+ mkdirSync(nested);
79
+ expect(resolveSchemaArtifactPath(nested)).toBe(
80
+ join(project, ".pragma", "schema.graphql"),
81
+ );
82
+ } finally {
83
+ rmSync(project, { recursive: true, force: true });
84
+ }
85
+ });
86
+ });
87
+
88
+ describe("ensureSchemaArtifact", () => {
89
+ it("writes the artifact with a source-hash header on first use", async () => {
90
+ const result = await ensureSchemaArtifact(makeRuntime(GRAPHQL_CLEAN_TTL));
91
+
92
+ expect(result.status).toBe("written");
93
+ expect(existsSync(result.path)).toBe(true);
94
+
95
+ const content = readFileSync(result.path, "utf-8");
96
+ expect(content.startsWith("# pragma:sources ")).toBe(true);
97
+ expect(content).toContain("type Thing");
98
+ });
99
+
100
+ it("is fresh on a second run with unchanged sources", async () => {
101
+ const runtime = makeRuntime(GRAPHQL_CLEAN_TTL);
102
+ await ensureSchemaArtifact(runtime);
103
+
104
+ const second = await ensureSchemaArtifact(runtime);
105
+ expect(second.status).toBe("fresh");
106
+ });
107
+
108
+ it("rewrites when the sources change", async () => {
109
+ await ensureSchemaArtifact(makeRuntime(GRAPHQL_CLEAN_TTL));
110
+
111
+ const changed = await ensureSchemaArtifact(
112
+ makeRuntime(`${GRAPHQL_CLEAN_TTL}\n# changed`),
113
+ );
114
+ expect(changed.status).toBe("written");
115
+ });
116
+
117
+ it("leaves a stale artifact in place when compilation fails", async () => {
118
+ const first = await ensureSchemaArtifact(makeRuntime(GRAPHQL_CLEAN_TTL));
119
+ const before = readFileSync(first.path, "utf-8");
120
+
121
+ const failed = await ensureSchemaArtifact(makeRuntime(GRAPHQL_FATAL_TTL));
122
+ expect(failed.status).toBe("failed");
123
+ expect(readFileSync(first.path, "utf-8")).toBe(before);
124
+ });
125
+
126
+ it("compiles from the parseable sources when one is malformed", async () => {
127
+ const pkg: SemanticPackage = {
128
+ name: "@test/ontology",
129
+ version: "1.0.0",
130
+ source: "local",
131
+ graphs: [
132
+ { path: "clean.ttl", content: GRAPHQL_CLEAN_TTL, format: "turtle" },
133
+ {
134
+ path: "broken.ttl",
135
+ content: "this is not turtle .",
136
+ format: "turtle",
137
+ },
138
+ ],
139
+ skills: [],
140
+ stories: [],
141
+ };
142
+ const result = await ensureSchemaArtifact({
143
+ cwd,
144
+ packages: [pkg],
145
+ store: { prefixes: PREFIXES } as never,
146
+ });
147
+
148
+ expect(result.status).toBe("written");
149
+ expect(readFileSync(result.path, "utf-8")).toContain("type Thing");
150
+ });
151
+
152
+ it("skips when no sources resolve", async () => {
153
+ const result = await ensureSchemaArtifact({
154
+ cwd,
155
+ packages: [],
156
+ store: { prefixes: PREFIXES } as never,
157
+ });
158
+ expect(result.status).toBe("skipped");
159
+ });
160
+
161
+ it("skips when disabled via PRAGMA_SCHEMA_ARTIFACT=off", async () => {
162
+ process.env.PRAGMA_SCHEMA_ARTIFACT = "off";
163
+ const result = await ensureSchemaArtifact(makeRuntime(GRAPHQL_CLEAN_TTL));
164
+ expect(result.status).toBe("skipped");
165
+ expect(existsSync(result.path)).toBe(false);
166
+ });
167
+ });
@@ -0,0 +1,178 @@
1
+ /**
2
+ * Hash-gated `schema.graphql` artifact.
3
+ *
4
+ * pragma keeps one compiled GraphQL schema per data set so downstream
5
+ * tooling (codegen, IDEs, `graphql serve` clients) always has a current
6
+ * SDL without anyone running `pragma graphql build` by hand:
7
+ *
8
+ * - **Location** — next to the project's `pragma.config.json`
9
+ * (`<projectRoot>/.pragma/schema.graphql`) when one is in tree, else the
10
+ * XDG data dir (`$XDG_DATA_HOME/pragma/schema.graphql`).
11
+ * - **Emission** — on first use of pragma (any command that boots the
12
+ * runtime) the artifact is generated if missing.
13
+ * - **Renewal** — the artifact's first line records the hash of the TTL
14
+ * sources it was compiled from (`# pragma:sources <hash>`); it is
15
+ * recompiled only when the resolved sources no longer match that hash.
16
+ *
17
+ * Failure is never fatal: a schema that does not compile leaves the
18
+ * previous artifact in place and the boot proceeds with a warning.
19
+ */
20
+
21
+ import {
22
+ closeSync,
23
+ existsSync,
24
+ mkdirSync,
25
+ openSync,
26
+ readSync,
27
+ writeFileSync,
28
+ } from "node:fs";
29
+ import { dirname, join } from "node:path";
30
+ import { createStore, definePlugin, type InlineSource } from "@canonical/ke";
31
+ import { hashSources } from "@canonical/ke-graphql";
32
+ import { findProjectConfigPath } from "#config";
33
+ import { dataRoot } from "../../refs/operations/paths.js";
34
+ import type { PragmaRuntime } from "../../shared/runtime.js";
35
+ import compileSchema from "./compileSchema.js";
36
+
37
+ /** First-line marker carrying the source fingerprint. */
38
+ const HASH_HEADER = "# pragma:sources ";
39
+
40
+ /** Environment kill-switch (CI, tests, air-gapped boots). */
41
+ const DISABLE_ENV = "PRAGMA_SCHEMA_ARTIFACT";
42
+
43
+ export interface SchemaArtifactResult {
44
+ /** Absolute path of the artifact. */
45
+ readonly path: string;
46
+ /**
47
+ * What happened: `fresh` (hash matched, nothing to do), `written`
48
+ * (compiled and saved), `failed` (compilation failed, artifact left
49
+ * as-is), `skipped` (no sources, or disabled via env).
50
+ */
51
+ readonly status: "fresh" | "written" | "failed" | "skipped";
52
+ }
53
+
54
+ /**
55
+ * Where the schema artifact lives for a working directory: project-local
56
+ * (`.pragma/` beside the nearest `pragma.config.json`) when the project is
57
+ * configured, global XDG data dir otherwise.
58
+ */
59
+ export function resolveSchemaArtifactPath(cwd: string): string {
60
+ const configPath = findProjectConfigPath(cwd);
61
+ return configPath
62
+ ? join(dirname(configPath), ".pragma", "schema.graphql")
63
+ : join(dataRoot(), "schema.graphql");
64
+ }
65
+
66
+ /**
67
+ * Ensure the schema artifact exists and matches the runtime's resolved
68
+ * TTL sources, recompiling only on hash mismatch.
69
+ *
70
+ * @note Impure — reads and writes the artifact; compiles via a throwaway
71
+ * ke store when stale.
72
+ */
73
+ export default async function ensureSchemaArtifact(
74
+ runtime: Pick<PragmaRuntime, "cwd" | "packages" | "store">,
75
+ ): Promise<SchemaArtifactResult> {
76
+ const path = resolveSchemaArtifactPath(runtime.cwd);
77
+
78
+ if (process.env[DISABLE_ENV] === "off") {
79
+ return { path, status: "skipped" };
80
+ }
81
+
82
+ // Sort by provenance path so the fingerprint is stable across boots
83
+ // regardless of package resolution order.
84
+ const sources: InlineSource[] = runtime.packages
85
+ .flatMap((pkg) =>
86
+ pkg.graphs.map((graph) => ({
87
+ content: graph.content,
88
+ format: "turtle" as const,
89
+ path: graph.path,
90
+ })),
91
+ )
92
+ .sort((a, b) => a.path.localeCompare(b.path));
93
+
94
+ if (sources.length === 0) {
95
+ return { path, status: "skipped" };
96
+ }
97
+
98
+ const hash = hashSources(sources.map((source) => source.content));
99
+
100
+ if (existsSync(path) && readFirstLine(path) === `${HASH_HEADER}${hash}`) {
101
+ return { path, status: "fresh" };
102
+ }
103
+
104
+ // Mirror boot's "one bad file cannot break boot": compile from the
105
+ // parseable sources only. The runtime already warned about any malformed
106
+ // graph while booting the main store, so the filter here is silent.
107
+ // The fingerprint above covers ALL sources, so fixing a malformed file
108
+ // changes the hash and triggers recompilation.
109
+ const parseable = await filterParseableSources(sources);
110
+ if (parseable.length === 0) {
111
+ return { path, status: "failed" };
112
+ }
113
+
114
+ const outcome = await compileSchema({
115
+ sources: parseable,
116
+ prefixes: runtime.store.prefixes,
117
+ cwd: runtime.cwd,
118
+ });
119
+
120
+ if (outcome.status === "failed" || !outcome.compiled) {
121
+ return { path, status: "failed" };
122
+ }
123
+
124
+ mkdirSync(dirname(path), { recursive: true });
125
+ writeFileSync(
126
+ path,
127
+ `${HASH_HEADER}${hash}\n${outcome.compiled.sdl}`,
128
+ "utf-8",
129
+ );
130
+
131
+ return { path, status: "written" };
132
+ }
133
+
134
+ /**
135
+ * Read just the artifact's header line — the freshness probe runs on every
136
+ * booted invocation, so it must not scale with the SDL's size.
137
+ */
138
+ function readFirstLine(path: string, max = 96): string {
139
+ const fd = openSync(path, "r");
140
+ try {
141
+ const buffer = Buffer.alloc(max);
142
+ const bytes = readSync(fd, buffer, 0, max, 0);
143
+ const text = buffer.subarray(0, bytes).toString("utf-8");
144
+ const newline = text.indexOf("\n");
145
+ return newline === -1 ? text : text.slice(0, newline);
146
+ } finally {
147
+ closeSync(fd);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Drop sources the Turtle parser rejects, keeping the rest — the same
153
+ * tolerance the main store's graph loader applies at boot. Probes with a
154
+ * throwaway store so the strict `graphql check` semantics of
155
+ * {@link compileSchema} stay untouched.
156
+ */
157
+ async function filterParseableSources(
158
+ sources: readonly InlineSource[],
159
+ ): Promise<InlineSource[]> {
160
+ const parseable: InlineSource[] = [];
161
+ const probe = definePlugin({
162
+ name: "pragma-schema-artifact-probe",
163
+ onReady(ctx) {
164
+ for (const source of sources) {
165
+ try {
166
+ ctx.load(source.content, { format: source.format });
167
+ parseable.push(source);
168
+ } catch {
169
+ // Already warned at boot by the main store's graph loader.
170
+ }
171
+ }
172
+ },
173
+ });
174
+
175
+ const store = await createStore({ sources: [], plugins: [probe] });
176
+ store.dispose();
177
+ return parseable;
178
+ }
@@ -5,3 +5,13 @@ export type {
5
5
  CompileSchemaResult,
6
6
  } from "./compileSchema.js";
7
7
  export { default as compileSchema } from "./compileSchema.js";
8
+ export type {
9
+ CreateSchemaServerOptions,
10
+ SchemaServer,
11
+ } from "./createSchemaServer.js";
12
+ export { default as createSchemaServer } from "./createSchemaServer.js";
13
+ export type { SchemaArtifactResult } from "./ensureSchemaArtifact.js";
14
+ export {
15
+ default as ensureSchemaArtifact,
16
+ resolveSchemaArtifactPath,
17
+ } from "./ensureSchemaArtifact.js";
@@ -8,7 +8,9 @@ import {
8
8
 
9
9
  function createUpgradeData(overrides: Partial<UpgradeData> = {}): UpgradeData {
10
10
  return {
11
- pm: "bun",
11
+ // `pm` is the install label, which already carries the scope (as the
12
+ // command populates it from source.label).
13
+ pm: "bun (global)",
12
14
  current: "0.18.0",
13
15
  latest: "0.19.0",
14
16
  command: "bun update -g @canonical/pragma-cli",
@@ -21,9 +23,10 @@ function createUpgradeData(overrides: Partial<UpgradeData> = {}): UpgradeData {
21
23
  }
22
24
 
23
25
  describe("renderUpgradePlain", () => {
24
- it("shows PM info", () => {
26
+ it("shows PM info without doubling the scope suffix", () => {
25
27
  const output = renderUpgradePlain(createUpgradeData());
26
- expect(output).toContain("bun (global)");
28
+ expect(output).toContain("Installed via: bun (global)");
29
+ expect(output).not.toContain("(global) (global)");
27
30
  });
28
31
 
29
32
  it("shows version transition", () => {
@@ -107,7 +110,7 @@ describe("renderUpgradeJson", () => {
107
110
  it("produces valid JSON", () => {
108
111
  const data = createUpgradeData();
109
112
  const parsed = JSON.parse(renderUpgradeJson(data));
110
- expect(parsed.pm).toBe("bun");
113
+ expect(parsed.pm).toBe("bun (global)");
111
114
  expect(parsed.current).toBe("0.18.0");
112
115
  expect(parsed.latest).toBe("0.19.0");
113
116
  });
@@ -12,7 +12,9 @@ import type { UpgradeData } from "../types.js";
12
12
  function renderUpgradePlain(data: UpgradeData): string {
13
13
  const lines: string[] = [];
14
14
 
15
- lines.push(`Installed via: ${data.pm} (global)`);
15
+ // `data.pm` is the install label (e.g. "bun (global)"), which already carries
16
+ // the scope — do not append another "(global)".
17
+ lines.push(`Installed via: ${data.pm}`);
16
18
 
17
19
  if (data.offline) {
18
20
  lines.push("Could not reach registry");