@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
@@ -0,0 +1,86 @@
1
+ /**
2
+ * Stale-command gate: the published README and every doc under `docs/` must be
3
+ * free of the retired v1 vocabulary. This is an ENFORCED test, not a lint — a
4
+ * doc that reintroduces a removed command (or the retired `--llm` flag) fails
5
+ * the build. The live `--format llm` form is deliberately NOT banned.
6
+ */
7
+
8
+ import { readdirSync, readFileSync } from "node:fs";
9
+ import { join, relative } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+ import { describe, expect, it } from "vitest";
12
+
13
+ /** The package root (`packages/cli/pragma/`), resolved from this test's URL. */
14
+ const packageRoot = fileURLToPath(new URL("../../../", import.meta.url));
15
+
16
+ /** Recursively collect every `.md` file under a directory. */
17
+ function collectMarkdown(dir: string): string[] {
18
+ const out: string[] = [];
19
+ for (const entry of readdirSync(dir, { withFileTypes: true })) {
20
+ const full = join(dir, entry.name);
21
+ if (entry.isDirectory()) out.push(...collectMarkdown(full));
22
+ else if (entry.name.endsWith(".md")) out.push(full);
23
+ }
24
+ return out;
25
+ }
26
+
27
+ /** One retired pattern and a human-readable reason for the failure message. */
28
+ interface BannedPattern {
29
+ readonly pattern: RegExp;
30
+ readonly reason: string;
31
+ }
32
+
33
+ /** The retired v1 vocabulary. The live `--format llm` form is intentionally absent. */
34
+ const BANNED: readonly BannedPattern[] = [
35
+ {
36
+ pattern: /\bupdate-refs\b/,
37
+ reason: "retired `update-refs` (now `sources update`)",
38
+ },
39
+ { pattern: /pragma\s+llm\b/, reason: "retired `llm` tool" },
40
+ { pattern: /pragma\s+data\b/, reason: "retired `data` noun (now `sources`)" },
41
+ {
42
+ pattern: /pragma\s+tokens\b/,
43
+ reason: "retired plural `tokens` noun (now singular `token`)",
44
+ },
45
+ {
46
+ pattern: /\btokens_(list|lookup|sample|add-config)\b/,
47
+ reason: "retired plural `tokens_*` tools (now `token_*`)",
48
+ },
49
+ {
50
+ pattern: /--llm\b/,
51
+ reason:
52
+ "retired `--llm` flag (now the `--format llm` form, auto-detected when piped)",
53
+ },
54
+ {
55
+ pattern: /--all-tiers\b/,
56
+ reason:
57
+ "retired `--all-tiers` flag (L-OPEN-9: `block list` is the declared, unfiltered story — there is no tier filter to escape)",
58
+ },
59
+ {
60
+ pattern: /\btoken[ _]add-config\b/,
61
+ reason:
62
+ "removed `token add-config` command / `token_add-config` tool (L-OPEN-9: a mutation the declared read grammar has no verb for)",
63
+ },
64
+ ];
65
+
66
+ // Every shipped doc must be free of retired vocabulary: the README plus every
67
+ // page under `docs/`. CHANGELOG.md (at the package root, outside `docs/`) is
68
+ // DELIBERATELY exempt — its migration table legitimately cites the retired
69
+ // names (`data`, `update-refs`, plural `tokens`) to tell readers what they
70
+ // became, so scanning it would false-positive on that historical vocabulary.
71
+ const files = [
72
+ join(packageRoot, "README.md"),
73
+ ...collectMarkdown(join(packageRoot, "docs")),
74
+ ];
75
+
76
+ describe("stale-command gate — docs never mention retired v1 commands", () => {
77
+ for (const file of files) {
78
+ const rel = relative(packageRoot, file);
79
+ it(`${rel} is free of retired commands`, () => {
80
+ const content = readFileSync(file, "utf-8");
81
+ for (const { pattern, reason } of BANNED) {
82
+ expect(pattern.test(content), `${rel}: ${reason}`).toBe(false);
83
+ }
84
+ });
85
+ }
86
+ });
@@ -2,6 +2,11 @@
2
2
 
