@canonical/pragma-cli 0.20.0 → 0.22.0-experimental.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 (203) hide show
  1. package/dist/pragma +0 -0
  2. package/package.json +31 -18
  3. package/src/bin.test.ts +2 -0
  4. package/src/bin.ts +1 -0
  5. package/src/compile-validation.ts +3 -4
  6. package/src/constants.ts +1 -1
  7. package/src/domains/block/blockConfig.ts +47 -3
  8. package/src/domains/block/commands/list.test.ts +1 -1
  9. package/src/domains/block/commands/list.ts +27 -0
  10. package/src/domains/block/commands/lookup.ts +26 -52
  11. package/src/domains/block/formatters/index.ts +1 -1
  12. package/src/domains/block/formatters/list.test.ts +1 -1
  13. package/src/domains/block/formatters/list.ts +28 -42
  14. package/src/domains/block/formatters/lookup.test.ts +3 -2
  15. package/src/domains/block/formatters/lookup.ts +91 -296
  16. package/src/domains/block/formatters/types.ts +1 -1
  17. package/src/domains/block/operations/list.test.ts +1 -1
  18. package/src/domains/block/operations/list.ts +1 -1
  19. package/src/domains/block/operations/lookup.test.ts +29 -8
  20. package/src/domains/block/operations/lookup.ts +94 -16
  21. package/src/domains/block/orchestration/blockEmptyError.ts +1 -1
  22. package/src/domains/block/orchestration/buildBlockFilters.ts +1 -1
  23. package/src/domains/block/orchestration/enrichBlocks.ts +6 -2
  24. package/src/domains/block/orchestration/resolveBlockList.ts +1 -1
  25. package/src/domains/block/orchestration/resolveBlockLookup.ts +8 -2
  26. package/src/domains/block/types.ts +1 -1
  27. package/src/domains/config/commands/channel.ts +6 -4
  28. package/src/domains/config/commands/tier.ts +6 -4
  29. package/src/domains/config/mcp/specs.ts +18 -6
  30. package/src/domains/config/operations/validateTier.ts +1 -1
  31. package/src/domains/config/tasks/index.ts +3 -0
  32. package/src/domains/config/tasks/setChannel.ts +30 -0
  33. package/src/domains/config/tasks/setTier.ts +33 -0
  34. package/src/domains/create/commands/package.test.ts +2 -2
  35. package/src/domains/doctor/mcp/index.ts +1 -1
  36. package/src/domains/doctor/mcp/specs.ts +2 -49
  37. package/src/domains/graph/commands/inspect.test.ts +1 -1
  38. package/src/domains/graph/formatters/inspect.ts +12 -7
  39. package/src/domains/graph/operations/inspectUri.ts +4 -1
  40. package/src/domains/info/commands/upgrade.ts +6 -3
  41. package/src/domains/info/index.ts +3 -1
  42. package/src/domains/info/mcp/index.ts +2 -0
  43. package/src/domains/info/mcp/specs.ts +65 -0
  44. package/src/domains/llm/data/commandReference.ts +4 -4
  45. package/src/domains/llm/data/decisionTrees.ts +14 -12
  46. package/src/domains/llm/operations/collectContext.ts +1 -1
  47. package/src/domains/modifier/commands/list.ts +9 -0
  48. package/src/domains/modifier/commands/lookup.ts +22 -42
  49. package/src/domains/modifier/formatters/index.ts +4 -1
  50. package/src/domains/modifier/formatters/list.test.ts +1 -1
  51. package/src/domains/modifier/formatters/list.ts +13 -26
  52. package/src/domains/modifier/formatters/lookup.test.ts +1 -1
  53. package/src/domains/modifier/formatters/lookup.ts +41 -29
  54. package/src/domains/modifier/index.ts +1 -0
  55. package/src/domains/modifier/modifierConfig.ts +36 -0
  56. package/src/domains/modifier/operations/list.ts +1 -1
  57. package/src/domains/modifier/operations/lookup.ts +4 -3
  58. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +1 -1
  59. package/src/domains/modifier/types.ts +1 -1
  60. package/src/domains/ontology/commands/list.test.ts +1 -1
  61. package/src/domains/ontology/commands/list.ts +18 -0
  62. package/src/domains/ontology/commands/show.test.ts +1 -1
  63. package/src/domains/ontology/formatters/list.ts +5 -3
  64. package/src/domains/ontology/formatters/show.ts +38 -13
  65. package/src/domains/ontology/helpers/queryClasses.ts +1 -1
  66. package/src/domains/ontology/helpers/queryProperties.ts +1 -1
  67. package/src/domains/ontology/operations/listOntologies.ts +1 -1
  68. package/src/domains/ontology/operations/showOntology.ts +1 -1
  69. package/src/domains/setup/helpers/completionScripts.test.ts +3 -3
  70. package/src/domains/setup/helpers/completionScripts.ts +4 -4
  71. package/src/domains/shared/ToolSpec.ts +1 -1
  72. package/src/domains/shared/context.ts +3 -2
  73. package/src/domains/shared/contracts.ts +40 -1
  74. package/src/domains/shared/filters/buildFilterConfig.ts +1 -2
  75. package/src/domains/shared/filters/buildFilters.ts +1 -1
  76. package/src/domains/shared/filters/describeFilters.ts +1 -1
  77. package/src/domains/shared/formatters.ts +47 -0
  78. package/src/domains/shared/renderers.ts +410 -0
  79. package/src/domains/shared/runtime.ts +7 -22
  80. package/src/domains/shared/types/block.ts +107 -0
  81. package/src/domains/shared/types/common.ts +33 -0
  82. package/src/domains/shared/types/graph.ts +17 -0
  83. package/src/domains/shared/types/index.ts +49 -0
  84. package/src/domains/shared/types/modifier.ts +22 -0
  85. package/src/domains/shared/types/ontology.ts +50 -0
  86. package/src/domains/shared/types/runtime.ts +21 -0
  87. package/src/domains/shared/types/standard.ts +64 -0
  88. package/src/domains/shared/types/tier.ts +15 -0
  89. package/src/domains/shared/types/token.ts +30 -0
  90. package/src/domains/standard/commands/list.ts +9 -0
  91. package/src/domains/standard/commands/lookup.ts +22 -50
  92. package/src/domains/standard/formatters/categories.test.ts +1 -1
  93. package/src/domains/standard/formatters/categories.ts +1 -1
  94. package/src/domains/standard/formatters/index.ts +4 -1
  95. package/src/domains/standard/formatters/list.test.ts +4 -1
  96. package/src/domains/standard/formatters/list.ts +112 -84
  97. package/src/domains/standard/formatters/lookup.test.ts +1 -1
  98. package/src/domains/standard/formatters/lookup.ts +85 -70
  99. package/src/domains/standard/formatters/types.ts +1 -1
  100. package/src/domains/standard/index.ts +1 -0
  101. package/src/domains/standard/operations/categories.ts +1 -1
  102. package/src/domains/standard/operations/list.ts +1 -1
  103. package/src/domains/standard/operations/lookup.ts +1 -1
  104. package/src/domains/standard/orchestration/buildStandardFilters.ts +1 -1
  105. package/src/domains/standard/orchestration/resolveStandardList.ts +4 -1
  106. package/src/domains/standard/orchestration/resolveStandardLookup.ts +1 -1
  107. package/src/domains/standard/orchestration/standardEmptyError.ts +1 -1
  108. package/src/domains/standard/standardConfig.ts +42 -0
  109. package/src/domains/standard/types.ts +1 -1
  110. package/src/domains/tier/commands/list.ts +16 -0
  111. package/src/domains/tier/formatters/list.test.ts +1 -1
  112. package/src/domains/tier/formatters/list.ts +11 -6
  113. package/src/domains/tier/operations/list.ts +1 -1
  114. package/src/domains/token/commands/list.ts +9 -0
  115. package/src/domains/token/commands/lookup.ts +22 -48
  116. package/src/domains/token/formatters/index.ts +4 -1
  117. package/src/domains/token/formatters/list.test.ts +1 -1
  118. package/src/domains/token/formatters/list.ts +15 -28
  119. package/src/domains/token/formatters/lookup.test.ts +2 -2
  120. package/src/domains/token/formatters/lookup.ts +80 -45
  121. package/src/domains/token/index.ts +1 -0
  122. package/src/domains/token/operations/list.ts +1 -1
  123. package/src/domains/token/operations/lookup.ts +1 -1
  124. package/src/domains/token/orchestration/resolveTokenLookup.ts +1 -1
  125. package/src/domains/token/tokenConfig.ts +38 -0
  126. package/src/domains/token/types.ts +1 -1
  127. package/src/embedWasm.ts +42 -0
  128. package/src/index.ts +1 -1
  129. package/src/mcp/runMcpServer.test.ts +2 -2
  130. package/src/mcp/tools/index.ts +2 -0
  131. package/src/mcp/tools/registerTools.test.ts +8 -3
  132. package/src/package-manager/types.ts +1 -3
  133. package/src/pipeline/createProgram.ts +20 -1
  134. package/src/pipeline/index.ts +4 -1
  135. package/src/pipeline/parseGlobalFlags.test.ts +92 -0
  136. package/src/pipeline/parseGlobalFlags.ts +25 -0
  137. package/src/pipeline/runCli.orchestration.test.ts +1 -0
  138. package/src/pipeline/runCli.ts +11 -4
  139. package/src/pipeline/runInteractiveCommand.ts +198 -0
  140. package/src/testing/cli.ts +2 -2
  141. package/src/testing/fixtures.ts +2 -2
  142. package/src/testing/integration/boot.test.ts +2 -2
  143. package/src/testing/integration/cli-user-stories.test.ts +198 -0
  144. package/src/testing/integration/edge-cases.test.ts +263 -0
  145. package/src/testing/integration/parity.test.ts +377 -27
  146. package/src/testing/store.ts +1 -1
  147. package/src/tui/components/BulletList/BulletList.tsx +46 -0
  148. package/src/tui/components/BulletList/index.ts +2 -0
  149. package/src/tui/components/BulletList/types.ts +6 -0
  150. package/src/tui/components/Card/Card.tsx +73 -0
  151. package/src/tui/components/Card/index.ts +1 -0
  152. package/src/tui/components/Card/types.ts +12 -0
  153. package/src/tui/components/CodeBlock/CodeBlock.tsx +37 -0
  154. package/src/tui/components/CodeBlock/index.ts +2 -0
  155. package/src/tui/components/CodeBlock/types.ts +6 -0
  156. package/src/tui/components/DataRow/DataRow.tsx +80 -0
  157. package/src/tui/components/DataRow/index.ts +2 -0
  158. package/src/tui/components/DataRow/types.ts +14 -0
  159. package/src/tui/components/FieldRow/FieldRow.tsx +24 -0
  160. package/src/tui/components/FieldRow/index.ts +2 -0
  161. package/src/tui/components/FieldRow/types.ts +10 -0
  162. package/src/tui/components/HeaderRow/HeaderRow.tsx +32 -0
  163. package/src/tui/components/HeaderRow/index.ts +2 -0
  164. package/src/tui/components/HeaderRow/types.ts +8 -0
  165. package/src/tui/components/MarkdownText/MarkdownText.tsx +19 -0
  166. package/src/tui/components/MarkdownText/index.ts +2 -0
  167. package/src/tui/components/MarkdownText/types.ts +4 -0
  168. package/src/tui/components/MiniTable/MiniTable.tsx +82 -0
  169. package/src/tui/components/MiniTable/index.ts +2 -0
  170. package/src/tui/components/MiniTable/types.ts +6 -0
  171. package/src/tui/components/SectionRenderer/SectionRenderer.tsx +91 -0
  172. package/src/tui/components/SectionRenderer/index.ts +2 -0
  173. package/src/tui/components/SectionRenderer/types.ts +15 -0
  174. package/src/tui/components/TreeView/TreeView.tsx +84 -0
  175. package/src/tui/components/TreeView/index.ts +2 -0
  176. package/src/tui/components/TreeView/types.ts +6 -0
  177. package/src/tui/components/index.ts +19 -0
  178. package/src/tui/constants.ts +39 -0
  179. package/src/tui/createListView.ts +23 -0
  180. package/src/tui/createLookupView.ts +26 -0
  181. package/src/tui/helpers/computeWidths.ts +53 -0
  182. package/src/tui/helpers/fitColumns.ts +33 -0
  183. package/src/tui/helpers/formatMarkdown.ts +19 -0
  184. package/src/tui/helpers/getTerminalSize.ts +16 -0
  185. package/src/tui/helpers/index.ts +6 -0
  186. package/src/tui/helpers/syntaxColor.ts +67 -0
  187. package/src/tui/helpers/truncateText.ts +12 -0
  188. package/src/tui/hooks/index.ts +3 -0
  189. package/src/tui/hooks/types.ts +7 -0
  190. package/src/tui/hooks/useAutoExit.ts +17 -0
  191. package/src/tui/hooks/useTerminalSize.ts +19 -0
  192. package/src/tui/index.ts +15 -0
  193. package/src/tui/renderInk.tsx +18 -0
  194. package/src/tui/types.ts +12 -0
  195. package/src/tui/views/ListView/ListView.tsx +104 -0
  196. package/src/tui/views/ListView/index.ts +2 -0
  197. package/src/tui/views/ListView/types.ts +14 -0
  198. package/src/tui/views/LookupView/LookupView.tsx +244 -0
  199. package/src/tui/views/LookupView/index.ts +2 -0
  200. package/src/tui/views/LookupView/types.ts +16 -0
  201. package/src/tui/views/index.ts +4 -0
  202. package/src/domains/shared/types.test-d.ts +0 -127
  203. package/src/domains/shared/types.ts +0 -348
