@canonical/pragma-cli 0.27.0 → 0.29.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 (169) hide show
  1. package/dist/pragma +0 -0
  2. package/package.json +19 -17
  3. package/src/config/readConfig.ts +38 -2
  4. package/src/config/types.ts +12 -1
  5. package/src/config/writeConfig.ts +12 -0
  6. package/src/constants.ts +12 -1
  7. package/src/domains/block/commands/index.ts +2 -1
  8. package/src/domains/block/commands/sample.ts +55 -0
  9. package/src/domains/block/formatters/index.ts +2 -1
  10. package/src/domains/block/formatters/sample.ts +21 -0
  11. package/src/domains/block/index.ts +3 -3
  12. package/src/domains/block/mcp/specs.ts +48 -0
  13. package/src/domains/block/operations/index.ts +2 -1
  14. package/src/domains/block/operations/lookup.ts +9 -0
  15. package/src/domains/block/operations/sample.ts +41 -0
  16. package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
  17. package/src/domains/block/orchestration/resolveBlockLookup.ts +10 -3
  18. package/src/domains/config/commands/framework.test.ts +88 -0
  19. package/src/domains/config/commands/framework.ts +85 -0
  20. package/src/domains/config/commands/index.ts +2 -0
  21. package/src/domains/config/commands/trace.test.ts +148 -0
  22. package/src/domains/config/commands/trace.ts +80 -0
  23. package/src/domains/config/formatters/framework.ts +34 -0
  24. package/src/domains/config/formatters/index.ts +2 -0
  25. package/src/domains/config/formatters/trace.ts +28 -0
  26. package/src/domains/config/index.ts +15 -2
  27. package/src/domains/config/operations/index.ts +1 -0
  28. package/src/domains/config/operations/validateFramework.ts +24 -0
  29. package/src/domains/config/tasks/index.ts +2 -0
  30. package/src/domains/config/tasks/setChannel.ts +7 -1
  31. package/src/domains/config/tasks/setFramework.ts +40 -0
  32. package/src/domains/config/tasks/setTier.ts +7 -1
  33. package/src/domains/config/tasks/setTrace.ts +38 -0
  34. package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
  35. package/src/domains/doctor/operations/checks/checkPackageRefs.ts +35 -41
  36. package/src/domains/doctor/operations/checks.test.ts +4 -1
  37. package/src/domains/doctor/operations/runChecks.test.ts +72 -0
  38. package/src/domains/doctor/operations/runChecks.ts +48 -12
  39. package/src/domains/graphql/commands/build.test.ts +194 -0
  40. package/src/domains/graphql/commands/build.ts +125 -0
  41. package/src/domains/graphql/commands/check.test.ts +139 -0
  42. package/src/domains/graphql/commands/check.ts +87 -0
  43. package/src/domains/graphql/commands/index.ts +4 -0
  44. package/src/domains/graphql/formatters/index.ts +3 -0
  45. package/src/domains/graphql/formatters/report.ts +78 -0
  46. package/src/domains/graphql/helpers/index.ts +4 -0
  47. package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
  48. package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
  49. package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
  50. package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
  51. package/src/domains/graphql/index.ts +21 -0
  52. package/src/domains/graphql/operations/compileSchema.test.ts +119 -0
  53. package/src/domains/graphql/operations/compileSchema.ts +109 -0
  54. package/src/domains/graphql/operations/index.ts +7 -0
  55. package/src/domains/graphql/types.ts +29 -0
  56. package/src/domains/info/operations/collectInfo.test.ts +2 -2
  57. package/src/domains/info/operations/collectInfo.ts +4 -4
  58. package/src/domains/llm/capabilities.test.ts +91 -0
  59. package/src/domains/llm/commands/capabilities.ts +28 -0
  60. package/src/domains/llm/commands/index.ts +1 -0
  61. package/src/domains/llm/data/commandReference.ts +90 -18
  62. package/src/domains/llm/data/decisionTrees.ts +15 -13
  63. package/src/domains/llm/data/index.ts +2 -0
  64. package/src/domains/llm/data/toolCatalog.ts +184 -0
  65. package/src/domains/llm/formatters/capabilities.ts +50 -0
  66. package/src/domains/llm/formatters/index.ts +1 -0
  67. package/src/domains/llm/index.ts +3 -2
  68. package/src/domains/llm/mcp/index.ts +1 -1
  69. package/src/domains/llm/mcp/specs.ts +59 -52
  70. package/src/domains/llm/types.ts +40 -0
  71. package/src/domains/modifier/commands/index.ts +2 -1
  72. package/src/domains/modifier/commands/sample.ts +53 -0
  73. package/src/domains/modifier/formatters/index.ts +2 -1
  74. package/src/domains/modifier/formatters/sample.ts +15 -0
  75. package/src/domains/modifier/index.ts +7 -3
  76. package/src/domains/modifier/mcp/specs.ts +47 -0
  77. package/src/domains/modifier/operations/index.ts +2 -1
  78. package/src/domains/modifier/operations/lookup.ts +9 -0
  79. package/src/domains/modifier/operations/sample.ts +32 -0
  80. package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
  81. package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
  82. package/src/domains/modifier/orchestration/resolveModifierLookup.ts +14 -2
  83. package/src/domains/refs/operations/index.ts +1 -1
  84. package/src/domains/refs/operations/paths.ts +29 -1
  85. package/src/domains/refs/operations/updateRefs.test.ts +6 -3
  86. package/src/domains/refs/operations/updateRefs.ts +3 -2
  87. package/src/domains/shared/bootStore.ts +88 -49
  88. package/src/domains/shared/contracts.ts +1 -0
  89. package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
  90. package/src/domains/shared/loaders/bundledLoader.ts +108 -0
  91. package/src/domains/shared/loaders/gitLoader.ts +31 -0
  92. package/src/domains/shared/loaders/index.ts +6 -0
  93. package/src/domains/shared/loaders/localLoader.ts +73 -0
  94. package/src/domains/shared/loaders/readPackageDir.ts +112 -0
  95. package/src/domains/shared/lookupMany.ts +9 -1
  96. package/src/domains/shared/packages.ts +21 -120
  97. package/src/domains/shared/pickRandom.ts +22 -0
  98. package/src/domains/shared/runtime.ts +6 -3
  99. package/src/domains/shared/sampleFormatters.ts +47 -0
  100. package/src/domains/shared/semanticPackage.ts +96 -0
  101. package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
  102. package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
  103. package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
  104. package/src/domains/shared/suggestions/expandGlob.ts +51 -0
  105. package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
  106. package/src/domains/shared/suggestions/index.ts +7 -0
  107. package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
  108. package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
  109. package/src/domains/shared/suggestions/suggestNames.ts +97 -0
  110. package/src/domains/shared/types/index.ts +1 -0
  111. package/src/domains/shared/types/runtime.ts +4 -1
  112. package/src/domains/shared/types/sample.ts +17 -0
  113. package/src/domains/skill/helpers/resolveSkillSources.ts +49 -16
  114. package/src/domains/standard/commands/index.ts +2 -1
  115. package/src/domains/standard/commands/sample.ts +57 -0
  116. package/src/domains/standard/formatters/index.ts +2 -1
  117. package/src/domains/standard/formatters/sample.ts +15 -0
  118. package/src/domains/standard/index.ts +8 -1
  119. package/src/domains/standard/mcp/specs.ts +48 -1
  120. package/src/domains/standard/operations/index.ts +2 -1
  121. package/src/domains/standard/operations/lookup.ts +9 -0
  122. package/src/domains/standard/operations/sample.ts +35 -0
  123. package/src/domains/standard/orchestration/resolveStandardLookup.ts +14 -2
  124. package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
  125. package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
  126. package/src/domains/token/commands/index.ts +2 -1
  127. package/src/domains/token/commands/sample.ts +53 -0
  128. package/src/domains/token/formatters/index.ts +2 -1
  129. package/src/domains/token/formatters/sample.ts +16 -0
  130. package/src/domains/token/index.ts +8 -1
  131. package/src/domains/token/mcp/specs.ts +47 -1
  132. package/src/domains/token/operations/index.ts +2 -1
  133. package/src/domains/token/operations/lookup.ts +9 -0
  134. package/src/domains/token/operations/sample.ts +32 -0
  135. package/src/domains/token/orchestration/resolveTokenLookup.ts +14 -2
  136. package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
  137. package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
  138. package/src/domains/trace/commands/clear.ts +71 -0
  139. package/src/domains/trace/commands/index.ts +5 -0
  140. package/src/domains/trace/commands/sessions.ts +32 -0
  141. package/src/domains/trace/commands/show.ts +145 -0
  142. package/src/domains/trace/formatters/index.ts +9 -0
  143. package/src/domains/trace/formatters/sessions.ts +80 -0
  144. package/src/domains/trace/formatters/show.ts +122 -0
  145. package/src/domains/trace/index.ts +17 -0
  146. package/src/domains/trace/operations/index.ts +8 -0
  147. package/src/domains/trace/operations/listSessions.test.ts +55 -0
  148. package/src/domains/trace/operations/listSessions.ts +52 -0
  149. package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
  150. package/src/domains/trace/operations/readTraceLog.ts +104 -0
  151. package/src/domains/trace/tracePlugin.test.ts +171 -0
  152. package/src/domains/trace/tracePlugin.ts +159 -0
  153. package/src/domains/trace/types.ts +65 -0
  154. package/src/error/PragmaError.ts +15 -2
  155. package/src/error/types.ts +15 -1
  156. package/src/mcp/error/serializeErrorPayload.ts +7 -0
  157. package/src/mcp/error/types.ts +8 -0
  158. package/src/mcp/registerResources.test.ts +38 -16
  159. package/src/mcp/registerResources.ts +57 -8
  160. package/src/mcp/tools/registerTools.test.ts +22 -7
  161. package/src/mcp/utils/wrapTool.test.ts +1 -0
  162. package/src/pipeline/collectCommands.ts +7 -1
  163. package/src/pipeline/renderError.ts +15 -0
  164. package/src/pipeline/resolveCommandKind.ts +8 -1
  165. package/src/pipeline/runCli.ts +12 -1
  166. package/src/testing/createTestMcpClient.ts +1 -0
  167. package/src/testing/graphqlFixtures.ts +72 -0
  168. package/src/testing/index.ts +7 -0
  169. package/src/testing/integration/mcp-surface.test.ts +11 -5
