@canonical/pragma-cli 0.40.0 → 0.42.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 (57) hide show
  1. package/README.md +2 -2
  2. package/dist/package.json +16 -16
  3. package/dist/src/capabilities/colophon/markdownTerminal.js.map +1 -1
  4. package/dist/src/capabilities/config/show.render.js.map +1 -1
  5. package/dist/src/capabilities/create/constants.js +6 -3
  6. package/dist/src/capabilities/create/constants.js.map +1 -1
  7. package/dist/src/capabilities/create/create.render.js.map +1 -1
  8. package/dist/src/capabilities/create/create.verb.js +22 -3
  9. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  10. package/dist/src/capabilities/create/createSurface.generated.js +22 -0
  11. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  12. package/dist/src/capabilities/create/index.js +1 -1
  13. package/dist/src/capabilities/create/pickGenerator.js +1 -0
  14. package/dist/src/capabilities/create/pickGenerator.js.map +1 -1
  15. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  16. package/dist/src/capabilities/resources/provider.js.map +1 -1
  17. package/dist/src/capabilities/setup/operations/writability.js.map +1 -1
  18. package/dist/src/capabilities/setup/plan.render.js.map +1 -1
  19. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  20. package/dist/src/capabilities/shared/packageManager.js.map +1 -1
  21. package/dist/src/capabilities/skill/discover.js.map +1 -1
  22. package/dist/src/capabilities/sources/collectStatus.js.map +1 -1
  23. package/dist/src/capabilities/sources/status.render.js.map +1 -1
  24. package/dist/src/kernel/completion/complete.js.map +1 -1
  25. package/dist/src/kernel/completion/emitScripts.js.map +1 -1
  26. package/dist/src/kernel/completion/parse.js.map +1 -1
  27. package/dist/src/kernel/completion/rank.js.map +1 -1
  28. package/dist/src/kernel/completion/resolve.js.map +1 -1
  29. package/dist/src/kernel/config/readConfig.js.map +1 -1
  30. package/dist/src/kernel/error/PragmaError.js.map +1 -1
  31. package/dist/src/kernel/packs/collect.js.map +1 -1
  32. package/dist/src/kernel/packs/graphql/fetchGraphqlLookup.js.map +1 -1
  33. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  34. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  35. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  36. package/dist/src/kernel/project/cli/globalFlags.js.map +1 -1
  37. package/dist/src/kernel/project/mcp/buildServer.js.map +1 -1
  38. package/dist/src/kernel/project/mcp/envelope.js.map +1 -1
  39. package/dist/src/kernel/render/envelope.js.map +1 -1
  40. package/dist/src/kernel/render/renderers.js.map +1 -1
  41. package/dist/src/kernel/runtime/boot.js.map +1 -1
  42. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  43. package/package.json +16 -16
  44. package/src/capabilities/create/constants.ts +6 -3
  45. package/src/capabilities/create/create.mcp.test.ts +173 -1
  46. package/src/capabilities/create/create.test.ts +4 -2
  47. package/src/capabilities/create/create.verb.ts +29 -3
  48. package/src/capabilities/create/createGrammar.test.ts +40 -4
  49. package/src/capabilities/create/createSurface.generated.ts +22 -0
  50. package/src/capabilities/create/crossCli.subprocess.test.ts +53 -14
  51. package/src/capabilities/create/index.ts +1 -1
  52. package/src/capabilities/create/pickGenerator.ts +1 -0
  53. package/src/capabilities/create/shippedCreate.subprocess.test.ts +28 -7
  54. package/src/capabilities/surface.test.ts +29 -4
  55. package/src/kernel/completion/shellDrive.test.ts +2 -2
  56. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  57. package/src/testing/behavioral/README.md +1 -1
@@ -17,8 +17,8 @@
17
17
  * existed to turn that crash into a clean refusal.
18
18
  *
19
19
  * There is no virtual filesystem now. Every generator's templates are real
20
- * files under its own package, so all three nouns run from the shipped entry and
21
- * all three are asserted here. THE REFUSAL CASES ARE GONE ON PURPOSE: a test
20
+ * files under its own package, so all four nouns run from the shipped entry and
21
+ * all four are asserted here. THE REFUSAL CASES ARE GONE ON PURPOSE: a test
22
22
  * asserting `create package` refuses would now be asserting a defect.
23
23
  *
24
24
  * The svelte + lit cases stay load-bearing: `types.ts.ejs` / `index.ts.ejs` /
@@ -28,7 +28,13 @@
28
28
  */
29
29
 
30
30
  import { execFileSync, spawnSync } from "node:child_process";
