@canonical/pragma-cli 0.32.0 → 0.34.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (295) hide show
  1. package/README.md +72 -0
  2. package/dist/pragma +0 -0
  3. package/package.json +19 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +15 -6
  6. package/src/capabilities/block.parity.test.ts +250 -0
  7. package/src/capabilities/capabilities/capabilities.render.ts +3 -2
  8. package/src/capabilities/capabilities/capabilities.test.ts +10 -1
  9. package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
  10. package/src/capabilities/capabilities/catalog.ts +20 -8
  11. package/src/capabilities/capabilities/hints.ts +21 -36
  12. package/src/capabilities/capabilities/types.ts +2 -0
  13. package/src/capabilities/colophon/collectColophon.ts +34 -19
  14. package/src/capabilities/colophon/colophon.render.test.ts +31 -4
  15. package/src/capabilities/colophon/colophon.render.ts +13 -4
  16. package/src/capabilities/colophon/colophon.test.ts +3 -3
  17. package/src/capabilities/colophon/colophon.verb.ts +11 -4
  18. package/src/capabilities/colophon/markdownTerminal.ts +33 -14
  19. package/src/capabilities/colophon/types.ts +16 -7
  20. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  21. package/src/capabilities/config/collectConfigShow.ts +41 -4
  22. package/src/capabilities/config/fields.ts +10 -108
  23. package/src/capabilities/config/set.test.ts +6 -6
  24. package/src/capabilities/config/set.verb.ts +12 -8
  25. package/src/capabilities/config/show.render.test.ts +137 -0
  26. package/src/capabilities/config/show.render.ts +57 -16
  27. package/src/capabilities/config/show.verb.ts +9 -8
  28. package/src/capabilities/config/types.ts +8 -0
  29. package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
  30. package/src/capabilities/create/byteEquality.test.ts +82 -101
  31. package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
  32. package/src/capabilities/create/constants.ts +75 -0
  33. package/src/capabilities/create/create.mcp.test.ts +2 -2
  34. package/src/capabilities/create/create.test.ts +192 -16
  35. package/src/capabilities/create/create.verb.ts +208 -79
  36. package/src/capabilities/create/index.ts +2 -10
  37. package/src/capabilities/create/pickGenerator.ts +34 -28
  38. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  39. package/src/capabilities/create/types.ts +10 -6
  40. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  41. package/src/capabilities/distribution.test.ts +120 -0
  42. package/src/capabilities/distribution.ts +59 -0
  43. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  44. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  45. package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
  46. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
  47. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  48. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  49. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  50. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  51. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  52. package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
  53. package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
  54. package/src/capabilities/doctor/doctor.render.test.ts +170 -0
  55. package/src/capabilities/doctor/doctor.render.ts +117 -34
  56. package/src/capabilities/doctor/doctor.test.ts +68 -9
  57. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  58. package/src/capabilities/doctor/runChecks.ts +33 -9
  59. package/src/capabilities/doctor/types.ts +16 -1
  60. package/src/capabilities/emptyRecovery.test.ts +32 -15
  61. package/src/capabilities/graph/inspect.verb.ts +3 -2
  62. package/src/capabilities/graph/query.verb.ts +3 -2
  63. package/src/capabilities/graph/runQuery.ts +8 -5
  64. package/src/capabilities/index.ts +22 -18
  65. package/src/capabilities/info/collectInfo.ts +10 -6
  66. package/src/capabilities/info/info.render.ts +3 -2
  67. package/src/capabilities/info/info.verb.ts +3 -2
  68. package/src/capabilities/lazy.test.ts +93 -2
  69. package/src/capabilities/ontology/index.ts +10 -3
  70. package/src/capabilities/ontology/ontology.test.ts +157 -5
  71. package/src/capabilities/ontology/verbs.ts +203 -104
  72. package/src/capabilities/prompt/prompt.render.test.ts +94 -0
  73. package/src/capabilities/prompt/prompt.render.ts +23 -6
  74. package/src/capabilities/prompt/prompt.test.ts +68 -1
  75. package/src/capabilities/prompt/verbs.ts +24 -11
  76. package/src/capabilities/reference.test.ts +32 -0
  77. package/src/capabilities/resources/provider.ts +58 -25
  78. package/src/capabilities/resources/resources.test.ts +176 -21
  79. package/src/capabilities/scopeTypeSync.test.ts +37 -0
  80. package/src/capabilities/setup/index.ts +7 -1
  81. package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
  82. package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
  83. package/src/capabilities/setup/operations/setupLsp.ts +56 -12
  84. package/src/capabilities/setup/operations/setupMcp.ts +216 -39
  85. package/src/capabilities/setup/operations/setupSkills.ts +10 -4
  86. package/src/capabilities/setup/setup.render.test.ts +109 -0
  87. package/src/capabilities/setup/setup.render.ts +61 -14
  88. package/src/capabilities/setup/setup.test.ts +352 -1
  89. package/src/capabilities/setup/setup.verb.ts +68 -9
  90. package/src/capabilities/setup/shell.ts +29 -3
  91. package/src/capabilities/setup/types.ts +59 -2
  92. package/src/capabilities/shared/bands.ts +16 -0
  93. package/src/capabilities/skill/discover.ts +11 -5
  94. package/src/capabilities/skill/render.ts +4 -5
  95. package/src/capabilities/skill/verbs.ts +7 -7
  96. package/src/capabilities/sources/collectStatus.ts +69 -49
  97. package/src/capabilities/sources/index.ts +1 -1
  98. package/src/capabilities/sources/installSkills.ts +2 -2
  99. package/src/capabilities/sources/runUpdate.ts +146 -98
  100. package/src/capabilities/sources/sources.test.ts +264 -84
  101. package/src/capabilities/sources/status.render.test.ts +94 -0
  102. package/src/capabilities/sources/status.render.ts +55 -33
  103. package/src/capabilities/sources/status.verb.ts +15 -8
  104. package/src/capabilities/sources/types.ts +18 -25
  105. package/src/capabilities/sources/update.render.ts +9 -2
  106. package/src/capabilities/sources/update.verb.ts +12 -23
  107. package/src/capabilities/surface.test.ts +65 -17
  108. package/src/capabilities/tier.parity.test.ts +144 -0
  109. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  110. package/src/constants.test.ts +9 -7
  111. package/src/constants.ts +49 -23
  112. package/src/identity.test.ts +333 -0
  113. package/src/index.ts +2 -2
  114. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
  115. package/src/kernel/completion/complete.test.ts +3 -5
  116. package/src/kernel/completion/emitScripts.test.ts +1 -1
  117. package/src/kernel/completion/emitScripts.ts +4 -2
  118. package/src/kernel/completion/entitySource.test.ts +185 -41
  119. package/src/kernel/completion/entitySource.ts +131 -113
  120. package/src/kernel/completion/model.test.ts +3 -3
  121. package/src/kernel/completion/model.ts +11 -1
  122. package/src/kernel/completion/parse.test.ts +2 -4
  123. package/src/kernel/completion/safety.test.ts +82 -36
  124. package/src/kernel/completion/shellDrive.test.ts +592 -0
  125. package/src/kernel/completion/templates/fish.ts +28 -6
  126. package/src/kernel/completion/types.ts +3 -3
  127. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  128. package/src/kernel/config/defaults.test.ts +85 -0
  129. package/src/kernel/config/defaults.ts +12 -23
  130. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  131. package/src/kernel/config/findProjectConfig.ts +4 -3
  132. package/src/kernel/config/firstRun.ts +12 -5
  133. package/src/kernel/config/globalConfig.ts +2 -2
  134. package/src/kernel/config/index.ts +4 -5
  135. package/src/kernel/config/paths.ts +17 -17
  136. package/src/kernel/config/readConfig.test.ts +242 -6
  137. package/src/kernel/config/readConfig.ts +8 -8
  138. package/src/kernel/config/schema.test.ts +50 -0
  139. package/src/kernel/config/schema.ts +97 -10
  140. package/src/kernel/config/types.ts +73 -19
  141. package/src/kernel/copy.test.ts +453 -0
  142. package/src/kernel/error/PragmaError.test.ts +5 -5
  143. package/src/kernel/error/constants.ts +3 -2
  144. package/src/kernel/error/index.ts +1 -1
  145. package/src/kernel/error/recovery.test.ts +22 -45
  146. package/src/kernel/error/recovery.ts +22 -27
  147. package/src/kernel/error/renderError.ts +1 -1
  148. package/src/kernel/error/types.ts +5 -4
  149. package/src/kernel/packs/collect.test.ts +195 -11
  150. package/src/kernel/packs/collect.ts +213 -58
  151. package/src/kernel/packs/compile.test.ts +163 -7
  152. package/src/kernel/packs/compile.ts +15 -12
  153. package/src/kernel/packs/disclosure.test.ts +5 -2
  154. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  155. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  156. package/src/kernel/packs/iri.ts +5 -7
  157. package/src/kernel/packs/renderPack.ts +5 -3
  158. package/src/kernel/packs/resolveEntity.ts +21 -17
  159. package/src/kernel/packs/runBodies.ts +15 -14
  160. package/src/kernel/packs/schema.ts +96 -10
  161. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  162. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  163. package/src/kernel/packs/toolDescription.test.ts +10 -2
  164. package/src/kernel/packs/types.ts +39 -4
  165. package/src/kernel/packs/uniqueness.ts +4 -4
  166. package/src/kernel/project/cli/buildProgram.test.ts +60 -11
  167. package/src/kernel/project/cli/buildProgram.ts +11 -0
  168. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  169. package/src/kernel/project/cli/dispatch.ts +34 -12
  170. package/src/kernel/project/cli/globalFlags.test.ts +12 -4
  171. package/src/kernel/project/cli/globalFlags.ts +14 -14
  172. package/src/kernel/project/cli/helpFormat.ts +1 -1
  173. package/src/kernel/project/cli/rootHelp.ts +109 -107
  174. package/src/kernel/project/mcp/buildServer.ts +11 -6
  175. package/src/kernel/project/mcp/instructions.ts +12 -7
  176. package/src/kernel/project/mcp/mcpError.ts +52 -0
  177. package/src/kernel/project/mcp/parity.test.ts +39 -1
  178. package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
  179. package/src/kernel/project/mcp/prompts/provider.ts +51 -7
  180. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  181. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  182. package/src/kernel/project/mcp/serve.ts +11 -3
  183. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  184. package/src/kernel/render/disclosure.ts +4 -2
  185. package/src/kernel/render/formatters.ts +5 -4
  186. package/src/kernel/render/prefixes.test.ts +36 -0
  187. package/src/kernel/render/prefixes.ts +40 -13
  188. package/src/kernel/render/renderers.test.ts +35 -1
  189. package/src/kernel/render/renderers.ts +25 -7
  190. package/src/kernel/render/style.test.ts +31 -0
  191. package/src/kernel/render/style.ts +74 -0
  192. package/src/kernel/runtime/graphpack/build.ts +57 -3
  193. package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
  194. package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
  195. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
  196. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
  197. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  198. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  199. package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
  200. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  201. package/src/kernel/runtime/graphpack/index.ts +1 -1
  202. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  203. package/src/kernel/runtime/graphpack/read.ts +32 -22
  204. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  205. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  206. package/src/kernel/runtime/graphpack/types.ts +72 -45
  207. package/src/kernel/runtime/loadSession.ts +4 -12
  208. package/src/kernel/runtime/paths.ts +64 -15
  209. package/src/kernel/runtime/probe.test.ts +8 -4
  210. package/src/kernel/runtime/readEntity.ts +6 -5
  211. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  212. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  213. package/src/kernel/runtime/refs/resolve.ts +141 -65
  214. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  215. package/src/kernel/runtime/resolveSources.ts +36 -25
  216. package/src/kernel/runtime/storeReadiness.ts +62 -0
  217. package/src/kernel/runtime/storeless.test.ts +1 -1
  218. package/src/kernel/runtime/types.ts +1 -1
  219. package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
  220. package/src/kernel/spec/emitReference.test.ts +196 -0
  221. package/src/kernel/spec/emitReference.ts +624 -0
  222. package/src/kernel/spec/emitSurface.ts +13 -7
  223. package/src/kernel/spec/index.ts +2 -0
  224. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  225. package/src/kernel/spec/surfaceConformance.ts +8 -0
  226. package/src/kernel/spec/types.ts +29 -9
  227. package/src/kernel/spec/validate.ts +2 -1
  228. package/src/kernel/vocabulary.test.ts +164 -0
  229. package/src/kernel/vocabulary.ts +146 -0
  230. package/src/testing/PARITY_GAPS.ts +9 -3
  231. package/src/testing/behavioral/README.md +3 -3
  232. package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
  233. package/src/testing/behavioral/completion.test.ts +21 -8
  234. package/src/testing/behavioral/disclosure.test.ts +12 -0
  235. package/src/testing/behavioral/docExamples.test.ts +283 -0
  236. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  237. package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
  238. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  239. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  240. package/src/testing/behavioral/journeys.cli.test.ts +116 -73
  241. package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
  242. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  243. package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
  244. package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
  245. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
  246. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  247. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  248. package/src/testing/behavioral/mcpSurface.test.ts +10 -5
  249. package/src/testing/behavioral/staleCommands.test.ts +86 -0
  250. package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
  251. package/src/testing/eval/cases/assembly.ts +16 -9
  252. package/src/testing/eval/cases/effectful.ts +31 -16
  253. package/src/testing/eval/cases/readNouns.ts +23 -18
  254. package/src/testing/eval/cases/stable.ts +5 -1
  255. package/src/testing/eval/eval.test.ts +7 -1
  256. package/src/testing/fixtures/fixtureCapability.ts +201 -1
  257. package/src/testing/fixtures/graph/canonical.ts +6 -3
  258. package/src/testing/fixtures/graph/defaultPack.ts +179 -0
  259. package/src/testing/fixtures/graph/owlExport.ts +97 -0
  260. package/src/testing/helpers/extractSnippets.ts +29 -0
  261. package/src/testing/helpers/fixtureGraph.ts +33 -16
  262. package/src/testing/helpers/golden.ts +1 -1
  263. package/src/testing/helpers/packRuntime.ts +2 -3
  264. package/src/testing/helpers/projectCli.ts +2 -1
  265. package/src/testing/helpers/projectMcp.ts +6 -0
  266. package/src/testing/perf/budgets.test.ts +4 -0
  267. package/src/testing/perf/budgets.ts +22 -7
  268. package/src/testing/perf/globalSetup.ts +121 -6
  269. package/src/types.ts +2 -2
  270. package/src/capabilities/block/blockList.verb.ts +0 -150
  271. package/src/capabilities/block/index.ts +0 -25
  272. package/src/capabilities/block/pack.ts +0 -155
  273. package/src/capabilities/block/parity.test.ts +0 -180
  274. package/src/capabilities/block/tierChain.ts +0 -78
  275. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  276. package/src/capabilities/config/field.test.ts +0 -188
  277. package/src/capabilities/modifier/index.ts +0 -15
  278. package/src/capabilities/modifier/pack.ts +0 -64
  279. package/src/capabilities/standard/index.ts +0 -18
  280. package/src/capabilities/standard/pack.ts +0 -128
  281. package/src/capabilities/tier/index.ts +0 -19
  282. package/src/capabilities/tier/lookup.render.ts +0 -34
  283. package/src/capabilities/tier/lookup.test.ts +0 -68
  284. package/src/capabilities/tier/lookup.verb.ts +0 -44
  285. package/src/capabilities/tier/pack.ts +0 -28
  286. package/src/capabilities/tier/runLookup.ts +0 -59
  287. package/src/capabilities/token/addConfig.render.ts +0 -27
  288. package/src/capabilities/token/addConfig.test.ts +0 -62
  289. package/src/capabilities/token/addConfig.verb.ts +0 -40
  290. package/src/capabilities/token/index.ts +0 -19
  291. package/src/capabilities/token/pack.ts +0 -58
  292. package/src/capabilities/token/runAddConfig.ts +0 -95
  293. package/src/kernel/completion/bashDrive.test.ts +0 -73
  294. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  295. package/src/kernel/runtime/lock.ts +0 -90
