@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,169 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import { PragmaError } from "#error";
3
+ import findNamespace from "../../domains/ontology/helpers/findNamespace.js";
4
+ import { buildQuery } from "../../domains/shared/buildQuery.js";
5
+ import compactUri from "../../domains/shared/compactUri.js";
6
+ import classifyEntity from "./classifyEntity.js";
7
+ import { RDF_TYPE, RDFS_DOMAIN, RDFS_SUBCLASS_OF } from "./constants.js";
8
+ import {
9
+ resolveDescriptionPredicates,
10
+ resolveLabelPredicates,
11
+ } from "./displayPredicates.js";
12
+ import pickFirstValue from "./pickFirstValue.js";
13
+ import pickPrimaryType from "./pickPrimaryType.js";
14
+ import type {
15
+ GraphIndex,
16
+ PropertyGroup,
17
+ PropertyValue,
18
+ ResourceEntity,
19
+ } from "./types.js";
20
+
21
+ /** Query the compacted subjects of `?x <predicate> <object>`. */
22
+ async function fetchReferencingSubjects(
23
+ store: Store,
24
+ predicate: string,
25
+ object: string,
26
+ prefixes: Readonly<Record<string, string>>,
27
+ ): Promise<string[]> {
28
+ const result = await store.query(
29
+ buildQuery(`SELECT ?x WHERE { ?x <${predicate}> <${object}> } ORDER BY ?x`),
30
+ );
31
+ if (result.type !== "select") return [];
32
+ return result.termBindings
33
+ .filter((t) => t.x?.termType === "NamedNode")
34
+ .map((t) => compactUri(t.x?.value ?? "", prefixes));
35
+ }
36
+
37
+ /**
38
+ * Read a single entity into a box-aware payload.
39
+ *
40
+ * Object values are classified by RDF term type (via `termBindings`), so a
41
+ * URL-shaped literal stays a literal and a foreign IRI is recognised as a URI.
42
+ * A class read is enriched with its superclasses, direct subclasses, asserted
43
+ * instance count, and declared properties; an individual read carries a
44
+ * pointer to its primary class. Object URIs are labelled from the prebuilt
45
+ * index — no per-object query.
46
+ *
47
+ * @param store - The ke store to query.
48
+ * @param fullUri - The fully resolved subject URI.
49
+ * @param prefixes - The store's merged prefix map.
50
+ * @param index - The prebuilt graph index for label and count lookups.
51
+ * @returns The entity payload.
52
+ * @throws PragmaError.notFound when the subject has no triples.
53
+ *
54
+ * @note Queries ke store
55
+ */
56
+ export default async function readEntity(
57
+ store: Store,
58
+ fullUri: string,
59
+ prefixes: Readonly<Record<string, string>>,
60
+ index: GraphIndex,
61
+ ): Promise<ResourceEntity> {
62
+ const result = await store.query(
63
+ buildQuery(`SELECT ?p ?o WHERE { <${fullUri}> ?p ?o } ORDER BY ?p ?o`),
64
+ );
65
+
66
+ if (result.type !== "select" || result.bindings.length === 0) {
67
+ throw PragmaError.notFound("entity", compactUri(fullUri, prefixes), {
68
+ recovery: {
69
+ message:
70
+ "Check the URI is correct and run a SPARQL query to find valid URIs.",
71
+ cli: "pragma graph query 'SELECT ?s WHERE { ?s ?p ?o } LIMIT 10'",
72
+ mcp: { tool: "graph_query" },
73
+ },
74
+ });
75
+ }
76
+
77
+ const fullTypes: string[] = [];
78
+ const types: string[] = [];
79
+ const groupMap = new Map<string, PropertyValue[]>();
80
+ const valuesByPredicate = new Map<string, string[]>();
81
+
82
+ for (const term of result.termBindings) {
83
+ const predicate = term.p?.value ?? "";
84
+ const objectTerm = term.o;
85
+ if (objectTerm === undefined) continue;
86
+
87
+ const rawValues = valuesByPredicate.get(predicate) ?? [];
88
+ rawValues.push(objectTerm.value);
89
+ valuesByPredicate.set(predicate, rawValues);
90
+
91
+ if (predicate === RDF_TYPE) {
92
+ fullTypes.push(objectTerm.value);
93
+ types.push(compactUri(objectTerm.value, prefixes));
94
+ continue;
95
+ }
96
+
97
+ const compactPred = compactUri(predicate, prefixes);
98
+ const existing = groupMap.get(compactPred) ?? [];
99
+ if (objectTerm.termType === "NamedNode") {
100
+ existing.push({
101
+ type: "uri",
102
+ uri: objectTerm.value,
103
+ prefixed: compactUri(objectTerm.value, prefixes),
104
+ label: index.labelByUri.get(objectTerm.value) ?? null,
105
+ });
106
+ } else if (objectTerm.termType === "BlankNode") {
107
+ existing.push({ type: "bnode", id: objectTerm.value });
108
+ } else {
109
+ existing.push({ type: "literal", value: objectTerm.value });
110
+ }
111
+ groupMap.set(compactPred, existing);
112
+ }
113
+
114
+ const classification = classifyEntity(fullTypes) ?? {
115
+ box: "abox" as const,
116
+ category: "individual" as const,
117
+ };
118
+ const prefix = findNamespace(fullUri, Object.entries(prefixes))?.prefix;
119
+ const label = pickFirstValue(
120
+ valuesByPredicate,
121
+ resolveLabelPredicates(prefix),
122
+ );
123
+ const description = pickFirstValue(
124
+ valuesByPredicate,
125
+ resolveDescriptionPredicates(prefix),
126
+ );
127
+
128
+ const properties: PropertyGroup[] = [...groupMap.entries()].map(
129
+ ([predicate, values]) => ({ predicate, values }),
130
+ );
131
+
132
+ const base: ResourceEntity = {
133
+ uri: fullUri,
134
+ prefixed: compactUri(fullUri, prefixes),
135
+ box: classification.box,
136
+ category: classification.category,
137
+ types,
138
+ label,
139
+ description,
140
+ properties,
141
+ };
142
+
143
+ if (classification.category === "class") {
144
+ const [subClasses, declaredProperties] = await Promise.all([
145
+ fetchReferencingSubjects(store, RDFS_SUBCLASS_OF, fullUri, prefixes),
146
+ fetchReferencingSubjects(store, RDFS_DOMAIN, fullUri, prefixes),
147
+ ]);
148
+ return {
149
+ ...base,
150
+ superClasses: (valuesByPredicate.get(RDFS_SUBCLASS_OF) ?? []).map((u) =>
151
+ compactUri(u, prefixes),
152
+ ),
153
+ subClasses,
154
+ instanceCount: index.instanceCountByType.get(fullUri) ?? 0,
155
+ declaredProperties,
156
+ };
157
+ }
158
+
159
+ if (classification.category === "individual") {
160
+ const primaryTypeFull = pickPrimaryType(fullTypes);
161
+ return {
162
+ ...base,
163
+ instanceOf:
164
+ primaryTypeFull === null ? null : compactUri(primaryTypeFull, prefixes),
165
+ };
166
+ }
167
+
168
+ return base;
169
+ }
@@ -0,0 +1,126 @@
1
+ import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
2
+ import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
3
+ import { PragmaError } from "#error";
4
+ import resolveUri from "../../domains/graph/helpers/resolveUri.js";
5
+ import type { PragmaRuntime } from "../../domains/shared/runtime.js";
6
+ import buildGraphIndex from "./buildGraphIndex.js";
7
+ import buildResourceList from "./buildResourceList.js";
8
+ import { COMPLETION_LIMIT } from "./constants.js";
9
+ import rankUriCompletions from "./rankUriCompletions.js";
10
+ import readEntity from "./readEntity.js";
11
+ import type { GraphIndex, ListingTruncation } from "./types.js";
12
+
13
+ /** RFC 6570 template capturing the whole (prefixed or full) URI as `uri`. */
14
+ const URI_TEMPLATE = "{+uri}";
15
+
16
+ const TEMPLATE_DESCRIPTION =
17
+ "Knowledge-graph entities from the ke store. TBox schema (owl:Class / property " +
18
+ "definitions) is listed first and in full; ABox individuals follow, grouped by " +
19
+ "class and capped per class. Category and box are carried in each entry's name, " +
20
+ "annotations.priority, and _meta. Read any entry by its prefixed URI " +
21
+ "(e.g. ds:global.component.button); autocomplete matches URI or label substrings.";
22
+
23
+ /**
24
+ * Report capped individuals to stderr so truncation is never silent.
25
+ *
26
+ * @note Impure — writes to stderr.
27
+ */
28
+ function warnTruncation(truncation: ListingTruncation): void {
29
+ const parts = [...truncation.droppedByType.entries()]
30
+ .filter(([, dropped]) => dropped > 0)
31
+ .map(([type, dropped]) => `${type}: ${dropped}`);
32
+ process.stderr.write(
33
+ `pragma MCP resources: capped ${truncation.totalDropped} individual(s) ` +
34
+ `from the listing (${parts.join(", ")}); reach them via URI autocomplete ` +
35
+ "or the graph_query tool.\n",
36
+ );
37
+ }
38
+
39
+ /** Read the single `uri` template variable, tolerating array expansion. */
40
+ function readUriVariable(value: string | string[] | undefined): string {
41
+ const uri = Array.isArray(value) ? value.at(0) : value;
42
+ if (uri === undefined) {
43
+ throw PragmaError.invalidInput("uri", String(value), {
44
+ recovery: { message: "Provide a prefixed or full entity URI." },
45
+ });
46
+ }
47
+ return uri;
48
+ }
49
+
50
+ /**
51
+ * Register the graph-driven MCP resource surface on the server.
52
+ *
53
+ * One `{+uri}` template backs listing, autocomplete, and reads. The graph
54
+ * index is built once per runtime (the store is immutable after boot) and
55
+ * reused across calls, so autocomplete stays cheap. Listing separates TBox
56
+ * schema from ABox individuals and caps the instance long tail; reads return
57
+ * a box-aware payload.
58
+ *
59
+ * @param server - The MCP server to register resources on.
60
+ * @param runtime - The pragma runtime providing the ke store and prefixes.
61
+ *
62
+ * @note Impure — registers handlers and queries the store lazily.
63
+ */
64
+ export default function registerResources(
65
+ server: McpServer,
66
+ runtime: PragmaRuntime,
67
+ ): void {
68
+ const { store } = runtime;
69
+ const prefixes = store.prefixes;
70
+
71
+ let indexPromise: Promise<GraphIndex> | undefined;
72
+ const getIndex = (): Promise<GraphIndex> => {
73
+ indexPromise ??= buildGraphIndex(store, prefixes);
74
+ return indexPromise;
75
+ };
76
+
77
+ const template = new ResourceTemplate(URI_TEMPLATE, {
78
+ list: async () => {
79
+ const index = await getIndex();
80
+ const { resources, truncation } = buildResourceList(index);
81
+ if (truncation.totalDropped > 0) warnTruncation(truncation);
82
+ return { resources };
83
+ },
84
+ complete: {
85
+ uri: async (value) => {
86
+ const index = await getIndex();
87
+ const candidates = index.entities.map((entity) => ({
88
+ prefixed: entity.prefixed,
89
+ label: entity.label,
90
+ }));
91
+ return rankUriCompletions(candidates, value, COMPLETION_LIMIT);
92
+ },
93
+ },
94
+ });
95
+
96
+ server.registerResource(
97
+ "graph-entity",
98
+ template,
99
+ { description: TEMPLATE_DESCRIPTION, mimeType: "application/json" },
100
+ async (url, variables) => {
101
+ try {
102
+ const prefixedUri = readUriVariable(variables.uri);
103
+ const fullUri = resolveUri(prefixedUri, prefixes);
104
+ const index = await getIndex();
105
+ const entity = await readEntity(store, fullUri, prefixes, index);
106
+ return {
107
+ contents: [
108
+ {
109
+ uri: url.href,
110
+ mimeType: "application/json",
111
+ text: JSON.stringify(entity, null, 2),
112
+ },
113
+ ],
114
+ };
115
+ } catch (error) {
116
+ const message =
117
+ error instanceof PragmaError
118
+ ? JSON.stringify({ code: error.code, message: error.message })
119
+ : String(error);
120
+ return {
121
+ contents: [{ uri: url.href, mimeType: "text/plain", text: message }],
122
+ };
123
+ }
124
+ },
125
+ );
126
+ }
@@ -0,0 +1,128 @@
1
+ /**
2
+ * Shared types for the MCP resource surface.
3
+ *
4
+ * These shapes flow from the bulk graph index (`buildGraphIndex`) through
5
+ * listing (`buildResourceList`), autocomplete (`rankUriCompletions`), and
6
+ * entity reads (`readEntity`). They live together because each stage
7
+ * consumes the previous stage's output — the box/category vocabulary is the
8
+ * spine that keeps TBox schema and ABox individuals distinguishable end to end.
9
+ */
10
+
11
+ /** Whether an entity is schema (TBox) or an individual (ABox). */
12
+ export type EntityBox = "tbox" | "abox";
13
+
14
+ /** Fine-grained entity kind within its box. */
15
+ export type EntityCategory = "class" | "property" | "individual";
16
+
17
+ /** A literal object value in an entity read. */
18
+ export interface PropertyValueLiteral {
19
+ readonly type: "literal";
20
+ readonly value: string;
21
+ }
22
+
23
+ /** A URI object value in an entity read, resolved to a summary. */
24
+ export interface PropertyValueUri {
25
+ readonly type: "uri";
26
+ readonly uri: string;
27
+ readonly prefixed: string;
28
+ readonly label: string | null;
29
+ }
30
+
31
+ /** A blank-node object value in an entity read. */
32
+ export interface PropertyValueBlank {
33
+ readonly type: "bnode";
34
+ readonly id: string;
35
+ }
36
+
37
+ /** One object value of a predicate in an entity read. */
38
+ export type PropertyValue =
39
+ | PropertyValueLiteral
40
+ | PropertyValueUri
41
+ | PropertyValueBlank;
42
+
43
+ /** All object values asserted for a single predicate. */
44
+ export interface PropertyGroup {
45
+ readonly predicate: string;
46
+ readonly values: PropertyValue[];
47
+ }
48
+
49
+ /** One classified subject in the graph index. */
50
+ export interface GraphEntity {
51
+ readonly uri: string;
52
+ readonly prefixed: string;
53
+ readonly box: EntityBox;
54
+ readonly category: EntityCategory;
55
+ /** Compacted `rdf:type` values. */
56
+ readonly types: string[];
57
+ /** Compacted primary type used for grouping and naming (individuals). */
58
+ readonly primaryType: string | null;
59
+ /** Compacted primary type label, or the type's local name. */
60
+ readonly primaryTypeLabel: string | null;
61
+ readonly label: string | null;
62
+ readonly description: string | null;
63
+ }
64
+
65
+ /** The in-memory index of all named-node subjects, built via bulk queries. */
66
+ export interface GraphIndex {
67
+ readonly entities: GraphEntity[];
68
+ /** Full URI → resolved label, for cheap type-label and object-label joins. */
69
+ readonly labelByUri: ReadonlyMap<string, string>;
70
+ /** Full URI → count of subjects declaring it as an `rdf:type`. */
71
+ readonly instanceCountByType: ReadonlyMap<string, number>;
72
+ }
73
+
74
+ /** MCP resource annotations we emit (schema-valid subset). */
75
+ export interface ResourceAnnotations {
76
+ readonly audience: ("assistant" | "user")[];
77
+ readonly priority: number;
78
+ }
79
+
80
+ /** A single resource entry returned from the `list` callback. */
81
+ export interface ListedResource {
82
+ readonly uri: string;
83
+ readonly name: string;
84
+ readonly description?: string;
85
+ readonly mimeType: "application/json";
86
+ readonly annotations: ResourceAnnotations;
87
+ readonly _meta: Record<string, unknown>;
88
+ }
89
+
90
+ /** How many individuals a listing dropped, per capped class. */
91
+ export interface ListingTruncation {
92
+ readonly totalDropped: number;
93
+ readonly droppedByType: ReadonlyMap<string, number>;
94
+ }
95
+
96
+ /** Result of building a resource listing from the graph index. */
97
+ export interface ResourceListing {
98
+ readonly resources: ListedResource[];
99
+ readonly truncation: ListingTruncation;
100
+ }
101
+
102
+ /** A candidate for URI autocomplete: its compacted form and human label. */
103
+ export interface UriCompletionCandidate {
104
+ readonly prefixed: string;
105
+ readonly label: string | null;
106
+ }
107
+
108
+ /** A read entity payload, shaped by its box. */
109
+ export interface ResourceEntity {
110
+ readonly uri: string;
111
+ readonly prefixed: string;
112
+ readonly box: EntityBox;
113
+ readonly category: EntityCategory;
114
+ readonly types: string[];
115
+ readonly label: string | null;
116
+ readonly description: string | null;
117
+ readonly properties: PropertyGroup[];
118
+ /** Class read: compacted superclasses. */
119
+ readonly superClasses?: string[];
120
+ /** Class read: compacted direct subclasses. */
121
+ readonly subClasses?: string[];
122
+ /** Class read: number of asserted individuals. */
123
+ readonly instanceCount?: number;
124
+ /** Class read: compacted properties declaring this class as their domain. */
125
+ readonly declaredProperties?: string[];
126
+ /** Individual read: compacted primary class this individual belongs to. */
127
+ readonly instanceOf?: string | null;
128
+ }
@@ -18,6 +18,7 @@ import { specs as orientationSpecs } from "../../domains/llm/mcp/index.js";
18
18
  import { specs as modifierSpecs } from "../../domains/modifier/mcp/index.js";