31
- import { mkdtempSync, readdirSync, readFileSync } from "node:fs";
31
+ import {
32
+ mkdirSync,
33
+ mkdtempSync,
34
+ readdirSync,
35
+ readFileSync,
36
+ writeFileSync,
37
+ } from "node:fs";
32
38
  import { tmpdir } from "node:os";
33
39
  import { dirname, join, resolve } from "node:path";
34
40
  import { fileURLToPath } from "node:url";
@@ -70,7 +76,12 @@ function snapshot(dir: string): Map<string, string> {
70
76
  * No case installs dependencies (`runInstall` defaults false), so every tree is
71
77
  * generator output alone and byte-comparable.
72
78
  */
73
- const CASES: ReadonlyArray<{ label: string; args: readonly string[] }> = [
79
+ const CASES: ReadonlyArray<{
80
+ label: string;
81
+ args: readonly string[];
82
+ /** Files that must already exist in the cwd (`page` adds to a domain). */
83
+ seed?: Readonly<Record<string, string>>;
84
+ }> = [
74
85
  ...(["react", "svelte", "lit"] as const).map((framework) => ({
75
86
  label: `component ${framework}`,
76
87
  // The framework is a PATH SEGMENT, not a flag — `create` mirrors summon's
@@ -88,6 +99,11 @@ const CASES: ReadonlyArray<{ label: string; args: readonly string[] }> = [
88
99
  // `application/react`, and the tree mirrors summon's.
89
100
  args: ["create", "application", "react", "probeapp", "--yes"],
90
101
  },
102
+ {
103
+ label: "page",
104
+ args: ["create", "page", "probe/detail", "--yes"],
105
+ seed: { "src/domains/probe/routes.ts": "export default {};\n" },
106
+ },
91
107
  ];
92
108
 
93
109
  /** Run one create invocation in its own cwd and snapshot what it wrote. */
@@ -95,19 +111,24 @@ function create(
95
111
  command: string,
96
112
  prefix: readonly string[],
97
113
  args: readonly string[],
114
+ seed: Readonly<Record<string, string>> = {},
98
115
  ): Map<string, string> {
99
116
  const dir = freshCwd();
117
+ for (const [file, content] of Object.entries(seed)) {
118
+ mkdirSync(dirname(join(dir, file)), { recursive: true });
119
+ writeFileSync(join(dir, file), content);
120
+ }
100
121
  execFileSync(command, [...prefix, ...args], { cwd: dir, stdio: "pipe" });
101
122
  return snapshot(dir);
102
123
  }
103
124
 
104
125
  describe("shipped pragma create (PROTECTED)", () => {
105
- for (const { label, args } of CASES) {
126
+ for (const { label, args, seed } of CASES) {
106
127
  it(`${label}: shipped entry ≡ source run, byte-for-byte`, () => {
107
128
  // (1) The shipped entry, exactly as a consumer's `pragma` runs it.
108
- const shipped = create(process.execPath, [shippedEntry], args);
129
+ const shipped = create(process.execPath, [shippedEntry], args, seed);
109
130
  // (2) A source run — the reference output.
110
- const source = create("bun", [sourceEntry], args);
131
+ const source = create("bun", [sourceEntry], args, seed);
111
132
 
112
133
  // Wrote something. Before the distribution stopped shipping a compiled
113
134
  // binary, `package` and `application` wrote NOTHING here — they refused.
@@ -9,6 +9,7 @@ import {
9
9
  type Covenant,
10
10
  } from "../kernel/spec/surfaceConformance.js";
11
11
  import { projectMcp } from "../testing/helpers/projectMcp.js";
12
+ import { CREATE_GENERATORS } from "./create/constants.js";
12
13
  import { CREATE_SURFACE } from "./create/createSurface.generated.js";
13
14
  import { capabilities } from "./index.js";
14
15
 
@@ -199,7 +200,7 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
199
200
  // args carry the kebab positional the usage line prints, so the covenant
200
201
  // names `--no-with-styles` and `[component-path]`, never a
201
202
  // `--with-styles` the CLI rejects or a `[componentPath]` its help never
202
- // prints. Pin the three entries verbatim so a prompt edit that moves the
203
+ // prints. Pin the four entries verbatim so a prompt edit that moves the
203
204
  // covenant is SEEN.
204
205
  expect(emitted.nouns.create?.verbs).toEqual([
205
206
  {
@@ -247,6 +248,13 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
247
248
  mutates: true,
248
249
  mcp: "create_application",
249
250
  },
251
+ {
252
+ v: "page",
253
+ // The one prompt is the positional page path, so there is no flag.
254
+ args: ["[page-path]"],
255
+ mutates: true,
256
+ mcp: "create_page",
257
+ },
250
258
  ]);
251
259
  });
252
260
 
@@ -267,8 +275,25 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
267
275
  );
268
276
  }
269
277
  }
278
+ // A create verb carries flags if and only if one of its generators has a
279
+ // non-positional prompt (`page`, whose one prompt is positional, carries
280
+ // none) — checked per verb against the projected surface.
281
+ for (const verb of emitted.nouns.create?.verbs ?? []) {
282
+ const binding =
283
+ CREATE_GENERATORS[verb.v as keyof typeof CREATE_GENERATORS];
284
+ expect(binding, `no binding for create ${verb.v}`).toBeDefined();
285
+ const hasFlagPrompt = (binding.paths as readonly string[]).some(
286
+ (commandPath) =>
287
+ CREATE_SURFACE[commandPath]?.prompts.some(
288
+ (prompt) => prompt.positional !== true,
289
+ ) === true,
290
+ );
291
+ expect(
292
+ (verb.flags?.length ?? 0) > 0,
293
+ `create ${verb.v}: flags iff a non-positional prompt`,
294
+ ).toBe(hasFlagPrompt);
295
+ }
270
296
  for (const verb of emitted.nouns.create?.verbs ?? []) {
271
- expect(verb.flags?.length ?? 0).toBeGreaterThan(0);
272
297
  for (const token of verb.flags ?? []) {
273
298
  expect([...registered.values()]).toContain(token);
274
299
  }
@@ -378,14 +403,14 @@ describe("surface COMPLETE — emitted == covenant (PROTECTED)", () => {
378
403
  // The CLOSING direction: assertConforms already proves emitted ⊆ covenant;
379
404
  // this proves covenant ⊆ emitted, so together the tool sets are EQUAL — the
380
405
  // surface-complete milestone. After PR7, every covenant tool is realized.
381
- it("emits every covenant tool (all 50) — set equality with the covenant", () => {
406
+ it("emits every covenant tool (all 51) — set equality with the covenant", () => {
382
407
  const emittedTools = new Set(emitted.mcpSurface.tools);
383
408
  const missing = golden.mcpSurface.tools.filter((t) => !emittedTools.has(t));
384
409
  expect(missing).toEqual([]);
385
410
  expect([...emitted.mcpSurface.tools].sort()).toEqual(
386
411
  [...golden.mcpSurface.tools].sort(),
387
412
  );
388
- expect(emitted.mcpSurface.tools).toHaveLength(50);
413
+ expect(emitted.mcpSurface.tools).toHaveLength(51);
389
414
  });
390
415
 
391
416
  // The covenant edit: the non-tool MCP surface is frozen too.
@@ -339,7 +339,7 @@ const STRUCTURE = [
339
339
  at: "pragma create <TAB>",
340
340
  words: ["pragma", "create", ""],
341
341
  cword: 2,
342
- offers: ["application", "component", "package"],
342
+ offers: ["application", "component", "package", "page"],
343
343
  },
344
344
  {
345
345
  at: "pragma --format <TAB>",
@@ -546,7 +546,7 @@ describe.skipIf(!hasShell("fish"))(
546
546
  },
547
547
  {
548
548
  line: "pragma create ",
549
- offers: ["application", "component", "package"],
549
+ offers: ["application", "component", "package", "page"],
550
550
  },
551
551
  // fish's own inline convention: it returns the whole `--flag=value` word,
552
552
  // not the bare value bash offers. Assert what fish returns.
@@ -84,8 +84,8 @@ describe("surface conformance (PROTECTED)", () => {
84
84
  );
85
85
  });
86
86
 
87
- it("freezes exactly 50 designed MCP tools including info, the config family, colophon", () => {
88
- expect(golden.mcpSurface.tools).toHaveLength(50);
87
+ it("freezes exactly 51 designed MCP tools including info, the config family, colophon", () => {
88
+ expect(golden.mcpSurface.tools).toHaveLength(51);
89
89
  expect(golden.mcpSurface.tools).toContain("info");
90
90
  expect(golden.mcpSurface.tools).toContain("config_show");
91
91
  expect(golden.mcpSurface.tools).toContain("config_get");
@@ -136,7 +136,7 @@ assuming a mismatch is a bug:
136
136
  ## Extension points this PR left for you, by owner
137
137
 
138
138
  - **PR5 (`create`)** — `C1` in the port ledger. No journey exists yet for
139
- `create component/package/application` dry-run/real/byte-equality. Add
139
+ `create component/package/application/page` dry-run/real/byte-equality. Add
140
140
  `journeys.create.test.ts` here plus its own eval cases.
141
141
  - **PR6 (config mutation, info enrichment, doctor, upgrade, setup, `graph
142
142
  query`)** — `C2`/`C3`/`C4` — **BUILT**. Each new verb ships its own