3
3
  exports[`eval harness — seed gate > runs the full seed (stable + read-noun) cases clean 1`] = `
4
4
  [
5
+ {
6
+ "id": "content-block-list-is-channel-independent",
7
+ "kind": "content",
8
+ "passed": true,
9
+ },
5
10
  {
6
11
  "id": "content-canonical-graph-has-4-components",
7
12
  "kind": "content",
@@ -57,11 +62,6 @@ exports[`eval harness — seed gate > runs the full seed (stable + read-noun) ca
57
62
  "kind": "content",
58
63
  "passed": true,
59
64
  },
60
- {
61
- "id": "content-normal-channel-drops-beta-only-block",
62
- "kind": "content",
63
- "passed": true,
64
- },
65
65
  {
66
66
  "id": "content-standard-list-length-equals-category-count-sum",
67
67
  "kind": "content",
@@ -103,7 +103,7 @@ exports[`eval harness — seed gate > runs the full seed (stable + read-noun) ca
103
103
  "passed": true,
104
104
  },
105
105
  {
106
- "id": "prompt-set-tier-maps-to-config-tier",
106
+ "id": "prompt-set-tier-maps-to-config-set",
107
107
  "kind": "prompt",
108
108
  "passed": true,
109
109
  },
@@ -113,7 +113,7 @@ exports[`eval harness — seed gate > runs the full seed (stable + read-noun) ca
113
113
  "passed": true,
114
114
  },
115
115
  {
116
- "id": "prompt-tokens-config-maps-to-token-add-config",
116
+ "id": "prompt-tokens-config-maps-to-nothing",
117
117
  "kind": "prompt",
118
118
  "passed": true,
119
119
  },
@@ -138,7 +138,7 @@ exports[`eval harness — seed gate > runs the full seed (stable + read-noun) ca
138
138
  "passed": true,
139
139
  },
140
140
  {
141
- "id": "tool-config-tier-is-plan-first",
141
+ "id": "tool-config-set-is-plan-first",
142
142
  "kind": "tool",
143
143
  "passed": true,
144
144
  },
@@ -183,7 +183,7 @@ exports[`eval harness — seed gate > runs the full seed (stable + read-noun) ca
183
183
  "passed": true,
184
184
  },
185
185
  {
186
- "id": "tool-token-add-config-is-plan-first",
186
+ "id": "tool-token-noun-is-read-only",
187
187
  "kind": "tool",
188
188
  "passed": true,
189
189
  },
@@ -163,23 +163,30 @@ export const assemblyEvalCases: readonly EvalCase[] = [
163
163
  },
164
164
  },
165
165
  {
166
- id: "prompt-set-tier-maps-to-config-tier",
166
+ id: "prompt-set-tier-maps-to-config-set",
167
167
  kind: "prompt",
168
- input: '"Set the active tier" should map to `config_tier`.',
168
+ input: '"Set the active tier" should map to `config_set` (B3).',
169
169
  async expect({ mcp }) {
170
- assertIntentTool(await mcp.listTools(), "config_tier", /tier/i);
170
+ assertIntentTool(await mcp.listTools(), "config_set", /tier/i);
171
171
  },
172
172
  },