19
19
  import { specs as ontologySpecs } from "../../domains/ontology/mcp/index.js";
20
20
  import type { PragmaRuntime } from "../../domains/shared/runtime.js";
21
+ import { compilePackToolSpecs } from "../../domains/shared/stories/pack/index.js";
21
22
  import type { ToolSpec } from "../../domains/shared/ToolSpec.js";
22
23
  import { specs as skillSpecs } from "../../domains/skill/mcp/index.js";
23
24
  import { specs as standardSpecs } from "../../domains/standard/mcp/index.js";
@@ -54,4 +55,12 @@ export default function registerAllTools(
54
55
  for (const spec of allSpecs) {
55
56
  registerFromSpec(server, runtime, spec);
56
57
  }
58
+
59
+ // Story packs project onto the same surface; built-in nouns are reserved.
60
+ const reservedNouns = new Set(
61
+ allSpecs.map((spec) => spec.name.split("_").at(0) ?? ""),
62
+ );
63
+ for (const spec of compilePackToolSpecs(runtime, reservedNouns)) {
64
+ registerFromSpec(server, runtime, spec);
65
+ }
57
66
  }
@@ -9,7 +9,12 @@ import { tmpdir } from "node:os";
9
9
  import { join } from "node:path";
10
10
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
11
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
12
- import { createTestMcpClient } from "#testing";
12
+ import {
13
+ createTestMcpClient,
14
+ RECIPE_PREFIXES,
15
+ RECIPE_STORY,
16
+ RECIPE_TTL,
17
+ } from "#testing";
13
18
  import type { McpErrorPayload } from "../types.js";
