@canonical/pragma-cli 0.33.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 (250) hide show
  1. package/README.md +7 -7
  2. package/dist/pragma +0 -0
  3. package/package.json +18 -15
  4. package/pragma.conf.ts +664 -0
  5. package/src/bin.ts +13 -4
  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.verb.ts +31 -13
  9. package/src/capabilities/capabilities/catalog.ts +18 -10
  10. package/src/capabilities/capabilities/hints.ts +13 -19
  11. package/src/capabilities/colophon/collectColophon.ts +34 -19
  12. package/src/capabilities/colophon/colophon.verb.ts +8 -4
  13. package/src/capabilities/colophon/types.ts +16 -7
  14. package/src/capabilities/config/collectConfigShow.test.ts +57 -0
  15. package/src/capabilities/config/collectConfigShow.ts +41 -4
  16. package/src/capabilities/config/fields.ts +0 -27
  17. package/src/capabilities/config/set.verb.ts +7 -3
  18. package/src/capabilities/config/show.render.test.ts +77 -5
  19. package/src/capabilities/config/show.render.ts +7 -12
  20. package/src/capabilities/config/show.verb.ts +4 -3
  21. package/src/capabilities/config/types.ts +8 -0
  22. package/src/capabilities/create/byteEquality.test.ts +78 -100
  23. package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
  24. package/src/capabilities/create/constants.ts +75 -0
  25. package/src/capabilities/create/create.mcp.test.ts +2 -2
  26. package/src/capabilities/create/create.test.ts +162 -15
  27. package/src/capabilities/create/create.verb.ts +140 -69
  28. package/src/capabilities/create/index.ts +2 -10
  29. package/src/capabilities/create/pickGenerator.ts +34 -28
  30. package/src/capabilities/create/templates.embedded.generated.ts +4 -29
  31. package/src/capabilities/create/types.ts +10 -6
  32. package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
  33. package/src/capabilities/distribution.test.ts +120 -0
  34. package/src/capabilities/distribution.ts +59 -0
  35. package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
  36. package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
  37. package/src/capabilities/doctor/checks/checkMcpConfigured.ts +7 -6
  38. package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
  39. package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
  40. package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
  41. package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
  42. package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
  43. package/src/capabilities/doctor/doctor.render.test.ts +6 -6
  44. package/src/capabilities/doctor/doctor.render.ts +2 -1
  45. package/src/capabilities/doctor/doctor.test.ts +37 -8
  46. package/src/capabilities/doctor/doctor.verb.ts +6 -2
  47. package/src/capabilities/doctor/runChecks.ts +5 -5
  48. package/src/capabilities/doctor/types.ts +1 -1
  49. package/src/capabilities/emptyRecovery.test.ts +32 -15
  50. package/src/capabilities/graph/inspect.verb.ts +3 -2
  51. package/src/capabilities/graph/query.verb.ts +3 -2
  52. package/src/capabilities/graph/runQuery.ts +8 -5
  53. package/src/capabilities/index.ts +22 -18
  54. package/src/capabilities/info/collectInfo.ts +10 -6
  55. package/src/capabilities/info/info.render.ts +3 -2
  56. package/src/capabilities/info/info.verb.ts +3 -2
  57. package/src/capabilities/lazy.test.ts +93 -2
  58. package/src/capabilities/ontology/verbs.ts +21 -21
  59. package/src/capabilities/prompt/prompt.test.ts +12 -6
  60. package/src/capabilities/prompt/verbs.ts +24 -11
  61. package/src/capabilities/resources/provider.ts +30 -11
  62. package/src/capabilities/resources/resources.test.ts +85 -29
  63. package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
  64. package/src/capabilities/setup/operations/setupMcp.ts +2 -2
  65. package/src/capabilities/setup/operations/setupSkills.ts +3 -3
  66. package/src/capabilities/setup/setup.verb.ts +12 -5
  67. package/src/capabilities/setup/shell.ts +29 -3
  68. package/src/capabilities/skill/discover.ts +11 -5
  69. package/src/capabilities/skill/render.ts +4 -5
  70. package/src/capabilities/skill/verbs.ts +7 -7
  71. package/src/capabilities/sources/collectStatus.ts +55 -50
  72. package/src/capabilities/sources/index.ts +1 -1
  73. package/src/capabilities/sources/installSkills.ts +2 -2
  74. package/src/capabilities/sources/runUpdate.ts +113 -122
  75. package/src/capabilities/sources/sources.test.ts +111 -117
  76. package/src/capabilities/sources/status.render.test.ts +53 -24
  77. package/src/capabilities/sources/status.render.ts +24 -29
  78. package/src/capabilities/sources/status.verb.ts +15 -8
  79. package/src/capabilities/sources/types.ts +18 -25
  80. package/src/capabilities/sources/update.render.ts +9 -2
  81. package/src/capabilities/sources/update.verb.ts +12 -23
  82. package/src/capabilities/surface.test.ts +33 -10
  83. package/src/capabilities/tier.parity.test.ts +144 -0
  84. package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
  85. package/src/constants.test.ts +8 -6
  86. package/src/constants.ts +47 -21
  87. package/src/identity.test.ts +333 -0
  88. package/src/index.ts +2 -2
  89. package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
  90. package/src/kernel/completion/emitScripts.test.ts +1 -1
  91. package/src/kernel/completion/emitScripts.ts +4 -2
  92. package/src/kernel/completion/entitySource.test.ts +185 -41
  93. package/src/kernel/completion/entitySource.ts +117 -107
  94. package/src/kernel/completion/model.test.ts +1 -1
  95. package/src/kernel/completion/model.ts +11 -0
  96. package/src/kernel/completion/safety.test.ts +82 -36
  97. package/src/kernel/completion/shellDrive.test.ts +592 -0
  98. package/src/kernel/completion/templates/fish.ts +28 -6
  99. package/src/kernel/completion/types.ts +3 -3
  100. package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
  101. package/src/kernel/config/defaults.test.ts +85 -0
  102. package/src/kernel/config/defaults.ts +12 -23
  103. package/src/kernel/config/evaluateProjectConfig.ts +2 -3
  104. package/src/kernel/config/findProjectConfig.ts +4 -3
  105. package/src/kernel/config/firstRun.ts +12 -5
  106. package/src/kernel/config/globalConfig.ts +2 -2
  107. package/src/kernel/config/index.ts +4 -5
  108. package/src/kernel/config/paths.ts +17 -17
  109. package/src/kernel/config/readConfig.test.ts +242 -6
  110. package/src/kernel/config/readConfig.ts +8 -8
  111. package/src/kernel/config/schema.test.ts +50 -0
  112. package/src/kernel/config/schema.ts +97 -10
  113. package/src/kernel/config/types.ts +73 -19
  114. package/src/kernel/copy.test.ts +453 -0
  115. package/src/kernel/error/PragmaError.test.ts +1 -1
  116. package/src/kernel/error/constants.ts +3 -2
  117. package/src/kernel/error/index.ts +1 -1
  118. package/src/kernel/error/recovery.test.ts +22 -45
  119. package/src/kernel/error/recovery.ts +22 -27
  120. package/src/kernel/error/types.ts +5 -4
  121. package/src/kernel/packs/collect.test.ts +195 -11
  122. package/src/kernel/packs/collect.ts +213 -58
  123. package/src/kernel/packs/compile.test.ts +163 -7
  124. package/src/kernel/packs/compile.ts +15 -12
  125. package/src/kernel/packs/disclosure.test.ts +5 -2
  126. package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
  127. package/src/kernel/packs/graphqlSource.test.ts +6 -3
  128. package/src/kernel/packs/iri.ts +5 -7
  129. package/src/kernel/packs/renderPack.ts +5 -3
  130. package/src/kernel/packs/resolveEntity.ts +21 -17
  131. package/src/kernel/packs/runBodies.ts +15 -14
  132. package/src/kernel/packs/schema.ts +96 -10
  133. package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
  134. package/src/kernel/packs/sparql/runSelect.ts +73 -34
  135. package/src/kernel/packs/toolDescription.test.ts +10 -2
  136. package/src/kernel/packs/types.ts +39 -4
  137. package/src/kernel/packs/uniqueness.ts +4 -4
  138. package/src/kernel/project/cli/buildProgram.test.ts +9 -4
  139. package/src/kernel/project/cli/dispatch.test.ts +67 -0
  140. package/src/kernel/project/cli/dispatch.ts +34 -12
  141. package/src/kernel/project/cli/helpFormat.ts +1 -1
  142. package/src/kernel/project/cli/rootHelp.ts +105 -105
  143. package/src/kernel/project/mcp/buildServer.ts +11 -6
  144. package/src/kernel/project/mcp/instructions.ts +11 -6
  145. package/src/kernel/project/mcp/parity.test.ts +39 -1
  146. package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
  147. package/src/kernel/project/mcp/prompts/provider.ts +22 -8
  148. package/src/kernel/project/mcp/prompts/source.ts +68 -67
  149. package/src/kernel/project/mcp/registerVerb.ts +25 -6
  150. package/src/kernel/project/mcp/serve.ts +11 -3
  151. package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
  152. package/src/kernel/render/disclosure.ts +4 -2
  153. package/src/kernel/render/prefixes.test.ts +36 -0
  154. package/src/kernel/render/prefixes.ts +40 -13
  155. package/src/kernel/render/renderers.test.ts +1 -1
  156. package/src/kernel/runtime/graphpack/build.ts +29 -3
  157. package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
  158. package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
  159. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
  160. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
  161. package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
  162. package/src/kernel/runtime/graphpack/embedded.ts +37 -11
  163. package/src/kernel/runtime/graphpack/graphpack.test.ts +205 -5
  164. package/src/kernel/runtime/graphpack/hash.ts +3 -2
  165. package/src/kernel/runtime/graphpack/index.ts +1 -1
  166. package/src/kernel/runtime/graphpack/manifest.ts +114 -12
  167. package/src/kernel/runtime/graphpack/read.ts +4 -9
  168. package/src/kernel/runtime/graphpack/schemas.ts +74 -0
  169. package/src/kernel/runtime/graphpack/stories.ts +84 -0
  170. package/src/kernel/runtime/graphpack/types.ts +61 -48
  171. package/src/kernel/runtime/paths.ts +64 -15
  172. package/src/kernel/runtime/probe.test.ts +8 -4
  173. package/src/kernel/runtime/readEntity.ts +6 -5
  174. package/src/kernel/runtime/refs/gitOps.ts +1 -19
  175. package/src/kernel/runtime/refs/parseRef.ts +7 -7
  176. package/src/kernel/runtime/refs/resolve.ts +78 -62
  177. package/src/kernel/runtime/resolveSources.test.ts +63 -24
  178. package/src/kernel/runtime/resolveSources.ts +36 -25
  179. package/src/kernel/runtime/storeReadiness.ts +9 -8
  180. package/src/kernel/runtime/storeless.test.ts +1 -1
  181. package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
  182. package/src/kernel/spec/emitReference.test.ts +3 -3
  183. package/src/kernel/spec/emitReference.ts +144 -11
  184. package/src/kernel/spec/emitSurface.ts +9 -5
  185. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  186. package/src/kernel/spec/surfaceConformance.ts +8 -0
  187. package/src/kernel/spec/types.ts +29 -9
  188. package/src/kernel/spec/validate.ts +2 -1
  189. package/src/kernel/vocabulary.test.ts +164 -0
  190. package/src/kernel/vocabulary.ts +146 -0
  191. package/src/testing/PARITY_GAPS.ts +8 -2
  192. package/src/testing/behavioral/README.md +3 -3
  193. package/src/testing/behavioral/completion.test.ts +19 -6
  194. package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
  195. package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
  196. package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
  197. package/src/testing/behavioral/harness.foundation.test.ts +14 -5
  198. package/src/testing/behavioral/journeys.cli.test.ts +116 -75
  199. package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
  200. package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
  201. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
  202. package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
  203. package/src/testing/behavioral/liveReadSurface.ts +4 -3
  204. package/src/testing/behavioral/mcpSurface.test.ts +9 -4
  205. package/src/testing/behavioral/staleCommands.test.ts +10 -0
  206. package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
  207. package/src/testing/eval/cases/assembly.ts +13 -6
  208. package/src/testing/eval/cases/effectful.ts +25 -13
  209. package/src/testing/eval/cases/readNouns.ts +22 -20
  210. package/src/testing/eval/cases/stable.ts +5 -1
  211. package/src/testing/eval/eval.test.ts +7 -1
  212. package/src/testing/fixtures/fixtureCapability.ts +45 -1
  213. package/src/testing/fixtures/graph/canonical.ts +6 -3
  214. package/src/testing/fixtures/graph/defaultPack.ts +6 -5
  215. package/src/testing/fixtures/graph/owlExport.ts +7 -5
  216. package/src/testing/helpers/fixtureGraph.ts +33 -16
  217. package/src/testing/helpers/packRuntime.ts +2 -3
  218. package/src/testing/helpers/projectCli.ts +2 -1
  219. package/src/testing/helpers/projectMcp.ts +6 -0
  220. package/src/testing/perf/budgets.test.ts +4 -0
  221. package/src/testing/perf/budgets.ts +22 -7
  222. package/src/testing/perf/globalSetup.ts +121 -6
  223. package/src/types.ts +2 -2
  224. package/src/capabilities/block/blockList.test.ts +0 -79
  225. package/src/capabilities/block/blockList.verb.ts +0 -163
  226. package/src/capabilities/block/index.ts +0 -25
  227. package/src/capabilities/block/pack.ts +0 -166
  228. package/src/capabilities/block/parity.test.ts +0 -187
  229. package/src/capabilities/block/tierChain.ts +0 -78
  230. package/src/capabilities/colophon/pragmaColophon.ts +0 -66
  231. package/src/capabilities/modifier/index.ts +0 -15
  232. package/src/capabilities/modifier/pack.ts +0 -64
  233. package/src/capabilities/standard/index.ts +0 -18
  234. package/src/capabilities/standard/pack.ts +0 -128
  235. package/src/capabilities/tier/index.ts +0 -19
  236. package/src/capabilities/tier/lookup.render.test.ts +0 -72
  237. package/src/capabilities/tier/lookup.render.ts +0 -59
  238. package/src/capabilities/tier/lookup.test.ts +0 -83
  239. package/src/capabilities/tier/lookup.verb.ts +0 -44
  240. package/src/capabilities/tier/pack.ts +0 -28
  241. package/src/capabilities/tier/runLookup.ts +0 -87
  242. package/src/capabilities/token/addConfig.render.ts +0 -27
  243. package/src/capabilities/token/addConfig.test.ts +0 -62
  244. package/src/capabilities/token/addConfig.verb.ts +0 -40
  245. package/src/capabilities/token/index.ts +0 -19
  246. package/src/capabilities/token/pack.ts +0 -58
  247. package/src/capabilities/token/runAddConfig.ts +0 -95
  248. package/src/kernel/completion/bashDrive.test.ts +0 -73
  249. package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
  250. package/src/kernel/runtime/lock.ts +0 -90
