@canonical/pragma-cli 0.29.0 → 0.31.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (410) hide show
  1. package/README.md +128 -47
  2. package/dist/pragma +0 -0
  3. package/package.json +15 -13
  4. package/src/completions/constants.ts +7 -0
  5. package/src/completions/handleQuery.test.ts +32 -0
  6. package/src/completions/handleQuery.ts +20 -1
  7. package/src/completions/queryCompletions.test.ts +5 -0
  8. package/src/completions/queryCompletions.ts +17 -3
  9. package/src/completions/querySocket.test.ts +31 -0
  10. package/src/completions/querySocket.ts +36 -9
  11. package/src/config/defaultConfig.test.ts +33 -0
  12. package/src/config/defaultConfig.ts +59 -0
  13. package/src/config/defaults.json +17 -0
  14. package/src/config/findProjectConfigPath.test.ts +53 -0
  15. package/src/config/findProjectConfigPath.ts +36 -0
  16. package/src/config/index.ts +17 -2
  17. package/src/config/mergeConfigFileUpdate.ts +58 -0
  18. package/src/config/parseConfigValues.ts +217 -0
  19. package/src/config/readConfig.test.ts +104 -13
  20. package/src/config/readConfig.ts +12 -153
  21. package/src/config/readConfigLayers.test.ts +109 -0
  22. package/src/config/readConfigLayers.ts +118 -0
  23. package/src/config/resolveGlobalConfigPath.ts +13 -0
  24. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  25. package/src/config/resolveWriteConfigPath.ts +31 -0
  26. package/src/config/types.ts +83 -1
  27. package/src/config/writeConfig.ts +25 -55
  28. package/src/constants.ts +3 -1
  29. package/src/domains/block/commands/list.ts +5 -90
  30. package/src/domains/block/commands/lookup.ts +8 -167
  31. package/src/domains/block/commands/sample.ts +2 -1
  32. package/src/domains/block/formatters/list.test.ts +3 -3
  33. package/src/domains/block/formatters/lookup.test.ts +5 -5
  34. package/src/domains/block/mcp/specs.ts +16 -196
  35. package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
  36. package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
  37. package/src/domains/block/stories.test.ts +28 -0
  38. package/src/domains/block/stories.ts +260 -0
  39. package/src/domains/config/commands/channel.test.ts +30 -3
  40. package/src/domains/config/commands/channel.ts +25 -8
  41. package/src/domains/config/commands/configScope.ts +46 -0
  42. package/src/domains/config/commands/framework.test.ts +32 -1
  43. package/src/domains/config/commands/framework.ts +26 -8
  44. package/src/domains/config/commands/show.ts +5 -45
  45. package/src/domains/config/commands/tier.test.ts +20 -2
  46. package/src/domains/config/commands/tier.ts +25 -8
  47. package/src/domains/config/commands/trace.test.ts +19 -2
  48. package/src/domains/config/commands/trace.ts +6 -3
  49. package/src/domains/config/formatters/channel.test.ts +20 -6
  50. package/src/domains/config/formatters/channel.ts +14 -11
  51. package/src/domains/config/formatters/framework.ts +14 -11
  52. package/src/domains/config/formatters/show.test.ts +27 -0
  53. package/src/domains/config/formatters/show.ts +27 -4
  54. package/src/domains/config/formatters/tier.test.ts +26 -10
  55. package/src/domains/config/formatters/tier.ts +19 -11
  56. package/src/domains/config/formatters/trace.ts +7 -7
  57. package/src/domains/config/mcp/specs.ts +33 -53
  58. package/src/domains/config/operations/show.test.ts +18 -0
  59. package/src/domains/config/operations/show.ts +7 -1
  60. package/src/domains/config/operations/types.ts +7 -0
  61. package/src/domains/config/operations/validateTier.test.ts +9 -0
  62. package/src/domains/config/operations/validateTier.ts +10 -2
  63. package/src/domains/config/stories.ts +42 -0
  64. package/src/domains/config/tasks/index.ts +1 -0
  65. package/src/domains/config/tasks/setChannel.ts +13 -28
  66. package/src/domains/config/tasks/setFramework.ts +11 -30
  67. package/src/domains/config/tasks/setTier.ts +10 -29
  68. package/src/domains/config/tasks/setTrace.ts +10 -28
  69. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  70. package/src/domains/create/generatorPack/bundled.ts +49 -0
  71. package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
  72. package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
  73. package/src/domains/create/index.ts +13 -7
  74. package/src/domains/create/mcp/specs.ts +6 -161
  75. package/src/domains/create/renderGeneratorUi.test.ts +103 -0
  76. package/src/domains/create/renderGeneratorUi.ts +65 -0
  77. package/src/domains/doctor/formatters/doctor.test.ts +50 -2
  78. package/src/domains/doctor/formatters/doctor.ts +90 -38
  79. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  80. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +133 -0
  81. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  82. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +32 -26
  83. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  84. package/src/domains/doctor/operations/checks/index.ts +1 -0
  85. package/src/domains/doctor/operations/checks.test.ts +160 -7
  86. package/src/domains/doctor/operations/runChecks.test.ts +15 -6
  87. package/src/domains/doctor/operations/runChecks.ts +2 -0
  88. package/src/domains/doctor/operations/types.ts +23 -1
  89. package/src/domains/graph/commands/inspect.test.ts +2 -2
  90. package/src/domains/graph/commands/query.test.ts +5 -3
  91. package/src/domains/graph/formatters/inspect.ts +7 -7
  92. package/src/domains/graph/formatters/query.test.ts +87 -0
  93. package/src/domains/graph/formatters/query.ts +42 -5
  94. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  95. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  96. package/src/domains/graph/operations/inspectUri.test.ts +5 -5
  97. package/src/domains/graphql/commands/build.test.ts +5 -3
  98. package/src/domains/graphql/commands/build.ts +8 -16
  99. package/src/domains/graphql/commands/check.test.ts +5 -3
  100. package/src/domains/graphql/commands/check.ts +7 -13
  101. package/src/domains/graphql/commands/index.ts +1 -0
  102. package/src/domains/graphql/commands/serve.test.ts +46 -0
  103. package/src/domains/graphql/commands/serve.ts +141 -0
  104. package/src/domains/graphql/constants.ts +10 -0
  105. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  106. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  107. package/src/domains/graphql/helpers/index.ts +2 -0
  108. package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
  109. package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
  110. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
  111. package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
  112. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  113. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  114. package/src/domains/graphql/index.ts +9 -6
  115. package/src/domains/graphql/operations/compileSchema.test.ts +23 -47
  116. package/src/domains/graphql/operations/compileSchema.ts +23 -25
  117. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  118. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  119. package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
  120. package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
  121. package/src/domains/graphql/operations/index.ts +10 -0
  122. package/src/domains/info/formatters/upgrade.test.ts +7 -4
  123. package/src/domains/info/formatters/upgrade.ts +3 -1
  124. package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
  125. package/src/domains/info/operations/collectEntityCounts.ts +123 -0
  126. package/src/domains/info/operations/collectInfo.test.ts +14 -3
  127. package/src/domains/info/operations/collectInfo.ts +12 -15
  128. package/src/domains/info/operations/index.ts +1 -0
  129. package/src/domains/info/types.ts +13 -0
  130. package/src/domains/llm/capabilities.test.ts +24 -4
  131. package/src/domains/llm/commands/capabilities.ts +12 -3
  132. package/src/domains/llm/commands/llm.ts +5 -1
  133. package/src/domains/llm/data/commandReference.ts +20 -11
  134. package/src/domains/llm/data/decisionTrees.ts +10 -5
  135. package/src/domains/llm/data/toolCatalog.ts +55 -18
  136. package/src/domains/llm/formatters/orientation.ts +5 -1
  137. package/src/domains/llm/llmCommand.test.ts +11 -3
  138. package/src/domains/llm/mcp/specs.ts +3 -4
  139. package/src/domains/llm/operations/collectContext.test.ts +64 -0
  140. package/src/domains/llm/operations/collectContext.ts +7 -17
  141. package/src/domains/modifier/commands/list.ts +5 -38
  142. package/src/domains/modifier/commands/lookup.ts +8 -79
  143. package/src/domains/modifier/commands/sample.ts +2 -1
  144. package/src/domains/modifier/mcp/specs.ts +15 -94
  145. package/src/domains/modifier/operations/lookup.ts +5 -3
  146. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
  147. package/src/domains/modifier/stories.test.ts +20 -0
  148. package/src/domains/modifier/stories.ts +86 -0
  149. package/src/domains/ontology/commands/list.test.ts +5 -3
  150. package/src/domains/ontology/commands/list.ts +5 -58
  151. package/src/domains/ontology/commands/show.test.ts +85 -9
  152. package/src/domains/ontology/commands/show.ts +6 -58
  153. package/src/domains/ontology/formatters/index.ts +1 -0
  154. package/src/domains/ontology/formatters/list.ts +37 -12
  155. package/src/domains/ontology/formatters/show.ts +361 -64
  156. package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
  157. package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
  158. package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
  159. package/src/domains/ontology/helpers/index.ts +14 -0
  160. package/src/domains/ontology/helpers/queryClasses.ts +32 -16
  161. package/src/domains/ontology/helpers/queryProperties.ts +39 -6
  162. package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
  163. package/src/domains/ontology/mcp/specs.ts +7 -84
  164. package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
  165. package/src/domains/ontology/operations/listOntologies.ts +126 -30
  166. package/src/domains/ontology/operations/showOntology.test.ts +131 -12
  167. package/src/domains/ontology/operations/showOntology.ts +289 -7
  168. package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
  169. package/src/domains/ontology/stories.test.ts +36 -0
  170. package/src/domains/ontology/stories.ts +153 -0
  171. package/src/domains/refs/operations/updateRefs.ts +2 -38
  172. package/src/domains/setup/commands/index.ts +1 -0
  173. package/src/domains/setup/commands/setup.ts +70 -0
  174. package/src/domains/setup/commands/skills.test.ts +6 -2
  175. package/src/domains/setup/commands/skills.ts +6 -2
  176. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  177. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  178. package/src/domains/setup/index.ts +7 -3
  179. package/src/domains/setup/operations/setupLsp.test.ts +3 -2
  180. package/src/domains/setup/operations/setupLsp.ts +7 -2
  181. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  182. package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
  183. package/src/domains/shared/bootStore.test.ts +82 -0
  184. package/src/domains/shared/bootStore.ts +57 -18
  185. package/src/domains/shared/compactUri.test.ts +11 -0
  186. package/src/domains/shared/compactUri.ts +10 -5
  187. package/src/domains/shared/context.ts +2 -2
  188. package/src/domains/shared/contracts.ts +11 -0
  189. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  190. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  191. package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
  192. package/src/domains/shared/extractLocalName.test.ts +3 -3
  193. package/src/domains/shared/formatters.ts +23 -2
  194. package/src/domains/shared/listTiers.ts +40 -0
  195. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  196. package/src/domains/shared/loaders/bundledLoader.ts +158 -12
  197. package/src/domains/shared/loaders/gitLoader.ts +1 -2
  198. package/src/domains/shared/loaders/index.ts +4 -1
  199. package/src/domains/shared/loaders/localLoader.ts +2 -4
  200. package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
  201. package/src/domains/shared/loaders/readPackageDir.ts +82 -9
  202. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  203. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  204. package/src/domains/shared/lookupMany.test.ts +135 -0
  205. package/src/domains/shared/lookupMany.ts +50 -2
  206. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  207. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  208. package/src/domains/shared/namespaces.ts +13 -5
  209. package/src/domains/shared/packages.ts +10 -17
  210. package/src/domains/shared/parseSampleCount.test.ts +47 -0
  211. package/src/domains/shared/parseSampleCount.ts +45 -0
  212. package/src/domains/shared/prefixes.test.ts +211 -0
  213. package/src/domains/shared/prefixes.ts +182 -15
  214. package/src/domains/shared/renderers.ts +5 -5
  215. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  216. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  217. package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
  218. package/src/domains/shared/resolveLookupExitCode.ts +42 -0
  219. package/src/domains/shared/runtime.ts +4 -63
  220. package/src/domains/shared/semanticPackage.test.ts +89 -0
  221. package/src/domains/shared/semanticPackage.ts +34 -3
  222. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  223. package/src/domains/shared/stories/compileLookupCommand.ts +102 -0
  224. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  225. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  226. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  227. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  228. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  229. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  230. package/src/domains/shared/stories/condense.test.ts +18 -0
  231. package/src/domains/shared/stories/condense.ts +15 -0
  232. package/src/domains/shared/stories/index.ts +20 -0
  233. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  234. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  235. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +151 -0
  236. package/src/domains/shared/stories/pack/applyPackFilters.ts +111 -0
  237. package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
  238. package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
  239. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +81 -0
  240. package/src/domains/shared/stories/pack/buildLookupQuery.ts +154 -0
  241. package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
  242. package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
  243. package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
  244. package/src/domains/shared/stories/pack/collectPackStories.test.ts +406 -0
  245. package/src/domains/shared/stories/pack/collectPackStories.ts +158 -0
  246. package/src/domains/shared/stories/pack/compilePackCommands.ts +50 -0
  247. package/src/domains/shared/stories/pack/compilePackStories.test.ts +751 -0
  248. package/src/domains/shared/stories/pack/compilePackStories.ts +724 -0
  249. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +55 -0
  250. package/src/domains/shared/stories/pack/index.ts +42 -0
  251. package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
  252. package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
  253. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  254. package/src/domains/shared/stories/pack/standardParity.test.ts +381 -0
  255. package/src/domains/shared/stories/pack/types.ts +224 -0
  256. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +742 -0
  257. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +859 -0
  258. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  259. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  260. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  261. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  262. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  263. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  264. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  265. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  266. package/src/domains/shared/stories/types.ts +179 -0
  267. package/src/domains/shared/suggestions/expandGlob.ts +1 -0
  268. package/src/domains/shared/suggestions/suggestNames.ts +43 -10
  269. package/src/domains/shared/types/index.ts +5 -9
  270. package/src/domains/shared/types/ontology.ts +119 -24
  271. package/src/domains/shared/types/standard.ts +9 -52
  272. package/src/domains/skill/commands/index.ts +3 -1
  273. package/src/domains/skill/commands/list.test.ts +6 -2
  274. package/src/domains/skill/commands/list.ts +5 -63
  275. package/src/domains/skill/commands/lookup.test.ts +118 -0
  276. package/src/domains/skill/commands/lookup.ts +14 -0
  277. package/src/domains/skill/formatters/index.ts +1 -0
  278. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  279. package/src/domains/skill/formatters/lookup.ts +57 -0
  280. package/src/domains/skill/helpers/resolveSkillSources.ts +0 -1
  281. package/src/domains/skill/index.ts +7 -3
  282. package/src/domains/skill/mcp/specs.ts +10 -29
  283. package/src/domains/skill/operations/discover.test.ts +61 -30
  284. package/src/domains/skill/operations/discover.ts +28 -37
  285. package/src/domains/skill/operations/index.ts +2 -1
  286. package/src/domains/skill/operations/list.test.ts +15 -7
  287. package/src/domains/skill/operations/lookup.test.ts +91 -0
  288. package/src/domains/skill/operations/lookup.ts +70 -0
  289. package/src/domains/skill/stories.test.ts +43 -0
  290. package/src/domains/skill/stories.ts +103 -0
  291. package/src/domains/skill/types.ts +8 -0
  292. package/src/domains/token/commands/lookup.test.ts +8 -0
  293. package/src/domains/token/commands/lookup.ts +12 -0
  294. package/src/domains/token/commands/sample.ts +2 -1
  295. package/src/domains/token/featureFlag.ts +17 -0
  296. package/src/domains/token/index.ts +5 -0
  297. package/src/domains/token/mcp/specs.ts +18 -2
  298. package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
  299. package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
  300. package/src/domains/trace/commands/show.ts +30 -4
  301. package/src/domains/trace/tracePlugin.test.ts +1 -1
  302. package/src/index.ts +1 -13
  303. package/src/mcp/registerResources.test.ts +382 -74
  304. package/src/mcp/registerResources.ts +5 -348
  305. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  306. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  307. package/src/mcp/resources/buildResourceList.ts +176 -0
  308. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  309. package/src/mcp/resources/classifyEntity.ts +27 -0
  310. package/src/mcp/resources/constants.ts +105 -0
  311. package/src/mcp/resources/displayPredicates.ts +62 -0
  312. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  313. package/src/mcp/resources/pickFirstValue.ts +26 -0
  314. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  315. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  316. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  317. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  318. package/src/mcp/resources/readEntity.ts +169 -0
  319. package/src/mcp/resources/registerResources.ts +126 -0
  320. package/src/mcp/resources/types.ts +128 -0
  321. package/src/mcp/tools/index.ts +22 -5
  322. package/src/mcp/tools/registerTools.test.ts +167 -34
  323. package/src/mcp/types.ts +4 -0
  324. package/src/pipeline/collectCommands.test.ts +151 -1
  325. package/src/pipeline/collectCommands.ts +35 -13
  326. package/src/pipeline/createProgram.test.ts +5 -1
  327. package/src/pipeline/createProgram.ts +2 -2
  328. package/src/pipeline/firstRun.test.ts +80 -0
  329. package/src/pipeline/firstRun.ts +99 -0
  330. package/src/pipeline/parseGlobalFlags.test.ts +102 -1
  331. package/src/pipeline/parseGlobalFlags.ts +98 -9
  332. package/src/pipeline/resolveCommandKind.test.ts +12 -0
  333. package/src/pipeline/resolveCommandKind.ts +4 -0
  334. package/src/pipeline/rootHelp.test.ts +87 -0
  335. package/src/pipeline/rootHelp.ts +265 -0
  336. package/src/pipeline/runCli.orchestration.test.ts +28 -12
  337. package/src/pipeline/runCli.test.ts +17 -4
  338. package/src/pipeline/runCli.ts +202 -25
  339. package/src/testing/cli.ts +30 -2
  340. package/src/testing/createTestMcpClient.ts +4 -1
  341. package/src/testing/dsFixtures.ts +21 -4
  342. package/src/testing/index.ts +7 -0
  343. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  344. package/src/testing/integration/cli-user-stories.test.ts +63 -0
  345. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  346. package/src/testing/integration/edge-cases.test.ts +62 -50
  347. package/src/testing/integration/executor-execution.test.ts +103 -0
  348. package/src/testing/integration/executor-golden.test.ts +145 -0
  349. package/src/testing/integration/mcp-surface.test.ts +40 -15
  350. package/src/testing/integration/parity.test.ts +136 -82
  351. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  352. package/src/testing/setupXdgIsolation.ts +26 -0
  353. package/src/testing/standardParityFixtures.ts +35 -0
  354. package/src/testing/store.ts +4 -4
  355. package/src/testing/storyFixtures.ts +63 -0
  356. package/src/domains/create/commands/component.test.ts +0 -166
  357. package/src/domains/create/commands/component.ts +0 -123
  358. package/src/domains/create/commands/index.ts +0 -3
  359. package/src/domains/create/commands/package.test.ts +0 -35
  360. package/src/domains/create/commands/package.ts +0 -21
  361. package/src/domains/create/generators.ts +0 -8
  362. package/src/domains/standard/commands/categories.test.ts +0 -70
  363. package/src/domains/standard/commands/categories.ts +0 -48
  364. package/src/domains/standard/commands/index.ts +0 -6
  365. package/src/domains/standard/commands/list.test.ts +0 -105
  366. package/src/domains/standard/commands/list.ts +0 -90
  367. package/src/domains/standard/commands/lookup.test.ts +0 -135
  368. package/src/domains/standard/commands/lookup.ts +0 -99
  369. package/src/domains/standard/commands/sample.ts +0 -57
  370. package/src/domains/standard/formatters/categories.test.ts +0 -44
  371. package/src/domains/standard/formatters/categories.ts +0 -38
  372. package/src/domains/standard/formatters/index.ts +0 -10
  373. package/src/domains/standard/formatters/list.test.ts +0 -169
  374. package/src/domains/standard/formatters/list.ts +0 -184
  375. package/src/domains/standard/formatters/lookup.test.ts +0 -95
  376. package/src/domains/standard/formatters/lookup.ts +0 -101
  377. package/src/domains/standard/formatters/sample.ts +0 -15
  378. package/src/domains/standard/formatters/types.ts +0 -31
  379. package/src/domains/standard/index.ts +0 -40
  380. package/src/domains/standard/mcp/index.ts +0 -2
  381. package/src/domains/standard/mcp/specs.ts +0 -244
  382. package/src/domains/standard/operations/categories.test.ts +0 -61
  383. package/src/domains/standard/operations/categories.ts +0 -38
  384. package/src/domains/standard/operations/index.ts +0 -6
  385. package/src/domains/standard/operations/list.test.ts +0 -75
  386. package/src/domains/standard/operations/list.ts +0 -64
  387. package/src/domains/standard/operations/lookup.test.ts +0 -58
  388. package/src/domains/standard/operations/lookup.ts +0 -151
  389. package/src/domains/standard/operations/sample.ts +0 -35
  390. package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
  391. package/src/domains/standard/orchestration/index.ts +0 -4
  392. package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
  393. package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -32
  394. package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
  395. package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
  396. package/src/domains/standard/standardConfig.ts +0 -42
  397. package/src/domains/standard/types.ts +0 -15
  398. package/src/domains/tier/commands/index.ts +0 -3
  399. package/src/domains/tier/commands/list.test.ts +0 -67
  400. package/src/domains/tier/commands/list.ts +0 -59
  401. package/src/domains/tier/formatters/index.ts +0 -3
  402. package/src/domains/tier/formatters/list.test.ts +0 -33
  403. package/src/domains/tier/formatters/list.ts +0 -45
  404. package/src/domains/tier/index.ts +0 -17
  405. package/src/domains/tier/mcp/index.ts +0 -2
  406. package/src/domains/tier/mcp/specs.ts +0 -38
  407. package/src/domains/tier/operations/index.ts +0 -3
  408. package/src/domains/tier/operations/list.test.ts +0 -32
  409. package/src/domains/tier/operations/list.ts +0 -33
  410. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -1,352 +1,9 @@
