@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (344) hide show
  1. package/README.md +67 -15
  2. package/dist/pragma +0 -0
  3. package/package.json +16 -17
  4. package/src/config/defaultConfig.test.ts +33 -0
  5. package/src/config/defaultConfig.ts +59 -0
  6. package/src/config/defaults.json +17 -0
  7. package/src/config/findProjectConfigPath.test.ts +53 -0
  8. package/src/config/findProjectConfigPath.ts +36 -0
  9. package/src/config/index.ts +17 -2
  10. package/src/config/mergeConfigFileUpdate.ts +58 -0
  11. package/src/config/parseConfigValues.ts +215 -0
  12. package/src/config/readConfig.test.ts +104 -13
  13. package/src/config/readConfig.ts +12 -117
  14. package/src/config/readConfigLayers.test.ts +107 -0
  15. package/src/config/readConfigLayers.ts +115 -0
  16. package/src/config/resolveGlobalConfigPath.ts +13 -0
  17. package/src/config/resolveWriteConfigPath.test.ts +53 -0
  18. package/src/config/resolveWriteConfigPath.ts +31 -0
  19. package/src/config/types.ts +88 -2
  20. package/src/config/writeConfig.ts +25 -43
  21. package/src/constants.ts +12 -1
  22. package/src/domains/block/commands/index.ts +2 -1
  23. package/src/domains/block/commands/list.ts +5 -90
  24. package/src/domains/block/commands/lookup.ts +8 -167
  25. package/src/domains/block/commands/sample.ts +55 -0
  26. package/src/domains/block/formatters/index.ts +2 -1
  27. package/src/domains/block/formatters/sample.ts +21 -0
  28. package/src/domains/block/index.ts +3 -3
  29. package/src/domains/block/mcp/specs.ts +36 -168
  30. package/src/domains/block/operations/index.ts +2 -1
  31. package/src/domains/block/operations/lookup.ts +9 -0
  32. package/src/domains/block/operations/sample.ts +41 -0
  33. package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
  34. package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
  35. package/src/domains/block/stories.test.ts +28 -0
  36. package/src/domains/block/stories.ts +254 -0
  37. package/src/domains/config/commands/channel.test.ts +14 -3
  38. package/src/domains/config/commands/channel.ts +11 -7
  39. package/src/domains/config/commands/configScope.ts +46 -0
  40. package/src/domains/config/commands/framework.test.ts +105 -0
  41. package/src/domains/config/commands/framework.ts +91 -0
  42. package/src/domains/config/commands/index.ts +2 -0
  43. package/src/domains/config/commands/show.ts +5 -45
  44. package/src/domains/config/commands/tier.test.ts +13 -2
  45. package/src/domains/config/commands/tier.ts +11 -7
  46. package/src/domains/config/commands/trace.test.ts +165 -0
  47. package/src/domains/config/commands/trace.ts +83 -0
  48. package/src/domains/config/formatters/channel.test.ts +20 -6
  49. package/src/domains/config/formatters/channel.ts +14 -11
  50. package/src/domains/config/formatters/framework.ts +37 -0
  51. package/src/domains/config/formatters/index.ts +2 -0
  52. package/src/domains/config/formatters/show.test.ts +27 -0
  53. package/src/domains/config/formatters/show.ts +27 -4
  54. package/src/domains/config/formatters/tier.test.ts +26 -10
  55. package/src/domains/config/formatters/tier.ts +19 -11
  56. package/src/domains/config/formatters/trace.ts +28 -0
  57. package/src/domains/config/index.ts +15 -2
  58. package/src/domains/config/mcp/specs.ts +33 -53
  59. package/src/domains/config/operations/index.ts +1 -0
  60. package/src/domains/config/operations/show.test.ts +18 -0
  61. package/src/domains/config/operations/show.ts +7 -1
  62. package/src/domains/config/operations/types.ts +7 -0
  63. package/src/domains/config/operations/validateFramework.ts +24 -0
  64. package/src/domains/config/stories.ts +42 -0
  65. package/src/domains/config/tasks/index.ts +3 -0
  66. package/src/domains/config/tasks/setChannel.ts +13 -22
  67. package/src/domains/config/tasks/setFramework.ts +21 -0
  68. package/src/domains/config/tasks/setTier.ts +10 -23
  69. package/src/domains/config/tasks/setTrace.ts +20 -0
  70. package/src/domains/config/tasks/writeConfigField.ts +41 -0
  71. package/src/domains/create/commands/component.ts +5 -5
  72. package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
  73. package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
  74. package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
  75. package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
  76. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
  77. package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
  78. package/src/domains/doctor/operations/checks/index.ts +1 -0
  79. package/src/domains/doctor/operations/checks.test.ts +162 -8
  80. package/src/domains/doctor/operations/runChecks.test.ts +85 -4
  81. package/src/domains/doctor/operations/runChecks.ts +50 -12
  82. package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
  83. package/src/domains/graph/helpers/resolveUri.ts +28 -21
  84. package/src/domains/graphql/commands/build.test.ts +196 -0
  85. package/src/domains/graphql/commands/build.ts +117 -0
  86. package/src/domains/graphql/commands/check.test.ts +141 -0
  87. package/src/domains/graphql/commands/check.ts +81 -0
  88. package/src/domains/graphql/commands/index.ts +5 -0
  89. package/src/domains/graphql/commands/serve.test.ts +40 -0
  90. package/src/domains/graphql/commands/serve.ts +131 -0
  91. package/src/domains/graphql/constants.ts +10 -0
  92. package/src/domains/graphql/formatters/index.ts +3 -0
  93. package/src/domains/graphql/formatters/report.ts +78 -0
  94. package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
  95. package/src/domains/graphql/helpers/gatherSources.ts +58 -0
  96. package/src/domains/graphql/helpers/index.ts +6 -0
  97. package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
  98. package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
  99. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
  100. package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
  101. package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
  102. package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
  103. package/src/domains/graphql/index.ts +24 -0
  104. package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
  105. package/src/domains/graphql/operations/compileSchema.ts +107 -0
  106. package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
  107. package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
  108. package/src/domains/graphql/operations/index.ts +12 -0
  109. package/src/domains/graphql/types.ts +29 -0
  110. package/src/domains/info/operations/collectInfo.test.ts +16 -5
  111. package/src/domains/info/operations/collectInfo.ts +16 -19
  112. package/src/domains/llm/capabilities.test.ts +91 -0
  113. package/src/domains/llm/commands/capabilities.ts +28 -0
  114. package/src/domains/llm/commands/index.ts +1 -0
  115. package/src/domains/llm/data/commandReference.ts +90 -18
  116. package/src/domains/llm/data/decisionTrees.ts +15 -13
  117. package/src/domains/llm/data/index.ts +2 -0
  118. package/src/domains/llm/data/toolCatalog.ts +190 -0
  119. package/src/domains/llm/formatters/capabilities.ts +50 -0
  120. package/src/domains/llm/formatters/index.ts +1 -0
  121. package/src/domains/llm/index.ts +3 -2
  122. package/src/domains/llm/mcp/index.ts +1 -1
  123. package/src/domains/llm/mcp/specs.ts +59 -52
  124. package/src/domains/llm/types.ts +40 -0
  125. package/src/domains/modifier/commands/index.ts +2 -1
  126. package/src/domains/modifier/commands/list.ts +5 -38
  127. package/src/domains/modifier/commands/lookup.ts +8 -79
  128. package/src/domains/modifier/commands/sample.ts +53 -0
  129. package/src/domains/modifier/formatters/index.ts +2 -1
  130. package/src/domains/modifier/formatters/sample.ts +15 -0
  131. package/src/domains/modifier/index.ts +7 -3
  132. package/src/domains/modifier/mcp/specs.ts +38 -70
  133. package/src/domains/modifier/operations/index.ts +2 -1
  134. package/src/domains/modifier/operations/lookup.ts +9 -0
  135. package/src/domains/modifier/operations/sample.ts +32 -0
  136. package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
  137. package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
  138. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
  139. package/src/domains/modifier/stories.test.ts +20 -0
  140. package/src/domains/modifier/stories.ts +77 -0
  141. package/src/domains/ontology/commands/list.ts +5 -58
  142. package/src/domains/ontology/commands/show.ts +6 -58
  143. package/src/domains/ontology/formatters/show.ts +7 -1
  144. package/src/domains/ontology/mcp/specs.ts +7 -84
  145. package/src/domains/ontology/stories.test.ts +36 -0
  146. package/src/domains/ontology/stories.ts +108 -0
  147. package/src/domains/refs/operations/index.ts +1 -1
  148. package/src/domains/refs/operations/paths.ts +29 -1
  149. package/src/domains/refs/operations/updateRefs.test.ts +6 -3
  150. package/src/domains/refs/operations/updateRefs.ts +2 -36
  151. package/src/domains/setup/commands/skills.test.ts +6 -2
  152. package/src/domains/setup/commands/skills.ts +6 -2
  153. package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
  154. package/src/domains/setup/helpers/runSetupTask.ts +62 -70
  155. package/src/domains/setup/operations/setupMcp.test.ts +6 -1
  156. package/src/domains/shared/bootStore.ts +92 -50
  157. package/src/domains/shared/compactUri.test.ts +11 -0
  158. package/src/domains/shared/compactUri.ts +10 -5
  159. package/src/domains/shared/context.ts +2 -2
  160. package/src/domains/shared/contracts.ts +12 -0
  161. package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
  162. package/src/domains/shared/createInteractivePromptSession.ts +236 -0
  163. package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
  164. package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
  165. package/src/domains/shared/loaders/bundledLoader.ts +206 -0
  166. package/src/domains/shared/loaders/gitLoader.ts +31 -0
  167. package/src/domains/shared/loaders/index.ts +9 -0
  168. package/src/domains/shared/loaders/localLoader.ts +87 -0
  169. package/src/domains/shared/loaders/readPackageDir.ts +147 -0
  170. package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
  171. package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
  172. package/src/domains/shared/lookupMany.test.ts +120 -0
  173. package/src/domains/shared/lookupMany.ts +43 -3
  174. package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
  175. package/src/domains/shared/mergeAndParseRefs.ts +44 -0
  176. package/src/domains/shared/packages.ts +14 -120
  177. package/src/domains/shared/pickRandom.ts +22 -0
  178. package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
  179. package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
  180. package/src/domains/shared/runtime.ts +7 -63
  181. package/src/domains/shared/sampleFormatters.ts +47 -0
  182. package/src/domains/shared/semanticPackage.ts +112 -0
  183. package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
  184. package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
  185. package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
  186. package/src/domains/shared/stories/compileLookupTool.ts +96 -0
  187. package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
  188. package/src/domains/shared/stories/compileReadCommand.ts +48 -0
  189. package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
  190. package/src/domains/shared/stories/compileReadTool.ts +38 -0
  191. package/src/domains/shared/stories/condense.test.ts +18 -0
  192. package/src/domains/shared/stories/condense.ts +15 -0
  193. package/src/domains/shared/stories/index.ts +20 -0
  194. package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
  195. package/src/domains/shared/stories/normalizeNames.ts +22 -0
  196. package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
  197. package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
  198. package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
  199. package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
  200. package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
  201. package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
  202. package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
  203. package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
  204. package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
  205. package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
  206. package/src/domains/shared/stories/pack/index.ts +28 -0
  207. package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
  208. package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
  209. package/src/domains/shared/stories/pack/types.ts +104 -0
  210. package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
  211. package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
  212. package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
  213. package/src/domains/shared/stories/projectCliParameters.ts +57 -0
  214. package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
  215. package/src/domains/shared/stories/projectToolParams.ts +32 -0
  216. package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
  217. package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
  218. package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
  219. package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
  220. package/src/domains/shared/stories/types.ts +171 -0
  221. package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
  222. package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
  223. package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
  224. package/src/domains/shared/suggestions/expandGlob.ts +52 -0
  225. package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
  226. package/src/domains/shared/suggestions/index.ts +7 -0
  227. package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
  228. package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
  229. package/src/domains/shared/suggestions/suggestNames.ts +130 -0
  230. package/src/domains/shared/types/index.ts +1 -0
  231. package/src/domains/shared/types/runtime.ts +4 -1
  232. package/src/domains/shared/types/sample.ts +17 -0
  233. package/src/domains/skill/commands/index.ts +3 -1
  234. package/src/domains/skill/commands/list.test.ts +6 -2
  235. package/src/domains/skill/commands/list.ts +5 -63
  236. package/src/domains/skill/commands/lookup.test.ts +118 -0
  237. package/src/domains/skill/commands/lookup.ts +14 -0
  238. package/src/domains/skill/formatters/index.ts +1 -0
  239. package/src/domains/skill/formatters/lookup.test.ts +46 -0
  240. package/src/domains/skill/formatters/lookup.ts +57 -0
  241. package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
  242. package/src/domains/skill/index.ts +7 -3
  243. package/src/domains/skill/mcp/specs.ts +10 -29
  244. package/src/domains/skill/operations/discover.test.ts +61 -30
  245. package/src/domains/skill/operations/discover.ts +28 -37
  246. package/src/domains/skill/operations/index.ts +2 -1
  247. package/src/domains/skill/operations/list.test.ts +15 -7
  248. package/src/domains/skill/operations/lookup.test.ts +91 -0
  249. package/src/domains/skill/operations/lookup.ts +70 -0
  250. package/src/domains/skill/stories.test.ts +43 -0
  251. package/src/domains/skill/stories.ts +103 -0
  252. package/src/domains/skill/types.ts +8 -0
  253. package/src/domains/standard/commands/index.ts +2 -1
  254. package/src/domains/standard/commands/list.ts +5 -84
  255. package/src/domains/standard/commands/lookup.ts +8 -93
  256. package/src/domains/standard/commands/sample.ts +57 -0
  257. package/src/domains/standard/formatters/index.ts +2 -1
  258. package/src/domains/standard/formatters/sample.ts +15 -0
  259. package/src/domains/standard/index.ts +8 -1
  260. package/src/domains/standard/mcp/specs.ts +44 -152
  261. package/src/domains/standard/operations/index.ts +2 -1
  262. package/src/domains/standard/operations/lookup.ts +9 -0
  263. package/src/domains/standard/operations/sample.ts +35 -0
  264. package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
  265. package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
  266. package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
  267. package/src/domains/standard/stories.test.ts +24 -0
  268. package/src/domains/standard/stories.ts +151 -0
  269. package/src/domains/tier/commands/list.ts +5 -55
  270. package/src/domains/tier/mcp/specs.ts +5 -31
  271. package/src/domains/tier/stories.test.ts +22 -0
  272. package/src/domains/tier/stories.ts +52 -0
  273. package/src/domains/token/commands/index.ts +2 -1
  274. package/src/domains/token/commands/sample.ts +53 -0
  275. package/src/domains/token/formatters/index.ts +2 -1
  276. package/src/domains/token/formatters/sample.ts +16 -0
  277. package/src/domains/token/index.ts +8 -1
  278. package/src/domains/token/mcp/specs.ts +52 -2
  279. package/src/domains/token/operations/index.ts +2 -1
  280. package/src/domains/token/operations/lookup.ts +9 -0
  281. package/src/domains/token/operations/sample.ts +32 -0
  282. package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
  283. package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
  284. package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
  285. package/src/domains/trace/commands/clear.ts +71 -0
  286. package/src/domains/trace/commands/index.ts +5 -0
  287. package/src/domains/trace/commands/sessions.ts +32 -0
  288. package/src/domains/trace/commands/show.ts +145 -0
  289. package/src/domains/trace/formatters/index.ts +9 -0
  290. package/src/domains/trace/formatters/sessions.ts +80 -0
  291. package/src/domains/trace/formatters/show.ts +122 -0
  292. package/src/domains/trace/index.ts +17 -0
  293. package/src/domains/trace/operations/index.ts +8 -0
  294. package/src/domains/trace/operations/listSessions.test.ts +55 -0
  295. package/src/domains/trace/operations/listSessions.ts +52 -0
  296. package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
  297. package/src/domains/trace/operations/readTraceLog.ts +104 -0
  298. package/src/domains/trace/tracePlugin.test.ts +171 -0
  299. package/src/domains/trace/tracePlugin.ts +159 -0
  300. package/src/domains/trace/types.ts +65 -0
  301. package/src/error/PragmaError.ts +15 -2
  302. package/src/error/types.ts +15 -1
  303. package/src/mcp/error/serializeErrorPayload.ts +7 -0
  304. package/src/mcp/error/types.ts +8 -0
  305. package/src/mcp/registerResources.test.ts +405 -75
  306. package/src/mcp/registerResources.ts +5 -299
  307. package/src/mcp/resources/buildGraphIndex.ts +167 -0
  308. package/src/mcp/resources/buildResourceList.test.ts +97 -0
  309. package/src/mcp/resources/buildResourceList.ts +176 -0
  310. package/src/mcp/resources/classifyEntity.test.ts +40 -0
  311. package/src/mcp/resources/classifyEntity.ts +27 -0
  312. package/src/mcp/resources/constants.ts +105 -0
  313. package/src/mcp/resources/displayPredicates.ts +62 -0
  314. package/src/mcp/resources/pickFirstValue.test.ts +29 -0
  315. package/src/mcp/resources/pickFirstValue.ts +26 -0
  316. package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
  317. package/src/mcp/resources/pickPrimaryType.ts +26 -0
  318. package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
  319. package/src/mcp/resources/rankUriCompletions.ts +64 -0
  320. package/src/mcp/resources/readEntity.ts +169 -0
  321. package/src/mcp/resources/registerResources.ts +126 -0
  322. package/src/mcp/resources/types.ts +128 -0
  323. package/src/mcp/tools/index.ts +9 -0
  324. package/src/mcp/tools/registerTools.test.ts +84 -12
  325. package/src/mcp/types.ts +4 -0
  326. package/src/mcp/utils/wrapTool.test.ts +1 -0
  327. package/src/pipeline/collectCommands.ts +15 -2
  328. package/src/pipeline/renderError.ts +15 -0
  329. package/src/pipeline/resolveCommandKind.ts +8 -1
  330. package/src/pipeline/runCli.ts +51 -6
  331. package/src/testing/createTestMcpClient.ts +5 -1
  332. package/src/testing/graphqlFixtures.ts +72 -0
  333. package/src/testing/index.ts +17 -0
  334. package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
  335. package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
  336. package/src/testing/integration/executor-execution.test.ts +101 -0
  337. package/src/testing/integration/executor-golden.test.ts +145 -0
  338. package/src/testing/integration/mcp-surface.test.ts +11 -5
  339. package/src/testing/integration/parity.test.ts +17 -12
  340. package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
  341. package/src/testing/setupXdgIsolation.ts +15 -0
  342. package/src/testing/standardParityFixtures.ts +92 -0
  343. package/src/testing/storyFixtures.ts +63 -0
  344. package/src/pipeline/runInteractiveCommand.ts +0 -198
