@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
@@ -2,14 +2,20 @@
2
2
  * Formatters for `pragma sources status` — plain, llm, json.
3
3
  */
4
4
 
5
+ import { BIN_NAME } from "../../constants.js";
5
6
  import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
6
7
  import type { Formatters } from "../../kernel/spec/types.js";
7
8
  import type { SourcesStatusData } from "./types.js";
8
9
 
9
- const STALENESS_MARK: Record<string, string> = {
10
- "up-to-date": "ok",
11
- "config-drift": "config drift",
12
- uncached: "not built",
10
+ /**
11
+ * The headline for each boot decision. The `embedded` case names the update
12
+ * explicitly: the distribution's snapshot answers reads, but it is a snapshot,
13
+ * so reporting it as "up to date" would be a lie.
14
+ */
15
+ const STORE_HEADLINE: Record<SourcesStatusData["store"], string> = {
16
+ built: "ready",
17
+ embedded: `embedded snapshot (run \`${BIN_NAME} sources update\` to build from the configured packs)`,
18
+ unavailable: `not built (run \`${BIN_NAME} sources update\`)`,
13
19
  };
14
20
 
15
21
  /**
@@ -18,22 +24,19 @@ const STALENESS_MARK: Record<string, string> = {
18
24
  * @param data - The resolved sources-status payload.
19
25
  * @param style - TTY styling; defaults to the process style. On a color-capable
20
26
  * terminal the `Sources:` heading is bold, source names align into a column,
21
- * and each staleness marker is tinted (green up-to-date, else yellow); off a
22
- * TTY the styler is inert, so the output is byte-identical to the plain form.
27
+ * and each configured ref is dimmed; off a TTY the styler is inert, so the
28
+ * output is byte-identical to the plain form.
23
29
  * @returns The formatted status block.
24
30
  */
25
31
  export function renderSourcesStatusPlain(
26
32
  data: SourcesStatusData,
27
33
  style: RenderStyle = defaultStyle(),
28
34
  ): string {
29
- const lines = [
30
- data.lockPresent
31
- ? `Store: ${data.cached ? "ready" : "locked but not cached"}`
32
- : "Store: no lock (run `pragma sources update`)",
33
- ];
34
- if (data.cached) {
35
+ const lines = [`Store: ${STORE_HEADLINE[data.store]}`];
36
+ if (data.contentHash !== null) {
35
37
  lines.push(
36
- ` pack: ${data.contentHash?.slice(0, 12)} — ${data.entityCount ?? "?"} entities, built ${data.builtAt ?? "?"}`,
38
+ ` pack: ${data.contentHash.slice(0, 12)} — ${data.entityCount ?? "?"} entities, built ${data.builtAt ?? "?"}`,
39
+ ` from: ${data.sourceRef ?? "?"}`,
37
40
  );
38
41
  }
39
42
  lines.push("", style.enabled ? style.bold("Sources:") : "Sources:");
@@ -44,17 +47,10 @@ export function renderSourcesStatusPlain(
44
47
  ? Math.max(0, ...data.sources.map((source) => source.name.length))
45
48
  : 0;
46
49
  for (const source of data.sources) {
47
- const mark = STALENESS_MARK[source.staleness] ?? source.staleness;
48
- const resolved = source.resolved
49
- ? ` @ ${source.resolved.slice(0, 12)}`
50
- : "";
51
- if (!style.enabled) {
52
- lines.push(` ${source.name} [${mark}]${resolved}`);
53
- continue;
54
- }
55
- const tint = source.staleness === "up-to-date" ? style.green : style.yellow;
56
50
  lines.push(
57
- ` ${source.name.padEnd(nameWidth)} ${tint(`[${mark}]`)}${style.dim(resolved)}`,
51
+ style.enabled
52
+ ? ` ${source.name.padEnd(nameWidth)} ${style.dim(source.ref)}`
53
+ : ` ${source.name} ${source.ref}`,
58
54
  );
59
55
  }
60
56
  return lines.join("\n");
@@ -66,13 +62,12 @@ export const statusFormatters: Formatters<SourcesStatusData> = {
66
62
  },
67
63
 
68
64
  llm(data) {
69
- const lines = [
70
- `# sources`,
71
- `- Store: ${data.lockPresent ? (data.cached ? "ready" : "locked, not cached") : "no lock"}`,
72
- ...(data.cached ? [`- Entities: ${data.entityCount ?? "?"}`] : []),
73
- ];
65
+ const lines = [`# sources`, `- Store: ${data.store}`];
66
+ if (data.sourceRef !== null) lines.push(`- From: ${data.sourceRef}`);
67
+ if (data.entityCount !== null)
68
+ lines.push(`- Entities: ${data.entityCount}`);
74
69
  for (const source of data.sources) {
75
- lines.push(`- ${source.name}: ${source.staleness}`);
70
+ lines.push(`- ${source.name}: ${source.ref}`);
76
71
  }
77
72
  return lines.join("\n");
78
73
  },
@@ -2,12 +2,13 @@
2
2
  * The `sources status` verb — a storeless read.
3
3
  *
4
4
  * `needsStore: false` is load-bearing: status must report even a cold or
5
- * unbuilt store (staleness `uncached`), so it must NOT boot the store — booting
6
- * a cold store would throw STORE_UNAVAILABLE before status could say so. Its
7
- * `run` is a lazy thunk (the collector is dynamic-imported), keeping the config
8
- * reader off the fast path, exactly like `info`.
5
+ * unbuilt store (`store: "unavailable"`), so it must NOT boot the store —
6
+ * booting a cold store would throw STORE_UNAVAILABLE before status could say
7
+ * so. Its `run` is a lazy thunk (the collector is dynamic-imported), keeping
8
+ * the config reader off the fast path, exactly like `info`.
9
9
  */
10
10
 
11
+ import { BIN_NAME } from "../../constants.js";
11
12
  import type { VerbSpec } from "../../kernel/spec/types.js";
12
13
  import { statusFormatters } from "./status.render.js";
13
14
  import type { SourcesStatusData } from "./types.js";
@@ -18,13 +19,19 @@ export const statusVerb: VerbSpec<
18
19
  SourcesStatusData
19
20
  > = {
20
21
  path: ["sources", "status"],
21
- summary: "Report the local store's readiness and per-source staleness.",
22
- doc: "Storeless — reads the lock, config, and pack cache without booting the store, so it works even when the store is cold.",
22
+ summary: "Report which pack answers reads, and the packs it was built from.",
23
+ doc: "Storeless — reads config and the pack cache without booting the store, so it works even when the store is cold. Reports whether reads are answered by a locally built pack, by the embedded snapshot, or not at all.",
23
24
  params: [],
24
25
  output: { formatters: statusFormatters },
25
26
  examples: [
26
- { cmd: "pragma sources status", note: "human-readable readiness summary" },
27
- { cmd: "pragma sources status --format json", note: "the full envelope" },
27
+ {
28
+ cmd: `${BIN_NAME} sources status`,
29
+ note: "human-readable readiness summary",
30
+ },
31
+ {
32
+ cmd: `${BIN_NAME} sources status --format json`,
33
+ note: "the full envelope",
34
+ },
28
35
  ],
29
36
  capability: {
30
37
  needsStore: false,
@@ -1,37 +1,29 @@
1
1
  /**
2
2
  * Data shapes for the `sources` noun — `status` (storeless read) and `update`
3
- * (the Task that resolves, builds, and locks the store).
3
+ * (the Task that resolves, builds, and points the project at a pack).
4
4
  */
5
5
 
6
- /** How a configured source stands relative to the lock and the pack cache. */
7
- export type SourceStaleness = "up-to-date" | "config-drift" | "uncached";
8
-
9
- /** One configured source's status. */
10
- export interface SourceStatusEntry {
11
- /** Package name. */
12
- readonly name: string;
13
- /** The config `packages` source ref (verbatim). */
14
- readonly ref: string;
15
- /** The lock's resolved revision, or null when not locked. */
16
- readonly resolved: string | null;
17
- /** Whether the config ref matches the lock and the pack is cached. */
18
- readonly staleness: SourceStaleness;
19
- }
20
-
21
6
  /** The `sources status` payload — assembled without booting the store. */
22
7
  export interface SourcesStatusData {
23
8
  readonly cwd: string;
24
- /** Whether a `pragma.lock.json` is present. */
25
- readonly lockPresent: boolean;
26
- /** The locked combined-pack content hash, or null. */
9
+ /**
10
+ * Which pack answers reads, straight from the boot decision: the project's
11
+ * own `built` pack, the distribution's `embedded` snapshot, or none at all.
12
+ */
13
+ readonly store: "embedded" | "built" | "unavailable";
14
+ /** The answering pack's content hash, or null when there is none. */
27
15
  readonly contentHash: string | null;
28
- /** Whether the locked pack is present in the cache. */
29
- readonly cached: boolean;
30
- /** When the cached pack was built (manifest `createdAt`), or null. */
16
+ /** The answering pack's provenance label (manifest `sourceRef`), or null. */
17
+ readonly sourceRef: string | null;
18
+ /** When the answering pack was built (manifest `createdAt`), or null. */
31
19
  readonly builtAt: string | null;
32
- /** Total indexed entity count from the cached pack, or null. */
20
+ /** Total indexed entity count from the answering pack, or null. */
33
21
  readonly entityCount: number | null;
34
- readonly sources: readonly SourceStatusEntry[];
22
+ /** The configured pack declarations, as written in the config. */
23
+ readonly sources: readonly {
24
+ readonly name: string;
25
+ readonly ref: string;
26
+ }[];
35
27
  }
36
28
 
37
29
  /** One resolved source in the `sources update` result. */
@@ -39,6 +31,8 @@ export interface UpdatedSource {
39
31
  readonly name: string;
40
32
  readonly resolved: string;
41
33
  readonly sourceCount: number;
34
+ /** How many `stories/*.json` files this package contributed to the pack. */
35
+ readonly storyCount: number;
42
36
  }
43
37
 
44
38
  /** The `sources update` result payload. */
@@ -46,6 +40,5 @@ export interface SourcesUpdateData {
46
40
  readonly contentHash: string;
47
41
  /** Whether the pack was reused from cache rather than rebuilt. */
48
42
  readonly reused: boolean;
49
- readonly lockPath: string;
50
43
  readonly packs: readonly UpdatedSource[];
51
44
  }
@@ -11,11 +11,18 @@ export const updateFormatters: Formatters<SourcesUpdateData> = {
11
11
  data.reused
12
12
  ? `Store up to date (pack ${data.contentHash.slice(0, 12)}).`
13
13
  : `Built pack ${data.contentHash.slice(0, 12)}.`,
14
- `Wrote ${data.lockPath}.`,
15
14
  ];
16
15
  for (const pack of data.packs) {
16
+ // FILES, not usable stories: `sources update` carries them verbatim and
17
+ // never validates them (zod would have to run per record, in the build).
18
+ // `doctor` is where "2 stories ignored" comes from — saying "stories"
19
+ // here would put a green count next to a broken file.
20
+ const stories =
21
+ pack.storyCount > 0
22
+ ? `, ${pack.storyCount} story file${pack.storyCount === 1 ? "" : "s"}`
23
+ : "";
17
24
  lines.push(
18
- ` ${pack.name} @ ${pack.resolved.slice(0, 12)} (${pack.sourceCount} source${pack.sourceCount === 1 ? "" : "s"})`,
25
+ ` ${pack.name} @ ${pack.resolved.slice(0, 12)} (${pack.sourceCount} source${pack.sourceCount === 1 ? "" : "s"}${stories})`,
19
26
  );
20
27
  }
21
28
  return lines.join("\n");
@@ -1,35 +1,32 @@
1
1
  /**
2
- * The `sources update` verb — resolve, build, and lock the store.
2
+ * The `sources update` verb — resolve the configured packs, build the store,
3
+ * and point the project at it.
3
4
  *
4
5
  * A mutation (`mutates: true`) that needs the network (`needsNetwork: true`)
5
6
  * but NOT the pre-booted store (`needsStore: false`): update is what *creates*
6
7
  * the store, so the dispatcher must not try to boot it first (a cold boot would
7
8
  * throw STORE_UNAVAILABLE). `run` returns a `Promise<Task<R>>` — the union's
8
9
  * third arm — which the dispatcher awaits: for a real execution it resolves and
9
- * builds before handing back the lock-writing Task; for a preview
10
+ * builds before handing back the pointer-writing Task; for a preview
10
11
  * (`runtime.mutation.preview`) it stays network-free and hands back a
11
- * plan-only Task. `--frozen` re-resolves to the lock's pinned revisions.
12
+ * plan-only Task.
12
13
  */
13
14
 
14
15
  import type { Task } from "@canonical/task";
16
+ import { BIN_NAME } from "../../constants.js";
15
17
  import type { VerbSpec } from "../../kernel/spec/types.js";
16
18
  import type { SourcesUpdateData } from "./types.js";
17
19
  import { updateFormatters } from "./update.render.js";
18
20
 
19
21
  /** The `sources update` verb spec. */
20
22
  export const updateVerb: VerbSpec<
21
- { frozen?: boolean; skipInvalid?: boolean },
23
+ { skipInvalid?: boolean },
22
24
  SourcesUpdateData
23
25
  > = {
24
26
  path: ["sources", "update"],
25
- summary: "Resolve configured packages, build the local store, and lock it.",
26
- doc: "Resolves each configured package (git/file/npm), builds one content-addressed pack, and writes pragma.lock.json. Networkless boots then load from the lock.",
27
+ summary: "Resolve configured packs and build the local store from them.",
28
+ doc: "Resolves each configured pack (git/file/npm) and builds one content-addressed pack, which every later boot reads with no network access. Pin a revision by putting a commit SHA in the pack's source ref.",
27
29
  params: [
28
- {
29
- kind: "boolean",
30
- name: "frozen",
31
- doc: "Re-resolve to the lock's pinned revisions exactly; never advance.",
32
- },
33
30
  {
34
31
  kind: "boolean",
35
32
  name: "skipInvalid",
@@ -38,13 +35,9 @@ export const updateVerb: VerbSpec<
38
35
  ],
39
36
  output: { formatters: updateFormatters },
40
37
  examples: [
41
- { cmd: "pragma sources update", note: "resolve, build, and lock" },
42
- {
43
- cmd: "pragma sources update --frozen",
44
- note: "reproduce the locked state",
45
- },
38
+ { cmd: `${BIN_NAME} sources update`, note: "resolve and build" },
46
39
  {
47
- cmd: "pragma sources update --skip-invalid",
40
+ cmd: `${BIN_NAME} sources update --skip-invalid`,
48
41
  note: "build from the parseable sources, warning about any dropped",
49
42
  },
50
43
  ],
@@ -59,7 +52,7 @@ export const updateVerb: VerbSpec<
59
52
  },
60
53
  // `run` really returns `Promise<Task<R>>`: `buildUpdateTask` awaits config
61
54
  // (and, on a real execution, resolves + builds) before handing back the
62
- // lock-writing Task — or, when `runtime.mutation.preview` is set, a
55
+ // pointer-writing Task — or, when `runtime.mutation.preview` is set, a
63
56
  // network-free plan Task. The dispatcher and MCP handler both `await` this
64
57
  // promise into a `Task`. The `VerbSpec.run` union is deliberately two-armed
65
58
  // (`Promise<R> | Task<R>`) — a third `Promise<Task<R>>` arm would poison async
@@ -67,10 +60,6 @@ export const updateVerb: VerbSpec<
67
60
  // `Task<R>` arm by this single, honest cast.
68
61
  run: (params, runtime) =>
69
62
  import("./runUpdate.js").then((module) =>
70
- module.buildUpdateTask(
71
- runtime,
72
- params.frozen === true,
73
- params.skipInvalid === true,
74
- ),
63
+ module.buildUpdateTask(runtime, params.skipInvalid === true),
75
64
  ) as unknown as Task<SourcesUpdateData>,
76
65
  };
@@ -1,11 +1,13 @@
1
1
  import { readFileSync } from "node:fs";
2
2
  import { fileURLToPath } from "node:url";
3
3
  import { describe, expect, it } from "vitest";
4
+ import { MCP_SERVER_NAME, VERSION } from "../constants.js";
4
5
  import { emitSurface } from "../kernel/spec/emitSurface.js";
5
6
  import {
6
7
  assertConforms,
7
8
  type Covenant,
8
9
  } from "../kernel/spec/surfaceConformance.js";
10
+ import { projectMcp } from "../testing/helpers/projectMcp.js";
9
11
  import { capabilities } from "./index.js";
10
12
 
11
13
  /** The committed covenant, read from disk exactly as a consumer would. */
@@ -42,15 +44,15 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
42
44
  expect(emitted.nouns.__complete).toBeUndefined();
43
45
  });
44
46
 
45
- it("emits the read nouns/verbs the packs add (block list hand-written, sample only where declared)", () => {
47
+ it("emits the read nouns/verbs the packs add (sample only where declared)", () => {
46
48
  expect(emitted.nouns.standard?.verbs.map((v) => v.v)).toEqual([
47
49
  "list",
48
50
  "categories",
49
51
  "lookup",
50
52
  "sample",
51
53
  ]);
52
- // PR7 completes the surface: tier gains a bespoke single-name lookup;
53
- // block/modifier/token gain no-argument samples (fixedCount).
54
+ // PR7 completed the surface; block/modifier/token carry no-argument
55
+ // samples (fixedCount), tier declares no sample.
54
56
  expect(emitted.nouns.tier?.verbs.map((v) => v.v)).toEqual([
55
57
  "list",
56
58
  "lookup",
@@ -60,16 +62,18 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
60
62
  "lookup",
61
63
  "sample",
62
64
  ]);
65
+ // `token` is purely declarative since L-OPEN-9 removed `add-config`: the
66
+ // three read verbs its story compiles, and no mutation.
63
67
  expect(emitted.nouns.token?.verbs.map((v) => v.v)).toEqual([
64
68
  "list",
65
69
  "lookup",
66
70
  "sample",
67
- "add-config",
68
71
  ]);
72
+ // block list is the story's compiled, unfiltered list (L-OPEN-9): no
73
+ // flags — the `--all-tiers` escape died with the hand-written filtering.
69
74
  expect(emitted.nouns.block?.verbs).toEqual([
70
75
  {
71
76
  v: "list",
72
- flags: ["--all-tiers"],
73
77
  needsStore: true,
74
78
  mcp: "block_list",
75
79
  },
@@ -81,11 +85,11 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
81
85
  },
82
86
  { v: "sample", needsStore: true, mcp: "block_sample" },
83
87
  ]);
84
- // The bespoke tier lookup emits the SINGLE-name positional the covenant
85
- // freezes (a pack lookup would emit the variadic `<name...>`).
88
+ // The tier lookup is the story's compiled lookup (L-OPEN-9), so it emits
89
+ // the variadic `<name...>` positional every pack lookup emits.
86
90
  expect(emitted.nouns.tier?.verbs).toContainEqual({
87
91
  v: "lookup",
88
- args: ["<name>"],
92
+ args: ["<name...>"],
89
93
  needsStore: true,
90
94
  mcp: "tier_lookup",
91
95
  });
@@ -209,14 +213,14 @@ describe("surface COMPLETE — emitted == covenant (PROTECTED)", () => {
209
213
  // The CLOSING direction: assertConforms already proves emitted ⊆ covenant;
210
214
  // this proves covenant ⊆ emitted, so together the tool sets are EQUAL — the
211
215
  // surface-complete milestone. After PR7, every covenant tool is realized.
212
- it("emits every covenant tool (all 38) — set equality with the covenant", () => {
216
+ it("emits every covenant tool (all 37) — set equality with the covenant", () => {
213
217
  const emittedTools = new Set(emitted.mcpSurface.tools);
214
218
  const missing = golden.mcpSurface.tools.filter((t) => !emittedTools.has(t));
215
219
  expect(missing).toEqual([]);
216
220
  expect([...emitted.mcpSurface.tools].sort()).toEqual(
217
221
  [...golden.mcpSurface.tools].sort(),
218
222
  );
219
- expect(emitted.mcpSurface.tools).toHaveLength(38);
223
+ expect(emitted.mcpSurface.tools).toHaveLength(37);
220
224
  });
221
225
 
222
226
  // The covenant edit: the non-tool MCP surface is frozen too.
@@ -230,4 +234,23 @@ describe("surface COMPLETE — emitted == covenant (PROTECTED)", () => {
230
234
  golden.mcpSurface.instructions,
231
235
  );
232
236
  });
237
+
238
+ // The L-PR6 covenant edit (PRA-107 ruling): the wire-identity RULE. The
239
+ // covenant states serverInfo as placeholders — a PROJECTION, deliberately not
240
+ // live values, so a release does not churn the covenant — and the live
241
+ // handshake is asserted to follow it: the server introduces itself with the
242
+ // distribution's declared name and the package version. The fork half of the
243
+ // same rule (a renamed distribution introduces itself under ITS name) is
244
+ // pinned in identity.test.ts, where the conf is mocked.
245
+ it("states the serverInfo projection rule and serves it on the wire", async () => {
246
+ expect(golden.mcpSurface.serverInfo).toEqual({
247
+ name: "<the declared distribution name>",
248
+ version: "<the package version>",
249
+ });
250
+
251
+ const mcp = await projectMcp([]);
252
+ const wire = mcp.serverInfo();
253
+ await mcp.cleanup();
254
+ expect(wire).toEqual({ name: MCP_SERVER_NAME, version: VERSION });
255
+ });
233
256
  });
@@ -0,0 +1,144 @@
1
+ /**
2
+ * Tier-noun semantic parity (PROTECTED) — the `tier` story `pragma.conf.ts`
3
+ * declares, over a tier/block fixture graph (the standard-noun pattern).
4
+ *
5
+ * The hand-written `tier lookup` is gone (L-OPEN-9): both verbs compile from
6
+ * the declared story, so parity is asserted directly against the graph — every
7
+ * tier is reachable through list/lookup with the same names, addressable by
8
+ * name AND by prefixed IRI (the candidates completion now offers, D9-A), the
9
+ * `blocks` expand resolves the INVERSE path `^ds:tier` (the members scoped
10
+ * directly to the tier), a memberless tier still resolves, and a miss carries
11
+ * "did you mean?" suggestions like every declared lookup.
12
+ */
13
+
14
+ import { afterAll, beforeAll, describe, expect, it } from "vitest";
15
+ import { compilePack } from "../kernel/packs/compile.js";
16
+ import type { LookupOutput } from "../kernel/packs/resolveEntity.js";
17
+ import type { PackRow } from "../kernel/packs/types.js";
18
+ import { verbKey } from "../kernel/packs/uniqueness.js";
19
+ import { DEFAULT_PREFIX_MAP } from "../kernel/render/prefixes.js";
20
+ import type { PragmaRuntime } from "../kernel/runtime/types.js";
21
+ import type { VerbSpec } from "../kernel/spec/types.js";
22
+ import { buildFixtureRuntime } from "../testing/helpers/packRuntime.js";
23
+ import { declaredStories } from "./distribution.js";
24
+
25
+ const tierPack = declaredStories.get("tier");
26
+ if (!tierPack) {
27
+ throw new Error('pragma.conf.ts declares no story for "tier"');
28
+ }
29
+
30
+ const DS = "https://ds.canonical.com/";
31
+ const PREFIXES = {
32
+ ds: DS,
33
+ owl: "http://www.w3.org/2002/07/owl#",
34
+ rdfs: "http://www.w3.org/2000/01/rdf-schema#",
35
+ xsd: "http://www.w3.org/2001/XMLSchema#",
36
+ };
37
+
38
+ const TTL = `
39
+ @prefix ds: <${DS}> .
40
+ @prefix owl: <http://www.w3.org/2002/07/owl#> .
41
+ @prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
42
+ @prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
43
+
44
+ ds:Tier a owl:Class .
45
+ ds:Component a owl:Class .
46
+ ds:name a owl:DatatypeProperty ; rdfs:range xsd:string .
47
+ ds:tier a owl:ObjectProperty ; rdfs:range ds:Tier .
48
+
49
+ ds:global a ds:Tier ; ds:name "global" .
50
+ ds:apps a ds:Tier ; ds:name "apps" .
51
+ ds:apps_lxd a ds:Tier ; ds:name "apps/lxd" .
52
+
53
+ ds:button a ds:Component ; ds:name "Button" ; ds:tier ds:global .
54
+ ds:modal a ds:Component ; ds:name "Modal" ; ds:tier ds:global .
55
+ ds:lxdPanel a ds:Component ; ds:name "LXD Panel" ; ds:tier ds:apps_lxd .
56
+ `;
57
+
58
+ const verbs = () => compilePack(tierPack, "pragma.conf.ts", DEFAULT_PREFIX_MAP);
59
+ const verb = (label: string) =>
60
+ verbs().find((v) => verbKey(v.path) === `tier ${label}`) as VerbSpec;
61
+
62
+ let rt: PragmaRuntime;
63
+ beforeAll(async () => {
64
+ ({ rt } = await buildFixtureRuntime({ ttl: TTL, prefixes: PREFIXES }));
65
+ });
66
+ afterAll(async () => {
67
+ (await rt.store.get()).store.dispose();
68
+ });
69
+
70
+ async function lookup(name: string): Promise<Record<string, unknown>> {
71
+ const out = (await verb("lookup").run({ name: [name] }, rt)) as LookupOutput;
72
+ expect(out.errors).toEqual([]);
73
+ return out.results.at(0) as Record<string, unknown>;
74
+ }
75
+
76
+ /** The `blocks` expand's child names, sorted (the sub-SELECT is unordered). */
77
+ function blockNames(entity: Record<string, unknown>): string[] {
78
+ return (entity.blocks as { name: string }[]).map((b) => b.name).sort();
79
+ }
80
+
81
+ describe("tier list parity", () => {
82
+ it("lists every tier, name-ordered, with the uniform row shape", async () => {
83
+ const rows = (await verb("list").run({}, rt)) as PackRow[];
84
+ expect(rows.map((r) => r.name)).toEqual(["apps", "apps/lxd", "global"]);
85
+ for (const row of rows) {
86
+ expect(row.uri).toBeTruthy();
87
+ }
88
+ });
89
+ });
90
+
91
+ describe("tier lookup parity — the declared story's compiled lookup", () => {
92
+ it("resolves a tier by name and expands the blocks scoped directly to it", async () => {
93
+ const tier = await lookup("apps/lxd");
94
+ expect(tier.uri).toBe(`${DS}apps_lxd`);
95
+ expect(tier.name).toBe("apps/lxd");
96
+ // The `blocks` expand walks the INVERSE `^ds:tier` path (contract:
97
+ // `PackExpand.relation` admits property paths), so only the DIRECT
98
+ // members appear — no chain inheritance.
99
+ expect(blockNames(tier)).toEqual(["LXD Panel"]);
100
+ const global = await lookup("global");
101
+ expect(blockNames(global)).toEqual(["Button", "Modal"]);
102
+ });
103
+
104
+ it("a memberless tier still resolves, with an empty blocks expand", async () => {
105
+ const apps = await lookup("apps");
106
+ expect(apps.name).toBe("apps");
107
+ expect(apps.blocks).toEqual([]);
108
+ // The empty expand renders as an OMITTED section (the generic renderers'
109
+ // behavior for every declared noun), not a placeholder line.
110
+ const out = (await verb("lookup").run({ name: ["apps"] }, rt)) as never;
111
+ expect(verb("lookup").output.formatters.llm(out)).not.toContain("Blocks");
112
+ });
113
+
114
+ it("resolves the same tier by prefixed IRI — the candidates completion offers (D9-A)", async () => {
115
+ // Completion for the declared lookup serves index entity NAMES (prefixed
116
+ // IRIs); a candidate is only honest if the lookup resolves it.
117
+ const byIri = await lookup("ds:apps_lxd");
118
+ expect(byIri.name).toBe("apps/lxd");
119
+ expect(byIri.uri).toBe(`${DS}apps_lxd`);
120
+ });
121
+
122
+ it("resolves a variadic batch in one call", async () => {
123
+ const out = (await verb("lookup").run(
124
+ { name: ["global", "apps"] },
125
+ rt,
126
+ )) as LookupOutput;
127
+ expect(out.results.map((r) => r.name)).toEqual(["global", "apps"]);
128
+ });
129
+
130
+ it("a miss fails ENTITY_NOT_FOUND with a near-miss suggestion (B6 parity kept)", async () => {
131
+ // The bespoke lookup earned "did you mean?" in B6; the declared path keeps
132
+ // it — suggestions come from the same name list every declared lookup uses.
133
+ let caught: unknown;
134
+ try {
135
+ await verb("lookup").run({ name: ["apps/lxdd"] }, rt);
136
+ } catch (error) {
137
+ caught = error;
138
+ }
139
+ expect(caught).toMatchObject({ code: "ENTITY_NOT_FOUND" });
140
+ expect((caught as { suggestions: string[] }).suggestions).toContain(
141
+ "apps/lxd",
142
+ );
143
+ });
144
+ });
@@ -14,6 +14,7 @@
14
14
  */
15
15
 
16
16
  import type { Task } from "@canonical/task";
17
+ import { BIN_NAME } from "../../constants.js";
17
18
  import { asVerb } from "../../kernel/spec/asVerb.js";
18
19
  import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
19
20
  import type { UpgradeData } from "./types.js";
@@ -21,13 +22,16 @@ import { upgradeFormatters } from "./upgrade.render.js";
21
22
 
22
23
  const upgradeVerb: VerbSpec<Record<string, unknown>, UpgradeData> = {
23
24
  path: ["upgrade"],
24
- summary: "Upgrade the pragma CLI to the latest version.",
25
+ summary: `Upgrade the ${BIN_NAME} CLI to the latest version.`,
25
26
  doc: "Checks the registry for the active channel's latest release and runs your package manager's global-update command. Preview the update before applying it.",
26
27
  params: [],
27
28
  output: { formatters: upgradeFormatters },
28
29
  examples: [
29
- { cmd: "pragma upgrade" },
30
- { cmd: "pragma upgrade --dry-run", note: "show the delta and the command" },
30
+ { cmd: `${BIN_NAME} upgrade` },
31
+ {
32
+ cmd: `${BIN_NAME} upgrade --dry-run`,
33
+ note: "show the delta and the command",
34
+ },
31
35
  ],
32
36
  capability: {
33
37
  needsStore: false,
@@ -3,6 +3,7 @@ import {
3
3
  BIN_NAME,
4
4
  DEFAULT_DETAIL_LEVEL,
5
5
  DETAIL_LEVELS,
6
+ ISSUES_URL,
6
7
  MCP_SERVER_NAME,
7
8
  OUTPUT_FORMATS,
8
9
  PROGRAM_DESCRIPTION,
@@ -11,23 +12,24 @@ import {
11
12
  } from "./constants.js";
12
13
 
13
14
  describe("constants", () => {
14
- it("names the v2 bin and the MCP server pragma", () => {
15
+ it("projects the shipped distribution's identity", () => {
16
+ // Every value here is read from `pragma.conf.ts`, so this pins what THIS
17
+ // distribution ships. That the projection is real — a fork changing the
18
+ // config changes the CLI — is proven by `src/identity.test.ts`.
15
19
  expect(BIN_NAME).toBe("pragma");
16
20
  expect(MCP_SERVER_NAME).toBe("pragma");
21
+ expect(PROGRAM_DESCRIPTION).toBe("Explore the design system");
22
+ expect(RECOVERY_CLI_PREFIX).toBe("pragma ");
23
+ expect(ISSUES_URL).toBe("https://github.com/canonical/pragma/issues");
17
24
  });
18
25
 
19
26
  it("reads a semver version from package.json", () => {
20
27
  expect(VERSION).toMatch(/^\d+\.\d+\.\d+/);
21
28
  });
22
29
 
23
- it("keeps the recovery prefix on the stable command name (D5)", () => {
24
- expect(RECOVERY_CLI_PREFIX).toBe("pragma ");
25
- });
26
-
27
30
  it("exposes the stable output formats and detail levels", () => {
28
31
  expect(OUTPUT_FORMATS).toEqual(["plain", "llm", "json"]);
29
32
  expect(DETAIL_LEVELS).toEqual(["summary", "standard", "detailed"]);
30
33
  expect(DETAIL_LEVELS).toContain(DEFAULT_DETAIL_LEVEL);
31
- expect(PROGRAM_DESCRIPTION.length).toBeGreaterThan(0);
32
34
  });
33
35
  });