@canonical/pragma-cli 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/pragma +0 -0
- package/package.json +19 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +15 -6
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.test.ts +10 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
- package/src/capabilities/capabilities/catalog.ts +20 -8
- package/src/capabilities/capabilities/hints.ts +21 -36
- package/src/capabilities/capabilities/types.ts +2 -0
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.render.test.ts +31 -4
- package/src/capabilities/colophon/colophon.render.ts +13 -4
- package/src/capabilities/colophon/colophon.test.ts +3 -3
- package/src/capabilities/colophon/colophon.verb.ts +11 -4
- package/src/capabilities/colophon/markdownTerminal.ts +33 -14
- package/src/capabilities/colophon/types.ts +16 -7
- package/src/capabilities/config/collectConfigShow.test.ts +57 -0
- package/src/capabilities/config/collectConfigShow.ts +41 -4
- package/src/capabilities/config/fields.ts +10 -108
- package/src/capabilities/config/set.test.ts +6 -6
- package/src/capabilities/config/set.verb.ts +12 -8
- package/src/capabilities/config/show.render.test.ts +137 -0
- package/src/capabilities/config/show.render.ts +57 -16
- package/src/capabilities/config/show.verb.ts +9 -8
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
- package/src/capabilities/create/byteEquality.test.ts +82 -101
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
- package/src/capabilities/create/constants.ts +75 -0
- package/src/capabilities/create/create.mcp.test.ts +2 -2
- package/src/capabilities/create/create.test.ts +192 -16
- package/src/capabilities/create/create.verb.ts +208 -79
- package/src/capabilities/create/index.ts +2 -10
- package/src/capabilities/create/pickGenerator.ts +34 -28
- package/src/capabilities/create/templates.embedded.generated.ts +4 -29
- package/src/capabilities/create/types.ts +10 -6
- package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
- package/src/capabilities/distribution.test.ts +120 -0
- package/src/capabilities/distribution.ts +59 -0
- package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
- package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
- package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
- package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
- package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
- package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
- package/src/capabilities/doctor/doctor.render.test.ts +170 -0
- package/src/capabilities/doctor/doctor.render.ts +117 -34
- package/src/capabilities/doctor/doctor.test.ts +68 -9
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +33 -9
- package/src/capabilities/doctor/types.ts +16 -1
- package/src/capabilities/emptyRecovery.test.ts +32 -15
- package/src/capabilities/graph/inspect.verb.ts +3 -2
- package/src/capabilities/graph/query.verb.ts +3 -2
- package/src/capabilities/graph/runQuery.ts +8 -5
- package/src/capabilities/index.ts +22 -18
- package/src/capabilities/info/collectInfo.ts +10 -6
- package/src/capabilities/info/info.render.ts +3 -2
- package/src/capabilities/info/info.verb.ts +3 -2
- package/src/capabilities/lazy.test.ts +93 -2
- package/src/capabilities/ontology/index.ts +10 -3
- package/src/capabilities/ontology/ontology.test.ts +157 -5
- package/src/capabilities/ontology/verbs.ts +203 -104
- package/src/capabilities/prompt/prompt.render.test.ts +94 -0
- package/src/capabilities/prompt/prompt.render.ts +23 -6
- package/src/capabilities/prompt/prompt.test.ts +68 -1
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/reference.test.ts +32 -0
- package/src/capabilities/resources/provider.ts +58 -25
- package/src/capabilities/resources/resources.test.ts +176 -21
- package/src/capabilities/scopeTypeSync.test.ts +37 -0
- package/src/capabilities/setup/index.ts +7 -1
- package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
- package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
- package/src/capabilities/setup/operations/setupLsp.ts +56 -12
- package/src/capabilities/setup/operations/setupMcp.ts +216 -39
- package/src/capabilities/setup/operations/setupSkills.ts +10 -4
- package/src/capabilities/setup/setup.render.test.ts +109 -0
- package/src/capabilities/setup/setup.render.ts +61 -14
- package/src/capabilities/setup/setup.test.ts +352 -1
- package/src/capabilities/setup/setup.verb.ts +68 -9
- package/src/capabilities/setup/shell.ts +29 -3
- package/src/capabilities/setup/types.ts +59 -2
- package/src/capabilities/shared/bands.ts +16 -0
- package/src/capabilities/skill/discover.ts +11 -5
- package/src/capabilities/skill/render.ts +4 -5
- package/src/capabilities/skill/verbs.ts +7 -7
- package/src/capabilities/sources/collectStatus.ts +69 -49
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +146 -98
- package/src/capabilities/sources/sources.test.ts +264 -84
- package/src/capabilities/sources/status.render.test.ts +94 -0
- package/src/capabilities/sources/status.render.ts +55 -33
- package/src/capabilities/sources/status.verb.ts +15 -8
- package/src/capabilities/sources/types.ts +18 -25
- package/src/capabilities/sources/update.render.ts +9 -2
- package/src/capabilities/sources/update.verb.ts +12 -23
- package/src/capabilities/surface.test.ts +65 -17
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +9 -7
- package/src/constants.ts +49 -23
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
- package/src/kernel/completion/complete.test.ts +3 -5
- package/src/kernel/completion/emitScripts.test.ts +1 -1
- package/src/kernel/completion/emitScripts.ts +4 -2
- package/src/kernel/completion/entitySource.test.ts +185 -41
- package/src/kernel/completion/entitySource.ts +131 -113
- package/src/kernel/completion/model.test.ts +3 -3
- package/src/kernel/completion/model.ts +11 -1
- package/src/kernel/completion/parse.test.ts +2 -4
- package/src/kernel/completion/safety.test.ts +82 -36
- package/src/kernel/completion/shellDrive.test.ts +592 -0
- package/src/kernel/completion/templates/fish.ts +28 -6
- package/src/kernel/completion/types.ts +3 -3
- package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
- package/src/kernel/config/defaults.test.ts +85 -0
- package/src/kernel/config/defaults.ts +12 -23
- package/src/kernel/config/evaluateProjectConfig.ts +2 -3
- package/src/kernel/config/findProjectConfig.ts +4 -3
- package/src/kernel/config/firstRun.ts +12 -5
- package/src/kernel/config/globalConfig.ts +2 -2
- package/src/kernel/config/index.ts +4 -5
- package/src/kernel/config/paths.ts +17 -17
- package/src/kernel/config/readConfig.test.ts +242 -6
- package/src/kernel/config/readConfig.ts +8 -8
- package/src/kernel/config/schema.test.ts +50 -0
- package/src/kernel/config/schema.ts +97 -10
- package/src/kernel/config/types.ts +73 -19
- package/src/kernel/copy.test.ts +453 -0
- package/src/kernel/error/PragmaError.test.ts +5 -5
- package/src/kernel/error/constants.ts +3 -2
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +22 -45
- package/src/kernel/error/recovery.ts +22 -27
- package/src/kernel/error/renderError.ts +1 -1
- package/src/kernel/error/types.ts +5 -4
- package/src/kernel/packs/collect.test.ts +195 -11
- package/src/kernel/packs/collect.ts +213 -58
- package/src/kernel/packs/compile.test.ts +163 -7
- package/src/kernel/packs/compile.ts +15 -12
- package/src/kernel/packs/disclosure.test.ts +5 -2
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
- package/src/kernel/packs/graphqlSource.test.ts +6 -3
- package/src/kernel/packs/iri.ts +5 -7
- package/src/kernel/packs/renderPack.ts +5 -3
- package/src/kernel/packs/resolveEntity.ts +21 -17
- package/src/kernel/packs/runBodies.ts +15 -14
- package/src/kernel/packs/schema.ts +96 -10
- package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
- package/src/kernel/packs/sparql/runSelect.ts +73 -34
- package/src/kernel/packs/toolDescription.test.ts +10 -2
- package/src/kernel/packs/types.ts +39 -4
- package/src/kernel/packs/uniqueness.ts +4 -4
- package/src/kernel/project/cli/buildProgram.test.ts +60 -11
- package/src/kernel/project/cli/buildProgram.ts +11 -0
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/globalFlags.test.ts +12 -4
- package/src/kernel/project/cli/globalFlags.ts +14 -14
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +109 -107
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +12 -7
- package/src/kernel/project/mcp/mcpError.ts +52 -0
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
- package/src/kernel/project/mcp/prompts/provider.ts +51 -7
- package/src/kernel/project/mcp/prompts/source.ts +68 -67
- package/src/kernel/project/mcp/registerVerb.ts +25 -6
- package/src/kernel/project/mcp/serve.ts +11 -3
- package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
- package/src/kernel/render/disclosure.ts +4 -2
- package/src/kernel/render/formatters.ts +5 -4
- package/src/kernel/render/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +35 -1
- package/src/kernel/render/renderers.ts +25 -7
- package/src/kernel/render/style.test.ts +31 -0
- package/src/kernel/render/style.ts +74 -0
- package/src/kernel/runtime/graphpack/build.ts +57 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
- package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +37 -11
- package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
- package/src/kernel/runtime/graphpack/hash.ts +3 -2
- package/src/kernel/runtime/graphpack/index.ts +1 -1
- package/src/kernel/runtime/graphpack/manifest.ts +114 -12
- package/src/kernel/runtime/graphpack/read.ts +32 -22
- package/src/kernel/runtime/graphpack/schemas.ts +74 -0
- package/src/kernel/runtime/graphpack/stories.ts +84 -0
- package/src/kernel/runtime/graphpack/types.ts +72 -45
- package/src/kernel/runtime/loadSession.ts +4 -12
- package/src/kernel/runtime/paths.ts +64 -15
- package/src/kernel/runtime/probe.test.ts +8 -4
- package/src/kernel/runtime/readEntity.ts +6 -5
- package/src/kernel/runtime/refs/gitOps.ts +1 -19
- package/src/kernel/runtime/refs/parseRef.ts +7 -7
- package/src/kernel/runtime/refs/resolve.ts +141 -65
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +62 -0
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/types.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
- package/src/kernel/spec/emitReference.test.ts +196 -0
- package/src/kernel/spec/emitReference.ts +624 -0
- package/src/kernel/spec/emitSurface.ts +13 -7
- package/src/kernel/spec/index.ts +2 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/surfaceConformance.ts +8 -0
- package/src/kernel/spec/types.ts +29 -9
- package/src/kernel/spec/validate.ts +2 -1
- package/src/kernel/vocabulary.test.ts +164 -0
- package/src/kernel/vocabulary.ts +146 -0
- package/src/testing/PARITY_GAPS.ts +9 -3
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
- package/src/testing/behavioral/completion.test.ts +21 -8
- package/src/testing/behavioral/disclosure.test.ts +12 -0
- package/src/testing/behavioral/docExamples.test.ts +283 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
- package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
- package/src/testing/behavioral/harness.foundation.test.ts +14 -5
- package/src/testing/behavioral/journeys.cli.test.ts +116 -73
- package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
- package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
- package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
- package/src/testing/behavioral/liveReadSurface.ts +4 -3
- package/src/testing/behavioral/mcpSurface.test.ts +10 -5
- package/src/testing/behavioral/staleCommands.test.ts +86 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
- package/src/testing/eval/cases/assembly.ts +16 -9
- package/src/testing/eval/cases/effectful.ts +31 -16
- package/src/testing/eval/cases/readNouns.ts +23 -18
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +201 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +179 -0
- package/src/testing/fixtures/graph/owlExport.ts +97 -0
- package/src/testing/helpers/extractSnippets.ts +29 -0
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- package/src/testing/helpers/golden.ts +1 -1
- package/src/testing/helpers/packRuntime.ts +2 -3
- package/src/testing/helpers/projectCli.ts +2 -1
- package/src/testing/helpers/projectMcp.ts +6 -0
- package/src/testing/perf/budgets.test.ts +4 -0
- package/src/testing/perf/budgets.ts +22 -7
- package/src/testing/perf/globalSetup.ts +121 -6
- package/src/types.ts +2 -2
- package/src/capabilities/block/blockList.verb.ts +0 -150
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -155
- package/src/capabilities/block/parity.test.ts +0 -180
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- package/src/capabilities/config/field.test.ts +0 -188
- package/src/capabilities/modifier/index.ts +0 -15
- package/src/capabilities/modifier/pack.ts +0 -64
- package/src/capabilities/standard/index.ts +0 -18
- package/src/capabilities/standard/pack.ts +0 -128
- package/src/capabilities/tier/index.ts +0 -19
- package/src/capabilities/tier/lookup.render.ts +0 -34
- package/src/capabilities/tier/lookup.test.ts +0 -68
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -59
- package/src/capabilities/token/addConfig.render.ts +0 -27
- package/src/capabilities/token/addConfig.test.ts +0 -62
- package/src/capabilities/token/addConfig.verb.ts +0 -40
- package/src/capabilities/token/index.ts +0 -19
- package/src/capabilities/token/pack.ts +0 -58
- package/src/capabilities/token/runAddConfig.ts +0 -95
- package/src/kernel/completion/bashDrive.test.ts +0 -73
- package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
- package/src/kernel/runtime/lock.ts +0 -90
|
@@ -2,50 +2,72 @@
|
|
|
2
2
|
* Formatters for `pragma sources status` — plain, llm, json.
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
+
import { BIN_NAME } from "../../constants.js";
|
|
6
|
+
import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
|
|
5
7
|
import type { Formatters } from "../../kernel/spec/types.js";
|
|
6
8
|
import type { SourcesStatusData } from "./types.js";
|
|
7
9
|
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* The headline for each boot decision. The `embedded` case names the update
|
|
12
|
+
* explicitly: the distribution's snapshot answers reads, but it is a snapshot,
|
|
13
|
+
* so reporting it as "up to date" would be a lie.
|
|
14
|
+
*/
|
|
15
|
+
const STORE_HEADLINE: Record<SourcesStatusData["store"], string> = {
|
|
16
|
+
built: "ready",
|
|
17
|
+
embedded: `embedded snapshot (run \`${BIN_NAME} sources update\` to build from the configured packs)`,
|
|
18
|
+
unavailable: `not built (run \`${BIN_NAME} sources update\`)`,
|
|
12
19
|
};
|
|
13
20
|
|
|
21
|
+
/**
|
|
22
|
+
* Render `sources status` as plain text.
|
|
23
|
+
*
|
|
24
|
+
* @param data - The resolved sources-status payload.
|
|
25
|
+
* @param style - TTY styling; defaults to the process style. On a color-capable
|
|
26
|
+
* terminal the `Sources:` heading is bold, source names align into a column,
|
|
27
|
+
* and each configured ref is dimmed; off a TTY the styler is inert, so the
|
|
28
|
+
* output is byte-identical to the plain form.
|
|
29
|
+
* @returns The formatted status block.
|
|
30
|
+
*/
|
|
31
|
+
export function renderSourcesStatusPlain(
|
|
32
|
+
data: SourcesStatusData,
|
|
33
|
+
style: RenderStyle = defaultStyle(),
|
|
34
|
+
): string {
|
|
35
|
+
const lines = [`Store: ${STORE_HEADLINE[data.store]}`];
|
|
36
|
+
if (data.contentHash !== null) {
|
|
37
|
+
lines.push(
|
|
38
|
+
` pack: ${data.contentHash.slice(0, 12)} — ${data.entityCount ?? "?"} entities, built ${data.builtAt ?? "?"}`,
|
|
39
|
+
` from: ${data.sourceRef ?? "?"}`,
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
lines.push("", style.enabled ? style.bold("Sources:") : "Sources:");
|
|
43
|
+
if (data.sources.length === 0) {
|
|
44
|
+
lines.push(" (none configured)");
|
|
45
|
+
}
|
|
46
|
+
const nameWidth = style.enabled
|
|
47
|
+
? Math.max(0, ...data.sources.map((source) => source.name.length))
|
|
48
|
+
: 0;
|
|
49
|
+
for (const source of data.sources) {
|
|
50
|
+
lines.push(
|
|
51
|
+
style.enabled
|
|
52
|
+
? ` ${source.name.padEnd(nameWidth)} ${style.dim(source.ref)}`
|
|
53
|
+
: ` ${source.name} ${source.ref}`,
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
return lines.join("\n");
|
|
57
|
+
}
|
|
58
|
+
|
|
14
59
|
export const statusFormatters: Formatters<SourcesStatusData> = {
|
|
15
60
|
plain(data) {
|
|
16
|
-
|
|
17
|
-
data.lockPresent
|
|
18
|
-
? `Store: ${data.cached ? "ready" : "locked but not cached"}`
|
|
19
|
-
: "Store: no lock (run `pragma sources update`)",
|
|
20
|
-
];
|
|
21
|
-
if (data.cached) {
|
|
22
|
-
lines.push(
|
|
23
|
-
` pack: ${data.contentHash?.slice(0, 12)} — ${data.entityCount ?? "?"} entities, built ${data.builtAt ?? "?"}`,
|
|
24
|
-
);
|
|
25
|
-
}
|
|
26
|
-
lines.push("", "Sources:");
|
|
27
|
-
if (data.sources.length === 0) {
|
|
28
|
-
lines.push(" (none configured)");
|
|
29
|
-
}
|
|
30
|
-
for (const source of data.sources) {
|
|
31
|
-
const resolved = source.resolved
|
|
32
|
-
? ` @ ${source.resolved.slice(0, 12)}`
|
|
33
|
-
: "";
|
|
34
|
-
lines.push(
|
|
35
|
-
` ${source.name} [${STALENESS_MARK[source.staleness]}]${resolved}`,
|
|
36
|
-
);
|
|
37
|
-
}
|
|
38
|
-
return lines.join("\n");
|
|
61
|
+
return renderSourcesStatusPlain(data);
|
|
39
62
|
},
|
|
40
63
|
|
|
41
64
|
llm(data) {
|
|
42
|
-
const lines = [
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
];
|
|
65
|
+
const lines = [`# sources`, `- Store: ${data.store}`];
|
|
66
|
+
if (data.sourceRef !== null) lines.push(`- From: ${data.sourceRef}`);
|
|
67
|
+
if (data.entityCount !== null)
|
|
68
|
+
lines.push(`- Entities: ${data.entityCount}`);
|
|
47
69
|
for (const source of data.sources) {
|
|
48
|
-
lines.push(`- ${source.name}: ${source.
|
|
70
|
+
lines.push(`- ${source.name}: ${source.ref}`);
|
|
49
71
|
}
|
|
50
72
|
return lines.join("\n");
|
|
51
73
|
},
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
* The `sources status` verb — a storeless read.
|
|
3
3
|
*
|
|
4
4
|
* `needsStore: false` is load-bearing: status must report even a cold or
|
|
5
|
-
* unbuilt store (
|
|
6
|
-
* a cold store would throw STORE_UNAVAILABLE before status could say
|
|
7
|
-
* `run` is a lazy thunk (the collector is dynamic-imported), keeping
|
|
8
|
-
* reader off the fast path, exactly like `info`.
|
|
5
|
+
* unbuilt store (`store: "unavailable"`), so it must NOT boot the store —
|
|
6
|
+
* booting a cold store would throw STORE_UNAVAILABLE before status could say
|
|
7
|
+
* so. Its `run` is a lazy thunk (the collector is dynamic-imported), keeping
|
|
8
|
+
* the config reader off the fast path, exactly like `info`.
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
|
+
import { BIN_NAME } from "../../constants.js";
|
|
11
12
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
12
13
|
import { statusFormatters } from "./status.render.js";
|
|
13
14
|
import type { SourcesStatusData } from "./types.js";
|
|
@@ -18,13 +19,19 @@ export const statusVerb: VerbSpec<
|
|
|
18
19
|
SourcesStatusData
|
|
19
20
|
> = {
|
|
20
21
|
path: ["sources", "status"],
|
|
21
|
-
summary: "Report
|
|
22
|
-
doc: "Storeless — reads
|
|
22
|
+
summary: "Report which pack answers reads, and the packs it was built from.",
|
|
23
|
+
doc: "Storeless — reads config and the pack cache without booting the store, so it works even when the store is cold. Reports whether reads are answered by a locally built pack, by the embedded snapshot, or not at all.",
|
|
23
24
|
params: [],
|
|
24
25
|
output: { formatters: statusFormatters },
|
|
25
26
|
examples: [
|
|
26
|
-
{
|
|
27
|
-
|
|
27
|
+
{
|
|
28
|
+
cmd: `${BIN_NAME} sources status`,
|
|
29
|
+
note: "human-readable readiness summary",
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
cmd: `${BIN_NAME} sources status --format json`,
|
|
33
|
+
note: "the full envelope",
|
|
34
|
+
},
|
|
28
35
|
],
|
|
29
36
|
capability: {
|
|
30
37
|
needsStore: false,
|
|
@@ -1,37 +1,29 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Data shapes for the `sources` noun — `status` (storeless read) and `update`
|
|
3
|
-
* (the Task that resolves, builds, and
|
|
3
|
+
* (the Task that resolves, builds, and points the project at a pack).
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
-
/** How a configured source stands relative to the lock and the pack cache. */
|
|
7
|
-
export type SourceStaleness = "up-to-date" | "config-drift" | "uncached";
|
|
8
|
-
|
|
9
|
-
/** One configured source's status. */
|
|
10
|
-
export interface SourceStatusEntry {
|
|
11
|
-
/** Package name. */
|
|
12
|
-
readonly name: string;
|
|
13
|
-
/** The config `packages` source ref (verbatim). */
|
|
14
|
-
readonly ref: string;
|
|
15
|
-
/** The lock's resolved revision, or null when not locked. */
|
|
16
|
-
readonly resolved: string | null;
|
|
17
|
-
/** Whether the config ref matches the lock and the pack is cached. */
|
|
18
|
-
readonly staleness: SourceStaleness;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
6
|
/** The `sources status` payload — assembled without booting the store. */
|
|
22
7
|
export interface SourcesStatusData {
|
|
23
8
|
readonly cwd: string;
|
|
24
|
-
/**
|
|
25
|
-
|
|
26
|
-
|
|
9
|
+
/**
|
|
10
|
+
* Which pack answers reads, straight from the boot decision: the project's
|
|
11
|
+
* own `built` pack, the distribution's `embedded` snapshot, or none at all.
|
|
12
|
+
*/
|
|
13
|
+
readonly store: "embedded" | "built" | "unavailable";
|
|
14
|
+
/** The answering pack's content hash, or null when there is none. */
|
|
27
15
|
readonly contentHash: string | null;
|
|
28
|
-
/**
|
|
29
|
-
readonly
|
|
30
|
-
/** When the
|
|
16
|
+
/** The answering pack's provenance label (manifest `sourceRef`), or null. */
|
|
17
|
+
readonly sourceRef: string | null;
|
|
18
|
+
/** When the answering pack was built (manifest `createdAt`), or null. */
|
|
31
19
|
readonly builtAt: string | null;
|
|
32
|
-
/** Total indexed entity count from the
|
|
20
|
+
/** Total indexed entity count from the answering pack, or null. */
|
|
33
21
|
readonly entityCount: number | null;
|
|
34
|
-
|
|
22
|
+
/** The configured pack declarations, as written in the config. */
|
|
23
|
+
readonly sources: readonly {
|
|
24
|
+
readonly name: string;
|
|
25
|
+
readonly ref: string;
|
|
26
|
+
}[];
|
|
35
27
|
}
|
|
36
28
|
|
|
37
29
|
/** One resolved source in the `sources update` result. */
|
|
@@ -39,6 +31,8 @@ export interface UpdatedSource {
|
|
|
39
31
|
readonly name: string;
|
|
40
32
|
readonly resolved: string;
|
|
41
33
|
readonly sourceCount: number;
|
|
34
|
+
/** How many `stories/*.json` files this package contributed to the pack. */
|
|
35
|
+
readonly storyCount: number;
|
|
42
36
|
}
|
|
43
37
|
|
|
44
38
|
/** The `sources update` result payload. */
|
|
@@ -46,6 +40,5 @@ export interface SourcesUpdateData {
|
|
|
46
40
|
readonly contentHash: string;
|
|
47
41
|
/** Whether the pack was reused from cache rather than rebuilt. */
|
|
48
42
|
readonly reused: boolean;
|
|
49
|
-
readonly lockPath: string;
|
|
50
43
|
readonly packs: readonly UpdatedSource[];
|
|
51
44
|
}
|
|
@@ -11,11 +11,18 @@ export const updateFormatters: Formatters<SourcesUpdateData> = {
|
|
|
11
11
|
data.reused
|
|
12
12
|
? `Store up to date (pack ${data.contentHash.slice(0, 12)}).`
|
|
13
13
|
: `Built pack ${data.contentHash.slice(0, 12)}.`,
|
|
14
|
-
`Wrote ${data.lockPath}.`,
|
|
15
14
|
];
|
|
16
15
|
for (const pack of data.packs) {
|
|
16
|
+
// FILES, not usable stories: `sources update` carries them verbatim and
|
|
17
|
+
// never validates them (zod would have to run per record, in the build).
|
|
18
|
+
// `doctor` is where "2 stories ignored" comes from — saying "stories"
|
|
19
|
+
// here would put a green count next to a broken file.
|
|
20
|
+
const stories =
|
|
21
|
+
pack.storyCount > 0
|
|
22
|
+
? `, ${pack.storyCount} story file${pack.storyCount === 1 ? "" : "s"}`
|
|
23
|
+
: "";
|
|
17
24
|
lines.push(
|
|
18
|
-
` ${pack.name} @ ${pack.resolved.slice(0, 12)} (${pack.sourceCount} source${pack.sourceCount === 1 ? "" : "s"})`,
|
|
25
|
+
` ${pack.name} @ ${pack.resolved.slice(0, 12)} (${pack.sourceCount} source${pack.sourceCount === 1 ? "" : "s"}${stories})`,
|
|
19
26
|
);
|
|
20
27
|
}
|
|
21
28
|
return lines.join("\n");
|
|
@@ -1,35 +1,32 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The `sources update` verb — resolve, build
|
|
2
|
+
* The `sources update` verb — resolve the configured packs, build the store,
|
|
3
|
+
* and point the project at it.
|
|
3
4
|
*
|
|
4
5
|
* A mutation (`mutates: true`) that needs the network (`needsNetwork: true`)
|
|
5
6
|
* but NOT the pre-booted store (`needsStore: false`): update is what *creates*
|
|
6
7
|
* the store, so the dispatcher must not try to boot it first (a cold boot would
|
|
7
8
|
* throw STORE_UNAVAILABLE). `run` returns a `Promise<Task<R>>` — the union's
|
|
8
9
|
* third arm — which the dispatcher awaits: for a real execution it resolves and
|
|
9
|
-
* builds before handing back the
|
|
10
|
+
* builds before handing back the pointer-writing Task; for a preview
|
|
10
11
|
* (`runtime.mutation.preview`) it stays network-free and hands back a
|
|
11
|
-
* plan-only Task.
|
|
12
|
+
* plan-only Task.
|
|
12
13
|
*/
|
|
13
14
|
|
|
14
15
|
import type { Task } from "@canonical/task";
|
|
16
|
+
import { BIN_NAME } from "../../constants.js";
|
|
15
17
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
16
18
|
import type { SourcesUpdateData } from "./types.js";
|
|
17
19
|
import { updateFormatters } from "./update.render.js";
|
|
18
20
|
|
|
19
21
|
/** The `sources update` verb spec. */
|
|
20
22
|
export const updateVerb: VerbSpec<
|
|
21
|
-
{
|
|
23
|
+
{ skipInvalid?: boolean },
|
|
22
24
|
SourcesUpdateData
|
|
23
25
|
> = {
|
|
24
26
|
path: ["sources", "update"],
|
|
25
|
-
summary: "Resolve configured
|
|
26
|
-
doc: "Resolves each configured
|
|
27
|
+
summary: "Resolve configured packs and build the local store from them.",
|
|
28
|
+
doc: "Resolves each configured pack (git/file/npm) and builds one content-addressed pack, which every later boot reads with no network access. Pin a revision by putting a commit SHA in the pack's source ref.",
|
|
27
29
|
params: [
|
|
28
|
-
{
|
|
29
|
-
kind: "boolean",
|
|
30
|
-
name: "frozen",
|
|
31
|
-
doc: "Re-resolve to the lock's pinned revisions exactly; never advance.",
|
|
32
|
-
},
|
|
33
30
|
{
|
|
34
31
|
kind: "boolean",
|
|
35
32
|
name: "skipInvalid",
|
|
@@ -38,13 +35,9 @@ export const updateVerb: VerbSpec<
|
|
|
38
35
|
],
|
|
39
36
|
output: { formatters: updateFormatters },
|
|
40
37
|
examples: [
|
|
41
|
-
{ cmd:
|
|
42
|
-
{
|
|
43
|
-
cmd: "pragma sources update --frozen",
|
|
44
|
-
note: "reproduce the locked state",
|
|
45
|
-
},
|
|
38
|
+
{ cmd: `${BIN_NAME} sources update`, note: "resolve and build" },
|
|
46
39
|
{
|
|
47
|
-
cmd:
|
|
40
|
+
cmd: `${BIN_NAME} sources update --skip-invalid`,
|
|
48
41
|
note: "build from the parseable sources, warning about any dropped",
|
|
49
42
|
},
|
|
50
43
|
],
|
|
@@ -59,7 +52,7 @@ export const updateVerb: VerbSpec<
|
|
|
59
52
|
},
|
|
60
53
|
// `run` really returns `Promise<Task<R>>`: `buildUpdateTask` awaits config
|
|
61
54
|
// (and, on a real execution, resolves + builds) before handing back the
|
|
62
|
-
//
|
|
55
|
+
// pointer-writing Task — or, when `runtime.mutation.preview` is set, a
|
|
63
56
|
// network-free plan Task. The dispatcher and MCP handler both `await` this
|
|
64
57
|
// promise into a `Task`. The `VerbSpec.run` union is deliberately two-armed
|
|
65
58
|
// (`Promise<R> | Task<R>`) — a third `Promise<Task<R>>` arm would poison async
|
|
@@ -67,10 +60,6 @@ export const updateVerb: VerbSpec<
|
|
|
67
60
|
// `Task<R>` arm by this single, honest cast.
|
|
68
61
|
run: (params, runtime) =>
|
|
69
62
|
import("./runUpdate.js").then((module) =>
|
|
70
|
-
module.buildUpdateTask(
|
|
71
|
-
runtime,
|
|
72
|
-
params.frozen === true,
|
|
73
|
-
params.skipInvalid === true,
|
|
74
|
-
),
|
|
63
|
+
module.buildUpdateTask(runtime, params.skipInvalid === true),
|
|
75
64
|
) as unknown as Task<SourcesUpdateData>,
|
|
76
65
|
};
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
import { describe, expect, it } from "vitest";
|
|
4
|
+
import { MCP_SERVER_NAME, VERSION } from "../constants.js";
|
|
4
5
|
import { emitSurface } from "../kernel/spec/emitSurface.js";
|
|
5
6
|
import {
|
|
6
7
|
assertConforms,
|
|
7
8
|
type Covenant,
|
|
8
9
|
} from "../kernel/spec/surfaceConformance.js";
|
|
10
|
+
import { projectMcp } from "../testing/helpers/projectMcp.js";
|
|
9
11
|
import { capabilities } from "./index.js";
|
|
10
12
|
|
|
11
13
|
/** The committed covenant, read from disk exactly as a consumer would. */
|
|
@@ -23,12 +25,11 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
|
|
|
23
25
|
expect(() => assertConforms(emitted, golden)).not.toThrow();
|
|
24
26
|
});
|
|
25
27
|
|
|
26
|
-
it("emits config show + the
|
|
28
|
+
it("emits config show + the one-command config set, in covenant order (field-verbs retired, B3)", () => {
|
|
29
|
+
// AV-228 B3 retired the per-field `tier`/`channel`/`detail` setters; the
|
|
30
|
+
// config noun is now just the `show` reader and the `set <key> <value>` writer.
|
|
27
31
|
expect(emitted.nouns.config?.verbs).toEqual([
|
|
28
32
|
{ v: "show", mcp: "config_show" },
|
|
29
|
-
{ v: "tier", args: ["<path>"], mutates: true, mcp: "config_tier" },
|
|
30
|
-
{ v: "channel", args: ["<name>"], mutates: true, mcp: "config_channel" },
|
|
31
|
-
{ v: "detail", args: ["<level>"], mutates: true, mcp: "config_detail" },
|
|
32
33
|
{
|
|
33
34
|
v: "set",
|
|
34
35
|
args: ["<key>", "<value>"],
|
|
@@ -43,15 +44,15 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
|
|
|
43
44
|
expect(emitted.nouns.__complete).toBeUndefined();
|
|
44
45
|
});
|
|
45
46
|
|
|
46
|
-
it("emits the read nouns/verbs the packs add (
|
|
47
|
+
it("emits the read nouns/verbs the packs add (sample only where declared)", () => {
|
|
47
48
|
expect(emitted.nouns.standard?.verbs.map((v) => v.v)).toEqual([
|
|
48
49
|
"list",
|
|
49
50
|
"categories",
|
|
50
51
|
"lookup",
|
|
51
52
|
"sample",
|
|
52
53
|
]);
|
|
53
|
-
// PR7
|
|
54
|
-
//
|
|
54
|
+
// PR7 completed the surface; block/modifier/token carry no-argument
|
|
55
|
+
// samples (fixedCount), tier declares no sample.
|
|
55
56
|
expect(emitted.nouns.tier?.verbs.map((v) => v.v)).toEqual([
|
|
56
57
|
"list",
|
|
57
58
|
"lookup",
|
|
@@ -61,16 +62,18 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
|
|
|
61
62
|
"lookup",
|
|
62
63
|
"sample",
|
|
63
64
|
]);
|
|
65
|
+
// `token` is purely declarative since L-OPEN-9 removed `add-config`: the
|
|
66
|
+
// three read verbs its story compiles, and no mutation.
|
|
64
67
|
expect(emitted.nouns.token?.verbs.map((v) => v.v)).toEqual([
|
|
65
68
|
"list",
|
|
66
69
|
"lookup",
|
|
67
70
|
"sample",
|
|
68
|
-
"add-config",
|
|
69
71
|
]);
|
|
72
|
+
// block list is the story's compiled, unfiltered list (L-OPEN-9): no
|
|
73
|
+
// flags — the `--all-tiers` escape died with the hand-written filtering.
|
|
70
74
|
expect(emitted.nouns.block?.verbs).toEqual([
|
|
71
75
|
{
|
|
72
76
|
v: "list",
|
|
73
|
-
flags: ["--all-tiers"],
|
|
74
77
|
needsStore: true,
|
|
75
78
|
mcp: "block_list",
|
|
76
79
|
},
|
|
@@ -82,11 +85,11 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
|
|
|
82
85
|
},
|
|
83
86
|
{ v: "sample", needsStore: true, mcp: "block_sample" },
|
|
84
87
|
]);
|
|
85
|
-
// The
|
|
86
|
-
//
|
|
88
|
+
// The tier lookup is the story's compiled lookup (L-OPEN-9), so it emits
|
|
89
|
+
// the variadic `<name...>` positional every pack lookup emits.
|
|
87
90
|
expect(emitted.nouns.tier?.verbs).toContainEqual({
|
|
88
91
|
v: "lookup",
|
|
89
|
-
args: ["<name
|
|
92
|
+
args: ["<name...>"],
|
|
90
93
|
needsStore: true,
|
|
91
94
|
mcp: "tier_lookup",
|
|
92
95
|
});
|
|
@@ -95,6 +98,15 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
|
|
|
95
98
|
it("emits the authored read nouns (ontology TBox, storeless skill, graph inspect)", () => {
|
|
96
99
|
expect(emitted.nouns.ontology?.verbs).toEqual([
|
|
97
100
|
{ v: "list", needsStore: true, mcp: "ontology_list" },
|
|
101
|
+
{
|
|
102
|
+
v: "lookup",
|
|
103
|
+
args: ["<prefix>"],
|
|
104
|
+
flags: ["--properties", "--full-uris", "--class"],
|
|
105
|
+
needsStore: true,
|
|
106
|
+
mcp: "ontology_lookup",
|
|
107
|
+
},
|
|
108
|
+
// `show` is the deprecated alias of `lookup` (AV-228 B1) — same shape,
|
|
109
|
+
// both blessed in the covenant.
|
|
98
110
|
{
|
|
99
111
|
v: "show",
|
|
100
112
|
args: ["<prefix>"],
|
|
@@ -146,11 +158,28 @@ describe("surface conformance — capabilities ⊆ covenant (PROTECTED)", () =>
|
|
|
146
158
|
it("emits setup as a mixed self+sub noun (self is a tool, sub-verbs are mcp:false)", () => {
|
|
147
159
|
// The one covenant noun both directly runnable AND with sub-verbs. The self
|
|
148
160
|
// verb is the `setup` tool; the four installers are CLI-only (mcp:false).
|
|
161
|
+
// The band-aware verbs (setup/mcp/skills) carry the --scope/--global/--local
|
|
162
|
+
// flags; completions/lsp are single-band installers with no scope.
|
|
149
163
|
expect(emitted.nouns.setup?.verbs).toEqual([
|
|
150
|
-
{
|
|
151
|
-
|
|
164
|
+
{
|
|
165
|
+
v: "setup",
|
|
166
|
+
flags: ["--scope", "--global", "--local"],
|
|
167
|
+
mutates: true,
|
|
168
|
+
mcp: "setup",
|
|
169
|
+
},
|
|
170
|
+
{
|
|
171
|
+
v: "mcp",
|
|
172
|
+
flags: ["--scope", "--global", "--local"],
|
|
173
|
+
mutates: true,
|
|
174
|
+
mcp: false,
|
|
175
|
+
},
|
|
152
176
|
{ v: "completions", mutates: true, mcp: false },
|
|
153
|
-
{
|
|
177
|
+
{
|
|
178
|
+
v: "skills",
|
|
179
|
+
flags: ["--scope", "--global", "--local"],
|
|
180
|
+
mutates: true,
|
|
181
|
+
mcp: false,
|
|
182
|
+
},
|
|
154
183
|
{ v: "lsp", mutates: true, mcp: false },
|
|
155
184
|
]);
|
|
156
185
|
});
|
|
@@ -184,14 +213,14 @@ describe("surface COMPLETE — emitted == covenant (PROTECTED)", () => {
|
|
|
184
213
|
// The CLOSING direction: assertConforms already proves emitted ⊆ covenant;
|
|
185
214
|
// this proves covenant ⊆ emitted, so together the tool sets are EQUAL — the
|
|
186
215
|
// surface-complete milestone. After PR7, every covenant tool is realized.
|
|
187
|
-
it("emits every covenant tool (all
|
|
216
|
+
it("emits every covenant tool (all 37) — set equality with the covenant", () => {
|
|
188
217
|
const emittedTools = new Set(emitted.mcpSurface.tools);
|
|
189
218
|
const missing = golden.mcpSurface.tools.filter((t) => !emittedTools.has(t));
|
|
190
219
|
expect(missing).toEqual([]);
|
|
191
220
|
expect([...emitted.mcpSurface.tools].sort()).toEqual(
|
|
192
221
|
[...golden.mcpSurface.tools].sort(),
|
|
193
222
|
);
|
|
194
|
-
expect(emitted.mcpSurface.tools).toHaveLength(
|
|
223
|
+
expect(emitted.mcpSurface.tools).toHaveLength(37);
|
|
195
224
|
});
|
|
196
225
|
|
|
197
226
|
// The covenant edit: the non-tool MCP surface is frozen too.
|
|
@@ -205,4 +234,23 @@ describe("surface COMPLETE — emitted == covenant (PROTECTED)", () => {
|
|
|
205
234
|
golden.mcpSurface.instructions,
|
|
206
235
|
);
|
|
207
236
|
});
|
|
237
|
+
|
|
238
|
+
// The L-PR6 covenant edit (PRA-107 ruling): the wire-identity RULE. The
|
|
239
|
+
// covenant states serverInfo as placeholders — a PROJECTION, deliberately not
|
|
240
|
+
// live values, so a release does not churn the covenant — and the live
|
|
241
|
+
// handshake is asserted to follow it: the server introduces itself with the
|
|
242
|
+
// distribution's declared name and the package version. The fork half of the
|
|
243
|
+
// same rule (a renamed distribution introduces itself under ITS name) is
|
|
244
|
+
// pinned in identity.test.ts, where the conf is mocked.
|
|
245
|
+
it("states the serverInfo projection rule and serves it on the wire", async () => {
|
|
246
|
+
expect(golden.mcpSurface.serverInfo).toEqual({
|
|
247
|
+
name: "<the declared distribution name>",
|
|
248
|
+
version: "<the package version>",
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
const mcp = await projectMcp([]);
|
|
252
|
+
const wire = mcp.serverInfo();
|
|
253
|
+
await mcp.cleanup();
|
|
254
|
+
expect(wire).toEqual({ name: MCP_SERVER_NAME, version: VERSION });
|
|
255
|
+
});
|
|
208
256
|
});
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tier-noun semantic parity (PROTECTED) — the `tier` story `pragma.conf.ts`
|
|
3
|
+
* declares, over a tier/block fixture graph (the standard-noun pattern).
|
|
4
|
+
*
|
|
5
|
+
* The hand-written `tier lookup` is gone (L-OPEN-9): both verbs compile from
|
|
6
|
+
* the declared story, so parity is asserted directly against the graph — every
|
|
7
|
+
* tier is reachable through list/lookup with the same names, addressable by
|
|
8
|
+
* name AND by prefixed IRI (the candidates completion now offers, D9-A), the
|
|
9
|
+
* `blocks` expand resolves the INVERSE path `^ds:tier` (the members scoped
|
|
10
|
+
* directly to the tier), a memberless tier still resolves, and a miss carries
|
|
11
|
+
* "did you mean?" suggestions like every declared lookup.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
15
|
+
import { compilePack } from "../kernel/packs/compile.js";
|
|
16
|
+
import type { LookupOutput } from "../kernel/packs/resolveEntity.js";
|
|
17
|
+
import type { PackRow } from "../kernel/packs/types.js";
|
|
18
|
+
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
19
|
+
import { DEFAULT_PREFIX_MAP } from "../kernel/render/prefixes.js";
|
|
20
|
+
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
21
|
+
import type { VerbSpec } from "../kernel/spec/types.js";
|
|
22
|
+
import { buildFixtureRuntime } from "../testing/helpers/packRuntime.js";
|
|
23
|
+
import { declaredStories } from "./distribution.js";
|
|
24
|
+
|
|
25
|
+
const tierPack = declaredStories.get("tier");
|
|
26
|
+
if (!tierPack) {
|
|
27
|
+
throw new Error('pragma.conf.ts declares no story for "tier"');
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
const DS = "https://ds.canonical.com/";
|
|
31
|
+
const PREFIXES = {
|
|
32
|
+
ds: DS,
|
|
33
|
+
owl: "http://www.w3.org/2002/07/owl#",
|
|
34
|
+
rdfs: "http://www.w3.org/2000/01/rdf-schema#",
|
|
35
|
+
xsd: "http://www.w3.org/2001/XMLSchema#",
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
const TTL = `
|
|
39
|
+
@prefix ds: <${DS}> .
|
|
40
|
+
@prefix owl: <http://www.w3.org/2002/07/owl#> .
|
|
41
|
+
@prefix rdfs: <http://www.w3.org/2000/01/rdf-schema#> .
|
|
42
|
+
@prefix xsd: <http://www.w3.org/2001/XMLSchema#> .
|
|
43
|
+
|
|
44
|
+
ds:Tier a owl:Class .
|
|
45
|
+
ds:Component a owl:Class .
|
|
46
|
+
ds:name a owl:DatatypeProperty ; rdfs:range xsd:string .
|
|
47
|
+
ds:tier a owl:ObjectProperty ; rdfs:range ds:Tier .
|
|
48
|
+
|
|
49
|
+
ds:global a ds:Tier ; ds:name "global" .
|
|
50
|
+
ds:apps a ds:Tier ; ds:name "apps" .
|
|
51
|
+
ds:apps_lxd a ds:Tier ; ds:name "apps/lxd" .
|
|
52
|
+
|
|
53
|
+
ds:button a ds:Component ; ds:name "Button" ; ds:tier ds:global .
|
|
54
|
+
ds:modal a ds:Component ; ds:name "Modal" ; ds:tier ds:global .
|
|
55
|
+
ds:lxdPanel a ds:Component ; ds:name "LXD Panel" ; ds:tier ds:apps_lxd .
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
const verbs = () => compilePack(tierPack, "pragma.conf.ts", DEFAULT_PREFIX_MAP);
|
|
59
|
+
const verb = (label: string) =>
|
|
60
|
+
verbs().find((v) => verbKey(v.path) === `tier ${label}`) as VerbSpec;
|
|
61
|
+
|
|
62
|
+
let rt: PragmaRuntime;
|
|
63
|
+
beforeAll(async () => {
|
|
64
|
+
({ rt } = await buildFixtureRuntime({ ttl: TTL, prefixes: PREFIXES }));
|
|
65
|
+
});
|
|
66
|
+
afterAll(async () => {
|
|
67
|
+
(await rt.store.get()).store.dispose();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
async function lookup(name: string): Promise<Record<string, unknown>> {
|
|
71
|
+
const out = (await verb("lookup").run({ name: [name] }, rt)) as LookupOutput;
|
|
72
|
+
expect(out.errors).toEqual([]);
|
|
73
|
+
return out.results.at(0) as Record<string, unknown>;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/** The `blocks` expand's child names, sorted (the sub-SELECT is unordered). */
|
|
77
|
+
function blockNames(entity: Record<string, unknown>): string[] {
|
|
78
|
+
return (entity.blocks as { name: string }[]).map((b) => b.name).sort();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
describe("tier list parity", () => {
|
|
82
|
+
it("lists every tier, name-ordered, with the uniform row shape", async () => {
|
|
83
|
+
const rows = (await verb("list").run({}, rt)) as PackRow[];
|
|
84
|
+
expect(rows.map((r) => r.name)).toEqual(["apps", "apps/lxd", "global"]);
|
|
85
|
+
for (const row of rows) {
|
|
86
|
+
expect(row.uri).toBeTruthy();
|
|
87
|
+
}
|
|
88
|
+
});
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
describe("tier lookup parity — the declared story's compiled lookup", () => {
|
|
92
|
+
it("resolves a tier by name and expands the blocks scoped directly to it", async () => {
|
|
93
|
+
const tier = await lookup("apps/lxd");
|
|
94
|
+
expect(tier.uri).toBe(`${DS}apps_lxd`);
|
|
95
|
+
expect(tier.name).toBe("apps/lxd");
|
|
96
|
+
// The `blocks` expand walks the INVERSE `^ds:tier` path (contract:
|
|
97
|
+
// `PackExpand.relation` admits property paths), so only the DIRECT
|
|
98
|
+
// members appear — no chain inheritance.
|
|
99
|
+
expect(blockNames(tier)).toEqual(["LXD Panel"]);
|
|
100
|
+
const global = await lookup("global");
|
|
101
|
+
expect(blockNames(global)).toEqual(["Button", "Modal"]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("a memberless tier still resolves, with an empty blocks expand", async () => {
|
|
105
|
+
const apps = await lookup("apps");
|
|
106
|
+
expect(apps.name).toBe("apps");
|
|
107
|
+
expect(apps.blocks).toEqual([]);
|
|
108
|
+
// The empty expand renders as an OMITTED section (the generic renderers'
|
|
109
|
+
// behavior for every declared noun), not a placeholder line.
|
|
110
|
+
const out = (await verb("lookup").run({ name: ["apps"] }, rt)) as never;
|
|
111
|
+
expect(verb("lookup").output.formatters.llm(out)).not.toContain("Blocks");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
it("resolves the same tier by prefixed IRI — the candidates completion offers (D9-A)", async () => {
|
|
115
|
+
// Completion for the declared lookup serves index entity NAMES (prefixed
|
|
116
|
+
// IRIs); a candidate is only honest if the lookup resolves it.
|
|
117
|
+
const byIri = await lookup("ds:apps_lxd");
|
|
118
|
+
expect(byIri.name).toBe("apps/lxd");
|
|
119
|
+
expect(byIri.uri).toBe(`${DS}apps_lxd`);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("resolves a variadic batch in one call", async () => {
|
|
123
|
+
const out = (await verb("lookup").run(
|
|
124
|
+
{ name: ["global", "apps"] },
|
|
125
|
+
rt,
|
|
126
|
+
)) as LookupOutput;
|
|
127
|
+
expect(out.results.map((r) => r.name)).toEqual(["global", "apps"]);
|
|
128
|
+
});
|
|
129
|
+
|
|
130
|
+
it("a miss fails ENTITY_NOT_FOUND with a near-miss suggestion (B6 parity kept)", async () => {
|
|
131
|
+
// The bespoke lookup earned "did you mean?" in B6; the declared path keeps
|
|
132
|
+
// it — suggestions come from the same name list every declared lookup uses.
|
|
133
|
+
let caught: unknown;
|
|
134
|
+
try {
|
|
135
|
+
await verb("lookup").run({ name: ["apps/lxdd"] }, rt);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
caught = error;
|
|
138
|
+
}
|
|
139
|
+
expect(caught).toMatchObject({ code: "ENTITY_NOT_FOUND" });
|
|
140
|
+
expect((caught as { suggestions: string[] }).suggestions).toContain(
|
|
141
|
+
"apps/lxd",
|
|
142
|
+
);
|
|
143
|
+
});
|
|
144
|
+
});
|
|
@@ -14,6 +14,7 @@
|
|
|
14
14
|
*/
|
|
15
15
|
|
|
16
16
|
import type { Task } from "@canonical/task";
|
|
17
|
+
import { BIN_NAME } from "../../constants.js";
|
|
17
18
|
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
18
19
|
import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
|
|
19
20
|
import type { UpgradeData } from "./types.js";
|
|
@@ -21,13 +22,16 @@ import { upgradeFormatters } from "./upgrade.render.js";
|
|
|
21
22
|
|
|
22
23
|
const upgradeVerb: VerbSpec<Record<string, unknown>, UpgradeData> = {
|
|
23
24
|
path: ["upgrade"],
|
|
24
|
-
summary:
|
|
25
|
+
summary: `Upgrade the ${BIN_NAME} CLI to the latest version.`,
|
|
25
26
|
doc: "Checks the registry for the active channel's latest release and runs your package manager's global-update command. Preview the update before applying it.",
|
|
26
27
|
params: [],
|
|
27
28
|
output: { formatters: upgradeFormatters },
|
|
28
29
|
examples: [
|
|
29
|
-
{ cmd:
|
|
30
|
-
{
|
|
30
|
+
{ cmd: `${BIN_NAME} upgrade` },
|
|
31
|
+
{
|
|
32
|
+
cmd: `${BIN_NAME} upgrade --dry-run`,
|
|
33
|
+
note: "show the delta and the command",
|
|
34
|
+
},
|
|
31
35
|
],
|
|
32
36
|
capability: {
|
|
33
37
|
needsStore: false,
|