@@ -0,0 +1,35 @@
1
+ /**
2
+ * Returns a small number of complete standard instances as exemplars.
3
+ *
4
+ * Composes the existing list and lookup operations to avoid duplicating
5
+ * any SPARQL logic. Agents use this to learn the data shape before querying.
6
+ *
7
+ * @param store - ke store to query
8
+ * @param count - number of samples (clamped 1–5, default 2)
9
+ * @returns sample result with detailed standards and total count
10
+ * @note Queries ke store
11
+ */
12
+
13
+ import type { Store } from "@canonical/ke";
14
+ import pickRandom from "../../shared/pickRandom.js";
15
+ import type {
16
+ SampleResult,
17
+ StandardDetailed,
18
+ } from "../../shared/types/index.js";
19
+ import listStandards from "./list.js";
20
+ import lookupStandard from "./lookup.js";
21
+
22
+ export default async function sampleStandards(
23
+ store: Store,
24
+ count = 2,
25
+ ): Promise<SampleResult<StandardDetailed>> {
26
+ const clamped = Math.max(1, Math.min(5, count));
27
+ const all = await listStandards(store);
28
+ const selected = pickRandom(all, clamped);
29
+
30
+ const samples = await Promise.all(
31
+ selected.map((s) => lookupStandard(store, s.name)),
32
+ );
33
+
34
+ return { samples, totalCount: all.length };
35
+ }
@@ -1,6 +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 { expandLookupQueries } from "../../shared/suggestions/index.js";
4
5
  import type { StandardDetailed } from "../../shared/types/index.js";
