@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
@@ -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
- expect(OUTPUT_FORMATS).toEqual(["plain", "json"]);
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
  });
package/src/constants.ts CHANGED
@@ -1,48 +1,72 @@
1
1
  /**
2
- * Package-level constants for the pragma v2 kernel.
2
+ * Package-level constants — the distribution's identity, projected.
3
3
  *
4
- * Program metadata (bin name, MCP server name, description, version) plus the
5
- * stable cross-cutting enums (output formats, detail levels). The CLI binary
6
- * and the MCP server share the single `pragma` identity, so existing agent
7
- * wiring keeps resolving the server unchanged.
4
+ * The bin name, MCP server name, description and issues URL are NOT authored
5
+ * here: they are read from the distribution config (`pragma.conf.ts`), so a fork
6
+ * changes values, not code. The import is static because the surfaces that need
7
+ * identity — `--help`, `__complete`, first-run onboarding, the MCP handshake —
8
+ * all run before or without the config layer. That is safe precisely because
9
+ * `pragma.conf.ts` is inert data: a plain object literal whose only import is a
10
+ * type. It reaches no zod schema, no fs, no evaluator. `capabilities/lazy.test.ts`
11
+ * pins that boundary, and `src/identity.test.ts` proves the projection.
12
+ *
13
+ * Also holds the stable cross-cutting enums: output formats here, and the
14
+ * detail levels re-exported from `kernel/config/types.ts`, where the config
15
+ * validator closes the `detail` field over them.
8
16
  */
9
17
 
10
18
  import pkg from "../package.json" with { type: "json" };
19
+ import identity from "../pragma.conf.js";
20
+ import { DETAIL_LEVELS, type DetailLevel } from "./kernel/config/types.js";
11
21
 
12
- /** CLI binary name for the v2 kernel (installed as `pragma`). */
13
- const BIN_NAME = "pragma";
22
+ /** CLI binary name — the distribution's `name`. */
23
+ const BIN_NAME = identity.name;
14
24
 
15
25
  /**
16
- * MCP server identity — `pragma`, matching the CLI bin, so agents already
17
- * pointed at the pragma server resolve it unchanged.
26
+ * MCP server identity — the same name as the CLI bin, so agents already pointed
27
+ * at the server resolve it unchanged. Also the `serverInfo.name` the server
28
+ * introduces itself with on the wire (paired with {@link VERSION} by
29
+ * `buildServer`), so a peer reads the distribution's declared name, never a
30
+ * hardcoded one.
18
31
  */
19
- const MCP_SERVER_NAME = "pragma";
32
+ const MCP_SERVER_NAME = identity.name;
20
33
 
21
34
  /** One-line program description for help and server metadata. */
22
- const PROGRAM_DESCRIPTION = "CLI and MCP server for Canonical's design system.";
35
+ const PROGRAM_DESCRIPTION = identity.help;
36
+
37
+ /** Where the distribution asks users to file issues and feedback. */
38
+ const ISSUES_URL = identity.issuesUrl;
23
39
 
24
40
  /** Semver version string read from package.json. */
25
41
  const VERSION: string = pkg.version;
26
42
 
27
43
  /**
28
- * Literal prefix for every `recovery.cli` hint (`pragma `): recovery strings
29
- * quote the stable, documented command name — matching the bin (D5). The
30
- * recovery invariant test enforces this.
44
+ * The project config filename the walker looks for and every surface that
45
+ * quotes it — diagnostics, the onboarding note, the generated reference, the
46
+ * surface covenant — names. It lives HERE rather than beside the walker so the
47
+ * quoting surfaces do not have to import `kernel/config/**`: the storeless
48
+ * `--help`/`__complete` graph is asserted to reach no config module, and
49
+ * `completion/safety.test.ts` fails the moment one arrives.
31
50
  */
32
- const RECOVERY_CLI_PREFIX = "pragma ";
51
+ const PROJECT_CONFIG_FILENAME = `${identity.name}.config.ts`;
33
52
 