14
19
 
15
20
  let client: Client;
@@ -52,9 +57,9 @@ function parseData(result: Record<string, unknown>): unknown {
52
57
  // =============================================================================
53
58
 
54
59
  describe("tool listing", () => {
55
- it("registers 25 tools", async () => {
60
+ it("registers 30 tools", async () => {
56
61
  const { tools } = await client.listTools();
57
- expect(tools).toHaveLength(25);
62
+ expect(tools).toHaveLength(30);
58
63
  });
59
64
 
60
65
  it("all tools have descriptions", async () => {
@@ -94,6 +99,11 @@ describe("tool listing", () => {
94
99
  expect(names).toContain("doctor");
95
100
  expect(names).toContain("info");
96
101
  expect(names).toContain("capabilities");
102
+ // Sample tools
103
+ expect(names).toContain("block_sample");
104
+ expect(names).toContain("standard_sample");
105
+ expect(names).toContain("token_sample");
106
+ expect(names).toContain("modifier_sample");
97
107
  });
98
108
 
99
109
  it("no tool names contain pragma_ prefix", async () => {
@@ -503,6 +513,8 @@ describe("config_show", () => {
503
513
  describe("config_tier", () => {
504
514
  it("sets, queries, and resets tier in workspace config", async () => {
505
515
  const dir = mkdtempSync(join(tmpdir(), "pragma-mcp-tier-"));
516
+ // Seed a project file so global-first writes stay in this workspace.
517
+ writeFileSync(join(dir, "pragma.config.json"), "{}");
506
518
 
507
519
  try {
508
520
  const scoped = await createTestMcpClient({ cwd: dir });
@@ -512,7 +524,10 @@ describe("config_tier", () => {
512
524
  name: "config_tier",
513
525
  arguments: { path: "global" },
514
526
  });
515
- expect(parseData(setResult)).toEqual({ tier: "global", action: "set" });
527
+ expect(parseData(setResult)).toMatchObject({
528
+ tier: "global",
529
+ action: "set",
530
+ });
516
531
 
517
532
  const queryResult = await scoped.client.callTool({
518
533
  name: "config_tier",
@@ -527,7 +542,10 @@ describe("config_tier", () => {
527
542
  name: "config_tier",
528
543
  arguments: { reset: true },
529
544
  });
530
- expect(parseData(resetResult)).toEqual({ tier: null, action: "reset" });
545
+ expect(parseData(resetResult)).toMatchObject({
546
+ tier: null,
547
+ action: "reset",
548
+ });
531
549
 
532
550
  const queriedAfterReset = await scoped.client.callTool({
533
551
  name: "config_tier",
@@ -549,6 +567,8 @@ describe("config_tier", () => {
549
567
  describe("config_channel", () => {
550
568
  it("sets, queries, and resets channel in workspace config", async () => {
551
569
  const dir = mkdtempSync(join(tmpdir(), "pragma-mcp-channel-"));
570
+ // Seed a project file so global-first writes stay in this workspace.
571
+ writeFileSync(join(dir, "pragma.config.json"), "{}");
552
572
 
553
573
  try {
554
574
  const scoped = await createTestMcpClient({ cwd: dir });
@@ -558,7 +578,7 @@ describe("config_channel", () => {
558
578
  name: "config_channel",
559
579
  arguments: { value: "experimental" },
560
580
  });
561
- expect(parseData(setResult)).toEqual({
581
+ expect(parseData(setResult)).toMatchObject({
562
582
  channel: "experimental",
563
583
  action: "set",
564
584
  });
@@ -576,7 +596,7 @@ describe("config_channel", () => {
576
596
  name: "config_channel",
577
597
  arguments: { reset: true },
578
598
  });
579
- expect(parseData(resetResult)).toEqual({
599
+ expect(parseData(resetResult)).toMatchObject({
580
600
  channel: "normal",
581
601
  action: "reset",
582
602
  });
@@ -959,6 +979,48 @@ describe("graph_inspect", () => {
959
979
  // Skill tools
960
980
  // =============================================================================
961
981
 
982
+ describe("story-pack tools", () => {
983
+ it("registers <noun>_list and <noun>_lookup from a stories config", async () => {
984
+ const scoped = await createTestMcpClient({
985
+ ttl: RECIPE_TTL,
986
+ config: {
987
+ tier: undefined,
988
+ channel: "normal",
989
+ stories: [RECIPE_STORY],
990
+ prefixes: RECIPE_PREFIXES,
991
+ },
992
+ });
993
+
994
+ try {
995
+ const tools = (await scoped.client.listTools()).tools.map(
996
+ (tool) => tool.name,
997
+ );
998
+ expect(tools).toHaveLength(32);
999
+ expect(tools).toContain("recipe_list");
1000
+ expect(tools).toContain("recipe_lookup");
1001
+
1002
+ const listResult = await scoped.client.callTool({
1003
+ name: "recipe_list",
1004
+ arguments: {},
1005
+ });
1006
+ const rows = parseData(listResult) as { name: string }[];
1007
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho", "Pancakes"]);
1008
+
1009
+ const lookupResult = await scoped.client.callTool({
1010
+ name: "recipe_lookup",
1011
+ arguments: { names: ["pancakes"] },
1012
+ });
1013
+ const data = parseData(lookupResult) as {
1014
+ results: { name: string; instructions: string }[];
1015
+ };
1016
+ expect(data.results.at(0)?.name).toBe("Pancakes");
1017
+ expect(data.results.at(0)?.instructions).toBe("Mix, fry, flip.");
1018
+ } finally {
1019
+ await scoped.cleanup();
1020
+ }
1021
+ });
1022
+ });
1023
+
962
1024
  describe("skill_list", () => {
963
1025
  it("returns skills data", async () => {
964
1026
  const result = await client.callTool({
@@ -1020,13 +1082,16 @@ describe("info", () => {
1020
1082
  // =============================================================================
1021
1083
 
1022
1084
  describe("capabilities", () => {
1023
- it("returns accurate tool list and counts", async () => {
1085
+ it("returns enriched tool catalog with counts", async () => {
1024
1086
  const result = await client.callTool({
1025
1087
  name: "capabilities",
1026
1088
  arguments: {},
1027
1089
  });
1028
1090
  const data = parseData(result) as {
1029
- tools: string[];
1091
+ version: string;
1092
+ conventions: Record<string, string>;
1093
+ discovery_sequence: { stage: number; tool: string; purpose: string }[];
1094
+ tools: { name: string; category: string; use_when: string }[];
1030
1095
  counts: {
1031
1096
  total: number;
1032
1097
  read: number;
@@ -1034,14 +1099,21 @@ describe("capabilities", () => {
1034
1099
  orientation: number;
1035
1100
  diagnostic: number;
1036
1101
  };
1102
+ limits: Record<string, unknown>;
1037
1103
  };
1038
- expect(data.tools).toContain("block_list");
1039
- expect(data.tools).toContain("capabilities");
1040
- expect(data.counts.total).toBe(25);
1104
+ const toolNames = data.tools.map((t) => t.name);
1105
+ expect(toolNames).toContain("block_list");
1106
+ expect(toolNames).toContain("capabilities");
1107
+ expect(data.tools.every((t) => t.use_when.length > 0)).toBe(true);
1108
+ expect(data.counts.total).toBe(30);
1041
1109
  expect(data.counts.read).toBeGreaterThan(0);
1042
1110
  expect(data.counts.write).toBe(5);
1043
1111
  expect(data.counts.orientation).toBe(2);
1044
1112
  expect(data.counts.diagnostic).toBe(2);
1113
+ expect(data.version).toBeDefined();
1114
+ expect(data.conventions).toBeDefined();
1115
+ expect(data.discovery_sequence.length).toBe(3);
1116
+ expect(data.limits).toBeDefined();
1045
1117
  });
1046
1118
  });
1047
1119
 
package/src/mcp/types.ts CHANGED
@@ -25,6 +25,8 @@ export type ToolPayload =
25
25
  readonly meta?: {
26
26
  readonly count?: number;
27
27
  readonly filters?: Record<string, string>;
28
+ /** Queries that failed unexpectedly (surfaced only when > 0). */
29
+ readonly internalErrorCount?: number;
28
30
  };
29
31
  }
30
32
  | {
@@ -47,6 +49,8 @@ export interface ToolResponse<T = unknown> {
47
49
  readonly meta: {
48
50
  readonly count?: number;
49
51
  readonly filters?: Record<string, string>;
52
+ /** Queries that failed unexpectedly (surfaced only when > 0). */
53
+ readonly internalErrorCount?: number;
50
54
  };
51
55
  }
52
56
 
@@ -7,6 +7,7 @@ const stubRuntime = {
7
7
  store: {} as PragmaRuntime["store"],
8
8
  config: { tier: undefined, channel: "normal" },
9
9
  cwd: "/test",
10
+ packages: [],
10
11
  dispose: () => {},
11
12
  } as PragmaRuntime;
12
13