173
173
  {
174
- id: "prompt-tokens-config-maps-to-token-add-config",
174
+ id: "prompt-tokens-config-maps-to-nothing",
175
175
  kind: "prompt",
176
176
  input:
177
- '"Generate a terrazzo tokens config" should map to `token_add-config`.',
177
+ '"Generate a terrazzo tokens config" maps to NO tool — `token_add-config` was removed in L-OPEN-9, and the token noun is read-only.',
178
178
  async expect({ mcp }) {
179
- assertIntentTool(
180
- await mcp.listTools(),
181
- "token_add-config",
182
- /token|terrazzo/i,
179
+ // The inverse of an intent-mapping case, and the point of the ruling: an
180
+ // agent asked to write a tokens config finds nothing in the catalog that
181
+ // claims to do it, rather than a verb the declared grammar cannot express.
182
+ const tools = await mcp.listTools();
183
+ assert.ok(
184
+ !tools.some(
185
+ (tool) =>
186
+ tool.name === "token_add-config" ||
187
+ /terrazzo|tokens\.config/i.test(String(tool.description ?? "")),
188
+ ),
189
+ "no tool may still offer to write a tokens config",
183
190
  );
184
191
  },
185
192
  },
@@ -70,12 +70,15 @@ export const effectfulEvalCases: readonly EvalCase[] = [
70
70
  },
71
71
  },
