@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
@@ -1,7 +1,13 @@
1
1
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
2
2
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
3
- import { createTestMcpClient } from "#testing";
4
- import { P } from "../domains/shared/prefixes.js";
3
+ import { createTestMcpClient, createTestStore } from "#testing";
4
+ import { P, PREFIX_MAP } from "../domains/shared/prefixes.js";
5
+ import type { PragmaRuntime } from "../domains/shared/runtime.js";
6
+ import createClientFromRuntime from "../testing/helpers/createTestMcpClient.js";
7
+ import {
8
+ ABOX_PER_CLASS_LIMIT,
9
+ COMPLETION_LIMIT,
10
+ } from "./resources/constants.js";
5
11
 
6
12
  let client: Client;
7
13
  let cleanup: () => Promise<void>;
@@ -22,102 +28,409 @@ interface ResourceContent {
22
28
  text: string;
23
29
  }
24
30
 
31
+ interface ListedMeta {
32
+ "pragma/box"?: string;
33
+ "pragma/category"?: string;
34
+ "pragma/type"?: string | null;
35
+ "pragma/instanceCount"?: number;
36
+ "pragma/instancesShown"?: number;
37
+ "pragma/truncated"?: boolean;
38
+ }
39
+
25
40
  function parseContents(result: { contents: unknown[] }): unknown {
26
- const first = result.contents[0] as ResourceContent;
41
+ const first = result.contents.at(0) as ResourceContent;
27
42
  return JSON.parse(first.text);
28
43
  }
29
44
 
45
+ function metaOf(resource: { _meta?: unknown }): ListedMeta {
46
+ return (resource._meta ?? {}) as ListedMeta;
47
+ }
48
+
49
+ /**
50
+ * Build an MCP client over a store with the given TTL and extra prefixes.
51
+ * Lets foreign/overlapping namespaces be registered, which the shared
52
+ * `createTestMcpClient` (fixed prefix set) cannot express.
53
+ */
54
+ async function createClientForTtl(
55
+ ttl: string,
56
+ prefixes?: Record<string, string>,
57
+ ): Promise<{ client: Client; cleanup: () => Promise<void> }> {
58
+ const { store, cleanup: closeStore } = await createTestStore({
59
+ ttl,
60
+ prefixes,
61
+ });
62
+ const runtime: PragmaRuntime = {
63
+ store,
64
+ config: { tier: undefined, channel: "normal" },
65
+ cwd: process.cwd(),
66
+ packages: [],
67
+ dispose: () => closeStore(),
68
+ };
69
+ const { client: scopedClient, cleanup: closeClient } =
70
+ await createClientFromRuntime(runtime);
71
+ return {
72
+ client: scopedClient,
73
+ cleanup: async () => {
74
+ await closeClient();
75
+ runtime.dispose();
76
+ },
77
+ };
78
+ }
79
+
30
80
  // =============================================================================
31
- // Resource listing
81
+ // Resource template
32
82
  // =============================================================================
33
83
 