@@ -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,24 +151,15 @@ 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: "",
187
- // The pragma path receives the CLI `--with-X` params (AV-228 B8); the summon
188
- // path receives the generator's bare prompt names. runCreate's boundary remap
189
- // (`withSsr` -> `ssr`, ...) makes both produce the byte-identical tree.
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.
190
163
  params: {
191
164
  appPath: "my-app",
192
165
  withSsr: true,
@@ -195,31 +168,36 @@ const cases: Case[] = [
195
168
  withRelay: false,
196
169
  runInstall: false,
197
170
  },
198
- answers: {
199
- appPath: "my-app",
200
- ssr: true,
201
- router: true,
202
- forms: true,
203
- relay: false,
204
- runInstall: false,
205
- },
206
171
  },
207
172
  ];
208
173
 
209
- 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
+
210
183
  for (const testCase of cases) {
211
- 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}"`);
212
189
  const pragma = await producePragma(testCase.kind, testCase.params);
213
- const summon = await produceSummon(
190
+ const reference = await produceSummon(
214
191
  testCase.kind,
215
192
  testCase.framework,
216
- testCase.answers,
193
+ shared.answers,
217
194
  );
218
195
  expect(pragma.size).toBeGreaterThan(0);
219
- expect([...pragma.keys()].sort()).toEqual([...summon.keys()].sort());
220
- for (const [path, content] of pragma) {
221
- expect(summon.get(path), `content of ${path}`).toBe(content);
222
- }
196
+ const diff = diffTrees(pragma, reference);
197
+ expect(
198
+ isIdentical(diff),
199
+ formatTreeDiff(diff, "pragma create", "the conformance reference"),
200
+ ).toBe(true);
223
201
  });
