@canonical/pragma-cli 0.18.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 (439) hide show
  1. package/README.md +377 -0
  2. package/package.json +78 -0
  3. package/src/bin.test.ts +52 -0
  4. package/src/bin.ts +19 -0
  5. package/src/compile-validation.runner.ts +137 -0
  6. package/src/compile-validation.ts +192 -0
  7. package/src/completions/computeSocketPath.test.ts +26 -0
  8. package/src/completions/computeSocketPath.ts +16 -0
  9. package/src/completions/constants.ts +18 -0
  10. package/src/completions/handleQuery.test.ts +93 -0
  11. package/src/completions/handleQuery.ts +30 -0
  12. package/src/completions/index.ts +14 -0
  13. package/src/completions/queryCompletions.test.ts +100 -0
  14. package/src/completions/queryCompletions.ts +61 -0
  15. package/src/completions/querySocket.test.ts +70 -0
  16. package/src/completions/querySocket.ts +44 -0
  17. package/src/completions/startCompletionsServer.test.ts +74 -0
  18. package/src/completions/startCompletionsServer.ts +75 -0
  19. package/src/completions/startCompletionsServer.unit.test.ts +131 -0
  20. package/src/completions/waitForSocket.test.ts +27 -0
  21. package/src/completions/waitForSocket.ts +27 -0
  22. package/src/config/configExists.ts +14 -0
  23. package/src/config/index.ts +6 -0
  24. package/src/config/readConfig.test.ts +152 -0
  25. package/src/config/readConfig.ts +64 -0
  26. package/src/config/resolveConfigPath.test.ts +20 -0
  27. package/src/config/resolveConfigPath.ts +11 -0
  28. package/src/config/types.ts +26 -0
  29. package/src/config/writeConfig.ts +49 -0
  30. package/src/constants.ts +78 -0
  31. package/src/domains/block/blockConfig.ts +53 -0
  32. package/src/domains/block/commands/index.ts +4 -0
  33. package/src/domains/block/commands/list.test.ts +111 -0
  34. package/src/domains/block/commands/list.ts +69 -0
  35. package/src/domains/block/commands/lookup.test.ts +187 -0
  36. package/src/domains/block/commands/lookup.ts +199 -0
  37. package/src/domains/block/formatters/index.ts +4 -0
  38. package/src/domains/block/formatters/list.test.ts +107 -0
  39. package/src/domains/block/formatters/list.ts +64 -0
  40. package/src/domains/block/formatters/lookup.test.ts +317 -0
  41. package/src/domains/block/formatters/lookup.ts +354 -0
  42. package/src/domains/block/formatters/types.ts +16 -0
  43. package/src/domains/block/helpers/index.ts +4 -0
  44. package/src/domains/block/helpers/resolveAspects.test.ts +64 -0
  45. package/src/domains/block/helpers/resolveAspects.ts +35 -0
  46. package/src/domains/block/index.ts +25 -0
  47. package/src/domains/block/mcp/index.ts +2 -0
  48. package/src/domains/block/mcp/specs.ts +199 -0
  49. package/src/domains/block/operations/index.ts +4 -0
  50. package/src/domains/block/operations/list.test.ts +80 -0
  51. package/src/domains/block/operations/list.ts +72 -0
  52. package/src/domains/block/operations/lookup.test.ts +108 -0
  53. package/src/domains/block/operations/lookup.ts +359 -0
  54. package/src/domains/block/orchestration/blockEmptyError.ts +19 -0
  55. package/src/domains/block/orchestration/buildBlockFilters.ts +12 -0
  56. package/src/domains/block/orchestration/enrichBlocks.ts +37 -0
  57. package/src/domains/block/orchestration/index.ts +5 -0
  58. package/src/domains/block/orchestration/resolveBlockList.ts +39 -0
  59. package/src/domains/block/orchestration/resolveBlockLookup.ts +21 -0
  60. package/src/domains/block/types.ts +30 -0
  61. package/src/domains/config/commands/channel.test.ts +101 -0
  62. package/src/domains/config/commands/channel.ts +80 -0
  63. package/src/domains/config/commands/index.ts +4 -0
  64. package/src/domains/config/commands/show.test.ts +61 -0
  65. package/src/domains/config/commands/show.ts +51 -0
  66. package/src/domains/config/commands/tier.test.ts +84 -0
  67. package/src/domains/config/commands/tier.ts +78 -0
  68. package/src/domains/config/formatters/channel.test.ts +56 -0
  69. package/src/domains/config/formatters/channel.ts +34 -0
  70. package/src/domains/config/formatters/index.ts +4 -0
  71. package/src/domains/config/formatters/show.test.ts +126 -0
  72. package/src/domains/config/formatters/show.ts +66 -0
  73. package/src/domains/config/formatters/tier.test.ts +70 -0
  74. package/src/domains/config/formatters/tier.ts +40 -0
  75. package/src/domains/config/index.ts +23 -0
  76. package/src/domains/config/mcp/index.ts +2 -0
  77. package/src/domains/config/mcp/specs.ts +108 -0
  78. package/src/domains/config/operations/index.ts +5 -0
  79. package/src/domains/config/operations/show.test.ts +60 -0
  80. package/src/domains/config/operations/show.ts +42 -0
  81. package/src/domains/config/operations/types.ts +13 -0
  82. package/src/domains/config/operations/validateChannel.test.ts +36 -0
  83. package/src/domains/config/operations/validateChannel.ts +24 -0
  84. package/src/domains/config/operations/validateTier.test.ts +56 -0
  85. package/src/domains/config/operations/validateTier.ts +35 -0
  86. package/src/domains/create/commands/component.test.ts +166 -0
  87. package/src/domains/create/commands/component.ts +129 -0
  88. package/src/domains/create/commands/index.ts +3 -0
  89. package/src/domains/create/commands/package.test.ts +35 -0
  90. package/src/domains/create/commands/package.ts +21 -0
  91. package/src/domains/create/index.ts +15 -0
  92. package/src/domains/create/mcp/index.ts +2 -0
  93. package/src/domains/create/mcp/specs.ts +174 -0
  94. package/src/domains/doctor/commands/doctor.test.ts +75 -0
  95. package/src/domains/doctor/commands/doctor.ts +43 -0
  96. package/src/domains/doctor/commands/index.ts +2 -0
  97. package/src/domains/doctor/formatters/doctor.test.ts +117 -0
  98. package/src/domains/doctor/formatters/doctor.ts +84 -0
  99. package/src/domains/doctor/formatters/index.ts +2 -0
  100. package/src/domains/doctor/index.ts +9 -0
  101. package/src/domains/doctor/mcp/index.ts +2 -0
  102. package/src/domains/doctor/mcp/specs.ts +88 -0
  103. package/src/domains/doctor/operations/checks/checkConfigFile.ts +28 -0
  104. package/src/domains/doctor/operations/checks/checkKeStore.ts +38 -0
  105. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +65 -0
  106. package/src/domains/doctor/operations/checks/checkNodeVersion.ts +25 -0
  107. package/src/domains/doctor/operations/checks/checkPragmaVersion.ts +20 -0
  108. package/src/domains/doctor/operations/checks/checkShellCompletions.ts +42 -0
  109. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +64 -0
  110. package/src/domains/doctor/operations/checks/index.ts +8 -0
  111. package/src/domains/doctor/operations/checks.test.ts +151 -0
  112. package/src/domains/doctor/operations/index.ts +3 -0
  113. package/src/domains/doctor/operations/runChecks.test.ts +89 -0
  114. package/src/domains/doctor/operations/runChecks.ts +38 -0
  115. package/src/domains/doctor/operations/types.ts +20 -0
  116. package/src/domains/graph/commands/index.ts +4 -0
  117. package/src/domains/graph/commands/inspect.test.ts +94 -0
  118. package/src/domains/graph/commands/inspect.ts +63 -0
  119. package/src/domains/graph/commands/query.test.ts +92 -0
  120. package/src/domains/graph/commands/query.ts +60 -0
  121. package/src/domains/graph/formatters/index.ts +4 -0
  122. package/src/domains/graph/formatters/inspect.ts +45 -0
  123. package/src/domains/graph/formatters/query.ts +37 -0
  124. package/src/domains/graph/helpers/index.ts +3 -0
  125. package/src/domains/graph/helpers/resolveUri.ts +70 -0
  126. package/src/domains/graph/index.ts +22 -0
  127. package/src/domains/graph/mcp/index.ts +2 -0
  128. package/src/domains/graph/mcp/specs.ts +82 -0
  129. package/src/domains/graph/operations/executeQuery.test.ts +68 -0
  130. package/src/domains/graph/operations/executeQuery.ts +32 -0
  131. package/src/domains/graph/operations/index.ts +4 -0
  132. package/src/domains/graph/operations/inspectUri.test.ts +101 -0
  133. package/src/domains/graph/operations/inspectUri.ts +61 -0
  134. package/src/domains/info/commands/index.ts +4 -0
  135. package/src/domains/info/commands/info.test.ts +88 -0
  136. package/src/domains/info/commands/info.ts +47 -0
  137. package/src/domains/info/commands/upgrade.test.ts +183 -0
  138. package/src/domains/info/commands/upgrade.ts +150 -0
  139. package/src/domains/info/constants.test.ts +22 -0
  140. package/src/domains/info/constants.ts +15 -0
  141. package/src/domains/info/formatters/index.ts +8 -0
  142. package/src/domains/info/formatters/info.test.ts +173 -0
  143. package/src/domains/info/formatters/info.ts +116 -0
  144. package/src/domains/info/formatters/upgrade.test.ts +133 -0
  145. package/src/domains/info/formatters/upgrade.ts +68 -0
  146. package/src/domains/info/index.ts +7 -0
  147. package/src/domains/info/operations/checkRegistryVersion.test.ts +124 -0
  148. package/src/domains/info/operations/checkRegistryVersion.ts +45 -0
  149. package/src/domains/info/operations/collectInfo.test.ts +111 -0
  150. package/src/domains/info/operations/collectInfo.ts +68 -0
  151. package/src/domains/info/operations/collectStoreSummary.test.ts +61 -0
  152. package/src/domains/info/operations/collectStoreSummary.ts +42 -0
  153. package/src/domains/info/operations/index.ts +5 -0
  154. package/src/domains/info/types.test-d.ts +35 -0
  155. package/src/domains/info/types.ts +46 -0
  156. package/src/domains/llm/commands/index.ts +3 -0
  157. package/src/domains/llm/commands/llm.ts +37 -0
  158. package/src/domains/llm/data/commandReference.ts +25 -0
  159. package/src/domains/llm/data/decisionTrees.ts +46 -0
  160. package/src/domains/llm/data/index.ts +4 -0
  161. package/src/domains/llm/formatters/index.ts +3 -0
  162. package/src/domains/llm/formatters/orientation.ts +53 -0
  163. package/src/domains/llm/index.ts +8 -0
  164. package/src/domains/llm/llmCommand.test.ts +93 -0
  165. package/src/domains/llm/mcp/index.ts +2 -0
  166. package/src/domains/llm/mcp/specs.ts +87 -0
  167. package/src/domains/llm/operations/collectContext.ts +47 -0
  168. package/src/domains/llm/operations/index.ts +3 -0
  169. package/src/domains/llm/types.ts +32 -0
  170. package/src/domains/modifier/commands/index.ts +4 -0
  171. package/src/domains/modifier/commands/list.test.ts +57 -0
  172. package/src/domains/modifier/commands/list.ts +33 -0
  173. package/src/domains/modifier/commands/lookup.test.ts +91 -0
  174. package/src/domains/modifier/commands/lookup.ts +105 -0
  175. package/src/domains/modifier/formatters/index.ts +4 -0
  176. package/src/domains/modifier/formatters/list.test.ts +38 -0
  177. package/src/domains/modifier/formatters/list.ts +35 -0
  178. package/src/domains/modifier/formatters/lookup.test.ts +33 -0
  179. package/src/domains/modifier/formatters/lookup.ts +38 -0
  180. package/src/domains/modifier/index.ts +22 -0
  181. package/src/domains/modifier/mcp/index.ts +2 -0
  182. package/src/domains/modifier/mcp/specs.ts +97 -0
  183. package/src/domains/modifier/operations/index.ts +4 -0
  184. package/src/domains/modifier/operations/list.test.ts +39 -0
  185. package/src/domains/modifier/operations/list.ts +41 -0
  186. package/src/domains/modifier/operations/lookup.test.ts +37 -0
  187. package/src/domains/modifier/operations/lookup.ts +57 -0
  188. package/src/domains/modifier/orchestration/index.ts +3 -0
  189. package/src/domains/modifier/orchestration/modifierEmptyError.ts +10 -0
  190. package/src/domains/modifier/orchestration/resolveModifierList.ts +16 -0
  191. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +20 -0
  192. package/src/domains/modifier/types.ts +5 -0
  193. package/src/domains/ontology/commands/index.ts +4 -0
  194. package/src/domains/ontology/commands/list.test.ts +82 -0
  195. package/src/domains/ontology/commands/list.ts +46 -0
  196. package/src/domains/ontology/commands/show.test.ts +103 -0
  197. package/src/domains/ontology/commands/show.ts +66 -0
  198. package/src/domains/ontology/formatters/index.ts +4 -0
  199. package/src/domains/ontology/formatters/list.ts +37 -0
  200. package/src/domains/ontology/formatters/show.ts +74 -0
  201. package/src/domains/ontology/helpers/findNamespace.ts +23 -0
  202. package/src/domains/ontology/helpers/index.ts +7 -0
  203. package/src/domains/ontology/helpers/queryClasses.ts +50 -0
  204. package/src/domains/ontology/helpers/queryProperties.ts +57 -0
  205. package/src/domains/ontology/helpers/resolvePrefix.ts +45 -0
  206. package/src/domains/ontology/index.ts +26 -0
  207. package/src/domains/ontology/mcp/index.ts +2 -0
  208. package/src/domains/ontology/mcp/specs.ts +93 -0
  209. package/src/domains/ontology/operations/index.ts +5 -0
  210. package/src/domains/ontology/operations/listOntologies.test.ts +45 -0
  211. package/src/domains/ontology/operations/listOntologies.ts +101 -0
  212. package/src/domains/ontology/operations/showOntology.test.ts +65 -0
  213. package/src/domains/ontology/operations/showOntology.ts +41 -0
  214. package/src/domains/ontology/operations/showOntologyRaw.test.ts +39 -0
  215. package/src/domains/ontology/operations/showOntologyRaw.ts +50 -0
  216. package/src/domains/setup/commands/all.ts +58 -0
  217. package/src/domains/setup/commands/completions.ts +85 -0
  218. package/src/domains/setup/commands/entrypoints.test.ts +170 -0
  219. package/src/domains/setup/commands/index.ts +6 -0
  220. package/src/domains/setup/commands/lsp.ts +49 -0
  221. package/src/domains/setup/commands/mcp.ts +119 -0
  222. package/src/domains/setup/commands/skills.test.ts +64 -0
  223. package/src/domains/setup/commands/skills.ts +106 -0
  224. package/src/domains/setup/formatters/index.ts +2 -0
  225. package/src/domains/setup/formatters/skills.test.ts +103 -0
  226. package/src/domains/setup/formatters/skills.ts +112 -0
  227. package/src/domains/setup/formatters/types.ts +7 -0
  228. package/src/domains/setup/helpers/completionScripts.test.ts +58 -0
  229. package/src/domains/setup/helpers/completionScripts.ts +92 -0
  230. package/src/domains/setup/helpers/constants.ts +2 -0
  231. package/src/domains/setup/helpers/detectShell.test.ts +48 -0
  232. package/src/domains/setup/helpers/detectShell.ts +20 -0
  233. package/src/domains/setup/helpers/index.ts +12 -0
  234. package/src/domains/setup/helpers/runSetupTask.test.ts +279 -0
  235. package/src/domains/setup/helpers/runSetupTask.ts +229 -0
  236. package/src/domains/setup/index.ts +33 -0
  237. package/src/domains/setup/operations/index.ts +6 -0
  238. package/src/domains/setup/operations/setupAll.test.ts +48 -0
  239. package/src/domains/setup/operations/setupAll.ts +41 -0
  240. package/src/domains/setup/operations/setupCompletions.test.ts +94 -0
  241. package/src/domains/setup/operations/setupCompletions.ts +54 -0
  242. package/src/domains/setup/operations/setupLsp.test.ts +29 -0
  243. package/src/domains/setup/operations/setupLsp.ts +17 -0
  244. package/src/domains/setup/operations/setupMcp.test.ts +115 -0
  245. package/src/domains/setup/operations/setupMcp.ts +108 -0
  246. package/src/domains/setup/operations/setupSkills.test.ts +191 -0
  247. package/src/domains/setup/operations/setupSkills.ts +160 -0
  248. package/src/domains/setup/types.ts +18 -0
  249. package/src/domains/shared/ToolSpec.ts +49 -0
  250. package/src/domains/shared/bootStore.ts +77 -0
  251. package/src/domains/shared/buildQuery.ts +18 -0
  252. package/src/domains/shared/compactUri.test.ts +21 -0
  253. package/src/domains/shared/compactUri.ts +16 -0
  254. package/src/domains/shared/context.ts +15 -0
  255. package/src/domains/shared/contracts.ts +113 -0
  256. package/src/domains/shared/extractLocalName.test.ts +21 -0
  257. package/src/domains/shared/extractLocalName.ts +14 -0
  258. package/src/domains/shared/filters/buildChannelFilter.test.ts +52 -0
  259. package/src/domains/shared/filters/buildChannelFilter.ts +41 -0
  260. package/src/domains/shared/filters/buildFilterConfig.ts +16 -0
  261. package/src/domains/shared/filters/buildFilters.test.ts +33 -0
  262. package/src/domains/shared/filters/buildFilters.ts +30 -0
  263. package/src/domains/shared/filters/buildTierFilter.test.ts +78 -0
  264. package/src/domains/shared/filters/buildTierFilter.ts +69 -0
  265. package/src/domains/shared/filters/describeFilters.ts +14 -0
  266. package/src/domains/shared/filters/index.ts +10 -0
  267. package/src/domains/shared/formatters.ts +35 -0
  268. package/src/domains/shared/lookupMany.ts +45 -0
  269. package/src/domains/shared/namespaces.ts +18 -0
  270. package/src/domains/shared/packages.ts +76 -0
  271. package/src/domains/shared/prefixes.ts +37 -0
  272. package/src/domains/shared/runtime.ts +62 -0
  273. package/src/domains/shared/types.test-d.ts +127 -0
  274. package/src/domains/shared/types.ts +348 -0
  275. package/src/domains/skill/commands/index.ts +2 -0
  276. package/src/domains/skill/commands/list.test.ts +111 -0
  277. package/src/domains/skill/commands/list.ts +69 -0
  278. package/src/domains/skill/formatters/index.ts +2 -0
  279. package/src/domains/skill/formatters/list.test.ts +140 -0
  280. package/src/domains/skill/formatters/list.ts +119 -0
  281. package/src/domains/skill/formatters/types.ts +8 -0
  282. package/src/domains/skill/helpers/index.ts +6 -0
  283. package/src/domains/skill/helpers/parseFrontmatter.test.ts +122 -0
  284. package/src/domains/skill/helpers/parseFrontmatter.ts +112 -0
  285. package/src/domains/skill/helpers/resolveSkillSources.ts +34 -0
  286. package/src/domains/skill/index.ts +19 -0
  287. package/src/domains/skill/mcp/index.ts +2 -0
  288. package/src/domains/skill/mcp/specs.ts +38 -0
  289. package/src/domains/skill/operations/discover.test.ts +145 -0
  290. package/src/domains/skill/operations/discover.ts +68 -0
  291. package/src/domains/skill/operations/index.ts +4 -0
  292. package/src/domains/skill/operations/list.test.ts +68 -0
  293. package/src/domains/skill/operations/list.ts +47 -0
  294. package/src/domains/skill/types.ts +25 -0
  295. package/src/domains/standard/commands/categories.test.ts +70 -0
  296. package/src/domains/standard/commands/categories.ts +48 -0
  297. package/src/domains/standard/commands/index.ts +5 -0
  298. package/src/domains/standard/commands/list.test.ts +105 -0
  299. package/src/domains/standard/commands/list.ts +81 -0
  300. package/src/domains/standard/commands/lookup.test.ts +135 -0
  301. package/src/domains/standard/commands/lookup.ts +127 -0
  302. package/src/domains/standard/formatters/categories.test.ts +44 -0
  303. package/src/domains/standard/formatters/categories.ts +38 -0
  304. package/src/domains/standard/formatters/index.ts +6 -0
  305. package/src/domains/standard/formatters/list.test.ts +166 -0
  306. package/src/domains/standard/formatters/list.ts +156 -0
  307. package/src/domains/standard/formatters/lookup.test.ts +95 -0
  308. package/src/domains/standard/formatters/lookup.ts +86 -0
  309. package/src/domains/standard/formatters/types.ts +31 -0
  310. package/src/domains/standard/index.ts +32 -0
  311. package/src/domains/standard/mcp/index.ts +2 -0
  312. package/src/domains/standard/mcp/specs.ts +197 -0
  313. package/src/domains/standard/operations/categories.test.ts +61 -0
  314. package/src/domains/standard/operations/categories.ts +38 -0
  315. package/src/domains/standard/operations/index.ts +5 -0
  316. package/src/domains/standard/operations/list.test.ts +75 -0
  317. package/src/domains/standard/operations/list.ts +64 -0
  318. package/src/domains/standard/operations/lookup.test.ts +58 -0
  319. package/src/domains/standard/operations/lookup.ts +142 -0
  320. package/src/domains/standard/orchestration/buildStandardFilters.ts +11 -0
  321. package/src/domains/standard/orchestration/index.ts +4 -0
  322. package/src/domains/standard/orchestration/resolveStandardList.ts +42 -0
  323. package/src/domains/standard/orchestration/resolveStandardLookup.ts +20 -0
  324. package/src/domains/standard/orchestration/standardEmptyError.ts +19 -0
  325. package/src/domains/standard/types.ts +15 -0
  326. package/src/domains/tier/commands/index.ts +3 -0
  327. package/src/domains/tier/commands/list.test.ts +67 -0
  328. package/src/domains/tier/commands/list.ts +43 -0
  329. package/src/domains/tier/formatters/index.ts +3 -0
  330. package/src/domains/tier/formatters/list.test.ts +33 -0
  331. package/src/domains/tier/formatters/list.ts +40 -0
  332. package/src/domains/tier/index.ts +17 -0
  333. package/src/domains/tier/mcp/index.ts +2 -0
  334. package/src/domains/tier/mcp/specs.ts +38 -0
  335. package/src/domains/tier/operations/index.ts +3 -0
  336. package/src/domains/tier/operations/list.test.ts +32 -0
  337. package/src/domains/tier/operations/list.ts +33 -0
  338. package/src/domains/token/commands/add-config.test.ts +68 -0
  339. package/src/domains/token/commands/add-config.ts +62 -0
  340. package/src/domains/token/commands/index.ts +5 -0
  341. package/src/domains/token/commands/list.test.ts +82 -0
  342. package/src/domains/token/commands/list.ts +45 -0
  343. package/src/domains/token/commands/lookup.test.ts +125 -0
  344. package/src/domains/token/commands/lookup.ts +121 -0
  345. package/src/domains/token/formatters/index.ts +4 -0
  346. package/src/domains/token/formatters/list.test.ts +37 -0
  347. package/src/domains/token/formatters/list.ts +37 -0
  348. package/src/domains/token/formatters/lookup.test.ts +61 -0
  349. package/src/domains/token/formatters/lookup.ts +72 -0
  350. package/src/domains/token/index.ts +32 -0
  351. package/src/domains/token/mcp/index.ts +2 -0
  352. package/src/domains/token/mcp/specs.ts +145 -0
  353. package/src/domains/token/operations/add-config.test.ts +68 -0
  354. package/src/domains/token/operations/add-config.ts +61 -0
  355. package/src/domains/token/operations/index.ts +7 -0
  356. package/src/domains/token/operations/list.test.ts +50 -0
  357. package/src/domains/token/operations/list.ts +52 -0
  358. package/src/domains/token/operations/lookup.test.ts +42 -0
  359. package/src/domains/token/operations/lookup.ts +66 -0
  360. package/src/domains/token/orchestration/index.ts +3 -0
  361. package/src/domains/token/orchestration/resolveTokenList.ts +17 -0
  362. package/src/domains/token/orchestration/resolveTokenLookup.ts +20 -0
  363. package/src/domains/token/orchestration/tokenEmptyError.ts +17 -0
  364. package/src/domains/token/types.ts +6 -0
  365. package/src/error/PragmaError.test.ts +121 -0
  366. package/src/error/PragmaError.ts +164 -0
  367. package/src/error/constants.ts +12 -0
  368. package/src/error/index.ts +4 -0
  369. package/src/error/types.ts +41 -0
  370. package/src/index.ts +141 -0
  371. package/src/mcp/createMcpServer.test.ts +41 -0
  372. package/src/mcp/createMcpServer.ts +53 -0
  373. package/src/mcp/error/buildRecovery.test.ts +37 -0
  374. package/src/mcp/error/buildRecovery.ts +17 -0
  375. package/src/mcp/error/index.ts +12 -0
  376. package/src/mcp/error/serializeError.test.ts +121 -0
  377. package/src/mcp/error/serializeError.ts +31 -0
  378. package/src/mcp/error/serializeErrorPayload.test.ts +38 -0
  379. package/src/mcp/error/serializeErrorPayload.ts +27 -0
  380. package/src/mcp/error/types.ts +51 -0
  381. package/src/mcp/index.ts +15 -0
  382. package/src/mcp/registerResources.test.ts +236 -0
  383. package/src/mcp/registerResources.ts +303 -0
  384. package/src/mcp/runMcpServer.test.ts +66 -0
  385. package/src/mcp/runMcpServer.ts +24 -0
  386. package/src/mcp/tools/index.ts +55 -0
  387. package/src/mcp/tools/registerFromSpec.ts +97 -0
  388. package/src/mcp/tools/registerTools.test.ts +1124 -0
  389. package/src/mcp/types.ts +62 -0
  390. package/src/mcp/utils/estimateTokens.test.ts +14 -0
  391. package/src/mcp/utils/estimateTokens.ts +9 -0
  392. package/src/mcp/utils/index.ts +11 -0
  393. package/src/mcp/utils/wrapTool.test.ts +108 -0
  394. package/src/mcp/utils/wrapTool.ts +81 -0
  395. package/src/package-manager/constants.ts +29 -0
  396. package/src/package-manager/detectInstallSource.test.ts +23 -0
  397. package/src/package-manager/detectInstallSource.ts +111 -0
  398. package/src/package-manager/detectLocalInstall.test.ts +16 -0
  399. package/src/package-manager/detectLocalInstall.ts +43 -0
  400. package/src/package-manager/detectPackageManager.test.ts +81 -0
  401. package/src/package-manager/detectPackageManager.ts +30 -0
  402. package/src/package-manager/index.ts +6 -0
  403. package/src/package-manager/types.ts +12 -0
  404. package/src/pipeline/collectCommands.test.ts +37 -0
  405. package/src/pipeline/collectCommands.ts +47 -0
  406. package/src/pipeline/constants.ts +14 -0
  407. package/src/pipeline/createProgram.test.ts +112 -0
  408. package/src/pipeline/createProgram.ts +70 -0
  409. package/src/pipeline/formatTerminal.test.ts +43 -0
  410. package/src/pipeline/formatTerminal.ts +53 -0
  411. package/src/pipeline/index.ts +19 -0
  412. package/src/pipeline/mapExitCode.test.ts +41 -0
  413. package/src/pipeline/mapExitCode.ts +12 -0
  414. package/src/pipeline/parseGlobalFlags.ts +21 -0
  415. package/src/pipeline/renderError.test.ts +151 -0
  416. package/src/pipeline/renderError.ts +114 -0
  417. package/src/pipeline/resolveCommandKind.test.ts +68 -0
  418. package/src/pipeline/resolveCommandKind.ts +71 -0
  419. package/src/pipeline/runCli.orchestration.test.ts +418 -0
  420. package/src/pipeline/runCli.test.ts +70 -0
  421. package/src/pipeline/runCli.ts +215 -0
  422. package/src/pipeline/types.ts +17 -0
  423. package/src/testing/cli.ts +43 -0
  424. package/src/testing/createTestMcpClient.ts +64 -0
  425. package/src/testing/dsFixtures.ts +506 -0
  426. package/src/testing/fixtures/canonical-config.json +4 -0
  427. package/src/testing/fixtures/canonical.ttl +437 -0
  428. package/src/testing/fixtures/filtered-config.json +4 -0
  429. package/src/testing/fixtures.ts +34 -0
  430. package/src/testing/helpers/assertParity.ts +28 -0
  431. package/src/testing/helpers/createTestMcpClient.ts +40 -0
  432. package/src/testing/helpers/createTestRuntime.ts +67 -0
  433. package/src/testing/index.ts +8 -0
  434. package/src/testing/integration/agent-session.test.ts +148 -0
  435. package/src/testing/integration/boot.test.ts +67 -0
  436. package/src/testing/integration/mcp-surface.test.ts +574 -0
  437. package/src/testing/integration/parity.test.ts +192 -0
  438. package/src/testing/store.ts +39 -0
  439. package/src/testing/types.ts +20 -0
