@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
|
@@ -185,7 +185,11 @@ describe("setup mcp — recap gate", () => {
|
|
|
185
185
|
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
186
186
|
const configPath = join(cwd, ".cursor", "mcp.json");
|
|
187
187
|
|
|
188
|
-
const { generator } = await buildSetupPlan(
|
|
188
|
+
const { generator } = await buildSetupPlan(
|
|
189
|
+
bootRuntime(FLAGS, cwd),
|
|
190
|
+
"mcp",
|
|
191
|
+
"both",
|
|
192
|
+
);
|
|
189
193
|
// A handler that declines EVERYTHING (incl. execute's "Proceed?" gate) must
|
|
190
194
|
// fail the run cleanly — the GENERATOR_CANCELLED code the boundary renders
|
|
191
195
|
// as a plain "Cancelled." — and write nothing.
|
|
@@ -197,6 +201,207 @@ describe("setup mcp — recap gate", () => {
|
|
|
197
201
|
});
|
|
198
202
|
});
|
|
199
203
|
|
|
204
|
+
describe("setup mcp — scope & dedup", () => {
|
|
205
|
+
// Isolate PATH so a `claude`/`codex` on the host PATH can't inject harnesses
|
|
206
|
+
// via a `process` signal — detection is driven only by the dirs each test makes.
|
|
207
|
+
let prevPath: string | undefined;
|
|
208
|
+
beforeEach(() => {
|
|
209
|
+
prevPath = process.env.PATH;
|
|
210
|
+
process.env.PATH = tmp("pragma-scope-path-");
|
|
211
|
+
});
|
|
212
|
+
afterEach(() => {
|
|
213
|
+
process.env.PATH = prevPath;
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("writes both VS Code and Cline keys into one .vscode/mcp.json (7a dedup)", async () => {
|
|
217
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
218
|
+
mkdirSync(join(cwd, ".vscode"), { recursive: true }); // VS Code (dir signal)
|
|
219
|
+
// Cline is detected by its extension (NOT the .vscode dir), so install a fake
|
|
220
|
+
// one under the isolated HOME — both harnesses then resolve to the SAME
|
|
221
|
+
// .vscode/mcp.json, which is what the two-level dedup write must handle. The
|
|
222
|
+
// extension is matched by its `package.json` manifest, so write one.
|
|
223
|
+
const clineExt = join(
|
|
224
|
+
process.env.HOME ?? "",
|
|
225
|
+
".vscode",
|
|
226
|
+
"extensions",
|
|
227
|
+
"saoudrizwan.claude-dev-1.0.0",
|
|
228
|
+
);
|
|
229
|
+
mkdirSync(clineExt, { recursive: true });
|
|
230
|
+
writeFileSync(join(clineExt, "package.json"), "{}");
|
|
231
|
+
const outcome = await executeVerb(
|
|
232
|
+
verbOf("mcp"),
|
|
233
|
+
{},
|
|
234
|
+
YES,
|
|
235
|
+
bootRuntime(FLAGS, cwd),
|
|
236
|
+
);
|
|
237
|
+
expect(outcome.exitCode).toBe(0);
|
|
238
|
+
const config = JSON.parse(
|
|
239
|
+
readFileSync(join(cwd, ".vscode", "mcp.json"), "utf-8"),
|
|
240
|
+
);
|
|
241
|
+
// VS Code writes under `servers`, Cline under `mcpServers` — the two-level
|
|
242
|
+
// dedup makes two writes to one file, each preserving the other's key.
|
|
243
|
+
expect(config.servers?.pragma?.command).toBe("pragma");
|
|
244
|
+
expect(config.mcpServers?.pragma?.command).toBe("pragma");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
it("--local writes the project config and skips a global-only harness", async () => {
|
|
248
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
249
|
+
const home = process.env.HOME ?? "";
|
|
250
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // project scope
|
|
251
|
+
mkdirSync(join(cwd, ".windsurf"), { recursive: true }); // global scope
|
|
252
|
+
await executeVerb(
|
|
253
|
+
verbOf("mcp"),
|
|
254
|
+
{ local: true },
|
|
255
|
+
YES,
|
|
256
|
+
bootRuntime(FLAGS, cwd),
|
|
257
|
+
);
|
|
258
|
+
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(true);
|
|
259
|
+
expect(
|
|
260
|
+
existsSync(join(home, ".codeium", "windsurf", "mcp_config.json")),
|
|
261
|
+
).toBe(false);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
it("--global writes a global harness's home config, skipping project harnesses", async () => {
|
|
265
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
266
|
+
const home = process.env.HOME ?? "";
|
|
267
|
+
mkdirSync(join(cwd, ".windsurf"), { recursive: true }); // global scope
|
|
268
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // project scope
|
|
269
|
+
await executeVerb(
|
|
270
|
+
verbOf("mcp"),
|
|
271
|
+
{ global: true },
|
|
272
|
+
YES,
|
|
273
|
+
bootRuntime(FLAGS, cwd),
|
|
274
|
+
);
|
|
275
|
+
const windsurfHome = JSON.parse(
|
|
276
|
+
readFileSync(
|
|
277
|
+
join(home, ".codeium", "windsurf", "mcp_config.json"),
|
|
278
|
+
"utf-8",
|
|
279
|
+
),
|
|
280
|
+
);
|
|
281
|
+
expect(windsurfHome.mcpServers?.pragma?.command).toBe("pragma");
|
|
282
|
+
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// The `--scope <enum>` values (project/global/both) resolve independently of
|
|
286
|
+
// the `--global`/`--local` boolean sugars above — a distinct resolveScope arm.
|
|
287
|
+
it("--scope project (the enum) writes the project config, skipping a global harness", async () => {
|
|
288
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
289
|
+
const home = process.env.HOME ?? "";
|
|
290
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // project scope
|
|
291
|
+
mkdirSync(join(cwd, ".windsurf"), { recursive: true }); // global scope
|
|
292
|
+
await executeVerb(
|
|
293
|
+
verbOf("mcp"),
|
|
294
|
+
{ scope: "project" },
|
|
295
|
+
YES,
|
|
296
|
+
bootRuntime(FLAGS, cwd),
|
|
297
|
+
);
|
|
298
|
+
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(true);
|
|
299
|
+
expect(
|
|
300
|
+
existsSync(join(home, ".codeium", "windsurf", "mcp_config.json")),
|
|
301
|
+
).toBe(false);
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
it("--scope global (the enum) writes the home config, skipping a project harness", async () => {
|
|
305
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
306
|
+
const home = process.env.HOME ?? "";
|
|
307
|
+
mkdirSync(join(cwd, ".windsurf"), { recursive: true }); // global scope
|
|
308
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // project scope
|
|
309
|
+
await executeVerb(
|
|
310
|
+
verbOf("mcp"),
|
|
311
|
+
{ scope: "global" },
|
|
312
|
+
YES,
|
|
313
|
+
bootRuntime(FLAGS, cwd),
|
|
314
|
+
);
|
|
315
|
+
expect(
|
|
316
|
+
existsSync(join(home, ".codeium", "windsurf", "mcp_config.json")),
|
|
317
|
+
).toBe(true);
|
|
318
|
+
expect(existsSync(join(cwd, ".cursor", "mcp.json"))).toBe(false);
|
|
319
|
+
});
|
|
320
|
+
});
|
|
321
|
+
|
|
322
|
+
describe("setup mcp — customize opt-in gate (Item 6)", () => {
|
|
323
|
+
it("gates the per-file multiselect behind an explicit customize=true", async () => {
|
|
324
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
325
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // ⇒ one MCP target group
|
|
326
|
+
const { generator } = await buildSetupPlan(
|
|
327
|
+
bootRuntime(FLAGS, cwd),
|
|
328
|
+
"mcp",
|
|
329
|
+
"both",
|
|
330
|
+
);
|
|
331
|
+
const customize = generator.prompts.find((p) => p.name === "customize");
|
|
332
|
+
const targets = generator.prompts.find((p) => p.name === "mcpTargets");
|
|
333
|
+
// The gate is an opt-in confirm that defaults to NOT customizing.
|
|
334
|
+
expect(customize?.type).toBe("confirm");
|
|
335
|
+
expect(customize?.default).toBe(false);
|
|
336
|
+
// The per-file multiselect only surfaces after an explicit yes — so the
|
|
337
|
+
// "all" default configures every deduped file without an extra question.
|
|
338
|
+
expect(targets?.when?.({})).toBe(false);
|
|
339
|
+
expect(targets?.when?.({ customize: false })).toBe(false);
|
|
340
|
+
expect(targets?.when?.({ customize: true })).toBe(true);
|
|
341
|
+
});
|
|
342
|
+
});
|
|
343
|
+
|
|
344
|
+
describe("setup (run-all wizard) — scope threading", () => {
|
|
345
|
+
// Isolate PATH so an ambient `claude`/`codex` can't inject a harness via a
|
|
346
|
+
// `process` signal — detection is driven only by the dirs each test makes.
|
|
347
|
+
let prevPath: string | undefined;
|
|
348
|
+
beforeEach(() => {
|
|
349
|
+
prevPath = process.env.PATH;
|
|
350
|
+
process.env.PATH = tmp("pragma-runall-path-");
|
|
351
|
+
});
|
|
352
|
+
afterEach(() => {
|
|
353
|
+
process.env.PATH = prevPath;
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
/** Seed a discoverable skill so the project-band skills step is offerable. */
|
|
357
|
+
const seedSkill = (cwd: string): void => {
|
|
358
|
+
const dir = join(cwd, ".pragma", "skills", "s");
|
|
359
|
+
mkdirSync(dir, { recursive: true });
|
|
360
|
+
writeFileSync(
|
|
361
|
+
join(dir, "SKILL.md"),
|
|
362
|
+
"---\nname: s\ndescription: A skill.\n---\n",
|
|
363
|
+
);
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
it("--local omits the global-band completions + lsp steps, keeping project steps", async () => {
|
|
367
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
368
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true }); // project-band MCP target
|
|
369
|
+
seedSkill(cwd); // project-band skills step
|
|
370
|
+
const outcome = await executeVerb(
|
|
371
|
+
setupSelfVerb,
|
|
372
|
+
{ local: true },
|
|
373
|
+
DRY,
|
|
374
|
+
bootRuntime(FLAGS, cwd),
|
|
375
|
+
);
|
|
376
|
+
expect(outcome.exitCode).toBe(0);
|
|
377
|
+
const plan = outcome.stdout ?? "";
|
|
378
|
+
// The global-band steps are gone under --local (the bug: they used to run).
|
|
379
|
+
expect(plan).not.toContain(completionScriptPath("zsh"));
|
|
380
|
+
expect(plan).not.toContain("terrazzo-lsp-extension");
|
|
381
|
+
// The project-band steps remain: MCP into .cursor, skills into .agents/skills.
|
|
382
|
+
expect(plan).toContain("mcp.json");
|
|
383
|
+
expect(plan).toContain(".agents/skills");
|
|
384
|
+
});
|
|
385
|
+
|
|
386
|
+
it("--global omits the project-band skills step, keeping global steps", async () => {
|
|
387
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
388
|
+
seedSkill(cwd); // WOULD be offered under the default `both`
|
|
389
|
+
const outcome = await executeVerb(
|
|
390
|
+
setupSelfVerb,
|
|
391
|
+
{ global: true },
|
|
392
|
+
DRY,
|
|
393
|
+
bootRuntime(FLAGS, cwd),
|
|
394
|
+
);
|
|
395
|
+
expect(outcome.exitCode).toBe(0);
|
|
396
|
+
const plan = outcome.stdout ?? "";
|
|
397
|
+
// The global-band steps are present under --global.
|
|
398
|
+
expect(plan).toContain(completionScriptPath("zsh"));
|
|
399
|
+
expect(plan).toContain("terrazzo-lsp-extension");
|
|
400
|
+
// The project-band skills step is gone (the bug: it used to run under --global).
|
|
401
|
+
expect(plan).not.toContain(".agents/skills");
|
|
402
|
+
});
|
|
403
|
+
});
|
|
404
|
+
|
|
200
405
|
describe("setup skills", () => {
|
|
201
406
|
it("empty skills raise EMPTY_RESULTS on the direct sub-verb", async () => {
|
|
202
407
|
const cwd = tmp("pragma-setup-proj-");
|
|
@@ -261,6 +466,152 @@ describe("setup lsp — missing-binary guard (bunx absent)", () => {
|
|
|
261
466
|
});
|
|
262
467
|
});
|
|
263
468
|
|
|
469
|
+
describe("setup — idempotent detection of already-present config", () => {
|
|
470
|
+
// Isolate PATH so an ambient harness can't inject via a `process` signal.
|
|
471
|
+
let prevPath: string | undefined;
|
|
472
|
+
beforeEach(() => {
|
|
473
|
+
prevPath = process.env.PATH;
|
|
474
|
+
process.env.PATH = tmp("pragma-idem-path-");
|
|
475
|
+
});
|
|
476
|
+
afterEach(() => {
|
|
477
|
+
process.env.PATH = prevPath;
|
|
478
|
+
});
|
|
479
|
+
|
|
480
|
+
it("mcp: a second run detects the already-configured file (byte-identical rewrite)", async () => {
|
|
481
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
482
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
483
|
+
const configPath = join(cwd, ".cursor", "mcp.json");
|
|
484
|
+
|
|
485
|
+
// First run writes the pragma entry.
|
|
486
|
+
await executeVerb(verbOf("mcp"), {}, YES, bootRuntime(FLAGS, cwd));
|
|
487
|
+
const firstBody = readFileSync(configPath, "utf-8");
|
|
488
|
+
|
|
489
|
+
// Detection now classifies the group `configured`.
|
|
490
|
+
const { toResult } = await buildSetupPlan(
|
|
491
|
+
bootRuntime(FLAGS, cwd),
|
|
492
|
+
"mcp",
|
|
493
|
+
"both",
|
|
494
|
+
);
|
|
495
|
+
const result = toResult({});
|
|
496
|
+
expect(result.kind).toBe("mcp");
|
|
497
|
+
if (result.kind === "mcp") {
|
|
498
|
+
expect(result.targets.at(0)?.state).toBe("configured");
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
// The wizard default-DESELECTS a configured target (so a plain re-run offers
|
|
502
|
+
// it unchecked) — proven on the generated multiselect's default.
|
|
503
|
+
const { generator } = await buildSetupPlan(
|
|
504
|
+
bootRuntime(FLAGS, cwd),
|
|
505
|
+
"mcp",
|
|
506
|
+
"both",
|
|
507
|
+
);
|
|
508
|
+
const multiselect = generator.prompts.find((p) => p.name === "mcpTargets");
|
|
509
|
+
expect(multiselect?.default).toEqual([]); // the only file is configured
|
|
510
|
+
expect(
|
|
511
|
+
(multiselect?.choices ?? []).some((c) =>
|
|
512
|
+
typeof c === "object" && "label" in c
|
|
513
|
+
? c.label.includes("already configured")
|
|
514
|
+
: false,
|
|
515
|
+
),
|
|
516
|
+
).toBe(true);
|
|
517
|
+
|
|
518
|
+
// A real second run is idempotent: the file stays byte-identical (the
|
|
519
|
+
// read-modify-write re-merges the SAME pragma entry).
|
|
520
|
+
await executeVerb(verbOf("mcp"), {}, YES, bootRuntime(FLAGS, cwd));
|
|
521
|
+
expect(readFileSync(configPath, "utf-8")).toBe(firstBody);
|
|
522
|
+
});
|
|
523
|
+
|
|
524
|
+
it("mcp: a drifted pragma entry (wrong cwd) reads as `drifted` and is updated", async () => {
|
|
525
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
526
|
+
mkdirSync(join(cwd, ".cursor"), { recursive: true });
|
|
527
|
+
const configPath = join(cwd, ".cursor", "mcp.json");
|
|
528
|
+
// Seed a stale pragma entry pointing at a different cwd.
|
|
529
|
+
writeFileSync(
|
|
530
|
+
configPath,
|
|
531
|
+
`${JSON.stringify(
|
|
532
|
+
{
|
|
533
|
+
mcpServers: {
|
|
534
|
+
pragma: { command: "pragma", args: ["mcp"], cwd: "/old" },
|
|
535
|
+
},
|
|
536
|
+
},
|
|
537
|
+
null,
|
|
538
|
+
2,
|
|
539
|
+
)}\n`,
|
|
540
|
+
);
|
|
541
|
+
|
|
542
|
+
const { toResult, generator } = await buildSetupPlan(
|
|
543
|
+
bootRuntime(FLAGS, cwd),
|
|
544
|
+
"mcp",
|
|
545
|
+
"both",
|
|
546
|
+
);
|
|
547
|
+
const result = toResult({});
|
|
548
|
+
if (result.kind === "mcp") {
|
|
549
|
+
expect(result.targets.at(0)?.state).toBe("drifted");
|
|
550
|
+
}
|
|
551
|
+
// A drifted target stays SELECTED by default (it needs the update).
|
|
552
|
+
const multiselect = generator.prompts.find((p) => p.name === "mcpTargets");
|
|
553
|
+
expect(multiselect?.default).toEqual([configPath]);
|
|
554
|
+
|
|
555
|
+
await executeVerb(verbOf("mcp"), {}, YES, bootRuntime(FLAGS, cwd));
|
|
556
|
+
const config = JSON.parse(readFileSync(configPath, "utf-8"));
|
|
557
|
+
expect(config.mcpServers.pragma.cwd).toBe(cwd); // updated to the real cwd
|
|
558
|
+
});
|
|
559
|
+
|
|
560
|
+
it("completions: a second run detects the up-to-date script (state=installed)", async () => {
|
|
561
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
562
|
+
const path = completionScriptPath("zsh");
|
|
563
|
+
// First install.
|
|
564
|
+
await executeVerb(completionsVerb, {}, YES, bootRuntime(FLAGS, cwd));
|
|
565
|
+
const firstBody = readFileSync(path, "utf-8");
|
|
566
|
+
|
|
567
|
+
// Detection now classifies the installed script `installed`.
|
|
568
|
+
const { toResult } = await buildSetupPlan(
|
|
569
|
+
bootRuntime(FLAGS, cwd),
|
|
570
|
+
"completions",
|
|
571
|
+
"both",
|
|
572
|
+
);
|
|
573
|
+
const result = toResult({});
|
|
574
|
+
if (result.kind === "completions") {
|
|
575
|
+
expect(result.state).toBe("installed");
|
|
576
|
+
}
|
|
577
|
+
|
|
578
|
+
// A real second run is idempotent — the byte-identical script survives.
|
|
579
|
+
await executeVerb(completionsVerb, {}, YES, bootRuntime(FLAGS, cwd));
|
|
580
|
+
expect(readFileSync(path, "utf-8")).toBe(firstBody);
|
|
581
|
+
});
|
|
582
|
+
|
|
583
|
+
it("completions: a stale script (different body) reads as `stale`", async () => {
|
|
584
|
+
const cwd = tmp("pragma-setup-proj-");
|
|
585
|
+
const path = completionScriptPath("zsh");
|
|
586
|
+
mkdirSync(join(path, ".."), { recursive: true });
|
|
587
|
+
writeFileSync(path, "# stale hand-edited completion\n");
|
|
588
|
+
const { toResult } = await buildSetupPlan(
|
|
589
|
+
bootRuntime(FLAGS, cwd),
|
|
590
|
+
"completions",
|
|
591
|
+
"both",
|
|
592
|
+
);
|
|
593
|
+
const result = toResult({});
|
|
594
|
+
if (result.kind === "completions") {
|
|
595
|
+
expect(result.state).toBe("stale");
|
|
596
|
+
}
|
|
597
|
+
});
|
|
598
|
+
|
|
599
|
+
it("lsp: reports `unknown` when the `code` CLI is absent from PATH", async () => {
|
|
600
|
+
// PATH is the isolated empty dir (beforeEach), so `code` is unresolvable:
|
|
601
|
+
// detection cannot enumerate and reports `unknown` (installer still runs).
|
|
602
|
+
const { toResult } = await buildSetupPlan(
|
|
603
|
+
bootRuntime(FLAGS, tmp("pragma-setup-proj-")),
|
|
604
|
+
"lsp",
|
|
605
|
+
"both",
|
|
606
|
+
);
|
|
607
|
+
const result = toResult({});
|
|
608
|
+
expect(result.kind).toBe("lsp");
|
|
609
|
+
if (result.kind === "lsp") {
|
|
610
|
+
expect(result.state).toBe("unknown");
|
|
611
|
+
}
|
|
612
|
+
});
|
|
613
|
+
});
|
|
614
|
+
|
|
264
615
|
describe("setup (run-all wizard)", () => {
|
|
265
616
|
it("--dry-run previews every DETECTED step (completions + lsp + mcp), writing nothing", async () => {
|
|
266
617
|
const cwd = tmp("pragma-setup-proj-");
|
|
@@ -21,11 +21,55 @@
|
|
|
21
21
|
|
|
22
22
|
import type { GeneratorResult } from "@canonical/summon-core";
|
|
23
23
|
import { $, gen, type Task } from "@canonical/task";
|
|
24
|
+
import { BIN_NAME } from "../../constants.js";
|
|
24
25
|
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
25
26
|
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
26
|
-
import type {
|
|
27
|
+
import type {
|
|
28
|
+
CapabilityModule,
|
|
29
|
+
ParamSpec,
|
|
30
|
+
VerbSpec,
|
|
31
|
+
} from "../../kernel/spec/types.js";
|
|
27
32
|
import { setupFormatters } from "./setup.render.js";
|
|
28
|
-
import type { SetupMode, SetupResult } from "./types.js";
|
|
33
|
+
import type { ScopeSelection, SetupMode, SetupResult } from "./types.js";
|
|
34
|
+
|
|
35
|
+
/**
|
|
36
|
+
* The `--scope` flag + its `--global`/`--local` boolean sugars, shared by the
|
|
37
|
+
* band-aware verbs (`setup`/`mcp`/`skills`). `completions`/`lsp` never carry
|
|
38
|
+
* them — they are single-band installers.
|
|
39
|
+
*/
|
|
40
|
+
const SCOPE_PARAMS: readonly ParamSpec[] = [
|
|
41
|
+
{
|
|
42
|
+
kind: "enum",
|
|
43
|
+
name: "scope",
|
|
44
|
+
doc: "Which config band(s) to configure: project, global, or both.",
|
|
45
|
+
values: ["project", "global", "both"],
|
|
46
|
+
default: "both",
|
|
47
|
+
},
|
|
48
|
+
{
|
|
49
|
+
kind: "boolean",
|
|
50
|
+
name: "global",
|
|
51
|
+
doc: "Shorthand for --scope global (configure the user/home band).",
|
|
52
|
+
},
|
|
53
|
+
{
|
|
54
|
+
kind: "boolean",
|
|
55
|
+
name: "local",
|
|
56
|
+
doc: "Shorthand for --scope project (configure the per-project band).",
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Resolve the `--scope`/`--global`/`--local` params into a single selection.
|
|
62
|
+
* The boolean sugars win over `--scope`; `--global` wins over `--local`.
|
|
63
|
+
*
|
|
64
|
+
* @param params - The coerced param bag.
|
|
65
|
+
* @returns The resolved scope selection (defaults to `both`).
|
|
66
|
+
*/
|
|
67
|
+
function resolveScope(params: Record<string, unknown>): ScopeSelection {
|
|
68
|
+
if (params.global === true) return "global";
|
|
69
|
+
if (params.local === true) return "project";
|
|
70
|
+
const scope = params.scope;
|
|
71
|
+
return scope === "global" || scope === "project" ? scope : "both";
|
|
72
|
+
}
|
|
29
73
|
|
|
30
74
|
/** The exposed-to-MCP capability the run-all self-verb declares. */
|
|
31
75
|
const SELF_CAPABILITY = {
|
|
@@ -76,6 +120,7 @@ const SUB_CAPABILITY = {
|
|
|
76
120
|
async function runSetup(
|
|
77
121
|
mode: SetupMode,
|
|
78
122
|
rt: PragmaRuntime,
|
|
123
|
+
scope: ScopeSelection,
|
|
79
124
|
): Promise<Task<SetupResult>> {
|
|
80
125
|
// Lazy dynamic imports (R1 lazy-React discipline): summon-core's barrel is
|
|
81
126
|
// React-free, and the non-TTY branch picks autoPrompt/mcpPrompt (never mounts
|
|
@@ -85,7 +130,7 @@ async function runSetup(
|
|
|
85
130
|
import("./operations/setupGenerator.js"),
|
|
86
131
|
]);
|
|
87
132
|
|
|
88
|
-
const { generator, toResult } = await ops.buildSetupPlan(rt, mode);
|
|
133
|
+
const { generator, toResult } = await ops.buildSetupPlan(rt, mode, scope);
|
|
89
134
|
|
|
90
135
|
const { isTTY, transport, yes, signal, abort } = rt.interaction ?? {
|
|
91
136
|
isTTY: false,
|
|
@@ -149,7 +194,8 @@ function setupVerb(
|
|
|
149
194
|
output: { formatters: setupFormatters },
|
|
150
195
|
capability,
|
|
151
196
|
...extras,
|
|
152
|
-
run: (
|
|
197
|
+
run: (params, rt) =>
|
|
198
|
+
runSetup(mode, rt, resolveScope(params)) as unknown as Task<SetupResult>,
|
|
153
199
|
};
|
|
154
200
|
}
|
|
155
201
|
|
|
@@ -159,20 +205,32 @@ const setupAllVerb = setupVerb(
|
|
|
159
205
|
"all",
|
|
160
206
|
SELF_CAPABILITY,
|
|
161
207
|
{
|
|
162
|
-
|
|
208
|
+
params: SCOPE_PARAMS,
|
|
209
|
+
doc: "Runs the shell-completions, LSP, MCP, and skills installers as a single wizard: pick the steps, review the recap, then apply. The scope option targets the project band, the user/home band, or both.",
|
|
163
210
|
examples: [
|
|
164
|
-
{ cmd:
|
|
165
|
-
{
|
|
166
|
-
|
|
211
|
+
{ cmd: `${BIN_NAME} setup` },
|
|
212
|
+
{
|
|
213
|
+
cmd: `${BIN_NAME} setup --dry-run`,
|
|
214
|
+
note: "preview every step's effects",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
cmd: `${BIN_NAME} setup --global`,
|
|
218
|
+
note: "configure only the user/home band",
|
|
219
|
+
},
|
|
220
|
+
{
|
|
221
|
+
cmd: `${BIN_NAME} setup mcp`,
|
|
222
|
+
note: "just the MCP server registration",
|
|
223
|
+
},
|
|
167
224
|
],
|
|
168
225
|
},
|
|
169
226
|
);
|
|
170
227
|
|
|
171
228
|
const mcpVerb = setupVerb(
|
|
172
229
|
["setup", "mcp"],
|
|
173
|
-
|
|
230
|
+
`Register the ${BIN_NAME} MCP server in detected AI harnesses.`,
|
|
174
231
|
"mcp",
|
|
175
232
|
SUB_CAPABILITY,
|
|
233
|
+
{ params: SCOPE_PARAMS },
|
|
176
234
|
);
|
|
177
235
|
|
|
178
236
|
const completionsVerb = setupVerb(
|
|
@@ -187,6 +245,7 @@ const skillsVerb = setupVerb(
|
|
|
187
245
|
"Symlink discovered skills into each AI harness.",
|
|
188
246
|
"skills",
|
|
189
247
|
SUB_CAPABILITY,
|
|
248
|
+
{ params: SCOPE_PARAMS },
|
|
190
249
|
);
|
|
191
250
|
|
|
192
251
|
const lspVerb = setupVerb(
|
|
@@ -9,6 +9,7 @@
|
|
|
9
9
|
|
|
10
10
|
import { homedir } from "node:os";
|
|
11
11
|
import { join } from "node:path";
|
|
12
|
+
import { BIN_NAME } from "../../constants.js";
|
|
12
13
|
|
|
13
14
|
/** The shells `setup completions` can install for. */
|
|
14
15
|
export type ShellId = "zsh" | "bash" | "fish";
|
|
@@ -34,6 +35,31 @@ export function detectShell(): ShellId | null {
|
|
|
34
35
|
/**
|
|
35
36
|
* The standard install path for a shell's completion script.
|
|
36
37
|
*
|
|
38
|
+
* The BASENAME is {@link BIN_NAME}, never a literal, because `emitScripts`
|
|
39
|
+
* derives the script's own `#compdef` / `complete -F` / `complete -c` from that
|
|
40
|
+
* same name, and for two of the three shells the basename decides whether the
|
|
41
|
+
* file is ever loaded. Measured against real shells with the distribution
|
|
42
|
+
* renamed to `widget9`:
|
|
43
|
+
*
|
|
44
|
+
* - **fish** autoloads `completions/<cmd>.fish` BY NAME. A correct `widget9`
|
|
45
|
+
* script in a file called `pragma.fish` is never loaded — TAB silently falls
|
|
46
|
+
* back to filenames. Verified: renaming that one file is the entire
|
|
47
|
+
* difference between candidates and no candidates.
|
|
48
|
+
* - **bash-completion** looks up `completions/<cmd>` by the command name the
|
|
49
|
+
* same way. Verified against bash-completion 2.11's `__load_completion`:
|
|
50
|
+
* with the file named `widget9`, `complete -p widget9` reports
|
|
51
|
+
* `complete -F _widget9 widget9`; with the identical file named `pragma`,
|
|
52
|
+
* `_widget9` is never defined and bash falls back to `_minimal`.
|
|
53
|
+
* - **zsh** does NOT work that way: `compinit` binds by the `#compdef` tag
|
|
54
|
+
* inside the file, so `~/.zfunc/_pragma` holding `#compdef widget9` does
|
|
55
|
+
* complete `widget9` (verified — `_comps[widget9]` resolves to `_pragma`).
|
|
56
|
+
* The basename still matters, for a different reason: it names the function
|
|
57
|
+
* zsh autoloads, and two distributions both writing `_pragma` would silently
|
|
58
|
+
* overwrite each other's completions.
|
|
59
|
+
*
|
|
60
|
+
* Either way `setup completions` and `doctor` report success, so nothing else
|
|
61
|
+
* would tell the user.
|
|
62
|
+
*
|
|
37
63
|
* @param shell - The target shell.
|
|
38
64
|
* @returns The absolute path the completion script is written to.
|
|
39
65
|
* @note Impure — reads the home directory.
|
|
@@ -42,7 +68,7 @@ export function completionScriptPath(shell: ShellId): string {
|
|
|
42
68
|
const home = homedir();
|
|
43
69
|
switch (shell) {
|
|
44
70
|
case "zsh":
|
|
45
|
-
return join(home, ".zfunc",
|
|
71
|
+
return join(home, ".zfunc", `_${BIN_NAME}`);
|
|
46
72
|
case "bash":
|
|
47
73
|
return join(
|
|
48
74
|
home,
|
|
@@ -50,10 +76,10 @@ export function completionScriptPath(shell: ShellId): string {
|
|
|
50
76
|
"share",
|
|
51
77
|
"bash-completion",
|
|
52
78
|
"completions",
|
|
53
|
-
|
|
79
|
+
BIN_NAME,
|
|
54
80
|
);
|
|
55
81
|
case "fish":
|
|
56
|
-
return join(home, ".config", "fish", "completions",
|
|
82
|
+
return join(home, ".config", "fish", "completions", `${BIN_NAME}.fish`);
|
|
57
83
|
}
|
|
58
84
|
}
|
|
59
85
|
|
|
@@ -11,6 +11,58 @@ import type { ShellId } from "./shell.js";
|
|
|
11
11
|
*/
|
|
12
12
|
export type SetupMode = "all" | "completions" | "lsp" | "mcp" | "skills";
|
|
13
13
|
|
|
14
|
+
/**
|
|
15
|
+
* One of the two config bands: the per-user/home `global` band or the per-repo
|
|
16
|
+
* `project` band. Mirrors `@canonical/harnesses`' `ScopeBand` structurally, but
|
|
17
|
+
* is redeclared here so this statically-reachable type module never pulls the
|
|
18
|
+
* harnesses runtime into the fast-path module graph (the lazy-dispatch invariant).
|
|
19
|
+
*/
|
|
20
|
+
export type ScopeBand = "project" | "global";
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* The resolved `--scope` selection: which config band(s) a run touches. `both`
|
|
24
|
+
* (the default) runs each detected harness's default band; `global`/`project`
|
|
25
|
+
* run only that band. Structurally mirrors the harnesses `ScopeSelection`.
|
|
26
|
+
*/
|
|
27
|
+
export type ScopeSelection = "project" | "global" | "both";
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* The prior state of an MCP target file, read up front by `detectMcp`:
|
|
31
|
+
* `absent` (no pragma entry yet), `configured` (a matching pragma entry already
|
|
32
|
+
* present in every write — a re-run skips it), or `drifted` (a pragma entry
|
|
33
|
+
* exists but differs, so a write updates it). Mirrors the skills step's
|
|
34
|
+
* created/skipped/replaced idempotency at the file grain.
|
|
35
|
+
*/
|
|
36
|
+
export type McpTargetState = "absent" | "configured" | "drifted";
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* One configured MCP target in a result: the file that was written, which band
|
|
40
|
+
* it belongs to, the harness name(s) that share it, and its prior
|
|
41
|
+
* {@link McpTargetState} (so the recap can report new vs updated vs unchanged).
|
|
42
|
+
*/
|
|
43
|
+
export interface ConfiguredTarget {
|
|
44
|
+
readonly name: string;
|
|
45
|
+
readonly band: ScopeBand;
|
|
46
|
+
readonly path: string;
|
|
47
|
+
readonly state: McpTargetState;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* The prior on-disk state of the shell-completion script, read up front by
|
|
52
|
+
* `detectCompletions`: `absent` (no script), `installed` (a byte-identical
|
|
53
|
+
* script is already present — a re-run skips it), or `stale` (a different
|
|
54
|
+
* script is present, so a write updates it).
|
|
55
|
+
*/
|
|
56
|
+
export type CompletionsState = "absent" | "installed" | "stale";
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* The detected state of the Terrazzo LSP VS Code extension, probed up front by
|
|
60
|
+
* `detectLsp`: `installed` (already present — a re-run skips it), `absent` (not
|
|
61
|
+
* present, so the installer runs), or `unknown` (the `code` CLI is not on PATH,
|
|
62
|
+
* so we cannot enumerate and the installer runs regardless).
|
|
63
|
+
*/
|
|
64
|
+
export type LspState = "installed" | "absent" | "unknown";
|
|
65
|
+
|
|
14
66
|
/** One symlink create/skip/replace action performed during skill setup. */
|
|
15
67
|
export interface SymlinkAction {
|
|
16
68
|
readonly skillName: string;
|
|
@@ -35,8 +87,13 @@ export type SetupResult =
|
|
|
35
87
|
readonly shell: ShellId | null;
|
|
36
88
|
readonly path: string | null;
|
|
37
89
|
readonly installed: boolean;
|
|
90
|
+
readonly state: CompletionsState;
|
|
91
|
+
}
|
|
92
|
+
| { readonly kind: "lsp"; readonly state: LspState }
|
|
93
|
+
| {
|
|
94
|
+
readonly kind: "mcp";
|
|
95
|
+
readonly configured: readonly string[];
|
|
96
|
+
readonly targets: readonly ConfiguredTarget[];
|
|
38
97
|
}
|
|
39
|
-
| { readonly kind: "lsp" }
|
|
40
|
-
| { readonly kind: "mcp"; readonly configured: readonly string[] }
|
|
41
98
|
| { readonly kind: "skills"; readonly result: SetupSkillsResult }
|
|
42
99
|
| { readonly kind: "all"; readonly steps: readonly string[] };
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* User-facing labels for the two config bands, shared by the setup recap
|
|
3
|
+
* ({@link ../setup/setup.render}) and the doctor report
|
|
4
|
+
* ({@link ../doctor/doctor.render}) so ONE consistent vocabulary appears across
|
|
5
|
+
* both. The pair matches the CLI grammar users already meet elsewhere: the
|
|
6
|
+
* `--global`/`--local` flags, the `--scope project|global|both` enum, config
|
|
7
|
+
* show's `[global]`/`[project]` origin markers, and the `setup` manifest's
|
|
8
|
+
* `[global]`/`[project]` lines. (Earlier these two surfaces said MACHINE/PROJECT
|
|
9
|
+
* — a third term for the same bands; this is the single source that unifies it.)
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
/** The user-facing display label for each of the two config bands. */
|
|
13
|
+
export const BAND_LABELS: Record<"project" | "global", string> = {
|
|
14
|
+
global: "Global",
|
|
15
|
+
project: "Project",
|
|
16
|
+
};
|