@canonical/pragma-cli 0.35.0 → 0.37.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 (295) hide show
  1. package/README.md +2 -2
  2. package/bundled-skills/add-standard/SKILL.md +338 -0
  3. package/bundled-skills/adoption-a1-styles/SKILL.md +269 -0
  4. package/bundled-skills/adoption-a2-components/SKILL.md +291 -0
  5. package/bundled-skills/adoption-a3-forms/SKILL.md +260 -0
  6. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +406 -0
  7. package/bundled-skills/anatomy-author/SKILL.md +1226 -0
  8. package/bundled-skills/design-auditor/SKILL.md +455 -0
  9. package/bundled-skills/implement-component/SKILL.md +333 -0
  10. package/bundled-skills/specify-component/SKILL.md +331 -0
  11. package/bundled-skills/specify-pattern/SKILL.md +292 -0
  12. package/dist/package.json +24 -20
  13. package/dist/pragma.conf.js +266 -34
  14. package/dist/pragma.conf.js.map +1 -1
  15. package/dist/src/bin.js +1 -1
  16. package/dist/src/bin.js.map +1 -1
  17. package/dist/src/capabilities/capabilities/hints.js +1 -1
  18. package/dist/src/capabilities/capabilities/hints.js.map +1 -1
  19. package/dist/src/capabilities/colophon/colophon.render.js +1 -1
  20. package/dist/src/capabilities/colophon/colophon.render.js.map +1 -1
  21. package/dist/src/capabilities/create/create.verb.js +2 -1
  22. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  23. package/dist/src/capabilities/create/createSurface.generated.js +23 -1
  24. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  25. package/dist/src/capabilities/create/mount.js +4 -3
  26. package/dist/src/capabilities/create/mount.js.map +1 -1
  27. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
  28. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
  29. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js +60 -4
  30. package/dist/src/capabilities/doctor/checks/checkPackageRefs.js.map +1 -1
  31. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js +8 -8
  32. package/dist/src/capabilities/doctor/checks/checkShellCompletions.js.map +1 -1
  33. package/dist/src/capabilities/doctor/checks/harnessInventory.js +195 -0
  34. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -0
  35. package/dist/src/capabilities/doctor/checks/index.js +6 -6
  36. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
  37. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
  38. package/dist/src/capabilities/doctor/checks/targetHealth.js +194 -50
  39. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  40. package/dist/src/capabilities/doctor/doctor.render.js +43 -64
  41. package/dist/src/capabilities/doctor/doctor.render.js.map +1 -1
  42. package/dist/src/capabilities/doctor/doctor.verb.js +6 -2
  43. package/dist/src/capabilities/doctor/doctor.verb.js.map +1 -1
  44. package/dist/src/capabilities/doctor/runChecks.js +14 -11
  45. package/dist/src/capabilities/doctor/runChecks.js.map +1 -1
  46. package/dist/src/capabilities/graph/query.render.js +2 -2
  47. package/dist/src/capabilities/graph/query.render.js.map +1 -1
  48. package/dist/src/capabilities/info/collectInfo.js +8 -2
  49. package/dist/src/capabilities/info/collectInfo.js.map +1 -1
  50. package/dist/src/capabilities/info/info.render.js +6 -2
  51. package/dist/src/capabilities/info/info.render.js.map +1 -1
  52. package/dist/src/capabilities/prompt/prompt.render.js +2 -2
  53. package/dist/src/capabilities/prompt/prompt.render.js.map +1 -1
  54. package/dist/src/capabilities/setup/buildPlan.js +43 -39
  55. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  56. package/dist/src/capabilities/setup/mode.js +2 -2
  57. package/dist/src/capabilities/setup/operations/index.js +4 -4
  58. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  59. package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
  60. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
  61. package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
  62. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
  63. package/dist/src/capabilities/setup/operations/setupGenerator.js +156 -58
  64. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  65. package/dist/src/capabilities/setup/operations/setupLsp.js +136 -38
  66. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  67. package/dist/src/capabilities/setup/operations/setupMcp.js +101 -52
  68. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  69. package/dist/src/capabilities/setup/operations/setupSkills.js +329 -49
  70. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  71. package/dist/src/capabilities/setup/plan.js.map +1 -1
  72. package/dist/src/capabilities/setup/plan.render.js +144 -66
  73. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  74. package/dist/src/capabilities/setup/setup.render.js +1 -1
  75. package/dist/src/capabilities/setup/setup.verb.js +165 -63
  76. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  77. package/dist/src/capabilities/setup/targets.js +122 -51
  78. package/dist/src/capabilities/setup/targets.js.map +1 -1
  79. package/dist/src/capabilities/shared/index.js +7 -6
  80. package/dist/src/capabilities/shared/index.js.map +1 -1
  81. package/dist/src/capabilities/shared/packageManager.js +300 -54
  82. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  83. package/dist/src/capabilities/skill/discover.js +97 -11
  84. package/dist/src/capabilities/skill/discover.js.map +1 -1
  85. package/dist/src/capabilities/skill/render.js +2 -2
  86. package/dist/src/capabilities/skill/render.js.map +1 -1
  87. package/dist/src/capabilities/sources/installSkills.js +145 -3
  88. package/dist/src/capabilities/sources/installSkills.js.map +1 -1
  89. package/dist/src/capabilities/sources/runUpdate.js +68 -2
  90. package/dist/src/capabilities/sources/runUpdate.js.map +1 -1
  91. package/dist/src/capabilities/sources/update.verb.js +3 -3
  92. package/dist/src/capabilities/sources/update.verb.js.map +1 -1
  93. package/dist/src/capabilities/upgrade/runUpgrade.js +43 -7
  94. package/dist/src/capabilities/upgrade/runUpgrade.js.map +1 -1
  95. package/dist/src/capabilities/upgrade/upgrade.render.js +10 -2
  96. package/dist/src/capabilities/upgrade/upgrade.render.js.map +1 -1
  97. package/dist/src/constants.js +13 -2
  98. package/dist/src/constants.js.map +1 -1
  99. package/dist/src/kernel/index.js +11 -2
  100. package/dist/src/kernel/index.js.map +1 -1
  101. package/dist/src/kernel/interactivity.js +68 -0
  102. package/dist/src/kernel/interactivity.js.map +1 -0
  103. package/dist/src/kernel/packs/renderPack.js +3 -3
  104. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  105. package/dist/src/kernel/packs/resolveEntity.js +63 -12
  106. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  107. package/dist/src/kernel/packs/runBodies.js +57 -4
  108. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  109. package/dist/src/kernel/packs/schema.js +54 -2
  110. package/dist/src/kernel/packs/schema.js.map +1 -1
  111. package/dist/src/kernel/packs/sparql/applyFilters.js +104 -5
  112. package/dist/src/kernel/packs/sparql/applyFilters.js.map +1 -1
  113. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +310 -43
  114. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  115. package/dist/src/kernel/packs/types.js.map +1 -1
  116. package/dist/src/kernel/project/cli/buildProgram.js +1 -1
  117. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  118. package/dist/src/kernel/project/cli/dispatch.js +46 -24
  119. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  120. package/dist/src/kernel/project/cli/globalFlags.js +4 -1
  121. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  122. package/dist/src/kernel/project/cli/helpFormat.js +1 -2
  123. package/dist/src/kernel/project/cli/helpFormat.js.map +1 -1
  124. package/dist/src/kernel/project/cli/rootHelp.js +42 -9
  125. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  126. package/dist/src/kernel/project/mcp/registerVerb.js +16 -1
  127. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  128. package/dist/src/kernel/render/renderers.js +1 -1
  129. package/dist/src/kernel/render/style.js +29 -9
  130. package/dist/src/kernel/render/style.js.map +1 -1
  131. package/dist/src/kernel/render/vocabulary.js +174 -0
  132. package/dist/src/kernel/render/vocabulary.js.map +1 -0
  133. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
  134. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
  135. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
  136. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
  137. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
  138. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
  139. package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
  140. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
  141. package/dist/src/kernel/runtime/graphpack/read.js +14 -9
  142. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  143. package/dist/src/kernel/runtime/loadSession.js +3 -1
  144. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  145. package/dist/src/kernel/runtime/probe.js +1 -1
  146. package/dist/src/kernel/runtime/probe.js.map +1 -1
  147. package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
  148. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
  149. package/dist/src/kernel/runtime/refs/resolve.js +9 -3
  150. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  151. package/dist/src/kernel/spec/emitReference.js +1 -1
  152. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  153. package/package.json +24 -20
  154. package/pragma.conf.ts +272 -34
  155. package/src/bin.ts +8 -1
  156. package/src/capabilities/block.parity.test.ts +25 -11
  157. package/src/capabilities/block.shipped.exec.test.ts +282 -0
  158. package/src/capabilities/block.tierRank.exec.test.ts +300 -0
  159. package/src/capabilities/capabilities/hints.ts +1 -1
  160. package/src/capabilities/colophon/colophon.render.ts +1 -2
  161. package/src/capabilities/colophon/colophon.test.ts +2 -2
  162. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  163. package/src/capabilities/create/byteEquality.test.ts +28 -1
  164. package/src/capabilities/create/create.test.ts +1 -0
  165. package/src/capabilities/create/create.verb.ts +2 -1
  166. package/src/capabilities/create/createGrammar.test.ts +5 -2
  167. package/src/capabilities/create/createSurface.generated.ts +23 -1
  168. package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
  169. package/src/capabilities/create/mount.ts +3 -3
  170. package/src/capabilities/distribution.parity.test.ts +4 -0
  171. package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
  172. package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
  173. package/src/capabilities/doctor/checks/checkPackageRefs.ts +69 -4
  174. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +6 -4
  175. package/src/capabilities/doctor/checks/checkShellCompletions.ts +8 -8
  176. package/src/capabilities/doctor/checks/harnessInventory.test.ts +219 -0
  177. package/src/capabilities/doctor/checks/harnessInventory.ts +298 -0
  178. package/src/capabilities/doctor/checks/index.ts +6 -6
  179. package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
  180. package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
  181. package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
  182. package/src/capabilities/doctor/checks/targetHealth.ts +229 -59
  183. package/src/capabilities/doctor/doctor.render.test.ts +108 -32
  184. package/src/capabilities/doctor/doctor.render.ts +56 -80
  185. package/src/capabilities/doctor/doctor.test.ts +247 -35
  186. package/src/capabilities/doctor/doctor.verb.ts +7 -2
  187. package/src/capabilities/doctor/runChecks.ts +14 -11
  188. package/src/capabilities/doctor/types.ts +15 -24
  189. package/src/capabilities/emptyRecovery.test.ts +10 -2
  190. package/src/capabilities/graph/query.render.ts +2 -2
  191. package/src/capabilities/info/collectInfo.ts +8 -1
  192. package/src/capabilities/info/info.render.ts +6 -2
  193. package/src/capabilities/info/info.test.ts +37 -1
  194. package/src/capabilities/info/types.ts +10 -2
  195. package/src/capabilities/lazy.test.ts +26 -2
  196. package/src/capabilities/listLookup.shipped.exec.test.ts +340 -0
  197. package/src/capabilities/prompt/prompt.render.ts +2 -2
  198. package/src/capabilities/renderVocabularySync.test.ts +82 -0
  199. package/src/capabilities/resources/resources.test.ts +38 -2
  200. package/src/capabilities/scopeTypeSync.test.ts +29 -15
  201. package/src/capabilities/setup/buildPlan.ts +45 -41
  202. package/src/capabilities/setup/index.ts +1 -1
  203. package/src/capabilities/setup/mode.ts +2 -2
  204. package/src/capabilities/setup/operations/index.ts +6 -1
  205. package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
  206. package/src/capabilities/setup/operations/setupConfig.ts +7 -2
  207. package/src/capabilities/setup/operations/setupGenerator.ts +227 -84
  208. package/src/capabilities/setup/operations/setupLsp.ts +203 -38
  209. package/src/capabilities/setup/operations/setupMcp.ts +139 -62
  210. package/src/capabilities/setup/operations/setupSkills.ts +399 -60
  211. package/src/capabilities/setup/plan.render.ts +171 -76
  212. package/src/capabilities/setup/plan.ts +17 -12
  213. package/src/capabilities/setup/setup.mcp.correctness.test.ts +9 -9
  214. package/src/capabilities/setup/setup.render.test.ts +50 -36
  215. package/src/capabilities/setup/setup.render.ts +1 -1
  216. package/src/capabilities/setup/setup.test.ts +1126 -50
  217. package/src/capabilities/setup/setup.verb.ts +174 -68
  218. package/src/capabilities/setup/targets.ts +138 -60
  219. package/src/capabilities/setup/types.ts +10 -18
  220. package/src/capabilities/shared/index.ts +18 -6
  221. package/src/capabilities/shared/packageManager.test.ts +421 -6
  222. package/src/capabilities/shared/packageManager.ts +394 -59
  223. package/src/capabilities/skill/bundledSkills.test.ts +347 -0
  224. package/src/capabilities/skill/discover.ts +100 -11
  225. package/src/capabilities/skill/render.ts +2 -2
  226. package/src/capabilities/skill/skill.test.ts +17 -4
  227. package/src/capabilities/sources/installSkills.ts +181 -3
  228. package/src/capabilities/sources/runUpdate.ts +78 -2
  229. package/src/capabilities/sources/sources.test.ts +228 -0
  230. package/src/capabilities/sources/update.verb.ts +3 -3
  231. package/src/capabilities/surface.test.ts +14 -6
  232. package/src/capabilities/upgrade/runUpgrade.ts +45 -6
  233. package/src/capabilities/upgrade/types.ts +10 -2
  234. package/src/capabilities/upgrade/upgrade.render.ts +9 -2
  235. package/src/capabilities/upgrade/upgrade.test.ts +72 -0
  236. package/src/constants.ts +14 -1
  237. package/src/identity.test.ts +18 -5
  238. package/src/kernel/completion/entitySource.test.ts +15 -2
  239. package/src/kernel/completion/safety.test.ts +16 -9
  240. package/src/kernel/config/defaults.test.ts +19 -3
  241. package/src/kernel/config/readConfig.test.ts +1 -1
  242. package/src/kernel/index.ts +11 -2
  243. package/src/kernel/interactivity.test.ts +76 -0
  244. package/src/kernel/interactivity.ts +69 -0
  245. package/src/kernel/packs/compile.test.ts +92 -0
  246. package/src/kernel/packs/renderPack.ts +3 -3
  247. package/src/kernel/packs/resolveEntity.test.ts +75 -14
  248. package/src/kernel/packs/resolveEntity.ts +88 -24
  249. package/src/kernel/packs/runBodies.ts +84 -19
  250. package/src/kernel/packs/schema.ts +60 -4
  251. package/src/kernel/packs/sparql/applyFilters.ts +125 -4
  252. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +290 -0
  253. package/src/kernel/packs/sparql/buildLookupQuery.ts +327 -45
  254. package/src/kernel/packs/toolDescription.test.ts +35 -1
  255. package/src/kernel/packs/types.ts +125 -2
  256. package/src/kernel/project/cli/buildProgram.test.ts +73 -2
  257. package/src/kernel/project/cli/buildProgram.ts +8 -1
  258. package/src/kernel/project/cli/dispatch.test.ts +5 -4
  259. package/src/kernel/project/cli/dispatch.ts +48 -25
  260. package/src/kernel/project/cli/globalFlags.ts +4 -1
  261. package/src/kernel/project/cli/helpFormat.ts +1 -2
  262. package/src/kernel/project/cli/rootHelp.ts +44 -8
  263. package/src/kernel/project/mcp/registerVerb.ts +20 -1
  264. package/src/kernel/render/prefixes.test.ts +2 -0
  265. package/src/kernel/render/renderers.ts +1 -1
  266. package/src/kernel/render/style.ts +32 -9
  267. package/src/kernel/render/vocabulary.ts +276 -0
  268. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
  269. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  270. package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
  271. package/src/kernel/runtime/graphpack/embedded.ts +14 -6
  272. package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
  273. package/src/kernel/runtime/graphpack/read.ts +14 -12
  274. package/src/kernel/runtime/loadSession.ts +3 -1
  275. package/src/kernel/runtime/probe.ts +1 -1
  276. package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
  277. package/src/kernel/runtime/refs/parseRef.ts +25 -1
  278. package/src/kernel/runtime/refs/resolve.ts +9 -3
  279. package/src/kernel/runtime/types.ts +17 -0
  280. package/src/kernel/spec/emitReference.ts +1 -1
  281. package/src/kernel/spec/types.ts +35 -6
  282. package/src/testing/PARITY_GAPS.ts +9 -3
  283. package/src/testing/behavioral/docExamples.test.ts +4 -2
  284. package/src/testing/behavioral/errorMatrix.mcp.test.ts +110 -7
  285. package/src/testing/behavioral/journeys.firstInstall.test.ts +7 -2
  286. package/src/testing/behavioral/journeys.packageStories.test.ts +6 -3
  287. package/src/testing/eval/__snapshots__/eval.test.ts.snap +26 -1
  288. package/src/testing/eval/cases/readNouns.ts +245 -9
  289. package/src/testing/fixtures/blockGraph.ts +28 -13
  290. package/src/testing/fixtures/graph/canonical.ts +95 -12
  291. package/src/testing/helpers/runCli.test.ts +44 -0
  292. package/src/testing/helpers/runCli.ts +27 -3
  293. package/dist/src/capabilities/shared/bands.js +0 -16
  294. package/dist/src/capabilities/shared/bands.js.map +0 -1
  295. package/src/capabilities/shared/bands.ts +0 -16
