@canonical/pragma-cli 0.36.0 → 0.38.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 (312) hide show
  1. package/README.md +3 -2
  2. package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
  3. package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
  4. package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
  5. package/bundled-skills/anatomy-author/SKILL.md +840 -137
  6. package/bundled-skills/design-auditor/SKILL.md +133 -9
  7. package/bundled-skills/implement-component/SKILL.md +192 -23
  8. package/bundled-skills/specify-component/SKILL.md +124 -12
  9. package/bundled-skills/specify-pattern/SKILL.md +47 -8
  10. package/dist/package.json +23 -20
  11. package/dist/pragma.conf.js +963 -40
  12. package/dist/pragma.conf.js.map +1 -1
  13. package/dist/src/capabilities/capabilities/catalog.js +14 -5
  14. package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
  15. package/dist/src/capabilities/capabilities/hints.js +33 -5
  16. package/dist/src/capabilities/capabilities/hints.js.map +1 -1
  17. package/dist/src/capabilities/config/show.render.js +30 -2
  18. package/dist/src/capabilities/config/show.render.js.map +1 -1
  19. package/dist/src/capabilities/create/create.verb.js +2 -1
  20. package/dist/src/capabilities/create/create.verb.js.map +1 -1
  21. package/dist/src/capabilities/create/createSurface.generated.js +23 -1
  22. package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
  23. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
  24. package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
  25. package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
  26. package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
  27. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
  28. package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
  29. package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
  30. package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
  31. package/dist/src/capabilities/graph/connect.render.js +101 -0
  32. package/dist/src/capabilities/graph/connect.render.js.map +1 -0
  33. package/dist/src/capabilities/graph/connect.types.js +21 -0
  34. package/dist/src/capabilities/graph/connect.types.js.map +1 -0
  35. package/dist/src/capabilities/graph/connect.verb.js +147 -0
  36. package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
  37. package/dist/src/capabilities/graph/index.js +5 -3
  38. package/dist/src/capabilities/graph/index.js.map +1 -1
  39. package/dist/src/capabilities/graph/queryText.js +206 -0
  40. package/dist/src/capabilities/graph/queryText.js.map +1 -0
  41. package/dist/src/capabilities/graph/relationIndex.js +302 -0
  42. package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
  43. package/dist/src/capabilities/graph/runConnect.js +456 -0
  44. package/dist/src/capabilities/graph/runConnect.js.map +1 -0
  45. package/dist/src/capabilities/graph/runQuery.js +21 -4
  46. package/dist/src/capabilities/graph/runQuery.js.map +1 -1
  47. package/dist/src/capabilities/setup/buildPlan.js +6 -2
  48. package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
  49. package/dist/src/capabilities/setup/operations/index.js +7 -2
  50. package/dist/src/capabilities/setup/operations/index.js.map +1 -1
  51. package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
  52. package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
  53. package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
  54. package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
  55. package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
  56. package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
  57. package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
  58. package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
  59. package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
  60. package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
  61. package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
  62. package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
  63. package/dist/src/capabilities/setup/operations/writability.js +114 -0
  64. package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
  65. package/dist/src/capabilities/setup/plan.js.map +1 -1
  66. package/dist/src/capabilities/setup/setup.verb.js +80 -31
  67. package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
  68. package/dist/src/capabilities/setup/targets.js +124 -42
  69. package/dist/src/capabilities/setup/targets.js.map +1 -1
  70. package/dist/src/capabilities/shared/index.js +1 -1
  71. package/dist/src/capabilities/shared/index.js.map +1 -1
  72. package/dist/src/constants.js +7 -1
  73. package/dist/src/constants.js.map +1 -1
  74. package/dist/src/kernel/error/renderError.js +3 -2
  75. package/dist/src/kernel/error/renderError.js.map +1 -1
  76. package/dist/src/kernel/error/validOptions.js +41 -0
  77. package/dist/src/kernel/error/validOptions.js.map +1 -0
  78. package/dist/src/kernel/packs/compile.js +90 -6
  79. package/dist/src/kernel/packs/compile.js.map +1 -1
  80. package/dist/src/kernel/packs/cursor.js +135 -0
  81. package/dist/src/kernel/packs/cursor.js.map +1 -0
  82. package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
  83. package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
  84. package/dist/src/kernel/packs/paging.js +108 -0
  85. package/dist/src/kernel/packs/paging.js.map +1 -0
  86. package/dist/src/kernel/packs/renderPack.js +154 -5
  87. package/dist/src/kernel/packs/renderPack.js.map +1 -1
  88. package/dist/src/kernel/packs/resolveEntity.js +118 -19
  89. package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
  90. package/dist/src/kernel/packs/runBodies.js +156 -18
  91. package/dist/src/kernel/packs/runBodies.js.map +1 -1
  92. package/dist/src/kernel/packs/schema.js +160 -12
  93. package/dist/src/kernel/packs/schema.js.map +1 -1
  94. package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
  95. package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
  96. package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
  97. package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
  98. package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
  99. package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
  100. package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
  101. package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
  102. package/dist/src/kernel/packs/sparql/index.js +24 -14
  103. package/dist/src/kernel/packs/sparql/index.js.map +1 -1
  104. package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
  105. package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
  106. package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
  107. package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
  108. package/dist/src/kernel/packs/storyRules.js +151 -0
  109. package/dist/src/kernel/packs/storyRules.js.map +1 -0
  110. package/dist/src/kernel/packs/tierScope.js +310 -0
  111. package/dist/src/kernel/packs/tierScope.js.map +1 -0
  112. package/dist/src/kernel/packs/types.js +80 -0
  113. package/dist/src/kernel/packs/types.js.map +1 -1
  114. package/dist/src/kernel/project/cli/buildProgram.js +10 -1
  115. package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
  116. package/dist/src/kernel/project/cli/dispatch.js +104 -3
  117. package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
  118. package/dist/src/kernel/project/cli/rootHelp.js +8 -5
  119. package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
  120. package/dist/src/kernel/project/cli/suggestNames.js +31 -7
  121. package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
  122. package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
  123. package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
  124. package/dist/src/kernel/render/renderers.js +34 -2
  125. package/dist/src/kernel/render/renderers.js.map +1 -1
  126. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
  127. package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
  128. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
  129. package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
  130. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
  131. package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
  132. package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
  133. package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
  134. package/dist/src/kernel/runtime/graphpack/read.js +17 -10
  135. package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
  136. package/dist/src/kernel/runtime/loadSession.js +3 -1
  137. package/dist/src/kernel/runtime/loadSession.js.map +1 -1
  138. package/dist/src/kernel/runtime/probe.js +1 -1
  139. package/dist/src/kernel/runtime/probe.js.map +1 -1
  140. package/dist/src/kernel/runtime/readEntity.js +19 -4
  141. package/dist/src/kernel/runtime/readEntity.js.map +1 -1
  142. package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
  143. package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
  144. package/dist/src/kernel/runtime/refs/resolve.js +38 -5
  145. package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
  146. package/dist/src/kernel/spec/emitReference.js +1 -1
  147. package/dist/src/kernel/spec/emitReference.js.map +1 -1
  148. package/package.json +23 -20
  149. package/pragma.conf.ts +983 -40
  150. package/src/capabilities/block.parity.test.ts +207 -27
  151. package/src/capabilities/block.shipped.exec.test.ts +156 -14
  152. package/src/capabilities/block.tierRank.exec.test.ts +46 -9
  153. package/src/capabilities/capabilities/catalog.ts +14 -5
  154. package/src/capabilities/capabilities/hints.ts +40 -5
  155. package/src/capabilities/config/collectConfigShow.test.ts +1 -0
  156. package/src/capabilities/config/set.test.ts +24 -25
  157. package/src/capabilities/config/show.render.test.ts +9 -4
  158. package/src/capabilities/config/show.render.ts +30 -2
  159. package/src/capabilities/create/byteEquality.test.ts +28 -1
  160. package/src/capabilities/create/create.test.ts +1 -0
  161. package/src/capabilities/create/create.verb.ts +2 -1
  162. package/src/capabilities/create/createGrammar.test.ts +4 -1
  163. package/src/capabilities/create/createSurface.generated.ts +23 -1
  164. package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
  165. package/src/capabilities/distribution.parity.test.ts +12 -11
  166. package/src/capabilities/distribution.test.ts +77 -7
  167. package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
  168. package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
  169. package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
  170. package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
  171. package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
  172. package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
  173. package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
  174. package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
  175. package/src/capabilities/doctor/doctor.test.ts +327 -36
  176. package/src/capabilities/emptyRecovery.test.ts +84 -28
  177. package/src/capabilities/graph/connect.render.ts +141 -0
  178. package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
  179. package/src/capabilities/graph/connect.test.ts +361 -0
  180. package/src/capabilities/graph/connect.types.ts +136 -0
  181. package/src/capabilities/graph/connect.verb.ts +165 -0
  182. package/src/capabilities/graph/index.ts +5 -3
  183. package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
  184. package/src/capabilities/graph/query.test.ts +17 -0
  185. package/src/capabilities/graph/queryText.test.ts +121 -0
  186. package/src/capabilities/graph/queryText.ts +240 -0
  187. package/src/capabilities/graph/relationIndex.ts +372 -0
  188. package/src/capabilities/graph/runConnect.ts +577 -0
  189. package/src/capabilities/graph/runQuery.ts +21 -6
  190. package/src/capabilities/lazy.test.ts +32 -2
  191. package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
  192. package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
  193. package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
  194. package/src/capabilities/resources/resources.test.ts +38 -2
  195. package/src/capabilities/setup/buildPlan.ts +7 -1
  196. package/src/capabilities/setup/operations/index.ts +14 -0
  197. package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
  198. package/src/capabilities/setup/operations/setupConfig.ts +7 -2
  199. package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
  200. package/src/capabilities/setup/operations/setupLsp.ts +544 -85
  201. package/src/capabilities/setup/operations/setupMcp.ts +212 -4
  202. package/src/capabilities/setup/operations/setupSkills.ts +7 -2
  203. package/src/capabilities/setup/operations/writability.test.ts +151 -0
  204. package/src/capabilities/setup/operations/writability.ts +135 -0
  205. package/src/capabilities/setup/plan.ts +11 -0
  206. package/src/capabilities/setup/setup.test.ts +992 -16
  207. package/src/capabilities/setup/setup.verb.ts +84 -36
  208. package/src/capabilities/setup/targets.ts +152 -48
  209. package/src/capabilities/setup/types.ts +41 -5
  210. package/src/capabilities/shared/index.ts +1 -0
  211. package/src/capabilities/sources/sources.test.ts +55 -0
  212. package/src/capabilities/surface.test.ts +59 -11
  213. package/src/capabilities/tier.parity.test.ts +2 -2
  214. package/src/capabilities/token.parity.test.ts +784 -0
  215. package/src/capabilities/tokenGraph.cli.test.ts +279 -0
  216. package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
  217. package/src/capabilities/variable.parity.test.ts +448 -0
  218. package/src/constants.ts +8 -0
  219. package/src/kernel/completion/shellDrive.test.ts +32 -30
  220. package/src/kernel/config/defaults.test.ts +21 -2
  221. package/src/kernel/config/readConfig.test.ts +1 -1
  222. package/src/kernel/error/renderError.ts +3 -2
  223. package/src/kernel/error/validOptions.test.ts +57 -0
  224. package/src/kernel/error/validOptions.ts +45 -0
  225. package/src/kernel/packs/collect.test.ts +2 -0
  226. package/src/kernel/packs/compile.test.ts +188 -15
  227. package/src/kernel/packs/compile.ts +109 -14
  228. package/src/kernel/packs/cursor.ts +135 -0
  229. package/src/kernel/packs/filteredEmpty.test.ts +213 -0
  230. package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
  231. package/src/kernel/packs/graphqlSource.test.ts +152 -0
  232. package/src/kernel/packs/listNotice.test.ts +156 -0
  233. package/src/kernel/packs/paging.ts +110 -0
  234. package/src/kernel/packs/renderPack.ts +186 -13
  235. package/src/kernel/packs/resolveEntity.test.ts +62 -3
  236. package/src/kernel/packs/resolveEntity.ts +186 -26
  237. package/src/kernel/packs/runBodies.ts +196 -26
  238. package/src/kernel/packs/schema.ts +180 -13
  239. package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
  240. package/src/kernel/packs/sparql/authorQuery.ts +257 -0
  241. package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
  242. package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
  243. package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
  244. package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
  245. package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
  246. package/src/kernel/packs/sparql/filterValues.ts +182 -0
  247. package/src/kernel/packs/sparql/index.ts +30 -14
  248. package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
  249. package/src/kernel/packs/sparql/runSelect.ts +42 -2
  250. package/src/kernel/packs/sparql/searchTerm.ts +29 -0
  251. package/src/kernel/packs/storyRules.ts +180 -0
  252. package/src/kernel/packs/tierScope.test.ts +487 -0
  253. package/src/kernel/packs/tierScope.ts +409 -0
  254. package/src/kernel/packs/toolDescription.test.ts +13 -10
  255. package/src/kernel/packs/types.ts +311 -9
  256. package/src/kernel/project/cli/buildProgram.ts +17 -1
  257. package/src/kernel/project/cli/dispatch.test.ts +87 -0
  258. package/src/kernel/project/cli/dispatch.ts +114 -4
  259. package/src/kernel/project/cli/rootHelp.ts +8 -5
  260. package/src/kernel/project/cli/suggest.test.ts +35 -0
  261. package/src/kernel/project/cli/suggestNames.ts +34 -7
  262. package/src/kernel/project/mcp/registerVerb.ts +5 -1
  263. package/src/kernel/render/contracts.ts +2 -0
  264. package/src/kernel/render/prefixes.test.ts +2 -0
  265. package/src/kernel/render/renderers.test.ts +43 -0
  266. package/src/kernel/render/renderers.ts +34 -1
  267. package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
  268. package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
  269. package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
  270. package/src/kernel/runtime/graphpack/embedded.ts +14 -6
  271. package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
  272. package/src/kernel/runtime/graphpack/read.ts +17 -13
  273. package/src/kernel/runtime/loadSession.ts +3 -1
  274. package/src/kernel/runtime/probe.ts +1 -1
  275. package/src/kernel/runtime/readEntity.ts +19 -4
  276. package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
  277. package/src/kernel/runtime/refs/parseRef.ts +25 -1
  278. package/src/kernel/runtime/refs/resolve.ts +39 -5
  279. package/src/kernel/runtime/types.ts +30 -0
  280. package/src/kernel/spec/emitReference.ts +1 -1
  281. package/src/kernel/spec/surfaceConformance.test.ts +2 -2
  282. package/src/kernel/spec/types.ts +17 -0
  283. package/src/testing/PARITY_GAPS.ts +4 -4
  284. package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
  285. package/src/testing/behavioral/completion.test.ts +7 -6
  286. package/src/testing/behavioral/disclosure.test.ts +39 -37
  287. package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
  288. package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
  289. package/src/testing/behavioral/journeys.cli.test.ts +71 -32
  290. package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
  291. package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
  292. package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
  293. package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
  294. package/src/testing/behavioral/mcpSurface.test.ts +28 -25
  295. package/src/testing/behavioral/parity.test.ts +25 -23
  296. package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
  297. package/src/testing/eval/cases/effectful.ts +7 -1
  298. package/src/testing/eval/cases/readNouns.ts +100 -11
  299. package/src/testing/fixtures/blockGraph.ts +89 -5
  300. package/src/testing/fixtures/connectGraph.ts +159 -0
  301. package/src/testing/fixtures/graph/canonical.ts +128 -32
  302. package/src/testing/helpers/fsProbe.ts +88 -0
  303. package/src/testing/helpers/runCli.test.ts +44 -0
  304. package/src/testing/helpers/runCli.ts +27 -3
  305. package/src/testing/perf/budgets.test.ts +3 -1
  306. package/src/testing/perf/budgets.ts +138 -54
  307. package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
  308. package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
  309. package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
  310. package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
  311. package/src/kernel/packs/sparql/applyFilters.ts +0 -203
  312. package/src/kernel/packs/sparql/applySearch.ts +0 -33
