@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
@@ -9,9 +9,19 @@ import { tmpdir } from "node:os";
9
9
  import { join } from "node:path";
10
10
  import type { Client } from "@modelcontextprotocol/sdk/client/index.js";
11
11
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
12
- import { createTestMcpClient } from "#testing";
12
+ import {
13
+ createTestMcpClient,
14
+ RECIPE_PREFIXES,
15
+ RECIPE_STORY,
16
+ RECIPE_TTL,
17
+ } from "#testing";
18
+ import { TOKEN_READ_SURFACE_ENABLED } from "../../domains/token/featureFlag.js";
13
19
  import type { McpErrorPayload } from "../types.js";
14
20
 
21
+ // token_list, token_lookup, and token_sample are gated behind the token
22
+ // read-surface feature flag.
23
+ const EXPECTED_TOOL_COUNT = TOKEN_READ_SURFACE_ENABLED ? 34 : 31;
24
+
15
25
  let client: Client;
16
26
  let cleanup: () => Promise<void>;
17
27
 
@@ -52,9 +62,9 @@ function parseData(result: Record<string, unknown>): unknown {
52
62
  // =============================================================================
53
63
 
54
64
  describe("tool listing", () => {
55
- it("registers 29 tools", async () => {
65
+ it("registers the expected number of tools", async () => {
56
66
  const { tools } = await client.listTools();
57
- expect(tools).toHaveLength(29);
67
+ expect(tools).toHaveLength(EXPECTED_TOOL_COUNT);
58
68
  });
59
69
 
60
70
  it("all tools have descriptions", async () => {
@@ -75,8 +85,10 @@ describe("tool listing", () => {
75
85
  expect(names).toContain("standard_categories");
76
86
  expect(names).toContain("modifier_list");
77
87
  expect(names).toContain("modifier_lookup");
78
- expect(names).toContain("token_list");
79
- expect(names).toContain("token_lookup");
88
+ if (TOKEN_READ_SURFACE_ENABLED) {
89
+ expect(names).toContain("token_list");
90
+ expect(names).toContain("token_lookup");
91
+ }
80
92
  expect(names).toContain("tier_list");
81
93
  expect(names).toContain("config_show");
82
94
  expect(names).toContain("config_tier");
@@ -97,7 +109,9 @@ describe("tool listing", () => {
97
109
  // Sample tools
98
110
  expect(names).toContain("block_sample");
99
111
  expect(names).toContain("standard_sample");
100
- expect(names).toContain("token_sample");
112
+ if (TOKEN_READ_SURFACE_ENABLED) {
113
+ expect(names).toContain("token_sample");
114
+ }
101
115
  expect(names).toContain("modifier_sample");
102
116
  });
103
117
 
@@ -107,6 +121,67 @@ describe("tool listing", () => {
107
121
  expect(tool.name).not.toMatch(/^pragma_/);
108
122
  }
109
123
  });
124
+
125
+ // Golden surface: the full built-in tool set plus bundled packs, in
126
+ // registration order. The per-(noun, verb) reserved-guard flip must leave
127
+ // this byte-identical AND order-identical — registration order is the tool
128
+ // surface a client sees.
129
+ it("has a stable built-in tool surface", async () => {
130
+ const names = (await client.listTools()).tools.map((tool) => tool.name);
131
+
132
+ expect(names).toEqual([
133
+ "block_list",
134
+ "block_lookup",
135
+ "block_sample",
136
+ "modifier_list",
137
+ "modifier_lookup",
138
+ "modifier_sample",
139
+ ...(TOKEN_READ_SURFACE_ENABLED ? ["token_list", "token_lookup"] : []),
140
+ "tokens_add_config",
141
+ ...(TOKEN_READ_SURFACE_ENABLED ? ["token_sample"] : []),
142
+ "config_show",
143
+ "config_tier",
144
+ "config_channel",
145
+ "ontology_list",
146
+ "ontology_show",
147
+ "graph_query",
148
+ "graph_inspect",
149
+ "skill_list",
150
+ "skill_lookup",
151
+ "doctor",
152
+ "info",
153
+ "capabilities",
154
+ "llm",
155
+ "create_component",
156
+ "create_package",
157
+ "create_application",
158
+ "create_domain",
159
+ "create_route",
160
+ "create_wrapper",
161
+ // Bundled packs register after the built-in tools: `tier_list` and the
162
+ // whole `standard` noun are served by bundled story packs — the
163
+ // hand-written tier and standard domains were deleted.
164
+ "tier_list",
165
+ "standard_list",
166
+ "standard_lookup",
167
+ "standard_categories",
168
+ "standard_sample",
169
+ ]);
170
+ });
171
+
172
+ it("serves the standard tools from the bundled pack, not built-ins", async () => {
173
+ const { tools } = await client.listTools();
174
+ const standardList = tools.find((tool) => tool.name === "standard_list");
175
+ // Pack-compiled tool descriptions carry no story-pack suffix here only
176
+ // because the bundled pack curates them; the giveaway is the derived
177
+ // `search`/`category` params on the pack projection.
178
+ const params = Object.keys(
179
+ (standardList?.inputSchema as { properties?: object })?.properties ?? {},
180
+ );
181
+ expect(params).toContain("category");
182
+ expect(params).toContain("search");
183
+ expect(params).toContain("condensed");
184
+ });
110
185
  });
111
186
 
112
187
  // =============================================================================
@@ -414,7 +489,7 @@ describe("modifier_lookup", () => {
414
489
  // Token tools
415
490
  // =============================================================================
416
491
 
417
- describe("token_list", () => {
492
+ describe.skipIf(!TOKEN_READ_SURFACE_ENABLED)("token_list", () => {
418
493
  it("returns tokens", async () => {
419
494
  const result = await client.callTool({
420
495
  name: "token_list",
@@ -425,7 +500,7 @@ describe("token_list", () => {
425
500
  });
426
501
  });
427
502
 
428
- describe("token_lookup", () => {
503
+ describe.skipIf(!TOKEN_READ_SURFACE_ENABLED)("token_lookup", () => {
429
504
  it("returns token with theme values", async () => {
430
505
  const result = await client.callTool({
431
506
  name: "token_lookup",
@@ -463,10 +538,11 @@ describe("tier_list", () => {
463
538
  name: "tier_list",
464
539
  arguments: {},
465
540
  });
466
- const data = parseData(result) as { path: string }[];
541
+ // The bundled tier pack names the field `name` (its `ds:name` value).
542
+ const data = parseData(result) as { name: string }[];
467
543
  expect(data.length).toBeGreaterThan(0);
468
- const paths = data.map((t) => t.path);
469
- expect(paths).toContain("global");
544
+ const names = data.map((t) => t.name);
545
+ expect(names).toContain("global");
470
546
  });
471
547
  });
472
548
 
@@ -508,6 +584,8 @@ describe("config_show", () => {
508
584
  describe("config_tier", () => {
509
585
  it("sets, queries, and resets tier in workspace config", async () => {
510
586
  const dir = mkdtempSync(join(tmpdir(), "pragma-mcp-tier-"));
587
+ // Seed a project file so global-first writes stay in this workspace.
588
+ writeFileSync(join(dir, "pragma.config.json"), "{}");
511
589
 
512
590
  try {
513
591
  const scoped = await createTestMcpClient({ cwd: dir });
@@ -517,7 +595,10 @@ describe("config_tier", () => {
517
595
  name: "config_tier",
518
596
  arguments: { path: "global" },
519
597
  });
520
- expect(parseData(setResult)).toEqual({ tier: "global", action: "set" });
598
+ expect(parseData(setResult)).toMatchObject({
599
+ tier: "global",
600
+ action: "set",
601
+ });
521
602
 
522
603
  const queryResult = await scoped.client.callTool({
523
604
  name: "config_tier",
@@ -532,7 +613,10 @@ describe("config_tier", () => {
532
613
  name: "config_tier",
533
614
  arguments: { reset: true },
534
615
  });
535
- expect(parseData(resetResult)).toEqual({ tier: null, action: "reset" });
616
+ expect(parseData(resetResult)).toMatchObject({
617
+ tier: null,
618
+ action: "reset",
619
+ });
536
620
 
537
621
  const queriedAfterReset = await scoped.client.callTool({
538
622
  name: "config_tier",
@@ -554,6 +638,8 @@ describe("config_tier", () => {
554
638
  describe("config_channel", () => {
555
639
  it("sets, queries, and resets channel in workspace config", async () => {
556
640
  const dir = mkdtempSync(join(tmpdir(), "pragma-mcp-channel-"));
641
+ // Seed a project file so global-first writes stay in this workspace.
642
+ writeFileSync(join(dir, "pragma.config.json"), "{}");
557
643
 
558
644
  try {
559
645
  const scoped = await createTestMcpClient({ cwd: dir });
@@ -563,7 +649,7 @@ describe("config_channel", () => {
563
649
  name: "config_channel",
564
650
  arguments: { value: "experimental" },
565
651
  });
566
- expect(parseData(setResult)).toEqual({
652
+ expect(parseData(setResult)).toMatchObject({
567
653
  channel: "experimental",
568
654
  action: "set",
569
655
  });
@@ -581,7 +667,7 @@ describe("config_channel", () => {
581
667
  name: "config_channel",
582
668
  arguments: { reset: true },
583
669
  });
584
- expect(parseData(resetResult)).toEqual({
670
+ expect(parseData(resetResult)).toMatchObject({
585
671
  channel: "normal",
586
672
  action: "reset",
587
673
  });
@@ -699,7 +785,7 @@ describe("create_component", () => {
699
785
  const result = await client.callTool({
700
786
  name: "create_component",
701
787
  arguments: {
702
- framework: "react",
788
+ variant: "react",
703
789
  componentPath: "src/components/Button",
704
790
  },
705
791
  });
@@ -714,7 +800,7 @@ describe("create_component", () => {
714
800
  const result = await client.callTool({
715
801
  name: "create_component",
716
802
  arguments: {
717
- framework: "svelte",
803
+ variant: "svelte",
718
804
  componentPath: "src/lib/components/Toggle",
719
805
  },
720
806
  });
@@ -723,11 +809,11 @@ describe("create_component", () => {
723
809
  expect((data.generator as { name: string }).name).toBe("component/svelte");
724
810
  });
725
811
 
726
- it("returns error for invalid framework", async () => {
812
+ it("returns error for invalid variant", async () => {
727
813
  const result = await client.callTool({
728
814
  name: "create_component",
729
815
  arguments: {
730
- framework: "angular",
816
+ variant: "angular",
731
817
  componentPath: "src/components/Button",
732
818
  },
733
819
  });
@@ -769,7 +855,7 @@ describe("llm", () => {
769
855
  expect(data.context.counts.blocks).toBeGreaterThan(0);
770
856
  expect(data.context.counts.standards).toBeGreaterThan(0);
771
857
  expect(data.context.namespaces.length).toBeGreaterThan(0);
772
- expect(data.decisionTrees).toHaveLength(5);
858
+ expect(data.decisionTrees).toHaveLength(TOKEN_READ_SURFACE_ENABLED ? 5 : 4);
773
859
  expect(data.commandReference.length).toBeGreaterThan(0);
774
860
  });
775
861
  });
@@ -783,7 +869,9 @@ describe("error handling", () => {
783
869
  const notFoundCalls = [
784
870
  { name: "block_lookup", arguments: { names: ["X"] } },
785
871
  { name: "modifier_lookup", arguments: { names: ["X"] } },
786
- { name: "token_lookup", arguments: { names: ["X"] } },
872
+ ...(TOKEN_READ_SURFACE_ENABLED
873
+ ? [{ name: "token_lookup", arguments: { names: ["X"] } }]
874
+ : []),
787
875
  { name: "standard_lookup", arguments: { names: ["X"] } },
788
876
  ] as const;
789
877
 
@@ -857,7 +945,7 @@ describe("ontology_show", () => {
857
945
  const envelope = parseEnvelope(result);
858
946
  expect(envelope.ok).toBe(true);
859
947
  expect(envelope.condensed).toBe(true);
860
- expect(envelope.text).toEqual(expect.stringContaining("## ds:"));
948
+ expect(envelope.text).toEqual(expect.stringContaining("## Ontology ds:"));
861
949
  });
862
950
  });
863
951
 
@@ -964,6 +1052,48 @@ describe("graph_inspect", () => {
964
1052
  // Skill tools
965
1053
  // =============================================================================
966
1054
 
1055
+ describe("story-pack tools", () => {
1056
+ it("registers <noun>_list and <noun>_lookup from a stories config", async () => {
1057
+ const scoped = await createTestMcpClient({
1058
+ ttl: RECIPE_TTL,
1059
+ config: {
1060
+ tier: undefined,
1061
+ channel: "normal",
1062
+ stories: [RECIPE_STORY],
1063
+ prefixes: RECIPE_PREFIXES,
1064
+ },
1065
+ });
1066
+
1067
+ try {
1068
+ const tools = (await scoped.client.listTools()).tools.map(
1069
+ (tool) => tool.name,
1070
+ );
1071
+ expect(tools).toHaveLength(EXPECTED_TOOL_COUNT + 2);
1072
+ expect(tools).toContain("recipe_list");
1073
+ expect(tools).toContain("recipe_lookup");
1074
+
1075
+ const listResult = await scoped.client.callTool({
1076
+ name: "recipe_list",
1077
+ arguments: {},
1078
+ });
1079
+ const rows = parseData(listResult) as { name: string }[];
1080
+ expect(rows.map((row) => row.name)).toEqual(["Gazpacho", "Pancakes"]);
1081
+
1082
+ const lookupResult = await scoped.client.callTool({
1083
+ name: "recipe_lookup",
1084
+ arguments: { names: ["pancakes"] },
1085
+ });
1086
+ const data = parseData(lookupResult) as {
1087
+ results: { name: string; instructions: string }[];
1088
+ };
1089
+ expect(data.results.at(0)?.name).toBe("Pancakes");
1090
+ expect(data.results.at(0)?.instructions).toBe("Mix, fry, flip.");
1091
+ } finally {
1092
+ await scoped.cleanup();
1093
+ }
1094
+ });
1095
+ });
1096
+
967
1097
  describe("skill_list", () => {
968
1098
  it("returns skills data", async () => {
969
1099
  const result = await client.callTool({
@@ -1048,9 +1178,9 @@ describe("capabilities", () => {
1048
1178
  expect(toolNames).toContain("block_list");
1049
1179
  expect(toolNames).toContain("capabilities");
1050
1180
  expect(data.tools.every((t) => t.use_when.length > 0)).toBe(true);
1051
- expect(data.counts.total).toBe(29);
1181
+ expect(data.counts.total).toBe(EXPECTED_TOOL_COUNT);
1052
1182
  expect(data.counts.read).toBeGreaterThan(0);
1053
- expect(data.counts.write).toBe(5);
1183
+ expect(data.counts.write).toBe(9);
1054
1184
  expect(data.counts.orientation).toBe(2);
1055
1185
  expect(data.counts.diagnostic).toBe(2);
1056
1186
  expect(data.version).toBeDefined();
@@ -1131,14 +1261,17 @@ describe("condensed parameter", () => {
1131
1261
  expect((envelope.text as string).length).toBeGreaterThan(0);
1132
1262
  });
1133
1263
 
1134
- it("token_lookup returns condensed text", async () => {
1135
- const result = await client.callTool({
1136
- name: "token_lookup",
1137
- arguments: { names: ["color.primary"], condensed: true },
1138
- });
1139
- const envelope = parseEnvelope(result);
1140
- expect(envelope.ok).toBe(true);
1141
- expect(envelope.condensed).toBe(true);
1142
- expect(typeof envelope.text).toBe("string");
1143
- });
1264
+ it.skipIf(!TOKEN_READ_SURFACE_ENABLED)(
1265
+ "token_lookup returns condensed text",
1266
+ async () => {
1267
+ const result = await client.callTool({
1268
+ name: "token_lookup",
1269
+ arguments: { names: ["color.primary"], condensed: true },
1270
+ });
1271
+ const envelope = parseEnvelope(result);
1272
+ expect(envelope.ok).toBe(true);
1273
+ expect(envelope.condensed).toBe(true);
1274
+ expect(typeof envelope.text).toBe("string");
1275
+ },
1276
+ );
1144
1277
  });
package/src/mcp/types.ts CHANGED
@@ -25,6 +25,8 @@ export type ToolPayload =
25
25
  readonly meta?: {
26
26
  readonly count?: number;
27
27
  readonly filters?: Record<string, string>;
28
+ /** Queries that failed unexpectedly (surfaced only when > 0). */
29
+ readonly internalErrorCount?: number;
28
30
  };
29
31
  }
30
32
  | {
@@ -47,6 +49,8 @@ export interface ToolResponse<T = unknown> {
47
49
  readonly meta: {
48
50
  readonly count?: number;
49
51
  readonly filters?: Record<string, string>;
52
+ /** Queries that failed unexpectedly (surfaced only when > 0). */
53
+ readonly internalErrorCount?: number;
50
54
  };
51
55
  }
52
56
 
@@ -1,8 +1,16 @@
1
1
  import { afterAll, beforeAll, describe, expect, it } from "vitest";
2
2
  import type { PragmaContext } from "../domains/shared/context.js";
3
3
  import type { PragmaRuntime } from "../domains/shared/runtime.js";
4
+ import {
5
+ deriveReservedVerbs,
6
+ isReserved,
7
+ nounVerbFromPath,
8
+ nounVerbFromToolName,
9
+ } from "../domains/shared/stories/pack/index.js";
10
+ import { TOKEN_READ_SURFACE_ENABLED } from "../domains/token/featureFlag.js";
11
+ import { allSpecs } from "../mcp/tools/index.js";
4
12
  import createTestRuntime from "../testing/helpers/createTestRuntime.js";
5
- import collectCommands from "./collectCommands.js";
13
+ import collectCommands, { builtInCommands } from "./collectCommands.js";
6
14
 
7
15
  let runtime: PragmaRuntime;
8
16
 
@@ -34,4 +42,146 @@ describe("collectCommands", () => {
34
42
  expect(paths).toContain("graph query");
35
43
  expect(paths).toContain("graph inspect");
36
44
  });
45
+
46
+ // Golden surface: the full built-in command set, in emission order. The
47
+ // per-(noun, verb) reserved-guard flip must not add, drop, rename, or REORDER
48
+ // a single built-in command — order drives help output and registration.
49
+ // `tier list` and the whole `standard` noun are no longer here: the
50
+ // hand-written domains were deleted and are served by bundled story packs
51
+ // (asserted below).
52
+ it("has a stable built-in command surface", () => {
53
+ const paths = builtInCommands(makeCtx()).map((command) =>
54
+ command.path.join(" "),
55
+ );
56
+
57
+ expect(paths).toEqual([
58
+ "config tier",
59
+ "config channel",
60
+ "config trace",
61
+ "config framework",
62
+ "config show",
63
+ "create component",
64
+ "create package",
65
+ "create application",
66
+ "create domain",
67
+ "create route",
68
+ "create wrapper",
69
+ "setup",
70
+ "setup lsp",
71
+ "setup mcp",
72
+ "setup completions",
73
+ "setup skills",
74
+ "modifier list",
75
+ "modifier lookup",
76
+ "modifier sample",
77
+ ...(TOKEN_READ_SURFACE_ENABLED ? ["token list", "token lookup"] : []),
78
+ "tokens add-config",
79
+ ...(TOKEN_READ_SURFACE_ENABLED ? ["token sample"] : []),
80
+ "block list",
81
+ "block lookup",
82
+ "block sample",
83
+ "ontology list",
84
+ "ontology show",
85
+ "graph query",
86
+ "graph inspect",
87
+ "graphql build",
88
+ "graphql check",
89
+ "graphql serve",
90
+ "skill list",
91
+ "skill lookup",
92
+ "doctor",
93
+ "info",
94
+ "upgrade",
95
+ "llm",
96
+ "capabilities",
97
+ ]);
98
+ });
99
+
100
+ it("serves `tier list` from the bundled pack, not a built-in", () => {
101
+ const builtInPaths = builtInCommands(makeCtx()).map((c) =>
102
+ c.path.join(" "),
103
+ );
104
+ const allPaths = collectCommands(makeCtx()).map((c) => c.path.join(" "));
105
+
106
+ expect(builtInPaths).not.toContain("tier list");
107
+ expect(allPaths).toContain("tier list");
108
+ });
109
+
110
+ it("serves the whole `standard` noun from the bundled pack", () => {
111
+ const builtInPaths = builtInCommands(makeCtx()).map((c) =>
112
+ c.path.join(" "),
113
+ );
114
+ const allPaths = collectCommands(makeCtx()).map((c) => c.path.join(" "));
115
+
116
+ const standardVerbs = [
117
+ "standard list",
118
+ "standard lookup",
119
+ "standard categories",
120
+ "standard sample",
121
+ ];
122
+ for (const path of standardVerbs) {
123
+ expect(builtInPaths).not.toContain(path);
124
+ expect(allPaths).toContain(path);
125
+ }
126
+ });
127
+ });
128
+
129
+ // Cross-surface skew invariant (FIX 2 / R4-F1): the CLI and MCP surfaces both
130
+ // derive their reserved map through the single shared deriveReservedVerbs, so
131
+ // they MUST agree on every leaf read (noun, verb) reservation. If they drifted,
132
+ // a pack migrating a `list`/`lookup` verb could be admitted on one surface and
133
+ // blocked on the other. Operational nouns legitimately differ (graphql/setup/
134
+ // upgrade are CLI-only), so the invariant is scoped to the read verbs that
135
+ // packs actually emit.
136
+ describe("cross-surface reserved-verb parity", () => {
137
+ const READ_VERBS = ["list", "lookup"] as const;
138
+
139
+ it("CLI and MCP reserve the same list/lookup verbs for every leaf read noun", () => {
140
+ // Both surfaces derive reservations from BUILT-INS only (production reserves
141
+ // from built-ins, before appending packs), so a bundled/config/package pack
142
+ // never inflates the reserved map. Using collectCommands here would fold the
143
+ // bundled tier pack into the CLI side and break the symmetry.
144
+ const cliPairs = builtInCommands(makeCtx()).map((command) =>
145
+ nounVerbFromPath(command.path),
146
+ );
147
+ const mcpPairs = allSpecs.map((spec) => nounVerbFromToolName(spec.name));
148
+
149
+ const cliReserved = deriveReservedVerbs(cliPairs);
150
+ const mcpReserved = deriveReservedVerbs(mcpPairs);
151
+
152
+ // FIX 1 on the real CLI surface: operational nouns own no list/lookup, so
153
+ // they are reserved wholesale — a pack emitting `list` stays blocked.
154
+ for (const noun of ["config", "graph", "graphql", "setup", "create"]) {
155
+ expect(isReserved(cliReserved, noun, "list")).toBe(true);
156
+ }
157
+
158
+ // Leaf read nouns: those that own a `list`/`lookup` verb on either surface.
159
+ const readNouns = new Set(
160
+ [...cliPairs, ...mcpPairs]
161
+ .filter(([, verb]) => verb === "list" || verb === "lookup")
162
+ .map(([noun]) => noun),
163
+ );
164
+
165
+ // Sanity: the real leaf-migration targets are present, so this asserts
166
+ // something (a silently empty set would make the test vacuous). `tier`
167
+ // and `standard` are no longer here — both were cut over to bundled
168
+ // packs, so they are correctly absent from the built-in reserved
169
+ // surface on both sides. The token noun only owns read verbs while its
170
+ // feature flag is on.
171
+ const expectedReadNouns = ["block", "modifier"];
172
+ if (TOKEN_READ_SURFACE_ENABLED) expectedReadNouns.push("token");
173
+ for (const noun of expectedReadNouns) {
174
+ expect(readNouns.has(noun)).toBe(true);
175
+ }
176
+ expect(readNouns.has("standard")).toBe(false);
177
+
178
+ for (const noun of readNouns) {
179
+ for (const verb of READ_VERBS) {
180
+ expect(
181
+ isReserved(cliReserved, noun, verb),
182
+ `CLI/MCP disagree on ${noun} ${verb}`,
183
+ ).toBe(isReserved(mcpReserved, noun, verb));
184
+ }
185
+ }
186
+ });
37
187
  });
@@ -14,30 +14,27 @@ import { commands as modifierCommands } from "../domains/modifier/index.js";
14
14
  import { commands as ontologyCommands } from "../domains/ontology/index.js";
15
15
  import { commands as setupCommands } from "../domains/setup/index.js";
16
16
  import type { PragmaContext } from "../domains/shared/context.js";
17
+ import {
18
+ compilePackCommands,
19
+ deriveReservedVerbs,
20
+ nounVerbFromPath,
21
+ } from "../domains/shared/stories/pack/index.js";
17
22
  import { commands as skillCommands } from "../domains/skill/index.js";
18
- import { commands as standardCommands } from "../domains/standard/index.js";
19
- import { commands as tierCommands } from "../domains/tier/index.js";
20
23
  import { commands as tokenCommands } from "../domains/token/index.js";
21
24
 
22
25
  /**
23
- * Collect all CLI command definitions from domain modules.
24
- *
25
- * Extracted from runCli so the completions server can import
26
- * the command list independently without running the full CLI pipeline.
26
+ * The built-in (hand-written domain) command surface, in emission order.
27
27
  *
28
- * @param ctx - The pragma context providing store, config, and global flags.
29
- * @returns The full array of command definitions for all domains.
28
+ * Excludes story-pack commands (bundled, config, or package). Exported so the
29
+ * reserved-verb derivation and its tests share the exact input production uses
30
+ * — the reserved map is built from built-ins only, never from packs.
30
31
  */
31
- export default function collectCommands(
32
- ctx: PragmaContext,
33
- ): CommandDefinition[] {
32
+ export function builtInCommands(ctx: PragmaContext): CommandDefinition[] {
34
33
  return [
35
34
  ...configCommands(ctx),
36
35
  ...createCommands(),
37
36
  ...setupCommands(),
38
- ...standardCommands(ctx),
39
37
  ...modifierCommands(ctx),
40
- ...tierCommands(ctx),
41
38
  ...tokenCommands(ctx),
42
39
  ...blockCommands(ctx),
43
40
  ...ontologyCommands(ctx),
@@ -51,3 +48,28 @@ export default function collectCommands(
51
48
  buildCapabilitiesCommand(),
52
49
  ];
53
50
  }
51
+
52
+ /**
53
+ * Collect all CLI command definitions: built-in domains plus story packs.
54
+ *
55
+ * Extracted from runCli so the completions server can import the command list
56
+ * independently without running the full CLI pipeline.
57
+ *
58
+ * Story packs (bundled transitional packs, plus config- and package-declared
59
+ * stories) project onto the same surface. Leaf read nouns reserve only the
60
+ * (noun, verb) pairs they own, so a pack can add a verb no built-in owns and,
61
+ * once a built-in leaf is deleted, its bundled pack takes over the freed noun;
62
+ * operational nouns (config, graph, setup, …) stay reserved wholesale.
63
+ *
64
+ * @param ctx - The pragma context providing store, config, and global flags.
65
+ * @returns The full array of command definitions for all domains.
66
+ */
67
+ export default function collectCommands(
68
+ ctx: PragmaContext,
69
+ ): CommandDefinition[] {
70
+ const builtIn = builtInCommands(ctx);
71
+ const reserved = deriveReservedVerbs(
72
+ builtIn.map((command) => nounVerbFromPath(command.path)),
73
+ );
74
+ return [...builtIn, ...compilePackCommands(ctx, reserved)];
75
+ }
@@ -36,9 +36,13 @@ describe("createProgram", () => {
36
36
  }
37
37
  )._events.beforeAllHelp({ command: program, write });
38
38
 
39
+ // Assert on single-chalk-span strings so the check is robust whether or
40
+ // not color is enabled (CI renders with color; ANSI resets would otherwise
41
+ // split a multi-word phrase like "Usage: pragma").
39
42
  expect(write).toHaveBeenCalledWith(
40
- expect.stringContaining("Usage: pragma <command> [options]"),
43
+ expect.stringContaining("For AI agents"),
41
44
  );
45
+ expect(write).toHaveBeenCalledWith(expect.stringContaining("Global flags"));
42
46
  });
43
47
 
44
48
  it("uses llm root help when llm is enabled", async () => {
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  type CommandDefinition,
3
3
  detectRenderMode,
4
- formatHelp,
5
4
  formatLlmHelp,
6
5
  type HandleResultOptions,
7
6
  registerAll,
@@ -9,6 +8,7 @@ import {
9
8
  import { Command } from "commander";
10
9
  import { PROGRAM_DESCRIPTION, PROGRAM_NAME, VERSION } from "../constants.js";
11
10
  import type { PragmaContext } from "../domains/shared/context.js";
11
+ import formatRootHelp from "./rootHelp.js";
12
12
 
13
13
  /**
14
14
  * Build and configure the top-level Commander program.
@@ -57,7 +57,7 @@ export default function createProgram(
57
57
  if (ctx.globalFlags.llm) {
58
58
  return formatLlmHelp(PROGRAM_NAME, commands);
59
59
  }
60
- return formatHelp(PROGRAM_NAME, PROGRAM_DESCRIPTION, commands);
60
+ return formatRootHelp(PROGRAM_NAME, PROGRAM_DESCRIPTION, commands);
61
61
  });
62
62
 
63
63
  program.helpOption("-h, --help", "Show help");