1
- import type { Store } from "@canonical/ke";
2
- import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
3
- import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
4
- import { PragmaError } from "#error";
5
- import { PROPERTY_MAP } from "../constants.js";
6
- import resolveUri from "../domains/graph/helpers/resolveUri.js";
7
- import { buildQuery } from "../domains/shared/buildQuery.js";
8
- import compactUri from "../domains/shared/compactUri.js";
9
- import type { PragmaRuntime } from "../domains/shared/runtime.js";
10
-
11
- // =============================================================================
12
- // Types
13
- // =============================================================================
14
-
15
- interface PropertyValueLiteral {
16
- readonly type: "literal";
17
- readonly value: string;
18
- readonly datatype?: string;
19
- }
20
-
21
- interface PropertyValueUri {
22
- readonly type: "uri";
23
- readonly uri: string;
24
- readonly prefixed: string;
25
- readonly label: string | null;
26
- }
27
-
28
- type PropertyValue = PropertyValueLiteral | PropertyValueUri;
29
-
30
- interface PropertyGroup {
31
- readonly predicate: string;
32
- readonly values: PropertyValue[];
33
- }
34
-
35
- interface ResourceEntity {
36
- readonly uri: string;
37
- readonly prefixed: string;
38
- readonly types: string[];
39
- readonly label: string | null;
40
- readonly description: string | null;
41
- readonly properties: PropertyGroup[];
42
- }
43
-
44
- // =============================================================================
45
- // Helpers
46
- // =============================================================================
47
-
48
- const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
49
-
50
- /**
51
- * Determine the namespace prefix for a full URI.
52
- */
53
- function findPrefix(
54
- fullUri: string,
55
- prefixes: Readonly<Record<string, string>>,
56
- ): string | null {
57
- for (const [prefix, namespace] of Object.entries(prefixes)) {
58
- if (fullUri.startsWith(namespace)) return prefix;
59
- }
60
- return null;
61
- }
62
-
63
- /**
64
- * Fetch a single property value for a URI using the PROPERTY_MAP.
65
- */
66
- async function fetchProperty(
67
- store: Store,
68
- fullUri: string,
69
- prefixes: Readonly<Record<string, string>>,
70
- field: "label" | "description",
71
- ): Promise<string | null> {
72
- const prefix = findPrefix(fullUri, prefixes);
73
- if (!prefix) return null;
74
-
75
- const propUri = PROPERTY_MAP[prefix]?.[field];
76
- if (!propUri) return null;
77
-
78
- const result = await store.query(
79
- buildQuery(`
80
- SELECT ?val
81
- WHERE { <${fullUri}> <${propUri}> ?val }
82
- LIMIT 1
83
- `),
84
- );
85
-
86
- if (result.type === "select" && result.bindings.length > 0) {
87
- return result.bindings[0]?.val ?? null;
88
- }
89
- return null;
90
- }
91
-
92
- /**
93
- * Fetch the human-readable label for a URI using the PROPERTY_MAP.
94
- */
95
- async function fetchLabel(
96
- store: Store,
97
- fullUri: string,
98
- prefixes: Readonly<Record<string, string>>,
99
- ): Promise<string | null> {
100
- return fetchProperty(store, fullUri, prefixes, "label");
101
- }
102
-
103
- /**
104
- * Fetch the human-readable description for a URI using the PROPERTY_MAP.
105
- */
106
- async function fetchDescription(
107
- store: Store,
108
- fullUri: string,
109
- prefixes: Readonly<Record<string, string>>,
110
- ): Promise<string | null> {
111
- return fetchProperty(store, fullUri, prefixes, "description");
112
- }
113
-
114
- /**
115
- * Detect whether a SPARQL binding value is a URI or a literal.
116
- * URIs start with http:// or https:// or are prefixed; literals are everything else.
117
- */
118
- function isUri(value: string): boolean {
119
- return (
120
- value.startsWith("http://") ||
121
- value.startsWith("https://") ||
122
- value.startsWith("urn:")
123
- );
124
- }
125
-
126
- /**
127
- * Enumerate all distinct subject URIs in the graph.
128
- */
129
- async function listAllSubjects(store: Store): Promise<string[]> {
130
- const result = await store.query(
131
- buildQuery("SELECT DISTINCT ?s WHERE { ?s ?p ?o } ORDER BY ?s"),
132
- );
133
-
134
- if (result.type !== "select") return [];
135
- return result.bindings.map((b) => b.s ?? "").filter((s) => isUri(s));
136
- }
137
-
138
- const RDFS_LABEL = "http://www.w3.org/2000/01/rdf-schema#label";
139
-
140
- /**
141
- * Fetch the rdfs:label of the entity's rdf:type class.
142
- *
143
- * Returns the label of the first typed class that has one, or null.
144
- */
145
- async function fetchTypeLabel(
146
- store: Store,
147
- fullUri: string,
148
- ): Promise<string | null> {
149
- const result = await store.query(
150
- buildQuery(`
151
- SELECT ?typeLabel
152
- WHERE {
153
- <${fullUri}> <${RDF_TYPE}> ?type .
154
- ?type <${RDFS_LABEL}> ?typeLabel .
155
- }
156
- LIMIT 1
157
- `),
158
- );
159
-
160
- if (result.type !== "select" || result.bindings.length === 0) return null;
161
- return result.bindings[0]?.typeLabel ?? null;
162
- }
163
-
164
1
  /**
165
- * Build a resource description from type label and graph description.
2
+ * Entry point for the MCP resource surface.
166
3
  *
167
- * Produces: "Component — Buttons are used to trigger…" or just "Component"
168
- * when no description exists, or just the truncated description when no
169
- * type is recognized.
4
+ * The implementation lives in `./resources/`, split across focused modules
5
+ * (index build, listing, autocomplete, reads). This file re-exports the
6
+ * registrar so existing importers keep a stable path.
170
7
  */
