@canonical/pragma-cli 0.30.0 → 0.31.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.
- package/README.md +64 -35
- package/dist/pragma +0 -0
- package/package.json +15 -13
- package/src/completions/constants.ts +7 -0
- package/src/completions/handleQuery.test.ts +32 -0
- package/src/completions/handleQuery.ts +20 -1
- package/src/completions/queryCompletions.test.ts +5 -0
- package/src/completions/queryCompletions.ts +17 -3
- package/src/completions/querySocket.test.ts +31 -0
- package/src/completions/querySocket.ts +36 -9
- package/src/config/parseConfigValues.ts +2 -0
- package/src/config/readConfigLayers.test.ts +2 -0
- package/src/config/readConfigLayers.ts +3 -0
- package/src/config/types.ts +7 -0
- package/src/constants.ts +3 -1
- package/src/domains/block/commands/sample.ts +2 -1
- package/src/domains/block/formatters/list.test.ts +3 -3
- package/src/domains/block/formatters/lookup.test.ts +5 -5
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
- package/src/domains/block/stories.ts +9 -3
- package/src/domains/config/commands/channel.test.ts +16 -0
- package/src/domains/config/commands/channel.ts +14 -1
- package/src/domains/config/commands/framework.test.ts +14 -0
- package/src/domains/config/commands/framework.ts +13 -1
- package/src/domains/config/commands/tier.test.ts +7 -0
- package/src/domains/config/commands/tier.ts +14 -1
- package/src/domains/config/operations/validateTier.test.ts +9 -0
- package/src/domains/config/operations/validateTier.ts +10 -2
- package/src/domains/create/generatorPack/bundled.ts +49 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
- package/src/domains/create/index.ts +13 -7
- package/src/domains/create/mcp/specs.ts +6 -161
- package/src/domains/create/renderGeneratorUi.test.ts +103 -0
- package/src/domains/create/renderGeneratorUi.ts +65 -0
- package/src/domains/doctor/formatters/doctor.test.ts +50 -2
- package/src/domains/doctor/formatters/doctor.ts +90 -38
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +9 -6
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -12
- package/src/domains/doctor/operations/checks.test.ts +6 -4
- package/src/domains/doctor/operations/types.ts +23 -1
- package/src/domains/graph/commands/inspect.test.ts +2 -2
- package/src/domains/graph/commands/query.test.ts +5 -3
- package/src/domains/graph/formatters/inspect.ts +7 -7
- package/src/domains/graph/formatters/query.test.ts +87 -0
- package/src/domains/graph/formatters/query.ts +42 -5
- package/src/domains/graph/operations/inspectUri.test.ts +5 -5
- package/src/domains/graphql/commands/serve.test.ts +12 -6
- package/src/domains/graphql/commands/serve.ts +16 -6
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
- package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
- package/src/domains/graphql/operations/compileSchema.test.ts +2 -2
- package/src/domains/graphql/operations/createSchemaServer.test.ts +2 -2
- package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
- package/src/domains/graphql/operations/index.ts +5 -0
- package/src/domains/info/formatters/upgrade.test.ts +7 -4
- package/src/domains/info/formatters/upgrade.ts +3 -1
- package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
- package/src/domains/info/operations/collectEntityCounts.ts +123 -0
- package/src/domains/info/operations/index.ts +1 -0
- package/src/domains/info/types.ts +13 -0
- package/src/domains/llm/capabilities.test.ts +23 -3
- package/src/domains/llm/commands/capabilities.ts +12 -3
- package/src/domains/llm/commands/llm.ts +5 -1
- package/src/domains/llm/data/commandReference.ts +20 -11
- package/src/domains/llm/data/decisionTrees.ts +10 -5
- package/src/domains/llm/data/toolCatalog.ts +49 -18
- package/src/domains/llm/formatters/orientation.ts +5 -1
- package/src/domains/llm/llmCommand.test.ts +11 -3
- package/src/domains/llm/mcp/specs.ts +3 -4
- package/src/domains/llm/operations/collectContext.test.ts +64 -0
- package/src/domains/llm/operations/collectContext.ts +7 -17
- package/src/domains/modifier/commands/sample.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +5 -3
- package/src/domains/modifier/stories.ts +9 -0
- package/src/domains/ontology/commands/list.test.ts +5 -3
- package/src/domains/ontology/commands/show.test.ts +85 -9
- package/src/domains/ontology/formatters/index.ts +1 -0
- package/src/domains/ontology/formatters/list.ts +37 -12
- package/src/domains/ontology/formatters/show.ts +358 -67
- package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
- package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
- package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
- package/src/domains/ontology/helpers/index.ts +14 -0
- package/src/domains/ontology/helpers/queryClasses.ts +32 -16
- package/src/domains/ontology/helpers/queryProperties.ts +39 -6
- package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
- package/src/domains/ontology/operations/listOntologies.ts +126 -30
- package/src/domains/ontology/operations/showOntology.test.ts +131 -12
- package/src/domains/ontology/operations/showOntology.ts +289 -7
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
- package/src/domains/ontology/stories.ts +53 -8
- package/src/domains/refs/operations/updateRefs.ts +0 -1
- package/src/domains/setup/commands/index.ts +1 -0
- package/src/domains/setup/commands/setup.ts +70 -0
- package/src/domains/setup/index.ts +7 -3
- package/src/domains/setup/operations/setupLsp.test.ts +3 -2
- package/src/domains/setup/operations/setupLsp.ts +7 -2
- package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
- package/src/domains/shared/bootStore.test.ts +82 -0
- package/src/domains/shared/bootStore.ts +56 -20
- package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
- package/src/domains/shared/extractLocalName.test.ts +3 -3
- package/src/domains/shared/formatters.ts +23 -2
- package/src/domains/shared/listTiers.ts +40 -0
- package/src/domains/shared/loaders/bundledLoader.ts +48 -0
- package/src/domains/shared/loaders/gitLoader.ts +1 -2
- package/src/domains/shared/loaders/localLoader.ts +2 -18
- package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
- package/src/domains/shared/loaders/readPackageDir.ts +46 -8
- package/src/domains/shared/lookupMany.test.ts +15 -0
- package/src/domains/shared/lookupMany.ts +22 -6
- package/src/domains/shared/namespaces.ts +13 -5
- package/src/domains/shared/parseSampleCount.test.ts +47 -0
- package/src/domains/shared/parseSampleCount.ts +45 -0
- package/src/domains/shared/prefixes.test.ts +211 -0
- package/src/domains/shared/prefixes.ts +182 -15
- package/src/domains/shared/renderers.ts +5 -5
- package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
- package/src/domains/shared/resolveLookupExitCode.ts +42 -0
- package/src/domains/shared/semanticPackage.test.ts +89 -0
- package/src/domains/shared/semanticPackage.ts +17 -2
- package/src/domains/shared/stories/compileLookupCommand.ts +3 -1
- package/src/domains/shared/stories/compileLookupTool.ts +1 -1
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +49 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +46 -11
- package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
- package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +30 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +75 -1
- package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
- package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
- package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +305 -7
- package/src/domains/shared/stories/pack/collectPackStories.ts +90 -8
- package/src/domains/shared/stories/pack/compilePackCommands.ts +17 -7
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +607 -2
- package/src/domains/shared/stories/pack/compilePackStories.ts +544 -73
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +20 -8
- package/src/domains/shared/stories/pack/index.ts +14 -0
- package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
- package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +293 -220
- package/src/domains/shared/stories/pack/types.ts +123 -3
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +502 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +475 -24
- package/src/domains/shared/stories/types.ts +9 -1
- package/src/domains/shared/types/index.ts +5 -9
- package/src/domains/shared/types/ontology.ts +119 -24
- package/src/domains/shared/types/standard.ts +9 -52
- package/src/domains/token/commands/lookup.test.ts +8 -0
- package/src/domains/token/commands/lookup.ts +12 -0
- package/src/domains/token/commands/sample.ts +2 -1
- package/src/domains/token/featureFlag.ts +17 -0
- package/src/domains/token/index.ts +5 -0
- package/src/domains/token/mcp/specs.ts +13 -1
- package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
- package/src/domains/trace/commands/show.ts +30 -4
- package/src/index.ts +1 -13
- package/src/mcp/registerResources.test.ts +2 -2
- package/src/mcp/resources/pickPrimaryType.test.ts +5 -5
- package/src/mcp/tools/index.ts +18 -10
- package/src/mcp/tools/registerTools.test.ts +106 -30
- package/src/pipeline/collectCommands.test.ts +151 -1
- package/src/pipeline/collectCommands.ts +34 -19
- package/src/pipeline/createProgram.test.ts +5 -1
- package/src/pipeline/createProgram.ts +2 -2
- package/src/pipeline/firstRun.test.ts +80 -0
- package/src/pipeline/firstRun.ts +99 -0
- package/src/pipeline/parseGlobalFlags.test.ts +102 -1
- package/src/pipeline/parseGlobalFlags.ts +98 -9
- package/src/pipeline/resolveCommandKind.test.ts +12 -0
- package/src/pipeline/resolveCommandKind.ts +4 -0
- package/src/pipeline/rootHelp.test.ts +87 -0
- package/src/pipeline/rootHelp.ts +265 -0
- package/src/pipeline/runCli.orchestration.test.ts +28 -12
- package/src/pipeline/runCli.test.ts +17 -4
- package/src/pipeline/runCli.ts +162 -19
- package/src/testing/cli.ts +30 -2
- package/src/testing/dsFixtures.ts +21 -4
- package/src/testing/index.ts +1 -4
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +4 -4
- package/src/testing/integration/cli-user-stories.test.ts +63 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +2 -2
- package/src/testing/integration/edge-cases.test.ts +62 -50
- package/src/testing/integration/executor-execution.test.ts +6 -4
- package/src/testing/integration/executor-golden.test.ts +2 -2
- package/src/testing/integration/mcp-surface.test.ts +40 -15
- package/src/testing/integration/parity.test.ts +119 -70
- package/src/testing/setupXdgIsolation.ts +16 -5
- package/src/testing/standardParityFixtures.ts +26 -83
- package/src/testing/store.ts +4 -4
- package/src/domains/create/commands/component.test.ts +0 -166
- package/src/domains/create/commands/component.ts +0 -123
- package/src/domains/create/commands/index.ts +0 -3
- package/src/domains/create/commands/package.test.ts +0 -35
- package/src/domains/create/commands/package.ts +0 -21
- package/src/domains/create/generators.ts +0 -8
- package/src/domains/standard/commands/categories.test.ts +0 -70
- package/src/domains/standard/commands/categories.ts +0 -48
- package/src/domains/standard/commands/index.ts +0 -6
- package/src/domains/standard/commands/list.test.ts +0 -105
- package/src/domains/standard/commands/list.ts +0 -11
- package/src/domains/standard/commands/lookup.test.ts +0 -135
- package/src/domains/standard/commands/lookup.ts +0 -14
- package/src/domains/standard/commands/sample.ts +0 -57
- package/src/domains/standard/formatters/categories.test.ts +0 -44
- package/src/domains/standard/formatters/categories.ts +0 -38
- package/src/domains/standard/formatters/index.ts +0 -10
- package/src/domains/standard/formatters/list.test.ts +0 -169
- package/src/domains/standard/formatters/list.ts +0 -184
- package/src/domains/standard/formatters/lookup.test.ts +0 -95
- package/src/domains/standard/formatters/lookup.ts +0 -101
- package/src/domains/standard/formatters/sample.ts +0 -15
- package/src/domains/standard/formatters/types.ts +0 -31
- package/src/domains/standard/index.ts +0 -40
- package/src/domains/standard/mcp/index.ts +0 -2
- package/src/domains/standard/mcp/specs.ts +0 -89
- package/src/domains/standard/operations/categories.test.ts +0 -61
- package/src/domains/standard/operations/categories.ts +0 -38
- package/src/domains/standard/operations/index.ts +0 -6
- package/src/domains/standard/operations/list.test.ts +0 -75
- package/src/domains/standard/operations/list.ts +0 -64
- package/src/domains/standard/operations/lookup.test.ts +0 -58
- package/src/domains/standard/operations/lookup.ts +0 -151
- package/src/domains/standard/operations/sample.ts +0 -35
- package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
- package/src/domains/standard/orchestration/index.ts +0 -4
- package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
- package/src/domains/standard/standardConfig.ts +0 -42
- package/src/domains/standard/stories.test.ts +0 -24
- package/src/domains/standard/stories.ts +0 -151
- package/src/domains/standard/types.ts +0 -15
- package/src/domains/tier/commands/index.ts +0 -3
- package/src/domains/tier/commands/list.test.ts +0 -67
- package/src/domains/tier/commands/list.ts +0 -9
- package/src/domains/tier/formatters/index.ts +0 -3
- package/src/domains/tier/formatters/list.test.ts +0 -33
- package/src/domains/tier/formatters/list.ts +0 -45
- package/src/domains/tier/index.ts +0 -17
- package/src/domains/tier/mcp/index.ts +0 -2
- package/src/domains/tier/mcp/specs.ts +0 -12
- package/src/domains/tier/operations/index.ts +0 -3
- package/src/domains/tier/operations/list.test.ts +0 -32
- package/src/domains/tier/operations/list.ts +0 -33
- package/src/domains/tier/stories.test.ts +0 -22
- package/src/domains/tier/stories.ts +0 -52
|
@@ -48,6 +48,26 @@ const mixed: DoctorData = {
|
|
|
48
48
|
skipped: 1,
|
|
49
49
|
};
|
|
50
50
|
|
|
51
|
+
const withItems: DoctorData = {
|
|
52
|
+
checks: [
|
|
53
|
+
{
|
|
54
|
+
name: "package refs",
|
|
55
|
+
status: "pass",
|
|
56
|
+
detail: "1 package resolved",
|
|
57
|
+
items: [
|
|
58
|
+
{
|
|
59
|
+
label: "@canonical/design-system",
|
|
60
|
+
status: "pass",
|
|
61
|
+
detail: "git v0.1.2 · 362 graphs, 3 skills",
|
|
62
|
+
},
|
|
63
|
+
],
|
|
64
|
+
},
|
|
65
|
+
],
|
|
66
|
+
passed: 1,
|
|
67
|
+
failed: 0,
|
|
68
|
+
skipped: 0,
|
|
69
|
+
};
|
|
70
|
+
|
|
51
71
|
describe("doctor formatters", () => {
|
|
52
72
|
describe("plain", () => {
|
|
53
73
|
it("renders passing checks with ✓", () => {
|
|
@@ -74,6 +94,29 @@ describe("doctor formatters", () => {
|
|
|
74
94
|
const output = formatters.plain(allPass);
|
|
75
95
|
expect(output).not.toContain("Run ");
|
|
76
96
|
});
|
|
97
|
+
|
|
98
|
+
it("renders a summary tally", () => {
|
|
99
|
+
const output = formatters.plain(mixed);
|
|
100
|
+
expect(output).toContain("1 passed");
|
|
101
|
+
expect(output).toContain("2 failed");
|
|
102
|
+
expect(output).toContain("1 skipped");
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it("renders sub-items indented under a check", () => {
|
|
106
|
+
const output = formatters.plain(withItems);
|
|
107
|
+
expect(output).toContain("@canonical/design-system");
|
|
108
|
+
expect(output).toContain("git v0.1.2 · 362 graphs, 3 skills");
|
|
109
|
+
// sub-item lines are indented beneath the check headline
|
|
110
|
+
const lines = output.split("\n");
|
|
111
|
+
const sub = lines.find((l) => l.includes("@canonical/design-system"));
|
|
112
|
+
expect(sub?.startsWith(" ")).toBe(true);
|
|
113
|
+
});
|
|
114
|
+
|
|
115
|
+
it("renders the remedy inline with a fix label", () => {
|
|
116
|
+
const output = formatters.plain(mixed);
|
|
117
|
+
expect(output).toContain("fix:");
|
|
118
|
+
expect(output).toContain("pragma config tier <tier>");
|
|
119
|
+
});
|
|
77
120
|
});
|
|
78
121
|
|
|
79
122
|
describe("llm", () => {
|
|
@@ -87,12 +130,17 @@ describe("doctor formatters", () => {
|
|
|
87
130
|
expect(output).toContain("- ✓ **Node version**: v24.1.0");
|
|
88
131
|
});
|
|
89
132
|
|
|
90
|
-
it("includes
|
|
133
|
+
it("includes inline fixes for failures", () => {
|
|
91
134
|
const output = formatters.llm(mixed);
|
|
92
|
-
expect(output).toContain("
|
|
135
|
+
expect(output).toContain("_fix:_");
|
|
93
136
|
expect(output).toContain("`pragma config tier <tier>`");
|
|
94
137
|
expect(output).toContain("`pragma setup completions`");
|
|
95
138
|
});
|
|
139
|
+
|
|
140
|
+
it("renders sub-items as nested bullets", () => {
|
|
141
|
+
const output = formatters.llm(withItems);
|
|
142
|
+
expect(output).toContain(" - ✓ @canonical/design-system");
|
|
143
|
+
});
|
|
96
144
|
});
|
|
97
145
|
|
|
98
146
|
describe("json", () => {
|
|
@@ -1,78 +1,130 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
2
|
import type { Formatters } from "../../shared/formatters.js";
|
|
3
|
-
import type {
|
|
3
|
+
import type {
|
|
4
|
+
CheckResult,
|
|
5
|
+
CheckStatus,
|
|
6
|
+
DoctorData,
|
|
7
|
+
} from "../operations/types.js";
|
|
4
8
|
|
|
5
|
-
/** Status icons for pass/fail/skip
|
|
6
|
-
const STATUS_ICONS = {
|
|
9
|
+
/** Status icons for pass/fail/skip results (checks and sub-items). */
|
|
10
|
+
const STATUS_ICONS: Record<CheckStatus, string> = {
|
|
7
11
|
pass: chalk.green("✓"),
|
|
8
12
|
fail: chalk.red("✗"),
|
|
9
13
|
skip: chalk.yellow("○"),
|
|
10
|
-
}
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
const SUB_BULLET = chalk.dim("·");
|
|
17
|
+
const FIX_ARROW = chalk.cyan("↳");
|
|
18
|
+
const INDENT = " "; // aligns sub-lines under the check name
|
|
11
19
|
|
|
12
20
|
/**
|
|
13
|
-
*
|
|
21
|
+
* Render one check as terminal lines: a headline row (icon, aligned name,
|
|
22
|
+
* detail), an optional indented breakdown of sub-items, and — for failures —
|
|
23
|
+
* an inline remedial instruction.
|
|
14
24
|
*
|
|
15
|
-
* @param check - The check result to
|
|
16
|
-
* @
|
|
25
|
+
* @param check - The check result to render.
|
|
26
|
+
* @param nameWidth - Width to pad check names to so details align in a column.
|
|
27
|
+
* @returns The lines for this check.
|
|
17
28
|
*/
|
|
18
|
-
function formatCheckPlain(check: CheckResult): string {
|
|
19
|
-
|
|
29
|
+
function formatCheckPlain(check: CheckResult, nameWidth: number): string[] {
|
|
30
|
+
const name = check.name.padEnd(nameWidth);
|
|
31
|
+
const label = check.status === "fail" ? chalk.red(name) : chalk.bold(name);
|
|
32
|
+
const lines = [
|
|
33
|
+
` ${STATUS_ICONS[check.status]} ${label} ${chalk.dim(check.detail)}`,
|
|
34
|
+
];
|
|
35
|
+
|
|
36
|
+
if (check.items && check.items.length > 0) {
|
|
37
|
+
const itemWidth = Math.max(...check.items.map((i) => i.label.length));
|
|
38
|
+
const anyStatus = check.items.some((i) => i.status !== undefined);
|
|
39
|
+
for (const item of check.items) {
|
|
40
|
+
const icon = anyStatus
|
|
41
|
+
? item.status
|
|
42
|
+
? `${STATUS_ICONS[item.status]} `
|
|
43
|
+
: " "
|
|
44
|
+
: "";
|
|
45
|
+
const itemLabel = item.detail ? item.label.padEnd(itemWidth) : item.label;
|
|
46
|
+
const detail = item.detail ? ` ${chalk.dim(item.detail)}` : "";
|
|
47
|
+
lines.push(`${INDENT}${SUB_BULLET} ${icon}${itemLabel}${detail}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (check.status === "fail" && check.remedy) {
|
|
52
|
+
lines.push(`${INDENT}${FIX_ARROW} ${chalk.cyan("fix:")} ${check.remedy}`);
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return lines;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Render the pass/fail/skip tally, coloring non-zero fail/skip. */
|
|
59
|
+
function formatSummary(data: DoctorData): string {
|
|
60
|
+
const parts = [chalk.green(`${data.passed} passed`)];
|
|
61
|
+
parts.push(
|
|
62
|
+
data.failed > 0
|
|
63
|
+
? chalk.red(`${data.failed} failed`)
|
|
64
|
+
: chalk.dim(`${data.failed} failed`),
|
|
65
|
+
);
|
|
66
|
+
parts.push(
|
|
67
|
+
data.skipped > 0
|
|
68
|
+
? chalk.yellow(`${data.skipped} skipped`)
|
|
69
|
+
: chalk.dim(`${data.skipped} skipped`),
|
|
70
|
+
);
|
|
71
|
+
return ` ${parts.join(chalk.dim(" · "))}`;
|
|
20
72
|
}
|
|
21
73
|
|
|
22
74
|
/**
|
|
23
75
|
* Formatters for `pragma doctor` output.
|
|
24
76
|
*
|
|
25
|
-
* - **plain** renders
|
|
26
|
-
*
|
|
27
|
-
* - **
|
|
77
|
+
* - **plain** renders an aligned check list with indented sub-items and
|
|
78
|
+
* inline remedies, followed by a summary tally.
|
|
79
|
+
* - **llm** renders a Markdown list with nested sub-items and inline fixes.
|
|
80
|
+
* - **json** serializes the raw DoctorData (including `items`).
|
|
28
81
|
*/
|
|
29
82
|
const formatters: Formatters<DoctorData> = {
|
|
30
83
|
plain(data) {
|
|
31
|
-
const
|
|
84
|
+
const nameWidth = Math.max(...data.checks.map((c) => c.name.length), 0);
|
|
85
|
+
const lines: string[] = [chalk.bold("pragma doctor"), ""];
|
|
32
86
|
|
|
33
87
|
for (const check of data.checks) {
|
|
34
|
-
lines.push(formatCheckPlain(check));
|
|
88
|
+
lines.push(...formatCheckPlain(check, nameWidth));
|
|
35
89
|
}
|
|
36
90
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
c.status === "fail" && c.remedy !== undefined,
|
|
40
|
-
);
|
|
41
|
-
|
|
42
|
-
if (remedies.length > 0) {
|
|
43
|
-
lines.push("");
|
|
44
|
-
for (const r of remedies) {
|
|
45
|
-
lines.push(
|
|
46
|
-
`Run ${chalk.cyan(`\`${r.remedy}\``)} to fix ${chalk.dim(r.name)}.`,
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
}
|
|
91
|
+
lines.push("");
|
|
92
|
+
lines.push(formatSummary(data));
|
|
50
93
|
|
|
51
94
|
return lines.join("\n");
|
|
52
95
|
},
|
|
53
96
|
|
|
54
97
|
llm(data) {
|
|
55
|
-
const lines: string[] = [];
|
|
56
|
-
|
|
57
|
-
lines.push("## Doctor");
|
|
58
|
-
lines.push("");
|
|
98
|
+
const lines: string[] = ["## Doctor", ""];
|
|
59
99
|
|
|
60
100
|
for (const check of data.checks) {
|
|
61
101
|
const icon =
|
|
62
102
|
check.status === "pass" ? "✓" : check.status === "fail" ? "✗" : "○";
|
|
63
103
|
lines.push(`- ${icon} **${check.name}**: ${check.detail}`);
|
|
64
|
-
}
|
|
65
104
|
|
|
66
|
-
|
|
105
|
+
for (const item of check.items ?? []) {
|
|
106
|
+
const itemIcon =
|
|
107
|
+
item.status === "pass"
|
|
108
|
+
? "✓ "
|
|
109
|
+
: item.status === "fail"
|
|
110
|
+
? "✗ "
|
|
111
|
+
: item.status === "skip"
|
|
112
|
+
? "○ "
|
|
113
|
+
: "";
|
|
114
|
+
const detail = item.detail ? `: ${item.detail}` : "";
|
|
115
|
+
lines.push(` - ${itemIcon}${item.label}${detail}`);
|
|
116
|
+
}
|
|
67
117
|
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
lines.push("### Remedial");
|
|
71
|
-
for (const r of remedies) {
|
|
72
|
-
lines.push(`- \`${r.remedy}\``);
|
|
118
|
+
if (check.status === "fail" && check.remedy) {
|
|
119
|
+
lines.push(` - _fix:_ \`${check.remedy}\``);
|
|
73
120
|
}
|
|
74
121
|
}
|
|
75
122
|
|
|
123
|
+
lines.push("");
|
|
124
|
+
lines.push(
|
|
125
|
+
`_${data.passed} passed, ${data.failed} failed, ${data.skipped} skipped_`,
|
|
126
|
+
);
|
|
127
|
+
|
|
76
128
|
return lines.join("\n");
|
|
77
129
|
},
|
|
78
130
|
|
|
@@ -3,7 +3,7 @@ import { delimiter, isAbsolute, join } from "node:path";
|
|
|
3
3
|
import type { DetectedHarness } from "@canonical/harnesses";
|
|
4
4
|
import { detectHarnesses, readMcpConfig } from "@canonical/harnesses";
|
|
5
5
|
import { runTask } from "@canonical/task/node";
|
|
6
|
-
import type { CheckContext, CheckResult } from "../types.js";
|
|
6
|
+
import type { CheckContext, CheckItem, CheckResult } from "../types.js";
|
|
7
7
|
|
|
8
8
|
const NAME = "MCP commands";
|
|
9
9
|
|
|
@@ -79,7 +79,7 @@ export default async function checkMcpCommands(
|
|
|
79
79
|
};
|
|
80
80
|
}
|
|
81
81
|
|
|
82
|
-
const broken:
|
|
82
|
+
const broken: CheckItem[] = [];
|
|
83
83
|
let checked = 0;
|
|
84
84
|
|
|
85
85
|
for (const d of withConfig) {
|
|
@@ -97,9 +97,11 @@ export default async function checkMcpCommands(
|
|
|
97
97
|
|
|
98
98
|
checked += 1;
|
|
99
99
|
if (!commandResolves(command, ctx.cwd)) {
|
|
100
|
-
broken.push(
|
|
101
|
-
`"${serverName}"
|
|
102
|
-
|
|
100
|
+
broken.push({
|
|
101
|
+
label: `"${serverName}"`,
|
|
102
|
+
status: "fail",
|
|
103
|
+
detail: `"${command}" not found · ${d.configPath}`,
|
|
104
|
+
});
|
|
103
105
|
}
|
|
104
106
|
}
|
|
105
107
|
}
|
|
@@ -123,7 +125,8 @@ export default async function checkMcpCommands(
|
|
|
123
125
|
return {
|
|
124
126
|
name: NAME,
|
|
125
127
|
status: "fail",
|
|
126
|
-
detail:
|
|
128
|
+
detail: `${broken.length} of ${checked} unresolvable`,
|
|
129
|
+
items: broken,
|
|
127
130
|
remedy:
|
|
128
131
|
"Install the missing command or remove the entry from the MCP config — every agent session tries and fails to boot it.",
|
|
129
132
|
};
|
|
@@ -13,7 +13,12 @@ import {
|
|
|
13
13
|
} from "../../../shared/loaders/index.js";
|
|
14
14
|
import { mergeAndParseRefs } from "../../../shared/mergeAndParseRefs.js";
|
|
15
15
|
import { resolveSemanticPackages } from "../../../shared/semanticPackage.js";
|
|
16
|
-
import type { CheckContext, CheckResult } from "../types.js";
|
|
16
|
+
import type { CheckContext, CheckItem, CheckResult } from "../types.js";
|
|
17
|
+
|
|
18
|
+
/** Pluralize a count, e.g. `1 graph` / `2 graphs`. */
|
|
19
|
+
function count(n: number, unit: string): string {
|
|
20
|
+
return `${n} ${unit}${n === 1 ? "" : "s"}`;
|
|
21
|
+
}
|
|
17
22
|
|
|
18
23
|
export default async function checkPackageRefs(
|
|
19
24
|
ctx: CheckContext,
|
|
@@ -27,32 +32,45 @@ export default async function checkPackageRefs(
|
|
|
27
32
|
];
|
|
28
33
|
const packages = await resolveSemanticPackages(refs, loaders);
|
|
29
34
|
|
|
30
|
-
const
|
|
31
|
-
let
|
|
35
|
+
const items: CheckItem[] = [];
|
|
36
|
+
let unresolved = 0;
|
|
32
37
|
|
|
33
38
|
for (const ref of refs) {
|
|
34
39
|
const resolved = packages.find((p) => p.name === ref.pkg);
|
|
35
40
|
|
|
36
41
|
if (resolved) {
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
42
|
+
items.push({
|
|
43
|
+
label: ref.pkg,
|
|
44
|
+
status: "pass",
|
|
45
|
+
detail: `${resolved.source} v${resolved.version} · ${count(resolved.graphs.length, "graph")}, ${count(resolved.skills.length, "skill")}`,
|
|
46
|
+
});
|
|
40
47
|
} else {
|
|
41
48
|
// Check if covered by bundled fallback
|
|
42
49
|
const bundled = packages.find((p) => p.name === "(bundled)");
|
|
43
50
|
if (bundled) {
|
|
44
|
-
|
|
51
|
+
items.push({
|
|
52
|
+
label: ref.pkg,
|
|
53
|
+
status: "pass",
|
|
54
|
+
detail: `bundled fallback v${bundled.version}`,
|
|
55
|
+
});
|
|
45
56
|
} else {
|
|
46
|
-
|
|
47
|
-
|
|
57
|
+
items.push({ label: ref.pkg, status: "fail", detail: "not resolved" });
|
|
58
|
+
unresolved += 1;
|
|
48
59
|
}
|
|
49
60
|
}
|
|
50
61
|
}
|
|
51
62
|
|
|
63
|
+
const total = refs.length;
|
|
64
|
+
const detail =
|
|
65
|
+
unresolved > 0
|
|
66
|
+
? `${unresolved} of ${count(total, "package")} not resolved`
|
|
67
|
+
: `${count(total, "package")} resolved`;
|
|
68
|
+
|
|
52
69
|
return {
|
|
53
70
|
name: "package refs",
|
|
54
|
-
status:
|
|
55
|
-
detail
|
|
56
|
-
|
|
71
|
+
status: unresolved > 0 ? "fail" : "pass",
|
|
72
|
+
detail,
|
|
73
|
+
items,
|
|
74
|
+
...(unresolved > 0 ? { remedy: "pragma update-refs" } : {}),
|
|
57
75
|
};
|
|
58
76
|
}
|
|
@@ -268,8 +268,9 @@ describe("checkMcpCommands", () => {
|
|
|
268
268
|
|
|
269
269
|
const result = await checkMcpCommands(ctx);
|
|
270
270
|
expect(result.status).toBe("fail");
|
|
271
|
-
|
|
272
|
-
expect(
|
|
271
|
+
const semItem = result.items?.find((i) => i.label === '"sem"');
|
|
272
|
+
expect(semItem).toBeDefined();
|
|
273
|
+
expect(semItem?.detail).toContain("/test/.mcp.json");
|
|
273
274
|
expect(result.remedy).toBeDefined();
|
|
274
275
|
});
|
|
275
276
|
|
|
@@ -291,8 +292,9 @@ describe("checkMcpCommands", () => {
|
|
|
291
292
|
|
|
292
293
|
const result = await checkMcpCommands(ctx);
|
|
293
294
|
expect(result.status).toBe("fail");
|
|
294
|
-
|
|
295
|
-
expect(
|
|
295
|
+
const labels = (result.items ?? []).map((i) => i.label);
|
|
296
|
+
expect(labels).toContain('"sem"');
|
|
297
|
+
expect(labels).not.toContain('"pragma"');
|
|
296
298
|
});
|
|
297
299
|
});
|
|
298
300
|
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
+
/** Status of a doctor check or one of its sub-items. */
|
|
2
|
+
export type CheckStatus = "pass" | "fail" | "skip";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A structured sub-item under a check — e.g. one resolved package under
|
|
6
|
+
* `package refs`, or one unresolvable server under `MCP commands`. Lets the
|
|
7
|
+
* formatter render an indented, aligned breakdown instead of cramming
|
|
8
|
+
* everything into a single `detail` string.
|
|
9
|
+
*/
|
|
10
|
+
export interface CheckItem {
|
|
11
|
+
/** Left-column label (e.g. the package or server name). */
|
|
12
|
+
readonly label: string;
|
|
13
|
+
/** Right-column detail (e.g. `git v0.1.2 · 362 graphs`). */
|
|
14
|
+
readonly detail?: string;
|
|
15
|
+
/** Optional per-item status, rendered as its own icon. */
|
|
16
|
+
readonly status?: CheckStatus;
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
/** Result of a single doctor check. */
|
|
2
20
|
export interface CheckResult {
|
|
3
21
|
readonly name: string;
|
|
4
|
-
readonly status:
|
|
22
|
+
readonly status: CheckStatus;
|
|
23
|
+
/** One-line headline shown next to the check name. */
|
|
5
24
|
readonly detail: string;
|
|
25
|
+
/** Optional structured breakdown, rendered as indented sub-items. */
|
|
26
|
+
readonly items?: readonly CheckItem[];
|
|
27
|
+
/** Remedial instruction shown inline under a failing check. */
|
|
6
28
|
readonly remedy?: string;
|
|
7
29
|
}
|
|
8
30
|
|
|
@@ -2,7 +2,7 @@ import type { CommandDefinition } from "@canonical/cli-core";
|
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
3
|
import createTestRuntime from "../../../testing/helpers/createTestRuntime.js";
|
|
4
4
|
import type { PragmaContext } from "../../shared/context.js";
|
|
5
|
-
import {
|
|
5
|
+
import { DEFAULT_PREFIX_MAP } from "../../shared/prefixes.js";
|
|
6
6
|
import type { PragmaRuntime } from "../../shared/runtime.js";
|
|
7
7
|
import type { InspectResult } from "../../shared/types/index.js";
|
|
8
8
|
import buildInspectCommand from "./inspect.js";
|
|
@@ -37,7 +37,7 @@ async function executeOutput(
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
describe("graph inspect command", () => {
|
|
40
|
-
const uri = `${
|
|
40
|
+
const uri = `${DEFAULT_PREFIX_MAP.ds}global.component.button`;
|
|
41
41
|
|
|
42
42
|
it("returns inspect results", async () => {
|
|
43
43
|
const ctx = makeCtx();
|
|
@@ -58,15 +58,17 @@ describe("graph query command", () => {
|
|
|
58
58
|
expect(text).toContain("n");
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it("renders llm output", async () => {
|
|
61
|
+
it("renders llm output as a Markdown table for SELECT", async () => {
|
|
62
62
|
const ctx = makeCtx({
|
|
63
63
|
globalFlags: { llm: true, format: "text" as const, verbose: false },
|
|
64
64
|
});
|
|
65
65
|
const cmd = buildQueryCommand(ctx);
|
|
66
66
|
const { text } = await executeOutput(cmd, { sparql }, ctx);
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
expect(
|
|
68
|
+
// Condensed Markdown, not a raw JSON dump.
|
|
69
|
+
expect(text).toContain("| n |");
|
|
70
|
+
expect(text).toContain("| --- |");
|
|
71
|
+
expect(() => JSON.parse(text)).toThrow();
|
|
70
72
|
});
|
|
71
73
|
|
|
72
74
|
it("renders json output", async () => {
|
|
@@ -2,7 +2,7 @@ import chalk from "chalk";
|
|
|
2
2
|
import { formatHeading } from "#pipeline";
|
|
3
3
|
import compactUri from "../../shared/compactUri.js";
|
|
4
4
|
import type { Formatters } from "../../shared/formatters.js";
|
|
5
|
-
import {
|
|
5
|
+
import { DEFAULT_PREFIX_MAP } from "../../shared/prefixes.js";
|
|
6
6
|
import type { InspectResult } from "../../shared/types/index.js";
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -15,13 +15,13 @@ import type { InspectResult } from "../../shared/types/index.js";
|
|
|
15
15
|
const formatters: Formatters<InspectResult> = {
|
|
16
16
|
plain(result) {
|
|
17
17
|
const lines: string[] = [];
|
|
18
|
-
lines.push(formatHeading(compactUri(result.uri,
|
|
18
|
+
lines.push(formatHeading(compactUri(result.uri, DEFAULT_PREFIX_MAP)));
|
|
19
19
|
|
|
20
20
|
for (const g of result.groups) {
|
|
21
21
|
lines.push("");
|
|
22
|
-
lines.push(chalk.bold(compactUri(g.predicate,
|
|
22
|
+
lines.push(chalk.bold(compactUri(g.predicate, DEFAULT_PREFIX_MAP)));
|
|
23
23
|
for (const o of g.objects) {
|
|
24
|
-
lines.push(` ${compactUri(o,
|
|
24
|
+
lines.push(` ${compactUri(o, DEFAULT_PREFIX_MAP)}`);
|
|
25
25
|
}
|
|
26
26
|
}
|
|
27
27
|
|
|
@@ -30,13 +30,13 @@ const formatters: Formatters<InspectResult> = {
|
|
|
30
30
|
|
|
31
31
|
llm(result) {
|
|
32
32
|
const lines = [
|
|
33
|
-
`## ${compactUri(result.uri,
|
|
33
|
+
`## ${compactUri(result.uri, DEFAULT_PREFIX_MAP)} (${result.uri})`,
|
|
34
34
|
"",
|
|
35
35
|
];
|
|
36
36
|
for (const g of result.groups) {
|
|
37
|
-
lines.push(`**${compactUri(g.predicate,
|
|
37
|
+
lines.push(`**${compactUri(g.predicate, DEFAULT_PREFIX_MAP)}:**`);
|
|
38
38
|
for (const o of g.objects) {
|
|
39
|
-
lines.push(` - ${compactUri(o,
|
|
39
|
+
lines.push(` - ${compactUri(o, DEFAULT_PREFIX_MAP)}`);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
return lines.join("\n");
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import type { QueryResult } from "@canonical/ke";
|
|
2
|
+
import { describe, expect, it } from "vitest";
|
|
3
|
+
import formatters from "./query.js";
|
|
4
|
+
|
|
5
|
+
const selectResult = {
|
|
6
|
+
type: "select",
|
|
7
|
+
bindings: [
|
|
8
|
+
{ s: "a", n: "1" },
|
|
9
|
+
{ s: "b", n: "2" },
|
|
10
|
+
],
|
|
11
|
+
} as unknown as QueryResult;
|
|
12
|
+
|
|
13
|
+
const emptySelect = {
|
|
14
|
+
type: "select",
|
|
15
|
+
bindings: [],
|
|
16
|
+
} as unknown as QueryResult;
|
|
17
|
+
|
|
18
|
+
const askResult = { type: "ask", result: true } as unknown as QueryResult;
|
|
19
|
+
|
|
20
|
+
const constructResult = {
|
|
21
|
+
type: "construct",
|
|
22
|
+
triples: [
|
|
23
|
+
{ subject: "s1", predicate: "p1", object: "line one\nline two" },
|
|
24
|
+
{ subject: "s2", predicate: "p2", object: "o2" },
|
|
25
|
+
],
|
|
26
|
+
} as unknown as QueryResult;
|
|
27
|
+
|
|
28
|
+
const emptyConstruct = {
|
|
29
|
+
type: "construct",
|
|
30
|
+
triples: [],
|
|
31
|
+
} as unknown as QueryResult;
|
|
32
|
+
|
|
33
|
+
describe("graph query formatters — plain", () => {
|
|
34
|
+
it("renders SELECT as a tab table", () => {
|
|
35
|
+
const text = formatters.plain(selectResult);
|
|
36
|
+
expect(text.split("\n")).toEqual(["s\tn", "a\t1", "b\t2"]);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it("reports no results for an empty SELECT", () => {
|
|
40
|
+
expect(formatters.plain(emptySelect)).toBe("No results.");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("renders ASK as a boolean", () => {
|
|
44
|
+
expect(formatters.plain(askResult)).toBe("ASK: true");
|
|
45
|
+
});
|
|
46
|
+
|
|
47
|
+
it("renders CONSTRUCT as one-line triples", () => {
|
|
48
|
+
const text = formatters.plain(constructResult);
|
|
49
|
+
expect(text).toContain("s1\tp1\tline one line two");
|
|
50
|
+
expect(text).toContain("s2\tp2\to2");
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
it("reports no triples for an empty CONSTRUCT", () => {
|
|
54
|
+
expect(formatters.plain(emptyConstruct)).toBe("No triples.");
|
|
55
|
+
});
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
describe("graph query formatters — llm", () => {
|
|
59
|
+
it("renders SELECT as a Markdown table", () => {
|
|
60
|
+
const text = formatters.llm(selectResult);
|
|
61
|
+
expect(text).toContain("| s | n |");
|
|
62
|
+
expect(text).toContain("| --- | --- |");
|
|
63
|
+
expect(text).toContain("| a | 1 |");
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
it("renders an empty SELECT as italic no-results", () => {
|
|
67
|
+
expect(formatters.llm(emptySelect)).toBe("_No results._");
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("renders ASK as bold", () => {
|
|
71
|
+
expect(formatters.llm(askResult)).toBe("**ASK** → true");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("renders CONSTRUCT triples as a bullet list", () => {
|
|
75
|
+
const text = formatters.llm(constructResult);
|
|
76
|
+
expect(text).toContain("- s1\tp1\tline one line two");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
describe("graph query formatters — json", () => {
|
|
81
|
+
it("serializes the raw result", () => {
|
|
82
|
+
expect(JSON.parse(formatters.json(askResult))).toMatchObject({
|
|
83
|
+
type: "ask",
|
|
84
|
+
result: true,
|
|
85
|
+
});
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -1,12 +1,33 @@
|
|
|
1
1
|
import type { QueryResult } from "@canonical/ke";
|
|
2
2
|
import type { Formatters } from "../../shared/formatters.js";
|
|
3
3
|
|
|
4
|
+
/**
|
|
5
|
+
* Render CONSTRUCT triples as readable `subject predicate object` lines,
|
|
6
|
+
* collapsing any newlines in literal objects so each triple stays on one line.
|
|
7
|
+
*/
|
|
8
|
+
function formatTriples(
|
|
9
|
+
triples: ReadonlyArray<{
|
|
10
|
+
subject: string;
|
|
11
|
+
predicate: string;
|
|
12
|
+
object: string;
|
|
13
|
+
}>,
|
|
14
|
+
): string {
|
|
15
|
+
if (triples.length === 0) return "No triples.";
|
|
16
|
+
return triples
|
|
17
|
+
.map((t) => {
|
|
18
|
+
const object = t.object.replace(/\s*\n\s*/g, " ").trim();
|
|
19
|
+
return `${t.subject}\t${t.predicate}\t${object}`;
|
|
20
|
+
})
|
|
21
|
+
.join("\n");
|
|
22
|
+
}
|
|
23
|
+
|
|
4
24
|
/**
|
|
5
25
|
* Formatters for `pragma graph query` output.
|
|
6
26
|
*
|
|
7
|
-
* - **plain**: tab-separated table for SELECT
|
|
8
|
-
*
|
|
9
|
-
* - **llm**:
|
|
27
|
+
* - **plain**: tab-separated table for SELECT, `ASK: true/false` for ASK, and
|
|
28
|
+
* readable triple lines for CONSTRUCT.
|
|
29
|
+
* - **llm**: condensed Markdown (table for SELECT, one line for ASK, a triple
|
|
30
|
+
* list for CONSTRUCT) rather than a raw JSON dump.
|
|
10
31
|
* - **json**: serialized {@link QueryResult} as indented JSON.
|
|
11
32
|
*/
|
|
12
33
|
const formatters: Formatters<QueryResult> = {
|
|
@@ -22,11 +43,27 @@ const formatters: Formatters<QueryResult> = {
|
|
|
22
43
|
if (result.type === "ask") {
|
|
23
44
|
return `ASK: ${String(result.result)}`;
|
|
24
45
|
}
|
|
25
|
-
return
|
|
46
|
+
return formatTriples(result.triples);
|
|
26
47
|
},
|
|
27
48
|
|
|
28
49
|
llm(result) {
|
|
29
|
-
|
|
50
|
+
if (result.type === "select") {
|
|
51
|
+
if (result.bindings.length === 0) return "_No results._";
|
|
52
|
+
const cols = Object.keys(result.bindings[0] ?? {});
|
|
53
|
+
const header = `| ${cols.join(" | ")} |`;
|
|
54
|
+
const divider = `| ${cols.map(() => "---").join(" | ")} |`;
|
|
55
|
+
const rows = result.bindings.map(
|
|
56
|
+
(b) => `| ${cols.map((c) => b[c] ?? "").join(" | ")} |`,
|
|
57
|
+
);
|
|
58
|
+
return [header, divider, ...rows].join("\n");
|
|
59
|
+
}
|
|
60
|
+
if (result.type === "ask") {
|
|
61
|
+
return `**ASK** → ${String(result.result)}`;
|
|
62
|
+
}
|
|
63
|
+
return formatTriples(result.triples)
|
|
64
|
+
.split("\n")
|
|
65
|
+
.map((line) => (line.includes("\t") ? `- ${line}` : line))
|
|
66
|
+
.join("\n");
|
|
30
67
|
},
|
|
31
68
|
|
|
32
69
|
json(result) {
|