@@ -0,0 +1,340 @@
1
+ /**
2
+ * The list→lookup grammar, EXECUTED against the shipped pack, for every noun
3
+ * that has both halves (PROTECTED).
4
+ *
5
+ * The tool descriptions instruct an agent to take a row's `name` VERBATIM from
6
+ * `<noun>_list` to `<noun>_lookup`. That promise broke for `standard` when the
7
+ * shipped data and the story keyed the two halves on different properties:
8
+ * `standard list` published names synthesized from the IRI while
9
+ * `standard lookup` answered only the ~13% of standards carrying an asserted
10
+ * `cs:name` — a closed loop where every published address missed (#1047). The
11
+ * fixture suites could not see it, because a fixture obligingly asserts
12
+ * whatever the story reads; only the corpus the release actually ships can
13
+ * falsify the promise the release actually makes.
14
+ *
15
+ * So this file boots the SHIPPED graph and asserts the round trip over the
16
+ * WHOLE corpus, not a sample, for every noun with a list+lookup pair — the
17
+ * defect class is not standard-specific, since every pair shares this
18
+ * machinery. Three guarantees per noun:
19
+ *
20
+ * - every name `list` publishes resolves through `lookup` (executed through
21
+ * the real verb bodies, so glob/IRI argument-shape dispatch is exercised
22
+ * with the very strings an agent would paste);
23
+ * - every published name is in the addressable population (`listEntityNames`
24
+ * — the ONE pool that feeds miss-suggestions, glob expansion AND `sample`'s
25
+ * draw), so a suggestion can never offer a name the resolve cannot answer;
26
+ * - for a story that derives names (`nameFallback: "iri"`), that pool covers
27
+ * the WHOLE class population — `sample` is presented as "see real data
28
+ * shapes", and drawing only from the subset carrying an asserted name would
29
+ * be the #1047 defect wearing sample's clothes.
30
+ *
31
+ * The final describe is the negative control: the same assertions run against
32
+ * a fixture compiled in the exact pre-#1047 shape (a list that synthesizes
33
+ * names over a lookup that requires the asserted property) and are shown to
34
+ * FAIL. A guard whose failing case no longer fails is worse than no guard.
35
+ */
36
+
37
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
38
+ import { compileStoryModule } from "../kernel/packs/compile.js";
39
+ import { listEntityNames } from "../kernel/packs/resolveEntity.js";
40
+ import {
41
+ distributionSource,
42
+ type PackDefinition,
43
+ } from "../kernel/packs/types.js";
44
+ import { verbKey } from "../kernel/packs/uniqueness.js";
45
+ import { bootRuntime } from "../kernel/runtime/boot.js";
46
+ import type { PragmaRuntime } from "../kernel/runtime/types.js";
47
+ import type { CapabilityModule, VerbSpec } from "../kernel/spec/types.js";
48
+ import { buildFixtureRuntime } from "../testing/helpers/packRuntime.js";
49
+ import { TEST_FLAGS } from "../testing/helpers/projectCli.js";
50
+ import { declaredStories, storyModules } from "./distribution.js";
51
+
52
+ /** The provenance label the distribution's own compiled stories carry. */
53
+ const SOURCE = distributionSource("pragma.conf.ts");
54
+
55
+ /**
56
+ * Every noun declaring BOTH halves of the grammar, derived from the config
57
+ * rather than listed — a story added tomorrow is covered the moment it is
58
+ * declared, by the author who declares it and without their help.
59
+ */
60
+ const NOUNS: readonly string[] = [...declaredStories]
61
+ .filter(([, story]) => story.list !== undefined && story.lookup !== undefined)
62
+ .map(([noun]) => noun);
63
+
64
+ /**
65
+ * Nouns whose shipped corpus is empty TODAY (`ds:Token` has no instances in
66
+ * the current packs) — a data-content gap this suite does not own.
67
+ *
68
+ * The allowlist EXPIRES BY CONSTRUCTION: an entry here is asserted to have
69
+ * ZERO rows, so the moment upstream ships instances the entry goes red and
70
+ * must be deleted. A list that merely skipped the non-empty assertion would
71
+ * be a permanent blind spot — `token` gains data, nobody removes the entry,
72
+ * a later regression back to zero rows stays vacuously green — which is
73
+ * exactly the hidden-empty case this guard exists to prevent, sitting inside
74
+ * the guard itself.
75
+ */
76
+ const EMPTY_CORPUS_TODAY: readonly string[] = ["token"];
77
+
78
+ /** The verb `<noun> <verb>` from a compiled module, or throw naming the gap. */
79
+ function verbOf(
80
+ module: CapabilityModule,
81
+ noun: string,
82
+ verb: string,
83
+ ): VerbSpec {
84
+ const found = module.verbs.find((v) => verbKey(v.path) === `${noun} ${verb}`);
85
+ if (!found) throw new Error(`no compiled "${noun} ${verb}" verb`);
86
+ return found;
87
+ }
88
+
89
+ /** Run `<noun> list` and return the names it publishes, verbatim, deduped. */
90
+ async function publishedNames(
91
+ rt: PragmaRuntime,
92
+ module: CapabilityModule,
93
+ noun: string,
94
+ ): Promise<string[]> {
95
+ const rows = (await verbOf(module, noun, "list").run({}, rt)) as Record<
96
+ string,
97
+ string
98
+ >[];
99
+ return [
100
+ ...new Set(rows.map((row) => row.name ?? "").filter((name) => name !== "")),
101
+ ];
102
+ }
103
+
104
+ /**
105
+ * Take every published name VERBATIM through the real lookup verb, exactly as
106
+ * the tool description instructs an agent to, and return the per-name errors.
107
+ * One batched call: the resolver treats each name independently, and a total
108
+ * miss throws — surfaced as one synthetic error so the assertion that reads
109
+ * this stays a set comparison rather than a try/catch.
110
+ */
111
+ async function lookupErrors(
112
+ rt: PragmaRuntime,
113
+ module: CapabilityModule,
114
+ noun: string,
115
+ names: readonly string[],
116
+ ): Promise<{ query: string; code: string }[]> {
117
+ if (names.length === 0) return [];
118
+ try {
119
+ const output = (await verbOf(module, noun, "lookup").run(
120
+ { name: [...names] },
121
+ rt,
122
+ )) as { errors: { query: string; code: string }[] };
123
+ return output.errors;
124
+ } catch (error) {
125
+ return [
126
+ {
127
+ query: `(all ${names.length})`,
128
+ code: error instanceof Error ? error.message : String(error),
129
+ },
130
+ ];
131
+ }
132
+ }
133
+
134
+ // ONE store for the file — booting the shipped pack is the expensive part, and
135
+ // this package already contends under parallel runs.
136
+ let rt: PragmaRuntime;
137
+ beforeAll(async () => {
138
+ rt = bootRuntime(TEST_FLAGS);
139
+ await rt.store.get();
140
+ }, 60_000);
141
+ afterAll(async () => {
142
+ (await rt.store.get()).store.dispose();
143
+ });
144
+
145
+ /** One scalar from the shipped graph, queried directly. */
146
+ async function scalar(query: string): Promise<string | undefined> {
147
+ const result = await rt.query.sparql(query);
148
+ if (result.type !== "select") return undefined;
149
+ const row = result.bindings[0];
150
+ return row === undefined ? undefined : Object.values(row)[0];
151
+ }
152
+
153
+ describe("every list-published name resolves through lookup, whole corpus (PROTECTED)", () => {
154
+ it("derives at least one list+lookup noun from the config", () => {
155
+ expect(NOUNS.length).toBeGreaterThan(0);
156
+ // The two nouns this guarantee has already been broken for / leaned on.
157
+ expect(NOUNS).toContain("standard");
158
+ expect(NOUNS).toContain("block");
159
+ });
160
+
161
+ it.each(
162
+ NOUNS.map((noun) => [noun] as const),
163
+ )("%s: every published name round-trips, and its corpus is not silently empty", async (noun) => {
164
+ const module = storyModules.get(noun) as CapabilityModule;
165
+ const names = await publishedNames(rt, module, noun);
166
+ if (EMPTY_CORPUS_TODAY.includes(noun)) {
167
+ // Asserted empty, not skipped: this is what expires the allowlist.
168
+ expect(
169
+ names,
170
+ `${noun} is allowlisted as an empty corpus but published rows — ` +
171
+ "delete it from EMPTY_CORPUS_TODAY so a later regression to zero rows cannot hide behind the entry",
172
+ ).toEqual([]);
173
+ } else {
174
+ expect(
175
+ names.length,
176
+ `${noun} list published no rows at all from the shipped pack`,
177
+ ).toBeGreaterThan(0);
178
+ }
179
+
180
+ const errors = await lookupErrors(rt, module, noun, names);
181
+ expect(
182
+ errors,
183
+ `${noun} list published ${errors.length} name(s) its own lookup cannot resolve — ` +
184
+ "the two-step grammar the tool descriptions document is broken for every agent following it",
185
+ ).toEqual([]);
186
+ }, 120_000);
187
+
188
+ it.each(
189
+ NOUNS.map((noun) => [noun] as const),
190
+ )("%s: every published name is in the pool that feeds suggestions, globs and sample", async (noun) => {
191
+ const story = declaredStories.get(noun) as PackDefinition;
192
+ const lookup = story.lookup;
193
+ if (!lookup) throw new Error(`no "${noun}" lookup`);
194
+ const module = storyModules.get(noun) as CapabilityModule;
195
+
196
+ const names = await publishedNames(rt, module, noun);
197
+ const pool = await listEntityNames(rt, lookup, SOURCE);
198
+ // The resolve matches case-insensitively; hold the pool to the same bar.
199
+ const addressable = new Set(pool.map((name) => name.toLowerCase()));
200
+ const missing = names.filter(
201
+ (name) => !addressable.has(name.toLowerCase()),
202
+ );
203
+ expect(
204
+ missing,
205
+ `${noun}: published but not addressable — a miss-suggestion or glob over this pool can hand out none of these, and sample can never draw them`,
206
+ ).toEqual([]);
207
+ }, 60_000);
208
+
209
+ it.each(
210
+ NOUNS.filter(
211
+ (noun) => declaredStories.get(noun)?.lookup?.nameFallback === "iri",
212
+ ).map((noun) => [noun] as const),
213
+ )("%s: the derived-name pool covers the WHOLE class population (sample is representative)", async (noun) => {
214
+ const story = declaredStories.get(noun) as PackDefinition;
215
+ const lookup = story.lookup;
216
+ if (!lookup?.type && !lookup?.types?.length) {
217
+ throw new Error(`"${noun}" derives names without a class constraint`);
218
+ }
219
+ const types = (lookup.types ?? [lookup.type as string]).join(" ");
220
+ // The pool is DISTINCT NAMES, and a name may be legitimately shared (two
221
+ // shipped standards carry the same upstream rdfs:label; the ranked
222
+ // resolve answers a shared name with every entity it reaches). So the
223
+ // full-population claim is counted the way the pool is built: one name
224
+ // per asserted `by` value, plus one derived name per instance carrying
225
+ // none. A pool smaller than that is the #1047 defect — sample and
226
+ // suggestions drawing from the labelled subset only. (A prose label
227
+ // colliding with a slash-derived name would make this one too strict;
228
+ // that collision would itself be a data defect worth the red bar.)
229
+ const labelled = Number(
230
+ await scalar(
231
+ [
232
+ "SELECT (COUNT(DISTINCT ?name) AS ?n) WHERE {",
233
+ ` VALUES ?class { ${types} }`,
234
+ ` ?uri a ?class ; ${lookup.by} ?name .`,
235
+ "}",
236
+ ].join("\n"),
237
+ ),
238
+ );
239
+ const bare = Number(
240
+ await scalar(
241
+ [
242
+ "SELECT (COUNT(DISTINCT ?uri) AS ?n) WHERE {",
243
+ ` VALUES ?class { ${types} }`,
244
+ " ?uri a ?class .",
245
+ ` FILTER NOT EXISTS { ?uri ${lookup.by} ?any . }`,
246
+ "}",
247
+ ].join("\n"),
248
+ ),
249
+ );
250
+ const pool = await listEntityNames(rt, lookup, SOURCE);
251
+
252
+ expect(labelled + bare).toBeGreaterThan(0);
253
+ expect(
254
+ pool.length,
255
+ `${noun}: ${labelled} asserted name(s) + ${bare} instance(s) with none, but only ` +
256
+ `${pool.length} addressable names — sample's draw pool is an unrepresentative slice of the corpus`,
257
+ ).toBe(labelled + bare);
258
+ }, 60_000);
259
+ });
260
+
261
+ describe("negative control: the pre-#1047 shape fails these assertions", () => {
262
+ // The exact divergence that shipped: a list that COALESCEs an OPTIONAL
263
+ // display name with an IRI-derived one, over a lookup that requires the
264
+ // asserted property (no nameFallback). One fixture standard carries the
265
+ // property; the other is published under a name the lookup cannot reach.
266
+ const PREFIXES = {
267
+ rdf: "http://www.w3.org/1999/02/22-rdf-syntax-ns#",
268
+ cs: "http://pragma.canonical.com/codestandards#",
269
+ };
270
+ const TTL = `
271
+ @prefix rdf: <http://www.w3.org/1999/02/22-rdf-syntax-ns#> .
272
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
273
+ @prefix cs: <http://pragma.canonical.com/codestandards#> .
274
+
275
+ cs:name a owl:DatatypeProperty .
276
+ cs:description a owl:DatatypeProperty .
277
+
278
+ cs:react.component.tsdoc a cs:CodeStandard ;
279
+ cs:description "Published under a derived name; carries no cs:name." .
280
+ cs:code.function.purity a cs:CodeStandard ;
281
+ cs:name "Function purity" ;
282
+ cs:description "Carries an asserted cs:name." .
283
+ `;
284
+ const BROKEN_STORY: PackDefinition = {
285
+ noun: "standard",
286
+ description: "pre-#1047 shape",
287
+ list: {
288
+ query: [
289
+ "SELECT ?uri ?name ?description WHERE {",
290
+ " ?uri a cs:CodeStandard ;",
291
+ " cs:description ?description .",
292
+ " OPTIONAL { ?uri cs:name ?n . }",
293
+ ' BIND(COALESCE(?n, REPLACE(STRAFTER(STR(?uri), "#"), "\\\\.", "/")) AS ?name)',
294
+ "}",
295
+ "ORDER BY ?name",
296
+ ].join("\n"),
297
+ columns: [
298
+ { field: "name", label: "Name" },
299
+ { field: "description", label: "Description" },
300
+ ],
301
+ },
302
+ lookup: {
303
+ source: "sparql",
304
+ by: "cs:name", // required — no nameFallback: the pre-#1047 population
305
+ type: "cs:CodeStandard",
306
+ fields: [
307
+ {
308
+ name: "description",
309
+ property: "cs:description",
310
+ label: "Description",
311
+ },
312
+ ],
313
+ },
314
+ };
315
+
316
+ it("publishes a name its own lookup cannot resolve, and these assertions catch it", async () => {
317
+ const { rt: fixtureRt } = await buildFixtureRuntime({
318
+ ttl: TTL,
319
+ prefixes: PREFIXES,
320
+ });
321
+ const module = compileStoryModule(BROKEN_STORY, SOURCE, PREFIXES);
322
+
323
+ const names = await publishedNames(fixtureRt, module, "standard");
324
+ expect(names).toContain("react/component/tsdoc");
325
+ expect(names).toContain("Function purity");
326
+
327
+ // The round-trip assertion goes RED on the synthesized name…
328
+ const errors = await lookupErrors(fixtureRt, module, "standard", names);
329
+ expect(errors.map((error) => error.query)).toEqual([
330
+ "react/component/tsdoc",
331
+ ]);
332
+
333
+ // …and so does the pool-inclusion assertion: the addressable population
334
+ // is only the labelled subset.
335
+ const lookup = BROKEN_STORY.lookup;
336
+ if (!lookup) throw new Error("fixture story has no lookup");
337
+ const pool = await listEntityNames(fixtureRt, lookup, SOURCE);
338
+ expect(pool).toEqual(["Function purity"]);
339
+ }, 60_000);
340
+ });
@@ -19,9 +19,9 @@ function argTokens(args: readonly PromptArgument[]): string {
19
19
  }