34
- /** The two machine/human output formats the renderer selects between. */
35
- const OUTPUT_FORMATS = ["plain", "json"] as const;
53
+ /**
54
+ * The distribution's name plus a space: the prefix a `recovery.cli` hint carries
55
+ * so it quotes a command the installed binary answers to (D5). Kernel hints are
56
+ * authored through `cliRecovery`, which prepends it; `packs/schema.ts` REJECTS
57
+ * it in a user-authored pack's `emptyRecovery.cli`, because the CONSUMING
58
+ * distribution's renderer supplies its own. Use {@link BIN_NAME} for prose —
59
+ * this constant's trailing space belongs to the recovery invariant, not to
60
+ * sentences.
61
+ */
62
+ const RECOVERY_CLI_PREFIX = `${identity.name} `;
63
+
64
+ /** The output formats the renderer selects between (`llm` = condensed Markdown). */
65
+ const OUTPUT_FORMATS = ["plain", "llm", "json"] as const;
36
66
 
37
67
  /** A selected output format. `--format text` is normalised to `plain`. */
38
68
  type OutputFormat = (typeof OUTPUT_FORMATS)[number];
39
69
 
40
- /** Progressive-disclosure levels, least to most detail. */
41
- const DETAIL_LEVELS = ["summary", "standard", "detailed"] as const;
42
-
43
- /** A progressive-disclosure level. */
44
- type DetailLevel = (typeof DETAIL_LEVELS)[number];
45
-
46
70
  /** Default detail level when neither flag, config, nor spec pins one. */
47
71
  const DEFAULT_DETAIL_LEVEL: DetailLevel = "standard";
48
72
 
@@ -51,9 +75,11 @@ export {
51
75
  BIN_NAME,
52
76
  DEFAULT_DETAIL_LEVEL,
53
77
  DETAIL_LEVELS,
78
+ ISSUES_URL,
54
79
  MCP_SERVER_NAME,
55
80
  OUTPUT_FORMATS,
56
81
  PROGRAM_DESCRIPTION,
82
+ PROJECT_CONFIG_FILENAME,
57
83
  RECOVERY_CLI_PREFIX,
58
84
  VERSION,
59
85
  };