@@ -0,0 +1,115 @@
1
+ import { readFileSync } from "node:fs";
2
+ import { DEFAULT_CONFIG } from "./defaultConfig.js";
3
+ import findProjectConfigPath from "./findProjectConfigPath.js";
4
+ import parseConfigValues from "./parseConfigValues.js";
5
+ import resolveConfigPath from "./resolveConfigPath.js";
6
+ import resolveGlobalConfigPath from "./resolveGlobalConfigPath.js";
7
+ import type {
8
+ ConfigFileValues,
9
+ ConfigLayers,
10
+ ConfigOrigin,
11
+ PragmaConfig,
12
+ } from "./types.js";
13
+
14
+ /**
15
+ * Fields the built-in defaults document contributes to the layer merge.
16
+ *
17
+ * `packages` is deliberately absent: it reaches the ref merge through
18
+ * `DEFAULT_PACKAGES` instead, so global `refs.json` entries keep merging
19
+ * over the default packages by name. Surfacing it here would make the
20
+ * defaults look user-declared and replace the merged set wholesale.
21
+ */
22
+ const DEFAULT_LAYER: ConfigFileValues = {
23
+ channel: DEFAULT_CONFIG.channel,
24
+ };
25
+
26
+ /**
27
+ * Resolve the layered configuration with per-field provenance.
28
+ *
29
+ * Layer order, least to most specific: built-in defaults, the global XDG
30
+ * file (`~/.config/pragma/config.json`), and the nearest project
31
+ * `pragma.config.json` up the tree. Merging is per field — a field set by
32
+ * a more specific layer wins wholesale (`packages` replaces as a whole,
33
+ * preserving the established project-pins-exactly semantics).
34
+ *
35
+ * @param cwd - Directory the project layer is resolved from.
36
+ * @returns The effective config, per-field origins, and both layers.
37
+ * @throws PragmaError with code `CONFIG_ERROR` when either file is invalid.
38
+ * @note Impure — reads config files from the filesystem.
39
+ */
40
+ export default function readConfigLayers(
41
+ cwd: string = process.cwd(),
42
+ ): ConfigLayers {
43
+ const globalPath = resolveGlobalConfigPath();
44
+ const projectPath = findProjectConfigPath(cwd) ?? resolveConfigPath(cwd);
45
+
46
+ const globalFile = readConfigFile(globalPath);
47
+ const projectFile = readConfigFile(projectPath);
48
+
49
+ const pick = <K extends keyof ConfigFileValues>(
50
+ field: K,
51
+ ): { value: ConfigFileValues[K] | undefined; origin: ConfigOrigin } => {
52
+ if (field in projectFile.values) {
53
+ return { value: projectFile.values[field], origin: "project" };
54
+ }
55
+ if (field in globalFile.values) {
56
+ return { value: globalFile.values[field], origin: "global" };
57
+ }
58
+ if (field in DEFAULT_LAYER) {
59
+ return { value: DEFAULT_LAYER[field], origin: "default" };
60
+ }
61
+ return { value: undefined, origin: "default" };
62
+ };
63
+
64
+ const tier = pick("tier");
65
+ const channel = pick("channel");
66
+ const packages = pick("packages");
67
+ const trace = pick("trace");
68
+ const framework = pick("framework");
69
+ const stories = pick("stories");
70
+ const prefixes = pick("prefixes");
71
+
72
+ const config: PragmaConfig = {
73
+ tier: tier.value,
74
+ channel: channel.value ?? DEFAULT_CONFIG.channel,
75
+ ...(packages.value ? { packages: packages.value } : {}),
76
+ ...(trace.value !== undefined ? { trace: trace.value } : {}),
77
+ ...(framework.value !== undefined ? { framework: framework.value } : {}),
78
+ ...(stories.value ? { stories: stories.value } : {}),
79
+ ...(prefixes.value ? { prefixes: prefixes.value } : {}),
80
+ };
81
+
82
+ return {
83
+ config,
84
+ origins: {
85
+ tier: tier.origin,
86
+ channel: channel.origin,
87
+ packages: packages.origin,
88
+ trace: trace.origin,
89
+ framework: framework.origin,
90
+ stories: stories.origin,
91
+ prefixes: prefixes.origin,
92
+ },
93
+ global: { path: globalPath, exists: globalFile.exists },
94
+ project: { path: projectPath, exists: projectFile.exists },
95
+ };
96
+ }
97
+
98
+ /**
99
+ * Read and parse one config file layer.
100
+ *
101
+ * A missing or unreadable file contributes no values — the layer simply
102
+ * does not participate in the merge.
103
+ */
104
+ function readConfigFile(path: string): {
105
+ values: ConfigFileValues;
106
+ exists: boolean;
107
+ } {
108
+ let raw: string;
109
+ try {
110
+ raw = readFileSync(path, "utf-8");
111
+ } catch {
112
+ return { values: {}, exists: false };
113
+ }
114
+ return { values: parseConfigValues(raw, path), exists: true };
115
+ }
@@ -0,0 +1,13 @@
1
+ import { join } from "node:path";
2
+ import { globalConfigDir } from "../domains/refs/operations/paths.js";
3
+
4
+ /**
5
+ * Resolve the absolute path of the global config file.
6
+ *
7
+ * Lives next to `refs.json` in the pragma XDG config directory:
8
+ * `$XDG_CONFIG_HOME/pragma/config.json`, defaulting to
9
+ * `~/.config/pragma/config.json`.
10
+ */
11
+ export default function resolveGlobalConfigPath(): string {
12
+ return join(globalConfigDir(), "config.json");
13
+ }
@@ -0,0 +1,53 @@
1
+ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, beforeEach, describe, expect, it } from "vitest";
5
+ import resolveWriteConfigPath from "./resolveWriteConfigPath.js";
6
+
7
+ describe("resolveWriteConfigPath", () => {
8
+ let xdgDir: string;
9
+ let projectDir: string;
10
+ let originalXdg: string | undefined;
11
+
12
+ beforeEach(() => {
13
+ originalXdg = process.env.XDG_CONFIG_HOME;
14
+ xdgDir = mkdtempSync(join(tmpdir(), "pragma-write-xdg-"));
15
+ process.env.XDG_CONFIG_HOME = xdgDir;
16
+ projectDir = mkdtempSync(join(tmpdir(), "pragma-write-project-"));
17
+ mkdirSync(join(projectDir, ".git"));
18
+ });
19
+
20
+ afterEach(() => {
21
+ process.env.XDG_CONFIG_HOME = originalXdg;
22
+ rmSync(xdgDir, { recursive: true, force: true });
23
+ rmSync(projectDir, { recursive: true, force: true });
24
+ });
25
+
26
+ it("targets the nearest existing project file", () => {
27
+ writeFileSync(join(projectDir, "pragma.config.json"), "{}");
28
+ const sub = join(projectDir, "packages/app");
29
+ mkdirSync(sub, { recursive: true });
30
+ expect(resolveWriteConfigPath(sub)).toBe(
31
+ join(projectDir, "pragma.config.json"),
32
+ );
33
+ });
34
+
35
+ it("falls back to the global file when no project file exists", () => {
36
+ expect(resolveWriteConfigPath(projectDir)).toBe(
37
+ join(xdgDir, "pragma/config.json"),
38
+ );
39
+ });
40
+
41
+ it("honors an explicit local scope even without an existing file", () => {
42
+ expect(resolveWriteConfigPath(projectDir, "local")).toBe(
43
+ join(projectDir, "pragma.config.json"),
44
+ );
45
+ });
46
+
47
+ it("honors an explicit global scope over an existing project file", () => {
48
+ writeFileSync(join(projectDir, "pragma.config.json"), "{}");
49
+ expect(resolveWriteConfigPath(projectDir, "global")).toBe(
50
+ join(xdgDir, "pragma/config.json"),
51
+ );
52
+ });
53
+ });
@@ -0,0 +1,31 @@
1
+ import findProjectConfigPath from "./findProjectConfigPath.js";
2
+ import resolveConfigPath from "./resolveConfigPath.js";
3
+ import resolveGlobalConfigPath from "./resolveGlobalConfigPath.js";
4
+ import type { ConfigScope } from "./types.js";
5
+
6
+ /**
7
+ * Resolve which config file a write should target.
8
+ *
9
+ * An explicit scope wins: `"local"` targets `<cwd>/pragma.config.json`,
10
+ * `"global"` targets the XDG file. Otherwise the nearest existing project
11
+ * file up the tree is updated in place, and when none exists the write
12
+ * goes to the global file — pragma is global-first: an unconfigured
13
+ * directory should not sprout a project file as a side effect.
14
+ *
15
+ * @param cwd - Directory the project layer is resolved from.
16
+ * @param scope - Optional explicit target layer.
17
+ * @returns Absolute path of the config file to write.
18
+ * @note Impure — probes the filesystem for the nearest project file.
19
+ */
20
+ export default function resolveWriteConfigPath(
21
+ cwd: string,
22
+ scope?: ConfigScope,
23
+ ): string {
24
+ if (scope === "local") {
25
+ return resolveConfigPath(cwd);
26
+ }
27
+ if (scope === "global") {
28
+ return resolveGlobalConfigPath();
29
+ }
30
+ return findProjectConfigPath(cwd) ?? resolveGlobalConfigPath();
31
+ }
@@ -5,8 +5,9 @@
5
5
  * so consumers can import types without pulling in filesystem code.