20
20
 
21
21
  export const promptListFormatters: Formatters<PromptListData> = {
22
- // Zero prompts: plain stdout stays empty — the notice is `emptyNotice`,
22
+ // Zero prompts: plain stdout stays empty — the notice is `notice`,
23
23
  // routed to stderr (exit 0) by the dispatcher so a pipe reads no prose.
24
- emptyNotice: (data) =>
24
+ notice: (data) =>
25
25
  data.prompts.length === 0 ? "No prompts in the store." : undefined,
26
26
  plain(data) {
27
27
  if (data.prompts.length === 0) return "";
@@ -0,0 +1,82 @@
1
+ /**
2
+ * Render-vocabulary sync (PROTECTED) — the two halves of the "one vocabulary"
3
+ * guarantee that cannot be a shared module.
4
+ *
5
+ * 1. CROSS-BINARY GLYPHS. The pragma CLI and the summon-core Ink wizard
6
+ * narrate the same run — a ✓ per completed effect, a ✗ when it fails — but
7
+ * they cannot read one module: pragma's renderers are statically reachable
8
+ * from the `--help`/`__complete` fast paths, and THREE protected guards
9
+ * (`create.test.ts`'s static-graph walk, `lazy.test.ts`, the perf budgets)
10
+ * forbid summon-core from that graph — loading it costs a measured
11
+ * double-digit-MB RSS, and an eager summon-core subpath import has already
12
+ * cost the fast paths ~46 ms once (BUDGETS.md). So the duplication is kept
13
+ * and made STRUCTURAL: summon-core publishes its wizard glyphs off the
14
+ * light `/format` seam, and this test pins them byte-equal to the
15
+ * vocabulary's. A divergence is a red build, not a review hope.
16
+ *
17
+ * 2. THE RETIRED WORD STAYS RETIRED, in the model as well as the copy. The
18
+ * render goldens pin that no output says "band"; this pins the same for
19
+ * the CODE of the modules that own setup/doctor's model and rendering, so
20
+ * the split cannot reopen the way it did — words moved, model unmoved.
21
+ * Comments are stripped first: history may NAME the retired word (the
22
+ * vocabulary docblock does, deliberately); code may not USE it.
23
+ */
24
+
25
+ import { readdirSync, readFileSync } from "node:fs";
26
+ import { dirname, join, relative, resolve } from "node:path";
27
+ import { fileURLToPath } from "node:url";
28
+ import { describe, expect, it } from "vitest";
29
+ import { GLYPHS } from "../kernel/render/vocabulary.js";
30
+
31
+ const here = dirname(fileURLToPath(import.meta.url));
32
+
33
+ describe("cross-binary glyph vocabulary (PROTECTED)", () => {
34
+ it("summon-core's wizard marks byte-equal the vocabulary's", async () => {
35
+ // Dynamic import, test-only — production pragma code never reaches
36
+ // summon-core statically, and this test must not become the exception.
37
+ const { COMPLETED_GLYPH, FAILURE_GLYPH } = await import(
38
+ "@canonical/summon-core/format"
39
+ );
40
+ expect(COMPLETED_GLYPH).toBe(GLYPHS.success);
41
+ expect(FAILURE_GLYPH).toBe(GLYPHS.failure);
42
+ });
43
+ });
44
+
45
+ /** The directories whose model/rendering code the word-guard covers. */
46
+ const GUARDED_DIRS = [
47
+ "doctor",
48
+ "setup",
49
+ "shared",
50
+ "sources",
51
+ "skill",
52
+ "../kernel/render",
53
+ ];
54
+
55
+ /** Every non-test .ts file under a directory, recursively. */
56
+ function sourceFiles(dir: string): string[] {
57
+ return readdirSync(dir, { withFileTypes: true }).flatMap((entry) => {
58
+ const path = join(dir, entry.name);
59
+ if (entry.isDirectory()) return sourceFiles(path);
60
+ if (!entry.name.endsWith(".ts") || entry.name.endsWith(".test.ts"))
61
+ return [];
62
+ return [path];
63
+ });
64
+ }
65
+
66
+ /** Strip block and line comments, leaving only code that compiles to bytes. */
67
+ const stripComments = (source: string): string =>
68
+ source.replace(/\/\*[\s\S]*?\*\//g, "").replace(/(^|\s)\/\/[^\n]*/g, "$1");
69
+
70
+ describe("the retired word stays retired in the type layer (PROTECTED)", () => {
71
+ it("no setup/doctor model or render code says `band`", () => {
72
+ const offenders: string[] = [];
73
+ for (const dir of GUARDED_DIRS) {
74
+ for (const file of sourceFiles(resolve(here, dir))) {
75
+ const code = stripComments(readFileSync(file, "utf-8"));
76
+ const hit = code.match(/\bbands?\b/i);
77
+ if (hit) offenders.push(`${relative(here, file)}: ${hit[0]}`);
78
+ }
79
+ }
80
+ expect(offenders).toEqual([]);
81
+ });
82
+ });
@@ -61,8 +61,31 @@ const EVERYTHING: McpListable = { sources: [{ as: "entities" }] };
61
61
  * only. Payload SIZE is the property that actually regressed (712 entries /
62
62
  * ~155 KB on every connect), and it is the one an agent pays for twice: once in
63
63
  * transfer, once in the context window it can no longer spend on the task.
64
+ *
65
+ * **Raised from 60,000 to 100,000 on 2026-09-01**, when the
66
+ * `@canonical/token-ontology` pack took the payload to 65,119 bytes. The
67
+ * listing was kept whole rather than trimmed to fit: the per-class counts are
68
+ * how an agent gets the shape of the graph before spending a single lookup,
69
+ * and that is worth more than the bytes.
70
+ *
71
+ * **Cost scales with the number of T-Box terms and their metadata, not with
72
+ * the number of packs.** The `ontology` noun emits one resource per matching
73
+ * T-Box entity (`provider.ts`), so a pack costs what its own vocabulary
74
+ * costs — this one happened to add ~23 KB, which is a fact about this
75
+ * ontology and not a rate. The headroom here does not convert into "one more
76
+ * pack" of any size.
77
+ *
78
+ * It is also the only listing that grows this way: every other noun lists
79
+ * collections with their counts, so those grow with the number of nouns. When
80
+ * this ceiling is next reached, the fix is to scope the T-Box slice or
81
+ * paginate it — not to raise the number again, which would turn a gate into a
82
+ * formality.
83
+ *
84
+ * It is deliberately not removed. The regression it was written for is still
85
+ * worth catching, and a ceiling with headroom catches it while a deleted one
86
+ * catches nothing.
64
87
  */
65
- const LISTING_BUDGET_BYTES = 60_000;
88
+ const LISTING_BUDGET_BYTES = 100_000;
66
89
 
67
90
  describe("resource listing (storeless, over the pack index)", () => {
68
91
  it("degrades to a recovery entry on a missing or legacy index", () => {
@@ -322,7 +345,20 @@ describe("resource surface over the server (embedded pack)", () => {
322
345
  let harness: Awaited<ReturnType<typeof projectMcp>>;
323
346
  beforeAll(async () => {
324
347
  harness = await projectMcp(capabilities);
325
- });
348
+ // Boot the store HERE, not in whichever test happens to read first.
349
+ //
350
+ // `createLazyStore` memoises the boot PROMISE, and vitest's per-test
351
+ // timeout aborts the test without aborting the work — so a cold boot that
352
+ // overruns is charged partly to the test that triggered it and partly to
353
+ // the next one, which merely awaits the same promise. That reads as a
354
+ // sudden per-read regression and is nothing of the kind; it cost a real
355
+ // investigation to find that out.
356
+ //
357
+ // Paying it in the hook attributes it where it belongs, keeps every `it`
358
+ // in this file measuring what it claims to, and bounds it by hookTimeout
359
+ // rather than by a 5 s default that pack growth will keep outgrowing.
360
+ await harness.readResource(BUTTON_URI);
361
+ }, 60_000);
326
362
  afterAll(async () => {
327
363
  await harness.cleanup();
328
364
  });
@@ -1,12 +1,20 @@
1
1
  /**
2
- * Type-sync pin (PROTECTED): the CLI's redeclared `ScopeBand`/`ScopeSelection`
3
- * mirrors must stay structurally EQUAL to the `@canonical/harnesses` types.
2
+ * Type-sync pin (PROTECTED): the CLI's `Scope`/`ScopeSelection` — declared
3
+ * once, in the rendering vocabulary — must stay structurally EQUAL to the
4
+ * `@canonical/harnesses` `ScopeBand`/`ScopeSelection` types.
4
5
  *
5
- * The CLI redeclares these in `doctor/types.ts` and `setup/types.ts` on purpose
6
- * — so those statically-reachable type modules never pull the harnesses runtime
7
- * onto the fast-path module graph (the lazy-dispatch invariant). That freedom is
8
- * only safe if the mirrors do not DRIFT from the source of truth, which this
9
- * `expectTypeOf` pin enforces at `tsc` time.
6
+ * The CLI declares these in `kernel/render/vocabulary.ts` on purpose — so the
7
+ * statically-reachable type modules never pull the harnesses runtime onto the
8
+ * fast-path module graph (the lazy-dispatch invariant). That freedom is only
9
+ * safe if the declaration does not DRIFT from the source of truth, which this
10
+ * `expectTypeOf` pin enforces at `tsc` time. The NAMES differ deliberately:
11
+ * "band" is the harnesses package's word (its published API), "scope" is
12
+ * pragma's — the word its `--global`/`--local` flags, its rendered sentences
13
+ * and, since the type-layer rename, its model all use.
14
+ *
15
+ * `doctor/types.ts` and `setup/types.ts` re-export the vocabulary's types, and
16
+ * those re-exports are pinned too, so the one-definition property cannot
17
+ * silently regress into a fresh local redeclaration that then drifts.
10
18
  *
11
19
  * Every harnesses import here is `import type` (type-only, erased at emit), so
12
20
  * this file loads no harnesses runtime — the `lazy.test` no-runtime-import guard
@@ -19,19 +27,25 @@ import type {
19
27
  ScopeSelection as HarnessScopeSelection,
20
28
  } from "@canonical/harnesses";
21
29
  import { describe, expectTypeOf, it } from "vitest";
22
- import type { ScopeBand as DoctorScopeBand } from "./doctor/types.js";
30
+ import type { Scope, ScopeSelection } from "../kernel/render/vocabulary.js";
31
+ import type { Scope as DoctorScope } from "./doctor/types.js";
23
32
  import type {
24
- ScopeBand as SetupScopeBand,
33
+ Scope as SetupScope,
25
34
  ScopeSelection as SetupScopeSelection,
26
35
  } from "./setup/types.js";
27
36
 
28
- describe("scope type-sync — CLI mirrors == @canonical/harnesses", () => {
29
- it("both ScopeBand mirrors equal the harnesses ScopeBand", () => {
30
- expectTypeOf<DoctorScopeBand>().toEqualTypeOf<HarnessScopeBand>();
31
- expectTypeOf<SetupScopeBand>().toEqualTypeOf<HarnessScopeBand>();
37
+ describe("scope type-sync — the CLI vocabulary == @canonical/harnesses", () => {
38
+ it("the vocabulary Scope equals the harnesses ScopeBand", () => {
39
+ expectTypeOf<Scope>().toEqualTypeOf<HarnessScopeBand>();
40
+ });
41
+
42
+ it("the vocabulary ScopeSelection equals the harnesses ScopeSelection", () => {
43
+ expectTypeOf<ScopeSelection>().toEqualTypeOf<HarnessScopeSelection>();
32
44
  });
33
45
 
34
- it("the setup ScopeSelection mirror equals the harnesses ScopeSelection", () => {
35
- expectTypeOf<SetupScopeSelection>().toEqualTypeOf<HarnessScopeSelection>();
46
+ it("doctor and setup re-export the one vocabulary definition", () => {
47
+ expectTypeOf<DoctorScope>().toEqualTypeOf<Scope>();
48
+ expectTypeOf<SetupScope>().toEqualTypeOf<Scope>();
49
+ expectTypeOf<SetupScopeSelection>().toEqualTypeOf<ScopeSelection>();
36
50
  });
37
51
  });