@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
@@ -0,0 +1,151 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { PragmaError } from "#error";
3
+ import applyPackFilters from "./applyPackFilters.js";
4
+ import type { StoryPackFilter } from "./types.js";
5
+
6
+ const ROWS = [
7
+ { name: "Gazpacho", category: "soup" },
8
+ { name: "Pancakes", category: "breakfast" },
9
+ ];
10
+ const FILTERS: StoryPackFilter[] = [
11
+ { param: "category", variable: "category", values: ["breakfast", "soup"] },
12
+ ];
13
+
14
+ describe("applyPackFilters", () => {
15
+ it("returns rows unchanged when no filter value is provided", () => {
16
+ expect(applyPackFilters(ROWS, FILTERS, {})).toEqual(ROWS);
17
+ });
18
+
19
+ it("returns rows unchanged when no filters are declared", () => {
20
+ expect(applyPackFilters(ROWS, undefined, { category: "soup" })).toEqual(
21
+ ROWS,
22
+ );
23
+ });
24
+
25
+ it("keeps only rows matching the provided value", () => {
26
+ const rows = applyPackFilters(ROWS, FILTERS, { category: "soup" });
27
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
28
+ });
29
+
30
+ it("rejects values outside the declared set", () => {
31
+ expect(() =>
32
+ applyPackFilters(ROWS, FILTERS, { category: "dinner" }),
33
+ ).toThrowError(PragmaError);
34
+ try {
35
+ applyPackFilters(ROWS, FILTERS, { category: "dinner" });
36
+ } catch (error) {
37
+ expect((error as PragmaError).code).toBe("INVALID_INPUT");
38
+ expect((error as PragmaError).recovery?.message).toContain("breakfast");
39
+ }
40
+ });
41
+
42
+ it("rejects non-string values and suggests the declared set", () => {
43
+ expect(() => applyPackFilters(ROWS, FILTERS, { category: 3 })).toThrowError(
44
+ PragmaError,
45
+ );
46
+ try {
47
+ applyPackFilters(ROWS, FILTERS, { category: ["soup"] });
48
+ } catch (error) {
49
+ expect((error as PragmaError).validOptions).toEqual([
50
+ "breakfast",
51
+ "soup",
52
+ ]);
53
+ }
54
+ });
55
+
56
+ it("canonicalizes case-insensitive input to the declared value", () => {
57
+ const rows = applyPackFilters(ROWS, FILTERS, { category: "SOUP" });
58
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
59
+ });
60
+
61
+ it("trims surrounding whitespace from the provided value", () => {
62
+ const rows = applyPackFilters(ROWS, FILTERS, { category: " soup " });
63
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
64
+ });
65
+
66
+ it("matches unicode values across NFC/NFD normalization forms", () => {
67
+ const filters: StoryPackFilter[] = [
68
+ { param: "kind", variable: "kind", values: ["entr\u00e9e"] },
69
+ ];
70
+ const rows = [{ name: "Salad", kind: "entre\u0301e" }];
71
+ expect(
72
+ applyPackFilters(rows, filters, { kind: "entre\u0301e" }),
73
+ ).toHaveLength(1);
74
+ });
75
+
76
+ it("never matches rows without a binding for the variable", () => {
77
+ const rows = [{ name: "Mystery" }, ...ROWS];
78
+ const filtered = applyPackFilters(rows, FILTERS, { category: "soup" });
79
+ expect(filtered.map((row) => row.name)).toEqual(["Gazpacho"]);
80
+ });
81
+
82
+ it("carries the declared values as valid options on rejection", () => {
83
+ try {
84
+ applyPackFilters(ROWS, FILTERS, { category: "sopu" });
85
+ } catch (error) {
86
+ expect((error as PragmaError).code).toBe("INVALID_INPUT");
87
+ expect((error as PragmaError).validOptions).toContain("soup");
88
+ }
89
+ });
90
+
91
+ it("applies several filters conjunctively", () => {
92
+ const filters: StoryPackFilter[] = [
93
+ ...FILTERS,
94
+ { param: "name", variable: "name", values: ["Gazpacho", "Pancakes"] },
95
+ ];
96
+ const rows = applyPackFilters(ROWS, filters, {
97
+ category: "soup",
98
+ name: "Pancakes",
99
+ });
100
+ expect(rows).toEqual([]);
101
+ });
102
+ });
103
+
104
+ // Value-free filters (pack v1): no declared value set, case-insensitive
105
+ // equality against the variable — the valid values live in the data.
106
+ describe("applyPackFilters — value-free filters", () => {
107
+ const FREE_FILTERS: StoryPackFilter[] = [
108
+ { param: "category", variable: "category" },
109
+ ];
110
+
111
+ it("matches on case-insensitive equality with the variable", () => {
112
+ const rows = applyPackFilters(ROWS, FREE_FILTERS, { category: "SOUP" });
113
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
114
+ });
115
+
116
+ it("trims surrounding whitespace from the provided value", () => {
117
+ const rows = applyPackFilters(ROWS, FREE_FILTERS, { category: " soup " });
118
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
119
+ });
120
+
121
+ it("returns no rows for an unknown value instead of rejecting", () => {
122
+ expect(
123
+ applyPackFilters(ROWS, FREE_FILTERS, { category: "dinner" }),
124
+ ).toEqual([]);
125
+ });
126
+
127
+ it("matches unicode values across NFC/NFD normalization forms", () => {
128
+ const rows = [{ name: "Salad", kind: "entr\u00e9e" }];
129
+ const filters: StoryPackFilter[] = [{ param: "kind", variable: "kind" }];
130
+ expect(
131
+ applyPackFilters(rows, filters, { kind: "entre\u0301e" }),
132
+ ).toHaveLength(1);
133
+ });
134
+
135
+ it("never matches rows without a binding for the variable", () => {
136
+ const rows = [{ name: "Mystery" }, ...ROWS];
137
+ const filtered = applyPackFilters(rows, FREE_FILTERS, { category: "soup" });
138
+ expect(filtered.map((row) => row.name)).toEqual(["Gazpacho"]);
139
+ });
140
+
141
+ it("rejects non-string values with INVALID_INPUT", () => {
142
+ expect(() =>
143
+ applyPackFilters(ROWS, FREE_FILTERS, { category: 3 }),
144
+ ).toThrowError(PragmaError);
145
+ try {
146
+ applyPackFilters(ROWS, FREE_FILTERS, { category: ["soup"] });
147
+ } catch (error) {
148
+ expect((error as PragmaError).code).toBe("INVALID_INPUT");
149
+ }
150
+ });
151
+ });
@@ -0,0 +1,111 @@
1
+ import { PragmaError } from "#error";
2
+ import type { StoryPackFilter } from "./types.js";
3
+
4
+ /**
5
+ * Apply declared pack filters to resolved list rows.
6
+ *
7
+ * Filters are row predicates on projected SELECT variables — the author
8
+ * query is never modified, so user input cannot inject SPARQL and the
9
+ * query's ordering is preserved.
10
+ *
11
+ * Matching semantics:
12
+ * - With declared `values`: input is trimmed, unicode-normalized (NFC),
13
+ * and matched against the declared values case-insensitively; the
14
+ * DECLARED value is canonical and rows are compared against it exactly
15
+ * (NFC on both sides).
16
+ * - Without `values` (value-free filter): the input itself is the term —
17
+ * trimmed, NFC-normalized — and rows match on case-insensitive equality
18
+ * with the variable's value. Nothing is rejected up front because the
19
+ * valid set lives in the data.
20
+ * - ke bindings carry lexical forms — language tags and datatypes are
21
+ * already stripped — so plain string comparison is sound.
22
+ * - A row without a binding for the variable (e.g. from OPTIONAL) never
23
+ * matches an active filter.
24
+ * - Several provided filters combine conjunctively.
25
+ *
26
+ * @param rows - Rows produced by the pack's list query.
27
+ * @param filters - Declared filters (absent means no filtering).
28
+ * @param params - Story parameters as provided by the surface.
29
+ * @returns Rows matching every provided filter.
30
+ * @throws PragmaError with code `INVALID_INPUT` (carrying the declared
31
+ * values as valid options) when a provided value is not in the
32
+ * filter's declared set, or when a value-free filter receives a
33
+ * non-string value.
34
+ */
35
+ export default function applyPackFilters(
36
+ rows: Record<string, string>[],
37
+ filters: readonly StoryPackFilter[] | undefined,
38
+ params: Record<string, unknown>,
39
+ ): Record<string, string>[] {
40
+ // Start from the caller's array; each active filter's `.filter()` yields a
41
+ // fresh array, so with no active filter the rows pass through unmodified —
42
+ // no defensive clone (the caller owns the query result).
43
+ let result = rows;
44
+ for (const filter of filters ?? []) {
45
+ const provided = params[filter.param];
46
+ if (provided === undefined) continue;
47
+ const values = filter.values;
48
+ if (values === undefined) {
49
+ // Value-free filter: case-insensitive equality against the variable.
50
+ const term = requireStringValue(provided, filter).toLowerCase();
51
+ result = result.filter(
52
+ (row) => row[filter.variable]?.normalize("NFC").toLowerCase() === term,
53
+ );
54
+ continue;
55
+ }
56
+ const canonical = canonicalizeFilterValue(provided, filter, values);
57
+ result = result.filter(
58
+ (row) => row[filter.variable]?.normalize("NFC") === canonical,
59
+ );
60
+ }
61
+ return result;
62
+ }
63
+
64
+ /**
65
+ * Require a value-free filter's input to be a trimmed, NFC-normalized
66
+ * string.
67
+ *
68
+ * @throws PragmaError with code `INVALID_INPUT` when it is not a string.
69
+ */
70
+ function requireStringValue(
71
+ provided: unknown,
72
+ filter: StoryPackFilter,
73
+ ): string {
74
+ if (typeof provided !== "string") {
75
+ throw PragmaError.invalidInput(filter.param, String(provided), {
76
+ recovery: {
77
+ message: `Provide a string value for --${filter.param}.`,
78
+ },
79
+ });
80
+ }
81
+ return provided.trim().normalize("NFC");
82
+ }
83
+
84
+ /**
85
+ * Resolve a provided filter value to its declared canonical form.
86
+ *
87
+ * @throws PragmaError with code `INVALID_INPUT` when the value is not a
88
+ * string or not in the declared set.
89
+ */
90
+ function canonicalizeFilterValue(
91
+ provided: unknown,
92
+ filter: StoryPackFilter,
93
+ values: readonly string[],
94
+ ): string {
95
+ if (typeof provided === "string") {
96
+ const normalized = provided.trim().normalize("NFC");
97
+ const match = values.find(
98
+ (value) =>
99
+ value.normalize("NFC").toLowerCase() === normalized.toLowerCase(),
100
+ );
101
+ if (match !== undefined) {
102
+ return match.normalize("NFC");
103
+ }
104
+ }
105
+ throw PragmaError.invalidInput(filter.param, String(provided), {
106
+ validOptions: [...values],
107
+ recovery: {
108
+ message: `Allowed values for --${filter.param}: ${values.join(", ")}.`,
109
+ },
110
+ });
111
+ }
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import applyPackSearch from "./applyPackSearch.js";
3
+ import type { StoryPackSearch } from "./types.js";
4
+
5
+ const ROWS = [
6
+ { name: "Gazpacho", description: "A cold soup" },
7
+ { name: "Pancakes", description: "Fried batter" },
8
+ { name: "Mystery" },
9
+ ];
10
+ const SEARCH: StoryPackSearch = { variables: ["name", "description"] };
11
+
12
+ describe("applyPackSearch", () => {
13
+ it("returns rows unchanged when no search is declared", () => {
14
+ expect(applyPackSearch(ROWS, undefined, { search: "soup" })).toEqual(ROWS);
15
+ });
16
+
17
+ it("returns rows unchanged when no term is provided", () => {
18
+ expect(applyPackSearch(ROWS, SEARCH, {})).toEqual(ROWS);
19
+ });
20
+
21
+ it("returns rows unchanged for an empty or whitespace term", () => {
22
+ expect(applyPackSearch(ROWS, SEARCH, { search: " " })).toEqual(ROWS);
23
+ });
24
+
25
+ it("keeps rows where any searched variable contains the term", () => {
26
+ const rows = applyPackSearch(ROWS, SEARCH, { search: "soup" });
27
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
28
+ });
29
+
30
+ it("matches case-insensitively", () => {
31
+ const rows = applyPackSearch(ROWS, SEARCH, { search: "PANCAKE" });
32
+ expect(rows.map((row) => row.name)).toEqual(["Pancakes"]);
33
+ });
34
+
35
+ it("matches substrings mid-value", () => {
36
+ const rows = applyPackSearch(ROWS, SEARCH, { search: "batter" });
37
+ expect(rows.map((row) => row.name)).toEqual(["Pancakes"]);
38
+ });
39
+
40
+ it("never matches rows without a binding for a searched variable", () => {
41
+ const rows = applyPackSearch(ROWS, SEARCH, { search: "myst" });
42
+ expect(rows.map((row) => row.name)).toEqual(["Mystery"]);
43
+ expect(applyPackSearch(ROWS, SEARCH, { search: "absent" })).toEqual([]);
44
+ });
45
+
46
+ it("matches unicode terms across NFC/NFD normalization forms", () => {
47
+ const rows = [{ name: "Entr\u00e9e" }];
48
+ const search: StoryPackSearch = { variables: ["name"] };
49
+ expect(
50
+ applyPackSearch(rows, search, { search: "entre\u0301e" }),
51
+ ).toHaveLength(1);
52
+ });
53
+
54
+ it("ignores non-string search values", () => {
55
+ expect(applyPackSearch(ROWS, SEARCH, { search: 3 })).toEqual(ROWS);
56
+ });
57
+ });
@@ -0,0 +1,41 @@
1
+ import type { StoryPackSearch } from "./types.js";
2
+
3
+ /**
4
+ * Apply a pack list's free-text search to resolved rows.
5
+ *
6
+ * Search is a row predicate on projected SELECT variables — like filters,
7
+ * it runs AFTER the author query, so user input never touches the query
8
+ * text (injection-safe by construction) and the author's ORDER BY is
9
+ * preserved.
10
+ *
11
+ * Matching semantics:
12
+ * - The term is NFC-normalized and matched case-insensitively.
13
+ * - A row passes when ANY declared variable's value contains the term as
14
+ * a substring; unbound variables (e.g. from OPTIONAL) never match.
15
+ * - A non-string or empty term is a no-op — search is a refinement, not a
16
+ * validated enum, so there is nothing to reject against.
17
+ *
18
+ * @param rows - Rows produced by the pack's list query (post-filter).
19
+ * @param search - The declared search (absent means no searching).
20
+ * @param params - Story parameters as provided by the surface.
21
+ * @returns Rows containing the term in at least one searched variable.
22
+ */
23
+ export default function applyPackSearch(
24
+ rows: Record<string, string>[],
25
+ search: StoryPackSearch | undefined,
26
+ params: Record<string, unknown>,
27
+ ): Record<string, string>[] {
28
+ const provided = params.search;
29
+ if (search === undefined || typeof provided !== "string") {
30
+ return rows;
31
+ }
32
+ const term = provided.trim().normalize("NFC").toLowerCase();
33
+ if (term === "") {
34
+ return rows;
35
+ }
36
+ return rows.filter((row) =>
37
+ search.variables.some((variable) =>
38
+ row[variable]?.normalize("NFC").toLowerCase().includes(term),
39
+ ),
40
+ );
41
+ }
@@ -0,0 +1,81 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import buildLookupQuery, {
3
+ buildLookupByIriQuery,
4
+ buildLookupNamesQuery,
5
+ escapeSparqlString,
6
+ formatTerm,
7
+ } from "./buildLookupQuery.js";
8
+
9
+ describe("escapeSparqlString", () => {
10
+ it("escapes quotes, backslashes, and newlines", () => {
11
+ expect(escapeSparqlString('a"b\\c\nd')).toBe('a\\"b\\\\c\\nd');
12
+ });
13
+ });
14
+
15
+ describe("formatTerm", () => {
16
+ it("keeps prefixed names and wraps absolute IRIs", () => {
17
+ expect(formatTerm("ex:Recipe")).toBe("ex:Recipe");
18
+ expect(formatTerm("http://example.org/x")).toBe("<http://example.org/x>");
19
+ });
20
+ });
21
+
22
+ describe("buildLookupQuery", () => {
23
+ it("generates a typed, escaped, case-insensitive match", () => {
24
+ const query = buildLookupQuery(
25
+ {
26
+ by: "ex:name",
27
+ type: "ex:Recipe",
28
+ fields: [{ name: "category", property: "ex:category" }],
29
+ },
30
+ 'Pan"cakes',
31
+ );
32
+ expect(query).toContain("?uri ex:name ?name .");
33
+ expect(query).toContain("?uri a ex:Recipe .");
34
+ expect(query).toContain("OPTIONAL { ?uri ex:category ?category . }");
35
+ expect(query).toContain('LCASE("Pan\\"cakes")');
36
+ expect(query).toContain("LIMIT 1");
37
+ });
38
+
39
+ it("omits the type constraint when absent", () => {
40
+ const query = buildLookupQuery({ by: "ex:name" }, "x");
41
+ expect(query).not.toContain("?uri a ");
42
+ });
43
+ });
44
+
45
+ describe("buildLookupByIriQuery", () => {
46
+ it("binds the resolved IRI instead of filtering on the name", () => {
47
+ const query = buildLookupByIriQuery(
48
+ {
49
+ by: "ex:name",
50
+ type: "ex:Recipe",
51
+ fields: [{ name: "category", property: "ex:category" }],
52
+ },
53
+ "http://example.org/recipes/pancakes",
54
+ );
55
+ expect(query).toContain(
56
+ "BIND(<http://example.org/recipes/pancakes> AS ?uri)",
57
+ );
58
+ expect(query).toContain("?uri ex:name ?name .");
59
+ expect(query).toContain("?uri a ex:Recipe .");
60
+ expect(query).toContain("OPTIONAL { ?uri ex:category ?category . }");
61
+ expect(query).not.toContain("FILTER");
62
+ expect(query).toContain("LIMIT 1");
63
+ });
64
+
65
+ it("omits the type constraint when absent", () => {
66
+ const query = buildLookupByIriQuery(
67
+ { by: "ex:name" },
68
+ "http://example.org/x",
69
+ );
70
+ expect(query).not.toContain("?uri a ");
71
+ });
72
+ });
73
+
74
+ describe("buildLookupNamesQuery", () => {
75
+ it("selects all names for the by property", () => {
76
+ const query = buildLookupNamesQuery({ by: "ex:name", type: "ex:Recipe" });
77
+ expect(query).toContain("SELECT ?name WHERE {");
78
+ expect(query).toContain("?uri ex:name ?name .");
79
+ expect(query).toContain("?uri a ex:Recipe .");
80
+ });
81
+ });
@@ -0,0 +1,154 @@
1
+ import type { StoryPackExpand, StoryPackLookup } from "./types.js";
2
+
3
+ /**
4
+ * Build the generated, injection-safe lookup queries for a pack story.
5
+ *
6
+ * Pack authors declare *what* names an entity (`by`, optional `type`) and
7
+ * which properties to read; the query text is generated here so
8
+ * user-supplied names are always escaped SPARQL string literals — a pack
9
+ * definition never interpolates user input itself. Matching is exact and
10
+ * case-insensitive on the `by` property's string value.
11
+ */
12
+
13
+ /** Escape a user-supplied value as a SPARQL string literal body. */
14
+ export function escapeSparqlString(value: string): string {
15
+ return value
16
+ .replaceAll("\\", "\\\\")
17
+ .replaceAll('"', '\\"')
18
+ .replaceAll("\n", "\\n")
19
+ .replaceAll("\r", "\\r");
20
+ }
21
+
22
+ /** Render a validated term (prefixed name or absolute IRI) as a query token. */
23
+ export function formatTerm(term: string): string {
24
+ return term.includes("://") ? `<${term}>` : term;
25
+ }
26
+
27
+ /**
28
+ * Build the SELECT retrieving one named entity with its declared fields.
29
+ *
30
+ * @param lookup - The pack's lookup declaration.
31
+ * @param name - User-supplied entity name (escaped here).
32
+ * @returns SPARQL SELECT text.
33
+ */
34
+ export default function buildLookupQuery(
35
+ lookup: StoryPackLookup,
36
+ name: string,
37
+ ): string {
38
+ const fields = [...(lookup.fields ?? []), ...(lookup.sections ?? [])];
39
+ const vars = fields.map((field) => `?${field.name}`).join(" ");
40
+ const optionals = fields
41
+ .map(
42
+ (field) =>
43
+ ` OPTIONAL { ?uri ${formatTerm(field.property)} ?${field.name} . }`,
44
+ )
45
+ .join("\n");
46
+ const typeConstraint = lookup.type
47
+ ? ` ?uri a ${formatTerm(lookup.type)} .\n`
48
+ : "";
49
+
50
+ return [
51
+ `SELECT ?uri ?name${vars.length > 0 ? ` ${vars}` : ""} WHERE {`,
52
+ ` ?uri ${formatTerm(lookup.by)} ?name .`,
53
+ typeConstraint + optionals,
54
+ ` FILTER (LCASE(STR(?name)) = LCASE("${escapeSparqlString(name)}"))`,
55
+ "}",
56
+ "LIMIT 1",
57
+ ]
58
+ .filter((line) => line !== "")
59
+ .join("\n");
60
+ }
61
+
62
+ /**
63
+ * Build the SELECT retrieving one entity addressed directly by IRI.
64
+ *
65
+ * Mirrors {@link buildLookupQuery} but binds the already-resolved IRI to
66
+ * `?uri` instead of filtering on the `by` property's value, so absolute
67
+ * IRIs and prefixed names address an entity exactly. Injection-safe by
68
+ * construction: the caller resolves and validates `iri` against the
69
+ * embeddable-IRI shape (no whitespace or `<>"` characters) BEFORE it is
70
+ * interpolated here — raw user input never reaches this function.
71
+ *
72
+ * @param lookup - The pack's lookup declaration.
73
+ * @param iri - A resolved, validated absolute IRI.
74
+ * @returns SPARQL SELECT text.
75
+ */
76
+ export function buildLookupByIriQuery(
77
+ lookup: StoryPackLookup,
78
+ iri: string,
79
+ ): string {
80
+ const fields = [...(lookup.fields ?? []), ...(lookup.sections ?? [])];
81
+ const vars = fields.map((field) => `?${field.name}`).join(" ");
82
+ const optionals = fields
83
+ .map(
84
+ (field) =>
85
+ ` OPTIONAL { ?uri ${formatTerm(field.property)} ?${field.name} . }`,
86
+ )
87
+ .join("\n");
88
+ const typeConstraint = lookup.type
89
+ ? ` ?uri a ${formatTerm(lookup.type)} .\n`
90
+ : "";
91
+
92
+ return [
93
+ `SELECT ?uri ?name${vars.length > 0 ? ` ${vars}` : ""} WHERE {`,
94
+ ` BIND(<${iri}> AS ?uri)`,
95
+ ` ?uri ${formatTerm(lookup.by)} ?name .`,
96
+ typeConstraint + optionals,
97
+ "}",
98
+ "LIMIT 1",
99
+ ]
100
+ .filter((line) => line !== "")
101
+ .join("\n");
102
+ }
103
+
104
+ /**
105
+ * Build the sub-SELECT retrieving one expand's child rows for a resolved
106
+ * entity.
107
+ *
108
+ * Injection-safe by construction: the only interpolated value is
109
+ * `entityUri`, which is the IRI the base lookup already resolved from the
110
+ * store (never user input), and the relation/properties are validated pack
111
+ * terms. Returns one row per child node, each carrying the declared fields.
112
+ *
113
+ * @param expand - The expand declaration.
114
+ * @param entityUri - The resolved entity IRI (from the base lookup's `?uri`).
115
+ * @returns SPARQL SELECT text yielding one row per child node.
116
+ */
117
+ export function buildExpandQuery(
118
+ expand: StoryPackExpand,
119
+ entityUri: string,
120
+ ): string {
121
+ const vars = expand.select.map((field) => `?${field.name}`).join(" ");
122
+ const optionals = expand.select
123
+ .map(
124
+ (field) =>
125
+ ` OPTIONAL { ?child ${formatTerm(field.property)} ?${field.name} . }`,
126
+ )
127
+ .join("\n");
128
+ return [
129
+ `SELECT ${vars} WHERE {`,
130
+ ` <${entityUri}> ${formatTerm(expand.relation)} ?child .`,
131
+ optionals,
132
+ "}",
133
+ ].join("\n");
134
+ }
135
+
136
+ /**
137
+ * Build the SELECT listing all entity names — lookup-miss suggestions.
138
+ *
139
+ * @param lookup - The pack's lookup declaration.
140
+ * @returns SPARQL SELECT text yielding a `?name` per entity.
141
+ */
142
+ export function buildLookupNamesQuery(lookup: StoryPackLookup): string {
143
+ const typeConstraint = lookup.type
144
+ ? ` ?uri a ${formatTerm(lookup.type)} .\n`
145
+ : "";
146
+ return [
147
+ "SELECT ?name WHERE {",
148
+ ` ?uri ${formatTerm(lookup.by)} ?name .`,
149
+ typeConstraint.trimEnd(),
150
+ "}",
151
+ ]
152
+ .filter((line) => line !== "")
153
+ .join("\n");
154
+ }
@@ -0,0 +1,23 @@
1
+ /**
2
+ * Bundled transitional story packs.
3
+ *
4
+ * These are declarative replacements for design-system domains that used to be
5
+ * hand-written TypeScript in the CLI core. They ship inside pragma during the
6
+ * strangler migration (ADR C.07) and are the **lowest-precedence** pack
7
+ * source: a `pragma.config.json` `stories` entry or a semantic package's
8
+ * `stories/*.json` for the same noun overrides the bundled one, so a package
9
+ * can take ownership of its noun (the P4 end state) without a code change here.
10
+ *
11
+ * As each leaf domain is cut over, its built-in command is deleted (freeing the
12
+ * noun from the reserved set) and its pack is added to this list.
13
+ */
14
+
15
+ import type { StoryPackDefinition } from "../types.js";
16
+ import { standardPack } from "./standardPack.js";
17
+ import { tierPack } from "./tierPack.js";
18
+
19
+ /** Every pack pragma ships by default, lowest precedence. */
20
+ export const BUNDLED_PACKS: readonly StoryPackDefinition[] = [
21
+ tierPack,
22
+ standardPack,
23
+ ];