171
- function buildResourceDescription(
172
- typeLabel: string | null,
173
- description: string | null,
174
- ): string | undefined {
175
- if (!typeLabel && !description) return undefined;
176
- const truncated =
177
- description && description.length > 120
178
- ? `${description.slice(0, 117)}...`
179
- : description;
180
- if (!typeLabel) return truncated ?? undefined;
181
- if (!truncated) return typeLabel;
182
- return `${typeLabel} — ${truncated}`;
183
- }
184
-
185
- /**
186
- * Read a single entity: all its triples with level-1 object summaries.
187
- */
188
- async function readEntity(
189
- store: Store,
190
- fullUri: string,
191
- prefixes: Readonly<Record<string, string>>,
192
- ): Promise<ResourceEntity> {
193
- const result = await store.query(
194
- buildQuery(`
195
- SELECT ?p ?o
196
- WHERE { <${fullUri}> ?p ?o }
197
- ORDER BY ?p ?o
198
- `),
199
- );
200
-
201
- if (result.type !== "select" || result.bindings.length === 0) {
202
- throw PragmaError.notFound("entity", compactUri(fullUri, prefixes), {
203
- recovery: {
204
- message:
205
- "Check the URI is correct and run a SPARQL query to find valid URIs.",
206
- cli: "pragma graph query 'SELECT ?s WHERE { ?s ?p ?o } LIMIT 10'",
207
- mcp: { tool: "graph_query" },
208
- },
209
- });
210
- }
211
-
212
- const types: string[] = [];
213
- const groupMap = new Map<string, PropertyValue[]>();
214
-
215
- for (const b of result.bindings) {
216
- const predicate = b.p ?? "";
217
- const object = b.o ?? "";
218
-
219
- if (predicate === RDF_TYPE) {
220
- types.push(compactUri(object, prefixes));
221
- continue;
222
- }
223
-
224
- const compactPred = compactUri(predicate, prefixes);
225
- const existing = groupMap.get(compactPred) ?? [];
226
-
227
- if (isUri(object)) {
228
- const label = await fetchLabel(store, object, prefixes);
229
- existing.push({
230
- type: "uri",
231
- uri: object,
232
- prefixed: compactUri(object, prefixes),
233
- label,
234
- });
235
- } else {
236
- existing.push({ type: "literal", value: object });
237
- }
238
-
239
- groupMap.set(compactPred, existing);
240
- }
241
-
242
- const [label, description] = await Promise.all([
243
- fetchLabel(store, fullUri, prefixes),
244
- fetchDescription(store, fullUri, prefixes),
245
- ]);
246
-
247
- const properties: PropertyGroup[] = [...groupMap.entries()].map(
248
- ([predicate, values]) => ({ predicate, values }),
249
- );
250
-
251
- return {
252
- uri: fullUri,
253
- prefixed: compactUri(fullUri, prefixes),
254
- types,
255
- label,
256
- description,
257
- properties,
258
- };
259
- }
260
-
261
- // =============================================================================
262
- // Registration
263
- // =============================================================================
264
-
265
- /**
266
- * Register graph-driven MCP resources on the server.
267
- *
268
- * Every subject URI in the ke graph becomes a discoverable MCP resource.
269
- * Reading a resource returns the entity's properties with level-1 object
270
- * relations resolved to summaries (label and description from PROPERTY_MAP).
271
- *
272
- * @param server - The MCP server to register resources on.
273
- * @param runtime - The pragma runtime providing the ke store and prefixes.
274
- *
275
- * @note Impure
276
- */
277
- export default function registerResources(
278
- server: McpServer,
279
- runtime: PragmaRuntime,
280
- ): void {
281
- const { store } = runtime;
282
- const prefixes = store.prefixes;
283
-
284
- const template = new ResourceTemplate("{+uri}", {
285
- list: async () => {
286
- const subjects = await listAllSubjects(store);
287
- const resources = await Promise.all(
288
- subjects.map(async (fullUri) => {
289
- const prefixed = compactUri(fullUri, prefixes);
290
- const [label, typeLabel, desc] = await Promise.all([
291
- fetchLabel(store, fullUri, prefixes),
292
- fetchTypeLabel(store, fullUri),
293
- fetchDescription(store, fullUri, prefixes),
294
- ]);
295
- return {
296
- uri: prefixed,
297
- name: label ?? prefixed,
298
- description: buildResourceDescription(typeLabel, desc),
299
- mimeType: "application/json" as const,
300
- annotations: { audience: ["assistant" as const] },
301
- };
302
- }),
303
- );
304
- return { resources };
305
- },
306
- complete: {
307
- uri: async (value) => {
308
- const subjects = await listAllSubjects(store);
309
- const compacted = subjects.map((s) => compactUri(s, prefixes));
310
- return compacted.filter((p) =>
311
- p.toLowerCase().startsWith(value.toLowerCase()),
312
- );
313
- },
314
- },
315
- });
316
8
 
317
- server.registerResource(
318
- "graph-entity",
319
- template,
320
- { mimeType: "application/json" },
321
- async (url, variables) => {
322
- try {
323
- const prefixedUri = variables.uri as string;
324
- const fullUri = resolveUri(prefixedUri, prefixes);
325
- const entity = await readEntity(store, fullUri, prefixes);
326
- return {
327
- contents: [
328
- {
329
- uri: url.href,
330
- mimeType: "application/json",
331
- text: JSON.stringify(entity, null, 2),
332
- },
333
- ],
334
- };
335
- } catch (error) {
336
- const message =
337
- error instanceof PragmaError
338
- ? JSON.stringify({ code: error.code, message: error.message })
339
- : String(error);
340
- return {
341
- contents: [
342
- {
343
- uri: url.href,
344
- mimeType: "text/plain",
345
- text: message,
346
- },
347
- ],
348
- };
349
- }
350
- },
351
- );
352
- }
9
+ export { default } from "./resources/registerResources.js";
@@ -0,0 +1,167 @@
1
+ import type { Store } from "@canonical/ke";
2
+ import findNamespace from "../../domains/ontology/helpers/findNamespace.js";
3
+ import { buildQuery } from "../../domains/shared/buildQuery.js";
4
+ import compactUri from "../../domains/shared/compactUri.js";
5
+ import extractLocalName from "../../domains/shared/extractLocalName.js";
6
+ import classifyEntity from "./classifyEntity.js";
7
+ import { RDF_TYPE } from "./constants.js";
8
+ import {
9
+ collectDescriptionPredicates,
10
+ collectLabelPredicates,
11
+ resolveDescriptionPredicates,
12
+ resolveLabelPredicates,
13
+ } from "./displayPredicates.js";
14
+ import pickFirstValue from "./pickFirstValue.js";
15
+ import pickPrimaryType from "./pickPrimaryType.js";
16
+ import type { GraphEntity, GraphIndex } from "./types.js";
17
+
18
+ /** A per-subject map of predicate URI → asserted lexical values. */
19
+ type ValuesBySubject = Map<string, Map<string, string[]>>;
20
+
21
+ /** Wrap full URIs as a SPARQL `VALUES` list body. */
22
+ function toValuesList(uris: readonly string[]): string {
23
+ return [...new Set(uris)].map((uri) => `<${uri}>`).join(" ");
24
+ }
25
+
26
+ /**
27
+ * Run one `?s ?p ?v` bulk query restricted to the given predicates and
28
+ * collect values per named-node subject. Blank-node subjects are skipped —
29
+ * they are never listed as resources.
30
+ *
31
+ * @note Queries ke store
32
+ */
33
+ async function fetchValuesBySubject(
34
+ store: Store,
35
+ predicates: readonly string[],
36
+ ): Promise<ValuesBySubject> {
37
+ const bySubject: ValuesBySubject = new Map();
38
+ if (predicates.length === 0) return bySubject;
39
+
40
+ const result = await store.query(
41
+ buildQuery(
42
+ `SELECT ?s ?p ?v WHERE { ?s ?p ?v . VALUES ?p { ${toValuesList(predicates)} } }`,
43
+ ),
44
+ );
45
+ if (result.type !== "select") return bySubject;
46
+
47
+ for (const term of result.termBindings) {
48
+ if (term.s?.termType !== "NamedNode") continue;
49
+ const subject = term.s.value;
50
+ const predicate = term.p?.value ?? "";
51
+ const value = term.v?.value ?? "";
52
+ const byPredicate = bySubject.get(subject) ?? new Map<string, string[]>();
53
+ const values = byPredicate.get(predicate) ?? [];
54
+ values.push(value);
55
+ byPredicate.set(predicate, values);
56
+ bySubject.set(subject, byPredicate);
57
+ }
58
+ return bySubject;
59
+ }
60
+
61
+ /**
62
+ * Build the in-memory graph index used by listing, autocomplete, and reads.
63
+ *
64
+ * Replaces the former per-subject label/type/description queries with three
65
+ * bulk queries — one for types, one for labels, one for descriptions — joined
66
+ * in memory. Only typed named-node subjects are indexed; untyped subjects and
67
+ * blank nodes are reachable via `graph_query` but are not listed as resources.
68
+ *
69
+ * @param store - The ke store to query.
70
+ * @param prefixes - The store's merged prefix map.
71
+ * @returns The classified entities plus label and instance-count lookups.
72
+ *
73
+ * @note Queries ke store
74
+ */
75
+ export default async function buildGraphIndex(
76
+ store: Store,
77
+ prefixes: Readonly<Record<string, string>>,
78
+ ): Promise<GraphIndex> {
79
+ const prefixEntries = Object.entries(prefixes);
80
+
81
+ const [typesResult, labelValues, descriptionValues] = await Promise.all([
82
+ store.query(buildQuery(`SELECT ?s ?type WHERE { ?s <${RDF_TYPE}> ?type }`)),
83
+ fetchValuesBySubject(store, collectLabelPredicates()),
84
+ fetchValuesBySubject(store, collectDescriptionPredicates()),
85
+ ]);
86
+
87
+ const typesBySubject = new Map<string, string[]>();
88
+ const instanceCountByType = new Map<string, number>();
89
+ if (typesResult.type === "select") {
90
+ for (const term of typesResult.termBindings) {
91
+ if (term.s?.termType !== "NamedNode") continue;
92
+ const subject = term.s.value;
93
+ const typeUri = term.type?.value ?? "";
94
+ const types = typesBySubject.get(subject) ?? [];
95
+ types.push(typeUri);
96
+ typesBySubject.set(subject, types);
97
+ instanceCountByType.set(
98
+ typeUri,
99
+ (instanceCountByType.get(typeUri) ?? 0) + 1,
100
+ );
101
+ }
102
+ }
103
+
104
+ // Phase 1: resolve every subject's label and description so that a class
105
+ // label is available before any individual that references it is built.
106
+ interface ResolvedSubject {
107
+ readonly subject: string;
108
+ readonly fullTypes: string[];
109
+ readonly box: GraphEntity["box"];
110
+ readonly category: GraphEntity["category"];
111
+ readonly label: string | null;
112
+ readonly description: string | null;
113
+ }
114
+
115
+ const labelByUri = new Map<string, string>();
116
+ const resolved: ResolvedSubject[] = [];
117
+
118
+ for (const [subject, fullTypes] of typesBySubject) {
119
+ const classification = classifyEntity(fullTypes);
120
+ if (classification === null) continue;
121
+
122
+ const prefix = findNamespace(subject, prefixEntries)?.prefix;
123
+ const label = pickFirstValue(
124
+ labelValues.get(subject) ?? new Map(),
125
+ resolveLabelPredicates(prefix),
126
+ );
127
+ const description = pickFirstValue(
128
+ descriptionValues.get(subject) ?? new Map(),
129
+ resolveDescriptionPredicates(prefix),
130
+ );
131
+ if (label !== null) labelByUri.set(subject, label);
132
+
133
+ resolved.push({
134
+ subject,
135
+ fullTypes,
136
+ box: classification.box,
137
+ category: classification.category,
138
+ label,
139
+ description,
140
+ });
141
+ }
142
+
143
+ // Phase 2: build entities, resolving each individual's grouping label from
144
+ // the now-complete label lookup.
145
+ const entities: GraphEntity[] = resolved.map((r) => {
146
+ const primaryTypeFull =
147
+ r.category === "individual" ? pickPrimaryType(r.fullTypes) : null;
148
+ return {
149
+ uri: r.subject,
150
+ prefixed: compactUri(r.subject, prefixes),
151
+ box: r.box,
152
+ category: r.category,
153
+ types: r.fullTypes.map((t) => compactUri(t, prefixes)),
154
+ primaryType:
155
+ primaryTypeFull === null ? null : compactUri(primaryTypeFull, prefixes),
156
+ primaryTypeLabel:
157
+ primaryTypeFull === null
158
+ ? null
159
+ : (labelByUri.get(primaryTypeFull) ??
160
+ extractLocalName(primaryTypeFull)),
161
+ label: r.label,
162
+ description: r.description,
163
+ };
164
+ });
165
+
166
+ return { entities, labelByUri, instanceCountByType };
167
+ }
@@ -0,0 +1,97 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import buildResourceList from "./buildResourceList.js";
3
+ import { ABOX_PER_CLASS_LIMIT } from "./constants.js";
4
+ import type { GraphEntity, GraphIndex } from "./types.js";
5
+
6
+ const gadgetClass: GraphEntity = {
7
+ uri: "https://ds.canonical.com/Gadget",
8
+ prefixed: "ds:Gadget",
9
+ box: "tbox",
10
+ category: "class",
11
+ types: ["owl:Class"],
12
+ primaryType: null,
13
+ primaryTypeLabel: null,
14
+ label: "Gadget",
15
+ description: null,
16
+ };
17
+
18
+ const nameProperty: GraphEntity = {
19
+ uri: "https://ds.canonical.com/name",
20
+ prefixed: "ds:name",
21
+ box: "tbox",
22
+ category: "property",
23
+ types: ["owl:DatatypeProperty"],
24
+ primaryType: null,
25
+ primaryTypeLabel: null,
26
+ label: null,
27
+ description: null,
28
+ };
29
+
30
+ function makeIndividual(n: number): GraphEntity {
31
+ return {
32
+ uri: `https://ds.canonical.com/w${n}`,
33
+ prefixed: `ds:w${n}`,
34
+ box: "abox",
35
+ category: "individual",
36
+ types: ["ds:Gadget"],
37
+ primaryType: "ds:Gadget",
38
+ primaryTypeLabel: "Gadget",
39
+ label: `Widget ${n}`,
40
+ description: null,
41
+ };
42
+ }
43
+
44
+ function makeIndex(entities: GraphEntity[]): GraphIndex {
45
+ return { entities, labelByUri: new Map(), instanceCountByType: new Map() };
46
+ }
47
+
48
+ function boxOf(resource: { _meta: Record<string, unknown> }): unknown {
49
+ return resource._meta["pragma/box"];
50
+ }
51
+
52
+ describe("buildResourceList", () => {
53
+ it("orders all TBox schema before any ABox individual", () => {
54
+ const index = makeIndex([makeIndividual(0), nameProperty, gadgetClass]);
55
+ const boxes = buildResourceList(index).resources.map(boxOf);
56
+ expect(boxes.slice(0, 2)).toEqual(["tbox", "tbox"]);
57
+ expect(boxes.at(-1)).toBe("abox");
58
+ });
59
+
60
+ it("caps individuals per class and records the dropped remainder", () => {
61
+ const overflow = ABOX_PER_CLASS_LIMIT + 7;
62
+ const individuals = Array.from({ length: overflow }, (_, i) =>
63
+ makeIndividual(i),
64
+ );
65
+ const { resources, truncation } = buildResourceList(
66
+ makeIndex([gadgetClass, ...individuals]),
67
+ );
68
+
69
+ const shown = resources.filter((r) => boxOf(r) === "abox");
70
+ expect(shown).toHaveLength(ABOX_PER_CLASS_LIMIT);
71
+ expect(truncation.totalDropped).toBe(7);
72
+ expect(truncation.droppedByType.get("ds:Gadget")).toBe(7);
73
+
74
+ const cls = resources.find((r) => r.uri === "ds:Gadget");
75
+ expect(cls?._meta["pragma/instanceCount"]).toBe(overflow);
76
+ expect(cls?._meta["pragma/instancesShown"]).toBe(ABOX_PER_CLASS_LIMIT);
77
+ expect(cls?._meta["pragma/truncated"]).toBe(true);
78
+ });
79
+
80
+ it("reports no truncation when under the cap", () => {
81
+ const { truncation } = buildResourceList(
82
+ makeIndex([gadgetClass, makeIndividual(0)]),
83
+ );
84
+ expect(truncation.totalDropped).toBe(0);
85
+ });
86
+
87
+ it("assigns higher annotation priority to classes than individuals", () => {
88
+ const { resources } = buildResourceList(
89
+ makeIndex([gadgetClass, makeIndividual(0)]),
90
+ );
91
+ const cls = resources.find((r) => r.uri === "ds:Gadget");
92
+ const individual = resources.find((r) => r.uri === "ds:w0");
93
+ expect(cls?.annotations.priority).toBeGreaterThan(
94
+ individual?.annotations.priority ?? 1,
95
+ );
96
+ });
97
+ });