@@ -0,0 +1,85 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import defaults from "./defaults.js";
3
+
4
+ describe("defaults — the validated distribution config (pragma.conf.ts)", () => {
5
+ it("ships the distribution identity", () => {
6
+ expect(defaults.name).toBe("pragma");
7
+ expect(defaults.help).toBe("Explore the design system");
8
+ expect(defaults.issuesUrl).toBe(
9
+ "https://github.com/canonical/pragma/issues",
10
+ );
11
+ });
12
+
13
+ it("declares the toolchain colophon as content (markdown body + summary)", () => {
14
+ // The narrative is a DECLARATION the `colophon` verb renders, not code:
15
+ // `collectColophon` reads exactly this object. Pin the shape and the two
16
+ // stable fragments — the story's own subject and the maker line the old
17
+ // one-line `colophon` string carried (folded in when the field went live).
18
+ expect(defaults.colophon?.markdown).toContain("domain-based toolchain");
19
+ expect(defaults.colophon?.markdown).toContain(
20
+ "Made by the Canonical Webteam — https://canonical.com.",
21
+ );
22
+ expect(defaults.colophon?.summary).toContain("domain-based toolchain");
23
+ // Bodies, not documents: the renderer supplies the H1 from the name.
24
+ expect(defaults.colophon?.markdown.startsWith("#")).toBe(false);
25
+ expect(defaults.colophon?.summary?.startsWith("#")).toBe(false);
26
+ });
27
+
28
+ it("ships the three canonical default packs (git+https sources)", () => {
29
+ expect(
30
+ defaults.packs?.map((pack) =>
31
+ typeof pack === "string"
32
+ ? pack
33
+ : { name: pack.name, source: pack.source },
34
+ ),
35
+ ).toEqual([
36
+ {
37
+ name: "@canonical/design-system",
38
+ source: "git+https://github.com/canonical/design-system.git#main",
39
+ },
40
+ {
41
+ name: "@canonical/anatomy-dsl",
42
+ source: "git+https://github.com/canonical/anatomy-dsl.git#main",
43
+ },
44
+ {
45
+ name: "@canonical/code-standards",
46
+ source: "git+https://github.com/canonical/web-code-standards.git#main",
47
+ },
48
+ ]);
49
+ });
50
+
51
+ it("carries the declared read stories through validation, on their packs", () => {
52
+ // The zod layer must not strip `stories` (unknown keys ARE stripped for
53
+ // forward compatibility). Content is owned by `capabilities/distribution.test.ts`;
54
+ // this pins only that the field survives `parseRawConfig` on the right pack.
55
+ const storyCounts = defaults.packs?.map((pack) =>
56
+ typeof pack === "string" ? 0 : (pack.stories?.length ?? 0),
57
+ );
58
+ expect(storyCounts).toEqual([4, 0, 1]);
59
+ });
60
+
61
+ it("declares no removed field — the validator would refuse to load one", () => {
62
+ // `generators` was deleted (L-OPEN-1 ruling): validated, layered, and read
63
+ // by nothing. The distribution config goes through the same strict
64
+ // `parseRawConfig` as every layer, so a `generators:` reintroduced here
65
+ // would fail the eager validation at import — this pins the honest state.
66
+ expect(defaults).not.toHaveProperty("generators");
67
+ });
68
+
69
+ it("declares the domain namespaces the store is built with and the CLI resolves", () => {
70
+ // Two consumers, one declaration. (1) The config layer wins every prefix
71
+ // harvest, so this is what keeps `ds:` entity names stable when the design
72
+ // system declares `ds:` with two IRIs. (2) It is the domain half of
73
+ // `DEFAULT_PREFIX_MAP`, so it also decides which prefixed names a lookup
74
+ // can expand — `cs:` is here for that reader.
75
+ expect(defaults.prefixes).toEqual({
76
+ ds: "https://ds.canonical.com/",
77
+ cs: "http://pragma.canonical.com/codestandards#",
78
+ });
79
+ });
80
+
81
+ it("ships the normal channel and standard detail level", () => {
82
+ expect(defaults.channel).toBe("normal");
83
+ expect(defaults.detail).toBe("standard");
84
+ });
85
+ });
@@ -1,28 +1,17 @@
1
1
  /**
2
- * Built-in configuration defaults (D6 — TS, not JSON).
2
+ * Built-in configuration defaults — the validated distribution config.
3
3
  *
4
- * The lowest config layer. Authored as a typed module so an invalid edit fails
5
- * at type-check rather than at runtime, and so the default `packages` sources
6
- * live in one place. Sources verified against the v1 `config/defaults.json`.
4
+ * The lowest config layer is the distribution's own `pragma.conf.ts` (package
5
+ * root), STATICALLY imported so `bun build --compile` inlines it (no fs read —
6
+ * `evaluateProjectConfig` stats/imports from disk, impossible inside the
7
+ * compiled binary) and validated through the same `parseRawConfig` as every
8
+ * other layer. Statically imported by `readConfig` (itself dynamic-imported,
9
+ * off the `--help`/`__complete` fast path) and by its own test, so the eager
10
+ * validation and the zod import here add nothing to the storeless surfaces —
11
+ * the lazy.test.ts module-graph probe covers that boundary.
7
12
  */
