@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
@@ -1,12 +1,12 @@
1
1
  /**
2
- * `config set <key> <value>` — the additive one-command setter (PR9, COVENANT).
2
+ * `config set <key> <value>` — the one-command config setter (PR9, COVENANT).
3
3
  *
4
4
  * Pins the emitted covenant slice (`{ v:"set", args:["<key>","<value>"],
5
- * mutates:true, mcp:"config_set" }`), proves it shares the per-field write path
6
- * (set / reset-sentinel / enum-backstop), rejects an unknown key and an
7
- * out-of-set enum value with INVALID_INPUT, and holds MCP plan-first/confirm
8
- * parity. The frozen `config tier/channel/detail` verbs are UNTOUCHED — this is
9
- * purely additive.
5
+ * mutates:true, mcp:"config_set" }`), proves it drives the shared per-field
6
+ * write path (set / reset-sentinel / enum-backstop), rejects an unknown key and
7
+ * an out-of-set enum value with INVALID_INPUT, and holds MCP plan-first/confirm
8
+ * parity. Since AV-228 B3 retired `config tier/channel/detail`, `config set` is
9
+ * the sole config setter — `config set <field> <value>` is the migration path.
10
10
  */
11
11
 
12
12
  import { existsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
@@ -1,11 +1,11 @@
1
1
  /**
2
2
  * `config set <key> <value>` — the one-command config setter (ADDITIVE).
3
3
  *
4
- * The user-facing primary form for writing config: `pragma config set tier
5
- * apps/lxd` instead of the per-field `pragma config tier apps/lxd`. It is purely
6
- * additive — the frozen `config tier`/`channel`/`detail` verbs stay — and shares
7
- * their exact write path (`runField` via `runSet`), so it inherits reset
8
- * sentinels, enum re-validation, and the global-layer-only write for free.
4
+ * The single form for writing config: `pragma config set tier apps/lxd`. Since
5
+ * AV-228 B3 retired the per-field `config tier`/`channel`/`detail` verbs, this
6
+ * is now the ONLY config setter. It drives the shared write path (`runField` via
7
+ * `runSet`), so it inherits reset sentinels, enum re-validation, and the
8
+ * global-layer-only write from the {@link CONFIG_FIELDS} table.
9
9
  *
10
10
  * Covenant shape: `<key>` is an ENUM over the field names (better completion +
11
11
  * validation; the token still emits as `<key>`), `<value>` a free string, so
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import type { Task } from "@canonical/task";
17
+ import { BIN_NAME } from "../../constants.js";
17
18
  import { asVerb } from "../../kernel/spec/asVerb.js";
18
19
  import type { VerbSpec } from "../../kernel/spec/types.js";
19
20
  import { configFieldFormatters } from "./field.render.js";
@@ -46,9 +47,12 @@ const setVerb: VerbSpec<Record<string, unknown>, ConfigFieldResult> = {
46
47
  ],
47
48
  output: { formatters: configFieldFormatters },
48
49
  examples: [
49
- { cmd: "pragma config set tier apps/lxd", note: "scope reads to a tier" },
50
- { cmd: "pragma config set channel experimental" },
51
- { cmd: "pragma config set tier none", note: "clear the tier" },
50
+ {
51
+ cmd: `${BIN_NAME} config set tier apps/lxd`,
52
+ note: "scope reads to a tier",
53
+ },
54
+ { cmd: `${BIN_NAME} config set channel experimental` },
55
+ { cmd: `${BIN_NAME} config set tier none`, note: "clear the tier" },
52
56
  ],
53
57
  capability: {
54
58
  needsStore: false,
@@ -0,0 +1,137 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import { type RenderStyle, styleFor } from "../../kernel/render/style.js";
3
+ import { emitReference } from "../../kernel/spec/emitReference.js";
4
+ import { capabilities } from "../index.js";
5
+ import { configShowFormatters, renderConfigShowPlain } from "./show.render.js";
6
+ import type { ConfigShowData } from "./types.js";
7
+
8
+ const DATA: ConfigShowData = {
9
+ config: {
10
+ tier: "apps/lxd",
11
+ channel: "normal",
12
+ detail: "standard",
13
+ packs: ["@canonical/ds"],
14
+ },
15
+ origins: {
16
+ tier: "project",
17
+ channel: "default",
18
+ detail: "default",
19
+ packs: "global",
20
+ stories: "default",
21
+ prefixes: "default",
22
+ },
23
+ globalConfigPath: "/home/u/.config/pragma/config.json",
24
+ projectConfigPath: "/repo/pragma.config.ts",
25
+ projectExists: true,
26
+ globalExists: true,
27
+ };
28
+
29
+ /** A sentinel styler — enabled, each color wrapped in a visible tag. */
30
+ const TAGGED: RenderStyle = {
31
+ enabled: true,
32
+ bold: (t) => `<b>${t}</b>`,
33
+ dim: (t) => `<d>${t}</d>`,
34
+ cyan: (t) => `<c>${t}</c>`,
35
+ green: (t) => `<g>${t}</g>`,
36
+ yellow: (t) => `<y>${t}</y>`,
37
+ };
38
+
39
+ describe("renderConfigShowPlain", () => {
40
+ it("is byte-identical to the plain form when the styler is disabled", () => {
41
+ expect(renderConfigShowPlain(DATA, styleFor(false))).toBe(
42
+ [
43
+ "tier: apps/lxd [project]",
44
+ "channel: normal",
45
+ "detail: standard",
46
+ "packs: @canonical/ds [global]",
47
+ "global config: /home/u/.config/pragma/config.json",
48
+ "project config: /repo/pragma.config.ts",
49
+ ].join("\n"),
50
+ );
51
+ });
52
+
53
+ it("aligns the key column and colorizes values on a TTY", () => {
54
+ const out = renderConfigShowPlain(DATA, TAGGED);
55
+ const keyWidth = "project config:".length;
56
+ // Dim, padded key; cyan value; dim `[layer]` marker.
57
+ expect(out).toContain(
58
+ `<d>${"tier:".padEnd(keyWidth)}</d> <c>apps/lxd</c><d> [project]</d>`,
59
+ );
60
+ // A default-origin value carries no marker.
61
+ expect(out).toContain(
62
+ `<d>${"channel:".padEnd(keyWidth)}</d> <c>normal</c>`,
63
+ );
64
+ expect(out).not.toContain("<d> [default]</d>");
65
+ });
66
+
67
+ it("renders absent optional fields as (none)", () => {
68
+ const bare: ConfigShowData = {
69
+ config: { channel: "normal" },
70
+ origins: {
71
+ tier: "default",
72
+ channel: "default",
73
+ detail: "default",
74
+ packs: "default",
75
+ stories: "default",
76
+ prefixes: "default",
77
+ },
78
+ globalConfigPath: "/home/u/.config/pragma/config.json",
79
+ projectExists: false,
80
+ globalExists: false,
81
+ };
82
+
83
+ const out = renderConfigShowPlain(bare, styleFor(false));
84
+
85
+ expect(out).toContain("tier: (none)");
86
+ expect(out).toContain("packs: (none)");
87
+ expect(out).toContain("project config: (not found)");
88
+ });
89
+ });
90
+
91
+ describe("configShowFormatters.llm", () => {
92
+ it("renders the layered rows with origin markers", () => {
93
+ const out = configShowFormatters.llm(DATA);
94
+
95
+ expect(out).toContain("- **Tier:** apps/lxd [project]");
96
+ expect(out).toContain("- **Packs:** @canonical/ds [global]");
97
+ });
98
+
99
+ it("reports no identity row, in either form", () => {
100
+ // Identity is read statically from `pragma.conf.ts`; reporting a layer for
101
+ // it was reporting a provenance the kernel does not honour. Both renderers
102
+ // are asserted, because they list their rows independently.
103
+ for (const out of [
104
+ renderConfigShowPlain(DATA, styleFor(false)),
105
+ configShowFormatters.llm(DATA),
106
+ ]) {
107
+ for (const field of ["name", "help", "colophon", "issues"]) {
108
+ expect(out.toLowerCase()).not.toContain(`${field}:`);
109
+ }
110
+ }
111
+ });
112
+ });
113
+
114
+ describe("the generated configuration reference", () => {
115
+ it("names exactly the fields this renderer reports with a layer", () => {
116
+ // The page and the renderer are written independently — the page is kernel
117
+ // prose, the renderer a capability — and the page's account of the renderer
118
+ // was wrong in both directions: it claimed every field but `completion`
119
+ // carried a printed provenance, while `prefixes`, `stories` and the four
120
+ // distribution-only fields do not, and it claimed `completion` was not
121
+ // printed, while `--format json` carries it.
122
+ //
123
+ // DERIVED from the real formatter: the field rows are the ones before the
124
+ // two PATH rows, which are not fields at all. Adding a row here without
125
+ // saying so on the page, or naming a field there this renderer drops, fails.
126
+ const printed = renderConfigShowPlain(DATA, styleFor(false))
127
+ .split("\n")
128
+ .map((line) => line.slice(0, line.indexOf(":")));
129
+ const fields = printed.slice(0, printed.indexOf("global config"));
130
+ expect(fields.length).toBeGreaterThan(0);
131
+
132
+ const page = emitReference(capabilities).get("config.md") ?? "";
133
+ expect(page).toContain(
134
+ `${fields.map((field) => `\`${field}\``).join(", ")} — those and only those —`,
135
+ );
136
+ });
137
+ });
@@ -7,6 +7,7 @@
7
7
  */
8
8
 
9
9
  import type { ConfigOrigin } from "../../kernel/config/types.js";
10
+ import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
10
11
  import type { Formatters } from "../../kernel/spec/types.js";
11
12
  import type { ConfigShowData } from "./types.js";
12
13
 
@@ -15,27 +16,67 @@ function originMarker(origin: ConfigOrigin): string {
15
16
  return origin === "default" ? "" : ` [${origin}]`;
16
17
  }
17
18
 
18
- /** Summarize the `packages` list as a comma-separated set of names. */
19
- function packageNames(data: ConfigShowData): string {
20
- const packages = data.config.packages ?? [];
21
- const names = packages.map((entry) =>
19
+ /** Summarize a list of named entries as a comma-separated set of names. */
20
+ function entryNames(entries: readonly (string | { name: string })[]): string {
21
+ const names = entries.map((entry) =>
22
22
  typeof entry === "string" ? entry : entry.name,
23
23
  );
24
24
  return names.length > 0 ? names.join(", ") : "(none)";
25
25
  }
26
26
 
27
+ /** One resolved row: its label, value, and `[layer]` marker (blank for a default). */
28
+ type ConfigRow = readonly [label: string, value: string, marker: string];
29
+
30
+ /** The resolved rows, in display order, shared by the plain + beautified forms. */
31
+ function configRows(data: ConfigShowData): readonly ConfigRow[] {
32
+ const { config, origins } = data;
33
+ return [
34
+ ["tier", config.tier ?? "(none)", originMarker(origins.tier)],
35
+ ["channel", config.channel, originMarker(origins.channel)],
36
+ ["detail", config.detail ?? "standard", originMarker(origins.detail)],
37
+ ["packs", entryNames(config.packs ?? []), originMarker(origins.packs)],
38
+ [
39
+ "global config",
40
+ `${data.globalConfigPath}${data.globalExists ? "" : " (not found)"}`,
41
+ "",
42
+ ],
43
+ ["project config", data.projectConfigPath ?? "(not found)", ""],
44
+ ];
45
+ }
46
+
47
+ /**
48
+ * Render `config show` as plain text.
49
+ *
50
+ * @param data - The resolved config-show payload.
51
+ * @param style - TTY styling; defaults to the process style. On a color-capable
52
+ * terminal the `key:` column is aligned + dim, values cyan, and `[layer]`
53
+ * markers dim; off a TTY the styler is inert, so the output is byte-identical
54
+ * to the pre-beautify `key: value[marker]` form.
55
+ * @returns The formatted config block.
56
+ */
57
+ export function renderConfigShowPlain(
58
+ data: ConfigShowData,
59
+ style: RenderStyle = defaultStyle(),
60
+ ): string {
61
+ const rows = configRows(data);
62
+ if (!style.enabled) {
63
+ return rows
64
+ .map(([label, value, marker]) => `${label}: ${value}${marker}`)
65
+ .join("\n");
66
+ }
67
+ const keyWidth = Math.max(...rows.map(([label]) => `${label}:`.length));
68
+ return rows
69
+ .map(([label, value, marker]) => {
70
+ const key = style.dim(`${label}:`.padEnd(keyWidth));
71
+ const tail = marker ? style.dim(marker) : "";
72
+ return `${key} ${style.cyan(value)}${tail}`;
73
+ })
74
+ .join("\n");
75
+ }
76
+
27
77
  export const configShowFormatters: Formatters<ConfigShowData> = {
28
78
  plain(data) {
29
- const { config, origins } = data;
30
- const lines = [
31
- `tier: ${config.tier ?? "(none — all tiers visible)"}${originMarker(origins.tier)}`,
32
- `channel: ${config.channel}${originMarker(origins.channel)}`,
33
- `detail: ${config.detail ?? "standard"}${originMarker(origins.detail)}`,
34
- `packages: ${packageNames(data)}${originMarker(origins.packages)}`,
35
- `global config: ${data.globalConfigPath}${data.globalExists ? "" : " (not found)"}`,
36
- `project config: ${data.projectConfigPath ?? "(not found)"}`,
37
- ];
38
- return lines.join("\n");
79
+ return renderConfigShowPlain(data);
39
80
  },
40
81
 
41
82
  llm(data) {
@@ -43,10 +84,10 @@ export const configShowFormatters: Formatters<ConfigShowData> = {
43
84
  const lines = [
44
85
  "## Configuration",
45
86
  "",
46
- `- **Tier:** ${config.tier ?? "none (all tiers)"}${originMarker(origins.tier)}`,
87
+ `- **Tier:** ${config.tier ?? "none"}${originMarker(origins.tier)}`,
47
88
  `- **Channel:** ${config.channel}${originMarker(origins.channel)}`,
48
89
  `- **Detail:** ${config.detail ?? "standard"}${originMarker(origins.detail)}`,
49
- `- **Packages:** ${packageNames(data)}${originMarker(origins.packages)}`,
90
+ `- **Packs:** ${entryNames(config.packs ?? [])}${originMarker(origins.packs)}`,
50
91
  `- **Global config:** \`${data.globalConfigPath}\``,
51
92
  ];
52
93
  if (data.projectConfigPath) {
@@ -5,9 +5,9 @@
5
5
  * later PR — this ships only the reader.
6
6
  */
7
7
 
8
+ import { BIN_NAME, PROJECT_CONFIG_FILENAME } from "../../constants.js";
8
9
  import { asVerb } from "../../kernel/spec/asVerb.js";
9
10
  import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
10
- import { configFieldVerbs } from "./fields.js";
11
11
  import { configSetVerb } from "./set.verb.js";
12
12
  import { configShowFormatters } from "./show.render.js";
13
13
  import type { ConfigShowData } from "./types.js";
@@ -15,12 +15,12 @@ import type { ConfigShowData } from "./types.js";
15
15
  const showVerb: VerbSpec<Record<string, unknown>, ConfigShowData> = {
16
16
  path: ["config", "show"],
17
17
  summary: "Show the resolved config and per-field provenance.",
18
- doc: "Merges built-in defaults, the global XDG config, and the nearest pragma.config.ts, marking which layer supplied each value.",
18
+ doc: `Merges built-in defaults, the global XDG config, and the nearest ${PROJECT_CONFIG_FILENAME}, marking which layer supplied each value.`,
19
19
  params: [],
20
20
  output: { formatters: configShowFormatters },
21
21
  examples: [
22
- { cmd: "pragma config show" },
23
- { cmd: "pragma config show --format json" },
22
+ { cmd: `${BIN_NAME} config show` },
23
+ { cmd: `${BIN_NAME} config show --format json` },
24
24
  ],
25
25
  capability: {
26
26
  needsStore: false,
@@ -35,11 +35,12 @@ const showVerb: VerbSpec<Record<string, unknown>, ConfigShowData> = {
35
35
  };
36
36
 
37
37
  /**
38
- * The `config` capability module: the `show` reader (PR1), the storeless
39
- * `tier`/`channel`/`detail` setters (PR6), and the additive one-command `set`
40
- * verb (PR9), emitted in covenant order.
38
+ * The `config` capability module: the `show` reader (PR1) and the one-command
39
+ * `set <key> <value>` setter (PR9), emitted in covenant order. The per-field
40
+ * `tier`/`channel`/`detail` verbs were retired in favour of `config set`
41
+ * (AV-228 B3).
41
42
  */
42
43
  export const configModule: CapabilityModule = {
43
44
  name: "config",
44
- verbs: [asVerb(showVerb), ...configFieldVerbs, configSetVerb],
45
+ verbs: [asVerb(showVerb), configSetVerb],
45
46
  };
@@ -6,6 +6,14 @@ import type { ConfigOrigins, PragmaConfig } from "../../kernel/config/types.js";
6
6
 
7
7
  /** The resolved configuration plus per-field provenance and layer locations. */
8
8
  export interface ConfigShowData {
9
+ /**
10
+ * The effective config, MINUS declared story bodies: `packs` entries carry
11
+ * `{ name, source }` only and the top-level `stories` array is omitted. The
12
+ * bodies are SPARQL, and MCP returns the JSON formatter's output verbatim, so
13
+ * carrying them would put ~2.7k tokens of query text in every `config_show`
14
+ * call. `origins` still reports where `packs`/`stories` came from, and
15
+ * `pragma capabilities` lists the verbs those stories produce.
16
+ */
9
17
  readonly config: PragmaConfig;
10
18
  readonly origins: ConfigOrigins;
11
19
  readonly projectConfigPath?: string;
@@ -20,7 +20,7 @@ import { describe, expect, it } from "vitest";
20
20
 
21
21
  const here = dirname(fileURLToPath(import.meta.url));
22
22
  const repoRoot = resolve(here, "../../../../../..");
23
- const pragmaBin = join(repoRoot, "packages/cli/next/src/bin.ts");
23
+ const pragmaBin = join(repoRoot, "packages/cli/pragma/src/bin.ts");
24
24
  const summonCore = join(repoRoot, "packages/summon/core/dist/esm/index.js");
25
25
  const pickGen = join(here, "pickGenerator.ts");
26
26
  const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-xbin-"));
@@ -1,29 +1,42 @@
1
1
  /**
2
- * PROTECTED — the permanent seam contract: every `create` tree is produced TWO
3
- * ways and diffed byte-for-byte (path set + contents, incl. the "Generated by"
4
- * stamp): (1) `pragma create <verb> --yes` through the kernel dispatcher, and
5
- * (2) summon-core `execute` with `autoPrompt`, run through `runGeneratorTask` +
6
- * the shared stamp — the summon bin's path. They match because both derive the
7
- * StampConfig from `createGeneratorStamp` (displayName + version) and run the
8
- * SAME `generator.generate` with the SAME answers.
2
+ * PROTECTED — pragma's side of the byte-equality seam contract: every `create`
3
+ * tree is produced TWO ways and diffed byte-for-byte (path set + contents, incl.
4
+ * the "Generated by" stamp): (1) `pragma create <verb> --yes` through the kernel
5
+ * dispatcher, and (2) the REFERENCE — summon-core `execute` with `autoPrompt`,
6
+ * run through `runGeneratorTask` + the shared stamp. They match because both
7
+ * derive the StampConfig from `createGeneratorStamp` (displayName + version) and
8
+ * run the SAME `generator.generate` with the SAME answers.
9
+ *
10
+ * The reference producer, the tree snapshot/diff, and the answer sets are NOT
11
+ * defined here any more: they are `@canonical/summon-core/testing`, the named
12
+ * conformance suite both bins run (L-OPEN-10). This file supplies what only
13
+ * pragma can — its kernel dispatch path, and the mapping from a fixture's
14
+ * logical generator id to the bundled generator plus the CLI param names the
15
+ * `create` verbs take. No new package edge: pragma already depends on
16
+ * summon-core, and never on the summon bin.
9
17
  *
10
18
  * Five generators are covered: component × {react, svelte, lit}, package, and
11
19
  * application. A cross-binary subprocess guard lives in byteEquality.subprocess.test.ts.
12
20
  */
13
21
 
14
- import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
22
+ import { mkdtempSync } from "node:fs";
15
23
  import { tmpdir } from "node:os";
16
24
  import { join } from "node:path";
25
+ import {
26
+ CONFORMANCE_FIXTURES,
27
+ diffTrees,
28
+ formatTreeDiff,
29
+ isIdentical,
30
+ produceReference,
31
+ snapshotTree,
32
+ type TreeSnapshot,
33
+ } from "@canonical/summon-core/testing";
17
34
  import { describe, expect, it } from "vitest";
18
35
  import { executeVerb } from "../../kernel/project/cli/dispatch.js";
19
36
  import { bootRuntime } from "../../kernel/runtime/boot.js";
20
37
  import type { GlobalFlags } from "../../kernel/runtime/types.js";
21
38
  import type { VerbSpec } from "../../kernel/spec/types.js";
22
- import {
23
- createApplicationVerb,
24
- createComponentVerb,
25
- createPackageVerb,
26
- } from "./create.verb.js";
39
+ import { createVerbs } from "./create.verb.js";
27
40
  import type { CreateKind } from "./types.js";
28
41
 
29
42
  const FLAGS: GlobalFlags = {
@@ -34,39 +47,17 @@ const FLAGS: GlobalFlags = {
34
47
  };
35
48
  const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-golden-"));
36
49
 
37
- /** Read a directory tree into a sorted map of relative path → contents. */
38
- function snapshot(dir: string): Map<string, string> {
39
- const out = new Map<string, string>();
40
- const walk = (d: string, base: string): void => {
41
- for (const entry of readdirSync(d, { withFileTypes: true }).sort((a, b) =>
42
- a.name.localeCompare(b.name),
43
- )) {
44
- const rel = base ? `${base}/${entry.name}` : entry.name;
45
- if (entry.isDirectory()) walk(join(d, entry.name), rel);
46
- else out.set(rel, readFileSync(join(d, entry.name), "utf-8"));
47
- }
48
- };
49
- walk(dir, "");
50
- return out;
51
- }
52
-
53
- const verbFor: Record<CreateKind, VerbSpec> = {
54
- component: createComponentVerb as VerbSpec,
55
- package: createPackageVerb as VerbSpec,
56
- application: createApplicationVerb as VerbSpec,
57
- };
58
-
59
50
  /** Producer (1): the full pragma kernel path — dispatcher runs the create verb. */
60
51
  async function producePragma(
61
52
  kind: CreateKind,
62
53
  params: Record<string, unknown>,
63
- ): Promise<Map<string, string>> {
54
+ ): Promise<TreeSnapshot> {
64
55
  const dir = freshCwd();
65
56
  const prev = process.cwd();
66
57
  process.chdir(dir);
67
58
  try {
68
59
  const outcome = await executeVerb(
69
- verbFor[kind],
60
+ createVerbs[kind] as VerbSpec,
70
61
  params,
71
62
  { dryRun: false, undo: false, yes: true },
72
63
  bootRuntime(FLAGS, dir),
@@ -75,44 +66,38 @@ async function producePragma(
75
66
  } finally {
76
67
  process.chdir(prev);
77
68
  }
78
- return snapshot(dir);
69
+ return snapshotTree(dir);
79
70
  }
80
71
 
81
- /** Producer (2): summon-core execute + autoPrompt + the shared stamp core. */
72
+ /**
73
+ * Producer (2): the shared conformance REFERENCE, over pragma's own generator.
74
+ *
75
+ * The fixture names its generator logically; `pickGenerator` is what turns that
76
+ * into the bundled definition. The suite never imports a generator itself —
77
+ * summon-core sits below both bins.
78
+ */
82
79
  async function produceSummon(
83
80
  kind: CreateKind,
84
81
  framework: string,
85
- answers: Record<string, unknown>,
86
- ): Promise<Map<string, string>> {
82
+ answers: Readonly<Record<string, unknown>>,
83
+ ): Promise<TreeSnapshot> {
87
84
  const { pickGenerator } = await import("./pickGenerator.js");
88
- const {
89
- execute,
90
- autoPrompt,
91
- runGeneratorTask,
92
- createGeneratorStamp,
93
- createStampOnEffectStart,
94
- } = await import("@canonical/summon-core");
95
- const generator = pickGenerator(kind, { framework });
96
- const dir = freshCwd();
97
- await runGeneratorTask(
98
- execute(generator, { prompt: autoPrompt(answers), params: answers }),
99
- {
100
- cwd: dir,
101
- promptHandler: autoPrompt(answers),
102
- onEffectStart: createStampOnEffectStart(createGeneratorStamp(generator)),
103
- onLog: () => {},
104
- },
105
- );
106
- return snapshot(dir);
85
+ return produceReference({
86
+ generator: pickGenerator(kind, { framework }),
87
+ answers,
88
+ });
107
89
  }
108
90
 
109
- /** One golden case: the create-verb params, and the equivalent summon answers. */
91
+ /**
92
+ * One golden case: the shared fixture, plus what only pragma knows — which
93
+ * bundled generator the fixture's id names, and the CLI param names the create
94
+ * verb takes for the same answers.
95
+ */
110
96
  interface Case {
111
- readonly name: string;
97
+ readonly fixture: string;
112
98
  readonly kind: CreateKind;
113
99
  readonly framework: string;
114
100
  readonly params: Record<string, unknown>;
115
- readonly answers: Record<string, unknown>;
116
101
  }
117
102
 
118
103
  const componentAnswers = {
@@ -123,7 +108,7 @@ const componentAnswers = {
123
108
 
124
109
  const cases: Case[] = [
125
110
  {
126
- name: "component/react",
111
+ fixture: "component/react",
127
112
  kind: "component",
128
113
  framework: "react",
129
114
  params: {
@@ -131,10 +116,9 @@ const cases: Case[] = [
131
116
  componentPath: "src/components/Button",
132
117
  ...componentAnswers,
133
118
  },
134
- answers: { componentPath: "src/components/Button", ...componentAnswers },
135
119
  },
136
120
  {
137
- name: "component/svelte",
121
+ fixture: "component/svelte",
138
122
  kind: "component",
139
123
  framework: "svelte",
140
124
  params: {
@@ -142,10 +126,9 @@ const cases: Case[] = [
142
126
  componentPath: "src/lib/Button",
143
127
  ...componentAnswers,
144
128
  },
145
- answers: { componentPath: "src/lib/Button", ...componentAnswers },
146
129
  },
147
130
  {
148
- name: "component/lit",
131
+ fixture: "component/lit",
149
132
  kind: "component",
150
133
  framework: "lit",
151
134
  params: {
@@ -153,10 +136,9 @@ const cases: Case[] = [
153
136
  componentPath: "src/lib/Button",
154
137
  ...componentAnswers,
155
138
  },
156
- answers: { componentPath: "src/lib/Button", ...componentAnswers },
157
139
  },
158
140
  {
159
- name: "package",
141
+ fixture: "package",
160
142
  kind: "package",
161
143
  framework: "",
162
144
  params: {
@@ -169,54 +151,53 @@ const cases: Case[] = [
169
151
  withPrTemplate: false,
170
152
  runInstall: false,
171
153
  },
172
- answers: {
173
- name: "@canonical/my-lib",
174
- type: "library",
175
- description: "A library.",
176
- withReact: false,
177
- withStorybook: false,
178
- withCli: false,
179
- withPrTemplate: false,
180
- runInstall: false,
181
- },
182
154
  },
183
155
  {
184
- name: "application",
156
+ fixture: "application",
185
157
  kind: "application",
186
158
  framework: "",
159
+ // The pragma path receives the CLI `--with-X` params (AV-228 B8); the shared
160
+ // fixture carries the generator's bare prompt names. runCreate's boundary
161
+ // remap (`withSsr` -> `ssr`, ...) makes both produce the identical tree —
162
+ // which is exactly what this case exists to prove.
187
163
  params: {
188
164
  appPath: "my-app",
189
- ssr: true,
190
- router: true,
191
- forms: true,
192
- relay: false,
193
- runInstall: false,
194
- },
195
- answers: {
196
- appPath: "my-app",
197
- ssr: true,
198
- router: true,
199
- forms: true,
200
- relay: false,
165
+ withSsr: true,
166
+ withRouter: true,
167
+ withForms: true,
168
+ withRelay: false,
201
169
  runInstall: false,
202
170
  },
203
171
  },
204
172
  ];
205
173
 
206
- describe("byte-equality goldens — pragma create ≡ summon execute (PROTECTED)", () => {
174
+ describe("byte-equality goldens — pragma create \u2261 the conformance reference (PROTECTED)", () => {
175
+ it("covers every shared conformance fixture", () => {
176
+ // The suite's fixture list is the contract; a fixture added there and not
177
+ // wired here would silently go unchecked on pragma's side.
178
+ expect(cases.map((entry) => entry.fixture)).toEqual(
179
+ CONFORMANCE_FIXTURES.map((entry) => entry.name),
180
+ );
181
+ });
182
+
207
183
  for (const testCase of cases) {
208
- it(`${testCase.name}: both producers write byte-identical trees`, async () => {
184
+ it(`${testCase.fixture}: the kernel path matches the reference byte for byte`, async () => {
185
+ const shared = CONFORMANCE_FIXTURES.find(
186
+ (entry) => entry.name === testCase.fixture,
187
+ );
188
+ if (!shared) throw new Error(`no fixture "${testCase.fixture}"`);
209
189
  const pragma = await producePragma(testCase.kind, testCase.params);
210
- const summon = await produceSummon(
190
+ const reference = await produceSummon(
211
191
  testCase.kind,
212
192
  testCase.framework,
213
- testCase.answers,
193
+ shared.answers,
214
194
  );
215
195
  expect(pragma.size).toBeGreaterThan(0);
216
- expect([...pragma.keys()].sort()).toEqual([...summon.keys()].sort());
217
- for (const [path, content] of pragma) {
218
- expect(summon.get(path), `content of ${path}`).toBe(content);
219
- }
196
+ const diff = diffTrees(pragma, reference);
197
+ expect(
198
+ isIdentical(diff),
199
+ formatTreeDiff(diff, "pragma create", "the conformance reference"),
200
+ ).toBe(true);
220
201
  });
221
202
  }
222
203
  });