224
202
  }
225
203
  });
@@ -24,7 +24,7 @@ import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
24
24
  import { tmpdir } from "node:os";
25
25
  import { dirname, join, resolve } from "node:path";
26
26
  import { fileURLToPath } from "node:url";
27
- import { beforeAll, describe, expect, it } from "vitest";
27
+ import { describe, expect, it } from "vitest";
28
28
 
29
29
  const here = dirname(fileURLToPath(import.meta.url));
30
30
  const repoRoot = resolve(here, "../../../../../..");
@@ -33,20 +33,11 @@ const pragmaBin = join(cliNextDir, "src/bin.ts");
33
33
  const compiledBin = join(cliNextDir, "dist/pragma");
34
34
  const freshCwd = (): string => mkdtempSync(join(tmpdir(), "pragma-compiled-"));
35
35
 
36
- // Build the standalone binary ONCE for every describe in this file (create +
37
- // the READ smoke share it), so the tests always exercise the current bundle +
38
- // embedded manifest rather than a stale `dist/pragma`.
39
- beforeAll(() => {
40
- const result = spawnSync("bun", ["run", "scripts/build.ts"], {
41
- cwd: cliNextDir,
42
- stdio: "pipe",
43
- });
44
- if (result.status !== 0) {
45
- throw new Error(
46
- `failed to build dist/pragma:\n${result.stderr?.toString() ?? ""}`,
47
- );
48
- }
49
- }, 180_000);
36
+ // The binary is provisioned by `testing/perf/globalSetup.ts`, which rebuilds it
37
+ // whenever it is missing or older than `src/**`, `scripts/**`, `pragma.conf.ts`
38
+ // or `package.json` — so these tests exercise the current bundle + embedded
39
+ // manifest without a second `beforeAll` writing `dist/pragma` in place while
40
+ // another worker's test is spawning it.
50
41
 
