@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,11 +1,11 @@
1
1
  /**
2
2
  * The `create` verbs — `component` / `package` / `application` (path capped at
3
- * two segments; the three component frameworks collapse to one verb + a
3
+ * two segments; the declared component frameworks collapse to one verb + a
4
4
  * `--framework` enum). Each is a mutating, interactive, storeless verb.
5
5
  *
6
6
  * LAZY DISPATCH (R9 + lazy-React): the params are STATIC — built by the
7
7
  * generator→grammar adapter over static prompt MIRRORS, never the live
8
- * generators (importing a generator runs a top-level `await loadTemplate`). The
8
+ * generators (importing a generator pulls summon-core, and with it React). The
9
9
  * `run` body lazily `import()`s `pickGenerator` + summon-core, so `buildProgram`
10
10
  * / `--help` / `__complete` / reads never load summon-core or the generators —
11
11
  * and `create --yes` never loads React (the Ink UI is dynamic-only, and only the
@@ -15,9 +15,11 @@
15
15
 
16
16
  import type { GeneratorResult, PromptDefinition } from "@canonical/summon-core";
17
17
  import type { Task } from "@canonical/task";
18
+ import { BIN_NAME } from "../../constants.js";
18
19
  import { PragmaError } from "../../kernel/error/PragmaError.js";
19
20
  import type { PragmaRuntime } from "../../kernel/runtime/types.js";
20
21
  import type { ParamSpec, VerbSpec } from "../../kernel/spec/types.js";
22
+ import { CREATE_GENERATORS } from "./constants.js";
21
23
  import { createFormatters } from "./create.render.js";
22
24
  import { generatorToParams } from "./generatorToVerbSpec.js";
23
25
  import { assertInsideWorkspace } from "./pathJail.js";
@@ -27,13 +29,14 @@ import type { CreateKind } from "./types.js";
27
29
  // Static params (mirrors of the generators' prompts — see the module doc)
28
30
  // =============================================================================
29
31
 
30
- /** `--framework` — the three component generators collapsed to one enum. */
32
+ /** `--framework` — the declared component generators collapsed to one enum. */
31
33
  const FRAMEWORK_PARAM: ParamSpec = {
32
34
  kind: "enum",
33
35
  name: "framework",
34
36
  doc: "Component framework.",
35
- values: ["react", "svelte", "lit"],
36
- default: "react",
37
+ values: CREATE_GENERATORS.component.frameworks,
38
+ // The FIRST declared framework, so the default is always inside the enum.
39
+ default: CREATE_GENERATORS.component.frameworks[0],
37
40
  };
38
41
 