@@ -2,7 +2,7 @@
2
2
  * Look up detailed information for a single block.
3
3
  *
4
4
  * Queries the base block, then resolves related modifiers, implementations,
5
- * tokens, anatomy nodes, blank-node properties, and direct subcomponents.
5
+ * tokens, anatomy nodes, blank-node properties, and recursive subcomponents.
6
6
  * The result is assembled into a {@link BlockDetailed} object.
7
7
  *
8
8
  * @param store - ke store to query
@@ -23,10 +23,11 @@ import { buildFilters } from "../../shared/filters/buildFilters.js";
23
23
  import { P } from "../../shared/prefixes.js";
24
24
  import type {
25
25
  BlockDetailed,
26
+ BlockSubcomponent,
26
27
  FilterConfig,
27
28
  StandardRef,
28
29
  TokenRef,
29
- } from "../../shared/types.js";
30
+ } from "../../shared/types/index.js";
30
31
 
31
32
  function normalizeBlockType(value: string | undefined): BlockDetailed["type"] {
32
33
  const localName = extractLocalName(value ?? "").toLowerCase();
@@ -45,17 +46,17 @@ export default async function lookupBlock(
45
46
  store: Store,
46
47
  nameOrUri: string,
47
48
  filters: FilterConfig,
48
- ): Promise<BlockDetailed> {
49
+ ): Promise<BlockDetailed[]> {
49
50
  const filterClauses = buildFilters(filters);
50
51
  const subjectClause = buildLookupSubjectClause(nameOrUri, store.prefixes);
51
52
  const bindClause =
52
53
  subjectClause === null ? "" : `BIND(${subjectClause} AS ?component)`;
53
54
  const matchClause =
54
55
  subjectClause === null
55
- ? `?component ${P.ds}name ${escapeSparqlValue(nameOrUri)} .`
56
+ ? `?component ${P.ds}name ?name . FILTER(LCASE(STR(?name)) = ${escapeSparqlValue(nameOrUri.toLowerCase())})`
56
57
  : "";
57
58
 
58
- // Base query: find the block
59
+ // Base query: find the block(s) — no LIMIT so ambiguous names return all matches
59
60
  const baseResult = await store.query(
60
61
  buildQuery(`
61
62
  SELECT ?component ?name ?type ?tier ?summary ?whenToUse ?whenNotToUse ?guidelines ?anatomyDsl ?anatomyClassic ?figmaLink
@@ -75,7 +76,6 @@ export default async function lookupBlock(
75
76
  OPTIONAL { ?component ${P.ds}figmaLink ?figmaLink }
76
77
  ${filterClauses}
77
78
  }
78
- LIMIT 1
79
79
  `),
80
80
  );
81
81
 
@@ -89,8 +89,18 @@ export default async function lookupBlock(
89
89
  });
90
90
  }