51
42
  /** Read a directory tree into a sorted map of relative path → contents. */
52
43
  function snapshot(dir: string): Map<string, string> {
@@ -108,6 +99,52 @@ describe("compiled pragma create component (PROTECTED)", () => {
108
99
  }
109
100
  });
110
101
 
102
+ /**
103
+ * PROTECTED — the compiled-binary `create` gate.
104
+ *
105
+ * `create component` runs from the binary (the describe above proves it byte for
106
+ * byte) because summon-component routes every template read through
107
+ * `loadTemplateSync` and passes `content:` into `template()`. `create package` /
108
+ * `create application` do NOT: they call `template({ source })`, summon-core
109
+ * falls through to `readFile(options.source)`, and the run dies with `ENOENT …
110
+ * /$bunfs/templates/package.json.ejs` AFTER `mkdir` has already run — a
111
+ * half-made package left on the user's disk. Measured against a real
112
+ * `dist/pragma` with the gate lifted, for both nouns.
113
+ *
114
+ * A `--dry-run` does NOT test this: it exits 0 without reading a template and
115
+ * merely PRINTS `Read file: /$bunfs/templates/package.json.ejs` as a planned
116
+ * effect. That false positive is why the gate has to be pinned by a real run.
117
+ *
118
+ * The `readdirSync(dir)` assertion is the load-bearing one: it fails with the
119
+ * real symptom if the gate is ever lifted without fixing the generators.
120
+ */
121
+ describe("compiled pragma create gate (PROTECTED)", () => {
122
+ // A LITERAL noun list, deliberately not derived from `create`'s own
123
+ // declaration: changing the gate's input must turn this red rather than
124
+ // silently drop the case.
125
+ for (const kind of ["package", "application"] as const) {
126
+ it(`refuses \`create ${kind}\` and leaves the cwd untouched`, () => {
127
+ const dir = freshCwd();
128
+ const result = spawnSync(compiledBin, ["create", kind, "--yes"], {
129
+ cwd: dir,
130
+ stdio: "pipe",
131
+ encoding: "utf-8",
132
+ });
133
+ const output = `${result.stdout ?? ""}${result.stderr ?? ""}`;
134
+ // 1, not merely non-zero: UNSUPPORTED is a runtime refusal, which
135
+ // `kernel/error/constants.ts` maps to the generic runtime exit.
136
+ expect(result.status).toBe(1);
137
+ expect(output).toContain("not available in the compiled pragma binary");
138
+ // The clean refusal, not the crash it exists to prevent.
139
+ expect(output).not.toMatch(/ENOENT/);
140
+ expect(output).not.toMatch(/Internal error/);
141
+ // And nothing half-made on disk: the generator's `mkdir` effects run
142
+ // BEFORE its first template read, so a lifted gate leaves a stub tree.
143
+ expect(readdirSync(dir)).toEqual([]);
144
+ }, 30_000);
145
+ }
146
+ });
147
+
111
148
  /**
112
149
  * PROTECTED — the compiled-binary READ guard (U1-orig).
113
150
  *
@@ -0,0 +1,75 @@
1
+ /**
2
+ * The generator bindings the `create` surface exposes — the one list the
3
+ * surface's mechanical copies derive from: the `CreateKind` union (`types.ts`),
4
+ * the `--framework` enum and the compiled-binary gate (`create.verb.ts`), the
5
+ * generator lookup (`pickGenerator.ts`), and the template roots the bundler
6
+ * embeds (`scripts/build.ts`). Prose is NOT derived: the verb summaries below
7
+ * `createVerbs` and `capabilities/hints.ts` still name the frameworks by hand.
8
+ *
9
+ * This table is the SINGLE authoring point for which generator packages the
10
+ * distribution ships: it binds each `create` noun to the generator-map key it
11
+ * runs, and — for the one generator whose templates the binary carries — to
12
+ * the package `scripts/build.ts` harvests them from. (A parallel `generators`
13
+ * CONFIG field once declared the same packages; it was validated, layered and
14
+ * read by nothing, and was removed under the L-OPEN-1 ruling — the config
15
+ * validator now rejects it loudly.) The binding is hand-written because
16
+ * neither half can be discovered:
17
+ * - surfacing a noun also needs a hand-written prompt mirror, path param and
18
+ * examples in `create.verb.ts`, so the surface is a deliberate SUBSET —
19
+ * `@canonical/summon-application` ships `application/react`, `domain`, `route`
20
+ * and `wrapper`, and `create` exposes one of them;
21
+ * - `bun build --compile` bundles only statically analysable import specifiers,
22
+ * so a shipped binary can never `import(name)` a declared package (measured:
23
+ * `Cannot find module '@canonical/summon-component' from '/$bunfs/root/…'`).
24
+ * `pickGenerator` must import all three statically.
25
+ *
26
+ * `create.test.ts` pins what is checkable: every binding resolves to the
27
+ * generator it names, and the embedded manifest carries only the templates of
28
+ * the binding that reads through it.
29
+ *
30
+ * Deliberately free of any import: `create.verb.ts` reads this on the `--help` /
31
+ * `__complete` fast path (`capabilities/lazy.test.ts`).
32
+ */
33
+ export const CREATE_GENERATORS = {
34
+ component: {
35
+ /**
36
+ * The declaring package: `scripts/build.ts` harvests its `.ejs` for the
37
+ * binary.
38
+ */
39
+ name: "@canonical/summon-component",
40
+ /** `--framework <f>` runs `component/<f>`; the FIRST is the enum default. */
41
+ frameworks: ["react", "svelte", "lit"],
42
+ /**
43
+ * Runs from the compiled binary: this generator routes every template read
44
+ * through `loadTemplateSync` and passes `content:` into `template()`, so the
45
+ * embedded manifest serves it. That — not the templates' file extensions —
46
+ * is the property that decides it, and it is a fact about the GENERATOR's
47
+ * source, not about what this build chooses to embed.
48
+ */
49
+ readsEmbeddedTemplates: true,
50
+ },
51
+ package: {
52
+ /** The generator-map key `create package` runs. */
53
+ key: "package",
54
+ /**
55
+ * NOT runnable from the compiled binary: this generator calls
56
+ * `template({ source })` with no `content:`, so summon-core falls through to
57
+ * `readFile(options.source)`. Measured against a real `dist/pragma` with the
58
+ * gate lifted: `ENOENT: no such file or directory, open
59
+ * '/$bunfs/templates/package.json.ejs'`, after `mkdir` had already created
60
+ * `my-lib/` and `my-lib/src/` — a half-made package left on disk. A
61
+ * `--dry-run` exits 0 without reading a template, so it does NOT test this.
62
+ */
63
+ readsEmbeddedTemplates: false,
64
+ },
65
+ application: {
66
+ /** The generator-map key `create application` runs. */
67
+ key: "application/react",
68
+ /**
69
+ * NOT runnable from the compiled binary — same cause as `package` (measured:
70
+ * `ENOENT … '/$bunfs/root/templates/package.json.ejs'`, after the app
71
+ * directory was created). It also `copy()`s non-`.ejs` assets.
72
+ */
73
+ readsEmbeddedTemplates: false,
74
+ },
75
+ } as const;
@@ -14,7 +14,7 @@ import { bootRuntime } from "../../kernel/runtime/boot.js";
14
14
  import type { GlobalFlags } from "../../kernel/runtime/types.js";
15
15
  import type { VerbSpec } from "../../kernel/spec/types.js";
16
16
  import { projectMcp } from "../../testing/helpers/projectMcp.js";
17
- import { createComponentVerb } from "./create.verb.js";
17
+ import { createVerbs } from "./create.verb.js";
18
18
  import { createModule } from "./index.js";
19
19
 
20
20
  const FLAGS: GlobalFlags = {
@@ -117,7 +117,7 @@ describe("create over MCP (PROTECTED)", () => {
117
117
  const mcpPlan = (mcpResult.data as { plan: string[] }).plan;
118
118
 
119
119
  const cliOutcome = await executeVerb(
120
- createComponentVerb as VerbSpec,
120
+ createVerbs.component as VerbSpec,
121
121
  {
122
122
  framework: "react",
123
123
  componentPath: "src/components/Button",