5
6
  import { lookupStandard } from "../operations/index.js";
6
7
 
@@ -8,11 +9,22 @@ export default async function resolveStandardLookup(
8
9
  store: Pick<PragmaRuntime, "store">["store"],
9
10
  queries: readonly string[],
10
11
  ): Promise<LookupContract<StandardDetailed>> {
11
- const result: LookupResult<StandardDetailed> = await lookupMany(
12
+ const { names, globErrors } = await expandLookupQueries(
12
13
  queries,
13
- (query) => lookupStandard(store, query),
14
+ store,
15
+ "standard",
14
16
  );
15
17
 
18
+ const lookupResult: LookupResult<StandardDetailed> =
19
+ names.length > 0
20
+ ? await lookupMany(names, (query) => lookupStandard(store, query))
21
+ : { results: [], errors: [] };
22
+
23
+ const result: LookupResult<StandardDetailed> = {
24
+ results: lookupResult.results,
25
+ errors: [...lookupResult.errors, ...globErrors],
26
+ };
27
+
16
28
  return {
17
29
  params: { names: queries },
18
30
  result,
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import standardEmptyError from "./standardEmptyError.js";
3
+
4
+ describe("standardEmptyError", () => {
5
+ it("filter-narrowing: a category/search was active → clear filters and re-list", () => {
6
+ const err = standardEmptyError({ category: "react", search: "fold" });
7
+
8
+ expect(err.filters).toEqual({ category: "react", search: "fold" });
9
+ expect(err.recovery).toEqual({
10
+ message: "List all standards without filters.",
11
+ cli: "pragma standard list",
12
+ mcp: { tool: "standard_list" },
13
+ });
14
+ });
15
+
16
+ it("store-empty: no filter active → install, not a self-looping list", () => {
17
+ const err = standardEmptyError({});
18
+
19
+ expect(err.filters).toBeUndefined();
20
+ // Must NOT suggest `pragma standard list` again — that just re-returns empty.
21
+ expect(err.recovery).toEqual({
22
+ message: "Install the code standards packages that provide standards.",
23
+ cli: "bun add -D @canonical/code-standards",
24
+ });
25
+ expect(err.recovery?.mcp).toBeUndefined();
26
+ });
27
+
28
+ it("install branch cites the code-standards package", () => {
29
+ const err = standardEmptyError({});
30
+ expect(err.recovery?.cli).toContain("@canonical/code-standards");
31
+ expect(err.recovery?.cli).not.toContain("ds-global");
32
+ });
33
+ });
@@ -7,13 +7,23 @@ export default function standardEmptyError(
7
7
  const active: Record<string, string> = {};
8
8
  if (filters.category) active.category = filters.category;
9
9
  if (filters.search) active.search = filters.search;
10
+ const hasFilter = Object.keys(active).length > 0;
10
11
 
11
12
  return PragmaError.emptyResults("standard", {
12
- filters: Object.keys(active).length > 0 ? active : undefined,
13
- recovery: {
14
- message: "List all standards without filters.",
15
- cli: "pragma standard list",
16
- mcp: { tool: "standard_list" },
17
- },
13
+ filters: hasFilter ? active : undefined,
14
+ recovery: hasFilter
15
+ ? {
16
+ // Filter-narrowing: clear the active category/search and re-list.
17
+ message: "List all standards without filters.",
18
+ cli: "pragma standard list",
19
+ mcp: { tool: "standard_list" },
20
+ }
21
+ : {
22
+ // Store-empty with no filter active: suggesting `standard list`
23
+ // again would just re-return empty, so route to the install.
24
+ message:
25
+ "Install the code standards packages that provide standards.",
26
+ cli: "bun add -D @canonical/code-standards",
27
+ },
18
28
  });
19
29
  }
@@ -1,5 +1,6 @@
1
- /** @module Re-exports token command builders (`addConfig`, `get`, `list`). */
1
+ /** @module Re-exports token command builders (`addConfig`, `list`, `lookup`, `sample`). */
2
2
 
3
3
  export { default as addConfigCommand } from "./add-config.js";
4
4
  export { default as listCommand } from "./list.js";
5
5
  export { default as lookupCommand } from "./lookup.js";
6
+ export { default as sampleCommand } from "./sample.js";
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Wires the `pragma token sample` CLI command.
3
+ *
4
+ * Returns 1–5 complete token instances as exemplars so agents
5
+ * can learn the data shape before writing queries.
6
+ */
7
+
8
+ import {
9
+ type CommandDefinition,
10
+ createOutputResult,
11
+ } from "@canonical/cli-core";
12
+ import type { PragmaContext } from "../../shared/context.js";
13
+ import { selectFormatter } from "../../shared/formatters.js";
14
+ import type { SampleOutput, TokenDetailed } from "../../shared/types/index.js";
15
+ import { sampleFormatters } from "../formatters/index.js";
16
+ import { sampleTokens } from "../operations/index.js";
17
+
18
+ const NEXT_STEPS = [
19
+ "Use token_lookup to inspect specific tokens by name.",
20
+ "Use token_list to see all available tokens with filtering.",
21
+ ];
22
+
23
+ export default function sampleCommand(ctx: PragmaContext): CommandDefinition {
24
+ return {
25
+ path: ["token", "sample"],
26
+ description:
27
+ "Return randomly selected complete token instances as exemplars for shape discovery",
28
+ parameters: [
29
+ {
30
+ name: "count",
31
+ type: "string",
32
+ description: "Number of samples to return (1–5, default 2)",
33
+ required: false,
34
+ },
35
+ ],
36
+ meta: {
37
+ examples: ["pragma token sample", "pragma token sample --count 3"],
38
+ },
39
+ async execute(params) {
40
+ const count = Number(params.count ?? 2);
41
+ const result = await sampleTokens(ctx.store, count);
42
+
43
+ const output: SampleOutput<TokenDetailed> = {
44
+ ...result,
45
+ nextSteps: NEXT_STEPS,
46
+ };
47
+
48
+ return createOutputResult(output, {
49
+ plain: selectFormatter(ctx, sampleFormatters),
50
+ });
51
+ },
52
+ };
53
+ }
@@ -1,7 +1,8 @@
1
- /** @module Re-exports token formatters (`createLookupFormatters`, `listFormatters`). */
1
+ /** @module Re-exports token formatters (`createLookupFormatters`, `listFormatters`, `sampleFormatters`). */
2
2
 
3
3
  export { default as listFormatters } from "./list.js";
4
4
  export {
5
5
  createInkLookupOptions as createTokenInkLookupOptions,
6
6
  default as createLookupFormatters,
7
7
  } from "./lookup.js";
8
+ export { default as sampleFormatters } from "./sample.js";
@@ -0,0 +1,16 @@
1
+ /**
2
+ * Sample formatter for the token domain.
3
+ *
4
+ * Delegates to the shared factory, rendering each item via the
5
+ * existing token lookup formatter with full detail.
6
+ */
7
+
8
+ import createSampleFormatters from "../../shared/sampleFormatters.js";
9
+ import type { TokenDetailed } from "../../shared/types/index.js";
10
+ import createLookupFormatters from "./lookup.js";
11
+
12
+ const lookupFmt = createLookupFormatters({ detailed: true });
13
+
14
+ export default createSampleFormatters<TokenDetailed>("tokens", (token, mode) =>
15
+ lookupFmt[mode](token),
16
+ );
@@ -12,10 +12,16 @@ import {
12
12
  addConfigCommand,
13
13
  listCommand,
14
14
  lookupCommand,
15
+ sampleCommand,
15
16
  } from "./commands/index.js";
16
17
 
17
18
  export function commands(ctx: PragmaContext): CommandDefinition[] {
18
- return [listCommand(ctx), lookupCommand(ctx), addConfigCommand()];
19
+ return [
20
+ listCommand(ctx),
21
+ lookupCommand(ctx),
22
+ addConfigCommand(),
23
+ sampleCommand(ctx),
24
+ ];
19
25
  }
20
26
 
21
27
  export { specs as mcpSpecs } from "./mcp/index.js";
@@ -24,6 +30,7 @@ export {
24
30
  listTokens,
25
31
  lookupToken,
26
32
  resolveAddConfig,
33
+ sampleTokens,
27
34
  } from "./operations/index.js";
28
35
  export {
29
36
  resolveTokenList,
@@ -11,8 +11,9 @@ import type { ToolSpec } from "../../shared/ToolSpec.js";
11
11
  import {
12
12
  createLookupFormatters as createTokenLookupFmt,
13
13
  listFormatters as tokenListFmt,
14
+ sampleFormatters as tokenSampleFmt,
14
15
  } from "../formatters/index.js";
15
- import { resolveAddConfig } from "../operations/index.js";
16
+ import { resolveAddConfig, sampleTokens } from "../operations/index.js";
16
17
  import {
17
18
  resolveTokenList,
18
19
  resolveTokenLookup,
@@ -140,6 +141,51 @@ const specs: readonly ToolSpec[] = [
140
141
  };
141
142
  },
142
143
  },
144
+ {
145
+ name: "token_sample",
146
+ description:
147
+ "Return 1–5 randomly selected complete token instances as exemplars. Use BEFORE writing queries to see actual data shapes, property names, and value formats. Each call returns different instances.",
148
+ params: {
149
+ count: {
150
+ type: "string",
151
+ description: "Number of samples (1–5, default 2)",
152
+ optional: true,
153
+ },
154
+ condensed: {
155
+ type: "boolean",
156
+ description: "Token-optimized output",
157
+ optional: true,
158
+ },
159
+ },
160
+ readOnly: true,
161
+ async execute(rt, { count, condensed }) {
162
+ const n = Number(count ?? 2);
163
+ const result = await sampleTokens(rt.store, n);
164
+ const nextSteps = [
165
+ `These are ${result.samples.length} of ${result.totalCount} total tokens.`,
166
+ "Use token_lookup to inspect specific tokens by name.",
167
+ "Use token_list with category param to filter by type.",
168
+ ];
169
+
170
+ if (condensed) {
171
+ const text = tokenSampleFmt.llm({ ...result, nextSteps });
172
+ return {
173
+ condensed: true,
174
+ text,
175
+ tokens: `~${Math.ceil(text.length / 4)}`,
176
+ };
177
+ }
178
+
179
+ return {
180
+ data: {
181
+ samples: result.samples,
182
+ totalCount: result.totalCount,
183
+ nextSteps,
184
+ },
185
+ meta: { count: result.samples.length },
186
+ };
187
+ },
188
+ },
143
189
  ] as const;
144
190
 
145
191
  export default specs;
@@ -1,7 +1,8 @@
1
- /** @module Re-exports token operations and types (`resolveAddConfig`, `getToken`, `listTokens`). */
1
+ /** @module Re-exports token operations and types (`resolveAddConfig`, `listTokens`, `lookupToken`, `sampleTokens`). */
2
2
 
3
3
  export type { AddConfigResult } from "./add-config.js";
4
4
  export { default as resolveAddConfig } from "./add-config.js";
5
5
  export type { TokenListFilters } from "./list.js";
6
6
  export { default as listTokens } from "./list.js";
7
7
  export { default as lookupToken } from "./lookup.js";
8
+ export { default as sampleTokens } from "./sample.js";
@@ -15,6 +15,11 @@ import { escapeSparqlValue } from "@canonical/ke";
15
15
  import { PragmaError } from "#error";
16
16
  import { buildQuery } from "../../shared/buildQuery.js";
17
17
  import { P } from "../../shared/prefixes.js";
18
+ import {
19
+ detectCrossDomain,
20
+ listDomainNames,
21
+ suggestNames,
22
+ } from "../../shared/suggestions/index.js";
18
23
  import type { TokenDetailed } from "../../shared/types/index.js";
19
24
 
20
25
  export default async function lookupToken(
@@ -41,12 +46,16 @@ export default async function lookupToken(
41
46
  );
42
47
 
43
48
  if (result.type !== "select" || result.bindings.length === 0) {
49
+ const candidates = await listDomainNames(store, "token");
50
+ const crossDomain = await detectCrossDomain(name, "token", store);
44
51
  throw PragmaError.notFound("token", name, {
52
+ suggestions: suggestNames(name, candidates),
45
53
  recovery: {
46
54
  message: "List available tokens.",
47
55
  cli: "pragma token list",
48
56
  mcp: { tool: "token_list" },
49
57
  },
58
+ crossDomain: crossDomain ?? undefined,
50
59
  });
51
60
  }
52
61
 
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Returns a small number of complete token instances as exemplars.
3
+ *
4
+ * Composes the existing list and lookup operations to avoid duplicating
5
+ * any SPARQL logic. Agents use this to learn the data shape before querying.
6
+ *
7
+ * @param store - ke store to query
8
+ * @param count - number of samples (clamped 1–5, default 2)
9
+ * @returns sample result with detailed tokens and total count
10
+ * @note Queries ke store
11
+ */
12
+
13
+ import type { Store } from "@canonical/ke";
14
+ import pickRandom from "../../shared/pickRandom.js";
15
+ import type { SampleResult, TokenDetailed } from "../../shared/types/index.js";
16
+ import listTokens from "./list.js";
17
+ import lookupToken from "./lookup.js";
18
+
19
+ export default async function sampleTokens(
20
+ store: Store,
21
+ count = 2,
22
+ ): Promise<SampleResult<TokenDetailed>> {
23
+ const clamped = Math.max(1, Math.min(5, count));
24
+ const all = await listTokens(store);
25
+ const selected = pickRandom(all, clamped);
26
+
27
+ const samples = await Promise.all(
28
+ selected.map((t) => lookupToken(store, t.name)),
29
+ );
30
+
31
+ return { samples, totalCount: all.length };
32
+ }
@@ -1,6 +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 { expandLookupQueries } from "../../shared/suggestions/index.js";
4
5
  import type { TokenDetailed } from "../../shared/types/index.js";
5
6
  import { lookupToken } from "../operations/index.js";
6
7
 
@@ -8,11 +9,22 @@ export default async function resolveTokenLookup(
8
9
  store: Pick<PragmaRuntime, "store">["store"],
9
10
  queries: readonly string[],
10
11
  ): Promise<LookupContract<TokenDetailed>> {
11
- const result: LookupResult<TokenDetailed> = await lookupMany(
12
+ const { names, globErrors } = await expandLookupQueries(
12
13
  queries,
13
- (query) => lookupToken(store, query),
14
+ store,
15
+ "token",
14
16
  );
15
17
 
18
+ const lookupResult: LookupResult<TokenDetailed> =
19
+ names.length > 0
20
+ ? await lookupMany(names, (query) => lookupToken(store, query))
21
+ : { results: [], errors: [] };
22
+
23
+ const result: LookupResult<TokenDetailed> = {
24
+ results: lookupResult.results,
25
+ errors: [...lookupResult.errors, ...globErrors],
26
+ };
27
+
16
28
  return {
17
29
  params: { names: queries },
18
30
  result,
@@ -0,0 +1,33 @@
1
+ import { describe, expect, it } from "vitest";
2
+ import tokenEmptyError from "./tokenEmptyError.js";
3
+
4
+ describe("tokenEmptyError", () => {
5
+ it("filter-narrowing: a category was active → clear it and re-list", () => {
6
+ const err = tokenEmptyError("color");
7
+
8
+ expect(err.filters).toEqual({ category: "color" });
9
+ expect(err.recovery).toEqual({
10
+ message: "List all tokens without category filter.",
11
+ cli: "pragma token list",
12
+ mcp: { tool: "token_list" },
13
+ });
14
+ });
15
+
16
+ it("store-empty: no category active → runnable install, no mcp retry", () => {
17
+ const err = tokenEmptyError();
18
+
19
+ expect(err.filters).toBeUndefined();
20
+ expect(err.recovery).toEqual({
21
+ message: "Install the design system packages that provide tokens.",
22
+ cli: "bun add -D @canonical/design-system",
23
+ });
24
+ // No mcp: re-calling token_list on an absent store returns empty again.
25
+ expect(err.recovery?.mcp).toBeUndefined();
26
+ });
27
+
28
+ it("install branch cites a real default package, not @canonical/ds-global", () => {
29
+ const err = tokenEmptyError();
30
+ expect(err.recovery?.cli).toContain("@canonical/design-system");
31
+ expect(err.recovery?.cli).not.toContain("ds-global");
32
+ });
33
+ });
@@ -5,13 +5,16 @@ export default function tokenEmptyError(category?: string): PragmaError {
5
5
  filters: category ? { category } : undefined,
6
6
  recovery: category
7
7
  ? {
8
+ // Filter-narrowing: a category was active — clear it and re-list.
8
9
  message: "List all tokens without category filter.",
9
10
  cli: "pragma token list",
10
11
  mcp: { tool: "token_list" },
11
12
  }
12
13
  : {
13
- message:
14
- "Ensure design system packages are installed: bun add -D @canonical/ds-global",
14
+ // Store-empty: no category was active, so the token-supplying
15
+ // packages are absent. No mcp retry — re-listing returns empty.
16
+ message: "Install the design system packages that provide tokens.",
17
+ cli: "bun add -D @canonical/design-system",
15
18
  },
16
19
  });
17
20
  }
@@ -0,0 +1,71 @@
1
+ /**
2
+ * Wires the `pragma trace clear` CLI command.
3
+ *
4
+ * Deletes all trace log files.
5
+ */
6
+
7
+ import { readdirSync, unlinkSync } from "node:fs";
8
+ import { join } from "node:path";
9
+ import {
10
+ type CommandDefinition,
11
+ createOutputResult,
12
+ } from "@canonical/cli-core";
13
+ import { traceDir } from "../../refs/operations/paths.js";
14
+
15
+ const clearCommand: CommandDefinition = {
16
+ path: ["trace", "clear"],
17
+ description: "Delete all trace log files",
18
+ parameters: [
19
+ {
20
+ name: "yes",
21
+ description: "Skip confirmation",
22
+ type: "boolean",
23
+ default: false,
24
+ },
25
+ ],
26
+ meta: {
27
+ examples: ["pragma trace clear", "pragma trace clear --yes"],
28
+ },
29
+ async execute(params) {
30
+ const dir = traceDir();
31
+
32
+ let entries: string[];
33
+ try {
34
+ entries = readdirSync(dir).filter((e) => e.endsWith(".ndjson"));
35
+ } catch {
36
+ return createOutputResult("No trace directory found.\n", {
37
+ plain: (s) => s,
38
+ });
39
+ }
40
+
41
+ if (entries.length === 0) {
42
+ return createOutputResult("No trace files to delete.\n", {
43
+ plain: (s) => s,
44
+ });
45
+ }
46
+
47
+ if (params.yes !== true) {
48
+ return createOutputResult(
49
+ `Would delete ${entries.length} trace file${entries.length !== 1 ? "s" : ""}. Pass --yes to confirm.\n`,
50
+ { plain: (s) => s },
51
+ );
52
+ }
53
+
54
+ let deleted = 0;
55
+ for (const entry of entries) {
56
+ try {
57
+ unlinkSync(join(dir, entry));
58
+ deleted++;
59
+ } catch {
60
+ // Skip files we can't delete
61
+ }
62
+ }
63
+
64
+ return createOutputResult(
65
+ `Deleted ${deleted} trace file${deleted !== 1 ? "s" : ""}.\n`,
66
+ { plain: (s) => s },
67
+ );
68
+ },
69
+ };
70
+
71
+ export default clearCommand;
@@ -0,0 +1,5 @@
1
+ /** @module Re-exports trace command builders. */
2
+
3
+ export { default as clearCommand } from "./clear.js";
4
+ export { default as sessionsCommand } from "./sessions.js";
5
+ export { default as showCommand } from "./show.js";
@@ -0,0 +1,32 @@
1
+ /**
2
+ * Wires the `pragma trace sessions` CLI command.
3
+ *
4
+ * Lists all available trace sessions with metadata.
5
+ */
6
+
7
+ import {
8
+ type CommandDefinition,
9
+ createOutputResult,
10
+ } from "@canonical/cli-core";
11
+ import { traceDir } from "../../refs/operations/paths.js";
12
+ import { selectFormatter } from "../../shared/formatters.js";
13
+ import { sessionsFormatters } from "../formatters/index.js";
14
+ import { listSessions } from "../operations/index.js";
15
+
16
+ const sessionsCommand: CommandDefinition = {
17
+ path: ["trace", "sessions"],
18
+ description: "List available trace sessions",
19
+ parameters: [],
20
+ meta: {
21
+ examples: ["pragma trace sessions"],
22
+ },
23
+ async execute(_params, ctx) {
24
+ const sessions = listSessions(traceDir());
25
+
26
+ return createOutputResult(sessions, {
27
+ plain: selectFormatter(ctx, sessionsFormatters),
28
+ });
29
+ },
30
+ };
31
+
32
+ export default sessionsCommand;