@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
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collect the `colophon` payload — the verb's run body (lazily imported).
|
|
3
3
|
*
|
|
4
|
-
* STORELESS: it
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* storeless-guarantee spy still sees `store.booted === false`.
|
|
4
|
+
* STORELESS: it resolves the EFFECTIVE capability modules exactly as a real
|
|
5
|
+
* command would — through `loadEffectiveModules`, so a story declared by a
|
|
6
|
+
* PACKAGE the active pack carries surfaces its colophon here too, not only a
|
|
7
|
+
* config-declared one. It never boots the triple store, so the
|
|
8
|
+
* storeless-guarantee spy still sees `store.booted === false`. The static
|
|
9
|
+
* import of the validated distribution config (`config/defaults.js`) is safe
|
|
10
|
+
* here because this module sits behind the verb's dynamic `import()` — it
|
|
11
|
+
* never lands on the `--help`/`__complete` fast path.
|
|
9
12
|
*
|
|
10
13
|
* The registry is reached through a RUNTIME dynamic `import("../index.js")`
|
|
11
14
|
* inside this async body — never a static import — so the static
|
|
@@ -13,44 +16,56 @@
|
|
|
13
16
|
* into a static import.
|
|
14
17
|
*/
|
|
15
18
|
|
|
19
|
+
import { BIN_NAME } from "../../constants.js";
|
|
20
|
+
import distribution from "../../kernel/config/defaults.js";
|
|
16
21
|
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
17
22
|
import type { CapabilityModule } from "../../kernel/spec/types.js";
|
|
18
|
-
import { PRAGMA_COLOPHON, PRAGMA_COLOPHON_SUMMARY } from "./pragmaColophon.js";
|
|
19
23
|
import type { ColophonData, ColophonSection } from "./types.js";
|
|
20
24
|
|
|
21
25
|
/**
|
|
22
26
|
* Assemble the colophon data for the current runtime.
|
|
23
27
|
*
|
|
28
|
+
* The toolchain section is DECLARED, not authored here: the distribution
|
|
29
|
+
* config's `colophon` field carries the narrative (and its condensed summary),
|
|
30
|
+
* and this collector renders whatever it declares under the distribution's own
|
|
31
|
+
* name — a fork edits its config, never this module. A distribution declaring
|
|
32
|
+
* no `colophon` gets no toolchain section: the packs' domain colophons stand
|
|
33
|
+
* alone. The `kind: "pragma"` value is a frozen JSON discriminant (wire
|
|
34
|
+
* compatibility), not the distribution's name.
|
|
35
|
+
*
|
|
24
36
|
* @param runtime - The per-invocation runtime.
|
|
25
|
-
* @returns The storeless colophon payload:
|
|
26
|
-
* pack/domain that declares a `colophon`.
|
|
37
|
+
* @returns The storeless colophon payload: the distribution's declared section,
|
|
38
|
+
* then each active pack/domain that declares a `colophon`.
|
|
27
39
|
* @note Impure — reads the config layers (never boots the store).
|
|
28
40
|
*/
|
|
29
41
|
export async function collectColophon(
|
|
30
42
|
runtime: PragmaRuntime,
|
|
31
43
|
): Promise<ColophonData> {
|
|
32
|
-
const sections: ColophonSection[] = [
|
|
33
|
-
|
|
44
|
+
const sections: ColophonSection[] = [];
|
|
45
|
+
if (distribution.colophon) {
|
|
46
|
+
sections.push({
|
|
34
47
|
kind: "pragma",
|
|
35
|
-
title:
|
|
36
|
-
markdown:
|
|
37
|
-
summary
|
|
48
|
+
title: BIN_NAME,
|
|
49
|
+
markdown: distribution.colophon.markdown,
|
|
50
|
+
...(distribution.colophon.summary === undefined
|
|
51
|
+
? {}
|
|
52
|
+
: { summary: distribution.colophon.summary }),
|
|
38
53
|
source: "built-in",
|
|
39
|
-
}
|
|
40
|
-
|
|
54
|
+
});
|
|
55
|
+
}
|
|
41
56
|
|
|
42
|
-
const {
|
|
57
|
+
const { loadEffectiveModules } = await import(
|
|
43
58
|
"../../kernel/packs/collect.js"
|
|
44
59
|
);
|
|
45
60
|
const { capabilities } = await import("../index.js");
|
|
46
61
|
|
|
47
62
|
let modules: readonly CapabilityModule[] = capabilities;
|
|
48
63
|
try {
|
|
49
|
-
|
|
50
|
-
modules = assembleEffectiveModules(capabilities, layers);
|
|
64
|
+
modules = (await loadEffectiveModules(capabilities, runtime.cwd)).modules;
|
|
51
65
|
} catch {
|
|
52
66
|
// A bad config story is surfaced by the real commands; the colophon degrades
|
|
53
|
-
// to the static
|
|
67
|
+
// to the static modules rather than failing the narrative. (A bad PACKAGE
|
|
68
|
+
// story never throws — it is dropped and reported by doctor.)
|
|
54
69
|
}
|
|
55
70
|
|
|
56
71
|
for (const module of modules) {
|
|
@@ -78,10 +78,37 @@ describe("colophon render golden — color-free (chalk.level = 0)", () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
/** Run `body` with stdout's `isTTY` forced to `value`, then restore it. */
|
|
82
|
+
function withStdoutTty(value: boolean | undefined, body: () => void): void {
|
|
83
|
+
const stream = process.stdout as { isTTY?: boolean };
|
|
84
|
+
const saved = stream.isTTY;
|
|
85
|
+
stream.isTTY = value;
|
|
86
|
+
try {
|
|
87
|
+
body();
|
|
88
|
+
} finally {
|
|
89
|
+
stream.isTTY = saved;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("colophon render — color ON (attended TTY)", () => {
|
|
94
|
+
it("plain emits ANSI escapes on a color-capable TTY", () => {
|
|
83
95
|
chalk.level = 1;
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
withStdoutTty(true, () => {
|
|
97
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: asserting the literal ESC byte is the point
|
|
98
|
+
expect(colophonFormatters.plain(FIXTURE)).toMatch(/\x1b\[/);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("colophon render — piped output is ANSI-free (F1)", () => {
|
|
104
|
+
it("plain emits ZERO ANSI off a TTY even when chalk reports color (CI/FORCE_COLOR)", () => {
|
|
105
|
+
// The CI leak: `supports-color` sets a non-zero level off a TTY under
|
|
106
|
+
// GITHUB_ACTIONS / FORCE_COLOR, so gating on `chalk.level` alone bled ANSI
|
|
107
|
+
// into `colophon --format plain | tee`. The isTTY gate closes it.
|
|
108
|
+
chalk.level = 3;
|
|
109
|
+
withStdoutTty(undefined, () => {
|
|
110
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: asserting NO ESC byte survives
|
|
111
|
+
expect(colophonFormatters.plain(FIXTURE)).not.toMatch(/\x1b\[/);
|
|
112
|
+
});
|
|
86
113
|
});
|
|
87
114
|
});
|
|
@@ -9,23 +9,32 @@
|
|
|
9
9
|
* (which carries no leading H1) is never double-titled.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
|
|
12
13
|
import type { Formatters } from "../../kernel/spec/types.js";
|
|
13
14
|
import { renderMarkdownToTerminal } from "./markdownTerminal.js";
|
|
14
15
|
import type { ColophonData, ColophonSection } from "./types.js";
|
|
15
16
|
|
|
16
17
|
/** Render one section (title as H1 + body) as styled terminal Markdown. */
|
|
17
|
-
function plainSection(section: ColophonSection): string {
|
|
18
|
-
return renderMarkdownToTerminal(
|
|
18
|
+
function plainSection(section: ColophonSection, style: RenderStyle): string {
|
|
19
|
+
return renderMarkdownToTerminal(
|
|
20
|
+
`# ${section.title}\n\n${section.markdown}`,
|
|
21
|
+
style,
|
|
22
|
+
);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
/** Render one section as condensed Markdown for `--llm` (summary preferred). */
|
|
25
|
+
/** Render one section as condensed Markdown for `--format llm` (summary preferred). */
|
|
22
26
|
function llmSection(section: ColophonSection): string {
|
|
23
27
|
return `## ${section.title}\n\n${section.summary ?? section.markdown}`;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export const colophonFormatters: Formatters<ColophonData> = {
|
|
27
31
|
plain(data) {
|
|
28
|
-
|
|
32
|
+
// Resolve the TTY decision ONCE (impure: reads stdout.isTTY + chalk level),
|
|
33
|
+
// then render every section through the same inert-off-a-TTY styler.
|
|
34
|
+
const style = defaultStyle();
|
|
35
|
+
return data.sections
|
|
36
|
+
.map((section) => plainSection(section, style))
|
|
37
|
+
.join("\n\n");
|
|
29
38
|
},
|
|
30
39
|
|
|
31
40
|
llm(data) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Pins the covenant-exact emitted slice (`{ v:"colophon", mcp:"colophon" }`),
|
|
5
5
|
* proves the collector is storeless and combines pragma's built-in section with
|
|
6
6
|
* the active pack's (the bundled `block` design-system colophon), exercises the
|
|
7
|
-
* three formatter modes + `--format
|
|
7
|
+
* three formatter modes + `--format` precedence, holds CLI-json ≡ MCP
|
|
8
8
|
* parity, and checks the pack-grammar accepts a `colophon` field (rejecting a
|
|
9
9
|
* non-string).
|
|
10
10
|
*/
|
|
@@ -47,7 +47,7 @@ const FLAGS: GlobalFlags = {
|
|
|
47
47
|
verbose: false,
|
|
48
48
|
};
|
|
49
49
|
const FLAGS_JSON: GlobalFlags = { ...FLAGS, format: "json" };
|
|
50
|
-
const FLAGS_LLM: GlobalFlags = { ...FLAGS, llm: true };
|
|
50
|
+
const FLAGS_LLM: GlobalFlags = { ...FLAGS, format: "llm", llm: true };
|
|
51
51
|
const NO_MUT = { dryRun: false, undo: false, yes: false };
|
|
52
52
|
|
|
53
53
|
/** A deterministic two-section fixture for pure formatter assertions. */
|
|
@@ -170,7 +170,7 @@ describe("colophon — formatter modes", () => {
|
|
|
170
170
|
expect((envelope.data as ColophonData).sections[0]?.kind).toBe("pragma");
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
it("--llm selects the condensed Markdown form", async () => {
|
|
173
|
+
it("--format llm selects the condensed Markdown form", async () => {
|
|
174
174
|
const outcome = await executeVerb(
|
|
175
175
|
colophonVerb,
|
|
176
176
|
{},
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* `infoVerb` / `doctorVerb` / `capabilitiesVerb`.
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
+
import { BIN_NAME } from "../../constants.js";
|
|
10
11
|
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
11
12
|
import type { CapabilityModule, VerbSpec } from "../../kernel/spec/types.js";
|
|
12
13
|
import { colophonFormatters } from "./colophon.render.js";
|
|
@@ -14,13 +15,19 @@ import type { ColophonData } from "./types.js";
|
|
|
14
15
|
|
|
15
16
|
const colophonVerb: VerbSpec<Record<string, unknown>, ColophonData> = {
|
|
16
17
|
path: ["colophon"],
|
|
17
|
-
summary:
|
|
18
|
-
doc:
|
|
18
|
+
summary: `Narrate how ${BIN_NAME} and the active domain are made.`,
|
|
19
|
+
doc: `Storeless — a colophon for the toolchain. Prints the story the distribution declares for itself (its config's \`colophon\`) followed by the active pack's domain colophon. Also available as a condensed Markdown narration for agents, or as a structured JSON projection of the sections.`,
|
|
19
20
|
params: [],
|
|
20
21
|
output: { formatters: colophonFormatters },
|
|
21
22
|
examples: [
|
|
22
|
-
{
|
|
23
|
-
|
|
23
|
+
{
|
|
24
|
+
cmd: `${BIN_NAME} colophon`,
|
|
25
|
+
note: "the toolchain + active domain story",
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
cmd: `${BIN_NAME} colophon --format llm`,
|
|
29
|
+
note: "condensed Markdown for agents",
|
|
30
|
+
},
|
|
24
31
|
],
|
|
25
32
|
capability: {
|
|
26
33
|
needsStore: false,
|
|
@@ -7,11 +7,14 @@
|
|
|
7
7
|
*
|
|
8
8
|
* Single consumer today (`pragma colophon`); a promotion candidate to
|
|
9
9
|
* `kernel/render/` if another verb ever wants the same helper. Determinism note:
|
|
10
|
-
* styling routes
|
|
11
|
-
*
|
|
10
|
+
* styling routes through the shared {@link RenderStyle} seam (color only on a
|
|
11
|
+
* color-capable TTY), so piped / redirected / CI output — where `supports-color`
|
|
12
|
+
* can report a level with no TTY — stays byte-for-byte the color-free structural
|
|
13
|
+
* transform, and a test can force either mode by passing an explicit style.
|
|
12
14
|
*/
|
|
13
15
|
|
|
14
16
|
import chalk from "chalk";
|
|
17
|
+
import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
|
|
15
18
|
|
|
16
19
|
/** Full ANSI escape sequences (`ESC [ … final-letter`) — colour/cursor codes. */
|
|
17
20
|
// biome-ignore lint/suspicious/noControlCharactersInRegex: deliberate control-char scrub
|
|
@@ -33,25 +36,35 @@ function stripControl(line: string): string {
|
|
|
33
36
|
return line.replace(ANSI_ESCAPE, "").replace(C0_CONTROLS, "");
|
|
34
37
|
}
|
|
35
38
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
39
|
+
/**
|
|
40
|
+
* Style inline `**bold**` and `` `code` `` spans within one line. The `**` / `` ` ``
|
|
41
|
+
* markers are ALWAYS consumed; off a TTY the styler is inert, so the markers drop
|
|
42
|
+
* with no color (byte-identical to the pre-seam `chalk.level = 0` behavior).
|
|
43
|
+
*/
|
|
44
|
+
function styleInline(text: string, style: RenderStyle): string {
|
|
38
45
|
return text
|
|
39
|
-
.replace(/\*\*([^*]+)\*\*/g, (_match, inner: string) =>
|
|
40
|
-
.replace(/`([^`]+)`/g, (_match, inner: string) =>
|
|
46
|
+
.replace(/\*\*([^*]+)\*\*/g, (_match, inner: string) => style.bold(inner))
|
|
47
|
+
.replace(/`([^`]+)`/g, (_match, inner: string) => style.cyan(inner));
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
/**
|
|
44
51
|
* Render a Markdown string to styled terminal text.
|
|
45
52
|
*
|
|
46
53
|
* @param markdown - The authored Markdown body.
|
|
47
|
-
* @
|
|
54
|
+
* @param style - The TTY styler; defaults to the process style. On a color-capable
|
|
55
|
+
* terminal headings/bullets/inline spans are tinted; off a TTY the styler is
|
|
56
|
+
* inert, so the output is byte-identical to the color-free structural form.
|
|
57
|
+
* @returns The styled terminal string (no trailing newline).
|
|
48
58
|
*/
|
|
49
|
-
export function renderMarkdownToTerminal(
|
|
59
|
+
export function renderMarkdownToTerminal(
|
|
60
|
+
markdown: string,
|
|
61
|
+
style: RenderStyle = defaultStyle(),
|
|
62
|
+
): string {
|
|
50
63
|
const lines: string[] = [];
|
|
51
64
|
let inFence = false;
|
|
52
65
|
|
|
53
66
|
for (const rawLine of markdown.split("\n")) {
|
|
54
|
-
// Scrub pack-authored terminal control codes BEFORE our own
|
|
67
|
+
// Scrub pack-authored terminal control codes BEFORE our own styling
|
|
55
68
|
// (colophon bodies are domain-as-data — never a channel for raw ANSI).
|
|
56
69
|
const raw = stripControl(rawLine);
|
|
57
70
|
// A fence toggles code mode; the ``` markers themselves are dropped.
|
|
@@ -60,25 +73,31 @@ export function renderMarkdownToTerminal(markdown: string): string {
|
|
|
60
73
|
continue;
|
|
61
74
|
}
|
|
62
75
|
if (inFence) {
|
|
63
|
-
lines.push(
|
|
76
|
+
lines.push(style.dim(` ${raw}`));
|
|
64
77
|
continue;
|
|
65
78
|
}
|
|
66
79
|
|
|
67
80
|
const heading = /^(#{1,6})\s+(.+)$/.exec(raw);
|
|
68
81
|
if (heading) {
|
|
69
82
|
const level = heading[1]?.length ?? 1;
|
|
70
|
-
const text = styleInline(heading[2] ?? "");
|
|
71
|
-
|
|
83
|
+
const text = styleInline(heading[2] ?? "", style);
|
|
84
|
+
// H1 earns bold+underline on a TTY; underline is outside the RenderStyle
|
|
85
|
+
// seam, so reach for chalk only when the styler is enabled.
|
|
86
|
+
lines.push(
|
|
87
|
+
level === 1 && style.enabled
|
|
88
|
+
? chalk.bold.underline(text)
|
|
89
|
+
: style.bold(text),
|
|
90
|
+
);
|
|
72
91
|
continue;
|
|
73
92
|
}
|
|
74
93
|
|
|
75
94
|
const bullet = /^\s*[-*]\s+(.+)$/.exec(raw);
|
|
76
95
|
if (bullet) {
|
|
77
|
-
lines.push(` ${
|
|
96
|
+
lines.push(` ${style.cyan("•")} ${styleInline(bullet[1] ?? "", style)}`);
|
|
78
97
|
continue;
|
|
79
98
|
}
|
|
80
99
|
|
|
81
|
-
lines.push(styleInline(raw));
|
|
100
|
+
lines.push(styleInline(raw, style));
|
|
82
101
|
}
|
|
83
102
|
|
|
84
103
|
return lines.join("\n");
|
|
@@ -2,28 +2,37 @@
|
|
|
2
2
|
* Data shapes for `pragma colophon`.
|
|
3
3
|
*
|
|
4
4
|
* A colophon (the printer's end-note on how a book is made) for the toolchain:
|
|
5
|
-
* one section narrating how
|
|
6
|
-
*
|
|
5
|
+
* one section narrating how the distribution itself is built — declared in its
|
|
6
|
+
* config, not authored in code — followed by one section per active pack/domain
|
|
7
|
+
* that declares a `colophon`. Storeless and read-only.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
|
-
/** One colophon section —
|
|
10
|
+
/** One colophon section — the distribution's own, or an active pack's story. */
|
|
10
11
|
export interface ColophonSection {
|
|
11
|
-
/**
|
|
12
|
+
/**
|
|
13
|
+
* Whether this is the distribution's declared section or an active pack's.
|
|
14
|
+
* `"pragma"` is a FROZEN JSON-visible discriminant (wire compatibility, like
|
|
15
|
+
* the `pragma:` resource scheme) — it does not track the distribution's
|
|
16
|
+
* name; a fork's own section still carries `kind: "pragma"`.
|
|
17
|
+
*/
|
|
12
18
|
readonly kind: "pragma" | "pack";
|
|
13
|
-
/** Section heading —
|
|
19
|
+
/** Section heading — the distribution's name, or the pack's noun/name. */
|
|
14
20
|
readonly title: string;
|
|
15
21
|
/**
|
|
16
22
|
* The authored Markdown BODY (no leading H1 — the renderer supplies the
|
|
17
23
|
* heading from {@link title}, so a section is never double-titled).
|
|
18
24
|
*/
|
|
19
25
|
readonly markdown: string;
|
|
20
|
-
/** Optional condensed body for `--llm` (
|
|
26
|
+
/** Optional condensed body for `--format llm` (the declaration's `summary`). */
|
|
21
27
|
readonly summary?: string;
|
|
22
28
|
/** Provenance: `"built-in"`, or `"pack:<name>"`. */
|
|
23
29
|
readonly source?: string;
|
|
24
30
|
}
|
|
25
31
|
|
|
26
|
-
/**
|
|
32
|
+
/**
|
|
33
|
+
* The full `colophon` payload: the distribution's declared section first (when
|
|
34
|
+
* its config declares one), then active packs'.
|
|
35
|
+
*/
|
|
27
36
|
export interface ColophonData {
|
|
28
37
|
readonly sections: readonly ColophonSection[];
|
|
29
38
|
}
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `config show` payload projection — declared story BODIES never ship.
|
|
3
|
+
*
|
|
4
|
+
* MCP returns the JSON formatter's output verbatim, so anything in this payload
|
|
5
|
+
* is tokens in every `config_show` tool call. The distribution declares five
|
|
6
|
+
* read stories on its packs, whose bodies are SPARQL; carried through, they take
|
|
7
|
+
* the payload from ~1.3 KB to ~11 KB — larger than `capabilities`, the discovery
|
|
8
|
+
* tool, for the least useful content in the surface.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { mkdtempSync } from "node:fs";
|
|
12
|
+
import { tmpdir } from "node:os";
|
|
13
|
+
import { join } from "node:path";
|
|
14
|
+
import { describe, expect, it } from "vitest";
|
|
15
|
+
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
16
|
+
import type { GlobalFlags } from "../../kernel/runtime/types.js";
|
|
17
|
+
import { collectConfigShow } from "./collectConfigShow.js";
|
|
18
|
+
|
|
19
|
+
const FLAGS: GlobalFlags = {
|
|
20
|
+
llm: false,
|
|
21
|
+
autoLlm: false,
|
|
22
|
+
format: "json",
|
|
23
|
+
verbose: false,
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
describe("config show payload", () => {
|
|
27
|
+
it("names the declared packs but carries no story bodies", async () => {
|
|
28
|
+
const cwd = mkdtempSync(join(tmpdir(), "pragma-config-show-"));
|
|
29
|
+
const data = await collectConfigShow(bootRuntime(FLAGS, cwd));
|
|
30
|
+
|
|
31
|
+
// The pack declarations themselves are still fully reported…
|
|
32
|
+
expect(
|
|
33
|
+
data.config.packs?.map((pack) =>
|
|
34
|
+
typeof pack === "string" ? pack : pack.name,
|
|
35
|
+
),
|
|
36
|
+
).toEqual([
|
|
37
|
+
"@canonical/design-system",
|
|
38
|
+
"@canonical/anatomy-dsl",
|
|
39
|
+
"@canonical/code-standards",
|
|
40
|
+
]);
|
|
41
|
+
for (const pack of data.config.packs ?? []) {
|
|
42
|
+
expect(
|
|
43
|
+
typeof pack === "string" ? undefined : pack.stories,
|
|
44
|
+
).toBeUndefined();
|
|
45
|
+
}
|
|
46
|
+
expect(data.config.stories).toBeUndefined();
|
|
47
|
+
|
|
48
|
+
// …and provenance stays honest: the payload still says where they came from.
|
|
49
|
+
expect(data.origins.packs).toBe("default");
|
|
50
|
+
expect(data.origins.stories).toBe("default");
|
|
51
|
+
|
|
52
|
+
// The sharp end: no query text reaches the agent surface.
|
|
53
|
+
const payload = JSON.stringify(data);
|
|
54
|
+
expect(payload).not.toContain("SELECT ?");
|
|
55
|
+
expect(payload.length).toBeLessThan(3000);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -1,15 +1,52 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Collect the `config show` payload — the verb's run body (lazily imported).
|
|
3
3
|
*
|
|
4
|
-
* Storeless: read the layered config and pass it through with its provenance
|
|
5
|
-
*
|
|
6
|
-
*
|
|
4
|
+
* Storeless: read the layered config and pass it through with its provenance,
|
|
5
|
+
* minus the declared story BODIES (see {@link withoutStoryBodies}). Dynamic-
|
|
6
|
+
* imported by the spec so building the tree never pulls the config reader (or
|
|
7
|
+
* zod) onto the fast path.
|
|
7
8
|
*/
|
|
8
9
|
|
|
9
10
|
import { readConfig } from "../../kernel/config/readConfig.js";
|
|
11
|
+
import type {
|
|
12
|
+
PackDeclaration,
|
|
13
|
+
PragmaConfig,
|
|
14
|
+
} from "../../kernel/config/types.js";
|
|
10
15
|
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
11
16
|
import type { ConfigShowData } from "./types.js";
|
|
12
17
|
|
|
18
|
+
/** A pack declaration with any declared story bodies dropped. */
|
|
19
|
+
function nameAndSourceOnly(pack: PackDeclaration): PackDeclaration {
|
|
20
|
+
if (typeof pack === "string") return pack;
|
|
21
|
+
return {
|
|
22
|
+
name: pack.name,
|
|
23
|
+
...(pack.source === undefined ? {} : { source: pack.source }),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* The effective config with declared story BODIES dropped — `packs` entries keep
|
|
29
|
+
* `{ name, source }`, and the top-level `stories` array is omitted.
|
|
30
|
+
*
|
|
31
|
+
* A PROJECTION of the payload, not a hiding of config: `origins` still reports
|
|
32
|
+
* where `packs` and `stories` came from, the plain and llm renders already print
|
|
33
|
+
* pack NAMES only, and `pragma capabilities` lists the verbs the stories
|
|
34
|
+
* produce. It exists because the bodies are SPARQL — the distribution's own five
|
|
35
|
+
* stories take `config show --format json` from 1.3 KB to 11 KB, and MCP returns
|
|
36
|
+
* the JSON formatter's output, so every `config_show` tool call would carry
|
|
37
|
+
* ~2.7k tokens of query text: the largest payload in the surface, for its least
|
|
38
|
+
* useful content.
|
|
39
|
+
*/
|
|
40
|
+
function withoutStoryBodies(config: PragmaConfig): PragmaConfig {
|
|
41
|
+
const { stories, ...rest } = config;
|
|
42
|
+
return {
|
|
43
|
+
...rest,
|
|
44
|
+
...(config.packs === undefined
|
|
45
|
+
? {}
|
|
46
|
+
: { packs: config.packs.map(nameAndSourceOnly) }),
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
|
|
13
50
|
/**
|
|
14
51
|
* Assemble the resolved config with provenance for the current runtime.
|
|
15
52
|
*
|
|
@@ -22,7 +59,7 @@ export async function collectConfigShow(
|
|
|
22
59
|
): Promise<ConfigShowData> {
|
|
23
60
|
const layers = await readConfig(runtime.cwd);
|
|
24
61
|
return {
|
|
25
|
-
config: layers.config,
|
|
62
|
+
config: withoutStoryBodies(layers.config),
|
|
26
63
|
origins: layers.origins,
|
|
27
64
|
...(layers.project.path ? { projectConfigPath: layers.project.path } : {}),
|
|
28
65
|
globalConfigPath: layers.global.path,
|
|
@@ -1,28 +1,19 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* The config field table — one declarative row per writable field
|
|
3
|
-
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
* mcp:"config_<field>" }` — one REQUIRED positional, NO flags, NO needsStore
|
|
9
|
-
* (config setters are storeless; the old store-backed `validateTier` is dropped
|
|
10
|
-
* — PARITY_GAP `config-tier-no-ontology-validation`). The factory produces
|
|
11
|
-
* exactly that shape; the `field.test.ts` deep-equals the golden slice.
|
|
2
|
+
* The config field table — one declarative row per writable field. Since AV-228
|
|
3
|
+
* B3 retired the per-field `config tier`/`channel`/`detail` verbs, this table no
|
|
4
|
+
* longer emits verbs of its own: it is the shared source of truth that drives
|
|
5
|
+
* `config set <key> <value>` (via `runSet` → `runField`), giving `set` its
|
|
6
|
+
* `<key>` enum, per-field reset sentinels, enum validation, and positional
|
|
7
|
+
* shaping for free.
|
|
12
8
|
*/
|
|
13
9
|
|
|
14
|
-
import type { Task } from "@canonical/task";
|
|
15
10
|
import { DETAIL_LEVELS } from "../../constants.js";
|
|
16
11
|
import type { RawConfig } from "../../kernel/config/types.js";
|
|
17
12
|
import { CHANNELS } from "../../kernel/config/types.js";
|
|
18
|
-
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
19
|
-
import type { ParamSpec, VerbSpec } from "../../kernel/spec/types.js";
|
|
20
|
-
import { configFieldFormatters } from "./field.render.js";
|
|
21
|
-
import type { ConfigFieldResult } from "./types.js";
|
|
22
13
|
|
|
23
14
|
/** One writable config field and how its single positional is shaped/validated. */
|
|
24
15
|
export interface ConfigFieldSpec {
|
|
25
|
-
/** The `RawConfig` key written (also the
|
|
16
|
+
/** The `RawConfig` key written (also the `config set` `<key>` enum member). */
|
|
26
17
|
readonly field: keyof RawConfig & ("tier" | "channel" | "detail");
|
|
27
18
|
/** The positional param NAME — its usage token is `<positional>` (covenant). */
|
|
28
19
|
readonly positional: string;
|
|
@@ -30,24 +21,16 @@ export interface ConfigFieldSpec {
|
|
|
30
21
|
readonly kind: "string" | "enum";
|
|
31
22
|
/** The allowed values for an enum field. */
|
|
32
23
|
readonly values?: readonly string[];
|
|
33
|
-
/** One-line verb summary. */
|
|
34
|
-
readonly summary: string;
|
|
35
|
-
/** Extended help doc. */
|
|
36
|
-
readonly doc: string;
|
|
37
24
|
/** Reserved values that REMOVE the field instead of setting it. */
|
|
38
25
|
readonly resetSentinel?: readonly string[];
|
|
39
|
-
/** Verb help examples. */
|
|
40
|
-
readonly examples: readonly { cmd: string; note?: string }[];
|
|
41
26
|
}
|
|
42
27
|
|
|
43
28
|
/**
|
|
44
29
|
* The three writable fields. Only `tier` (a free string with a meaningful
|
|
45
30
|
* "no value") carries reset sentinels; `channel`/`detail` reset by setting
|
|
46
|
-
* their default (`normal`/`standard`)
|
|
47
|
-
*
|
|
48
|
-
* `config
|
|
49
|
-
* each setter exactly ONE required positional, so a no-arg "print current" mode
|
|
50
|
-
* is impossible here (PARITY_GAP `config-field-query-via-show`).
|
|
31
|
+
* their default (`normal`/`standard`). The migration path for the retired
|
|
32
|
+
* field-verbs is `config set <field> <value>` (e.g. `config set tier apps/lxd`,
|
|
33
|
+
* `config set tier none` to clear it).
|
|
51
34
|
*/
|
|
52
35
|
export const CONFIG_FIELDS: readonly ConfigFieldSpec[] = [
|
|
53
36
|
{
|
|
@@ -55,98 +38,17 @@ export const CONFIG_FIELDS: readonly ConfigFieldSpec[] = [
|
|
|
55
38
|
positional: "path",
|
|
56
39
|
kind: "string",
|
|
57
40
|
resetSentinel: ["none", "default", "-"],
|
|
58
|
-
summary: "Set the active design-system tier.",
|
|
59
|
-
doc: "Writes the `tier` field to the global config. Pass `none`, `default`, or `-` to clear it. Written to the global layer only — project configs are authored by hand.",
|
|
60
|
-
examples: [
|
|
61
|
-
{ cmd: "pragma config tier apps/lxd", note: "scope reads to a tier" },
|
|
62
|
-
{ cmd: "pragma config tier none", note: "clear the tier" },
|
|
63
|
-
],
|
|
64
41
|
},
|
|
65
42
|
{
|
|
66
43
|
field: "channel",
|
|
67
44
|
positional: "name",
|
|
68
45
|
kind: "enum",
|
|
69
46
|
values: CHANNELS,
|
|
70
|
-
summary: "Set the release channel controlling component visibility.",
|
|
71
|
-
doc: "Writes the `channel` field to the global config. Reset by setting the default, `normal`.",
|
|
72
|
-
examples: [
|
|
73
|
-
{ cmd: "pragma config channel experimental" },
|
|
74
|
-
{ cmd: "pragma config channel normal", note: "back to the default" },
|
|
75
|
-
],
|
|
76
47
|
},
|
|
77
48
|
{
|
|
78
49
|
field: "detail",
|
|
79
50
|
positional: "level",
|
|
80
51
|
kind: "enum",
|
|
81
52
|
values: DETAIL_LEVELS,
|
|
82
|
-
summary: "Set the default progressive-disclosure level.",
|
|
83
|
-
doc: "Writes the `detail` field to the global config. Reset by setting the default, `standard`.",
|
|
84
|
-
examples: [
|
|
85
|
-
{ cmd: "pragma config detail detailed" },
|
|
86
|
-
{ cmd: "pragma config detail standard", note: "back to the default" },
|
|
87
|
-
],
|
|
88
53
|
},
|
|
89
54
|
];
|
|
90
|
-
|
|
91
|
-
/** The single required positional for a field, shaped per its `kind`. */
|
|
92
|
-
function fieldPositional(spec: ConfigFieldSpec): ParamSpec {
|
|
93
|
-
// Name the positional by its usage token (`<path>`/`<name>`/`<level>`) so the
|
|
94
|
-
// doc and the token agree (was "The tier value to write." for a `<path>`).
|
|
95
|
-
const doc = `The ${spec.field} ${spec.positional} to write.`;
|
|
96
|
-
if (spec.kind === "enum") {
|
|
97
|
-
return {
|
|
98
|
-
kind: "enum",
|
|
99
|
-
name: spec.positional,
|
|
100
|
-
doc,
|
|
101
|
-
values: spec.values ?? [],
|
|
102
|
-
required: true,
|
|
103
|
-
positional: true,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
return {
|
|
107
|
-
kind: "string",
|
|
108
|
-
name: spec.positional,
|
|
109
|
-
doc,
|
|
110
|
-
required: true,
|
|
111
|
-
positional: true,
|
|
112
|
-
};
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Build one storeless, mutating config setter from a field spec.
|
|
117
|
-
*
|
|
118
|
-
* `run` lazily imports the write body and presents `Promise<Task<R>>` through
|
|
119
|
-
* the `Task<R>` arm by the honest cast (the same seam `sources update` uses) —
|
|
120
|
-
* a literal `Promise<Task>` union arm would poison async read-verb inference.
|
|
121
|
-
*
|
|
122
|
-
* @param spec - The field-table row.
|
|
123
|
-
* @returns The verb spec for `config <field>`.
|
|
124
|
-
*/
|
|
125
|
-
export function fieldVerb(
|
|
126
|
-
spec: ConfigFieldSpec,
|
|
127
|
-
): VerbSpec<Record<string, unknown>, ConfigFieldResult> {
|
|
128
|
-
return {
|
|
129
|
-
path: ["config", spec.field],
|
|
130
|
-
summary: spec.summary,
|
|
131
|
-
doc: spec.doc,
|
|
132
|
-
params: [fieldPositional(spec)],
|
|
133
|
-
output: { formatters: configFieldFormatters },
|
|
134
|
-
examples: [...spec.examples],
|
|
135
|
-
capability: {
|
|
136
|
-
needsStore: false,
|
|
137
|
-
mutates: true,
|
|
138
|
-
mcp: {
|
|
139
|
-
expose: true,
|
|
140
|
-
annotations: { readOnlyHint: false, openWorldHint: false },
|
|
141
|
-
},
|
|
142
|
-
},
|
|
143
|
-
run: (params) =>
|
|
144
|
-
import("./runField.js").then((m) =>
|
|
145
|
-
m.runField(spec, params),
|
|
146
|
-
) as unknown as Task<ConfigFieldResult>,
|
|
147
|
-
};
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
/** The three config setters, in covenant order (tier, channel, detail). */
|
|
151
|
-
export const configFieldVerbs: VerbSpec[] =
|
|
152
|
-
CONFIG_FIELDS.map(fieldVerb).map(asVerb);
|