39
42
  /**
@@ -129,6 +132,13 @@ const PACKAGE_MIRROR: PromptDefinition[] = [
129
132
  },
130
133
  ];
131
134
 
135
+ // The include-flag prompts carry the CLI grammar's `--with-X` names
136
+ // (`withSsr`/`withRouter`/`withForms`/`withRelay`, AV-228 B8) rather than the
137
+ // summon generator's bare prompt names (`ssr`/`router`/`forms`/`relay`). {@link
138
+ // INCLUDE_FLAG_ALIASES} maps them back at the CLI↔generator boundary so the
139
+ // generator prompt names — and their embedded templates + byte-equality
140
+ // goldens — stay stable. `--run-install` (default false) is an action flag, not
141
+ // an include, so it keeps its spelling.
132
142
  const APPLICATION_MIRROR: PromptDefinition[] = [
133
143
  {
134
144
  name: "appPath",
@@ -137,21 +147,21 @@ const APPLICATION_MIRROR: PromptDefinition[] = [
137
147
  default: "my-app",
138
148
  positional: true,
139
149
  },
140
- { name: "ssr", type: "confirm", message: "Include SSR?", default: true },
150
+ { name: "withSsr", type: "confirm", message: "Include SSR?", default: true },
141
151
  {
142
- name: "router",
152
+ name: "withRouter",
143
153
  type: "confirm",
144
154
  message: "Include router?",
145
155
  default: true,
146
156
  },
147
157
  {
148
- name: "forms",
158
+ name: "withForms",
149
159
  type: "confirm",
150
160
  message: "Include form components?",
151
161
  default: true,
152
162
  },
153
163
  {
154
- name: "relay",
164
+ name: "withRelay",
155
165
  type: "confirm",
156
166
  message: "Include a Relay (GraphQL) data layer?",
157
167
  default: false,
@@ -164,6 +174,50 @@ const APPLICATION_MIRROR: PromptDefinition[] = [
164
174
  },
165
175
  ];
166
176
 
177
+ /**
178
+ * CLI include-flag names → summon generator prompt names, per create kind
179
+ * (AV-228 B8). All four `create application` include-flags are exposed on the
180
+ * unified `--with-X` convention, so their params arrive keyed `withSsr`/
181
+ * `withRouter`/`withForms`/`withRelay`; the summon generator reads the bare
182
+ * `ssr`/`router`/`forms`/`relay`. We normalize at the ONE CLI↔generator seam
183
+ * ({@link toGeneratorAnswers}), keeping the generator prompt names (and their
184
+ * templates/goldens) untouched. Component and package already use `--with-X`
185
+ * names that match their prompts, so their maps are empty.
186
+ */
187
+ export const INCLUDE_FLAG_ALIASES: Record<
188
+ CreateKind,
189
+ Readonly<Record<string, string>>
190
+ > = {
191
+ component: {},
192
+ package: {},
193
+ application: {
194
+ withSsr: "ssr",
195
+ withRouter: "router",
196
+ withForms: "forms",
197
+ withRelay: "relay",
198
+ },
199
+ };
200
+
201
+ /**
202
+ * Re-key a param bag's CLI include-flag aliases to the generator prompt names,
203
+ * so the summon generator reads `ssr` where the CLI grammar exposes `--with-ssr`.
204
+ *
205
+ * @param kind - The create noun (selects the alias map).
206
+ * @param params - The coerced CLI/MCP param bag.
207
+ * @returns A new bag with aliased keys renamed to the generator prompt names.
208
+ */
209
+ export function toGeneratorAnswers(
210
+ kind: CreateKind,
211
+ params: Readonly<Record<string, unknown>>,
212
+ ): Record<string, unknown> {
213
+ const aliases = INCLUDE_FLAG_ALIASES[kind];
214
+ const answers: Record<string, unknown> = {};
215
+ for (const [key, value] of Object.entries(params)) {
216
+ answers[aliases[key] ?? key] = value;
217
+ }
218
+ return answers;
219
+ }
220
+
167
221
  const componentParams: ParamSpec[] = [
168
222
  FRAMEWORK_PARAM,
169
223
  COMPONENT_PATH_PARAM,
@@ -183,20 +237,16 @@ const PATH_PARAM: Record<CreateKind, string | undefined> = {
183
237
  // The lazy run — the one-line summon↔pragma seam per invocation
184
238
  // =============================================================================
185
239
 
186
- /**
187
- * Build the `create` Task for one invocation: pick the generator, jail its
188
- * output path, pre-validate flag/arg answers, pick the prompt strategy against
189
- * the interaction context, wire `runtime.exec`, and return `execute`.
190
- *
191
- * @returns A `Promise<Task<GeneratorResult>>` (the union's third arm) the
192
- * dispatcher/MCP handler awaits into a Task before interpreting.
193
- */
194
240
  /**
195
241
  * The standalone `bun build --compile` binary resolves every bundled module
196
242
  * under the virtual `/$bunfs` filesystem — a marker absent from any source or
197
- * `bun`-run module URL. `create component` is fully embedded and runs from the
198
- * binary; `create package` / `create application` are NOT (application also
199
- * copies non-`.ejs` assets), so they are gated to a source run below.
243
+ * `bun`-run module URL. Only a generator that routes its template reads through
244
+ * the embedded manifest can run there
245
+ * (`CREATE_GENERATORS[kind].readsEmbeddedTemplates`);
246
+ * the others call `template({ source })`, which falls through to
247
+ * `readFile(source)` and dies with `ENOENT … /$bunfs/…` after `mkdir` has
248
+ * already run, leaving partially-created files behind.
249
+ * `compiledCreate.subprocess.test.ts` pins the refusal AND the empty cwd.
200
250
  */
201
251
  const IS_COMPILED_BINARY = import.meta.url.includes("/$bunfs/");
202
252
 
@@ -230,23 +280,22 @@ export function isModuleNotFound(cause: unknown): boolean {
230
280
  * `--compile` bundler includes them — they stay behind this lazy boundary, so
231
281
  * the fast paths and `create --yes` still load neither summon-core nor React).
232
282
  *
233
- * The manifest is injected BEFORE `pickGenerator` — importing the generator
234
- * packages runs a top-level `await loadTemplate`, and the component loader
235
- * consults the manifest when its disk read fails (the compiled binary). It is
236
- * imported from summon-component's `loadTemplate` submodule so this does not
237
- * evaluate the generator index first. In a source run the disk read wins and the
238
- * manifest is inert.
283
+ * The manifest is injected BEFORE `pickGenerator` — the component loader
284
+ * consults it when its disk read fails (the compiled binary), and the generators
285
+ * load their templates on first `generate()`. It is imported from
286
+ * summon-component's `loadTemplate` submodule so this does not evaluate the
287
+ * generator index first. In a source run the disk read wins and the manifest is
288
+ * inert.
239
289
  *
240
- * `create package` / `create application` are not yet embedded for the compiled
241
- * binary (application also copies non-`.ejs` assets), so they are gated to a
242
- * source run here. A stale resolution failure is a defensive backstop
243
- * {@link isModuleNotFound} turns into the same clean gate.
290
+ * A binding whose generator does not read through that manifest is gated to a
291
+ * source run here ({@link IS_COMPILED_BINARY}). A stale resolution failure is a
292
+ * defensive backstop {@link isModuleNotFound} turns into the same clean gate.
244
293
  */
245
294
  async function loadCreateRuntime(kind: CreateKind) {
246
- if (IS_COMPILED_BINARY && kind !== "component") {
295
+ if (IS_COMPILED_BINARY && !CREATE_GENERATORS[kind].readsEmbeddedTemplates) {
247
296
  throw new PragmaError({
248
297
  code: "UNSUPPORTED",
249
- message: `\`create ${kind}\` is not available in the compiled pragma binary yet — only \`create component\` is embedded. Run it from a source checkout, or use the \`summon\` CLI.`,
298
+ message: `\`create ${kind}\` is not available in the compiled ${BIN_NAME} binary — ${SOURCE_ONLY_REASON}. Run it from a source checkout, or use the \`summon\` CLI.`,
250
299
  recovery: {
251
300
  message: `Run \`create ${kind}\` from a source checkout, or use \`summon\`.`,
252
301
  },
@@ -280,26 +329,42 @@ async function loadCreateRuntime(kind: CreateKind) {
280
329
  }
281
330
  }
282
331
 
332
+ /**
333
+ * Build the `create` Task for one invocation: pick the generator, jail its
334
+ * output path, pre-validate flag/arg answers, pick the prompt strategy against
335
+ * the interaction context, wire `runtime.exec`, and return `execute`.
336
+ *
337
+ * @param kind - The create noun.
338
+ * @param params - The coerced params for this invocation.
339
+ * @param rt - The pragma runtime.
340
+ * @returns A `Promise<Task<GeneratorResult>>` (the union's third arm) the
341
+ * dispatcher/MCP handler awaits into a Task before interpreting.
342
+ */
283
343
  async function runCreate(
284
344
  kind: CreateKind,
285
345
  params: Record<string, unknown>,
286
346
  rt: PragmaRuntime,
287
347
  ): Promise<Task<GeneratorResult>> {
288
- // Lazy: importing these runs the generators' top-level template loads and
289
- // pulls summon-core — kept off every non-create path. Now STATIC dynamic
290
- // imports so `--compile` bundles them; the generators' `.ejs` are embedded and
291
- // injected here. `package`/`application` stay a source-run feature in the binary.
348
+ // Lazy: importing these pulls summon-core (and with it React) — kept off every
349
+ // non-create path. Now STATIC dynamic imports so `--compile` bundles them; the
350
+ // embedded `.ejs` manifest is injected here. A binding that does not read
351
+ // through it stays a source-run feature in the binary.
292
352
  const { pickGenerator, summon } = await loadCreateRuntime(kind);
293
353
 
294
- const generator = pickGenerator(kind, params);
354
+ // Normalize the CLI/MCP `--with-X` include-flags to the generator prompt names
355
+ // (AV-228 B8) once, at this seam; every summon interaction below reads the
356
+ // generator-facing `answers` bag so the generator prompt names stay stable.
357
+ const answers = toGeneratorAnswers(kind, params);
358
+
359
+ const generator = pickGenerator(kind, answers);
295
360
 
296
361
  // SEC-2: reject a path escaping the workspace BEFORE any effect runs.
297
362
  const pathParam = PATH_PARAM[kind];
298
- if (pathParam) assertInsideWorkspace(pathParam, params[pathParam], rt.cwd);
363
+ if (pathParam) assertInsideWorkspace(pathParam, answers[pathParam], rt.cwd);
299
364
 
300
365
  // Reject a flag/arg-provided answer that fails its prompt's own constraint,
301
366
  // with a clean INVALID_INPUT (execute re-validates as a backstop).
302
- const invalid = summon.validateAnswers(generator.prompts, params);
367
+ const invalid = summon.validateAnswers(generator.prompts, answers);
303
368
  if (invalid !== null) {
304
369
  throw new PragmaError({ code: "INVALID_INPUT", message: invalid });
305
370
  }
@@ -318,7 +383,13 @@ async function runCreate(
318
383
  // `onCancel` (H2): an in-Ink Ctrl-C during execution aborts the run so the
319
384
  // interpreter stops writing — raw mode swallows the SIGINT, so the wizard
320
385
  // drives the abort. Shared with setup by construction.
321
- const session = summon.inkPrompt(generator, { signal, onCancel: abort });
386
+ // `cwd` also reaches the wizard: its confirm-gate preview reads the same
387
+ // jail-checked write root the interpreter resolves effect paths against.
388
+ const session = summon.inkPrompt(generator, {
389
+ signal,
390
+ onCancel: abort,
391
+ cwd: rt.cwd,
392
+ });
322
393
  // Thread the per-call write root: the interpreter resolves the generator's
323
394
  // relative output paths against `rt.cwd` — the SAME dir the SEC-2 jail
324
395
  // validated above — so the write can never escape the checked directory.
@@ -336,14 +407,16 @@ async function runCreate(
336
407
  };
337
408
  return summon.execute(generator, {
338
409
  prompt: session.promptHandler,
339
- params,
410
+ params: answers,
340
411
  signal,
341
412
  });
342
413
  }
343
414
 
344
415
  // Non-interactive: MCP → params-or-error; CLI/--yes/CI → flags+defaults.
345
416
  const prompt =
346
- transport === "mcp" ? summon.mcpPrompt(params) : summon.autoPrompt(params);
417
+ transport === "mcp"
418
+ ? summon.mcpPrompt(answers)
419
+ : summon.autoPrompt(answers);
347
420
  // Same per-call write root as the Ink branch: `rt.cwd` feeds both the SEC-2
348
421
  // jail and the interpreter's effect-path base, atomically.
349
422
  rt.exec = {
@@ -353,7 +426,7 @@ async function runCreate(
353
426
  onLog: (_level, message) => process.stderr.write(`${message}\n`),
354
427
  signal,
355
428
  };
356
- return summon.execute(generator, { prompt, params, signal });
429
+ return summon.execute(generator, { prompt, params: answers, signal });
357
430
  }
358
431
 
359
432
  /**
@@ -375,10 +448,51 @@ const CREATE_CAPABILITY = {
375
448
  },
376
449
  };
377
450
 
451
+ /**
452
+ * Why a binding is source-run only — the ONE authoring of it, shared by the
453
+ * documentation below and by {@link loadCreateRuntime}'s `UNSUPPORTED` refusal,
454
+ * so what the reference promises and what the binary does cannot disagree.
455
+ */
456
+ const SOURCE_ONLY_REASON =
457
+ "its generator reads templates from disk, which the binary does not carry";
458
+
459
+ /**
460
+ * The published caveat for a binding the compiled binary cannot run. DERIVED
461
+ * from `CREATE_GENERATORS[kind].readsEmbeddedTemplates` — flipping that bit
462
+ * moves the caveat, `docs/reference/*.md`, and `create.test.ts`'s binding
463
+ * assertion together.
464
+ *
465
+ * It LEADS WITH THE PURPOSE, because `tools.md` and the MCP tool description
466
+ * both render `doc ?? summary` — so a caveat alone REPLACED the only sentence
467
+ * saying what the tool makes, and an agent enumerating tools could not learn
468
+ * that `create_package` scaffolds a package. The tool works from a source
469
+ * checkout; describing nothing but its refusal made a live tool
470
+ * undiscoverable. The repeated sentence in `commands.md`'s section is the
471
+ * cheaper of the two costs.
472
+ *
473
+ * It names no CLI flag (`mcp/toolDescriptions.test.ts` forbids one, and an
474
+ * agent has no flags): the plan-only refusal is stated in the terms BOTH
475
+ * surfaces share.
476
+ *
477
+ * @param kind - The create binding.
478
+ * @param summary - The verb's own one-liner, which the `doc` replaces on MCP.
479
+ * @returns The `doc` for a source-run-only binding, or `undefined`.
480
+ */
481
+ function buildAvailabilityDoc(
482
+ kind: CreateKind,
483
+ summary: string,
484
+ ): string | undefined {
485
+ if (CREATE_GENERATORS[kind].readsEmbeddedTemplates) return undefined;
486
+ return `${summary} From the compiled ${BIN_NAME} binary, \`create ${kind}\` refuses with \`UNSUPPORTED\` and writes nothing. Asking it only to PLAN refuses too — the gate runs while the plan is built — so a successful plan is never evidence it would run. The cause is that ${SOURCE_ONLY_REASON}. Run it from a source checkout, or use the \`summon\` CLI.`;
487
+ }
488
+
378
489
  /**
379
490
  * Build a create verb. `run` presents `Promise<Task<R>>` through the `Task<R>`
380
491
  * arm by an honest cast at this one site (mirroring `sources update`): a literal
381
492
  * `Promise<Task<R>>` arm in the union would poison async read-verb inference.
493
+ *
494
+ * A binding the compiled binary cannot run says so in its `summary` (which is
495
+ * what `--help` and the noun listing show) and explains itself in its `doc`.
382
496
  */
383
497
  function createVerb(
384
498
  kind: CreateKind,
@@ -386,9 +500,11 @@ function createVerb(
386
500
  params: ParamSpec[],
387
501
  examples: VerbSpec["examples"],
388
502
  ): VerbSpec<Record<string, unknown>, GeneratorResult> {
503
+ const doc = buildAvailabilityDoc(kind, summary);
389
504
  return {
390
505
  path: ["create", kind],
391
- summary,
506
+ summary: doc ? `${summary} Source-run only.` : summary,
507
+ ...(doc ? { doc } : {}),
392
508
  params,
393
509
  output: { formatters: createFormatters },
394
510
  examples,
@@ -398,38 +514,51 @@ function createVerb(
398
514
  };
399
515
  }
400
516
 
401
- export const createComponentVerb = createVerb(
402
- "component",
403
- "Scaffold a React, Svelte, or Lit component.",
404
- componentParams,
405
- [
406
- {
407
- cmd: "pragma create component src/components/Button --framework react",
408
- note: "React component with tests, stories, and styles",
409
- },
410
- {
411
- cmd: "pragma create component src/lib/Card --framework svelte --dry-run",
412
- note: "preview the files without writing",
413
- },
414
- ],
415
- );
416
-
417
- export const createPackageVerb = createVerb(
418
- "package",
419
- "Scaffold a new npm package for the monorepo.",
420
- packageParams,
421
- [
422
- { cmd: "pragma create package --name @canonical/my-lib --type library" },
423
- { cmd: "pragma create package --name @canonical/my-tool --run-install" },
424
- ],
425
- );
426
-
427
- export const createApplicationVerb = createVerb(
428
- "application",
429
- "Scaffold a full React application with SSR and routing.",
430
- applicationParams,
431
- [
432
- { cmd: "pragma create application my-app" },
433
- { cmd: "pragma create application my-app --relay" },
434
- ],
435
- );
517
+ /**
518
+ * The `create` verbs, one per declared generator binding — so adding a binding
519
+ * to {@link CREATE_GENERATORS} without surfacing it is a type error rather than
520
+ * a silent no-op. Key order is authoring order, which `index.ts` preserves into
521
+ * the command tree, `--help` and the emitted surface.
522
+ */
523
+ export const createVerbs: Record<
524
+ CreateKind,
525
+ VerbSpec<Record<string, unknown>, GeneratorResult>
526
+ > = {
527
+ component: createVerb(
528
+ "component",
529
+ "Scaffold a React, Svelte, or Lit component.",
530
+ componentParams,
531
+ [
532
+ {
533
+ cmd: `${BIN_NAME} create component src/components/Button --framework react`,
534
+ note: "React component with tests, stories, and styles",
535
+ },
536
+ {
537
+ cmd: `${BIN_NAME} create component src/lib/Card --framework svelte --dry-run`,
538
+ note: "preview the files without writing",
539
+ },
540
+ ],
541
+ ),
542
+ package: createVerb(
543
+ "package",
544
+ "Scaffold a new npm package for the monorepo.",
545
+ packageParams,
546
+ [
547
+ {
548
+ cmd: `${BIN_NAME} create package --name @canonical/my-lib --type library`,
549
+ },
550
+ {
551
+ cmd: `${BIN_NAME} create package --name @canonical/my-tool --run-install`,
552
+ },
553
+ ],
554
+ ),
555
+ application: createVerb(
556
+ "application",
557
+ "Scaffold a full React application with SSR and routing.",
558
+ applicationParams,
559
+ [
560
+ { cmd: `${BIN_NAME} create application my-app` },
561
+ { cmd: `${BIN_NAME} create application my-app --with-relay` },
562
+ ],
563
+ ),
564
+ };
@@ -8,18 +8,10 @@
8
8
 
9
9
  import { asVerb } from "../../kernel/spec/asVerb.js";
10
10
  import type { CapabilityModule } from "../../kernel/spec/types.js";
11
- import {
12
- createApplicationVerb,
13
- createComponentVerb,
14
- createPackageVerb,
15
- } from "./create.verb.js";
11
+ import { createVerbs } from "./create.verb.js";
16
12
 
17
13
  /** The `create` capability module. */
18
14
  export const createModule: CapabilityModule = {
19
15
  name: "create",
20
- verbs: [
21
- asVerb(createComponentVerb),
22
- asVerb(createPackageVerb),
23
- asVerb(createApplicationVerb),
24
- ],
16
+ verbs: Object.values(createVerbs).map(asVerb),
25
17
  };
@@ -1,38 +1,46 @@
1
- /**
2
- * Select the summon generator for a `create` invocation.
3
- *
4
- * This module statically imports the summon generator packages' `generators`
5
- * maps — and importing them runs a top-level `await loadTemplate` that reads
6
- * every template file from disk. So this module MUST stay behind `create`'s
7
- * lazy `import()` (R9): the fast paths (`buildProgram` / `__complete` / `--help`
8
- * / reads) never load it, and `create --yes` never loads React either (the
9
- * generators pull summon-core, whose Ink UI is dynamic-only).
10
- */
11
-
12
1
  import { generators as applicationGenerators } from "@canonical/summon-application";
13
2
  import { generators as componentGenerators } from "@canonical/summon-component";
14
3
  import type { GeneratorDefinition } from "@canonical/summon-core";
15
4
  import { generators as packageGenerators } from "@canonical/summon-package";
16
5
  import { PragmaError } from "../../kernel/error/PragmaError.js";
6
+ import { CREATE_GENERATORS } from "./constants.js";
17
7
  import type { CreateKind } from "./types.js";
18
8
 
19
- /** The component frameworks that collapse to one `create component` verb. */
20
- export const FRAMEWORKS = ["react", "svelte", "lit"] as const;
9
+ type GeneratorMap = Record<string, GeneratorDefinition | undefined>;
21
10
 
22
11
  /**
23
- * The generators' `generate` is invariant in its (specific) answer type, so the
24
- * strongly-typed maps are erased to the base definition via `unknown`. This is
25
- * runtime-safe: `execute` calls `generate(answers)` with the resolved answers,
26
- * which carry the fields the specific generator reads.
12
+ * The declared packages' generator maps, bound by noun. The STATIC imports above
13
+ * are load-bearing: `bun build --compile` bundles only statically analysable
14
+ * specifiers, so `import(CREATE_GENERATORS[kind].name)` would leave the
15
+ * generators out of the binary (measured: `Cannot find module
16
+ * '@canonical/summon-component' from '/$bunfs/root/…'`). The declaration decides
17
+ * WHICH generator runs; it cannot decide which module is linked.
18
+ *
19
+ * The maps' `generate` is invariant in its (specific) answer type, so they are
20
+ * erased to the base definition via `unknown` — runtime-safe because `execute`
21
+ * calls `generate(answers)` with the resolved answers, which carry the fields
22
+ * the specific generator reads.
27
23
  */
28
- type GeneratorMap = Record<string, GeneratorDefinition | undefined>;
29
- const componentMap = componentGenerators as unknown as GeneratorMap;
30
- const packageMap = packageGenerators as unknown as GeneratorMap;
31
- const applicationMap = applicationGenerators as unknown as GeneratorMap;
24
+ const GENERATOR_MAPS: Record<CreateKind, GeneratorMap> = {
25
+ component: componentGenerators as unknown as GeneratorMap,
26
+ package: packageGenerators as unknown as GeneratorMap,
27
+ application: applicationGenerators as unknown as GeneratorMap,
28
+ };
32
29
 
33
30
  /**
34
31
  * Pick the generator for a `create <kind>` run.
35
32
  *
33
+ * The module this lives in statically imports the summon generator packages'
34
+ * `generators` maps — and importing them pulls summon-core. So it MUST stay
35
+ * behind `create`'s lazy `import()` (R9): the fast paths (`buildProgram` /
36
+ * `__complete` / `--help` / reads) never load it, and `create --yes` never loads
37
+ * React either (summon-core's Ink UI is dynamic-only).
38
+ *
39
+ * `component` is branched on literally: it is the only noun with a framework
40
+ * axis (which is why the verb synthesises a `--framework` enum), and narrowing
41
+ * on `kind` here lets `CREATE_GENERATORS[kind].key` type-check over the
42
+ * remaining nouns without `in`-narrowing gymnastics.
43
+ *
36
44
  * @param kind - The create noun.
37
45
  * @param params - The coerced params (used for `--framework` on `component`).
38
46
  * @returns The selected generator definition.
@@ -43,19 +51,17 @@ export function pickGenerator(
43
51
  params: Readonly<Record<string, unknown>>,
44
52
  ): GeneratorDefinition {
45
53
  if (kind === "component") {
46
- const framework = String(params.framework ?? "react");
47
- const generator = componentMap[`component/${framework}`];
54
+ const { frameworks } = CREATE_GENERATORS.component;
55
+ const framework = String(params.framework ?? frameworks[0]);
56
+ const generator = GENERATOR_MAPS.component[`component/${framework}`];
48
57
  if (!generator) {
49
58
  throw PragmaError.invalidInput("framework", framework, {
50
- validOptions: [...FRAMEWORKS],
59
+ validOptions: [...frameworks],
51
60
  });
52
61
  }
53
62
  return generator;
54
63
  }
55
- const generator =
56
- kind === "package"
57
- ? packageMap.package
58
- : applicationMap["application/react"];
64
+ const generator = GENERATOR_MAPS[kind][CREATE_GENERATORS[kind].key];
59
65
  if (!generator) {
60
66
  throw PragmaError.internalError(`missing ${kind} generator`);
61
67
  }