@@ -0,0 +1,333 @@
1
+ import { mkdtempSync } from "node:fs";
2
+ import { tmpdir } from "node:os";
3
+ import { basename, join } from "node:path";
4
+ import chalk from "chalk";
5
+ import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
6
+ import type { VerbSpec } from "./kernel/spec/types.js";
7
+
8
+ // A fork's distribution config. Everything the kernel says about itself must
9
+ // follow from THIS object — that is the whole claim under test.
10
+ vi.mock("../pragma.conf.js", () => ({
11
+ default: {
12
+ name: "recipes",
13
+ help: "Explore the recipe graph",
14
+ colophon: {
15
+ markdown: "Made by the kitchen, one dish at a time.",
16
+ summary: "Kitchen-made.",
17
+ },
18
+ issuesUrl: "https://example.invalid/recipes/issues",
19
+ packs: [],
20
+ prefixes: { rcp: "https://example.invalid/recipes/" },
21
+ channel: "normal",
22
+ detail: "standard",
23
+ },
24
+ // The fork's domain vocabulary, declared beside its identity. Every read of
25
+ // a domain term must follow from THIS object, which the last case below
26
+ // asserts by capturing the SPARQL the readers actually emit.
27
+ vocabulary: {
28
+ altName: "rcp:name",
29
+ prompt: {
30
+ type: "rcp:Prompt",
31
+ body: "rcp:promptBody",
32
+ argument: "rcp:promptArgument",
33
+ argName: "rcp:argName",
34
+ argRequired: "rcp:argRequired",
35
+ },
36
+ },
37
+ }));
38
+
39
+ /** Anything that would betray THIS distribution leaking through the kernel. */
40
+ const THIS_DISTRIBUTION = /pragma|canonical|design[- ]system/i;
41
+
42
+ /**
43
+ * THIS distribution's name alone. The generated reference legitimately carries
44
+ * bundled-pack CONTENT a fork owns and edits — `@canonical/…` package names in
45
+ * `create` examples, `ds:`/`cs:` entity values — so the reference probe below
46
+ * is scoped to the one token that must never survive a rename.
47
+ */
48
+ const THIS_NAME = /\bpragma\b/i;
49
+
50
+ // chalk paints ANSI when GITHUB_ACTIONS is set even off a TTY, which would
51
+ // break the plain-text structure assertions below.
52
+ const prevChalkLevel = chalk.level;
53
+ chalk.level = 0;
54
+ afterAll(() => {
55
+ chalk.level = prevChalkLevel;
56
+ });
57
+
58
+ const originalConfigHome = process.env.XDG_CONFIG_HOME;
59
+ const originalDataHome = process.env.XDG_DATA_HOME;
60
+ afterEach(() => {
61
+ process.env.XDG_CONFIG_HOME = originalConfigHome;
62
+ process.env.XDG_DATA_HOME = originalDataHome;
63
+ });
64
+
65
+ /** A pack-contributed noun the kernel has never heard of. */
66
+ const dishList: VerbSpec = {
67
+ path: ["dish", "list"],
68
+ summary: "List every dish in the graph.",
69
+ params: [],
70
+ output: {
71
+ formatters: {
72
+ plain: (d) => String(d),
73
+ llm: (d) => String(d),
74
+ json: (d) => JSON.stringify(d),
75
+ },
76
+ },
77
+ capability: { needsStore: false, mutates: false, mcp: { expose: true } },
78
+ run: async () => null,
79
+ };
80
+
81
+ describe("identity projection — a fork changes values, not code (PROTECTED)", () => {
82
+ it("names the bin, the MCP server, and the recovery prefix from the distribution", async () => {
83
+ const c = await import("./constants.js");
84
+ expect(c.BIN_NAME).toBe("recipes");
85
+ expect(c.MCP_SERVER_NAME).toBe("recipes");
86
+ expect(c.RECOVERY_CLI_PREFIX).toBe("recipes ");
87
+ expect(c.PROGRAM_DESCRIPTION).toBe("Explore the recipe graph");
88
+ expect(c.ISSUES_URL).toBe("https://example.invalid/recipes/issues");
89
+ });
90
+
91
+ it("installs its completion script under the fork's name, in all three shells", async () => {
92
+ // The basename is load-bearing, not cosmetic — `shell.ts` records what each
93
+ // shell measurably does with it (fish and bash-completion autoload BY the
94
+ // command's name; zsh binds by the `#compdef` tag but autoloads a function
95
+ // named after the file). A mismatch installs a file the shell never loads
96
+ // while `setup completions` prints success, `doctor` passes, and TAB does
97
+ // nothing. It can only be pinned from a fork: with the distribution named
98
+ // `pragma` a hardcoded literal and `BIN_NAME` agree by construction, so a
99
+ // test in `setup.test.ts` cannot fail. Here they cannot agree by accident.
100
+ const { completionScriptPath } = await import(
101
+ "./capabilities/setup/shell.js"
102
+ );
103
+ expect(basename(completionScriptPath("zsh"))).toBe("_recipes");
104
+ expect(basename(completionScriptPath("bash"))).toBe("recipes");
105
+ expect(basename(completionScriptPath("fish"))).toBe("recipes.fish");
106
+ });
107
+
108
+ it("renders a front door that names only the fork", async () => {
109
+ const { BIN_NAME, PROGRAM_DESCRIPTION } = await import("./constants.js");
110
+ const { formatRootHelp } = await import("./kernel/project/cli/rootHelp.js");
111
+ const { configModule } = await import("./capabilities/config/index.js");
112
+ const { upgradeModule } = await import("./capabilities/upgrade/index.js");
113
+ const { colophonModule } = await import("./capabilities/colophon/index.js");
114
+
115
+ const help = formatRootHelp(BIN_NAME, PROGRAM_DESCRIPTION, [
116
+ dishList,
117
+ ...configModule.verbs,
118
+ ...upgradeModule.verbs,
119
+ ...colophonModule.verbs,
120
+ ]);
121
+
122
+ expect(help).not.toMatch(THIS_DISTRIBUTION);
123
+ expect(help).toMatch(/^recipes — Explore the recipe graph$/m);
124
+ // The pack noun leads the page untitled, and the blurb is not repeated as
125
+ // a heading three lines under the header that already carries it.
126
+ expect(help).toMatch(/^Usage: recipes .*\n\n {2}dish\b/m);
127
+ expect(help.match(/Explore the recipe graph/g)).toHaveLength(1);
128
+ expect(help).toContain("Read and write recipes configuration");
129
+ });
130
+
131
+ it("orients an MCP agent with the fork's identity", async () => {
132
+ const { buildInstructions } = await import(
133
+ "./kernel/project/mcp/instructions.js"
134
+ );
135
+ const { emitSurface } = await import("./kernel/spec/emitSurface.js");
136
+ const { capabilities } = await import("./capabilities/index.js");
137
+
138
+ // The `pragma:` resource scheme is covenant-frozen PROTOCOL identity
139
+ // (surface.v2.json), not copy. The orientation DERIVES it from the emitted
140
+ // surface, so mask exactly what the surface declares — a leak the kernel
141
+ // authored itself would survive this substitution and fail below.
142
+ const { resources } = emitSurface(capabilities).mcpSurface;
143
+ expect(resources.length).toBeGreaterThan(0);
144
+ const text = resources.reduce(
145
+ (orientation, template) => orientation.replace(template, "<resource>"),
146
+ buildInstructions(capabilities),
147
+ );
148
+
149
+ expect(text).not.toMatch(THIS_DISTRIBUTION);
150
+ // No punctuation is appended to the fork's `help` phrase.
151
+ expect(text.startsWith("recipes — Explore the recipe graph (")).toBe(true);
152
+ });
153
+
154
+ it("greets a first-time user with the fork's issues URL and config file", async () => {
155
+ process.env.XDG_CONFIG_HOME = mkdtempSync(join(tmpdir(), "identity-"));
156
+ const { ensureFirstRun } = await import("./kernel/config/firstRun.js");
157
+ const { globalConfigPath } = await import("./kernel/config/paths.js");
158
+
159
+ const lines: string[] = [];
160
+ await ensureFirstRun((line) => lines.push(line));
161
+ const greeting = lines.join("\n");
162
+
163
+ // Nothing is masked: the resolved config path is IN the greeting, so the
164
+ // XDG namespace has to follow the fork's name too or this assertion fails.
165
+ expect(greeting).not.toMatch(THIS_DISTRIBUTION);
166
+ expect(greeting).toContain(globalConfigPath());
167
+ expect(globalConfigPath()).toContain("/recipes/");
168
+ expect(greeting).toContain("https://example.invalid/recipes/issues");
169
+ expect(greeting).toContain("`recipes.config.ts`");
170
+ });
171
+
172
+ it("narrates the colophon the fork declares, titled with the fork's name", async () => {
173
+ // The toolchain colophon is DECLARED content (`pragma.conf.ts#colophon`),
174
+ // not authored code: the collector must render the fork's declaration
175
+ // under the fork's own name. With no packs declared, the fork's whole
176
+ // colophon is its own story. `kind: "pragma"` is asserted AS the literal:
177
+ // it is the frozen JSON discriminant every distribution serves (wire
178
+ // compatibility, like the resource scheme), deliberately NOT derived.
179
+ process.env.XDG_CONFIG_HOME = mkdtempSync(join(tmpdir(), "identity-"));
180
+ const { collectColophon } = await import(
181
+ "./capabilities/colophon/collectColophon.js"
182
+ );
183
+ const { bootRuntime } = await import("./kernel/runtime/boot.js");
184
+
185
+ const runtime = bootRuntime(
186
+ { llm: false, autoLlm: false, format: "plain", verbose: false },
187
+ mkdtempSync(join(tmpdir(), "identity-colophon-")),
188
+ );
189
+ const { sections } = await collectColophon(runtime);
190
+
191
+ expect(sections).toHaveLength(1);
192
+ const own = sections[0];
193
+ expect(own?.kind).toBe("pragma");
194
+ expect(own?.title).toBe("recipes");
195
+ expect(own?.markdown).toBe("Made by the kitchen, one dish at a time.");
196
+ expect(own?.summary).toBe("Kitchen-made.");
197
+ expect(own?.source).toBe("built-in");
198
+ for (const text of [own?.title, own?.markdown, own?.summary]) {
199
+ expect(text).not.toMatch(THIS_DISTRIBUTION);
200
+ }
201
+ });
202
+
203
+ it("owns its own on-disk skills namespace", async () => {
204
+ // Config, state and cache were namespaced by the bin name; the SKILLS roots
205
+ // were not. Two distributions installed side by side therefore shared one
206
+ // `$XDG_DATA_HOME/pragma/skills` and one `<cwd>/.pragma/skills`, so a fork
207
+ // read the other's skills and could not install its own without collision.
208
+ process.env.XDG_DATA_HOME = join(tmpdir(), "identity-data");
209
+ const { skillRoots, installedSkillsDir } = await import(
210
+ "./capabilities/skill/discover.js"
211
+ );
212
+ expect(installedSkillsDir().endsWith(join("recipes", "skills"))).toBe(true);
213
+ expect(skillRoots("/work")).toEqual([
214
+ join("/work", ".recipes", "skills"),
215
+ installedSkillsDir(),
216
+ ]);
217
+ for (const root of skillRoots("/work")) {
218
+ expect(root).not.toMatch(THIS_DISTRIBUTION);
219
+ }
220
+ });
221
+
222
+ it("introduces its MCP server on the wire under the fork's name", async () => {
223
+ // The wire half of the identity rule the covenant records
224
+ // (mcpSurface.serverInfo): serverInfo PROJECTS — name from the declared
225
+ // distribution, version from the package. Asserted through a real
226
+ // initialize handshake (in-memory transport), not on the constant, so a
227
+ // hardcoded serverInfo in buildServer would fail here even while
228
+ // MCP_SERVER_NAME projects correctly.
229
+ const { projectMcp } = await import("./testing/helpers/projectMcp.js");
230
+ const { VERSION } = await import("./constants.js");
231
+
232
+ const mcp = await projectMcp([]);
233
+ const wire = mcp.serverInfo();
234
+ await mcp.cleanup();
235
+
236
+ expect(wire).toEqual({ name: "recipes", version: VERSION });
237
+ expect(wire?.name).not.toMatch(THIS_DISTRIBUTION);
238
+ });
239
+
240
+ it("registers an MCP server entry the fork's own binary answers to", async () => {
241
+ // The entry `setup mcp` writes is what a harness later EXECUTES. It was
242
+ // hardcoded while the key it is stored under derived from the same
243
+ // identity, so a fork registered a `recipes` server that ran `pragma` —
244
+ // and `doctor`'s `MCP configured` check, which re-derived the key by hand,
245
+ // could never see its own registration.
246
+ const { pragmaMcpEntry } = await import(
247
+ "./capabilities/setup/operations/setupMcp.js"
248
+ );
249
+ expect(pragmaMcpEntry("/work")).toEqual({
250
+ command: "recipes",
251
+ args: ["mcp"],
252
+ cwd: "/work",
253
+ });
254
+ });
255
+
256
+ it("generates a reference that never names this distribution", async () => {
257
+ // `docs/reference/` is the surface this package PUBLISHES as machine-derived
258
+ // truth, so it is the surface a fork's rename must reach in full. Every
259
+ // command a page quotes, every page title and every prose mention is
260
+ // composed from `BIN_NAME`. The failure names `page: line`, so it is a
261
+ // worklist and not just a count.
262
+ //
263
+ // ONE exemption, the same one the MCP orientation case above makes and for
264
+ // the same reason: the `pragma:` resource scheme is covenant-frozen PROTOCOL
265
+ // identity (`surface.v2.json`), inherited by a fork along with the
266
+ // `pragma/box` and `pragma/instanceCount` `_meta` keys it travels with, and
267
+ // `tools.md` reports it truthfully. Masked from the emitted surface, not by
268
+ // a literal, so a leak the kernel authored itself still fails here.
269
+ const { emitReference } = await import("./kernel/spec/emitReference.js");
270
+ const { emitSurface } = await import("./kernel/spec/emitSurface.js");
271
+ const { capabilities } = await import("./capabilities/index.js");
272
+
273
+ const { resources } = emitSurface(capabilities).mcpSurface;
274
+ expect(resources.length).toBeGreaterThan(0);
275
+ const unmask = (line: string): string =>
276
+ resources.reduce((text, template) => text.replace(template, ""), line);
277
+
278
+ const offenders: string[] = [];
279
+ for (const [page, content] of emitReference(capabilities)) {
280
+ for (const line of content.split("\n")) {
281
+ if (THIS_NAME.test(unmask(line))) offenders.push(`${page}: ${line}`);
282
+ }
283
+ }
284
+ expect(offenders).toEqual([]);
285
+ });
286
+
287
+ it("reads the graph with the fork's declared terms, not this distribution's", async () => {
288
+ // The couplings this tranche removed were all hardcoded terms, and a
289
+ // hardcoded term still passes every other test in this suite: the fixtures
290
+ // on both sides would simply agree. So capture what the readers EMIT.
291
+ const { DEFAULT_PREFIX_MAP } = await import("./kernel/render/prefixes.js");
292
+ const { VOCABULARY } = await import("./kernel/vocabulary.js");
293
+ const { readPrompts } = await import(
294
+ "./kernel/project/mcp/prompts/source.js"
295
+ );
296
+
297
+ // The display/expansion map: the fork's namespaces, none of ours, and the
298
+ // W3C half the kernel owns unchanged.
299
+ expect(DEFAULT_PREFIX_MAP.rcp).toBe("https://example.invalid/recipes/");
300
+ expect(DEFAULT_PREFIX_MAP.ds).toBeUndefined();
301
+ expect(DEFAULT_PREFIX_MAP.rdfs).toBe(
302
+ "http://www.w3.org/2000/01/rdf-schema#",
303
+ );
304
+
305
+ // The tier noun is no longer code AT ALL (L-OPEN-9): both its verbs are a
306
+ // story the distribution declares, so a fork writes its own terms into the
307
+ // declaration directly and there is nothing left in the kernel for a
308
+ // hardcoded tier term to hide in. `distribution.test.ts` holds this
309
+ // distribution's declaration coherent with the vocabulary. What the KERNEL
310
+ // still reads domain terms with is the vocabulary projection (the index
311
+ // builder interpolates `VOCABULARY.altName`) and the prompts reader —
312
+ // captured below.
313
+ expect(VOCABULARY.altName).toBe("rcp:name");
314
+
315
+ // The generated prompt read, captured off a stub facade.
316
+ const queries: string[] = [];
317
+ const recorder = {
318
+ query: {
319
+ sparql: (query: string) => {
320
+ queries.push(query);
321
+ return Promise.resolve({ type: "select", bindings: [] });
322
+ },
323
+ },
324
+ } as never;
325
+
326
+ await expect(readPrompts(recorder)).resolves.toEqual([]);
327
+
328
+ const emitted = queries.join("\n");
329
+ expect(emitted).toContain("rcp:Prompt");
330
+ expect(emitted).toContain("rcp:promptBody");
331
+ expect(emitted).not.toMatch(THIS_DISTRIBUTION);
332
+ });
333
+ });
package/src/index.ts CHANGED
@@ -22,8 +22,8 @@ export type {
22
22
  Formatters,
23
23
  McpAnnotations,
24
24
  OutputFormat,
25
- PackageDeclaration,
26
- PackageEntry,
25
+ PackDeclaration,
26
+ PackSource,
27
27
  ParamComplete,
28
28
  ParamSpec,
29
29
  PragmaConfig,
@@ -58,21 +58,21 @@ _pragma() {
58
58
  block/list/--tags) return ;;
59
59
  esac
60
60
  case "$prev" in
61
- --format) COMPREPLY=($(compgen -W "plain json" -- "$cur")); return ;;
61
+ --format) COMPREPLY=($(compgen -W "plain llm json" -- "$cur")); return ;;
62
62
  --detail) COMPREPLY=($(compgen -W "summary standard detailed" -- "$cur")); return ;;