91
91
 
92
- // Safe: length check above guarantees bindings[0] exists
93
- const base = baseResult.bindings[0] as (typeof baseResult.bindings)[number];
92
+ const results: BlockDetailed[] = [];
93
+ for (const base of baseResult.bindings) {
94
+ results.push(await resolveBlockDetail(store, base, nameOrUri));
95
+ }
96
+ return results;
97
+ }
98
+
99
+ async function resolveBlockDetail(
100
+ store: Store,
101
+ base: Record<string, string>,
102
+ nameOrUri: string,
103
+ ): Promise<BlockDetailed> {
94
104
  const componentUri = base.component;
95
105
 
96
106
  // Modifier families with values
@@ -231,24 +241,31 @@ export default async function lookupBlock(
231
241
  }))
232
242
  : [];
233
243
 
234
- // Direct subcomponents
244
+ // Recursive subcomponents
235
245
  const subcomponentsResult = await store.query(
236
246
  buildQuery(`
237
- SELECT ?subcomponent ?name
247
+ SELECT DISTINCT ?parent ?subcomponent ?name
238
248
  WHERE {
239
- <${componentUri}> ${P.ds}hasSubcomponent ?subcomponent .
249
+ <${componentUri}> (${P.ds}hasSubcomponent|^${P.ds}parentComponent)+ ?subcomponent .
250
+ ?parent (${P.ds}hasSubcomponent|^${P.ds}parentComponent) ?subcomponent .
240
251
  ?subcomponent ${P.ds}name ?name .
252
+ FILTER(
253
+ ?parent = <${componentUri}> ||
254
+ EXISTS {
255
+ <${componentUri}> (${P.ds}hasSubcomponent|^${P.ds}parentComponent)+ ?parent
256
+ }
257
+ )
241
258
  }
242
- ORDER BY ?name
259
+ ORDER BY ?parent ?name
243
260
  `),
244
261
  );