8
13
 
9
- import type { PragmaConfig } from "./types.js";
14
+ import rawConfig from "../../../pragma.conf.js";
15
+ import { parseRawConfig } from "./schema.js";
10
16
 
11
- export default {
12
- channel: "normal",
13
- detail: "standard",
14
- packages: [
15
- {
16
- name: "@canonical/design-system",
17
- source: "git+https://github.com/canonical/design-system.git#main",
18
- },
19
- {
20
- name: "@canonical/anatomy-dsl",
21
- source: "git+https://github.com/canonical/anatomy-dsl.git#main",
22
- },
23
- {
24
- name: "@canonical/code-standards",
25
- source: "git+https://github.com/canonical/web-code-standards.git#main",
26
- },
27
- ],
28
- } satisfies PragmaConfig;
17
+ export default parseRawConfig(rawConfig, "pragma.conf.ts");
@@ -33,7 +33,7 @@ import {
33
33
  } from "node:fs";
34
34
  import { join } from "node:path";
35
35
  import { pathToFileURL } from "node:url";
36
- import { VERSION } from "../../constants.js";
36
+ import { PROJECT_CONFIG_FILENAME, VERSION } from "../../constants.js";
37
37
  import { PragmaError } from "../error/PragmaError.js";
38
38
  import { configCacheDir } from "./paths.js";