@@ -83,6 +83,14 @@ function buildKernelGroups(programName: string): readonly HelpGroup[] {
83
83
  },
84
84
  { noun: "info", summary: "Show version, config, and update status" },
85
85
  { noun: "version", summary: "Print the CLI version" },
86
+ // A colophon is the note at the back of a book: the typeface, the
87
+ // press, the paper. Nothing depends on it, which is the point — so
88
+ // this line does not argue for it. Every other summary in this column
89
+ // promises work done; this one just invites you to look. It sits with
90
+ // the other commands that describe the distribution rather than act on
91
+ // it; "For AI agents" is for surfaces an agent drives, and credits are
92
+ // for a person reading.
93
+ { noun: "colophon", summary: "Read the credits" },
86
94
  {
87
95
  noun: "upgrade",
88
96
  summary: `Upgrade the ${programName} CLI to the latest version`,
@@ -96,11 +104,6 @@ function buildKernelGroups(programName: string): readonly HelpGroup[] {
96
104
  noun: "capabilities",
97
105
  summary: "Discover conventions, tools, and the discovery sequence",
98
106
  },
99
- // A colophon is the note at the back of a book: the typeface, the
100
- // press, the paper. Nothing depends on it, which is the point — so
101
- // this line does not argue for it. Every other summary in this column
102
- // promises work done; this one just invites you to look.
103
- { noun: "colophon", summary: "Read the credits" },
104
107
  { noun: "skill", summary: "Browse agent skills from the active packs" },
105
108
  { noun: "prompt", summary: "Browse reusable prompt templates" },
106
109
  // Curated for PLACEMENT, not for existence: `mcp serve` is an
@@ -83,4 +83,39 @@ describe("suggestNames", () => {
83
83
  it("returns nothing for an empty query", () => {
84
84
  expect(suggestNames("", ["config"])).toEqual([]);
85
85
  });
86
+
87
+ it("returns nothing for a query that is only whitespace", () => {
88
+ expect(suggestNames(" ", ["config"])).toEqual([]);
89
+ });
90
+
91
+ it("never offers a candidate that IS the query once padding is ignored", () => {
92
+ // The reported defect, in the unit that produced the sentence:
93
+ // `block lookup Timeline` printed `Did you mean? - Timeline ` because 66
94
+ // shipped `ds:name` literals carry the source document's trailing space, and
95
+ // the exclusion compared the raw strings. A suggestion that differs from the
96
+ // query by a character no surface prints reads as the CLI refusing the word
97
+ // it just echoed — and if a candidate really is the query, the miss is the
98
+ // bug, so restating it would only hide the bug.
99
+ //
100
+ // The near-miss beside it is what proves the exclusion is the only thing
101
+ // dropped — an empty list would pass whether the rule worked or the
102
+ // threshold had simply swallowed both.
103
+ expect(suggestNames("Timeline", ["Timeline ", "Timelines"])).toEqual([
104
+ "Timelines",
105
+ ]);
106
+ // Both directions, and leading padding too: the query is the padded one
107
+ // here, and case is folded alongside.
108
+ expect(suggestNames(" timeline\t", ["Timeline", "Timelines"])).toEqual([
109
+ "Timelines",
110
+ ]);
111
+ });
112
+
113
+ it("suggests a padded candidate by its LITERAL value on a genuine typo", () => {
114
+ // Padding costs a candidate no edit distance, and the value handed back is
115
+ // the one the graph holds — the suggester matches loosely and quotes
116
+ // exactly, because a suggestion names something a reader can look up.
117
+ expect(suggestNames("Timelime", ["Timeline ", "block"])).toEqual([
118
+ "Timeline ",
119
+ ]);
120
+ });
86
121
  });
@@ -10,30 +10,43 @@
10
10
  * Return up to {@link maxResults} candidates most similar to `query`.
11
11
  *
12
12
  * Exact matches are excluded — a match means the caller should have resolved it
13
- * and never reached here.
13
+ * and never reached here. "Exact" is judged on the TRIMMED, case-folded token,
14
+ * which is the same equality the resolver's own name FILTER applies, so the two
15
+ * cannot disagree about whether a candidate is a match. They did:
16
+ * `block lookup Timeline` missed an entity whose `ds:name` ends in a space and
17
+ * then suggested "Timeline " back, which reads as the CLI declining to accept
18
+ * the word it just printed. Restating the query is never a useful suggestion —
19
+ * if a candidate really is the query, the miss is the bug and the suggestion
20
+ * would only hide it.
21
+ *
22
+ * Scoring reads the same trimmed forms, so padding costs a candidate no edit
23
+ * distance either; what is RETURNED is the candidate verbatim, padding and
24
+ * casing intact, because a suggestion names a value the graph holds.
14
25
  *
15
26
  * @param query - The token that failed to resolve.
16
27
  * @param candidates - All known names to rank against.
17
28
  * @param opts.maxResults - Max suggestions to return (default 5).
18
29
  * @param opts.threshold - Max normalized edit distance (default 0.4).
19
- * @returns Ranked suggestions, original casing preserved.
30
+ * @returns Ranked suggestions, original spelling preserved.
20
31
  */
21
32
  export function suggestNames(
22
33
  query: string,
23
34
  candidates: readonly string[],
24
35
  opts?: { maxResults?: number; threshold?: number },
25
36
  ): string[] {
26
- if (query === "") return [];
27
-
28
37
  const maxResults = opts?.maxResults ?? 5;
29
38
  const threshold = opts?.threshold ?? 0.4;
30
- const queryLower = query.toLowerCase();
39
+ const queryLower = query.trim().toLowerCase();
40
+
41
+ // A token that is nothing but whitespace has no word to rank against, the
42
+ // same as the empty one.
43
+ if (queryLower === "") return [];
31
44
 
32
45
  type Scored = { name: string; score: number };
33
46
  const scored: Scored[] = [];
34
47
 
35
48
  for (const candidate of candidates) {
36
- const candidateLower = candidate.toLowerCase();
49
+ const candidateLower = candidate.trim().toLowerCase();
37
50
 
38
51
  if (candidateLower === queryLower) continue;
39
52
 
@@ -42,8 +55,22 @@ export function suggestNames(
42
55
  continue;
43
56
  }
44
57
 
45
- const distance = damerauLevenshtein(queryLower, candidateLower);
46
58
  const maxLen = Math.max(queryLower.length, candidateLower.length);
59
+ // Skip on LENGTH before building a matrix. An edit distance is at least the
60
+ // difference in length, so a candidate whose length alone already exceeds
61
+ // the threshold cannot pass it — and the full Damerau-Levenshtein for it
62
+ // costs an O(n·m) matrix allocation. This changes no result, only the cost
63
+ // of reaching it: ranking one mistyped name against the shipped pack's
64
+ // 4,380 entities took 1.2 SECONDS, which is a long time to wait to be told
65
+ // about a typo.
66
+ if (
67
+ Math.abs(queryLower.length - candidateLower.length) >
68
+ threshold * maxLen
69
+ ) {
70
+ continue;
71
+ }
72
+
73
+ const distance = damerauLevenshtein(queryLower, candidateLower);
47
74
  const normalized = maxLen === 0 ? 0 : distance / maxLen;
48
75
 
49
76
  if (normalized <= threshold) {
@@ -156,7 +156,11 @@ function withDetail(
156
156
  */
157
157
  function noticeMeta(verb: VerbSpec, data: unknown): Record<string, unknown> {
158
158
  const notice = verb.output.formatters.notice?.(data as never);
159
- return notice ? { notice } : {};
159
+ // The seam's machine half, merged here so an agent gets the tier scope as
160
+ // DATA (`meta.scope`) and not only as the sentence `notice` carries. The CLI
161
+ // merges the same seam into the same keys (`cli/dispatch.ts#renderData`).
162
+ const extra = verb.output.formatters.meta?.(data as never);
163
+ return { ...(extra ?? {}), ...(notice ? { notice } : {}) };
160
164
  }
161
165
 
162
166
  /** The tool handler for a read verb: run, project, envelope. */
@@ -51,6 +51,8 @@ export interface SectionDef<T> {
51
51
  readonly heading: string;
52
52
  readonly kind: SectionKind;
53
53
  readonly showWhenEmpty?: boolean;
54
+ /** One sentence on how to READ the body, rendered under the heading. */
55
+ readonly note?: string;
54
56
  }
55
57
 
56
58
  /** A single inline field rendered in a lookup heading block. */
@@ -13,6 +13,8 @@ describe("DEFAULT_PREFIX_MAP", () => {
13
13
  skos: "http://www.w3.org/2004/02/skos/core#",
14
14
  ds: "https://ds.canonical.com/",
15
15
  cs: "http://pragma.canonical.com/codestandards#",
16
+ dt: "https://dt.canonical.com/",
17
+ "w3c-tokens": "https://dt.canonical.com/w3c-tokens/",
16
18
  });
17
19
  });
18
20
 
@@ -440,3 +440,46 @@ describe("section-body nesting follows CommonMark, not a near-enough reading", (
440
440
  expect(out).toMatch(/^### also inside$/m);
441
441
  });
442
442
  });
443
+
444
+ describe("a section can say how to read its body", () => {
445
+ const entity = { anatomy: "background.color: [a, b]" };
446
+ const options = {
447
+ title: () => "Button",
448
+ fields: [],
449
+ sections: [
450
+ {
451
+ key: "anatomy" as const,
452
+ heading: "Anatomy (DSL)",
453
+ kind: "code" as const,
454
+ note: "A list value is the fallback chain, first wins.",
455
+ },
456
+ ],
457
+ };
458
+
459
+ it("puts the note between the heading and the body, once", () => {
460
+ const llm = renderLookupLlm(entity, options);
461
+ expect(llm).toContain(
462
+ "### Anatomy (DSL)\nA list value is the fallback chain, first wins.",
463
+ );
464
+ expect(llm.split("first wins")).toHaveLength(2);
465
+
466
+ const plain = renderLookupPlain(entity, options, styleFor(false));
467
+ expect(plain).toContain(
468
+ "Anatomy (DSL):\n A list value is the fallback chain, first wins.",
469
+ );
470
+ });
471
+
472
+ it("renders nothing extra for a section that declares none", () => {
473
+ const bare = {
474
+ ...options,
475
+ sections: [
476
+ {
477
+ key: "anatomy" as const,
478
+ heading: "Anatomy (DSL)",
479
+ kind: "code" as const,
480
+ },
481
+ ],
482
+ };
483
+ expect(renderLookupLlm(entity, bare)).not.toContain("fallback chain");
484
+ });
485
+ });
@@ -189,12 +189,40 @@ function renderHeaderRow<T>(
189
189
  return style.enabled ? style.bold(line) : line;
190
190
  }
191
191
 
192
+ /**
193
+ * Render a list as condensed Markdown.
194
+ *
195
+ * The heading COUNTS what the reader is looking at and — when the caller says
196
+ * so — admits that the count is not the population: `## Token (300, more
197
+ * exist)` rather than a bare `## Token (300)` that reads as a complete answer.
198
+ * The total is not named because it is not known: a page is cut with one extra
199
+ * row (`packs/runBodies.ts`), never with a second COUNT over the whole filtered
200
+ * population, so "more exist" is the strongest honest claim.
201
+ *
202
+ * The SCOPE is in the heading for the same reason, and it is the more serious
203
+ * of the two omissions: a truncated page at least ends where it ends, while a
204
+ * scoped read looks exactly like a complete one. `## Block (175, tier scope:
205
+ * global, apps, documentation, sites, stores)` says which part of the design
206
+ * system the 175 are, in the words `--tier` accepts.
207
+ *
208
+ * @param items - The rows on this page.
209
+ * @param options - Columns and empty-state copy.
210
+ * @param page - What the page knows about itself (`more`: further rows exist;
211
+ * `scope`: the tiers it answered from, when it answered from some of them).
212
+ * @returns The formatted Markdown list.
213
+ */
192
214
  export function renderListLlm<T>(
193
215
  items: readonly T[],
194
216
  options: RenderListOptions<T>,
217
+ page: { readonly more?: boolean; readonly scope?: string } = {},
195
218
  ): string {
196
219
  const prefixes = options.prefixes ?? DEFAULT_PREFIX_MAP;
197
- const lines = [`## ${options.heading} (${items.length})`, ""];
220
+ const count = [
221
+ `${items.length}`,
222
+ ...(page.more === true ? ["more exist"] : []),
223
+ ...(page.scope === undefined ? [] : [`tier scope: ${page.scope}`]),
224
+ ].join(", ");
225
+ const lines = [`## ${options.heading} (${count})`, ""];
198
226
 
199
227
  if (items.length === 0) {
200
228
  const body = emptyBody(options);
@@ -326,6 +354,11 @@ function renderLookupSections<T>(
326
354
  ? `${"#".repeat(SECTION_HEADING_LEVEL)} ${section.heading}`
327
355
  : `${section.heading}:`,
328
356
  );
357
+ // How to read what follows goes between the heading and the body — a
358
+ // notation's key is no use anywhere else.
359
+ if (section.note) {
360
+ sections.push(mode === "llm" ? section.note : indentBlock(section.note));
361
+ }
329
362
  sections.push(body);
330
363
  }
331
364