@canonical/pragma-cli 0.33.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 +7 -7
- package/dist/pragma +0 -0
- package/package.json +18 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +13 -4
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.verb.ts +31 -13
- package/src/capabilities/capabilities/catalog.ts +18 -10
- package/src/capabilities/capabilities/hints.ts +13 -19
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.verb.ts +8 -4
- 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 +0 -27
- package/src/capabilities/config/set.verb.ts +7 -3
- package/src/capabilities/config/show.render.test.ts +77 -5
- package/src/capabilities/config/show.render.ts +7 -12
- package/src/capabilities/config/show.verb.ts +4 -3
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.test.ts +78 -100
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +52 -15
- 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 +162 -15
- package/src/capabilities/create/create.verb.ts +140 -69
- 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/checkMcpConfigured.ts +7 -6
- 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/doctor.render.test.ts +6 -6
- package/src/capabilities/doctor/doctor.render.ts +2 -1
- package/src/capabilities/doctor/doctor.test.ts +37 -8
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +5 -5
- package/src/capabilities/doctor/types.ts +1 -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/verbs.ts +21 -21
- package/src/capabilities/prompt/prompt.test.ts +12 -6
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/resources/provider.ts +30 -11
- package/src/capabilities/resources/resources.test.ts +85 -29
- package/src/capabilities/setup/operations/setupGenerator.ts +16 -9
- package/src/capabilities/setup/operations/setupMcp.ts +2 -2
- package/src/capabilities/setup/operations/setupSkills.ts +3 -3
- package/src/capabilities/setup/setup.verb.ts +12 -5
- package/src/capabilities/setup/shell.ts +29 -3
- 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 +55 -50
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +113 -122
- package/src/capabilities/sources/sources.test.ts +111 -117
- package/src/capabilities/sources/status.render.test.ts +53 -24
- package/src/capabilities/sources/status.render.ts +24 -29
- 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 +33 -10
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +8 -6
- package/src/constants.ts +47 -21
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +5 -2
- 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 +117 -107
- package/src/kernel/completion/model.test.ts +1 -1
- package/src/kernel/completion/model.ts +11 -0
- 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 +1 -1
- 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/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 +9 -4
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +105 -105
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +11 -6
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +120 -5
- package/src/kernel/project/mcp/prompts/provider.ts +22 -8
- 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/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +1 -1
- package/src/kernel/runtime/graphpack/build.ts +29 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +46 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +52 -17
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -5
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -3
- 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 +205 -5
- 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 +4 -9
- 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 +61 -48
- 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 +78 -62
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +9 -8
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +22 -19
- package/src/kernel/spec/emitReference.test.ts +3 -3
- package/src/kernel/spec/emitReference.ts +144 -11
- package/src/kernel/spec/emitSurface.ts +9 -5
- 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 +8 -2
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/completion.test.ts +19 -6
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +1 -1
- 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 -75
- package/src/testing/behavioral/journeys.defaultPack.test.ts +82 -176
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +9 -6
- 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 +9 -4
- package/src/testing/behavioral/staleCommands.test.ts +10 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +7 -7
- package/src/testing/eval/cases/assembly.ts +13 -6
- package/src/testing/eval/cases/effectful.ts +25 -13
- package/src/testing/eval/cases/readNouns.ts +22 -20
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +45 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +6 -5
- package/src/testing/fixtures/graph/owlExport.ts +7 -5
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- 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.test.ts +0 -79
- package/src/capabilities/block/blockList.verb.ts +0 -163
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -166
- package/src/capabilities/block/parity.test.ts +0 -187
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- 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.test.ts +0 -72
- package/src/capabilities/tier/lookup.render.ts +0 -59
- package/src/capabilities/tier/lookup.test.ts +0 -83
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -87
- 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
|
@@ -12,10 +12,13 @@
|
|
|
12
12
|
* formatters).
|
|
13
13
|
*/
|
|
14
14
|
|
|
15
|
+
import { BIN_NAME } from "../../constants.js";
|
|
15
16
|
import { PragmaError } from "../../kernel/error/PragmaError.js";
|
|
17
|
+
import { cliRecovery } from "../../kernel/error/recovery.js";
|
|
16
18
|
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
17
19
|
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
18
20
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
21
|
+
import { VOCABULARY } from "../../kernel/vocabulary.js";
|
|
19
22
|
import {
|
|
20
23
|
promptListFormatters,
|
|
21
24
|
promptLookupFormatters,
|
|
@@ -34,10 +37,10 @@ const READ_CAPABILITY = {
|
|
|
34
37
|
const listVerb: VerbSpec<Record<string, unknown>, PromptListData> = {
|
|
35
38
|
path: ["prompt", "list"],
|
|
36
39
|
summary: "List the workflow prompt templates the design system offers.",
|
|
37
|
-
doc:
|
|
40
|
+
doc: `Browse the prompt entities the active graph declares (${VOCABULARY.prompt.type} in this distribution) — name, description, and argument names. This distribution's graph carries none today. The same prompts are offered natively over MCP prompts/list; use prompt_lookup for the full template body.`,
|
|
38
41
|
params: [],
|
|
39
42
|
output: { formatters: promptListFormatters },
|
|
40
|
-
examples: [{ cmd:
|
|
43
|
+
examples: [{ cmd: `${BIN_NAME} prompt list` }],
|
|
41
44
|
capability: READ_CAPABILITY,
|
|
42
45
|
run: (_params: Record<string, unknown>, rt: PragmaRuntime) =>
|
|
43
46
|
import("../../kernel/project/mcp/prompts/source.js").then(async (m) => {
|
|
@@ -55,19 +58,31 @@ const listVerb: VerbSpec<Record<string, unknown>, PromptListData> = {
|
|
|
55
58
|
const lookupVerb: VerbSpec<Record<string, unknown>, PromptLookupData> = {
|
|
56
59
|
path: ["prompt", "lookup"],
|
|
57
60
|
summary: "Show one workflow prompt template's body and arguments by name.",
|
|
58
|
-
doc: "Fetch a single
|
|
61
|
+
doc: "Fetch a single prompt entity's full template body (with {{arg}} placeholders) and its declared arguments. A prompt is addressed by its label; prompt_list names the ones the active graph carries.",
|
|
59
62
|
params: [
|
|
60
63
|
{
|
|
61
64
|
kind: "string",
|
|
62
65
|
name: "name",
|
|
63
|
-
doc: "The prompt name
|
|
66
|
+
doc: "The prompt name, as `prompt list` reports it.",
|
|
64
67
|
positional: true,
|
|
65
68
|
required: true,
|
|
66
|
-
|
|
69
|
+
// A prompt is addressed by its `rdfs:label`, which the index carries as
|
|
70
|
+
// `label`, and the index reader offers no substitute for a prompt that
|
|
71
|
+
// carries none. Not a total guarantee: `label` is the first of several
|
|
72
|
+
// label predicates the index accepts, so an entity labelled ONLY by
|
|
73
|
+
// `skos:prefLabel` would still complete without resolving. That gap is
|
|
74
|
+
// the index's label preference order, not this ref.
|
|
75
|
+
complete: {
|
|
76
|
+
kind: "names",
|
|
77
|
+
source: { from: "index", type: VOCABULARY.prompt.type, field: "label" },
|
|
78
|
+
},
|
|
67
79
|
},
|
|
68
80
|
],
|
|
69
81
|
output: { formatters: promptLookupFormatters },
|
|
70
|
-
|
|
82
|
+
// No example command: this distribution's graph declares no prompt, so any
|
|
83
|
+
// name quoted here exits 1 with ENTITY_NOT_FOUND — and this is the GENERATED
|
|
84
|
+
// reference, published as machine-derived truth.
|
|
85
|
+
examples: [],
|
|
71
86
|
capability: READ_CAPABILITY,
|
|
72
87
|
run: (params: Record<string, unknown>, rt: PragmaRuntime) =>
|
|
73
88
|
import("../../kernel/project/mcp/prompts/source.js").then(async (m) => {
|
|
@@ -77,11 +92,9 @@ const lookupVerb: VerbSpec<Record<string, unknown>, PromptLookupData> = {
|
|
|
77
92
|
const available = await m.readPrompts(rt);
|
|
78
93
|
throw PragmaError.notFound("prompt", name, {
|
|
79
94
|
suggestions: available.map((prompt) => prompt.name),
|
|
80
|
-
recovery: {
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
mcp: { tool: "prompt_list" },
|
|
84
|
-
},
|
|
95
|
+
recovery: cliRecovery("prompt list", "List available prompts.", {
|
|
96
|
+
tool: "prompt_list",
|
|
97
|
+
}),
|
|
85
98
|
});
|
|
86
99
|
}
|
|
87
100
|
return entry;
|
|
@@ -3,9 +3,11 @@
|
|
|
3
3
|
*
|
|
4
4
|
* Listing and autocomplete are STORELESS: they read the enriched `index.json`
|
|
5
5
|
* (readPackIndex) off disk, so the resource surface costs nothing until an agent
|
|
6
|
-
* reads one.
|
|
7
|
-
*
|
|
8
|
-
* the
|
|
6
|
+
* reads one. They read it for the pack the BOOT DECISION names, so the listing
|
|
7
|
+
* can never advertise entities whose reads then fail STORE_UNAVAILABLE. A read
|
|
8
|
+
* is STORE-BACKED through the SHARED {@link readEntity} — the same reader
|
|
9
|
+
* `graph inspect` uses — so a resource read and a `graph inspect` of the same
|
|
10
|
+
* URI return identical content (the mirror contract).
|
|
9
11
|
*
|
|
10
12
|
* Degradation (Risk4): on a missing or legacy (pre-v2) index the listing returns
|
|
11
13
|
* a single "run `pragma sources update`" hint — never a live re-index of the
|
|
@@ -16,12 +18,14 @@
|
|
|
16
18
|
* the `--help`/`__complete` fast path.
|
|
17
19
|
*/
|
|
18
20
|
|
|
21
|
+
import { BIN_NAME } from "../../constants.js";
|
|
19
22
|
import { readPackIndex } from "../../kernel/completion/entitySource.js";
|
|
20
23
|
import { asPragmaError } from "../../kernel/error/fromTaskError.js";
|
|
21
24
|
import { PragmaError } from "../../kernel/error/PragmaError.js";
|
|
22
25
|
import { mcpErrorFrom } from "../../kernel/project/mcp/mcpError.js";
|
|
23
26
|
import type { PackIndex } from "../../kernel/runtime/graphpack/types.js";
|
|
24
27
|
import { readEntity } from "../../kernel/runtime/readEntity.js";
|
|
28
|
+
import { resolveSources } from "../../kernel/runtime/resolveSources.js";
|
|
25
29
|
import type { McpResourceProvider } from "../../kernel/spec/types.js";
|
|
26
30
|
|
|
27
31
|
/** Max autocomplete suggestions returned for a partial URI. */
|
|
@@ -32,13 +36,25 @@ const CLASS_PRIORITY = 0.9;
|
|
|
32
36
|
/** MCP annotation priority for an individual (ABox) entry. */
|
|
33
37
|
const INDIVIDUAL_PRIORITY = 0.3;
|
|
34
38
|
|
|
35
|
-
/**
|
|
39
|
+
/**
|
|
40
|
+
* The `pragma:` URI scheme + the single reserved-expansion template variable.
|
|
41
|
+
*
|
|
42
|
+
* A LITERAL, and it must stay one until the wire identifiers move together.
|
|
43
|
+
* `surface/surface.v2.json` freezes `pragma:{+uri}` as protocol identity, and
|
|
44
|
+
* `buildResourceList` mints `pragma:<prefixed>` for every entry it lists —
|
|
45
|
+
* three writings of one decision. Deriving only this one made a fork advertise
|
|
46
|
+
* `recipes:{+uri}` over a list of 653 `pragma:` URIs: every resource the server
|
|
47
|
+
* offered became unreadable, and the readable form was never advertised.
|
|
48
|
+
* `resources.test.ts` pins the pair through {@link resourceProvider}'s declared
|
|
49
|
+
* surface; `identity.test.ts` masks this template out of its leak scan for the
|
|
50
|
+
* same reason.
|
|
51
|
+
*/
|
|
36
52
|
const URI_TEMPLATE = "pragma:{+uri}";
|
|
37
53
|
|
|
38
54
|
const TEMPLATE_DESCRIPTION =
|
|
39
55
|
"Knowledge-graph entities from the local pack. Read any entry by its prefixed " +
|
|
40
56
|
"URI (e.g. pragma:ds:button); autocomplete matches URI or label substrings. " +
|
|
41
|
-
|
|
57
|
+
`Content mirrors \`${BIN_NAME} graph inspect <uri>\`.`;
|
|
42
58
|
|
|
43
59
|
/** Agent-navigability annotations mirrored onto a listed resource. */
|
|
44
60
|
export interface ResourceAnnotations {
|
|
@@ -80,8 +96,7 @@ export function buildResourceList(
|
|
|
80
96
|
{
|
|
81
97
|
uri: "pragma:sources",
|
|
82
98
|
name: "Store not indexed",
|
|
83
|
-
description:
|
|
84
|
-
"No enriched entity index. Run `pragma sources update` to build it.",
|
|
99
|
+
description: `No enriched entity index. Run \`${BIN_NAME} sources update\` to build it.`,
|
|
85
100
|
mimeType: "application/json",
|
|
86
101
|
},
|
|
87
102
|
];
|
|
@@ -197,14 +212,18 @@ export const resourceProvider: McpResourceProvider = {
|
|
|
197
212
|
const { McpError, ErrorCode } = await import(
|
|
198
213
|
"@modelcontextprotocol/sdk/types.js"
|
|
199
214
|
);
|
|
215
|
+
// The index of the pack the boot decision names — `undefined` when the
|
|
216
|
+
// store is unavailable, which `buildResourceList` renders as the recovery
|
|
217
|
+
// entry rather than a catalogue of unreadable URIs.
|
|
218
|
+
const activeIndex = async (): Promise<PackIndex | undefined> =>
|
|
219
|
+
readPackIndex(resolveSources(await runtime.loadConfig(), runtime.cwd));
|
|
220
|
+
|
|
200
221
|
const template = new ResourceTemplate(URI_TEMPLATE, {
|
|
201
|
-
list: async () => ({
|
|
202
|
-
resources: buildResourceList(readPackIndex(runtime.cwd)),
|
|
203
|
-
}),
|
|
222
|
+
list: async () => ({ resources: buildResourceList(await activeIndex()) }),
|
|
204
223
|
complete: {
|
|
205
224
|
uri: async (value: string) =>
|
|
206
225
|
rankUriCompletions(
|
|
207
|
-
|
|
226
|
+
(await activeIndex())?.entities ?? [],
|
|
208
227
|
value,
|
|
209
228
|
COMPLETION_LIMIT,
|
|
210
229
|
),
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* degrades to a `pragma sources update` hint (never a live re-index).
|
|
8
8
|
*/
|
|
9
9
|
|
|
10
|
-
import { mkdtempSync } from "node:fs";
|
|
10
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
11
11
|
import { tmpdir } from "node:os";
|
|
12
12
|
import { join } from "node:path";
|
|
13
13
|
import { ErrorCode } from "@modelcontextprotocol/sdk/types.js";
|
|
@@ -16,13 +16,40 @@ import { readPackIndex } from "../../kernel/completion/entitySource.js";
|
|
|
16
16
|
import { verbKey } from "../../kernel/packs/uniqueness.js";
|
|
17
17
|
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
18
18
|
import type { PackIndex } from "../../kernel/runtime/graphpack/types.js";
|
|
19
|
-
import { writeLock } from "../../kernel/runtime/lock.js";
|
|
20
19
|
import type { InspectResult } from "../../kernel/runtime/readEntity.js";
|
|
21
20
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
22
21
|
import { TEST_FLAGS } from "../../testing/helpers/projectCli.js";
|
|
23
22
|
import { projectMcp } from "../../testing/helpers/projectMcp.js";
|
|
24
23
|
import { graphModule } from "../graph/index.js";
|
|
25
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
buildResourceList,
|
|
26
|
+
rankUriCompletions,
|
|
27
|
+
resourceProvider,
|
|
28
|
+
} from "./provider.js";
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Two entities of the committed embedded pack, chosen for what they PROVE: an
|
|
32
|
+
* abox individual and the tbox class it instantiates, so the schema-before-
|
|
33
|
+
* individual ordering has both sides. Named, not counted — the roster moves
|
|
34
|
+
* whenever the design system does.
|
|
35
|
+
*/
|
|
36
|
+
const BUTTON_NAME = "ds:global.component.button";
|
|
37
|
+
const BUTTON_URI = `pragma:${BUTTON_NAME}`;
|
|
38
|
+
const COMPONENT_URI = "pragma:ds:Component";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Blank out blank-node labels.
|
|
42
|
+
*
|
|
43
|
+
* Oxigraph re-mints blank-node identifiers every time it loads a store, so two
|
|
44
|
+
* independently booted sessions over the SAME pack name the same blank node
|
|
45
|
+
* differently. Those labels are store-local handles, not content — and this
|
|
46
|
+
* test's subject is that both surfaces return the same CONTENT for the same
|
|
47
|
+
* entity. (That the labels are not stable across processes is a real property
|
|
48
|
+
* of exposing blank nodes through `graph inspect`; the toy pack this suite used
|
|
49
|
+
* to run against simply had none.)
|
|
50
|
+
*/
|
|
51
|
+
const withoutBlankNodeLabels = (value: InspectResult): unknown =>
|
|
52
|
+
JSON.parse(JSON.stringify(value).replace(/_:[0-9a-f]+/g, "_:b"));
|
|
26
53
|
|
|
27
54
|
describe("resource listing (storeless, over the pack index)", () => {
|
|
28
55
|
it("degrades to a recovery entry on a missing or legacy index", () => {
|
|
@@ -37,15 +64,13 @@ describe("resource listing (storeless, over the pack index)", () => {
|
|
|
37
64
|
});
|
|
38
65
|
|
|
39
66
|
it("lists the enriched (v2) embedded index, schema entries first", () => {
|
|
40
|
-
const index = readPackIndex(
|
|
67
|
+
const index = readPackIndex({ kind: "embedded" });
|
|
41
68
|
expect(index?.version).toBe(2);
|
|
42
69
|
const resources = buildResourceList(index);
|
|
43
|
-
expect(resources.some((r) => r.uri ===
|
|
70
|
+
expect(resources.some((r) => r.uri === BUTTON_URI)).toBe(true);
|
|
44
71
|
// A class (tbox) sorts before an individual (abox).
|
|
45
|
-
const componentIdx = resources.findIndex(
|
|
46
|
-
|
|
47
|
-
);
|
|
48
|
-
const buttonIdx = resources.findIndex((r) => r.uri === "pragma:ex:Button");
|
|
72
|
+
const componentIdx = resources.findIndex((r) => r.uri === COMPONENT_URI);
|
|
73
|
+
const buttonIdx = resources.findIndex((r) => r.uri === BUTTON_URI);
|
|
49
74
|
expect(componentIdx).toBeLessThan(buttonIdx);
|
|
50
75
|
});
|
|
51
76
|
|
|
@@ -92,24 +117,46 @@ describe("resource listing (storeless, over the pack index)", () => {
|
|
|
92
117
|
});
|
|
93
118
|
|
|
94
119
|
it("enriches each entry with the _meta taxonomy (pragma/box + priority)", () => {
|
|
95
|
-
const resources = buildResourceList(readPackIndex(
|
|
96
|
-
const component = resources.find((r) => r.uri ===
|
|
97
|
-
const button = resources.find((r) => r.uri ===
|
|
120
|
+
const resources = buildResourceList(readPackIndex({ kind: "embedded" }));
|
|
121
|
+
const component = resources.find((r) => r.uri === COMPONENT_URI);
|
|
122
|
+
const button = resources.find((r) => r.uri === BUTTON_URI);
|
|
98
123
|
// A schema class: tbox, higher priority, carries its instance count.
|
|
99
124
|
expect(component?._meta?.["pragma/box"]).toBe("tbox");
|
|
100
125
|
expect(component?.annotations?.audience).toEqual(["assistant"]);
|
|
101
126
|
expect(component?.annotations?.priority).toBe(0.9);
|
|
102
|
-
expect(component?._meta?.["pragma/instanceCount"]).
|
|
127
|
+
expect(component?._meta?.["pragma/instanceCount"]).toBeGreaterThan(0);
|
|
103
128
|
// An individual: abox, lower priority, no instance count.
|
|
104
129
|
expect(button?._meta?.["pragma/box"]).toBe("abox");
|
|
105
130
|
expect(button?.annotations?.priority).toBe(0.3);
|
|
106
131
|
expect(button?._meta?.["pragma/instanceCount"]).toBeUndefined();
|
|
107
132
|
});
|
|
108
133
|
|
|
134
|
+
it("mints every URI in the scheme the declared template routes", () => {
|
|
135
|
+
// Two writings of one decision: the template `register` installs (and the
|
|
136
|
+
// covenant publishes as `mcpSurface.resources`) and the scheme every listed
|
|
137
|
+
// URI carries. Nothing compared them, and a half-derivation left a fork
|
|
138
|
+
// advertising `recipes:{+uri}` over a list of `pragma:` URIs — every
|
|
139
|
+
// resource offered was unreadable and the readable form was unadvertised.
|
|
140
|
+
// DERIVED from the declared surface, so this cannot be satisfied by two
|
|
141
|
+
// literals that agree today.
|
|
142
|
+
const declared = resourceProvider.surface?.templates?.[0];
|
|
143
|
+
const scheme = `${declared?.split(":")[0]}:`;
|
|
144
|
+
const listed = [
|
|
145
|
+
...buildResourceList(readPackIndex({ kind: "embedded" })),
|
|
146
|
+
...buildResourceList(undefined),
|
|
147
|
+
];
|
|
148
|
+
expect(listed.length).toBeGreaterThan(1);
|
|
149
|
+
expect(listed.filter((r) => !r.uri.startsWith(scheme))).toEqual([]);
|
|
150
|
+
});
|
|
151
|
+
|
|
109
152
|
it("ranks autocomplete over prefixed URI and label", () => {
|
|
110
|
-
const index = readPackIndex(
|
|
111
|
-
const hits = rankUriCompletions(
|
|
112
|
-
|
|
153
|
+
const index = readPackIndex({ kind: "embedded" });
|
|
154
|
+
const hits = rankUriCompletions(
|
|
155
|
+
index?.entities ?? [],
|
|
156
|
+
"global.component.button",
|
|
157
|
+
10,
|
|
158
|
+
);
|
|
159
|
+
expect(hits).toContain(BUTTON_NAME);
|
|
113
160
|
});
|
|
114
161
|
});
|
|
115
162
|
|
|
@@ -124,7 +171,7 @@ describe("resource surface over the server (embedded pack)", () => {
|
|
|
124
171
|
|
|
125
172
|
it("registers the {+uri} template and lists resources (not tools)", async () => {
|
|
126
173
|
const resources = await harness.listResources();
|
|
127
|
-
expect(resources.some((r) => r.uri ===
|
|
174
|
+
expect(resources.some((r) => r.uri === BUTTON_URI)).toBe(true);
|
|
128
175
|
// Resources are NOT tools — the graph module's tools (inspect + the PR6
|
|
129
176
|
// SPARQL escape hatch) are what appear in the tool surface.
|
|
130
177
|
const tools = await harness.listTools();
|
|
@@ -135,12 +182,12 @@ describe("resource surface over the server (embedded pack)", () => {
|
|
|
135
182
|
});
|
|
136
183
|
|
|
137
184
|
it("autocompletes a partial URI through the template", async () => {
|
|
138
|
-
const values = await harness.completeResource("
|
|
139
|
-
expect(values).toContain(
|
|
185
|
+
const values = await harness.completeResource("global.component.butt");
|
|
186
|
+
expect(values).toContain(BUTTON_NAME);
|
|
140
187
|
});
|
|
141
188
|
|
|
142
189
|
it("reads an entity, mirroring `graph inspect` content", async () => {
|
|
143
|
-
const read = await harness.readResource(
|
|
190
|
+
const read = await harness.readResource(BUTTON_URI);
|
|
144
191
|
expect(read.mimeType).toBe("application/json");
|
|
145
192
|
const fromResource = JSON.parse(read.text) as InspectResult;
|
|
146
193
|
|
|
@@ -150,21 +197,25 @@ describe("resource surface over the server (embedded pack)", () => {
|
|
|
150
197
|
(v) => verbKey(v.path) === "graph inspect",
|
|
151
198
|
) as VerbSpec;
|
|
152
199
|
const fromCli = (await inspect.run(
|
|
153
|
-
{ uri:
|
|
200
|
+
{ uri: BUTTON_NAME },
|
|
154
201
|
rt,
|
|
155
202
|
)) as InspectResult;
|
|
156
203
|
(await rt.store.get()).store.dispose();
|
|
157
204
|
|
|
158
|
-
expect(fromResource).toEqual(
|
|
159
|
-
|
|
205
|
+
expect(withoutBlankNodeLabels(fromResource)).toEqual(
|
|
206
|
+
withoutBlankNodeLabels(fromCli),
|
|
207
|
+
);
|
|
208
|
+
expect(fromResource.uri).toBe(
|
|
209
|
+
"https://ds.canonical.com/global.component.button",
|
|
210
|
+
);
|
|
160
211
|
});
|
|
161
212
|
|
|
162
213
|
it("rejects a read of an absent entity as InvalidParams carrying ENTITY_NOT_FOUND", async () => {
|
|
163
|
-
// The warm embedded pack resolves the `
|
|
214
|
+
// The warm embedded pack resolves the `ds:` prefix but holds no `Nonexistent`
|
|
164
215
|
// subject, so the read fails ENTITY_NOT_FOUND — the caller's fault, which
|
|
165
216
|
// `mcpErrorFrom` maps to JSON-RPC InvalidParams, NOT the InternalError a cold
|
|
166
217
|
// store (STORE_UNAVAILABLE) earns.
|
|
167
|
-
const error = await harness.readResource("pragma:
|
|
218
|
+
const error = await harness.readResource("pragma:ds:Nonexistent").then(
|
|
168
219
|
() => undefined,
|
|
169
220
|
(caught: unknown) => caught,
|
|
170
221
|
);
|
|
@@ -181,14 +232,19 @@ describe("resource surface over the server (embedded pack)", () => {
|
|
|
181
232
|
* D3 — a resource-read failure surfaces as a JSON-RPC error (the read analogue of
|
|
182
233
|
* a tool result's `isError`) carrying the recovery, NOT swallowed into a
|
|
183
234
|
* `text/plain` "success" body that drops the recovery and reads as though the
|
|
184
|
-
* entity itself were malformed. A
|
|
185
|
-
*
|
|
235
|
+
* entity itself were malformed. A project that declares its own packs and has
|
|
236
|
+
* never built them is the cold store — it must NOT be served the distribution's
|
|
237
|
+
* embedded graph; the read boots the store, so it refuses with
|
|
238
|
+
* STORE_UNAVAILABLE.
|
|
186
239
|
*/
|
|
187
240
|
describe("resource read — cold-store failure surfaces isError + recovery (D3)", () => {
|
|
188
241
|
let harness: Awaited<ReturnType<typeof projectMcp>>;
|
|
189
242
|
beforeAll(async () => {
|
|
190
243
|
const cwd = mkdtempSync(join(tmpdir(), "pragma-resource-cold-"));
|
|
191
|
-
|
|
244
|
+
writeFileSync(
|
|
245
|
+
join(cwd, "pragma.config.ts"),
|
|
246
|
+
`export default { packs: [{ name: "unbuilt", source: "file:///pragma-never-built" }] };\n`,
|
|
247
|
+
);
|
|
192
248
|
harness = await projectMcp([graphModule], cwd);
|
|
193
249
|
});
|
|
194
250
|
afterAll(async () => {
|
|
@@ -196,7 +252,7 @@ describe("resource read — cold-store failure surfaces isError + recovery (D3)"
|
|
|
196
252
|
});
|
|
197
253
|
|
|
198
254
|
it("rejects (does not return text/plain content) with the STORE_UNAVAILABLE recovery", async () => {
|
|
199
|
-
const error = await harness.readResource(
|
|
255
|
+
const error = await harness.readResource(BUTTON_URI).then(
|
|
200
256
|
() => undefined,
|
|
201
257
|
(caught: unknown) => caught,
|
|
202
258
|
);
|
|
@@ -28,6 +28,7 @@ import type {
|
|
|
28
28
|
PromptDefinition,
|
|
29
29
|
} from "@canonical/summon-core";
|
|
30
30
|
import { sequence_, type Task, when } from "@canonical/task";
|
|
31
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
31
32
|
import type { PragmaRuntime } from "../../../kernel/runtime/types.js";
|
|
32
33
|
import { guardMissingBinary } from "../../shared/assertExecOk.js";
|
|
33
34
|
import type {
|
|
@@ -125,7 +126,7 @@ const buildMeta = (
|
|
|
125
126
|
): GeneratorDefinition["meta"] => ({
|
|
126
127
|
name: title,
|
|
127
128
|
displayName: title,
|
|
128
|
-
description:
|
|
129
|
+
description: `Configure ${BIN_NAME} for this project`,
|
|
129
130
|
version: rt.version,
|
|
130
131
|
});
|
|
131
132
|
|
|
@@ -135,7 +136,7 @@ const buildCustomizePrompt = (
|
|
|
135
136
|
): PromptDefinition => ({
|
|
136
137
|
name: "customize",
|
|
137
138
|
type: "confirm",
|
|
138
|
-
message:
|
|
139
|
+
message: `Customize which files ${BIN_NAME} configures?`,
|
|
139
140
|
default: false,
|
|
140
141
|
when,
|
|
141
142
|
});
|
|
@@ -282,7 +283,7 @@ function buildRunAllPlan(
|
|
|
282
283
|
// reads already happened up front). Composing an unchosen step is harmless:
|
|
283
284
|
// `when(false, …)` discards the (side-effect-free) task it was handed.
|
|
284
285
|
const generator: GeneratorDefinition = {
|
|
285
|
-
meta: buildMeta(rt,
|
|
286
|
+
meta: buildMeta(rt, `${BIN_NAME} setup`),
|
|
286
287
|
prompts,
|
|
287
288
|
generate: (answers) => {
|
|
288
289
|
const chosen = readSteps(answers);
|
|
@@ -342,8 +343,11 @@ export async function buildSetupPlan(
|
|
|
342
343
|
case "completions": {
|
|
343
344
|
const d = await detectCompletions(rt.cwd);
|
|
344
345
|
return {
|
|
345
|
-
generator: buildSingleStep(
|
|
346
|
-
|
|
346
|
+
generator: buildSingleStep(
|
|
347
|
+
rt,
|
|
348
|
+
`${BIN_NAME} setup completions`,
|
|
349
|
+
[],
|
|
350
|
+
() => composeCompletions(d),
|
|
347
351
|
),
|
|
348
352
|
toResult: () => ({
|
|
349
353
|
kind: "completions",
|
|
@@ -358,7 +362,7 @@ export async function buildSetupPlan(
|
|
|
358
362
|
case "lsp": {
|
|
359
363
|
const d = await detectLsp(rt.cwd);
|
|
360
364
|
return {
|
|
361
|
-
generator: buildSingleStep(rt,
|
|
365
|
+
generator: buildSingleStep(rt, `${BIN_NAME} setup lsp`, [], () =>
|
|
362
366
|
composeGuardedLsp(rt, d.state),
|
|
363
367
|
),
|
|
364
368
|
toResult: () => ({ kind: "lsp", state: d.state }),
|
|
@@ -375,8 +379,11 @@ export async function buildSetupPlan(
|
|
|
375
379
|
]
|
|
376
380
|
: [];
|
|
377
381
|
return {
|
|
378
|
-
generator: buildSingleStep(
|
|
379
|
-
|
|
382
|
+
generator: buildSingleStep(
|
|
383
|
+
rt,
|
|
384
|
+
`${BIN_NAME} setup mcp`,
|
|
385
|
+
prompts,
|
|
386
|
+
(answers) => composeMcp(d, selectChosenGroups(d, answers)),
|
|
380
387
|
),
|
|
381
388
|
toResult: (answers) => {
|
|
382
389
|
const sel = selectChosenGroups(d, answers);
|
|
@@ -393,7 +400,7 @@ export async function buildSetupPlan(
|
|
|
393
400
|
const d = await detectSkills(rt);
|
|
394
401
|
if (!d.available) throw skillsEmptyError();
|
|
395
402
|
return {
|
|
396
|
-
generator: buildSingleStep(rt,
|
|
403
|
+
generator: buildSingleStep(rt, `${BIN_NAME} setup skills`, [], () =>
|
|
397
404
|
composeSkills(d),
|
|
398
405
|
),
|
|
399
406
|
toResult: () => ({ kind: "skills", result: toSkillsResult(d) }),
|
|
@@ -63,7 +63,7 @@ export interface McpDetection {
|
|
|
63
63
|
* @returns The pragma {@link McpServerConfig}.
|
|
64
64
|
*/
|
|
65
65
|
export function pragmaMcpEntry(cwd: string): McpServerConfig {
|
|
66
|
-
return { command:
|
|
66
|
+
return { command: MCP_SERVER_NAME, args: ["mcp"], cwd };
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
/**
|
|
@@ -228,7 +228,7 @@ export function composeMcp(
|
|
|
228
228
|
: "→";
|
|
229
229
|
tasks.push(
|
|
230
230
|
info(
|
|
231
|
-
`[${group.scope}]
|
|
231
|
+
`[${group.scope}] ${MCP_SERVER_NAME} MCP server ${verb} ${group.path} (${names})`,
|
|
232
232
|
),
|
|
233
233
|
);
|
|
234
234
|
}
|
|
@@ -19,7 +19,7 @@ import {
|
|
|
19
19
|
symlink,
|
|
20
20
|
type Task,
|
|
21
21
|
} from "@canonical/task";
|
|
22
|
-
import {
|
|
22
|
+
import { BIN_NAME } from "../../../constants.js";
|
|
23
23
|
import { PragmaError } from "../../../kernel/error/PragmaError.js";
|
|
24
24
|
import { cliRecovery } from "../../../kernel/error/recovery.js";
|
|
25
25
|
import type { PragmaRuntime } from "../../../kernel/runtime/types.js";
|
|
@@ -198,8 +198,8 @@ export function skillsEmptyError(): PragmaError {
|
|
|
198
198
|
return PragmaError.emptyResults("skill", {
|
|
199
199
|
message: "No skills found to link.",
|
|
200
200
|
recovery: cliRecovery(
|
|
201
|
-
|
|
202
|
-
|
|
201
|
+
"sources update",
|
|
202
|
+
`Add a design-system package that ships skills to your config, then run \`${BIN_NAME} sources update\` to install them.`,
|
|
203
203
|
),
|
|
204
204
|
});
|
|
205
205
|
}
|
|
@@ -21,6 +21,7 @@
|
|
|
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
27
|
import type {
|
|
@@ -207,20 +208,26 @@ const setupAllVerb = setupVerb(
|
|
|
207
208
|
params: SCOPE_PARAMS,
|
|
208
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.",
|
|
209
210
|
examples: [
|
|
210
|
-
{ cmd:
|
|
211
|
-
{ cmd: "pragma setup --dry-run", note: "preview every step's effects" },
|
|
211
|
+
{ cmd: `${BIN_NAME} setup` },
|
|
212
212
|
{
|
|
213
|
-
cmd:
|
|
213
|
+
cmd: `${BIN_NAME} setup --dry-run`,
|
|
214
|
+
note: "preview every step's effects",
|
|
215
|
+
},
|
|
216
|
+
{
|
|
217
|
+
cmd: `${BIN_NAME} setup --global`,
|
|
214
218
|
note: "configure only the user/home band",
|
|
215
219
|
},
|
|
216
|
-
{
|
|
220
|
+
{
|
|
221
|
+
cmd: `${BIN_NAME} setup mcp`,
|
|
222
|
+
note: "just the MCP server registration",
|
|
223
|
+
},
|
|
217
224
|
],
|
|
218
225
|
},
|
|
219
226
|
);
|
|
220
227
|
|
|
221
228
|
const mcpVerb = setupVerb(
|
|
222
229
|
["setup", "mcp"],
|
|
223
|
-
|
|
230
|
+
`Register the ${BIN_NAME} MCP server in detected AI harnesses.`,
|
|
224
231
|
"mcp",
|
|
225
232
|
SUB_CAPABILITY,
|
|
226
233
|
{ params: SCOPE_PARAMS },
|
|
@@ -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
|
|
|
@@ -13,6 +13,7 @@
|
|
|
13
13
|
import { readdirSync, readFileSync, statSync } from "node:fs";
|
|
14
14
|
import { homedir } from "node:os";
|
|
15
15
|
import { basename, join } from "node:path";
|
|
16
|
+
import { BIN_NAME } from "../../constants.js";
|
|
16
17
|
|
|
17
18
|
/** Parsed SKILL.md frontmatter. */
|
|
18
19
|
export interface SkillFrontmatter {
|
|
@@ -103,26 +104,31 @@ export function parseFrontmatter(content: string): SkillFrontmatter | null {
|
|
|
103
104
|
}
|
|
104
105
|
|
|
105
106
|
/**
|
|
106
|
-
* The installed-skills root: `$XDG_DATA_HOME
|
|
107
|
+
* The installed-skills root: `$XDG_DATA_HOME/<bin>/skills`. This is where
|
|
107
108
|
* `sources update` INSTALLS package-provided skills (U10) — a symlink per skill
|
|
108
109
|
* — and the second discovery root below reads them back. The single source of
|
|
109
110
|
* truth for both, so the install target and the discovery root can never drift.
|
|
111
|
+
*
|
|
112
|
+
* Namespaced by {@link BIN_NAME}, like config, state and cache: two
|
|
113
|
+
* distributions installed side by side owned ONE skills root, so a `recipes`
|
|
114
|
+
* build served skills a `pragma` build had installed and could not install its
|
|
115
|
+
* own without colliding.
|
|
110
116
|
*/
|
|
111
117
|
export function installedSkillsDir(): string {
|
|
112
118
|
const dataHome =
|
|
113
119
|
process.env.XDG_DATA_HOME ?? join(homedir(), ".local", "share");
|
|
114
|
-
return join(dataHome,
|
|
120
|
+
return join(dataHome, BIN_NAME, "skills");
|
|
115
121
|
}
|
|
116
122
|
|
|
117
123
|
/**
|
|
118
124
|
* The conventional roots skills are discovered from, in PRECEDENCE order: the
|
|
119
|
-
* project root (`<cwd
|
|
125
|
+
* project root (`<cwd>/.<bin>/skills`) FIRST so a project-local skill overrides
|
|
120
126
|
* an installed skill of the same name (`discoverSkills` dedups first-seen-wins),
|
|
121
|
-
* then installed skills under `$XDG_DATA_HOME
|
|
127
|
+
* then installed skills under `$XDG_DATA_HOME/<bin>/skills` (where package
|
|
122
128
|
* skills land on `sources update`).
|
|
123
129
|
*/
|
|
124
130
|
export function skillRoots(cwd: string): string[] {
|
|
125
|
-
return [join(cwd,
|
|
131
|
+
return [join(cwd, `.${BIN_NAME}`, "skills"), installedSkillsDir()];
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/** Immediate subdirectories of `root` (each a candidate skill folder). */
|