63
63
  esac
64
64
 
65
65
  # flag names
66
66
  if [[ "$cur" == -* ]]; then
67
67
  case "$noun/$verb" in
68
- block/get) COMPREPLY=($(compgen -W "--view --out --with-meta --note --llm --format --verbose --detail --help" -- "$cur")); return ;;
69
- block/list) COMPREPLY=($(compgen -W "--tags --llm --format --verbose --detail --help" -- "$cur")); return ;;
70
- block/remove) COMPREPLY=($(compgen -W "--dry-run --undo --yes --llm --format --verbose --detail --help" -- "$cur")); return ;;
68
+ block/get) COMPREPLY=($(compgen -W "--view --out --with-meta --note --format --verbose --detail --help" -- "$cur")); return ;;
69
+ block/list) COMPREPLY=($(compgen -W "--tags --format --verbose --detail --help" -- "$cur")); return ;;
70
+ block/remove) COMPREPLY=($(compgen -W "--dry-run --undo --yes --format --verbose --detail --help" -- "$cur")); return ;;
71
71
  esac
72
72
  if [ -z "$noun" ]; then
73
- COMPREPLY=($(compgen -W "--llm --format --verbose --detail --help --version" -- "$cur"))
73
+ COMPREPLY=($(compgen -W "--format --verbose --detail --help --version" -- "$cur"))
74
74
  else