245
262
 
246
263
  const subcomponents =
247
264
  subcomponentsResult.type === "select"
248
- ? subcomponentsResult.bindings.map((binding) => ({
249
- uri: (binding.subcomponent ?? "") as URI,
250
- name: binding.name ?? "",
251
- }))
265
+ ? buildSubcomponentTree(
266
+ (componentUri ?? "") as URI,
267
+ subcomponentsResult.bindings,
268
+ )
252
269
  : [];
253
270
 
254
271
  // Standards (not linked directly to blocks in current ontology;
@@ -357,3 +374,64 @@ async function listAllFrameworks(store: Store): Promise<string[]> {
357
374
  if (result.type !== "select") return [];
358
375
  return result.bindings.map((b) => b.framework ?? "");
359
376
  }
377
+
378
+ function buildSubcomponentTree(
379
+ rootUri: URI,
380
+ bindings: readonly Record<string, string>[],
381
+ ): readonly BlockSubcomponent[] {
382
+ const nodeMap = new Map<string, BlockSubcomponent>();
383
+ const childrenByParent = new Map<string, string[]>();
384
+
385
+ for (const binding of bindings) {
386
+ const uri = (binding.subcomponent ?? "") as URI;
387
+ const parent = (binding.parent ?? "") as URI;
388
+ if (!uri || !parent) {
389
+ continue;
390
+ }
391
+
392
+ nodeMap.set(String(uri), {
393
+ uri,
394
+ name: binding.name ?? extractLocalName(String(uri)),
395
+ children: [],
396
+ });
397
+
398
+ const siblings = childrenByParent.get(String(parent)) ?? [];
399
+ if (!siblings.includes(String(uri))) {
400
+ siblings.push(String(uri));
401
+ childrenByParent.set(String(parent), siblings);
402
+ }
403
+ }
404
+
405
+ return buildSubcomponentChildren(String(rootUri), childrenByParent, nodeMap);
406
+ }
407
+
408
+ function buildSubcomponentChildren(
409
+ parentUri: string,
410
+ childrenByParent: ReadonlyMap<string, readonly string[]>,
411
+ nodeMap: ReadonlyMap<string, BlockSubcomponent>,
412
+ visited = new Set<string>(),
413
+ ): readonly BlockSubcomponent[] {
414
+ const childUris = childrenByParent.get(parentUri) ?? [];
415
+
416
+ return childUris.flatMap((childUri) => {
417
+ const node = nodeMap.get(childUri);
418
+ if (!node || visited.has(childUri)) {
419
+ return [];
420
+ }
421
+
422
+ const nextVisited = new Set(visited);
423
+ nextVisited.add(childUri);
424
+
425
+ return [
426
+ {
427
+ ...node,
428
+ children: buildSubcomponentChildren(
429
+ childUri,
430
+ childrenByParent,
431
+ nodeMap,
432
+ nextVisited,
433
+ ),
434
+ },
435
+ ];
436
+ });
437
+ }
@@ -1,6 +1,6 @@
1
1
  import { PragmaError } from "#error";
