@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.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 (344) hide show
  1. package/README.md +67 -15
  2. package/dist/pragma +0 -0
  3. package/package.json +16 -17
  4. package/src/config/defaultConfig.test.ts +33 -0
  5. package/src/config/defaultConfig.ts +59 -0
  6. package/src/config/defaults.json +17 -0
  7. package/src/config/findProjectConfigPath.test.ts +53 -0
  8. package/src/config/findProjectConfigPath.ts +36 -0
  9. package/src/config/index.ts +17 -2
  10. package/src/config/mergeConfigFileUpdate.ts +58 -0
  11. package/src/config/parseConfigValues.ts +215 -0
  12. package/src/config/readConfig.test.ts +104 -13
  13. package/src/config/readConfig.ts +12 -117
  14. package/src/config/readConfigLayers.test.ts +107 -0
  15. package/src/config/readConfigLayers.ts +115 -0
  16. package/src/config/resolveGlobalConfigPath.ts +13 -0
  17. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  18. package/src/config/resolveWriteConfigPath.ts +31 -0
  19. package/src/config/types.ts +88 -2
  20. package/src/config/writeConfig.ts +25 -43
  21. package/src/constants.ts +12 -1
  22. package/src/domains/block/commands/index.ts +2 -1
  23. package/src/domains/block/commands/list.ts +5 -90
  24. package/src/domains/block/commands/lookup.ts +8 -167
  25. package/src/domains/block/commands/sample.ts +55 -0
  26. package/src/domains/block/formatters/index.ts +2 -1
  27. package/src/domains/block/formatters/sample.ts +21 -0
  28. package/src/domains/block/index.ts +3 -3
  29. package/src/domains/block/mcp/specs.ts +36 -168
  30. package/src/domains/block/operations/index.ts +2 -1
  31. package/src/domains/block/operations/lookup.ts +9 -0
  32. package/src/domains/block/operations/sample.ts +41 -0
  33. package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
  34. package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
  35. package/src/domains/block/stories.test.ts +28 -0
  36. package/src/domains/block/stories.ts +254 -0
  37. package/src/domains/config/commands/channel.test.ts +14 -3
  38. package/src/domains/config/commands/channel.ts +11 -7
  39. package/src/domains/config/commands/configScope.ts +46 -0
  40. package/src/domains/config/commands/framework.test.ts +105 -0
  41. package/src/domains/config/commands/framework.ts +91 -0
  42. package/src/domains/config/commands/index.ts +2 -0
  43. package/src/domains/config/commands/show.ts +5 -45
  44. package/src/domains/config/commands/tier.test.ts +13 -2
  45. package/src/domains/config/commands/tier.ts +11 -7
  46. package/src/domains/config/commands/trace.test.ts +165 -0
  47. package/src/domains/config/commands/trace.ts +83 -0
  48. package/src/domains/config/formatters/channel.test.ts +20 -6
  49. package/src/domains/config/formatters/channel.ts +14 -11
  50. package/src/domains/config/formatters/framework.ts +37 -0
  51. package/src/domains/config/formatters/index.ts +2 -0
  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 +28 -0
  57. package/src/domains/config/index.ts +15 -2
  58. package/src/domains/config/mcp/specs.ts +33 -53
  59. package/src/domains/config/operations/index.ts +1 -0
  60. package/src/domains/config/operations/show.test.ts +18 -0
  61. package/src/domains/config/operations/show.ts +7 -1
  62. package/src/domains/config/operations/types.ts +7 -0
  63. package/src/domains/config/operations/validateFramework.ts +24 -0
  64. package/src/domains/config/stories.ts +42 -0
  65. package/src/domains/config/tasks/index.ts +3 -0
  66. package/src/domains/config/tasks/setChannel.ts +13 -22
  67. package/src/domains/config/tasks/setFramework.ts +21 -0
  68. package/src/domains/config/tasks/setTier.ts +10 -23
  69. package/src/domains/config/tasks/setTrace.ts +20 -0
  70. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  71. package/src/domains/create/commands/component.ts +5 -5
  72. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  73. package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
  74. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
  75. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  76. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
  77. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  78. package/src/domains/doctor/operations/checks/index.ts +1 -0
  79. package/src/domains/doctor/operations/checks.test.ts +162 -8
  80. package/src/domains/doctor/operations/runChecks.test.ts +85 -4
  81. package/src/domains/doctor/operations/runChecks.ts +50 -12
  82. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  83. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  84. package/src/domains/graphql/commands/build.test.ts +196 -0
  85. package/src/domains/graphql/commands/build.ts +117 -0
  86. package/src/domains/graphql/commands/check.test.ts +141 -0
  87. package/src/domains/graphql/commands/check.ts +81 -0
  88. package/src/domains/graphql/commands/index.ts +5 -0
  89. package/src/domains/graphql/commands/serve.test.ts +40 -0
  90. package/src/domains/graphql/commands/serve.ts +131 -0
  91. package/src/domains/graphql/constants.ts +10 -0
  92. package/src/domains/graphql/formatters/index.ts +3 -0
  93. package/src/domains/graphql/formatters/report.ts +78 -0
  94. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  95. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  96. package/src/domains/graphql/helpers/index.ts +6 -0
  97. package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
  98. package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
  99. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
  100. package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
  101. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  102. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  103. package/src/domains/graphql/index.ts +24 -0
  104. package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
  105. package/src/domains/graphql/operations/compileSchema.ts +107 -0
  106. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  107. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  108. package/src/domains/graphql/operations/index.ts +12 -0
  109. package/src/domains/graphql/types.ts +29 -0
  110. package/src/domains/info/operations/collectInfo.test.ts +16 -5
  111. package/src/domains/info/operations/collectInfo.ts +16 -19
  112. package/src/domains/llm/capabilities.test.ts +91 -0
  113. package/src/domains/llm/commands/capabilities.ts +28 -0
  114. package/src/domains/llm/commands/index.ts +1 -0
  115. package/src/domains/llm/data/commandReference.ts +90 -18
  116. package/src/domains/llm/data/decisionTrees.ts +15 -13
  117. package/src/domains/llm/data/index.ts +2 -0
  118. package/src/domains/llm/data/toolCatalog.ts +190 -0
  119. package/src/domains/llm/formatters/capabilities.ts +50 -0
  120. package/src/domains/llm/formatters/index.ts +1 -0
  121. package/src/domains/llm/index.ts +3 -2
  122. package/src/domains/llm/mcp/index.ts +1 -1
  123. package/src/domains/llm/mcp/specs.ts +59 -52
  124. package/src/domains/llm/types.ts +40 -0
  125. package/src/domains/modifier/commands/index.ts +2 -1
  126. package/src/domains/modifier/commands/list.ts +5 -38
  127. package/src/domains/modifier/commands/lookup.ts +8 -79
  128. package/src/domains/modifier/commands/sample.ts +53 -0
  129. package/src/domains/modifier/formatters/index.ts +2 -1
  130. package/src/domains/modifier/formatters/sample.ts +15 -0
  131. package/src/domains/modifier/index.ts +7 -3
  132. package/src/domains/modifier/mcp/specs.ts +38 -70
  133. package/src/domains/modifier/operations/index.ts +2 -1
  134. package/src/domains/modifier/operations/lookup.ts +9 -0
  135. package/src/domains/modifier/operations/sample.ts +32 -0
  136. package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
  137. package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
  138. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
  139. package/src/domains/modifier/stories.test.ts +20 -0
  140. package/src/domains/modifier/stories.ts +77 -0
  141. package/src/domains/ontology/commands/list.ts +5 -58
  142. package/src/domains/ontology/commands/show.ts +6 -58
  143. package/src/domains/ontology/formatters/show.ts +7 -1
  144. package/src/domains/ontology/mcp/specs.ts +7 -84
  145. package/src/domains/ontology/stories.test.ts +36 -0
  146. package/src/domains/ontology/stories.ts +108 -0
  147. package/src/domains/refs/operations/index.ts +1 -1
  148. package/src/domains/refs/operations/paths.ts +29 -1
  149. package/src/domains/refs/operations/updateRefs.test.ts +6 -3
  150. package/src/domains/refs/operations/updateRefs.ts +2 -36
  151. package/src/domains/setup/commands/skills.test.ts +6 -2
  152. package/src/domains/setup/commands/skills.ts +6 -2
  153. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  154. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  155. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  156. package/src/domains/shared/bootStore.ts +92 -50
  157. package/src/domains/shared/compactUri.test.ts +11 -0
  158. package/src/domains/shared/compactUri.ts +10 -5
  159. package/src/domains/shared/context.ts +2 -2
  160. package/src/domains/shared/contracts.ts +12 -0
  161. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  162. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  163. package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
  164. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  165. package/src/domains/shared/loaders/bundledLoader.ts +206 -0
  166. package/src/domains/shared/loaders/gitLoader.ts +31 -0
  167. package/src/domains/shared/loaders/index.ts +9 -0
  168. package/src/domains/shared/loaders/localLoader.ts +87 -0
  169. package/src/domains/shared/loaders/readPackageDir.ts +147 -0
  170. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  171. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  172. package/src/domains/shared/lookupMany.test.ts +120 -0
  173. package/src/domains/shared/lookupMany.ts +43 -3
  174. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  175. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  176. package/src/domains/shared/packages.ts +14 -120
  177. package/src/domains/shared/pickRandom.ts +22 -0
  178. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  179. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  180. package/src/domains/shared/runtime.ts +7 -63
  181. package/src/domains/shared/sampleFormatters.ts +47 -0
  182. package/src/domains/shared/semanticPackage.ts +112 -0
  183. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  184. package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
  185. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  186. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  187. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  188. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  189. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  190. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  191. package/src/domains/shared/stories/condense.test.ts +18 -0
  192. package/src/domains/shared/stories/condense.ts +15 -0
  193. package/src/domains/shared/stories/index.ts +20 -0
  194. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  195. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  196. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
  197. package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
  198. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
  199. package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
  200. package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
  201. package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
  202. package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
  203. package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
  204. package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
  205. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
  206. package/src/domains/shared/stories/pack/index.ts +28 -0
  207. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  208. package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
  209. package/src/domains/shared/stories/pack/types.ts +104 -0
  210. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
  211. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
  212. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  213. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  214. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  215. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  216. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  217. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  218. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  219. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  220. package/src/domains/shared/stories/types.ts +171 -0
  221. package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
  222. package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
  223. package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
  224. package/src/domains/shared/suggestions/expandGlob.ts +52 -0
  225. package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
  226. package/src/domains/shared/suggestions/index.ts +7 -0
  227. package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
  228. package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
  229. package/src/domains/shared/suggestions/suggestNames.ts +130 -0
  230. package/src/domains/shared/types/index.ts +1 -0
  231. package/src/domains/shared/types/runtime.ts +4 -1
  232. package/src/domains/shared/types/sample.ts +17 -0
  233. package/src/domains/skill/commands/index.ts +3 -1
  234. package/src/domains/skill/commands/list.test.ts +6 -2
  235. package/src/domains/skill/commands/list.ts +5 -63
  236. package/src/domains/skill/commands/lookup.test.ts +118 -0
  237. package/src/domains/skill/commands/lookup.ts +14 -0
  238. package/src/domains/skill/formatters/index.ts +1 -0
  239. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  240. package/src/domains/skill/formatters/lookup.ts +57 -0
  241. package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
  242. package/src/domains/skill/index.ts +7 -3
  243. package/src/domains/skill/mcp/specs.ts +10 -29
  244. package/src/domains/skill/operations/discover.test.ts +61 -30
  245. package/src/domains/skill/operations/discover.ts +28 -37
  246. package/src/domains/skill/operations/index.ts +2 -1
  247. package/src/domains/skill/operations/list.test.ts +15 -7
  248. package/src/domains/skill/operations/lookup.test.ts +91 -0
  249. package/src/domains/skill/operations/lookup.ts +70 -0
  250. package/src/domains/skill/stories.test.ts +43 -0
  251. package/src/domains/skill/stories.ts +103 -0
  252. package/src/domains/skill/types.ts +8 -0
  253. package/src/domains/standard/commands/index.ts +2 -1
  254. package/src/domains/standard/commands/list.ts +5 -84
  255. package/src/domains/standard/commands/lookup.ts +8 -93
  256. package/src/domains/standard/commands/sample.ts +57 -0
  257. package/src/domains/standard/formatters/index.ts +2 -1
  258. package/src/domains/standard/formatters/sample.ts +15 -0
  259. package/src/domains/standard/index.ts +8 -1
  260. package/src/domains/standard/mcp/specs.ts +44 -152
  261. package/src/domains/standard/operations/index.ts +2 -1
  262. package/src/domains/standard/operations/lookup.ts +9 -0
  263. package/src/domains/standard/operations/sample.ts +35 -0
  264. package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
  265. package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
  266. package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
  267. package/src/domains/standard/stories.test.ts +24 -0
  268. package/src/domains/standard/stories.ts +151 -0
  269. package/src/domains/tier/commands/list.ts +5 -55
  270. package/src/domains/tier/mcp/specs.ts +5 -31
  271. package/src/domains/tier/stories.test.ts +22 -0
  272. package/src/domains/tier/stories.ts +52 -0
  273. package/src/domains/token/commands/index.ts +2 -1
  274. package/src/domains/token/commands/sample.ts +53 -0
  275. package/src/domains/token/formatters/index.ts +2 -1
  276. package/src/domains/token/formatters/sample.ts +16 -0
  277. package/src/domains/token/index.ts +8 -1
  278. package/src/domains/token/mcp/specs.ts +52 -2
  279. package/src/domains/token/operations/index.ts +2 -1
  280. package/src/domains/token/operations/lookup.ts +9 -0
  281. package/src/domains/token/operations/sample.ts +32 -0
  282. package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
  283. package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
  284. package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
  285. package/src/domains/trace/commands/clear.ts +71 -0
  286. package/src/domains/trace/commands/index.ts +5 -0
  287. package/src/domains/trace/commands/sessions.ts +32 -0
  288. package/src/domains/trace/commands/show.ts +145 -0
  289. package/src/domains/trace/formatters/index.ts +9 -0
  290. package/src/domains/trace/formatters/sessions.ts +80 -0
  291. package/src/domains/trace/formatters/show.ts +122 -0
  292. package/src/domains/trace/index.ts +17 -0
  293. package/src/domains/trace/operations/index.ts +8 -0
  294. package/src/domains/trace/operations/listSessions.test.ts +55 -0
  295. package/src/domains/trace/operations/listSessions.ts +52 -0
  296. package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
  297. package/src/domains/trace/operations/readTraceLog.ts +104 -0
  298. package/src/domains/trace/tracePlugin.test.ts +171 -0
  299. package/src/domains/trace/tracePlugin.ts +159 -0
  300. package/src/domains/trace/types.ts +65 -0
  301. package/src/error/PragmaError.ts +15 -2
  302. package/src/error/types.ts +15 -1
  303. package/src/mcp/error/serializeErrorPayload.ts +7 -0
  304. package/src/mcp/error/types.ts +8 -0
  305. package/src/mcp/registerResources.test.ts +405 -75
  306. package/src/mcp/registerResources.ts +5 -299
  307. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  308. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  309. package/src/mcp/resources/buildResourceList.ts +176 -0
  310. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  311. package/src/mcp/resources/classifyEntity.ts +27 -0
  312. package/src/mcp/resources/constants.ts +105 -0
  313. package/src/mcp/resources/displayPredicates.ts +62 -0
  314. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  315. package/src/mcp/resources/pickFirstValue.ts +26 -0
  316. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  317. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  318. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  319. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  320. package/src/mcp/resources/readEntity.ts +169 -0
  321. package/src/mcp/resources/registerResources.ts +126 -0
  322. package/src/mcp/resources/types.ts +128 -0
  323. package/src/mcp/tools/index.ts +9 -0
  324. package/src/mcp/tools/registerTools.test.ts +84 -12
  325. package/src/mcp/types.ts +4 -0
  326. package/src/mcp/utils/wrapTool.test.ts +1 -0
  327. package/src/pipeline/collectCommands.ts +15 -2
  328. package/src/pipeline/renderError.ts +15 -0
  329. package/src/pipeline/resolveCommandKind.ts +8 -1
  330. package/src/pipeline/runCli.ts +51 -6
  331. package/src/testing/createTestMcpClient.ts +5 -1
  332. package/src/testing/graphqlFixtures.ts +72 -0
  333. package/src/testing/index.ts +17 -0
  334. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  335. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  336. package/src/testing/integration/executor-execution.test.ts +101 -0
  337. package/src/testing/integration/executor-golden.test.ts +145 -0
  338. package/src/testing/integration/mcp-surface.test.ts +11 -5
  339. package/src/testing/integration/parity.test.ts +17 -12
  340. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  341. package/src/testing/setupXdgIsolation.ts +15 -0
  342. package/src/testing/standardParityFixtures.ts +92 -0
  343. package/src/testing/storyFixtures.ts +63 -0
  344. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -0,0 +1,57 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import resolveLookupDetailed from "./resolveLookupDetailed.js";