75
- COMPREPLY=($(compgen -W "--llm --format --verbose --detail --help" -- "$cur"))
75
+ COMPREPLY=($(compgen -W "--format --verbose --detail --help" -- "$cur"))
76
76
  fi
77
77
  return
78
78
  fi
@@ -142,6 +142,9 @@ end
142
142
  function __pragma_minchars
143
143
  test (string length -- (commandline -ct)) -ge $argv[1]
144
144
  end
145
+ function __pragma_notflag
146
+ not string match -q -- '-*' (commandline -ct)
147
+ end
145
148
  function __pragma_at
146
149
  set -l cmd (commandline -opc)
147
150
  set -l noun ''
@@ -172,8 +175,7 @@ complete -c pragma -f
172
175
  complete -c pragma -n "__pragma_at ''" -a "block mcp standard status"
173
176
 
174
177
  # global flags
175
- complete -c pragma -l llm
176
- complete -c pragma -l format -x -a "plain json"
178
+ complete -c pragma -l format -x -a "plain llm json"
177
179
  complete -c pragma -l verbose
178
180
  complete -c pragma -l detail -x -a "summary standard detailed"
179
181
  complete -c pragma -l help
@@ -186,14 +188,14 @@ complete -c pragma -n "__pragma_at block" -a "diff get list remove"
186
188
  complete -c pragma -n "__pragma_at standard" -a "list"