2
2
  import { describeFilters } from "../../shared/filters/index.js";
3
- import type { FilterConfig } from "../../shared/types.js";
3
+ import type { FilterConfig } from "../../shared/types/index.js";
4
4
 
5
5
  export default function blockEmptyError(
6
6
  filters: FilterConfig,
@@ -1,5 +1,5 @@
1
1
  import type { PragmaRuntime } from "../../shared/runtime.js";
2
- import type { FilterConfig } from "../../shared/types.js";
2
+ import type { FilterConfig } from "../../shared/types/index.js";
3
3
 
4
4
  export default function buildBlockFilters(
5
5
  rt: Pick<PragmaRuntime, "config">,
@@ -4,7 +4,7 @@ import type {
4
4
  BlockSummary,
5
5
  Disclosure,
6
6
  FilterConfig,
7
- } from "../../shared/types.js";
7
+ } from "../../shared/types/index.js";
8
8
  import { lookupBlock } from "../operations/index.js";
9
9
  import type { BlockListDigest } from "../types.js";
10
10
 
@@ -14,9 +14,13 @@ export default async function enrichBlocks(
14
14
  filters: FilterConfig,
15
15
  disclosure: Disclosure,
16
16
  ): Promise<readonly (BlockListDigest | BlockDetailed)[]> {
17
- const details = await Promise.all(
17
+ const nested = await Promise.all(
18
18
  summaries.map((summary) => lookupBlock(store, summary.name, filters)),
19
19
  );
20
+ // lookupBlock returns BlockDetailed[] — take the first match per summary
21
+ const details: BlockDetailed[] = nested.flatMap((matches) =>
22
+ matches[0] ? [matches[0]] : [],
23
+ );
20
24
 
21
25
  if (disclosure.level === "detailed") {
22
26
  return details;
@@ -1,6 +1,6 @@
1
1
  import type { ListParams } from "../../shared/contracts.js";
2
2
  import type { PragmaRuntime } from "../../shared/runtime.js";
3
- import type { Disclosure } from "../../shared/types.js";
3
+ import type { Disclosure } from "../../shared/types/index.js";
4
4
  import { listBlocks } from "../operations/index.js";
5
5
  import type { BlockListContract } from "../types.js";
6
6
  import blockEmptyError from "./blockEmptyError.js";
@@ -1,7 +1,7 @@
1
1
  import type { LookupContract, LookupResult } from "../../shared/contracts.js";
2
2
  import lookupMany from "../../shared/lookupMany.js";
3
3
  import type { PragmaRuntime } from "../../shared/runtime.js";
4
- import type { BlockDetailed, FilterConfig } from "../../shared/types.js";
4
+ import type { BlockDetailed, FilterConfig } from "../../shared/types/index.js";
5
5
  import { lookupBlock } from "../operations/index.js";
6
6
 
7
7
  export default async function resolveBlockLookup(
@@ -9,11 +9,17 @@ export default async function resolveBlockLookup(
9
9
  queries: readonly string[],
10
10
  filters: FilterConfig,
11
11
  ): Promise<LookupContract<BlockDetailed>> {
12
- const result: LookupResult<BlockDetailed> = await lookupMany(
12
+ const nested: LookupResult<BlockDetailed[]> = await lookupMany(
13
13
  queries,
14
14
  (query) => lookupBlock(store, query, filters),
15
15
  );
16
16
 
17
+ // lookupBlock returns BlockDetailed[] (multiple matches per name) — flatten
18
+ const result: LookupResult<BlockDetailed> = {
19
+ results: nested.results.flat(),
20
+ errors: nested.errors,
21
+ };
22
+
17
23
  return {
18
24
  params: { names: queries },
19
25
  result,
@@ -5,7 +5,7 @@
5
5
  */
6
6
 
7
7
  import type { ListContract } from "../shared/contracts.js";
8
- import type { BlockDetailed, BlockSummary } from "../shared/types.js";
8
+ import type { BlockDetailed, BlockSummary } from "../shared/types/index.js";
9
9
 
10
10
  /** Flags controlling which detail aspects are included in block output. */
11
11
  export interface AspectFlags {
@@ -3,11 +3,13 @@ import {
3
3
  type CommandResult,
4
4
  createOutputResult,
5
5
  } from "@canonical/cli-core";
6
- import { readConfig, writeConfig } from "#config";
6
+ import { runTask } from "@canonical/task";
7
+ import { readConfig } from "#config";
7
8
  import type { PragmaContext } from "../../shared/context.js";
8
9
  import { selectFormatter } from "../../shared/formatters.js";
9
10
  import { channelFormatters } from "../formatters/index.js";
10
11
  import { validateChannel } from "../operations/index.js";
12
+ import { setChannelTask } from "../tasks/index.js";
11
13
 
12
14
  /**
13
15
  * Build the `pragma config channel` command definition.
@@ -17,7 +19,7 @@ import { validateChannel } from "../operations/index.js";
17
19
  *
18
20
  * @param ctx - Pragma context providing cwd and formatter selection.
19
21
  * @returns The command definition for `pragma config channel`.
20
- * @note Impure
22
+ * @note - Impure — validates channel and writes config to disk.
21
23
  */
22
24
  export default function buildChannelCommand(
23
25
  ctx: PragmaContext,
@@ -52,7 +54,7 @@ export default function buildChannelCommand(
52
54
  const value = params.value as string | undefined;
53
55
 
54
56
  if (reset) {
55
- writeConfig(ctx.cwd, { channel: undefined });
57
+ await runTask(setChannelTask(ctx.cwd, undefined));
56
58
  const format = selectFormatter(ctx, channelFormatters.reset);
57
59
  return createOutputResult("Reset channel to default.", {
58
60
  plain: format,
@@ -69,7 +71,7 @@ export default function buildChannelCommand(
69
71
 
70
72
  const channel = validateChannel(value);
71
73
 
72
- writeConfig(ctx.cwd, { channel });
74
+ await runTask(setChannelTask(ctx.cwd, channel));
73
75
  const format = selectFormatter(ctx, channelFormatters.set);
74
76
  return createOutputResult(
75
77
  { field: "channel", value: channel },
@@ -3,11 +3,13 @@ import {
3
3
  type CommandResult,
4
4
  createOutputResult,
5
5
  } from "@canonical/cli-core";
6
- import { readConfig, writeConfig } from "#config";
6
+ import { runTask } from "@canonical/task";
7
+ import { readConfig } from "#config";
7
8
  import type { PragmaContext } from "../../shared/context.js";
8
9
  import { selectFormatter } from "../../shared/formatters.js";
9
10
  import { tierFormatters } from "../formatters/index.js";
10
11
  import { validateTier } from "../operations/index.js";
12
+ import { setTierTask } from "../tasks/index.js";
11
13
 
12
14
  /**
13
15
  * Build the `pragma config tier` command definition.
@@ -17,7 +19,7 @@ import { validateTier } from "../operations/index.js";
17
19
  *
18
20
  * @param ctx - Pragma context providing cwd, store, and formatter selection.
19
21
  * @returns The command definition for `pragma config tier`.
20
- * @note Impure
22
+ * @note - Impure — validates tier against the ke store and writes config to disk.
21
23
  */
22
24
  export default function buildTierCommand(
23
25
  ctx: PragmaContext,
@@ -49,7 +51,7 @@ export default function buildTierCommand(
49
51
  const tierPath = params.path as string | undefined;
50
52
 
51
53
  if (reset) {
52
- writeConfig(ctx.cwd, { tier: undefined });
54
+ await runTask(setTierTask(ctx.cwd, undefined));
53
55
  const format = selectFormatter(ctx, tierFormatters.reset);
54
56
  return createOutputResult("Reset tier to default.", {
55
57
  plain: format,
@@ -67,7 +69,7 @@ export default function buildTierCommand(
67
69
  // Validate tier against ontology
68
70
  await validateTier(ctx.store, tierPath);
69
71
 
70
- writeConfig(ctx.cwd, { tier: tierPath });
72
+ await runTask(setTierTask(ctx.cwd, tierPath));
71
73
  const format = selectFormatter(ctx, tierFormatters.set);
72
74
  return createOutputResult(
73
75
  { field: "tier", value: tierPath },
@@ -1,10 +1,19 @@
1
1
  /**
2
2
  * MCP tool specs for the config domain.
3
+ *
4
+ * config_show uses the shared resolveConfigShow() operation so that
5
+ * MCP data matches what the CLI produces (tierChain, includedReleases, etc.).
3
6
  */
4
7
 
5
8
  import { readConfig, writeConfig } from "#config";
9
+ import { detectInstallSource } from "#package-manager";
6
10
  import type { ToolSpec } from "../../shared/ToolSpec.js";
7
- import { validateChannel, validateTier } from "../operations/index.js";
11
+ import { showFormatters } from "../formatters/index.js";
12
+ import {
13
+ resolveConfigShow,
14
+ validateChannel,
15
+ validateTier,
16
+ } from "../operations/index.js";
8
17
 
9
18
  const specs: readonly ToolSpec[] = [
10
19
  {
@@ -20,13 +29,16 @@ const specs: readonly ToolSpec[] = [
20
29
  },
21
30
  readOnly: true,
22
31
  async execute(rt, { condensed }) {
23
- const data = {
24
- tier: rt.config.tier ?? null,
25
- channel: rt.config.channel,
26
- };
32
+ const install = detectInstallSource();
33
+ const data = resolveConfigShow(rt.config, {
34
+ packageManager: install.packageManager,
35
+ installSource: install.label,
36
+ configFilePath: "pragma.config.json",
37
+ configFileExists: true,
38
+ });
27
39
 
28
40
  if (condensed) {
29
- const text = `Config: tier=${data.tier ?? "(none)"} channel=${data.channel}`;
41
+ const text = showFormatters.llm(data);
30
42
  return {
31
43
  condensed: true,
32
44
  text,
@@ -1,6 +1,6 @@
1
1
  import type { Store } from "@canonical/ke";
2
2
  import { PragmaError } from "#error";
3
- import type { TierEntry } from "../../shared/types.js";
3
+ import type { TierEntry } from "../../shared/types/index.js";
4
4
  import { listTiers } from "../../tier/operations/index.js";
5
5
 
6
6
  /**
@@ -0,0 +1,3 @@
1
+ /** @module Config write tasks. */
2
+ export { default as setChannelTask } from "./setChannel.js";
3
+ export { default as setTierTask } from "./setTier.js";
@@ -0,0 +1,30 @@
1
+ import { $, gen, type Task, writeFile } from "@canonical/task";
2
+ import { readConfig } from "../../../config/index.js";
3
+ import resolveConfigPath from "../../../config/resolveConfigPath.js";
4
+
5
+ /**
6
+ * Build a config-write task for setting or resetting the release channel.
7
+ *
8
+ * @param cwd - Working directory containing pragma.config.json.
9
+ * @param channel - Channel value to persist, or `undefined` to reset.
10
+ * @returns Task yielding the persisted field/value payload.
11
+ * @note - Impure — reads config and writes to the file system.
12
+ */
13
+ export default function setChannel(
14
+ cwd: string,
15
+ channel: string | undefined,
16
+ ): Task<{ field: "channel"; value: string | undefined }> {
17
+ return gen(function* () {
18
+ const current = readConfig(cwd);
19
+ const next = {
20
+ ...(current.tier !== undefined ? { tier: current.tier } : {}),
21
+ ...(channel !== undefined ? { channel } : {}),
22
+ };
23
+
24
+ yield* $(
25
+ writeFile(resolveConfigPath(cwd), `${JSON.stringify(next, null, 2)}\n`),
26
+ );
27
+
28
+ return { field: "channel", value: channel };
29
+ });
30
+ }
@@ -0,0 +1,33 @@
1
+ import { $, gen, type Task, writeFile } from "@canonical/task";
2
+ import { readConfig } from "../../../config/index.js";
3
+ import resolveConfigPath from "../../../config/resolveConfigPath.js";
4
+
5
+ /**
6
+ * Build a config-write task for setting or resetting the active tier.
7
+ *
8
+ * @param cwd - Working directory containing pragma.config.json.
9
+ * @param tier - Tier path to persist, or `undefined` to reset.
10
+ * @returns Task yielding the persisted field/value payload.
11
+ * @note - Impure — reads config and writes to the file system.
12
+ */
13
+ export default function setTier(
14
+ cwd: string,
15
+ tier: string | undefined,
16
+ ): Task<{
17
+ field: "tier";
18
+ value: string | undefined;
19
+ }> {
20
+ return gen(function* () {
21
+ const current = readConfig(cwd);
22
+ const next = {
23
+ ...(tier !== undefined ? { tier } : {}),
24
+ ...(current.channel !== undefined ? { channel: current.channel } : {}),
25
+ };
26
+
27
+ yield* $(
28
+ writeFile(resolveConfigPath(cwd), `${JSON.stringify(next, null, 2)}\n`),
29
+ );
30
+
31
+ return { field: "tier", value: tier };
32
+ });
33
+ }
@@ -27,9 +27,9 @@ describe("buildPackageCommand", () => {
27
27
  expect(result.tag).toBe("output");
28
28
  });
29
29
 
30
- it("with missing required params returns interactive", async () => {
30
+ it("with missing required params returns batch output outside a TTY", async () => {
31
31
  const cmd = buildPackageCommand();
32
32
  const result = await cmd.execute({}, ctx);
33
- expect(result.tag).toBe("interactive");
33
+ expect(result.tag).toBe("output");
34
34
  });
35
35
  });
@@ -1,2 +1,2 @@
1
- /** @module Doctor domain MCP barrel — doctor, info. */
1
+ /** @module Doctor domain MCP barrel — doctor. */
2
2
  export { default as specs } from "./specs.js";
@@ -1,15 +1,9 @@
1
1
  /**
2
- * MCP tool specs for doctor domain — doctor, info.
2
+ * MCP tool specs for the doctor domain.
3
3
  *
4
- * The info tool is cross-domain: it uses operations and formatters from
5
- * the info domain, plus VERSION from #constants.
4
+ * The info tool has moved to its own domain: `info/mcp/specs.ts`.
6
5
  */
7
6
 
8
- import { VERSION } from "#constants";
9
- import { detectInstallSource } from "#package-manager";
10
- import { renderInfoLlm } from "../../info/formatters/index.js";
11
- import { collectStoreSummary } from "../../info/operations/index.js";
12
- import type { InfoData } from "../../info/types.js";
13
7
  import type { ToolSpec } from "../../shared/ToolSpec.js";
14
8
  import { doctorFormatters } from "../formatters/index.js";
15
9
  import { runChecks } from "../operations/index.js";
@@ -42,47 +36,6 @@ const specs: readonly ToolSpec[] = [
42
36
  return { data: result };
43
37
  },
44
38
  },
45
- {
46
- name: "info",
47
- description: "Show pragma version, configuration, and store summary.",
48
- params: {
49
- condensed: {
50
- type: "boolean",
51
- description: "Token-optimized output",
52
- optional: true,
53
- },
54
- },
55
- readOnly: true,
56
- async execute(rt, params) {
57
- const storeSummary = await collectStoreSummary(rt.store);
58
- const install = detectInstallSource();
59
-
60
- const data: InfoData = {
61
- version: VERSION,
62
- pm: install.packageManager,
63
- installSource: install.label,
64
- configPath: "pragma.config.json",
65
- tier: rt.config.tier,
66
- tierChain: [],
67
- channel: rt.config.channel,
68
- channelReleases: [],
69
- update: undefined,
70
- updateSkipped: true,
71
- store: storeSummary,
72
- };
73
-
74
- if (params.condensed) {
75
- const text = renderInfoLlm(data);
76
- return {
77
- condensed: true,
78
- text,
79
- tokens: `~${Math.ceil(text.length / 4)}`,
80
- };
81
- }
82
-
83
- return { data };
84
- },
85
- },
86
39
  ];
87
40
 
88
41
  export default specs;
@@ -4,7 +4,7 @@ import createTestRuntime from "../../../testing/helpers/createTestRuntime.js";
4
4
  import type { PragmaContext } from "../../shared/context.js";
5
5
  import { PREFIX_MAP } from "../../shared/prefixes.js";
6
6
  import type { PragmaRuntime } from "../../shared/runtime.js";
7
- import type { InspectResult } from "../../shared/types.js";
7
+ import type { InspectResult } from "../../shared/types/index.js";
8
8
  import buildInspectCommand from "./inspect.js";
9
9
 
10
10
  let runtime: PragmaRuntime;
@@ -1,7 +1,9 @@
1
1
  import chalk from "chalk";
2
2
  import { formatHeading } from "#pipeline";
3
+ import compactUri from "../../shared/compactUri.js";
3
4
  import type { Formatters } from "../../shared/formatters.js";
4
- import type { InspectResult } from "../../shared/types.js";
5
+ import { PREFIX_MAP } from "../../shared/prefixes.js";
6
+ import type { InspectResult } from "../../shared/types/index.js";
5
7
 
6
8
  /**
7
9
  * Formatters for `pragma graph inspect` output.
@@ -13,13 +15,13 @@ import type { InspectResult } from "../../shared/types.js";
13
15
  const formatters: Formatters<InspectResult> = {
14
16
  plain(result) {
15
17
  const lines: string[] = [];
16
- lines.push(formatHeading(result.uri));
18
+ lines.push(formatHeading(compactUri(result.uri, PREFIX_MAP)));
17
19
 
18
20
  for (const g of result.groups) {
19
21
  lines.push("");
20
- lines.push(chalk.bold(g.predicate));
22
+ lines.push(chalk.bold(compactUri(g.predicate, PREFIX_MAP)));
21
23
  for (const o of g.objects) {
22
- lines.push(` ${o}`);
24
+ lines.push(` ${compactUri(o, PREFIX_MAP)}`);
23
25
  }
24
26
  }
25
27
 
@@ -27,11 +29,14 @@ const formatters: Formatters<InspectResult> = {
27
29
  },
28
30
 
29
31
  llm(result) {
30
- const lines = [`## ${result.uri}`, ""];
32
+ const lines = [
33
+ `## ${compactUri(result.uri, PREFIX_MAP)} (${result.uri})`,
34
+ "",
35
+ ];
31
36
  for (const g of result.groups) {
32
- lines.push(`**${g.predicate}:**`);
37
+ lines.push(`**${compactUri(g.predicate, PREFIX_MAP)}:**`);
33
38
  for (const o of g.objects) {
34
- lines.push(` - ${o}`);
39
+ lines.push(` - ${compactUri(o, PREFIX_MAP)}`);
35
40
  }
36
41
  }
37
42
  return lines.join("\n");
@@ -1,7 +1,10 @@
1
1
  import type { Store } from "@canonical/ke";
2
2
  import { PragmaError } from "#error";
3
3
  import { buildQuery } from "../../shared/buildQuery.js";
4
- import type { InspectResult, PredicateGroup } from "../../shared/types.js";
4
+ import type {
5
+ InspectResult,
6
+ PredicateGroup,
7
+ } from "../../shared/types/index.js";
5
8
  import resolveUri from "../helpers/resolveUri.js";
6
9
 
7
10
  /**
@@ -3,7 +3,7 @@ import type { CommandDefinition, CommandResult } from "@canonical/cli-core";
3
3
  import { readConfig } from "#config";
4
4
  import { VERSION } from "#constants";
5
5
  import { PragmaError } from "#error";
6
- import { detectPackageManager, PM_COMMANDS } from "#package-manager";
6
+ import { detectInstallSource, PM_COMMANDS } from "#package-manager";
7
7
  import {
8
8
  renderUpgradeJson,
9
9
  renderUpgradeLlm,
@@ -58,9 +58,12 @@ async function executeUpgrade(
58
58
  ctx: { cwd: string; globalFlags: { llm: boolean; format: "text" | "json" } },
59
59
  ): Promise<CommandResult> {
60
60
  const dryRun = params.dryRun === true;
61
- const pm = detectPackageManager();
61
+ const source = detectInstallSource();
62
+ const pm = source.label;
62
63
  const config = readConfig(ctx.cwd);
63
- const command = PM_COMMANDS[pm].update("@canonical/pragma-cli");
64
+ const command = PM_COMMANDS[source.packageManager].update(
65
+ "@canonical/pragma-cli",
66
+ );
64
67
 
65
68
  const registryResult = await checkRegistryVersion(
66
69
  "@canonical/pragma-cli",
@@ -1,7 +1,9 @@
1
1
  /**
2
2
  * @module Info domain barrel.
3
3
  *
4
- * Provides the `pragma info` and `pragma upgrade` commands.
4
+ * Provides the `pragma info` and `pragma upgrade` commands,
5
+ * and the `info` MCP tool spec.
5
6
  */
6
7
 
7
8
  export { infoCommand, upgradeCommand } from "./commands/index.js";
9
+ export { specs as infoSpecs } from "./mcp/index.js";
@@ -0,0 +1,2 @@
1
+ /** @module Info domain MCP barrel — info. */
2
+ export { default as specs } from "./specs.js";