34
- describe("resource listing", () => {
35
- it("registers 1 resource template", async () => {
84
+ describe("resource template", () => {
85
+ it("registers 1 resource template with the {+uri} pattern", async () => {
36
86
  const { resourceTemplates } = await client.listResourceTemplates();
37
87
  expect(resourceTemplates).toHaveLength(1);
38
- expect(resourceTemplates[0]?.uriTemplate).toBe("pragma:{+uri}");
88
+ expect(resourceTemplates.at(0)?.uriTemplate).toBe("{+uri}");
89
+ });
90
+
91
+ it("documents the TBox/ABox split in the template description", async () => {
92
+ const { resourceTemplates } = await client.listResourceTemplates();
93
+ expect(resourceTemplates.at(0)?.description).toMatch(/TBox/);
39
94
  });
95
+ });
96
+
97
+ // =============================================================================
98
+ // Resource listing — presence
99
+ // =============================================================================
40
100
 
41
- it("lists resources including known entities", async () => {
101
+ describe("resource listing — presence", () => {
102
+ it("lists known instances, OWL classes, and code standards", async () => {
42
103
  const { resources } = await client.listResources();
43
104
  const uris = resources.map((r) => r.uri);
44
- expect(uris).toContain(`pragma:${P.ds}global.component.button`);
45
- expect(uris).toContain(`pragma:${P.ds}global.component.card`);
46
- expect(uris).toContain(`pragma:${P.ds}global`);
105
+ expect(uris).toContain(`${P.ds}global.component.button`);
106
+ expect(uris).toContain(`${P.ds}global`);
107
+ expect(uris).toContain(`${P.ds}UIBlock`);
108
+ expect(uris).toContain(`${P.ds}Component`);
109
+ expect(uris).toContain(`${P.cs}react_folder`);
110
+ expect(uris).toContain(`${P.cs}code_purity`);
47
111
  });
112
+ });
113
+
114
+ // =============================================================================
115
+ // Resource listing — TBox/ABox separation
116
+ // =============================================================================
48
117
 
49
- it("lists OWL classes as resources", async () => {
118
+ describe("resource listing — TBox/ABox separation", () => {
119
+ it("tags classes as tbox/class in _meta", async () => {
50
120
  const { resources } = await client.listResources();
51
- const uris = resources.map((r) => r.uri);
52
- expect(uris).toContain(`pragma:${P.ds}UIBlock`);
53
- expect(uris).toContain(`pragma:${P.ds}Component`);
121
+ const uiBlock = resources.find((r) => r.uri === `${P.ds}UIBlock`);
122
+ expect(metaOf(uiBlock ?? {})["pragma/box"]).toBe("tbox");
123
+ expect(metaOf(uiBlock ?? {})["pragma/category"]).toBe("class");
54
124
  });
55
125
 
56
- it("lists code standards as resources", async () => {
126
+ it("tags properties as tbox/property in _meta", async () => {
57
127
  const { resources } = await client.listResources();
58
- const uris = resources.map((r) => r.uri);
59
- expect(uris).toContain(`pragma:${P.cs}react_folder`);
60
- expect(uris).toContain(`pragma:${P.cs}code_purity`);
128
+ const tier = resources.find((r) => r.uri === `${P.ds}tier`);
129
+ expect(metaOf(tier ?? {})["pragma/box"]).toBe("tbox");
130
+ expect(metaOf(tier ?? {})["pragma/category"]).toBe("property");
61
131
  });
62
132
 
63
- it("uses label as resource name when available", async () => {
133
+ it("tags individuals as abox/individual with their class in _meta", async () => {
64
134
  const { resources } = await client.listResources();
65
135
  const button = resources.find(
66
- (r) => r.uri === `pragma:${P.ds}global.component.button`,
136
+ (r) => r.uri === `${P.ds}global.component.button`,
67
137
  );
68
- expect(button?.name).toBe("Button");
138
+ expect(metaOf(button ?? {})["pragma/box"]).toBe("abox");
139
+ expect(metaOf(button ?? {})["pragma/category"]).toBe("individual");
140
+ expect(metaOf(button ?? {})["pragma/type"]).toBe(`${P.ds}Component`);
141
+ });
142
+
143
+ it("places all TBox schema before any ABox individual", async () => {
144
+ const { resources } = await client.listResources();
145
+ const firstAbox = resources.findIndex(
146
+ (r) => metaOf(r)["pragma/box"] === "abox",
147
+ );
148
+ const lastTbox = resources.reduce(
149
+ (acc, r, i) => (metaOf(r)["pragma/box"] === "tbox" ? i : acc),
150
+ -1,
151
+ );
152
+ expect(firstAbox).toBeGreaterThan(-1);
153
+ expect(lastTbox).toBeGreaterThan(-1);
154
+ expect(lastTbox).toBeLessThan(firstAbox);
155
+ });
156
+
157
+ it("ranks schema above individuals via annotation priority", async () => {
158
+ const { resources } = await client.listResources();
159
+ const cls = resources.find((r) => r.uri === `${P.ds}Component`);
160
+ const individual = resources.find(
161
+ (r) => r.uri === `${P.ds}global.component.button`,
162
+ );
163
+ const clsPriority = cls?.annotations?.priority ?? 0;
164
+ const individualPriority = individual?.annotations?.priority ?? 1;
165
+ expect(clsPriority).toBeGreaterThan(individualPriority);
166
+ });
167
+ });
168
+
169
+ // =============================================================================
170
+ // Resource listing — names, descriptions, grouping
171
+ // =============================================================================
172
+
173
+ describe("resource listing — names and grouping", () => {
174
+ it("prefixes class names with their category", async () => {
175
+ const { resources } = await client.listResources();
176
+ const cls = resources.find((r) => r.uri === `${P.ds}Component`);
177
+ expect(cls?.name).toBe("Class · Component");
178
+ });
179
+
180
+ it("prefixes property names with their category", async () => {
181
+ const { resources } = await client.listResources();
182
+ const prop = resources.find((r) => r.uri === `${P.ds}tier`);
183
+ expect(prop?.name).toBe("Property · tier");
184
+ });
185
+
186
+ it("groups individuals under their class label in the name", async () => {
187
+ const { resources } = await client.listResources();
188
+ const button = resources.find(
189
+ (r) => r.uri === `${P.ds}global.component.button`,
190
+ );
191
+ expect(button?.name).toBe("Component · Button");
192
+ });
193
+
194
+ it("surfaces instance counts on class entries", async () => {
195
+ const { resources } = await client.listResources();
196
+ const cls = resources.find((r) => r.uri === `${P.ds}Component`);
197
+ expect(metaOf(cls ?? {})["pragma/instanceCount"]).toBe(4);
198
+ expect(metaOf(cls ?? {})["pragma/truncated"]).toBe(false);
199
+ expect(cls?.description).toMatch(/4 individuals/);
200
+ });
201
+
202
+ it("leads an individual description with its class label", async () => {
203
+ const { resources } = await client.listResources();
204
+ const button = resources.find(
205
+ (r) => r.uri === `${P.ds}global.component.button`,
206
+ );
207
+ expect(button?.description).toMatch(/^Component/);
208
+ });
209
+
210
+ it("marks resources with assistant audience", async () => {
211
+ const { resources } = await client.listResources();
212
+ const button = resources.find(
213
+ (r) => r.uri === `${P.ds}global.component.button`,
214
+ );
215
+ expect(button?.annotations?.audience).toEqual(["assistant"]);
216
+ });
217
+ });
218
+
219
+ // =============================================================================
220
+ // Resource listing — capping the ABox long tail
221
+ // =============================================================================
222
+
223
+ describe("resource listing — capping", () => {
224
+ it("caps individuals per class and reports the remainder", async () => {
225
+ const overflow = ABOX_PER_CLASS_LIMIT + 5;
226
+ const individuals = Array.from(
227
+ { length: overflow },
228
+ (_, i) => `ds:widget_${i} a ds:Gadget ; ds:name "Widget ${i}" .`,
229
+ ).join("\n");
230
+ const ttl = `@prefix ds: <${PREFIX_MAP.ds}> .
231
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
232
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
233
+
234
+ ds:Gadget a owl:Class ; rdfs:label "Gadget" .
235
+ ds:name a owl:DatatypeProperty .
236
+
237
+ ${individuals}
238
+ `;
239
+ const scoped = await createClientForTtl(ttl);
240
+ try {
241
+ const { resources } = await scoped.client.listResources();
242
+ const shown = resources.filter(
243
+ (r) => metaOf(r)["pragma/type"] === `${P.ds}Gadget`,
244
+ );
245
+ expect(shown).toHaveLength(ABOX_PER_CLASS_LIMIT);
246
+
247
+ const cls = resources.find((r) => r.uri === `${P.ds}Gadget`);
248
+ expect(metaOf(cls ?? {})["pragma/instanceCount"]).toBe(overflow);
249
+ expect(metaOf(cls ?? {})["pragma/instancesShown"]).toBe(
250
+ ABOX_PER_CLASS_LIMIT,
251
+ );
252
+ expect(metaOf(cls ?? {})["pragma/truncated"]).toBe(true);
253
+ expect(cls?.description).toMatch(
254
+ new RegExp(`showing ${ABOX_PER_CLASS_LIMIT} of ${overflow}`),
255
+ );
256
+ } finally {
257
+ await scoped.cleanup();
258
+ }
259
+ });
260
+ });
261
+
262
+ // =============================================================================
263
+ // Resource listing — foreign namespace label fallback
264
+ // =============================================================================
265
+
266
+ describe("resource listing — foreign namespace label fallback", () => {
267
+ const ttl = `
268
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
269
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
270
+ @prefix skos: <http://www.w3.org/2004/02/skos/core#> .
271
+ @prefix dcterms: <http://purl.org/dc/terms/> .
272
+ @prefix schema: <http://schema.org/> .
273
+ @prefix ex: <http://foreign.example/> .
274
+
275
+ ex:Widget a owl:Class ; skos:prefLabel "Foreign Widget" .
276
+ ex:gadget a ex:Widget ; dcterms:title "Shiny Gadget" .
277
+ ex:thing a ex:Widget ; schema:name "Plain Thing" .
278
+ `;
279
+
280
+ it("resolves a foreign class name via skos:prefLabel", async () => {
281
+ const scoped = await createClientForTtl(ttl, {
282
+ ex: "http://foreign.example/",
283
+ });
284
+ try {
285
+ const { resources } = await scoped.client.listResources();
286
+ const cls = resources.find((r) => r.uri === "ex:Widget");
287
+ expect(cls?.name).toBe("Class · Foreign Widget");
288
+ } finally {
289
+ await scoped.cleanup();
290
+ }
291
+ });
292
+
293
+ it("resolves foreign individual names via dcterms:title and schema:name", async () => {
294
+ const scoped = await createClientForTtl(ttl, {
295
+ ex: "http://foreign.example/",
296
+ });
297
+ try {
298
+ const { resources } = await scoped.client.listResources();
299
+ const gadget = resources.find((r) => r.uri === "ex:gadget");
300
+ const thing = resources.find((r) => r.uri === "ex:thing");
301
+ expect(gadget?.name).toBe("Foreign Widget · Shiny Gadget");
302
+ expect(thing?.name).toBe("Foreign Widget · Plain Thing");
303
+ } finally {
304
+ await scoped.cleanup();
305
+ }
69
306
  });
70
307
  });
71
308
 
72
309
  // =============================================================================
73
- // Read instances
310
+ // Resource listing — longest-namespace prefix resolution
311
+ // =============================================================================
312
+
313
+ describe("resource listing — longest-namespace prefix resolution", () => {
314
+ it("compacts with the longest matching namespace", async () => {
315
+ const ttl = `
316
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
317
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
318
+ @prefix base: <http://ex.org/> .
319
+ @prefix sub: <http://ex.org/sub/> .
320
+
321
+ base:Thing a owl:Class ; rdfs:label "Thing" .
322
+ sub:Widget a owl:Class ; rdfs:label "Widget" .
323
+ `;
324
+ // `base` is registered before `sub`; first-match compaction would
325
+ // mis-compact to "base:sub/Widget".
326
+ const scoped = await createClientForTtl(ttl, {
327
+ base: "http://ex.org/",
328
+ sub: "http://ex.org/sub/",
329
+ });
330
+ try {
331
+ const { resources } = await scoped.client.listResources();
332
+ const uris = resources.map((r) => r.uri);
333
+ expect(uris).toContain("sub:Widget");
334
+ expect(uris).not.toContain("base:sub/Widget");
335
+ } finally {
336
+ await scoped.cleanup();
337
+ }
338
+ });
339
+ });
340
+
341
+ // =============================================================================
342
+ // Autocomplete
343
+ // =============================================================================
344
+
345
+ async function completeUri(value: string): Promise<{
346
+ values: string[];
347
+ total?: number;
348
+ }> {
349
+ const result = await client.complete({
350
+ ref: { type: "ref/resource", uri: "{+uri}" },
351
+ argument: { name: "uri", value },
352
+ });
353
+ return result.completion;
354
+ }
355
+
356
+ describe("autocomplete", () => {
357
+ it("matches a URI substring, not just a prefix", async () => {
358
+ const { values } = await completeUri("button");
359
+ expect(values).toContain(`${P.ds}global.component.button`);
360
+ });
361
+
362
+ it("matches a human label fragment", async () => {
363
+ const { values } = await completeUri("Card");
364
+ expect(values).toContain(`${P.ds}global.component.card`);
365
+ });
366
+
367
+ it("returns compacted URIs, not full URIs", async () => {
368
+ const { values } = await completeUri("button");
369
+ expect(values.every((v) => !v.startsWith("http"))).toBe(true);
370
+ });
371
+
372
+ it("ranks an exact label match above a substring match", async () => {
373
+ const { values } = await completeUri("component");
374
+ const exactIdx = values.indexOf(`${P.ds}Component`);
375
+ const substringIdx = values.indexOf(`${P.ds}global.component.button`);
376
+ expect(exactIdx).toBeGreaterThan(-1);
377
+ expect(substringIdx).toBeGreaterThan(-1);
378
+ expect(exactIdx).toBeLessThan(substringIdx);
379
+ });
380
+
381
+ it("caps the number of suggestions", async () => {
382
+ const { values } = await completeUri("");
383
+ expect(values.length).toBeLessThanOrEqual(COMPLETION_LIMIT);
384
+ expect(values).toHaveLength(COMPLETION_LIMIT);
385
+ });
386
+ });
387
+
388
+ // =============================================================================
389
+ // Read — component instance (ABox)
74
390
  // =============================================================================
75
391
 
76
392
  describe("read component instance", () => {
77
- it("returns entity with types and label", async () => {
393
+ it("returns an abox individual with types, label, and class pointer", async () => {
78
394
  const result = await client.readResource({
79
- uri: `pragma:${P.ds}global.component.button`,
395
+ uri: `${P.ds}global.component.button`,
80
396
  });
81
397
  const entity = parseContents(result) as {
82
- uri: string;
83
398
  prefixed: string;
399
+ box: string;
400
+ category: string;
84
401
  types: string[];
85
402
  label: string | null;
86
- properties: { predicate: string; values: unknown[] }[];
403
+ instanceOf: string | null;
87
404
  };
88
405
  expect(entity.prefixed).toBe(`${P.ds}global.component.button`);
406
+ expect(entity.box).toBe("abox");
407
+ expect(entity.category).toBe("individual");
89
408
  expect(entity.types).toContain(`${P.ds}Component`);
90
409
  expect(entity.label).toBe("Button");
410
+ expect(entity.instanceOf).toBe(`${P.ds}Component`);
91
411
  });
92
412
 
93
413
  it("resolves level-1 URI objects to summaries", async () => {
94
414
  const result = await client.readResource({
95
- uri: `pragma:${P.ds}global.component.button`,
415
+ uri: `${P.ds}global.component.button`,
96
416
  });
97
417
  const entity = parseContents(result) as {
98
418
  properties: {
99
419
  predicate: string;
100
- values: {
101
- type: string;
102
- uri?: string;
103
- prefixed?: string;
104
- label?: string | null;
105
- }[];
420
+ values: { type: string; prefixed?: string }[];
106
421
  }[];
107
422
  };
108
-
109
423
  const tierProp = entity.properties.find(
110
424
  (p) => p.predicate === `${P.ds}tier`,
111
425
  );
112
- expect(tierProp).toBeDefined();
113
- const tierValue = tierProp?.values[0];
426
+ const tierValue = tierProp?.values.at(0);
114
427
  expect(tierValue?.type).toBe("uri");
115
428
  expect(tierValue?.prefixed).toBe(`${P.ds}global`);
116
429
  });
117
430
 
118
431
  it("includes literal values", async () => {
119
432
  const result = await client.readResource({
120
- uri: `pragma:${P.ds}global.component.button`,
433
+ uri: `${P.ds}global.component.button`,
121
434
  });
122
435
  const entity = parseContents(result) as {
123
436
  properties: {
@@ -125,53 +438,85 @@ describe("read component instance", () => {
125
438
  values: { type: string; value?: string }[];
126
439
  }[];
127
440
  };
128
-
129
441
  const nameProp = entity.properties.find(
130
442
  (p) => p.predicate === `${P.ds}name`,
131
443
  );
132
- expect(nameProp).toBeDefined();
133
- expect(nameProp?.values[0]?.type).toBe("literal");
134
- expect(nameProp?.values[0]?.value).toBe("Button");
444
+ expect(nameProp?.values.at(0)?.type).toBe("literal");
445
+ expect(nameProp?.values.at(0)?.value).toBe("Button");
446
+ });
447
+
448
+ it("keeps URL-shaped literals as literals, not URIs", async () => {
449
+ const result = await client.readResource({
450
+ uri: `${P.ds}global.component.button`,
451
+ });
452
+ const entity = parseContents(result) as {
453
+ properties: {
454
+ predicate: string;
455
+ values: { type: string; value?: string }[];
456
+ }[];
457
+ };
458
+ const figma = entity.properties.find(
459
+ (p) => p.predicate === `${P.ds}figmaLink`,
460
+ );
461
+ const value = figma?.values.at(0);
462
+ expect(value?.type).toBe("literal");
463
+ expect(value?.value).toBe("https://figma.com/design/example/Button");
135
464
  });
136
465
  });
137
466
 
138
467
  // =============================================================================
139
- // Read OWL classes
468
+ // Read — OWL class (TBox)
140
469
  // =============================================================================
141
470
 
142
471
  describe("read OWL class", () => {
143
- it("returns class with rdfs:label", async () => {
144
- const result = await client.readResource({
145
- uri: `pragma:${P.ds}UIBlock`,
146
- });
472
+ it("returns a tbox class enriched with schema relations", async () => {
473
+ const result = await client.readResource({ uri: `${P.ds}UIBlock` });
147
474
  const entity = parseContents(result) as {
475
+ box: string;
476
+ category: string;
148
477
  types: string[];
149
- label: string | null;
478
+ subClasses?: string[];
479
+ instanceCount?: number;
480
+ declaredProperties?: string[];
150
481
  properties: {
151
482
  predicate: string;
152
483
  values: { type: string; value?: string }[];
153
484
  }[];
154
485
  };
486
+ expect(entity.box).toBe("tbox");
487
+ expect(entity.category).toBe("class");
155
488
  expect(entity.types).toContain(`${P.owl}Class`);
489
+ expect(entity.subClasses).toContain(`${P.ds}Component`);
490
+ expect(entity.subClasses).toContain(`${P.ds}Subcomponent`);
491
+ expect(entity.declaredProperties).toContain(`${P.ds}tier`);
492
+ expect(entity.instanceCount).toBe(0);
493
+
156
494
  const labelProp = entity.properties.find(
157
495
  (p) => p.predicate === `${P.rdfs}label`,
158
496
  );
159
- expect(labelProp?.values[0]?.value).toBe("UI Block");
497
+ expect(labelProp?.values.at(0)?.value).toBe("UI Block");
498
+ });
499
+
500
+ it("reports superclasses and instance count for a subclass", async () => {
501
+ const result = await client.readResource({ uri: `${P.ds}Component` });
502
+ const entity = parseContents(result) as {
503
+ superClasses?: string[];
504
+ instanceCount?: number;
505
+ };
506
+ expect(entity.superClasses).toContain(`${P.ds}UIBlock`);
507
+ expect(entity.instanceCount).toBe(4);
160
508
  });
161
509
  });
162
510
 
163
511
  // =============================================================================
164
- // Read code standards
512
+ // Read — code standard and tier
165
513
  // =============================================================================
166
514
 
167
515
  describe("read code standard", () => {
168
516
  it("returns standard with properties", async () => {
169
- const result = await client.readResource({
170
- uri: `pragma:${P.cs}code_purity`,
171
- });
517
+ const result = await client.readResource({ uri: `${P.cs}code_purity` });
172
518
  const entity = parseContents(result) as {
173
519
  types: string[];
174
- label: string | null;
175
520
  properties: {
176
521
  predicate: string;
177
522
  values: { type: string; value?: string }[];
@@ -182,41 +527,28 @@ describe("read code standard", () => {
182
527
  const nameProp = entity.properties.find(
183
528
  (p) => p.predicate === `${P.cs}name`,
184
529
  );
185
- expect(nameProp?.values[0]?.value).toBe("code/function/purity");
530
+ expect(nameProp?.values.at(0)?.value).toBe("code/function/purity");
186
531
 
187
532
  const doProp = entity.properties.find((p) => p.predicate === `${P.cs}do`);
188
- expect(doProp).toBeDefined();
189
533
  expect(doProp?.values.length).toBeGreaterThan(0);
190
-
191
- const dontProp = entity.properties.find(
192
- (p) => p.predicate === `${P.cs}dont`,
193
- );
194
- expect(dontProp).toBeDefined();
195
534
  });
196
535
  });
197
536
 
198
- // =============================================================================
199
- // Read tiers
200
- // =============================================================================
201
-
202
537
  describe("read tier", () => {
203
538
  it("returns tier with name", async () => {
204
- const result = await client.readResource({
205
- uri: `pragma:${P.ds}apps_lxd`,
206
- });
539
+ const result = await client.readResource({ uri: `${P.ds}apps_lxd` });
207
540
  const entity = parseContents(result) as {
208
541
  types: string[];
209
542
  properties: {
210
543
  predicate: string;
211
- values: { type: string; value?: string; prefixed?: string }[];
544
+ values: { type: string; value?: string }[];
212
545
  }[];
213
546
  };
214
547
  expect(entity.types).toContain(`${P.ds}Tier`);
215
-
216
548
  const nameProp = entity.properties.find(
217
549
  (p) => p.predicate === `${P.ds}name`,
218
550
  );
219
- expect(nameProp?.values[0]?.value).toBe("apps/lxd");
551
+ expect(nameProp?.values.at(0)?.value).toBe("apps/lxd");
220
552
  });
221
553
  });
222
554
 
@@ -226,10 +558,8 @@ describe("read tier", () => {
226
558
 
227
559
  describe("error handling", () => {
228
560
  it("returns text/plain error for unknown entity", async () => {
229
- const result = await client.readResource({
230
- uri: `pragma:${P.ds}nonexistent`,
231
- });
232
- const first = result.contents[0] as ResourceContent;
561
+ const result = await client.readResource({ uri: `${P.ds}nonexistent` });
562
+ const first = result.contents.at(0) as ResourceContent;
233
563
  expect(first.mimeType).toBe("text/plain");
234
564
  expect(first.text).toContain("ENTITY_NOT_FOUND");
235
565
  });