3
+ import type { LookupStory } from "./types.js";
4
+
5
+ type AnyLookupStory = LookupStory<unknown, unknown>;
6
+
7
+ const baseStory: Pick<
8
+ AnyLookupStory,
9
+ "noun" | "description" | "toolDescription" | "namesDescription"
10
+ > = {
11
+ noun: "thing",
12
+ description: "d",
13
+ toolDescription: "td",
14
+ namesDescription: "names",
15
+ };
16
+
17
+ function makeStory(overrides: Partial<AnyLookupStory>): AnyLookupStory {
18
+ return {
19
+ ...baseStory,
20
+ examples: [],
21
+ resolve: async () => ({ results: [], errors: [] }),
22
+ toFmtInput: (entity) => entity,
23
+ formatters: {
24
+ plain: () => "",
25
+ llm: () => "",
26
+ json: () => "",
27
+ },
28
+ ...overrides,
29
+ };
30
+ }
31
+
32
+ describe("resolveLookupDetailed", () => {
33
+ it("is never detailed when the story has no detailed param", () => {
34
+ const story = makeStory({});
35
+ expect(resolveLookupDetailed(story, "cli", { detailed: true })).toBe(false);
36
+ expect(resolveLookupDetailed(story, "mcp", {})).toBe(false);
37
+ });
38
+
39
+ it("defaults detailed to true on MCP and false on CLI", () => {
40
+ const story = makeStory({ detailedParam: { description: "d" } });
41
+ expect(resolveLookupDetailed(story, "mcp", {})).toBe(true);
42
+ expect(resolveLookupDetailed(story, "mcp", { detailed: false })).toBe(
43
+ false,
44
+ );
45
+ expect(resolveLookupDetailed(story, "cli", {})).toBe(false);
46
+ expect(resolveLookupDetailed(story, "cli", { detailed: true })).toBe(true);
47
+ });
48
+
49
+ it("lets a story override the surface defaults", () => {
50
+ const story = makeStory({
51
+ detailedParam: { description: "d" },
52
+ resolveDetailed: (surface) => surface === "cli",
53
+ });
54
+ expect(resolveLookupDetailed(story, "cli", {})).toBe(true);
55
+ expect(resolveLookupDetailed(story, "mcp", {})).toBe(false);
56
+ });
57
+ });
@@ -0,0 +1,27 @@
1
+ import type { LookupStory } from "./types.js";
2
+
3
+ /**
4
+ * Resolve the effective `detailed` flag for a lookup story on a surface.
5
+ *
6
+ * Defaults follow the ratified surface contract: the CLI opts in
7
+ * (`detailed` is false unless set) while MCP opts out (`detailed` is true
8
+ * unless set, since agents want full data by default). Stories without a
9
+ * `detailed` parameter are never detailed. A story's `resolveDetailed`
10
+ * override wins — block uses it to let aspect flags imply detail.
11
+ */
12
+ export default function resolveLookupDetailed<TDetailed, TFmtInput>(
13
+ story: LookupStory<TDetailed, TFmtInput>,
14
+ surface: "cli" | "mcp",
15
+ params: Record<string, unknown>,
16
+ ): boolean {
17
+ if (story.resolveDetailed) {
18
+ return story.resolveDetailed(surface, params);
19
+ }
20
+ if (!story.detailedParam) {
21
+ return false;
22
+ }
23
+ if (surface === "mcp") {
24
+ return ((params.detailed as boolean | undefined) ?? true) === true;
25
+ }
26
+ return params.detailed === true;
27
+ }
@@ -0,0 +1,171 @@
1
+ /**
2
+ * Read-story spec types — one declaration per read story, many projections.
3
+ *
4
+ * A read story (list, show, or lookup) is declared once per domain and
5
+ * compiled into both surfaces: a cli-core `CommandDefinition` and a
6
+ * declarative MCP `ToolSpec`. The compilers in this folder own the shared
7
+ * execute skeletons — parameter projection, condensed rendering with token
8
+ * estimates, empty-result guards, and summary projection — so a domain
9
+ * supplies only its facts: descriptions, parameters, resolve function,
10
+ * formatters, and envelope mapping. This kills the per-domain duplication
11
+ * between `commands/*.ts` and `mcp/specs.ts` for read verbs and makes
12
+ * CLI/MCP parity structural instead of conventional.
13
+ */
14
+
15
+ import type { CommandContext } from "@canonical/cli-core";
16
+ import type { PragmaError } from "#error";
17
+ import type { LookupResult } from "../contracts.js";
18
+ import type { Formatters } from "../formatters.js";
19
+ import type { PragmaRuntime } from "../types/index.js";
20
+
21
+ /** Surfaces a story parameter is projected to (default: "both"). */
22
+ export type StorySurface = "both" | "cli" | "mcp";
23
+
24
+ /**
25
+ * Neutral parameter definition projected to a CLI flag and an MCP tool
26
+ * parameter. `string[]` becomes a CLI multiselect and an MCP string array.
27
+ */
28
+ export interface StoryParam {
29
+ readonly name: string;
30
+ readonly type: "string" | "boolean" | "string[]";
31
+ /** CLI help text; also the MCP description unless `toolDescription` is set. */
32
+ readonly description: string;
33
+ /** MCP-specific description override. */
34
+ readonly toolDescription?: string;
35
+ readonly required?: boolean;
36
+ /** CLI default value — MCP parameters carry no defaults. */
37
+ readonly default?: unknown;
38
+ /** Allowed values; projects to CLI select choices and an MCP enum. */
39
+ readonly enum?: readonly string[];
40
+ /** If true, the CLI accepts this parameter as a positional argument. */
41
+ readonly positional?: boolean;
42
+ /** Surfaces this parameter appears on (default: "both"). */
43
+ readonly surfaces?: StorySurface;
44
+ /** CLI tab-completion for this parameter's argument. */
45
+ readonly complete?: (
46
+ partial: string,
47
+ ctx: CommandContext,
48
+ ) => Promise<string[]>;
49
+ }
50
+
51
+ /** MCP envelope fragment produced by a story's `toEnvelope`. */
52
+ export interface StoryEnvelope {
53
+ readonly data: unknown;
54
+ readonly meta?: Record<string, unknown>;
55
+ }
56
+
57
+ /**
58
+ * A collection-or-singleton read story (`<noun> list`, `<noun> show`).
59
+ *
60
+ * `TData` is the domain's resolved payload; `TOutput` is the formatter and
61
+ * renderer input derived from it via `toOutput`.
62
+ */
63
+ export interface ReadStory<TData, TOutput> {
64
+ readonly noun: string;
65
+ /** Read verb this story projects to (e.g. "list", "show"). */
66
+ readonly verb: string;
67
+ /** CLI one-line help description. */
68
+ readonly description: string;
69
+ /** MCP tool description. */
70
+ readonly toolDescription: string;
71
+ readonly params: readonly StoryParam[];
72
+ /** Usage examples shown in CLI help. */
73
+ readonly examples: readonly string[];
74
+ /** Fetch the story's data. Shared verbatim by both surfaces. */
75
+ readonly resolve: (
76
+ rt: PragmaRuntime,
77
+ params: Record<string, unknown>,
78
+ ) => Promise<TData>;
79
+ /** Map resolved data to the formatter/renderer input. */
80
+ readonly toOutput: (data: TData, params: Record<string, unknown>) => TOutput;
81
+ readonly formatters: Formatters<TOutput>;
82
+ /** Map resolved data to the MCP `{ data, meta }` envelope. */
83
+ readonly toEnvelope: (data: TData) => StoryEnvelope;
84
+ /**
85
+ * CLI-only pre-resolve guard: return an error to reject invalid
86
+ * parameters (e.g. an empty required positional).
87
+ */
88
+ readonly guardParams?: (
89
+ params: Record<string, unknown>,
90
+ ) => PragmaError | undefined;
91
+ /**
92
+ * CLI-only post-resolve guard: return an error to reject an empty
93
+ * result set with a recovery hint.
94
+ */
95
+ readonly emptyError?: (
96
+ data: TData,
97
+ params: Record<string, unknown>,
98
+ ) => PragmaError | undefined;
99
+ /** Optional interactive (ink) renderer for the CLI. */
100
+ readonly renderInk?: (output: TOutput) => unknown;
101
+ }
102
+
103
+ /** Per-invocation view state passed to lookup story hooks. */
104
+ export interface LookupStoryView {
105
+ readonly surface: "cli" | "mcp";
106
+ /** Effective detail level after surface defaults and flag implications. */
107
+ readonly detailed: boolean;
108
+ readonly params: Record<string, unknown>;
109
+ }
110
+
111
+ /**
112
+ * A batch retrieval story (`<noun> lookup <names...>`).
113
+ *
114
+ * `TDetailed` is the looked-up entity; `TFmtInput` is the per-entity
115
+ * formatter input derived via `toFmtInput` (some domains pair the entity
116
+ * with view flags such as `detailed` or aspect selections).
117
+ */
118
+ export interface LookupStory<TDetailed, TFmtInput> {
119
+ readonly noun: string;
120
+ readonly description: string;
121
+ readonly toolDescription: string;
122
+ /** CLI description of the `names` positional parameter. */
123
+ readonly namesDescription: string;
124
+ /** MCP-specific `names` description override. */
125
+ readonly namesToolDescription?: string;
126
+ /** CLI tab-completion for names. */
127
+ readonly complete?: (
128
+ partial: string,
129
+ ctx: CommandContext,
130
+ ) => Promise<string[]>;
131
+ /**
132
+ * The `detailed` toggle, when the story supports one. MCP defaults
133
+ * `detailed` to true; the CLI defaults it to false.
134
+ */
135
+ readonly detailedParam?: {
136
+ readonly description: string;
137
+ readonly toolDescription?: string;
138
+ };
139
+ /** Additional story parameters beyond `names` and `detailed`. */
140
+ readonly params?: readonly StoryParam[];
141
+ /** Named groups for organizing CLI parameters in help output. */
142
+ readonly parameterGroups?: Readonly<Record<string, readonly string[]>>;
143
+ readonly examples: readonly string[];
144
+ /** Resolve all queries, collecting per-query failures. */
145
+ readonly resolve: (
146
+ rt: PragmaRuntime,
147
+ names: readonly string[],
148
+ params: Record<string, unknown>,
149
+ ) => Promise<LookupResult<TDetailed>>;
150
+ /**
151
+ * Effective `detailed` for a surface. Defaults to the plain flag value
152
+ * (CLI: false unless set; MCP: true unless set). Domains override this
153
+ * when other flags imply detail (e.g. block aspect filters).
154
+ */
155
+ readonly resolveDetailed?: (
156
+ surface: "cli" | "mcp",
157
+ params: Record<string, unknown>,
158
+ ) => boolean;
159
+ /** Map an entity to the per-entity formatter input. */
160
+ readonly toFmtInput: (entity: TDetailed, view: LookupStoryView) => TFmtInput;
161
+ readonly formatters: Formatters<TFmtInput>;
162
+ /** MCP summary projection applied when `detailed` is false. */
163
+ readonly project?: (entity: TDetailed) => unknown;
164
+ /** CLI-only error thrown when no names are supplied. */
165
+ readonly emptyNamesError?: () => PragmaError;
166
+ /** Optional interactive (ink) renderer for the CLI. */
167
+ readonly renderInk?: (
168
+ result: LookupResult<TDetailed>,
169
+ view: LookupStoryView,
170
+ ) => unknown;
171
+ }
@@ -0,0 +1,43 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { describe, expect, it } from "vitest";
3
+ import { createTestStore, DS_ALL_TTL } from "#testing";
4
+ import detectCrossDomain from "./detectCrossDomain.js";
5
+
6
+ let store: Store;
7
+ let cleanup: () => void;
8
+
9
+ describe("detectCrossDomain", () => {
10
+ beforeAll(async () => {
11
+ const result = await createTestStore({ ttl: DS_ALL_TTL });
12
+ store = result.store;
13
+ cleanup = result.cleanup;
14
+ });
15
+
16
+ afterAll(() => {
17
+ cleanup();
18
+ });
19
+
20
+ it("T2-4: returns undefined when name is not in any domain", async () => {
21
+ const hint = await detectCrossDomain("nonexistent_xyz", "token", store);
22
+ expect(hint).toBeUndefined();
23
+ });
24
+
25
+ it("returns a hint with the correct shape", async () => {
26
+ const hint = await detectCrossDomain("Button", "modifier", store);
27
+ if (hint) {
28
+ expect(hint).toHaveProperty("domain");
29
+ expect(hint).toHaveProperty("entityType");
30
+ expect(hint).toHaveProperty("cli");
31
+ expect(hint).toHaveProperty("mcp");
32
+ expect(hint.mcp).toHaveProperty("tool");
33
+ expect(hint.mcp).toHaveProperty("params");
34
+ }
35
+ });
36
+
37
+ it("skips the current domain", async () => {
38
+ const hint = await detectCrossDomain("Button", "block", store);
39
+ if (hint) {
40
+ expect(hint.domain).not.toBe("block");
41
+ }
42
+ });
43
+ });
@@ -0,0 +1,116 @@
1
+ import type { SPARQL, Store } from "@canonical/ke";
2
+ import { escapeSparqlValue } from "@canonical/ke";
3
+ import { buildQuery } from "../buildQuery.js";
4
+ import { P } from "../prefixes.js";
5
+
6
+ /**
7
+ * Structured hint pointing the user/agent to the correct domain
8
+ * when a name exists in a different domain than the one queried.
9
+ */
10
+ export interface CrossDomainHint {
11
+ /** Domain where the name was found (e.g., "modifier"). */
12
+ readonly domain: string;
13
+ /** Entity type label for messages (e.g., "modifier"). */
14
+ readonly entityType: string;
15
+ /** CLI recovery command. */
16
+ readonly cli: string;
17
+ /** MCP recovery tool + params. */
18
+ readonly mcp: { tool: string; params: Record<string, unknown> };
19
+ }
20
+
21
+ interface DomainSpec {
22
+ readonly domain: string;
23
+ readonly entityType: string;
24
+ readonly lookupTool: string;
25
+ readonly cliCommand: string;
26
+ readonly query: (name: string) => SPARQL<string>;
27
+ }
28
+
29
+ const DOMAIN_SPECS: readonly DomainSpec[] = [
30
+ {
31
+ domain: "block",
32
+ entityType: "block",
33
+ lookupTool: "block_lookup",
34
+ cliCommand: "pragma block lookup",
35
+ query: (name) =>
36
+ buildQuery(`
37
+ ASK {
38
+ VALUES ?type { ${P.ds}Component ${P.ds}Pattern ${P.ds}Layout ${P.ds}Subcomponent }
39
+ ?s a ?type ; ${P.ds}name ?name .
40
+ FILTER(LCASE(STR(?name)) = LCASE(${escapeSparqlValue(name)}))
41
+ }
42
+ `),
43
+ },
44
+ {
45
+ domain: "token",
46
+ entityType: "token",
47
+ lookupTool: "token_lookup",
48
+ cliCommand: "pragma token lookup",
49
+ query: (name) =>
50
+ buildQuery(`
51
+ ASK {
52
+ ?s a ${P.ds}Token ; ${P.ds}tokenId ?id .
53
+ FILTER(LCASE(STR(?id)) = LCASE(${escapeSparqlValue(name)}))
54
+ }
55
+ `),
56
+ },
57
+ {
58
+ domain: "modifier",
59
+ entityType: "modifier",
60
+ lookupTool: "modifier_lookup",
61
+ cliCommand: "pragma modifier lookup",
62
+ query: (name) =>
63
+ buildQuery(`
64
+ ASK {
65
+ ?s a ${P.ds}ModifierFamily ; ${P.ds}name ?name .
66
+ FILTER(LCASE(STR(?name)) = LCASE(${escapeSparqlValue(name)}))
67
+ }
68
+ `),
69
+ },
70
+ {
71
+ domain: "standard",
72
+ entityType: "standard",
73
+ lookupTool: "standard_lookup",
74
+ cliCommand: "pragma standard lookup",
75
+ query: (name) =>
76
+ buildQuery(`
77
+ ASK {
78
+ ?s a ${P.cs}CodeStandard ; ${P.cs}name ?name .
79
+ FILTER(LCASE(STR(?name)) = LCASE(${escapeSparqlValue(name)}))
80
+ }
81
+ `),
82
+ },
83
+ ];
84
+
85
+ /**
86
+ * Check if a name exists in a domain other than the current one.
87
+ *
88
+ * Queries each domain in a deterministic order (block → token → modifier → standard)
89
+ * and returns the first match. Returns undefined if the name is not found anywhere.
90
+ *
91
+ * @param name - The user-supplied name.
92
+ * @param currentDomain - The domain the user queried (e.g., "token").
93
+ * @param store - ke store for cross-domain queries.
94
+ * @returns Hint pointing to the correct domain, or undefined.
95
+ */
96
+ export default async function detectCrossDomain(
97
+ name: string,
98
+ currentDomain: string,
99
+ store: Store,
100
+ ): Promise<CrossDomainHint | undefined> {
101
+ for (const spec of DOMAIN_SPECS) {
102
+ if (spec.domain === currentDomain) continue;
103
+
104
+ const result = await store.query(spec.query(name));
105
+ if (result.type === "ask" && result.result) {
106
+ return {
107
+ domain: spec.domain,
108
+ entityType: spec.entityType,
109
+ cli: `${spec.cliCommand} ${name}`,
110
+ mcp: { tool: spec.lookupTool, params: { names: [name] } },
111
+ };
112
+ }
113
+ }
114
+
115
+ return undefined;
116
+ }
@@ -0,0 +1,92 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import expandGlob, { isGlobPattern } from "./expandGlob.js";
3
+
4
+ const standardNames = [
5
+ "react.component.props",
6
+ "react.component.structure.folder",
7
+ "react.component.barrel_exports",
8
+ "react.hooks.naming",
9
+ "css.selectors.namespace",
10
+ "css.selectors.specificity",
11
+ "css.component.encapsulation",
12
+ "git.branch.name",
13
+ "git.commit.message",
14
+ ];
15
+
16
+ describe("expandGlob", () => {
17
+ it("T5-1: expands react.component.* to all matching entries", () => {
18
+ const result = expandGlob("react.component.*", standardNames);
19
+ expect(result).toContain("react.component.props");
20
+ expect(result).toContain("react.component.structure.folder");
21
+ expect(result).toContain("react.component.barrel_exports");
22
+ expect(result).not.toContain("react.hooks.naming");
23
+ expect(result).toHaveLength(3);
24
+ });
25
+
26
+ it("T5-2: expands css.* to all CSS entries", () => {
27
+ const result = expandGlob("css.*", standardNames);
28
+ expect(result).toHaveLength(3);
29
+ expect(result).toContain("css.selectors.namespace");
30
+ });
31
+
32
+ it("T5-3: expands prefix wildcard against plain names", () => {
33
+ const names = ["NavigationBar", "NavigationMenu", "Button"];
34
+ expect(expandGlob("Nav*", names)).toEqual([
35
+ "NavigationBar",
36
+ "NavigationMenu",
37
+ ]);
38
+ });
39
+
40
+ it("T5-4: full wildcard returns all candidates", () => {
41
+ expect(expandGlob("*", ["A", "B", "C"])).toEqual(["A", "B", "C"]);
42
+ });
43
+
44
+ it("T5-5: unmatched glob returns empty", () => {
45
+ expect(expandGlob("nonexistent.*", standardNames)).toEqual([]);
46
+ });
47
+
48
+ it("T5-6: non-glob name passes through unchanged", () => {
49
+ expect(expandGlob("Button", ["Button", "Card"])).toEqual(["Button"]);
50
+ });
51
+
52
+ it("T5-7: trailing dot treated as prefix glob", () => {
53
+ const withDot = expandGlob("react.component.", standardNames);
54
+ const withStar = expandGlob("react.component.*", standardNames);
55
+ expect(withDot).toEqual(withStar);
56
+ });
57
+
58
+ it("T5-8: suffix wildcard matches end of name", () => {
59
+ const result = expandGlob("*.props", [
60
+ "react.component.props",
61
+ "react.component.props.wrapper",
62
+ "css.selectors.namespace",
63
+ ]);
64
+ expect(result).toEqual(["react.component.props"]);
65
+ });
66
+
67
+ it("matches case-insensitively", () => {
68
+ expect(expandGlob("REACT.*", standardNames)).toHaveLength(4);
69
+ });
70
+ });
71
+
72
+ describe("isGlobPattern", () => {
73
+ it("T5-9: detects star pattern", () => {
74
+ expect(isGlobPattern("react.component.*")).toBe(true);
75
+ });
76
+
77
+ it("T5-10: detects prefix star", () => {
78
+ expect(isGlobPattern("Nav*")).toBe(true);
79
+ });
80
+
81
+ it("T5-11: detects trailing dot", () => {
82
+ expect(isGlobPattern("react.component.")).toBe(true);
83
+ });
84
+
85
+ it("T5-12: rejects plain name", () => {
86
+ expect(isGlobPattern("Button")).toBe(false);
87
+ });
88
+
89
+ it("T5-13: rejects empty string", () => {
90
+ expect(isGlobPattern("")).toBe(false);
91
+ });
92
+ });
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Expand a glob pattern against a list of known names.
3
+ *
4
+ * Supports:
5
+ * - `*` anywhere: wildcard matching (`react.component.*`, `Nav*`, `*.props`)
6
+ * - Trailing `.`: shorthand for `.*` (`react.component.` = `react.component.*`)
7
+ *
8
+ * Non-glob names are returned unchanged (wrapped in a single-element array)
9
+ * so callers can pass the result directly to the lookup pipeline.
10
+ *
11
+ * @param pattern - User-supplied name, possibly containing glob characters.
12
+ * @param candidates - All known names in the domain.
13
+ * @returns Expanded concrete names, or empty array if no matches.
14
+ */
15
+ export default function expandGlob(
16
+ pattern: string,
17
+ candidates: readonly string[],
18
+ ): string[] {
19
+ if (!isGlobPattern(pattern)) {
20
+ return [pattern];
21
+ }
22
+
23
+ const normalized = pattern.endsWith(".") ? `${pattern}*` : pattern;
24
+ const regex = globToRegex(normalized);
25
+
26
+ return candidates.filter((name) => regex.test(name));
27
+ }
28
+
29
+ /**
30
+ * Check whether a string contains glob meta-characters.
31
+ *
32
+ * A string is a glob pattern if it contains `*` or ends with `.` (trailing dot).
33
+ */
34
+ export function isGlobPattern(name: string): boolean {
35
+ if (name === "") return false;
36
+ return name.includes("*") || name.endsWith(".");
37
+ }
38
+
39
+ function globToRegex(glob: string): RegExp {
40
+ let pattern = "";
41
+ for (const char of glob) {
42
+ if (char === "*") {
43
+ pattern += ".*";
44
+ // biome-ignore lint/suspicious/noTemplateCurlyInString: regex meta-characters to escape, not a JS template
45
+ } else if (".+^${}()|[]\\".includes(char)) {
46
+ pattern += `\\${char}`;
47
+ } else {
48
+ pattern += char;
49
+ }
50
+ }
51
+ return new RegExp(`^${pattern}$`, "i");
52
+ }
@@ -0,0 +1,57 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import expandGlob, { isGlobPattern } from "./expandGlob.js";
3
+ import type { Domain } from "./listDomainNames.js";
4
+ import listDomainNames from "./listDomainNames.js";
5
+
6
+ interface ExpandedQueries {
7
+ /** Concrete names to pass to lookupMany. */
8
+ readonly names: string[];
9
+ /** Errors for glob patterns that matched nothing. */
10
+ readonly globErrors: readonly {
11
+ query: string;
12
+ code: string;
13
+ message: string;
14
+ }[];
15
+ }
16
+
17
+ /**
18
+ * Partition lookup queries into concrete names and expanded globs.
19
+ *
20
+ * Literal names pass through unchanged. Glob patterns are expanded against
21
+ * the domain's full name list. Globs that match nothing produce an
22
+ * EMPTY_RESULTS error entry.
23
+ */
24
+ export default async function expandLookupQueries(
25
+ queries: readonly string[],
26
+ store: Store,
27
+ domain: Domain,
28
+ ): Promise<ExpandedQueries> {
29
+ const hasGlobs = queries.some(isGlobPattern);
30
+ if (!hasGlobs) {
31
+ return { names: [...queries], globErrors: [] };
32
+ }
33
+
34
+ const allNames = await listDomainNames(store, domain);
35
+ const names: string[] = [];
36
+ const globErrors: { query: string; code: string; message: string }[] = [];
37
+
38
+ for (const query of queries) {
39
+ if (!isGlobPattern(query)) {
40
+ names.push(query);
41
+ continue;
42
+ }
43
+
44
+ const expanded = expandGlob(query, allNames);
45
+ if (expanded.length === 0) {
46
+ globErrors.push({
47
+ query,
48
+ code: "EMPTY_RESULTS",
49
+ message: `No ${domain}s matching "${query}".`,
50
+ });
51
+ } else {
52
+ names.push(...expanded);
53
+ }
54
+ }
55
+
56
+ return { names, globErrors };
57
+ }
@@ -0,0 +1,7 @@
1
+ export type { CrossDomainHint } from "./detectCrossDomain.js";
2
+ export { default as detectCrossDomain } from "./detectCrossDomain.js";
3
+ export { default as expandGlob, isGlobPattern } from "./expandGlob.js";
4
+ export { default as expandLookupQueries } from "./expandLookupQueries.js";
5
+ export type { Domain } from "./listDomainNames.js";
6
+ export { default as listDomainNames } from "./listDomainNames.js";
7
+ export { default as suggestNames } from "./suggestNames.js";
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Lightweight name queries for each domain.
3
+ *
4
+ * Used by suggestNames to fetch candidate lists when a lookup fails.
5
+ * Each query returns only names (no detail) for minimal overhead.
6
+ */
7
+
8
+ import type { SPARQL, Store } from "@canonical/ke";
9
+ import { buildQuery } from "../buildQuery.js";
10
+ import { P } from "../prefixes.js";
11
+
12
+ type Domain = "block" | "token" | "modifier" | "standard" | "ontology";
13
+
14
+ const QUERIES: Record<Domain, SPARQL<string>> = {
15
+ block: buildQuery(`
16
+ SELECT DISTINCT ?name WHERE {
17
+ VALUES ?type { ${P.ds}Component ${P.ds}Pattern ${P.ds}Layout ${P.ds}Subcomponent }
18
+ ?s a ?type ; ${P.ds}name ?name .
19
+ }
20
+ `),
21
+ token: buildQuery(`
22
+ SELECT DISTINCT ?name WHERE {
23
+ ?s a ${P.ds}Token ; ${P.ds}tokenId ?name .
24
+ }
25
+ `),
26
+ modifier: buildQuery(`
27
+ SELECT DISTINCT ?name WHERE {
28
+ ?s a ${P.ds}ModifierFamily ; ${P.ds}name ?name .
29
+ }
30
+ `),
31
+ standard: buildQuery(`
32
+ SELECT DISTINCT ?name WHERE {
33
+ ?s a ${P.cs}CodeStandard ; ${P.cs}name ?name .
34
+ }
35
+ `),
36
+ ontology: buildQuery(`
37
+ SELECT DISTINCT ?name WHERE {
38
+ ?s a owl:Ontology .
39
+ BIND(STR(?s) AS ?name)
40
+ }
41
+ `),
42
+ };
43
+
44
+ /**
45
+ * Fetch all entity names for a domain from the store.
46
+ *
47
+ * @note - This function is impure — it queries the ke store.
48
+ */
49
+ export default async function listDomainNames(
50
+ store: Store,
51
+ domain: Domain,
52
+ ): Promise<string[]> {
53
+ const query = QUERIES[domain];
54
+ const result = await store.query(query);
55
+ if (result.type !== "select") return [];
56
+ return result.bindings
57
+ .map((b) => b.name)
58
+ .filter((n): n is string => typeof n === "string" && n !== "");
59
+ }
60
+
61
+ export type { Domain };