39
39
  import { parseRawConfig } from "./schema.js";
@@ -107,8 +107,7 @@ export async function evaluateProjectConfig(path: string): Promise<RawConfig> {
107
107
  `Could not load project config ${path}: ${reason}`,
108
108
  {
109
109
  recovery: {
110
- message:
111
- "Fix the error in your pragma.config.ts (it must evaluate to a default export), then try again.",
110
+ message: `Fix the error in your ${PROJECT_CONFIG_FILENAME} (it must evaluate to a default export), then try again.`,
112
111
  },
113
112
  },
114
113
  );
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Locate the nearest project config by walking up from `cwd`.
3
3
  *
4
- * Looks for `pragma.config.ts` (or, as a compiled-binary fallback,
5
- * `pragma.config.js`) at or above `cwd`. The walk is bounded: it stops after
4
+ * Looks for `<bin>.config.ts` (or, as a compiled-binary fallback,
5
+ * `<bin>.config.js`) at or above `cwd`. The walk is bounded: it stops after
6
6
  * the first directory containing `.git` (the repo root), after the home
7
7
  * directory, and at the filesystem root — a config outside the repo or home
8
8
  * never leaks in. Ported from the v1 `findProjectConfigPath`.
@@ -11,9 +11,10 @@
11
11
  import { existsSync } from "node:fs";