187
189
 
188
190
  # block/diff
189
- complete -c pragma -n "__pragma_at block diff; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
191
+ complete -c pragma -n "__pragma_at block diff; and __pragma_notflag; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
190
192
 
191
193
  # block/get
192
194
  complete -c pragma -n "__pragma_at block get" -l view -x -a "anatomy full summary"
193
195
  complete -c pragma -n "__pragma_at block get" -l out -rF
194
196
  complete -c pragma -n "__pragma_at block get" -l with-meta
195
197
  complete -c pragma -n "__pragma_at block get" -l note -x
196
- complete -c pragma -n "__pragma_at block get; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
198
+ complete -c pragma -n "__pragma_at block get; and __pragma_notflag; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
197
199
 
198
200
  # block/list
199
201
  complete -c pragma -n "__pragma_at block list" -l tags -x
@@ -268,21 +270,21 @@ _pragma() {
268
270
  block/list/--tags) return 0 ;;
269
271
  esac
270
272
  case "$prev" in
271
- --format) compadd -- plain json; return 0 ;;
273
+ --format) compadd -- plain llm json; return 0 ;;
272
274
  --detail) compadd -- summary standard detailed; return 0 ;;
273
275
  esac
274
276
 
275
277
  # flag names
276
278
  if [[ "$cur" == -* ]]; then