6
6
  */
7
7
 
8
- import type { Channel } from "../constants.js";
8
+ import type { Channel, Framework } from "../constants.js";
9
9
  import type { RawPackageEntry } from "../domains/refs/operations/parseRef.js";
10
+ import type { StoryPackDefinition } from "../domains/shared/stories/pack/types.js";
10
11
 
11
12
  /** Parsed contents of pragma.config.json. */
12
13
  interface PragmaConfig {
@@ -20,6 +21,24 @@ interface PragmaConfig {
20
21
  * When absent, the hardcoded defaults are used.
21
22
  */
22
23
  packages?: ReadonlyArray<RawPackageEntry> | undefined;
24
+ /** Whether query access tracing is enabled. Overridden by PRAGMA_TRACE env var. */
25
+ trace?: boolean | undefined;
26
+ /**
27
+ * Preferred UI framework (`react` | `svelte` | `lit`). Currently advisory
28
+ * only — no behaviour reads it yet; reserved for future framework defaulting.
29
+ */
30
+ framework?: Framework | undefined;
31
+ /**
32
+ * Declarative read stories compiled into CLI commands and MCP tools at
33
+ * boot.
34
+ * @experimental Story packs (v0) are experimental — the format may change.
35
+ */
36
+ stories?: ReadonlyArray<StoryPackDefinition> | undefined;
37
+ /**
38
+ * Additional namespace prefixes merged over the built-in prefix map —
39
+ * registered on the store (usable in queries) and used for display.
40
+ */
41
+ prefixes?: Readonly<Record<string, string>> | undefined;
23
42
  }
24
43
 
25
44
  /** Partial update payload for writing config changes. */
@@ -30,6 +49,73 @@ interface ConfigUpdate {
30
49
  channel?: Channel | undefined;
31
50
  /** New packages list, `undefined` to remove the field. */
32
51
  packages?: ReadonlyArray<RawPackageEntry> | undefined;
52
+ /** Enable/disable query tracing, `undefined` to remove the field. */
53
+ trace?: boolean | undefined;
54
+ /** New preferred framework, `undefined` to remove the field. */
55
+ framework?: Framework | undefined;
56
+ }
57
+
58
+ /**
59
+ * Values a single config file declares. A key is present only when the
60
+ * file sets it — presence drives which layer wins during merging.
61
+ */
62
+ interface ConfigFileValues {
63
+ readonly tier?: string;
64
+ readonly channel?: Channel;
65
+ readonly packages?: ReadonlyArray<RawPackageEntry>;
66
+ readonly trace?: boolean;
67
+ readonly framework?: Framework;
68
+ readonly stories?: ReadonlyArray<StoryPackDefinition>;
69
+ readonly prefixes?: Readonly<Record<string, string>>;
33
70
  }
34
71
 
35
- export type { ConfigUpdate, PragmaConfig };
72
+ /** Which config layer supplied an effective field value. */
73
+ type ConfigOrigin = "default" | "global" | "project";
74
+
75
+ /** Per-field provenance for the effective merged config. */
76
+ interface ConfigOrigins {
77
+ readonly tier: ConfigOrigin;
78
+ readonly channel: ConfigOrigin;
79
+ readonly packages: ConfigOrigin;
80
+ readonly trace: ConfigOrigin;
81
+ readonly framework: ConfigOrigin;
82
+ readonly stories: ConfigOrigin;
83
+ readonly prefixes: ConfigOrigin;
84
+ }
85
+
86
+ /** A resolved config file layer. */
87
+ interface ConfigLayer {
88
+ /** Absolute path of the layer's config file (existing or would-be). */
89
+ readonly path: string;
90
+ /** Whether the file exists and was readable. */
91
+ readonly exists: boolean;
92
+ }
93
+
94
+ /** Layered config resolution result with per-field provenance. */
95
+ interface ConfigLayers {
96
+ /** The effective merged configuration (defaults < global < project). */
97
+ readonly config: PragmaConfig;
98
+ /** Which layer supplied each effective field. */
99
+ readonly origins: ConfigOrigins;
100
+ /** The global XDG layer (`$XDG_CONFIG_HOME/pragma/config.json`). */
101
+ readonly global: ConfigLayer;
102
+ /**
103
+ * The project layer: the nearest `pragma.config.json` up the tree, or
104
+ * the would-be path at `cwd` when none exists.
105
+ */
106
+ readonly project: ConfigLayer;
107
+ }
108
+
109
+ /** Target layer selector for config writes. */
110
+ type ConfigScope = "global" | "local";
111
+
112
+ export type {
113
+ ConfigFileValues,
114
+ ConfigLayer,
115
+ ConfigLayers,
116
+ ConfigOrigin,
117
+ ConfigOrigins,
118
+ ConfigScope,
119
+ ConfigUpdate,
120
+ PragmaConfig,
121
+ };
@@ -1,55 +1,37 @@
1
- import { readFileSync, renameSync, writeFileSync } from "node:fs";
2
- import resolveConfigPath from "./resolveConfigPath.js";
3
- import type { ConfigUpdate } from "./types.js";
1
+ import { mkdirSync, renameSync, writeFileSync } from "node:fs";
2
+ import { dirname } from "node:path";
3
+ import mergeConfigFileUpdate from "./mergeConfigFileUpdate.js";
4
+ import resolveWriteConfigPath from "./resolveWriteConfigPath.js";
5
+ import type { ConfigScope, ConfigUpdate } from "./types.js";
4
6
 
5
7
  /**
6
- * Write config updates to pragma.config.json.
8
+ * Write config updates to the resolved target layer.
7
9
  *
8
- * Merges updates into existing JSON. A field set to `undefined` removes it.
9
- * Creates the file if it doesn't exist.
10
+ * The target follows the global-first write rule (see
11
+ * `resolveWriteConfigPath`): an explicit scope wins, else the nearest
12
+ * existing project file, else the global XDG file. Merges updates into
13
+ * that file's own contents — a field set to `undefined` removes it —
14
+ * creating the file (and its directory) if needed, atomically via a
15
+ * temp file rename.
10
16
  *
11
- * @param cwd - Directory containing (or to contain) pragma.config.json.
17
+ * @param cwd - Directory the project layer is resolved from.
12
18
  * @param update - Fields to set or remove.
19
+ * @param scope - Optional explicit target layer (`"global"` | `"local"`).
20
+ * @returns Absolute path of the file that was written.
13
21
  *
14
22
  * @note Impure — writes to the filesystem.
15
23
  */
16
- export default function writeConfig(cwd: string, update: ConfigUpdate): void {
17
- const configPath = resolveConfigPath(cwd);
24
+ export default function writeConfig(
25
+ cwd: string,
26
+ update: ConfigUpdate,
27
+ scope?: ConfigScope,
28
+ ): string {
29
+ const configPath = resolveWriteConfigPath(cwd, scope);
18
30
  const tempPath = `${configPath}.tmp`;
31
+ const content = mergeConfigFileUpdate(configPath, update);
19
32
 
20
- let existing: Record<string, unknown> = {};
21
- try {
22
- const raw = readFileSync(configPath, "utf-8");
23
- existing = JSON.parse(raw) as Record<string, unknown>;
24
- } catch (err: unknown) {
25
- // Only swallow "file not found" — surface parse or permission errors.
26
- const isNotFound =
27
- err instanceof Error &&
28
- "code" in err &&
29
- (err as NodeJS.ErrnoException).code === "ENOENT";
30
- if (!isNotFound) {
31
- throw err;
32
- }
33
- }
34
-
35
- if (update.tier !== undefined) {
36
- existing.tier = update.tier;
37
- } else if ("tier" in update) {
38
- delete existing.tier;
39
- }
40
-
41
- if (update.channel !== undefined) {
42
- existing.channel = update.channel;
43
- } else if ("channel" in update) {
44
- delete existing.channel;
45
- }
46
-
47
- if (update.packages !== undefined) {
48
- existing.packages = update.packages;
49
- } else if ("packages" in update) {
50
- delete existing.packages;
51
- }
52
-
53
- writeFileSync(tempPath, `${JSON.stringify(existing, null, 2)}\n`);
33
+ mkdirSync(dirname(configPath), { recursive: true });
34
+ writeFileSync(tempPath, content);
54
35
  renameSync(tempPath, configPath);
36
+ return configPath;
55
37
  }
package/src/constants.ts CHANGED
@@ -24,6 +24,16 @@ const VALID_CHANNELS = ["normal", "experimental", "prerelease"] as const;
24
24
  /** A release channel name. */
25
25
  type Channel = (typeof VALID_CHANNELS)[number];
26
26
 
27
+ /** Allowed values for the `framework` config field. */
28
+ const VALID_FRAMEWORKS = ["react", "svelte", "lit"] as const;
29
+
30
+ /**
31
+ * A preferred UI framework. Currently advisory only (a no-op); reserved for
32
+ * future use to default framework-specific behaviour (e.g. which generator
33
+ * `pragma`/`summon` scaffolds, which component tier is surfaced).
34
+ */
35
+ type Framework = (typeof VALID_FRAMEWORKS)[number];
36
+
27
37
  /**
28
38
  * Per-namespace property URIs for semantically equivalent fields.
29
39
  * Each ontology uses different property names for the same concept
@@ -68,11 +78,12 @@ const PROPERTY_MAP: Record<string, OntologyPropertyMap> = {
68
78
  },
69
79
  };
70
80
 
71
- export type { Channel, OntologyPropertyMap };
81
+ export type { Channel, Framework, OntologyPropertyMap };
72
82
  export {
73
83
  PROGRAM_DESCRIPTION,
74
84
  PROGRAM_NAME,
75
85
  PROPERTY_MAP,
76
86
  VALID_CHANNELS,
87
+ VALID_FRAMEWORKS,
77
88
  VERSION,
78
89
  };
@@ -1,4 +1,5 @@
1
- /** @module Re-exports block command builders (`lookup`, `list`). */
1
+ /** @module Re-exports block command builders (`list`, `lookup`, `sample`). */
2
2
 
3
3
  export { default as listCommand } from "./list.js";
4
4
  export { default as lookupCommand } from "./lookup.js";
5
+ export { default as sampleCommand } from "./sample.js";
@@ -1,96 +1,11 @@
1
- /**
2
- * Wires the `pragma block list` CLI command.
3
- *
4
- * Lists all blocks visible under the current tier and channel filters.
5
- * Supports progressive disclosure via `--digest` and `--detailed` flags,
6
- * and an `--all-tiers` flag to widen the search.
7
- */
8
-
9
- import {
10
- type CommandDefinition,
11
- type CommandResult,
12
- createOutputResult,
13
- } from "@canonical/cli-core";
14
- import { createListView } from "#tui";
1
+ import type { CommandDefinition } from "@canonical/cli-core";
15
2
  import type { PragmaContext } from "../../shared/context.js";
16
- import type { ColumnDef } from "../../shared/contracts.js";
17
- import { selectFormatter } from "../../shared/formatters.js";
18
- import { blockConfig } from "../blockConfig.js";
19
- import { listFormatters } from "../formatters/index.js";
20
- import { resolveBlockList } from "../orchestration/index.js";
3
+ import { compileReadCommand } from "../../shared/stories/index.js";
4
+ import { blockListStory } from "../stories.js";
21
5
 
6
+ /** Wire the `pragma block list` CLI command from the block list story. */
22
7
  export default function buildListCommand(
23
8
  ctx: PragmaContext,
24
9
  ): CommandDefinition {
25
- return {
26
- path: ["block", "list"],
27
- description: "List blocks in the design system",
28
- parameters: [
29
- {
30
- name: "allTiers",
31
- description: "Show blocks from all tiers",
32
- type: "boolean",
33
- default: false,
34
- },
35
- {
36
- name: "digest",
37
- description: "Show enriched summary with implementations",
38
- type: "boolean",
39
- default: false,
40
- },
41
- {
42
- name: "detailed",
43
- description: "Show full details for each block",
44
- type: "boolean",
45
- default: false,
46
- },
47
- ],
48
- meta: {
49
- examples: [
50
- "pragma block list",
51
- "pragma block list --all-tiers",
52
- "pragma block list --digest",
53
- "pragma block list --detailed",
54
- "pragma block list --llm",
55
- "pragma block list --format json",
56
- ],
57
- },
58
- execute: async (
59
- params: Record<string, unknown>,
60
- ): Promise<CommandResult> => {
61
- const contract = await resolveBlockList(ctx, {
62
- allTiers: params.allTiers === true,
63
- digest: params.digest === true,
64
- detailed: params.detailed === true,
65
- });
66
-
67
- return createOutputResult(contract.result.items, {
68
- plain: selectFormatter(ctx, listFormatters),
69
- ink: (data) =>
70
- createListView({
71
- heading: "Blocks",
72
- domain: "block",
73
- items: data,
74
- columns: reorderColumns(blockConfig.listColumns, [
75
- "name",
76
- "type",
77
- "tier",
78
- "modifiers",
79
- "uri",
80
- ]),
81
- }),
82
- });
83
- },
84
- };
85
- }
86
-
87
- function reorderColumns<T>(
88
- columns: readonly ColumnDef<T>[],
89
- order: readonly string[],
90
- ): readonly ColumnDef<T>[] {
91
- const byKey = new Map(columns.map((col) => [col.key, col]));
92
- return order.flatMap((key) => {
93
- const col = byKey.get(key as keyof T & string);
94
- return col ? [col] : [];
95
- });
10
+ return compileReadCommand(ctx, blockListStory);
96
11
  }