12
12
  import { homedir } from "node:os";
13
13
  import { dirname, join, resolve } from "node:path";
14
+ import { BIN_NAME, PROJECT_CONFIG_FILENAME } from "../../constants.js";
14
15
 
15
16
  /** Candidate project-config filenames, in preference order. */
16
- const CANDIDATES = ["pragma.config.ts", "pragma.config.js"] as const;
17
+ const CANDIDATES = [PROJECT_CONFIG_FILENAME, `${BIN_NAME}.config.js`] as const;
17
18
 
18
19
  /**
19
20
  * Find the nearest project config file at or above `cwd`.
@@ -21,6 +21,11 @@ import {
21
21
  writeFile,
22
22
  } from "@canonical/task";
23
23
  import { runTask } from "@canonical/task/node";
24
+ import {
25
+ BIN_NAME,
26
+ ISSUES_URL,
27
+ PROJECT_CONFIG_FILENAME,
28
+ } from "../../constants.js";
24
29
  import { globalConfigPath } from "./paths.js";
25
30
 
26
31
  /** Seed content: an empty object, so nothing is pinned the user did not choose. */
@@ -29,10 +34,10 @@ const SEED_CONFIG = "{}\n";
29
34
  /** Build the welcome note; the resolved path is shown so it is copyable. */
30
35
  function welcomeLines(path: string): string[] {
31
36
  return [
32
- "Hello! Thanks for taking the time to try the pre-release pragma CLI.",
33
- "Please file issues and feedback at https://github.com/canonical/pragma/issues.",
34
- `Pragma stores its configuration in ${path} (just created with defaults).`,
35
- "A `pragma.config.ts` in this directory or above it overrides it per project.",
37
+ `Hello! Thanks for taking the time to try the pre-release ${BIN_NAME} CLI.`,
38
+ `Please file issues and feedback at ${ISSUES_URL}.`,
39
+ `${BIN_NAME} stores its configuration in ${path} (just created with defaults).`,
40
+ `A \`${PROJECT_CONFIG_FILENAME}\` in this directory or above it overrides it per project.`,
36
41
  "",
37
42
  ];
38
43
  }
@@ -86,6 +91,8 @@ export async function ensureFirstRun(
86
91
  });
87
92
  } catch (error) {
88
93
  const reason = error instanceof Error ? error.message : String(error);
89
- write(`Warning: could not create the global pragma config — ${reason}`);
94
+ write(
95
+ `Warning: could not create the global ${BIN_NAME} config — ${reason}`,
96
+ );
90
97
  }
91
98
  }
@@ -1,8 +1,8 @@
1
1
  /**
2
2
  * Read the global machine config: `$XDG_CONFIG_HOME/pragma/config.json`.
3
3
  *
4
- * The global layer holds cross-project machine state (channel, detail, tier,
5
- * prompts). A missing or empty file contributes no values — the layer simply
4
+ * The global layer holds cross-project machine state (channel, detail, tier).
5
+ * A missing or empty file contributes no values — the layer simply
6
6
  * does not participate in the merge. A *malformed* file (a torn write) must
7
7
  * never brick every command (the old loud `CONFIG_ERROR` throw) nor be silently
8
8
  * discarded (writeConfigField's old reset-to-`{}`): it is backed up to a
@@ -3,17 +3,16 @@
3
3
  *
4
4
  * `schema.ts` (zod) is intentionally not re-exported here: it is imported
5
5
  * directly and lazily by the readers, keeping this barrel — and any storeless
6
- * consumer — off the zod fast path.
6
+ * consumer — off the zod fast path. `defaults` is likewise not re-exported:
7
+ * it statically imports the distribution config and the validating schema.
7
8
  */
8
9
 
9
- export { default as defaults } from "./defaults.js";
10
10
  export { evaluateProjectConfig } from "./evaluateProjectConfig.js";
11
11
  export { findProjectConfig } from "./findProjectConfig.js";
12
12
  export { ensureFirstRun, firstRunTask } from "./firstRun.js";
13
13
  export type { GlobalConfigRead } from "./globalConfig.js";
14
14
  export { readGlobalConfig } from "./globalConfig.js";