277
279
  case "$noun/$verb" in
278
- block/get) compadd -- --view --out --with-meta --note --llm --format --verbose --detail --help; return 0 ;;
279
- block/list) compadd -- --tags --llm --format --verbose --detail --help; return 0 ;;
280
- block/remove) compadd -- --dry-run --undo --yes --llm --format --verbose --detail --help; return 0 ;;
280
+ block/get) compadd -- --view --out --with-meta --note --format --verbose --detail --help; return 0 ;;
281
+ block/list) compadd -- --tags --format --verbose --detail --help; return 0 ;;
282
+ block/remove) compadd -- --dry-run --undo --yes --format --verbose --detail --help; return 0 ;;
281
283
  esac
282
284
  if [[ -z "$noun" ]]; then
283
- compadd -- --llm --format --verbose --detail --help --version
285
+ compadd -- --format --verbose --detail --help --version
284
286
  else
285
- compadd -- --llm --format --verbose --detail --help
287
+ compadd -- --format --verbose --detail --help
286
288
  fi
287
289
  return 0
288
290
  fi
@@ -19,16 +19,14 @@ describe("runComplete — the __complete pipeline", () => {
19
19
  "config",
20
20
  ]);
21
21
  // The live `config` sub-verbs, prefix-filtered and sorted by the resolver.