@@ -0,0 +1,95 @@
1
+ import type { URI } from "@canonical/ke";
2
+ import { describe, expect, it } from "vitest";
3
+ import type { StandardDetailed } from "../../shared/types.js";
4
+ import formatters from "./lookup.js";
5
+
6
+ const DETAILED: StandardDetailed = {
7
+ uri: "http://example.com/s1" as URI,
8
+ name: "react/component/folder-structure",
9
+ category: "react",
10
+ description: "Components must follow the standard folder layout",
11
+ dos: [
12
+ { language: "typescript", code: "Place files in src/lib/" },
13
+ { language: "typescript", code: "Include index.ts barrel" },
14
+ ],
15
+ donts: [{ language: "typescript", code: "Use flat directory" }],
16
+ };
17
+
18
+ describe("formatters.plain", () => {
19
+ it("renders summary without dos/donts by default", () => {
20
+ const text = formatters.plain({ standard: DETAILED, detailed: false });
21
+ expect(text).toContain("react/component/folder-structure");
22
+ expect(text).toContain("URI: http://example.com/s1");
23
+ expect(text).toContain("Category: react");
24
+ expect(text).toContain("Description:");
25
+ expect(text).not.toContain("Do:");
26
+ expect(text).not.toContain("Don't:");
27
+ });
28
+
29
+ it("renders dos and donts when detailed", () => {
30
+ const text = formatters.plain({ standard: DETAILED, detailed: true });
31
+ expect(text).toContain("Do:");
32
+ expect(text).toContain(" Place files in src/lib/");
33
+ expect(text).toContain("Don't:");
34
+ expect(text).toContain(" Use flat directory");
35
+ });
36
+
37
+ it("renders dash for empty category", () => {
38
+ const text = formatters.plain({
39
+ standard: { ...DETAILED, category: "" },
40
+ detailed: false,
41
+ });
42
+ expect(text).toContain("Category: —");
43
+ });
44
+
45
+ it("renders extends when present", () => {
46
+ const text = formatters.plain({
47
+ standard: { ...DETAILED, extends: "react/component/base" },
48
+ detailed: false,
49
+ });
50
+ expect(text).toContain("Extends: react/component/base");
51
+ });
52
+ });
53
+
54
+ describe("formatters.llm", () => {
55
+ it("renders markdown heading", () => {
56
+ const text = formatters.llm({ standard: DETAILED, detailed: false });
57
+ expect(text).toContain("## react/component/folder-structure");
58
+ expect(text).toContain("URI: http://example.com/s1");
59
+ expect(text).not.toContain("### Do");
60
+ });
61
+
62
+ it("renders extends in llm output when present", () => {
63
+ const text = formatters.llm({
64
+ standard: { ...DETAILED, extends: "cs:react_base" },
65
+ detailed: false,
66
+ });
67
+ expect(text).toContain("Extends: cs:react_base");
68
+ });
69
+
70
+ it("renders do/dont sections when detailed", () => {
71
+ const text = formatters.llm({ standard: DETAILED, detailed: true });
72
+ expect(text).toContain("### Do");
73
+ expect(text).toContain("### Don't");
74
+ expect(text).toContain("- Place files in src/lib/");
75
+ });
76
+ });
77
+
78
+ describe("formatters.json", () => {
79
+ it("includes dos/donts when detailed", () => {
80
+ const parsed = JSON.parse(
81
+ formatters.json({ standard: DETAILED, detailed: true }),
82
+ );
83
+ expect(parsed.dos).toHaveLength(2);
84
+ expect(parsed.donts).toHaveLength(1);
85
+ });
86
+
87
+ it("omits dos/donts when not detailed", () => {
88
+ const parsed = JSON.parse(
89
+ formatters.json({ standard: DETAILED, detailed: false }),
90
+ );
91
+ expect(parsed.dos).toBeUndefined();
92
+ expect(parsed.donts).toBeUndefined();
93
+ expect(parsed.name).toBe("react/component/folder-structure");
94
+ });
95
+ });
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Three-mode formatter for `pragma standard lookup` output.
3
+ *
4
+ * - **plain** — terminal text showing name, category, description, and
5
+ * optional dos/donts when detailed.
6
+ * - **llm** — condensed Markdown consumed by LLM agents and reused
7
+ * by the MCP adapter when `condensed: true`.
8
+ * - **json** — structured JSON; omits dos/donts unless detailed.
9
+ */
10
+
11
+ import type { Formatters } from "../../shared/formatters.js";
12
+ import type { StandardLookupInput } from "./types.js";
13
+
14
+ const formatters: Formatters<StandardLookupInput> = {
15
+ plain({ standard, detailed }) {
16
+ const lines: string[] = [];
17
+ lines.push(standard.name);
18
+ lines.push(`URI: ${standard.uri}`);
19
+ lines.push(`Category: ${standard.category || "—"}`);
20
+ lines.push(`Description: ${standard.description}`);
21
+ if (standard.extends) {
22
+ lines.push(`Extends: ${standard.extends}`);
23
+ }
24
+
25
+ if (detailed) {
26
+ if (standard.dos.length > 0) {
27
+ lines.push("");
28
+ lines.push("Do:");
29
+ for (const d of standard.dos) {
30
+ lines.push(` ${d.code}`);
31
+ }
32
+ }
33
+
34
+ if (standard.donts.length > 0) {
35
+ lines.push("");
36
+ lines.push("Don't:");
37
+ for (const d of standard.donts) {
38
+ lines.push(` ${d.code}`);
39
+ }
40
+ }
41
+ }
42
+
43
+ return lines.join("\n");
44
+ },
45
+
46
+ llm({ standard, detailed }) {
47
+ const lines: string[] = [];
48
+ lines.push(`## ${standard.name}`);
49
+ lines.push(`URI: ${standard.uri}`);
50
+ lines.push(`Category: ${standard.category || "—"}`);
51
+ if (standard.extends) {
52
+ lines.push(`Extends: ${standard.extends}`);
53
+ }
54
+ lines.push(standard.description);
55
+
56
+ if (detailed) {
57
+ if (standard.dos.length > 0) {
58
+ lines.push("");
59
+ lines.push("### Do");
60
+ for (const d of standard.dos) {
61
+ lines.push(`- ${d.code}`);
62
+ }
63
+ }
64
+
65
+ if (standard.donts.length > 0) {
66
+ lines.push("");
67
+ lines.push("### Don't");
68
+ for (const d of standard.donts) {
69
+ lines.push(`- ${d.code}`);
70
+ }
71
+ }
72
+ }
73
+
74
+ return lines.join("\n");
75
+ },
76
+
77
+ json({ standard, detailed }) {
78
+ if (detailed) {
79
+ return JSON.stringify(standard, null, 2);
80
+ }
81
+ const { dos: _dos, donts: _donts, ...summary } = standard;
82
+ return JSON.stringify(summary, null, 2);
83
+ },
84
+ };
85
+
86
+ export default formatters;
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Formatter input types for standard commands.
3
+ *
4
+ * Composite types that bundle the data with display options,
5
+ * so formatters conform to the `Formatters<T>` contract.
6
+ */
7
+
8
+ import type {
9
+ Disclosure,
10
+ StandardDetailed,
11
+ StandardSummary,
12
+ } from "../../shared/types.js";
13
+
14
+ /** Input payload for the standard-lookup formatter, pairing data with a detail flag. */
15
+ export interface StandardLookupInput {
16
+ readonly standard: StandardDetailed;
17
+ readonly detailed: boolean;
18
+ }
19
+
20
+ /**
21
+ * Input for the list formatter with progressive disclosure.
22
+ *
23
+ * - summary: items only (name + category + description)
24
+ * - digest: items enriched with first do example
25
+ * - detailed: items enriched with full dos/donts
26
+ */
27
+ export interface StandardListOutput {
28
+ readonly items: readonly StandardSummary[];
29
+ readonly details?: readonly (StandardDetailed | null)[];
30
+ readonly disclosure: Disclosure;
31
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @module Standard domain — CLI commands and operations.
3
+ *
4
+ * Commands: `pragma standard list`, `pragma standard lookup <name>`,
5
+ * `pragma standard categories`.
6
+ * Operations: {@link lookupStandard}, {@link listStandards}, {@link listCategories}.
7
+ */
8
+
9
+ import type { CommandDefinition } from "@canonical/cli-core";
10
+ import type { PragmaContext } from "../shared/context.js";
11
+ import {
12
+ categoriesCommand,
13
+ listCommand,
14
+ lookupCommand,
15
+ } from "./commands/index.js";
16
+
17
+ export function commands(ctx: PragmaContext): CommandDefinition[] {
18
+ return [listCommand(ctx), lookupCommand(ctx), categoriesCommand(ctx)];
19
+ }
20
+
21
+ export { specs as mcpSpecs } from "./mcp/index.js";
22
+ export {
23
+ listCategories,
24
+ listStandards,
25
+ lookupStandard,
26
+ } from "./operations/index.js";
27
+ export {
28
+ buildStandardFilters,
29
+ resolveStandardList,
30
+ resolveStandardLookup,
31
+ standardEmptyError,
32
+ } from "./orchestration/index.js";
@@ -0,0 +1,2 @@
1
+ /** @module Standard domain MCP barrel — standard_list, standard_lookup, standard_categories. */
2
+ export { default as specs } from "./specs.js";
@@ -0,0 +1,197 @@
1
+ /**
2
+ * MCP tool specs for the standard domain.
3
+ *
4
+ * Declarative definitions — no MCP imports. The adapter layer
5
+ * converts these into registered MCP tools via `registerFromSpec()`.
6
+ */
7
+
8
+ import type { ToolSpec } from "../../shared/ToolSpec.js";
9
+ import {
10
+ type StandardListOutput,
11
+ categoriesFormatters as standardCatFmt,
12
+ listFormatters as standardListFmt,
13
+ lookupFormatters as standardLookupFmt,
14
+ } from "../formatters/index.js";
15
+ import { listCategories } from "../operations/index.js";
16
+ import {
17
+ resolveStandardList,
18
+ resolveStandardLookup,
19
+ } from "../orchestration/index.js";
20
+
21
+ const specs: readonly ToolSpec[] = [
22
+ {
23
+ name: "standard_list",
24
+ description:
25
+ "List code standards. Optionally filter by category or search term. Use digest/detailed for progressive disclosure.",
26
+ params: {
27
+ category: {
28
+ type: "string",
29
+ description: "Filter by category name",
30
+ optional: true,
31
+ },
32
+ search: {
33
+ type: "string",
34
+ description: "Search in name and description",
35
+ optional: true,
36
+ },
37
+ digest: {
38
+ type: "boolean",
39
+ description: "Show description and first example for each standard",
40
+ optional: true,
41
+ },
42
+ detailed: {
43
+ type: "boolean",
44
+ description: "Show full dos/donts for each standard",
45
+ optional: true,
46
+ },
47
+ condensed: {
48
+ type: "boolean",
49
+ description: "Token-optimized output",
50
+ optional: true,
51
+ },
52
+ },
53
+ readOnly: true,
54
+ async execute(rt, { category, search, digest, detailed, condensed }) {
55
+ const resolution = await resolveStandardList(rt, {
56
+ category: category as string | undefined,
57
+ search: search as string | undefined,
58
+ digest: digest === true,
59
+ detailed: detailed === true,
60
+ });
61
+ const output: StandardListOutput = {
62
+ items: resolution.items,
63
+ details: resolution.details,
64
+ disclosure: resolution.disclosure,
65
+ };
66
+
67
+ if (condensed) {
68
+ const text = standardListFmt.llm(output);
69
+ return {
70
+ condensed: true,
71
+ text,
72
+ tokens: `~${Math.ceil(text.length / 4)}`,
73
+ };
74
+ }
75
+
76
+ if (resolution.disclosure.level === "summary") {
77
+ return {
78
+ data: resolution.items,
79
+ meta: { count: resolution.items.length },
80
+ };
81
+ }
82
+
83
+ return {
84
+ data: JSON.parse(standardListFmt.json(output)),
85
+ meta: {
86
+ count: resolution.items.length,
87
+ disclosure: resolution.disclosure.level,
88
+ },
89
+ };
90
+ },
91
+ },
92
+
93
+ {
94
+ name: "standard_lookup",
95
+ description:
96
+ "Get detailed information about one or more code standards including dos and donts with code examples.",
97
+ params: {
98
+ names: {
99
+ type: "string[]",
100
+ description: "Standard names or IRIs to look up",
101
+ optional: false,
102
+ },
103
+ detailed: {
104
+ type: "boolean",
105
+ description:
106
+ "Return full details with dos/donts (default: true for MCP)",
107
+ optional: true,
108
+ },
109
+ condensed: {
110
+ type: "boolean",
111
+ description: "Token-optimized output",
112
+ optional: true,
113
+ },
114
+ },
115
+ readOnly: true,
116
+ async execute(rt, { names, detailed, condensed }) {
117
+ const showDetailed = (detailed as boolean | undefined) ?? true;
118
+ const contract = await resolveStandardLookup(rt.store, names as string[]);
119
+ const result = contract.result;
120
+
121
+ if (condensed) {
122
+ const textParts = result.results.map((standard) =>
123
+ standardLookupFmt.llm({
124
+ standard,
125
+ detailed: showDetailed,
126
+ }),
127
+ );
128
+
129
+ if (result.errors.length > 0) {
130
+ textParts.push(
131
+ [
132
+ "### Errors",
133
+ ...result.errors.map(
134
+ (error) => `- ${error.query}: ${error.message}`,
135
+ ),
136
+ ].join("\n"),
137
+ );
138
+ }
139
+
140
+ const text = textParts.join("\n\n");
141
+ return {
142
+ condensed: true,
143
+ text,
144
+ tokens: `~${Math.ceil(text.length / 4)}`,
145
+ };
146
+ }
147
+
148
+ if (!showDetailed) {
149
+ return {
150
+ data: {
151
+ results: result.results.map(
152
+ ({ uri, name, category, description }) => ({
153
+ uri,
154
+ name,
155
+ category,
156
+ description,
157
+ }),
158
+ ),
159
+ errors: result.errors,
160
+ },
161
+ meta: { count: result.results.length },
162
+ };
163
+ }
164
+
165
+ return { data: result, meta: { count: result.results.length } };
166
+ },
167
+ },
168
+
169
+ {
170
+ name: "standard_categories",
171
+ description: "List all code standard categories.",
172
+ params: {
173
+ condensed: {
174
+ type: "boolean",
175
+ description: "Token-optimized output",
176
+ optional: true,
177
+ },
178
+ },
179
+ readOnly: true,
180
+ async execute(rt, { condensed }) {
181
+ const result = await listCategories(rt.store);
182
+
183
+ if (condensed) {
184
+ const text = standardCatFmt.llm(result);
185
+ return {
186
+ condensed: true,
187
+ text,
188
+ tokens: `~${Math.ceil(text.length / 4)}`,
189
+ };
190
+ }
191
+
192
+ return { data: result, meta: { count: result.length } };
193
+ },
194
+ },
195
+ ];
196
+
197
+ export default specs;
@@ -0,0 +1,61 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+ import { createTestStore, DS_ALL_TTL } from "#testing";
4
+ import { PREFIX_MAP } from "../../shared/prefixes.js";
5
+ import listCategories from "./categories.js";
6
+
7
+ let store: Store;
8
+ let cleanup: () => void;
9
+
10
+ beforeAll(async () => {
11
+ const result = await createTestStore({ ttl: DS_ALL_TTL });
12
+ store = result.store;
13
+ cleanup = result.cleanup;
14
+ });
15
+
16
+ afterAll(() => cleanup());
17
+
18
+ describe("listCategories", () => {
19
+ it("returns categories with standard counts", async () => {
20
+ const result = await listCategories(store);
21
+ expect(result.length).toBe(2);
22
+
23
+ const react = result.find((c) => c.name === "react");
24
+ expect(react?.standardCount).toBe(2);
25
+
26
+ const code = result.find((c) => c.name === "code");
27
+ expect(code?.standardCount).toBe(1);
28
+ });
29
+
30
+ it("returns sorted by name", async () => {
31
+ const result = await listCategories(store);
32
+ const names = result.map((c) => c.name);
33
+ const sorted = [...names].sort();
34
+ expect(names).toEqual(sorted);
35
+ });
36
+
37
+ it("includes categories with zero standards", async () => {
38
+ const ttlWithEmptyCategory = `
39
+ @prefix cs: <${PREFIX_MAP.cs}> .
40
+ cs:empty_cat a cs:Category ; cs:slug "empty" .
41
+ cs:filled_cat a cs:Category ; cs:slug "filled" .
42
+ cs:s1 a cs:CodeStandard ;
43
+ cs:name "filled/one" ;
44
+ cs:hasCategory cs:filled_cat ;
45
+ cs:description "A standard" .
46
+ `;
47
+ const { store: testStore, cleanup: testCleanup } = await createTestStore({
48
+ ttl: ttlWithEmptyCategory,
49
+ });
50
+ try {
51
+ const result = await listCategories(testStore);
52
+ const empty = result.find((c) => c.name === "empty");
53
+ expect(empty).toBeDefined();
54
+ expect(empty?.standardCount).toBe(0);
55
+ const filled = result.find((c) => c.name === "filled");
56
+ expect(filled?.standardCount).toBe(1);
57
+ } finally {
58
+ testCleanup();
59
+ }
60
+ });
61
+ });
@@ -0,0 +1,38 @@
1
+ /**
2
+ * Lists all standard categories with the number of standards in each.
3
+ *
4
+ * @param store - ke store to query
5
+ * @returns array of category summaries ordered by name, empty when none exist
6
+ * @note Queries ke store
7
+ */
8
+
9
+ import type { Store } from "@canonical/ke";
10
+ import { buildQuery } from "../../shared/buildQuery.js";
11
+ import { P } from "../../shared/prefixes.js";
12
+ import type { CategorySummary } from "../../shared/types.js";
13
+
14
+ export default async function listCategories(
15
+ store: Store,
16
+ ): Promise<CategorySummary[]> {
17
+ const result = await store.query(
18
+ buildQuery(`
19
+ SELECT ?categoryName (COUNT(?standard) AS ?count)
20
+ WHERE {
21
+ ?cat a ${P.cs}Category ;
22
+ ${P.cs}slug ?categoryName .
23
+ OPTIONAL {
24
+ ?standard a ${P.cs}CodeStandard ;
25
+ ${P.cs}hasCategory ?cat .
26
+ }
27
+ }
28
+ GROUP BY ?categoryName
29
+ ORDER BY ?categoryName
30
+ `),
31
+ );
32
+
33
+ if (result.type !== "select") return [];
34
+ return result.bindings.map((b) => ({
35
+ name: b.categoryName ?? "",
36
+ standardCount: Number.parseInt(b.count ?? "0", 10) || 0,
37
+ }));
38
+ }
@@ -0,0 +1,5 @@
1
+ /** @module Re-exports standard operations (`listCategories`, `getStandard`, `listStandards`). */
2
+
3
+ export { default as listCategories } from "./categories.js";
4
+ export { default as listStandards } from "./list.js";
5
+ export { default as lookupStandard } from "./lookup.js";
@@ -0,0 +1,75 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
+ import { createTestStore, DS_ALL_TTL } from "#testing";
4
+ import listStandards from "./list.js";
5
+
6
+ let store: Store;
7
+ let cleanup: () => void;
8
+
9
+ beforeAll(async () => {
10
+ const result = await createTestStore({ ttl: DS_ALL_TTL });
11
+ store = result.store;
12
+ cleanup = result.cleanup;
13
+ });
14
+
15
+ afterAll(() => cleanup());
16
+
17
+ describe("listStandards", () => {
18
+ it("returns all standards", async () => {
19
+ const result = await listStandards(store);
20
+ expect(result.length).toBe(3);
21
+ const names = result.map((s) => s.name);
22
+ expect(names).toContain("react/component/folder-structure");
23
+ expect(names).toContain("react/component/props");
24
+ expect(names).toContain("code/function/purity");
25
+ });
26
+
27
+ it("includes category names", async () => {
28
+ const result = await listStandards(store);
29
+ const folder = result.find(
30
+ (s) => s.name === "react/component/folder-structure",
31
+ );
32
+ expect(folder?.category).toBe("react");
33
+ });
34
+
35
+ it("returns sorted by name", async () => {
36
+ const result = await listStandards(store);
37
+ const names = result.map((s) => s.name);
38
+ const sorted = [...names].sort();
39
+ expect(names).toEqual(sorted);
40
+ });
41
+
42
+ it("filters by category", async () => {
43
+ const result = await listStandards(store, { category: "react" });
44
+ expect(result.length).toBe(2);
45
+ for (const s of result) {
46
+ expect(s.category).toBe("react");
47
+ }
48
+ });
49
+
50
+ it("filters by search term in name", async () => {
51
+ const result = await listStandards(store, { search: "folder" });
52
+ expect(result.length).toBe(1);
53
+ expect(result[0]?.name).toBe("react/component/folder-structure");
54
+ });
55
+
56
+ it("filters by search term in description", async () => {
57
+ const result = await listStandards(store, { search: "pure" });
58
+ expect(result.length).toBe(1);
59
+ expect(result[0]?.name).toBe("code/function/purity");
60
+ });
61
+
62
+ it("returns empty for non-matching category", async () => {
63
+ const result = await listStandards(store, { category: "nonexistent" });
64
+ expect(result.length).toBe(0);
65
+ });
66
+
67
+ it("combines category and search filters", async () => {
68
+ const result = await listStandards(store, {
69
+ category: "react",
70
+ search: "props",
71
+ });
72
+ expect(result.length).toBe(1);
73
+ expect(result[0]?.name).toBe("react/component/props");
74
+ });
75
+ });
@@ -0,0 +1,64 @@
1
+ /**
2
+ * Lists all code standards, optionally filtered by category or search term.
3
+ *
4
+ * @param store - ke store to query
5
+ * @param filters - optional category and/or search text filters
6
+ * @returns array of standard summaries ordered by name, empty when none match
7
+ * @note Queries ke store
8
+ */
9
+
10
+ import type { Store, URI } from "@canonical/ke";
11
+ import { escapeSparqlValue } from "@canonical/ke";
12
+ import { buildQuery } from "../../shared/buildQuery.js";
13
+ import { P } from "../../shared/prefixes.js";
14
+ import type {
15
+ StandardListFilters,
16
+ StandardSummary,
17
+ } from "../../shared/types.js";
18
+
19
+ export default async function listStandards(
20
+ store: Store,
21
+ filters?: StandardListFilters,
22
+ ): Promise<StandardSummary[]> {
23
+ const filterClauses: string[] = [];
24
+
25
+ if (filters?.category) {
26
+ const escaped = escapeSparqlValue(filters.category.toLowerCase());
27
+ filterClauses.push(
28
+ `?standard ${P.cs}hasCategory ?filterCat . ?filterCat ${P.cs}slug ?catSlug . FILTER(?catSlug = ${escaped})`,
29
+ );
30
+ }
31
+
32
+ if (filters?.search) {
33
+ const escaped = escapeSparqlValue(filters.search.toLowerCase());
34
+ filterClauses.push(
35
+ `FILTER(CONTAINS(LCASE(?name), ${escaped}) || CONTAINS(LCASE(?description), ${escaped}))`,
36
+ );
37
+ }
38
+
39
+ const result = await store.query(
40
+ buildQuery(`
41
+ SELECT ?standard ?name ?categoryName ?description
42
+ WHERE {
43
+ ?standard a ${P.cs}CodeStandard ;
44
+ ${P.cs}name ?name ;
45
+ ${P.cs}description ?description .
46
+ OPTIONAL {
47
+ ?standard ${P.cs}hasCategory ?cat .
48
+ ?cat ${P.cs}slug ?categoryName .
49
+ }
50
+ ${filterClauses.join("\n ")}
51
+ }
52
+ ORDER BY ?name
53
+ `),
54
+ );
55
+
56
+ if (result.type !== "select") return [];
57
+
58
+ return result.bindings.map((b) => ({
59
+ uri: (b.standard ?? "") as URI,
60
+ name: b.name ?? "",
61
+ category: b.categoryName ?? "",
62
+ description: b.description ?? "",
63
+ }));
64
+ }