@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
|
@@ -11,6 +11,12 @@
|
|
|
11
11
|
* it more than once (the confirm-gate preview + the build), so all real reads
|
|
12
12
|
* belong in detection, never in `generate`.
|
|
13
13
|
*
|
|
14
|
+
* The `--scope` selection is threaded through detection AND step-offering so
|
|
15
|
+
* `listAvailableSteps` only offers steps whose band the scope runs, and the MCP
|
|
16
|
+
* step targets exactly the deduped files for that scope. Per-file narrowing is
|
|
17
|
+
* opt-in (Item 6): the "all" default never springs a per-file question — it
|
|
18
|
+
* configures every deduped file.
|
|
19
|
+
*
|
|
14
20
|
* This module carries NO static import of `@canonical/summon-core` VALUES or of
|
|
15
21
|
* React/Ink — it only `import type`s the generator shape and builds plain object
|
|
16
22
|
* literals over `@canonical/task`. It is itself reached only through the verb's
|
|
@@ -22,20 +28,29 @@ import type {
|
|
|
22
28
|
PromptDefinition,
|
|
23
29
|
} from "@canonical/summon-core";
|
|
24
30
|
import { sequence_, type Task, when } from "@canonical/task";
|
|
31
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
25
32
|
import type { PragmaRuntime } from "../../../kernel/runtime/types.js";
|
|
26
33
|
import { guardMissingBinary } from "../../shared/assertExecOk.js";
|
|
27
|
-
import type {
|
|
34
|
+
import type {
|
|
35
|
+
LspState,
|
|
36
|
+
ScopeSelection,
|
|
37
|
+
SetupMode,
|
|
38
|
+
SetupResult,
|
|
39
|
+
} from "../types.js";
|
|
28
40
|
import {
|
|
29
41
|
type CompletionsDetection,
|
|
30
42
|
composeCompletions,
|
|
31
43
|
detectCompletions,
|
|
32
44
|
} from "./setupCompletions.js";
|
|
33
|
-
import { composeLsp } from "./setupLsp.js";
|
|
45
|
+
import { composeLsp, detectLsp, type LspDetection } from "./setupLsp.js";
|
|
34
46
|
import {
|
|
35
47
|
composeMcp,
|
|
36
48
|
detectMcp,
|
|
37
49
|
type McpDetection,
|
|
38
50
|
mcpConfigured,
|
|
51
|
+
mcpGroupState,
|
|
52
|
+
mcpTargets,
|
|
53
|
+
selectedGroups,
|
|
39
54
|
} from "./setupMcp.js";
|
|
40
55
|
import {
|
|
41
56
|
composeSkills,
|
|
@@ -61,22 +76,29 @@ export interface SetupPlan {
|
|
|
61
76
|
/** The union of every step's detection, gathered up front for the run-all. */
|
|
62
77
|
interface SetupDetection {
|
|
63
78
|
readonly completions: CompletionsDetection;
|
|
79
|
+
readonly lsp: LspDetection;
|
|
64
80
|
readonly mcp: McpDetection;
|
|
65
81
|
readonly skills: SkillsDetection;
|
|
66
82
|
}
|
|
67
83
|
|
|
68
84
|
/** Read `answers.steps` (the run-all multiselect) as a string array. */
|
|
69
|
-
const
|
|
85
|
+
const readSteps = (answers: Record<string, unknown>): string[] =>
|
|
70
86
|
Array.isArray(answers.steps) ? (answers.steps as string[]) : [];
|
|
71
87
|
|
|
72
|
-
/** Read `answers.
|
|
73
|
-
const
|
|
88
|
+
/** Read `answers.mcpTargets` (selected file paths), falling back to ALL files. */
|
|
89
|
+
const resolveMcpPaths = (
|
|
74
90
|
d: McpDetection,
|
|
75
91
|
answers: Record<string, unknown>,
|
|
76
92
|
): string[] =>
|
|
77
|
-
Array.isArray(answers.
|
|
78
|
-
? (answers.
|
|
79
|
-
: d.
|
|
93
|
+
Array.isArray(answers.mcpTargets)
|
|
94
|
+
? (answers.mcpTargets as string[])
|
|
95
|
+
: d.groups.map((g) => g.path);
|
|
96
|
+
|
|
97
|
+
/** The groups the user chose (or all, under the "all" default). */
|
|
98
|
+
const selectChosenGroups = (
|
|
99
|
+
d: McpDetection,
|
|
100
|
+
answers: Record<string, unknown>,
|
|
101
|
+
) => selectedGroups(d, resolveMcpPaths(d, answers));
|
|
80
102
|
|
|
81
103
|
/**
|
|
82
104
|
* The LSP-install step, guarded at its use site. An absent `bunx` (no Bun on
|
|
@@ -87,41 +109,71 @@ const mcpSelection = (
|
|
|
87
109
|
* exec (no spawn) — and re-runnable (the guard is a `recover`, and `composeLsp`
|
|
88
110
|
* is combinator-built), so it survives `execute`'s double interpretation.
|
|
89
111
|
*/
|
|
90
|
-
const composeGuardedLsp = (rt: PragmaRuntime): Task<void> =>
|
|
112
|
+
const composeGuardedLsp = (rt: PragmaRuntime, state: LspState): Task<void> =>
|
|
91
113
|
guardMissingBinary(
|
|
92
114
|
"bunx",
|
|
93
115
|
{
|
|
94
116
|
message:
|
|
95
117
|
"Install Bun (https://bun.sh) to provide `bunx`, then run this again.",
|
|
96
118
|
},
|
|
97
|
-
composeLsp(rt.cwd),
|
|
119
|
+
composeLsp(rt.cwd, state),
|
|
98
120
|
);
|
|
99
121
|
|
|
100
122
|
/** Build a generator's `meta` (no stamping — the version is just header text). */
|
|
101
|
-
const
|
|
123
|
+
const buildMeta = (
|
|
102
124
|
rt: PragmaRuntime,
|
|
103
125
|
title: string,
|
|
104
126
|
): GeneratorDefinition["meta"] => ({
|
|
105
127
|
name: title,
|
|
106
128
|
displayName: title,
|
|
107
|
-
description:
|
|
129
|
+
description: `Configure ${BIN_NAME} for this project`,
|
|
108
130
|
version: rt.version,
|
|
109
131
|
});
|
|
110
132
|
|
|
111
|
-
/** The
|
|
112
|
-
const
|
|
133
|
+
/** The opt-in "customize which files" gate (Item 6) — defaults to false. */
|
|
134
|
+
const buildCustomizePrompt = (
|
|
135
|
+
when?: PromptDefinition["when"],
|
|
136
|
+
): PromptDefinition => ({
|
|
137
|
+
name: "customize",
|
|
138
|
+
type: "confirm",
|
|
139
|
+
message: `Customize which files ${BIN_NAME} configures?`,
|
|
140
|
+
default: false,
|
|
141
|
+
when,
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/** The suffix a group's prior state adds to its multiselect label. */
|
|
145
|
+
const mcpStateSuffix = (d: McpDetection, path: string): string => {
|
|
146
|
+
switch (mcpGroupState(d, path)) {
|
|
147
|
+
case "configured":
|
|
148
|
+
return " — already configured";
|
|
149
|
+
case "drifted":
|
|
150
|
+
return " — needs update";
|
|
151
|
+
default:
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* The per-file MCP multiselect — one row per deduped {@link TargetGroup} file.
|
|
158
|
+
* A row's label carries its prior state, and an already-`configured` file is
|
|
159
|
+
* DEFAULT-DESELECTED (so a re-run does not re-offer to rewrite what is already
|
|
160
|
+
* current); `absent`/`drifted` files stay selected by default.
|
|
161
|
+
*/
|
|
162
|
+
const buildMcpTargetsPrompt = (
|
|
113
163
|
d: McpDetection,
|
|
114
164
|
when?: PromptDefinition["when"],
|
|
115
165
|
): PromptDefinition => ({
|
|
116
|
-
name: "
|
|
166
|
+
name: "mcpTargets",
|
|
117
167
|
type: "multiselect",
|
|
118
|
-
message: "Configure MCP for which
|
|
168
|
+
message: "Configure MCP for which files?",
|
|
119
169
|
when,
|
|
120
|
-
choices: d.
|
|
121
|
-
label:
|
|
122
|
-
value:
|
|
170
|
+
choices: d.groups.map((g) => ({
|
|
171
|
+
label: `${g.path} — ${g.harnessNames.join(", ")} [${g.scope}]${mcpStateSuffix(d, g.path)}`,
|
|
172
|
+
value: g.path,
|
|
123
173
|
})),
|
|
124
|
-
default: d.
|
|
174
|
+
default: d.groups
|
|
175
|
+
.filter((g) => mcpGroupState(d, g.path) !== "configured")
|
|
176
|
+
.map((g) => g.path),
|
|
125
177
|
});
|
|
126
178
|
|
|
127
179
|
// =============================================================================
|
|
@@ -129,39 +181,55 @@ const mcpHarnessesPrompt = (
|
|
|
129
181
|
// =============================================================================
|
|
130
182
|
|
|
131
183
|
/** Gather EVERY step's detection up front (real reads), for the run-all. */
|
|
132
|
-
async function gatherDetection(
|
|
133
|
-
|
|
184
|
+
async function gatherDetection(
|
|
185
|
+
rt: PragmaRuntime,
|
|
186
|
+
scope: ScopeSelection,
|
|
187
|
+
): Promise<SetupDetection> {
|
|
188
|
+
const [completions, lsp, mcp, skills] = await Promise.all([
|
|
134
189
|
detectCompletions(rt.cwd),
|
|
135
|
-
|
|
190
|
+
detectLsp(rt.cwd),
|
|
191
|
+
detectMcp(rt, scope),
|
|
136
192
|
detectSkills(rt),
|
|
137
193
|
]);
|
|
138
|
-
return { completions, mcp, skills };
|
|
194
|
+
return { completions, lsp, mcp, skills };
|
|
139
195
|
}
|
|
140
196
|
|
|
141
197
|
/**
|
|
142
|
-
* The steps worth OFFERING, given detection
|
|
143
|
-
* the run-all degrades gracefully instead of
|
|
144
|
-
* EMPTY_RESULTS
|
|
145
|
-
*
|
|
198
|
+
* The steps worth OFFERING, given detection AND the scope selection. An
|
|
199
|
+
* undetectable step is omitted so the run-all degrades gracefully instead of
|
|
200
|
+
* throwing a mid-wizard EMPTY_RESULTS; a step whose band the scope does not run
|
|
201
|
+
* is omitted too (completions/lsp are global-band, skills are project-band, MCP
|
|
202
|
+
* spans both via its resolved groups). Threading `scope` here is what makes
|
|
203
|
+
* `setup --local` skip completions+lsp and `setup --global` skip skills — the
|
|
204
|
+
* MCP groups are already scoped by {@link detectMcp}.
|
|
205
|
+
*
|
|
206
|
+
* @param detected - Every step's up-front detection.
|
|
207
|
+
* @param scope - The resolved `--scope` selection (project/global/both).
|
|
208
|
+
* @returns The offerable steps, in composition/display order.
|
|
146
209
|
*/
|
|
147
|
-
function
|
|
210
|
+
function listAvailableSteps(
|
|
148
211
|
detected: SetupDetection,
|
|
212
|
+
scope: ScopeSelection,
|
|
149
213
|
): { label: string; value: StepId }[] {
|
|
214
|
+
const hasProject = scope !== "global";
|
|
215
|
+
const hasGlobal = scope !== "project";
|
|
150
216
|
const choices: { label: string; value: StepId }[] = [];
|
|
151
|
-
if (detected.completions.shell) {
|
|
217
|
+
if (detected.completions.shell && hasGlobal) {
|
|
152
218
|
choices.push({
|
|
153
219
|
label: `Shell completions (${detected.completions.shell})`,
|
|
154
220
|
value: "completions",
|
|
155
221
|
});
|
|
156
222
|
}
|
|
157
|
-
|
|
158
|
-
|
|
223
|
+
if (hasGlobal) {
|
|
224
|
+
choices.push({ label: "Terrazzo LSP extension", value: "lsp" });
|
|
225
|
+
}
|
|
226
|
+
if (detected.mcp.groups.length > 0) {
|
|
159
227
|
choices.push({
|
|
160
|
-
label: `MCP config (${detected.mcp.
|
|
228
|
+
label: `MCP config (${detected.mcp.groups.length} file(s))`,
|
|
161
229
|
value: "mcp",
|
|
162
230
|
});
|
|
163
231
|
}
|
|
164
|
-
if (detected.skills.available) {
|
|
232
|
+
if (detected.skills.available && hasProject) {
|
|
165
233
|
choices.push({
|
|
166
234
|
label: `Link skills (${detected.skills.skillCount})`,
|
|
167
235
|
value: "skills",
|
|
@@ -174,9 +242,18 @@ function availableSteps(
|
|
|
174
242
|
// Plan builders
|
|
175
243
|
// =============================================================================
|
|
176
244
|
|
|
177
|
-
/**
|
|
178
|
-
|
|
179
|
-
|
|
245
|
+
/**
|
|
246
|
+
* The run-all self-verb: a step multiselect + per-step composition. The resolved
|
|
247
|
+
* `scope` narrows BOTH the offered steps (via {@link listAvailableSteps}) and the
|
|
248
|
+
* MCP target groups (already scoped by {@link detectMcp}), so `--local` omits the
|
|
249
|
+
* global-band completions+lsp and `--global` omits the project-band skills.
|
|
250
|
+
*/
|
|
251
|
+
function buildRunAllPlan(
|
|
252
|
+
rt: PragmaRuntime,
|
|
253
|
+
detected: SetupDetection,
|
|
254
|
+
scope: ScopeSelection,
|
|
255
|
+
): SetupPlan {
|
|
256
|
+
const steps = listAvailableSteps(detected, scope);
|
|
180
257
|
const prompts: PromptDefinition[] = [
|
|
181
258
|
{
|
|
182
259
|
name: "steps",
|
|
@@ -186,11 +263,16 @@ function runAllPlan(rt: PragmaRuntime, detected: SetupDetection): SetupPlan {
|
|
|
186
263
|
default: steps.map((c) => c.value), // --yes / non-TTY ⇒ every step
|
|
187
264
|
},
|
|
188
265
|
];
|
|
189
|
-
|
|
266
|
+
// Item 6: per-file narrowing is opt-in. The "customize?" gate (default false)
|
|
267
|
+
// only surfaces when MCP is chosen and there is more than one file; the
|
|
268
|
+
// per-file multiselect only surfaces after an explicit yes — so the "all"
|
|
269
|
+
// default configures every deduped file without an extra question.
|
|
270
|
+
if (detected.mcp.groups.length > 0) {
|
|
190
271
|
prompts.push(
|
|
191
|
-
|
|
272
|
+
buildCustomizePrompt((a) => readSteps(a).includes("mcp")),
|
|
273
|
+
buildMcpTargetsPrompt(
|
|
192
274
|
detected.mcp,
|
|
193
|
-
(a) =>
|
|
275
|
+
(a) => a.customize === true && readSteps(a).includes("mcp"),
|
|
194
276
|
),
|
|
195
277
|
);
|
|
196
278
|
}
|
|
@@ -201,19 +283,19 @@ function runAllPlan(rt: PragmaRuntime, detected: SetupDetection): SetupPlan {
|
|
|
201
283
|
// reads already happened up front). Composing an unchosen step is harmless:
|
|
202
284
|
// `when(false, …)` discards the (side-effect-free) task it was handed.
|
|
203
285
|
const generator: GeneratorDefinition = {
|
|
204
|
-
meta:
|
|
286
|
+
meta: buildMeta(rt, `${BIN_NAME} setup`),
|
|
205
287
|
prompts,
|
|
206
288
|
generate: (answers) => {
|
|
207
|
-
const chosen =
|
|
289
|
+
const chosen = readSteps(answers);
|
|
208
290
|
return sequence_([
|
|
209
291
|
when(
|
|
210
292
|
chosen.includes("completions"),
|
|
211
293
|
composeCompletions(detected.completions),
|
|
212
294
|
),
|
|
213
|
-
when(chosen.includes("lsp"), composeGuardedLsp(rt)),
|
|
295
|
+
when(chosen.includes("lsp"), composeGuardedLsp(rt, detected.lsp.state)),
|
|
214
296
|
when(
|
|
215
297
|
chosen.includes("mcp"),
|
|
216
|
-
composeMcp(detected.mcp,
|
|
298
|
+
composeMcp(detected.mcp, selectChosenGroups(detected.mcp, answers)),
|
|
217
299
|
),
|
|
218
300
|
when(chosen.includes("skills"), composeSkills(detected.skills)),
|
|
219
301
|
]);
|
|
@@ -222,18 +304,18 @@ function runAllPlan(rt: PragmaRuntime, detected: SetupDetection): SetupPlan {
|
|
|
222
304
|
|
|
223
305
|
return {
|
|
224
306
|
generator,
|
|
225
|
-
toResult: (answers) => ({ kind: "all", steps:
|
|
307
|
+
toResult: (answers) => ({ kind: "all", steps: readSteps(answers) }),
|
|
226
308
|
};
|
|
227
309
|
}
|
|
228
310
|
|
|
229
311
|
/** A single-step generator (no run-all multiselect) for one sub-verb. */
|
|
230
|
-
function
|
|
312
|
+
function buildSingleStep(
|
|
231
313
|
rt: PragmaRuntime,
|
|
232
314
|
title: string,
|
|
233
315
|
prompts: PromptDefinition[],
|
|
234
316
|
generate: (answers: Record<string, unknown>) => Task<void>,
|
|
235
317
|
): GeneratorDefinition {
|
|
236
|
-
return { meta:
|
|
318
|
+
return { meta: buildMeta(rt, title), prompts, generate };
|
|
237
319
|
}
|
|
238
320
|
|
|
239
321
|
/**
|
|
@@ -244,6 +326,7 @@ function singleStep(
|
|
|
244
326
|
*
|
|
245
327
|
* @param rt - The per-invocation runtime.
|
|
246
328
|
* @param mode - The entry point (`all` or one sub-verb).
|
|
329
|
+
* @param scope - The resolved `--scope` selection (project/global/both).
|
|
247
330
|
* @returns The generator + result mapper.
|
|
248
331
|
* @throws PragmaError EMPTY_RESULTS for a direct `setup skills` with no skills.
|
|
249
332
|
* @note Impure — performs each step's real detection.
|
|
@@ -251,45 +334,65 @@ function singleStep(
|
|
|
251
334
|
export async function buildSetupPlan(
|
|
252
335
|
rt: PragmaRuntime,
|
|
253
336
|
mode: SetupMode,
|
|
337
|
+
scope: ScopeSelection,
|
|
254
338
|
): Promise<SetupPlan> {
|
|
255
339
|
switch (mode) {
|
|
256
340
|
case "all":
|
|
257
|
-
return
|
|
341
|
+
return buildRunAllPlan(rt, await gatherDetection(rt, scope), scope);
|
|
258
342
|
|
|
259
343
|
case "completions": {
|
|
260
344
|
const d = await detectCompletions(rt.cwd);
|
|
261
345
|
return {
|
|
262
|
-
generator:
|
|
263
|
-
|
|
346
|
+
generator: buildSingleStep(
|
|
347
|
+
rt,
|
|
348
|
+
`${BIN_NAME} setup completions`,
|
|
349
|
+
[],
|
|
350
|
+
() => composeCompletions(d),
|
|
264
351
|
),
|
|
265
352
|
toResult: () => ({
|
|
266
353
|
kind: "completions",
|
|
267
354
|
shell: d.shell,
|
|
268
355
|
path: d.path,
|
|
269
356
|
installed: d.shell !== null,
|
|
357
|
+
state: d.state,
|
|
270
358
|
}),
|
|
271
359
|
};
|
|
272
360
|
}
|
|
273
361
|
|
|
274
|
-
case "lsp":
|
|
362
|
+
case "lsp": {
|
|
363
|
+
const d = await detectLsp(rt.cwd);
|
|
275
364
|
return {
|
|
276
|
-
generator:
|
|
277
|
-
composeGuardedLsp(rt),
|
|
365
|
+
generator: buildSingleStep(rt, `${BIN_NAME} setup lsp`, [], () =>
|
|
366
|
+
composeGuardedLsp(rt, d.state),
|
|
278
367
|
),
|
|
279
|
-
toResult: () => ({ kind: "lsp" }),
|
|
368
|
+
toResult: () => ({ kind: "lsp", state: d.state }),
|
|
280
369
|
};
|
|
370
|
+
}
|
|
281
371
|
|
|
282
372
|
case "mcp": {
|
|
283
|
-
const d = await detectMcp(rt);
|
|
284
|
-
const prompts
|
|
373
|
+
const d = await detectMcp(rt, scope);
|
|
374
|
+
const prompts: PromptDefinition[] =
|
|
375
|
+
d.groups.length > 0
|
|
376
|
+
? [
|
|
377
|
+
buildCustomizePrompt(),
|
|
378
|
+
buildMcpTargetsPrompt(d, (a) => a.customize === true),
|
|
379
|
+
]
|
|
380
|
+
: [];
|
|
285
381
|
return {
|
|
286
|
-
generator:
|
|
287
|
-
|
|
382
|
+
generator: buildSingleStep(
|
|
383
|
+
rt,
|
|
384
|
+
`${BIN_NAME} setup mcp`,
|
|
385
|
+
prompts,
|
|
386
|
+
(answers) => composeMcp(d, selectChosenGroups(d, answers)),
|
|
288
387
|
),
|
|
289
|
-
toResult: (answers) =>
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
388
|
+
toResult: (answers) => {
|
|
389
|
+
const sel = selectChosenGroups(d, answers);
|
|
390
|
+
return {
|
|
391
|
+
kind: "mcp",
|
|
392
|
+
configured: mcpConfigured(sel),
|
|
393
|
+
targets: mcpTargets(d, sel),
|
|
394
|
+
};
|
|
395
|
+
},
|
|
293
396
|
};
|
|
294
397
|
}
|
|
295
398
|
|
|
@@ -297,7 +400,7 @@ export async function buildSetupPlan(
|
|
|
297
400
|
const d = await detectSkills(rt);
|
|
298
401
|
if (!d.available) throw skillsEmptyError();
|
|
299
402
|
return {
|
|
300
|
-
generator:
|
|
403
|
+
generator: buildSingleStep(rt, `${BIN_NAME} setup skills`, [], () =>
|
|
301
404
|
composeSkills(d),
|
|
302
405
|
),
|
|
303
406
|
toResult: () => ({ kind: "skills", result: toSkillsResult(d) }),
|
|
@@ -1,10 +1,13 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* `setup lsp` — ensure the Terrazzo LSP VS Code extension is installed.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* `
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* `detectLsp` probes for the extension FOR REAL up front — it runs
|
|
5
|
+
* `code --list-extensions` (via the `exec` seam, over `runTask`) and matches the
|
|
6
|
+
* `terrazzo` extension id — so a re-run of an already-installed extension is a
|
|
7
|
+
* true no-op and the recap says so, rather than always shelling out. When the
|
|
8
|
+
* `code` CLI is absent from PATH the state is `unknown` (we cannot enumerate,
|
|
9
|
+
* so the installer still runs). `composeLsp` holds the sole mutation, mocked
|
|
10
|
+
* under `--dry-run` and skipped when detection already found the extension.
|
|
8
11
|
*/
|
|
9
12
|
|
|
10
13
|
import {
|
|
@@ -17,18 +20,51 @@ import {
|
|
|
17
20
|
type Task,
|
|
18
21
|
} from "@canonical/task";
|
|
19
22
|
import { assertExecOk } from "../../shared/assertExecOk.js";
|
|
23
|
+
import type { LspState } from "../types.js";
|
|
20
24
|
|
|
21
|
-
/**
|
|
25
|
+
/** The VS Code CLI queried for the installed-extensions list. */
|
|
26
|
+
const VSCODE_CLI = "code";
|
|
27
|
+
|
|
28
|
+
/** The substring an installed Terrazzo extension id contains (publisher-agnostic). */
|
|
29
|
+
const TERRAZZO_EXTENSION_MATCH = "terrazzo";
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* The detected LSP state: whether the Terrazzo extension is already `installed`,
|
|
33
|
+
* `absent`, or `unknown` (the `code` CLI is not on PATH, so we cannot enumerate).
|
|
34
|
+
*/
|
|
22
35
|
export interface LspDetection {
|
|
23
36
|
readonly available: true;
|
|
37
|
+
readonly state: LspState;
|
|
24
38
|
}
|
|
25
39
|
|
|
26
40
|
/**
|
|
27
|
-
*
|
|
28
|
-
*
|
|
41
|
+
* Probe VS Code for the Terrazzo extension. Runs `code --list-extensions` and
|
|
42
|
+
* matches the `terrazzo` id; an absent `code` CLI (spawn ENOENT) or a nonzero
|
|
43
|
+
* exit yields `unknown` (we fall through to running the installer).
|
|
44
|
+
*
|
|
45
|
+
* @param cwd - The directory the probe runs in.
|
|
46
|
+
* @returns The detected {@link LspState}.
|
|
47
|
+
* @note Impure — spawns `code --list-extensions`.
|
|
29
48
|
*/
|
|
30
|
-
export function detectLsp(): LspDetection {
|
|
31
|
-
|
|
49
|
+
export async function detectLsp(cwd: string): Promise<LspDetection> {
|
|
50
|
+
const { runTask } = await import("@canonical/task/node");
|
|
51
|
+
let state: LspState = "unknown";
|
|
52
|
+
try {
|
|
53
|
+
const result = (await runTask(
|
|
54
|
+
exec(VSCODE_CLI, ["--list-extensions"], cwd),
|
|
55
|
+
)) as ExecResult;
|
|
56
|
+
if (result.exitCode === 0) {
|
|
57
|
+
const installed = result.stdout
|
|
58
|
+
.toLowerCase()
|
|
59
|
+
.includes(TERRAZZO_EXTENSION_MATCH);
|
|
60
|
+
state = installed ? "installed" : "absent";
|
|
61
|
+
}
|
|
62
|
+
} catch {
|
|
63
|
+
// `code` absent from PATH (ENOENT) or any spawn failure → we cannot tell;
|
|
64
|
+
// leave `unknown` so the installer still runs.
|
|
65
|
+
state = "unknown";
|
|
66
|
+
}
|
|
67
|
+
return { available: true, state };
|
|
32
68
|
}
|
|
33
69
|
|
|
34
70
|
/**
|
|
@@ -37,12 +73,20 @@ export function detectLsp(): LspDetection {
|
|
|
37
73
|
* Built from re-runnable combinators (NOT a single-use `gen`) because `execute`
|
|
38
74
|
* interprets the task twice (preview + perform). Under the dry-run preview the
|
|
39
75
|
* `exec` is MOCKED to `exitCode 0`, so `assertExecOk` passes there; a real run's
|
|
40
|
-
* nonzero exit still fails loudly.
|
|
76
|
+
* nonzero exit still fails loudly. When detection already found the extension
|
|
77
|
+
* `installed`, the install is SKIPPED — a re-run is a true no-op.
|
|
41
78
|
*
|
|
42
79
|
* @param cwd - The directory to run the installer in.
|
|
43
|
-
* @
|
|
80
|
+
* @param state - The prior state from {@link detectLsp} (defaults to `unknown`).
|
|
81
|
+
* @returns A Task that execs the installer (or reports already-installed).
|
|
44
82
|
*/
|
|
45
|
-
export function composeLsp(
|
|
83
|
+
export function composeLsp(
|
|
84
|
+
cwd: string,
|
|
85
|
+
state: LspState = "unknown",
|
|
86
|
+
): Task<void> {
|
|
87
|
+
if (state === "installed") {
|
|
88
|
+
return info("Terrazzo LSP VS Code extension already installed — skipped.");
|
|
89
|
+
}
|
|
46
90
|
return sequence_([
|
|
47
91
|
info("Ensuring the Terrazzo LSP VS Code extension is installed..."),
|
|
48
92
|
// The interpreter RESOLVES on a nonzero exit — a failed installer must fail
|