22
+ // AV-228 B3 retired the per-field tier/channel/detail verbs, leaving set + show.
22
23
  await expect(runComplete(["config", ""], capabilities)).resolves.toEqual([
23
- "channel",
24
- "detail",
25
24
  "set",
26
25
  "show",
27
- "tier",
28
26
  ]);
29
27
  await expect(runComplete(["mc"], capabilities)).resolves.toEqual(["mcp"]);
30
- await expect(runComplete(["--l"], capabilities)).resolves.toEqual([
31
- "--llm",
28
+ await expect(runComplete(["--d"], capabilities)).resolves.toEqual([
29
+ "--detail",
32
30
  ]);
33
31
  });
34
32
 
@@ -108,7 +108,7 @@ describe("emitScripts — minChars gate + family opt-out", () => {
108
108
  });
109
109
  });
110
110
 
111
- describe("emitScripts — binName parameterization (PR8 seam)", () => {
111
+ describe("emitScripts — binName parameterization", () => {
112
112
  it("defaults to pragma", () => {
113
113
  expect(scripts.bash).toContain("complete -F _pragma pragma");
114
114
  expect(scripts.zsh.startsWith("#compdef pragma")).toBe(true);
@@ -5,12 +5,14 @@
5
5
  * derivation the `__complete` resolver uses — the tiers cannot disagree) and
6
6
  * renders one script per supported shell. The static tier answers STRUCTURE
7
7
  * with zero exec: nouns, verbs, flag names, enum values, mutation + global
8
- * flags, native file completion. Only `{kind:"entity"}` value contexts shell
8
+ * flags, native file completion. Only `{kind:"names"}` value contexts shell
9
9
  * out to `<bin> __complete`.
10
10
  *
11
11
  * PR6 boundary (`setup completions`): this module exports pure CONTENT only
12
12
  * — shell detection, install paths, mkdir/write effects, and hints all
13
- * belong to setup. `binName` is parameterized so the PR8 bin swap is free;
13
+ * belong to setup. `binName` is parameterized so a fork's rename reaches the
14
+ * script body (`completionScriptPath` derives the file NAME from the same
15
+ * constant, and the two must agree or the shell never loads the file);
14
16
  * `minChars` and `disabledFamilies` are read from `completion` config by setup.
15
17
  *
16
18
  * `minChars` (default {@link DEFAULT_MIN_CHARS}) is baked into the scripts to