15
15
  export {
16
- cacheDir,
17
16
  configCacheDir,
18
17
  configDir,
19
18
  globalConfigPath,
@@ -26,8 +25,8 @@ export type {
26
25
  ConfigLayers,
27
26
  ConfigOrigin,
28
27
  ConfigOrigins,
29
- PackageDeclaration,
30
- PackageEntry,
28
+ PackDeclaration,
29
+ PackSource,
31
30
  PragmaConfig,
32
31
  RawConfig,
33
32
  } from "./types.js";
@@ -1,39 +1,39 @@
1
1
  /**
2
- * XDG base-directory resolution for config, cache, and state.
2
+ * XDG base-directory resolution for config and state.
3
3
  *
4
- * Config (the global JSON) lives under `$XDG_CONFIG_HOME/pragma`; the evaluated
5
- * project-config cache lives under `$XDG_STATE_HOME/pragma/config-cache`. Tests
6
- * point these env vars at per-run temp directories (see setupXdgIsolation) so
7
- * they never touch the developer's real state.
4
+ * Every directory is namespaced by the distribution's own {@link BIN_NAME}, so a
5
+ * fork stores its state beside this one instead of colliding in it — the
6
+ * greeting that quotes the resolved path then names one program, not two. Config
7
+ * (the global JSON) lives under `$XDG_CONFIG_HOME/<bin>`; the evaluated
8
+ * project-config cache lives under `$XDG_STATE_HOME/<bin>/config-cache`. The
9
+ * store layer's cache root (`$XDG_CACHE_HOME/<bin>`) is resolved by
10
+ * `kernel/runtime/paths.ts` instead — it must stay leaf-clean for the storeless
11
+ * fast path. Tests point these env vars at per-run temp directories (see
12
+ * setupXdgIsolation) so they never touch the developer's real state.
8
13
  */
9
14
 
10
15
  import { homedir } from "node:os";
11
16
  import { join } from "node:path";
17
+ import { BIN_NAME } from "../../constants.js";
12
18
 
13
- /** `$XDG_CONFIG_HOME/pragma` (default `~/.config/pragma`). */
19
+ /** `$XDG_CONFIG_HOME/<bin>` (default `~/.config/<bin>`). */
14
20
  export function configDir(): string {
15
21
  const base = process.env.XDG_CONFIG_HOME ?? join(homedir(), ".config");
16
- return join(base, "pragma");
22
+ return join(base, BIN_NAME);
17
23
  }
18
24
 
19
- /** `$XDG_CACHE_HOME/pragma` (default `~/.cache/pragma`). */
20
- export function cacheDir(): string {
21
- const base = process.env.XDG_CACHE_HOME ?? join(homedir(), ".cache");
22
- return join(base, "pragma");
23
- }
24
-
25
- /** `$XDG_STATE_HOME/pragma` (default `~/.local/state/pragma`). */
25
+ /** `$XDG_STATE_HOME/<bin>` (default `~/.local/state/<bin>`). */
26
26
  export function stateDir(): string {
27
27
  const base = process.env.XDG_STATE_HOME ?? join(homedir(), ".local", "state");
28
- return join(base, "pragma");
28
+ return join(base, BIN_NAME);
29
29
  }
30
30
 
31
- /** The global config file: `$XDG_CONFIG_HOME/pragma/config.json`. */
31
+ /** The global config file: `$XDG_CONFIG_HOME/<bin>/config.json`. */
32
32
  export function globalConfigPath(): string {
33
33
  return join(configDir(), "config.json");
34
34
  }
35
35
 
36
- /** The evaluated-project-config cache dir: `$XDG_STATE_HOME/pragma/config-cache`. */
36
+ /** The evaluated-project-config cache dir: `$XDG_STATE_HOME/<bin>/config-cache`. */
37
37
  export function configCacheDir(): string {
38
38
  return join(stateDir(), "config-cache");
39
39
  }
@@ -10,6 +10,7 @@ import { tmpdir } from "node:os";
10
10
  import { dirname, join } from "node:path";
11
11
  import { runTask } from "@canonical/task/node";
12
12
  import { afterEach, describe, expect, it, vi } from "vitest";
13
+ import { BIN_NAME, PROGRAM_DESCRIPTION } from "../../constants.js";
13
14
  import { evaluateProjectConfig } from "./evaluateProjectConfig.js";
14
15
  import { findProjectConfig } from "./findProjectConfig.js";
15
16
  import { readGlobalConfig } from "./globalConfig.js";
@@ -58,13 +59,13 @@ describe("readConfig — layering + provenance", () => {
58
59
  expect(config.tier).toBe("core");
59
60
  expect(config.channel).toBe("experimental");
60
61
  expect(config.detail).toBe("detailed");
61
- expect(config.packages).toHaveLength(3); // from defaults
62
+ expect(config.packs).toHaveLength(3); // from defaults
62
63
 
63
64
  expect(origins).toMatchObject({
64
65
  tier: "project",
65
66
  channel: "global",
66
67
  detail: "project", // project wins over global
67
- packages: "default",
68
+ packs: "default",
68
69
  });
69
70
  expect(project.exists).toBe(true);
70
71
  expect(global.exists).toBe(true);
@@ -81,14 +82,43 @@ describe("readConfig — layering + provenance", () => {
81
82
  expect(project.exists).toBe(false);
82
83
  });
83
84
 
84
- it("replaces packages wholesale from the project layer", async () => {
85
+ it("replaces packs wholesale from the project layer", async () => {
85
86
  freshXdg();
86
- const dir = projectWith('export default { packages: ["@acme/only"] };');
87
+ const dir = projectWith('export default { packs: ["@acme/only"] };');
88
+
89
+ const { config, origins } = await readConfig(dir);
90
+
91
+ expect(config.packs).toEqual(["@acme/only"]);
92
+ expect(origins.packs).toBe("project");
93
+ });
94
+
95
+ it("accepts a layer's identity fields and gives them NO effect", async () => {
96
+ // Identity is read from `pragma.conf.ts` by `src/constants.ts` at module
97
+ // load, so a layer declaring it can only ever be silent. The validator
98
+ // still accepts the keys (the distribution config shares the schema), and
99
+ // the merged config now carries neither the value nor a provenance for it
100
+ // — which is what `config show` used to report and did not honour.
101
+ freshXdg();
102
+ writeGlobal('{"help":"Global help"}');
103
+ const dir = projectWith(
104
+ 'export default { name: "acme", colophon: { markdown: "By Acme." }, issuesUrl: "https://acme.test/issues", tier: "core" };',
105
+ );
87
106
 
88
107
  const { config, origins } = await readConfig(dir);
89
108
 
90
- expect(config.packages).toEqual(["@acme/only"]);
91
- expect(origins.packages).toBe("project");
109
+ expect(config).not.toHaveProperty("name");
110
+ expect(config).not.toHaveProperty("help");
111
+ expect(config).not.toHaveProperty("colophon");
112
+ expect(config).not.toHaveProperty("issuesUrl");
113
+ expect(origins).not.toHaveProperty("name");
114
+ // The positive control, from the SAME project file: a layerable field
115
+ // declared beside them IS merged and reported, so the assertions above
116
+ // cannot pass by the project layer having failed to load at all.
117
+ expect(config.tier).toBe("core");
118
+ expect(origins.tier).toBe("project");
119
+ // And the projected identity is unmoved by any of it.
120
+ expect(BIN_NAME).toBe("pragma");
121
+ expect(PROGRAM_DESCRIPTION).not.toBe("Global help");
92
122
  });
93
123
 
94
124
  it("merges the completion policy into the effective config", async () => {
@@ -106,6 +136,212 @@ describe("readConfig — layering + provenance", () => {
106
136
  });
107
137
  });
108
138
 
139
+ describe("`detail` — a closed enum at load", () => {
140
+ it("a project config declaring an unknown level throws CONFIG_ERROR naming the file and the three levels", async () => {
141
+ freshXdg();
142
+ // Before the enum, `detail: "banana"` passed validation, was reported as
143
+ // `[project]` by `config show`, and silently rendered at `standard`.
144
+ const dir = projectWith('export default { detail: "banana" };');
145
+ const path = join(dir, "pragma.config.ts");
146
+
147
+ let caught: unknown;
148
+ try {
149
+ await evaluateProjectConfig(path);
150
+ } catch (error) {
151
+ caught = error;
152
+ }
153
+
154
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
155
+ const message = (caught as { message: string }).message;
156
+ expect(message).toContain(path);
157
+ expect(message).toContain("detail");
158
+ for (const level of ["summary", "standard", "detailed"]) {
159
+ expect(message).toContain(level);
160
+ }
161
+ });
162
+
163
+ it("a global JSON declaring an unknown level throws the same loud error", () => {
164
+ freshXdg();
165
+ writeGlobal('{"detail": "digest"}');
166
+
167
+ let caught: unknown;
168
+ try {
169
+ readGlobalConfig();
170
+ } catch (error) {
171
+ caught = error;
172
+ }
173
+
174
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
175
+ const message = (caught as { message: string }).message;
176
+ expect(message).toContain(globalConfigPath());
177
+ for (const level of ["summary", "standard", "detailed"]) {
178
+ expect(message).toContain(level);
179
+ }
180
+ });
181
+ });
182
+
183
+ describe("legacy config shapes — loud rename/removal errors", () => {
184
+ it("a project config declaring `packages` throws CONFIG_ERROR naming the rename", async () => {
185
+ freshXdg();
186
+ // Unknown keys are stripped by the schema, so the legacy key must be
187
+ // DETECTED — a silent ignore would boot the default packs while the user
188
+ // believes their own list is in force.
189
+ const dir = projectWith('export default { packages: ["@acme/only"] };');
190
+ const path = join(dir, "pragma.config.ts");
191
+
192
+ let caught: unknown;
193
+ try {
194
+ await evaluateProjectConfig(path);
195
+ } catch (error) {
196
+ caught = error;
197
+ }
198
+
199
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
200
+ expect((caught as { message: string }).message).toContain(
201
+ `Invalid config in ${path}: the "packages" field was renamed to "packs". The entry shape is unchanged.`,
202
+ );
203
+ // The recovery carries the path; the message drops the instruction.
204
+ expect(
205
+ (caught as { recovery?: { message: string } }).recovery?.message,
206
+ ).toBe(`In ${path}, rename "packages:" to "packs".`);
207
+ });
208
+
209
+ it("a legacy global JSON declaring `packages` throws the same rename error", () => {
210
+ freshXdg();
211
+ writeGlobal('{"packages": []}');
212
+
213
+ let caught: unknown;
214
+ try {
215
+ readGlobalConfig();
216
+ } catch (error) {
217
+ caught = error;
218
+ }
219
+
220
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
221
+ expect((caught as { message: string }).message).toContain(
222
+ 'the "packages" field was renamed to "packs". The entry shape is unchanged.',
223
+ );
224
+ });
225
+
226
+ it("a removed `generators` field throws CONFIG_ERROR naming the field", async () => {
227
+ freshXdg();
228
+ // Ruled dead (L-OPEN-1): `resolveSources` never read it, `create` resolves
229
+ // its generators statically, and its only consumers were the merge and
230
+ // `config show`'s name display. A config still declaring it must fail
231
+ // naming the file — the schema's unknown-key stripping would otherwise
232
+ // swallow the deletion in silence.
233
+ const dir = projectWith(
234
+ 'export default { generators: [{ name: "@acme/gen", source: "npm:@acme/gen@^1.0.0" }] };',
235
+ );
236
+ const path = join(dir, "pragma.config.ts");
237
+
238
+ let caught: unknown;
239
+ try {
240
+ await evaluateProjectConfig(path);
241
+ } catch (error) {
242
+ caught = error;
243
+ }
244
+
245
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
246
+ const message = (caught as { message: string }).message;
247
+ expect(message).toContain(path);
248
+ expect(message).toContain('"generators"');
249
+ expect(message).toContain("removed");
250
+ expect(
251
+ (caught as { recovery?: { message: string } }).recovery?.message,
252
+ ).toBe(`In ${path}, delete the "generators" field.`);
253
+ });
254
+
255
+ it("a global JSON still declaring generators throws the same removal error", () => {
256
+ freshXdg();
257
+ writeGlobal('{"generators": []}');
258
+
259
+ let caught: unknown;
260
+ try {
261
+ readGlobalConfig();
262
+ } catch (error) {
263
+ caught = error;
264
+ }
265
+
266
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
267
+ expect((caught as { message: string }).message).toContain('"generators"');
268
+ });
269
+
270
+ it("a removed `completion.caseSensitive` throws CONFIG_ERROR naming the field", async () => {
271
+ freshXdg();
272
+ // The field was validated and read by NOTHING. Removing it silently would
273
+ // leave a config author believing case-sensitivity is in force; the schema
274
+ // strips unknown keys, so the removal must be DETECTED before validation.
275
+ const dir = projectWith(
276
+ "export default { completion: { minChars: 3, caseSensitive: true } };",
277
+ );
278
+ const path = join(dir, "pragma.config.ts");
279
+
280
+ let caught: unknown;
281
+ try {
282
+ await evaluateProjectConfig(path);
283
+ } catch (error) {
284
+ caught = error;
285
+ }
286
+
287
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
288
+ const message = (caught as { message: string }).message;
289
+ expect(message).toContain(path);
290
+ expect(message).toContain('"completion.caseSensitive"');
291
+ expect(message).toContain("removed");
292
+ expect(
293
+ (caught as { recovery?: { message: string } }).recovery?.message,
294
+ ).toBe(`In ${path}, delete "caseSensitive" from "completion".`);
295
+ });
296
+
297
+ it("a global JSON still setting completion.caseSensitive throws the same removal error", () => {
298
+ freshXdg();
299
+ writeGlobal('{"completion": {"caseSensitive": false}}');
300
+
301
+ let caught: unknown;
302
+ try {
303
+ readGlobalConfig();
304
+ } catch (error) {
305
+ caught = error;
306
+ }
307
+
308
+ expect(caught).toMatchObject({ code: "CONFIG_ERROR" });
309
+ expect((caught as { message: string }).message).toContain(
310
+ '"completion.caseSensitive"',
311
+ );
312
+ });
313
+
314
+ it("a TOP-LEVEL `caseSensitive` key is an ordinary unknown key, not the removed field", async () => {
315
+ freshXdg();
316
+ // The detection reads exactly `completion.caseSensitive` — a stray
317
+ // top-level key keeps the forward-compatible unknown-key stripping.
318
+ const dir = projectWith(
319
+ "export default { caseSensitive: true, tier: 'core' };",
320
+ );
321
+ const path = join(dir, "pragma.config.ts");
322
+
323
+ await expect(evaluateProjectConfig(path)).resolves.toEqual({
324
+ tier: "core",
325
+ });
326
+ });
327
+
328
+ it("a `packages` key NESTED under another field does NOT trip the detection", async () => {
329
+ freshXdg();
330
+ // The rename detection is deliberately SHALLOW (top-level keys only): a
331
+ // `packages` key inside `prefixes` is a namespace prefix named `packages`,
332
+ // not the legacy field. Pin that against a future "helpful" deep check,
333
+ // which would false-positive here.
334
+ const dir = projectWith(
335
+ 'export default { prefixes: { packages: "https://example.com/packages/" } };',
336
+ );
337
+ const path = join(dir, "pragma.config.ts");
338
+
339
+ await expect(evaluateProjectConfig(path)).resolves.toEqual({
340
+ prefixes: { packages: "https://example.com/packages/" },
341
+ });
342
+ });
343
+ });
344
+
109
345
  describe("global config — corrupt-file recovery", () => {
110
346
  /** Backup siblings the recovery created next to the config file. */
111
347
  function corruptBackups(): string[] {
@@ -3,7 +3,7 @@
3
3
  *
4
4
  * Layer order, least to most specific: the built-in {@link defaults}, the
5
5
  * global XDG JSON, and the nearest evaluated `pragma.config.ts`. Merging is
6
- * per field — a more specific layer wins wholesale (so `packages` *replaces*,
6
+ * per field — a more specific layer wins wholesale (so `packs` *replaces*,
7
7
  * preserving the "project pins exactly" semantics) — and each field records
8
8
  * the layer that supplied it. Async because the project layer is evaluated
9
9
  * (and content-hash cached).
@@ -37,7 +37,7 @@ export async function readConfig(
37
37
  const project: RawConfig = projectPath
38
38
  ? await evaluateProjectConfig(projectPath)
39
39
  : {};
40
- const defaultValues = defaults as RawConfig;
40
+ const defaultValues = defaults;
41
41
 
42
42
  const pick = <K extends keyof RawConfig>(
43
43
  field: K,
@@ -51,13 +51,15 @@ export async function readConfig(
51
51
  return { value: defaultValues[field], origin: "default" };
52
52
  };
53
53
 
54
+ // `name`/`help`/`colophon`/`issuesUrl` are DELIBERATELY not picked: identity
55
+ // is read from `pragma.conf.ts` statically (`src/constants.ts`), so merging a
56
+ // layer's value here would report a provenance nothing honours.
54
57
  const tier = pick("tier");
55
58
  const channel = pick("channel");
56
59
  const detail = pick("detail");
57
- const packages = pick("packages");
60
+ const packs = pick("packs");
58
61
  const stories = pick("stories");
59
62
  const prefixes = pick("prefixes");
60
- const prompts = pick("prompts");
61
63
  // `completion` is read at `setup completions` emit time (not on any fast
62
64
  // path) and carries no `config show` provenance, so it merges into the
63
65
  // effective config but is deliberately absent from ConfigOrigins.
@@ -67,10 +69,9 @@ export async function readConfig(
67
69
  channel: (channel.value ?? defaults.channel) as Channel,
68
70
  ...(tier.value !== undefined ? { tier: tier.value } : {}),
69
71
  ...(detail.value !== undefined ? { detail: detail.value } : {}),
70
- ...(packages.value !== undefined ? { packages: packages.value } : {}),
72
+ ...(packs.value !== undefined ? { packs: packs.value } : {}),
71
73
  ...(stories.value !== undefined ? { stories: stories.value } : {}),
72
74
  ...(prefixes.value !== undefined ? { prefixes: prefixes.value } : {}),
73
- ...(prompts.value !== undefined ? { prompts: prompts.value } : {}),
74
75
  ...(completion.value !== undefined ? { completion: completion.value } : {}),
75
76
  };
76
77
 
@@ -80,10 +81,9 @@ export async function readConfig(
80
81
  tier: tier.origin,
81
82
  channel: channel.origin,
82
83
  detail: detail.origin,
83
- packages: packages.origin,
84
+ packs: packs.origin,
84
85
  stories: stories.origin,
85
86
  prefixes: prefixes.origin,
86
- prompts: prompts.origin,
87
87
  },
88
88
  global: { path: global.path, exists: global.exists },
89
89
  project: {