72
72
  {
73
- id: "tool-config-tier-is-plan-first",
73
+ id: "tool-config-set-is-plan-first",
74
74
  kind: "tool",
75
75
  input:
76
- "config_tier without `confirm` returns a plan (planOnly) and writes nothing — the uniform plan-first mutation contract.",
76
+ "config_set without `confirm` returns a plan (planOnly) and writes nothing — the uniform plan-first mutation contract.",
77
77
  async expect({ mcp }) {
78
- const result = await mcp.callTool("config_tier", { path: "apps/lxd" });
78
+ const result = await mcp.callTool("config_set", {
79
+ key: "tier",
80
+ value: "apps/lxd",
81
+ });
79
82
  assert.equal(result.ok, true);
80
83
  const meta = result.meta as { planOnly?: boolean };
81
84
  assert.equal(meta.planOnly, true);
@@ -167,18 +170,23 @@ export const effectfulEvalCases: readonly EvalCase[] = [
167
170
  },
168
171
  },
169
172
  {
170
- id: "tool-token-add-config-is-plan-first",
173
+ id: "tool-token-noun-is-read-only",
171
174
  kind: "tool",
172
175
  input:
173
- "token_add-config (needsStore mutation) without `confirm` returns a plan naming tokens.config.mjs and writes nothing.",
176
+ "the `token` noun exposes only reads — `token_add-config` (the tokens.config.mjs writer) was REMOVED in L-OPEN-9, not ported.",
174
177
  async expect() {
175
178
  await withCanonicalFixture(CANONICAL_CONFIG, async (mcp) => {
176
- const result = await mcp.callTool("token_add-config");
177
- assert.equal(result.ok, true);
178
- const meta = result.meta as { planOnly?: boolean };
179
- assert.equal(meta.planOnly, true);
180
- const data = result.data as { plan: string[] };
181
- assert.match(data.plan.join("\n"), /tokens\.config\.mjs/);
179
+ // This case used to pin `token_add-config`'s plan-first contract. The
180
+ // verb is gone, so it pins the ruling instead: the read surface is
181
+ // declared content and carries no mutation. (The plan-first MECHANIC is
182
+ // still covered by `tool-config-set-is-plan-first` and
183
+ // `tool-upgrade-is-plan-first`.)
184
+ const tools = (await mcp.listTools()).map((tool) => tool.name);
185
+ assert.ok(!tools.includes("token_add-config"));
186
+ assert.deepEqual(
187
+ tools.filter((name) => name.startsWith("token")).sort(),
188
+ ["token_list", "token_lookup", "token_sample"],
189
+ );
182
190
  });
183
191
  },
184
192
  },
@@ -186,14 +194,21 @@ export const effectfulEvalCases: readonly EvalCase[] = [
186
194
  id: "tool-tier-lookup-lists-scoped-blocks",
187
195
  kind: "tool",
188
196
  input:
189
- "tier_lookup {name:apps/lxd} resolves the tier and lists the blocks scoped to it (LXD Panel).",
197
+ "tier_lookup {name:[apps/lxd]} resolves the tier and expands the blocks scoped to it (LXD Panel).",
190
198
  async expect() {
191
199
  await withCanonicalFixture(ALL_VISIBLE_CONFIG, async (mcp) => {
192
- const result = await mcp.callTool("tier_lookup", { name: "apps/lxd" });
200
+ // The declared pack lookup (L-OPEN-9): variadic names in, the uniform
201
+ // `{ results, errors }` lookup envelope out, blocks as an expand.
202
+ const result = await mcp.callTool("tier_lookup", {
203
+ name: ["apps/lxd"],
204
+ });
193
205
  assert.equal(result.ok, true);
194
- const data = result.data as { name: string; blocks: string[] };
195
- assert.equal(data.name, "apps/lxd");
196
- assert.ok(data.blocks.includes("LXD Panel"));
206
+ const data = result.data as {
207
+ results: { name: string; blocks: { name: string }[] }[];
208
+ };
209
+ const tier = data.results[0];
210
+ assert.equal(tier?.name, "apps/lxd");
211
+ assert.ok(tier?.blocks.some((block) => block.name === "LXD Panel"));
197
212
  });
198
213
  },
199
214
  },
@@ -90,16 +90,19 @@ export const readNounEvalCases: readonly EvalCase[] = [
90
90
  id: "content-canonical-graph-has-4-components",
91
91
  kind: "content",
92
92
  input:
93
- "the canonical fixture graph carries exactly 4 ds:Component individuals, including Button and Beta Widget.",
93
+ "the canonical fixture graph carries 4 ds:Component individuals (Button, Modal, LXD Panel, Beta Widget); block_list also surfaces the untiered Button Icon subcomponent (A2).",
94
94
  async expect() {
95
95
  await withCanonicalFixture(ALL_VISIBLE_CONFIG, async (mcp) => {
96
- const result = await mcp.callTool("block_list", { allTiers: true });
96
+ const result = await mcp.callTool("block_list");
97
97
  const names = (result.data as { name: string }[])
98
98
  .map((r) => r.name)
99
99
  .sort();
100
+ // The declared list takes no arguments and filters nothing: the 4 tiered
101
+ // components plus the untiered Button Icon subcomponent (A2).
100
102
  assert.deepEqual(names, [
101
103
  "Beta Widget",
102
104
  "Button",
105
+ "Button Icon",
103
106
  "LXD Panel",
104
107
  "Modal",
105
108
  ]);
@@ -165,25 +168,27 @@ export const readNounEvalCases: readonly EvalCase[] = [
165
168
  },
166
169
  },
167
170
  {
168
- id: "content-normal-channel-drops-beta-only-block",
171
+ id: "content-block-list-is-channel-independent",
169
172
  kind: "content",
170
173
  input:
171
- "the normal channel (CANONICAL_CONFIG) excludes the beta-only block; the prerelease channel (ALL_VISIBLE_CONFIG) includes it.",
174
+ "block_list returns the SAME rows on the normal channel (CANONICAL_CONFIG) and the prerelease one (ALL_VISIBLE_CONFIG) — including the beta-only block, which the normal channel used to hide (L-OPEN-9).",
172
175
  async expect() {
173
- await withCanonicalFixture(CANONICAL_CONFIG, async (mcp) => {
174
- const normal = await mcp.callTool("block_list", { allTiers: true });
175
- const normalNames = (normal.data as { name: string }[]).map(
176
- (r) => r.name,
177
- );
178
- assert.ok(!normalNames.includes("Beta Widget"));
179
- });
180
- await withCanonicalFixture(ALL_VISIBLE_CONFIG, async (mcp) => {
181
- const prerelease = await mcp.callTool("block_list", { allTiers: true });
182
- const prereleaseNames = (prerelease.data as { name: string }[]).map(
183
- (r) => r.name,
184
- );
185
- assert.ok(prereleaseNames.includes("Beta Widget"));
186
- });
176
+ const namesUnder = async (
177
+ config: typeof CANONICAL_CONFIG | typeof ALL_VISIBLE_CONFIG,
178
+ ): Promise<string[]> => {
179
+ let names: string[] = [];
180
+ await withCanonicalFixture(config, async (mcp) => {
181
+ const result = await mcp.callTool("block_list");
182
+ names = (result.data as { name: string }[]).map((r) => r.name).sort();
183
+ });
184
+ return names;
185
+ };
186
+ const normal = await namesUnder(CANONICAL_CONFIG);
187
+ const prerelease = await namesUnder(ALL_VISIBLE_CONFIG);
188
+ // The signed-off consequence: an experimental block is visible to
189
+ // everyone, on every channel, until filtering returns in declared form.
190
+ assert.ok(normal.includes("Beta Widget"));
191
+ assert.deepEqual(normal, prerelease);
187
192
  },
188
193
  },
189
194
  {
@@ -110,7 +110,11 @@ export const stableEvalCases: readonly EvalCase[] = [
110
110
  try {
111
111
  const widgetModule: CapabilityModule = {
112
112
  name: "widget",
113
- verbs: compilePack(WIDGET_PACK, "test:widget", WIDGET_PREFIXES),
113
+ verbs: compilePack(
114
+ WIDGET_PACK,
115
+ { label: "test:widget", origin: "package" },
116
+ WIDGET_PREFIXES,
117
+ ),
114
118
  };
115
119
  const mcp = await projectMcp([widgetModule], fixture.cwd);
116
120
  try {
@@ -70,5 +70,11 @@ describe("eval harness — seed gate", () => {
70
70
  expect(report.cases.length).toBeGreaterThanOrEqual(35);
71
71
  expect(report.cases.length).toBeLessThanOrEqual(60);
72
72
  expect(normalize(report.cases)).toMatchSnapshot();
73
- });
73
+ // Explicit timeout, like the other suite-level gates: this drives every
74
+ // seed case through a real runtime AND a real MCP server, and since a
75
+ // preview now performs its reads for real rather than answering from a
76
+ // mock, the effectful cases pay genuine IO. Measured 3.7s before that
77
+ // change and 5.4s after, so the vitest 5s default fails it on cost the
78
+ // PR intends to pay — not on a defect. 60s leaves room for CI contention.
79
+ }, 60_000);
74
80
  });
@@ -7,7 +7,7 @@
7
7
 
8
8
  import { tmpdir } from "node:os";
9
9
  import { join } from "node:path";
10
- import { $, gen, succeed, writeFile } from "@canonical/task";
10
+ import { $, gen, readFile, succeed, writeFile } from "@canonical/task";
11
11
  import type { CapabilityModule } from "../../kernel/spec/types.js";
12
12
 
13
13
  /** A synthetic module: noun `widget`, three verbs (one hidden). */
@@ -173,3 +173,203 @@ export const fixtureEffectsModule: CapabilityModule = {
173
173
  },
174
174
  ],
175
175
  };
176
+
177
+ /**
178
+ * A synthetic module for the HONEST-preview guards (PR7). `probe graft` READS a
179
+ * file before writing beside it, so a run whose source is missing dies on its
180
+ * first effect. The mock previewer answered that read with a placeholder string
181
+ * and reported a confident plan with exit 0 — the structural false positive
182
+ * PRA-104 indicts. Under `runPreview` the read is real, so `--dry-run` (and the
183
+ * MCP plan-first twin) fail exactly where and how the run would. Test-only (D8)
184
+ * — never listed in `capabilities/index.ts`.
185
+ */
186
+ export const fixturePreviewModule: CapabilityModule = {
187
+ name: "fixture-preview",
188
+ verbs: [
189
+ {
190
+ path: ["probe", "graft"],
191
+ summary: "Copy a file's content into a sibling.",
192
+ params: [
193
+ {
194
+ kind: "string",
195
+ name: "source",
196
+ doc: "The file to read.",
197
+ positional: true,
198
+ required: true,
199
+ },
200
+ ],
201
+ output: { formatters: passthroughFormatters },
202
+ capability: {
203
+ needsStore: false,
204
+ mutates: true,
205
+ mcp: {
206
+ expose: true,
207
+ annotations: { readOnlyHint: false, openWorldHint: false },
208
+ },
209
+ },
210
+ run: (p) =>
211
+ gen(function* () {
212
+ const { source } = p as { source: string };
213
+ const content = yield* $(readFile(source));
214
+ yield* $(writeFile(`${source}.grafted`, content));
215
+ return { grafted: source };
216
+ }),
217
+ },
218
+ ],
219
+ };
220
+
221
+ /**
222
+ * A synthetic module that exercises every RENDER path of `emitReference` (D8 —
223
+ * never listed in `src/capabilities/index.ts`). Distinct from `fixtureModule`,
224
+ * which only exercises `emitSurface`: this one carries the doc-only shapes the
225
+ * reference generator reads and the machine surface ignores — a rich `doc`,
226
+ * examples with and without notes, a {@link DisclosureSpec}, every param kind
227
+ * as a flag (including a repeatable `string[]` flag), an OPTIONAL variadic
228
+ * positional, a destructive mutation AND a non-destructive one (the two
229
+ * `formatToolAnnotations` mutation branches), a visible MCP-withheld verb, a
230
+ * self-verb, a hidden verb the emitter must drop, and — so `renderNonToolSurface`
231
+ * renders both its optional bullets — an `mcpResources` template surface and an
232
+ * `mcpPrompts` native-prompt surface. The `register` hooks are no-ops: the
233
+ * reference emitter reads only `mcpResources.surface.templates` and the presence
234
+ * of `mcpPrompts`, never invoking either.
235
+ */
236
+ export const fixtureReferenceModule: CapabilityModule = {
237
+ name: "fixture-reference",
238
+ verbs: [
239
+ {
240
+ path: ["gizmo", "scan"],
241
+ summary: "Scan the gizmos.",
242
+ doc: "Scan every gizmo under the active scope, optionally narrowing to named targets. Reads nothing off disk.",
243
+ params: [
244
+ {
245
+ kind: "string[]",
246
+ name: "targets",
247
+ doc: "Gizmo names to scan (all when omitted).",
248
+ positional: true,
249
+ },
250
+ {
251
+ kind: "enum",
252
+ name: "mode",
253
+ doc: "Scan mode.",
254
+ values: ["fast", "slow"],
255
+ default: "fast",
256
+ },
257
+ { kind: "number", name: "limit", doc: "Maximum gizmos to scan." },
258
+ { kind: "string", name: "label", doc: "Label to stamp on the report." },
259
+ { kind: "string[]", name: "include", doc: "Extra globs to include." },
260
+ { kind: "boolean", name: "deep", doc: "Recurse into sub-gizmos." },
261
+ ],
262
+ output: { formatters: passthroughFormatters },
263
+ examples: [
264
+ { cmd: "pragma gizmo scan", note: "scan everything" },
265
+ { cmd: "pragma gizmo scan alpha --mode slow" },
266
+ ],
267
+ disclosure: { levels: ["summary", "detailed"], default: "summary" },
268
+ capability: {
269
+ needsStore: false,
270
+ mutates: false,
271
+ mcp: {
272
+ expose: true,
273
+ annotations: { readOnlyHint: true, openWorldHint: false },
274
+ },
275
+ },
276
+ run: async () => ({ scanned: true }),
277
+ },
278
+ {
279
+ path: ["gizmo", "wipe"],
280
+ summary: "Wipe a gizmo.",
281
+ params: [
282
+ {
283
+ kind: "string",
284
+ name: "path",
285
+ doc: "The gizmo path to wipe.",
286
+ positional: true,
287
+ required: true,
288
+ },
289
+ ],
290
+ output: { formatters: passthroughFormatters },
291
+ capability: {
292
+ needsStore: true,
293
+ mutates: true,
294
+ destructive: true,
295
+ mcp: {
296
+ expose: true,
297
+ annotations: {
298
+ readOnlyHint: false,
299
+ destructiveHint: true,
300
+ openWorldHint: false,
301
+ },
302
+ },
303
+ },
304
+ run: () => succeed({ wiped: true }),
305
+ },
306
+ {
307
+ path: ["gizmo", "tidy"],
308
+ summary: "Tidy the gizmo cache.",
309
+ params: [],
310
+ output: { formatters: passthroughFormatters },
311
+ capability: {
312
+ needsStore: false,
313
+ mutates: true,
314
+ destructive: false,
315
+ mcp: {
316
+ expose: true,
317
+ annotations: {
318
+ readOnlyHint: false,
319
+ destructiveHint: false,
320
+ openWorldHint: false,
321
+ },
322
+ },
323
+ },
324
+ run: () => succeed({ tidied: true }),
325
+ },
326
+ {
327
+ path: ["gizmo", "local"],
328
+ summary: "A visible gizmo verb withheld from MCP.",
329
+ params: [],
330
+ output: { formatters: passthroughFormatters },
331
+ capability: {
332
+ needsStore: false,
333
+ mutates: false,
334
+ mcp: { expose: false, reason: "CLI-only local helper" },
335
+ },
336
+ run: async () => ({ local: true }),
337
+ },
338
+ {
339
+ path: ["ping"],
340
+ summary: "Ping the gizmo host.",
341
+ params: [],
342
+ output: { formatters: passthroughFormatters },
343
+ capability: {
344
+ needsStore: false,
345
+ mutates: false,
346
+ mcp: {
347
+ expose: true,
348
+ annotations: { readOnlyHint: true, openWorldHint: false },
349
+ },
350
+ },
351
+ run: async () => ({ pong: true }),
352
+ },
353
+ {
354
+ path: ["gizmo", "hidden"],
355
+ summary: "Hidden gizmo probe.",
356
+ hidden: true,
357
+ params: [],
358
+ output: { formatters: passthroughFormatters },
359
+ capability: {
360
+ needsStore: false,
361
+ mutates: false,
362
+ mcp: { expose: false, reason: "internal probe" },
363
+ },
364
+ run: async () => ({ ok: true }),
365
+ },
366
+ ],
367
+ // A non-tool MCP surface, so `renderNonToolSurface` emits its Resources bullet.
368
+ // `register` is a no-op — the emitter reads only `surface.templates`.
369
+ mcpResources: {
370
+ surface: { templates: ["gizmo:{+uri}"] },
371
+ register: () => {},
372
+ },
373
+ // A native prompt surface, so `renderNonToolSurface` emits its Prompts bullet.
374
+ mcpPrompts: { register: () => {} },
375
+ };
@@ -6,9 +6,12 @@
6
6
  * `block` content-parity + `ontology`/`graph inspect` tests are protected
7
7
  * against) byte-for-byte and EXTENDS it with the entities the other bundled
8
8
  * read nouns (`standard`, `modifier`, `token`, `tier`) need, plus two more
9
- * blocks that exercise tier-chain inheritance and channel visibility (PR3's
10
- * hand-written `block list`, Risk5). Because `BLOCK_TTL` is reused verbatim,
11
- * Button/Modal content can never drift between PR3's fixture and this one.
9
+ * blocks that used to exercise tier-chain inheritance and channel visibility
10
+ * (PR3's hand-written `block list`, Risk5). Those two now serve the opposite
11
+ * purpose: L-OPEN-9 removed the filtering, so a tier-scoped and a
12
+ * channel-gated block are what prove the declared list hides NEITHER. Because
13
+ * `BLOCK_TTL` is reused verbatim, Button/Modal content can never drift between
14
+ * PR3's fixture and this one.
12
15
  *
13
16
  * Composed as one Turtle document, so ONE store answers every read noun:
14
17
  * `block`/`ontology`/`graph inspect` were already provable on `BLOCK_TTL`