@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
package/src/constants.ts
CHANGED
|
@@ -1,35 +1,65 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Package-level constants
|
|
2
|
+
* Package-level constants — the distribution's identity, projected.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
4
|
+
* The bin name, MCP server name, description and issues URL are NOT authored
|
|
5
|
+
* here: they are read from the distribution config (`pragma.conf.ts`), so a fork
|
|
6
|
+
* changes values, not code. The import is static because the surfaces that need
|
|
7
|
+
* identity — `--help`, `__complete`, first-run onboarding, the MCP handshake —
|
|
8
|
+
* all run before or without the config layer. That is safe precisely because
|
|
9
|
+
* `pragma.conf.ts` is inert data: a plain object literal whose only import is a
|
|
10
|
+
* type. It reaches no zod schema, no fs, no evaluator. `capabilities/lazy.test.ts`
|
|
11
|
+
* pins that boundary, and `src/identity.test.ts` proves the projection.
|
|
12
|
+
*
|
|
13
|
+
* Also holds the stable cross-cutting enums: output formats here, and the
|
|
14
|
+
* detail levels re-exported from `kernel/config/types.ts`, where the config
|
|
15
|
+
* validator closes the `detail` field over them.
|
|
8
16
|
*/
|
|
9
17
|
|
|
10
18
|
import pkg from "../package.json" with { type: "json" };
|
|
19
|
+
import identity from "../pragma.conf.js";
|
|
20
|
+
import { DETAIL_LEVELS, type DetailLevel } from "./kernel/config/types.js";
|
|
11
21
|
|
|
12
|
-
/** CLI binary name
|
|
13
|
-
const BIN_NAME =
|
|
22
|
+
/** CLI binary name — the distribution's `name`. */
|
|
23
|
+
const BIN_NAME = identity.name;
|
|
14
24
|
|
|
15
25
|
/**
|
|
16
|
-
* MCP server identity —
|
|
17
|
-
*
|
|
26
|
+
* MCP server identity — the same name as the CLI bin, so agents already pointed
|
|
27
|
+
* at the server resolve it unchanged. Also the `serverInfo.name` the server
|
|
28
|
+
* introduces itself with on the wire (paired with {@link VERSION} by
|
|
29
|
+
* `buildServer`), so a peer reads the distribution's declared name, never a
|
|
30
|
+
* hardcoded one.
|
|
18
31
|
*/
|
|
19
|
-
const MCP_SERVER_NAME =
|
|
32
|
+
const MCP_SERVER_NAME = identity.name;
|
|
20
33
|
|
|
21
34
|
/** One-line program description for help and server metadata. */
|
|
22
|
-
const PROGRAM_DESCRIPTION =
|
|
35
|
+
const PROGRAM_DESCRIPTION = identity.help;
|
|
36
|
+
|
|
37
|
+
/** Where the distribution asks users to file issues and feedback. */
|
|
38
|
+
const ISSUES_URL = identity.issuesUrl;
|
|
23
39
|
|
|
24
40
|
/** Semver version string read from package.json. */
|
|
25
41
|
const VERSION: string = pkg.version;
|
|
26
42
|
|
|
27
43
|
/**
|
|
28
|
-
*
|
|
29
|
-
*
|
|
30
|
-
*
|
|
44
|
+
* The project config filename the walker looks for and every surface that
|
|
45
|
+
* quotes it — diagnostics, the onboarding note, the generated reference, the
|
|
46
|
+
* surface covenant — names. It lives HERE rather than beside the walker so the
|
|
47
|
+
* quoting surfaces do not have to import `kernel/config/**`: the storeless
|
|
48
|
+
* `--help`/`__complete` graph is asserted to reach no config module, and
|
|
49
|
+
* `completion/safety.test.ts` fails the moment one arrives.
|
|
31
50
|
*/
|
|
32
|
-
const
|
|
51
|
+
const PROJECT_CONFIG_FILENAME = `${identity.name}.config.ts`;
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The distribution's name plus a space: the prefix a `recovery.cli` hint carries
|
|
55
|
+
* so it quotes a command the installed binary answers to (D5). Kernel hints are
|
|
56
|
+
* authored through `cliRecovery`, which prepends it; `packs/schema.ts` REJECTS
|
|
57
|
+
* it in a user-authored pack's `emptyRecovery.cli`, because the CONSUMING
|
|
58
|
+
* distribution's renderer supplies its own. Use {@link BIN_NAME} for prose —
|
|
59
|
+
* this constant's trailing space belongs to the recovery invariant, not to
|
|
60
|
+
* sentences.
|
|
61
|
+
*/
|
|
62
|
+
const RECOVERY_CLI_PREFIX = `${identity.name} `;
|
|
33
63
|
|
|
34
64
|
/** The output formats the renderer selects between (`llm` = condensed Markdown). */
|
|
35
65
|
const OUTPUT_FORMATS = ["plain", "llm", "json"] as const;
|
|
@@ -37,12 +67,6 @@ const OUTPUT_FORMATS = ["plain", "llm", "json"] as const;
|
|
|
37
67
|
/** A selected output format. `--format text` is normalised to `plain`. */
|
|
38
68
|
type OutputFormat = (typeof OUTPUT_FORMATS)[number];
|
|
39
69
|
|
|
40
|
-
/** Progressive-disclosure levels, least to most detail. */
|
|
41
|
-
const DETAIL_LEVELS = ["summary", "standard", "detailed"] as const;
|
|
42
|
-
|
|
43
|
-
/** A progressive-disclosure level. */
|
|
44
|
-
type DetailLevel = (typeof DETAIL_LEVELS)[number];
|
|
45
|
-
|
|
46
70
|
/** Default detail level when neither flag, config, nor spec pins one. */
|
|
47
71
|
const DEFAULT_DETAIL_LEVEL: DetailLevel = "standard";
|
|
48
72
|
|
|
@@ -51,9 +75,11 @@ export {
|
|
|
51
75
|
BIN_NAME,
|
|
52
76
|
DEFAULT_DETAIL_LEVEL,
|
|
53
77
|
DETAIL_LEVELS,
|
|
78
|
+
ISSUES_URL,
|
|
54
79
|
MCP_SERVER_NAME,
|
|
55
80
|
OUTPUT_FORMATS,
|
|
56
81
|
PROGRAM_DESCRIPTION,
|
|
82
|
+
PROJECT_CONFIG_FILENAME,
|
|
57
83
|
RECOVERY_CLI_PREFIX,
|
|
58
84
|
VERSION,
|
|
59
85
|
};
|
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
import { mkdtempSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { basename, join } from "node:path";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { afterAll, afterEach, describe, expect, it, vi } from "vitest";
|
|
6
|
+
import type { VerbSpec } from "./kernel/spec/types.js";
|
|
7
|
+
|
|
8
|
+
// A fork's distribution config. Everything the kernel says about itself must
|
|
9
|
+
// follow from THIS object — that is the whole claim under test.
|
|
10
|
+
vi.mock("../pragma.conf.js", () => ({
|
|
11
|
+
default: {
|
|
12
|
+
name: "recipes",
|
|
13
|
+
help: "Explore the recipe graph",
|
|
14
|
+
colophon: {
|
|
15
|
+
markdown: "Made by the kitchen, one dish at a time.",
|
|
16
|
+
summary: "Kitchen-made.",
|
|
17
|
+
},
|
|
18
|
+
issuesUrl: "https://example.invalid/recipes/issues",
|
|
19
|
+
packs: [],
|
|
20
|
+
prefixes: { rcp: "https://example.invalid/recipes/" },
|
|
21
|
+
channel: "normal",
|
|
22
|
+
detail: "standard",
|
|
23
|
+
},
|
|
24
|
+
// The fork's domain vocabulary, declared beside its identity. Every read of
|
|
25
|
+
// a domain term must follow from THIS object, which the last case below
|
|
26
|
+
// asserts by capturing the SPARQL the readers actually emit.
|
|
27
|
+
vocabulary: {
|
|
28
|
+
altName: "rcp:name",
|
|
29
|
+
prompt: {
|
|
30
|
+
type: "rcp:Prompt",
|
|
31
|
+
body: "rcp:promptBody",
|
|
32
|
+
argument: "rcp:promptArgument",
|
|
33
|
+
argName: "rcp:argName",
|
|
34
|
+
argRequired: "rcp:argRequired",
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
}));
|
|
38
|
+
|
|
39
|
+
/** Anything that would betray THIS distribution leaking through the kernel. */
|
|
40
|
+
const THIS_DISTRIBUTION = /pragma|canonical|design[- ]system/i;
|
|
41
|
+
|
|
42
|
+
/**
|
|
43
|
+
* THIS distribution's name alone. The generated reference legitimately carries
|
|
44
|
+
* bundled-pack CONTENT a fork owns and edits — `@canonical/…` package names in
|
|
45
|
+
* `create` examples, `ds:`/`cs:` entity values — so the reference probe below
|
|
46
|
+
* is scoped to the one token that must never survive a rename.
|
|
47
|
+
*/
|
|
48
|
+
const THIS_NAME = /\bpragma\b/i;
|
|
49
|
+
|
|
50
|
+
// chalk paints ANSI when GITHUB_ACTIONS is set even off a TTY, which would
|
|
51
|
+
// break the plain-text structure assertions below.
|
|
52
|
+
const prevChalkLevel = chalk.level;
|
|
53
|
+
chalk.level = 0;
|
|
54
|
+
afterAll(() => {
|
|
55
|
+
chalk.level = prevChalkLevel;
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
const originalConfigHome = process.env.XDG_CONFIG_HOME;
|
|
59
|
+
const originalDataHome = process.env.XDG_DATA_HOME;
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
process.env.XDG_CONFIG_HOME = originalConfigHome;
|
|
62
|
+
process.env.XDG_DATA_HOME = originalDataHome;
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
/** A pack-contributed noun the kernel has never heard of. */
|
|
66
|
+
const dishList: VerbSpec = {
|
|
67
|
+
path: ["dish", "list"],
|
|
68
|
+
summary: "List every dish in the graph.",
|
|
69
|
+
params: [],
|
|
70
|
+
output: {
|
|
71
|
+
formatters: {
|
|
72
|
+
plain: (d) => String(d),
|
|
73
|
+
llm: (d) => String(d),
|
|
74
|
+
json: (d) => JSON.stringify(d),
|
|
75
|
+
},
|
|
76
|
+
},
|
|
77
|
+
capability: { needsStore: false, mutates: false, mcp: { expose: true } },
|
|
78
|
+
run: async () => null,
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
describe("identity projection — a fork changes values, not code (PROTECTED)", () => {
|
|
82
|
+
it("names the bin, the MCP server, and the recovery prefix from the distribution", async () => {
|
|
83
|
+
const c = await import("./constants.js");
|
|
84
|
+
expect(c.BIN_NAME).toBe("recipes");
|
|
85
|
+
expect(c.MCP_SERVER_NAME).toBe("recipes");
|
|
86
|
+
expect(c.RECOVERY_CLI_PREFIX).toBe("recipes ");
|
|
87
|
+
expect(c.PROGRAM_DESCRIPTION).toBe("Explore the recipe graph");
|
|
88
|
+
expect(c.ISSUES_URL).toBe("https://example.invalid/recipes/issues");
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
it("installs its completion script under the fork's name, in all three shells", async () => {
|
|
92
|
+
// The basename is load-bearing, not cosmetic — `shell.ts` records what each
|
|
93
|
+
// shell measurably does with it (fish and bash-completion autoload BY the
|
|
94
|
+
// command's name; zsh binds by the `#compdef` tag but autoloads a function
|
|
95
|
+
// named after the file). A mismatch installs a file the shell never loads
|
|
96
|
+
// while `setup completions` prints success, `doctor` passes, and TAB does
|
|
97
|
+
// nothing. It can only be pinned from a fork: with the distribution named
|
|
98
|
+
// `pragma` a hardcoded literal and `BIN_NAME` agree by construction, so a
|
|
99
|
+
// test in `setup.test.ts` cannot fail. Here they cannot agree by accident.
|
|
100
|
+
const { completionScriptPath } = await import(
|
|
101
|
+
"./capabilities/setup/shell.js"
|
|
102
|
+
);
|
|
103
|
+
expect(basename(completionScriptPath("zsh"))).toBe("_recipes");
|
|
104
|
+
expect(basename(completionScriptPath("bash"))).toBe("recipes");
|
|
105
|
+
expect(basename(completionScriptPath("fish"))).toBe("recipes.fish");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("renders a front door that names only the fork", async () => {
|
|
109
|
+
const { BIN_NAME, PROGRAM_DESCRIPTION } = await import("./constants.js");
|
|
110
|
+
const { formatRootHelp } = await import("./kernel/project/cli/rootHelp.js");
|
|
111
|
+
const { configModule } = await import("./capabilities/config/index.js");
|
|
112
|
+
const { upgradeModule } = await import("./capabilities/upgrade/index.js");
|
|
113
|
+
const { colophonModule } = await import("./capabilities/colophon/index.js");
|
|
114
|
+
|
|
115
|
+
const help = formatRootHelp(BIN_NAME, PROGRAM_DESCRIPTION, [
|
|
116
|
+
dishList,
|
|
117
|
+
...configModule.verbs,
|
|
118
|
+
...upgradeModule.verbs,
|
|
119
|
+
...colophonModule.verbs,
|
|
120
|
+
]);
|
|
121
|
+
|
|
122
|
+
expect(help).not.toMatch(THIS_DISTRIBUTION);
|
|
123
|
+
expect(help).toMatch(/^recipes — Explore the recipe graph$/m);
|
|
124
|
+
// The pack noun leads the page untitled, and the blurb is not repeated as
|
|
125
|
+
// a heading three lines under the header that already carries it.
|
|
126
|
+
expect(help).toMatch(/^Usage: recipes .*\n\n {2}dish\b/m);
|
|
127
|
+
expect(help.match(/Explore the recipe graph/g)).toHaveLength(1);
|
|
128
|
+
expect(help).toContain("Read and write recipes configuration");
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
it("orients an MCP agent with the fork's identity", async () => {
|
|
132
|
+
const { buildInstructions } = await import(
|
|
133
|
+
"./kernel/project/mcp/instructions.js"
|
|
134
|
+
);
|
|
135
|
+
const { emitSurface } = await import("./kernel/spec/emitSurface.js");
|
|
136
|
+
const { capabilities } = await import("./capabilities/index.js");
|
|
137
|
+
|
|
138
|
+
// The `pragma:` resource scheme is covenant-frozen PROTOCOL identity
|
|
139
|
+
// (surface.v2.json), not copy. The orientation DERIVES it from the emitted
|
|
140
|
+
// surface, so mask exactly what the surface declares — a leak the kernel
|
|
141
|
+
// authored itself would survive this substitution and fail below.
|
|
142
|
+
const { resources } = emitSurface(capabilities).mcpSurface;
|
|
143
|
+
expect(resources.length).toBeGreaterThan(0);
|
|
144
|
+
const text = resources.reduce(
|
|
145
|
+
(orientation, template) => orientation.replace(template, "<resource>"),
|
|
146
|
+
buildInstructions(capabilities),
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
expect(text).not.toMatch(THIS_DISTRIBUTION);
|
|
150
|
+
// No punctuation is appended to the fork's `help` phrase.
|
|
151
|
+
expect(text.startsWith("recipes — Explore the recipe graph (")).toBe(true);
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
it("greets a first-time user with the fork's issues URL and config file", async () => {
|
|
155
|
+
process.env.XDG_CONFIG_HOME = mkdtempSync(join(tmpdir(), "identity-"));
|
|
156
|
+
const { ensureFirstRun } = await import("./kernel/config/firstRun.js");
|
|
157
|
+
const { globalConfigPath } = await import("./kernel/config/paths.js");
|
|
158
|
+
|
|
159
|
+
const lines: string[] = [];
|
|
160
|
+
await ensureFirstRun((line) => lines.push(line));
|
|
161
|
+
const greeting = lines.join("\n");
|
|
162
|
+
|
|
163
|
+
// Nothing is masked: the resolved config path is IN the greeting, so the
|
|
164
|
+
// XDG namespace has to follow the fork's name too or this assertion fails.
|
|
165
|
+
expect(greeting).not.toMatch(THIS_DISTRIBUTION);
|
|
166
|
+
expect(greeting).toContain(globalConfigPath());
|
|
167
|
+
expect(globalConfigPath()).toContain("/recipes/");
|
|
168
|
+
expect(greeting).toContain("https://example.invalid/recipes/issues");
|
|
169
|
+
expect(greeting).toContain("`recipes.config.ts`");
|
|
170
|
+
});
|
|
171
|
+
|
|
172
|
+
it("narrates the colophon the fork declares, titled with the fork's name", async () => {
|
|
173
|
+
// The toolchain colophon is DECLARED content (`pragma.conf.ts#colophon`),
|
|
174
|
+
// not authored code: the collector must render the fork's declaration
|
|
175
|
+
// under the fork's own name. With no packs declared, the fork's whole
|
|
176
|
+
// colophon is its own story. `kind: "pragma"` is asserted AS the literal:
|
|
177
|
+
// it is the frozen JSON discriminant every distribution serves (wire
|
|
178
|
+
// compatibility, like the resource scheme), deliberately NOT derived.
|
|
179
|
+
process.env.XDG_CONFIG_HOME = mkdtempSync(join(tmpdir(), "identity-"));
|
|
180
|
+
const { collectColophon } = await import(
|
|
181
|
+
"./capabilities/colophon/collectColophon.js"
|
|
182
|
+
);
|
|
183
|
+
const { bootRuntime } = await import("./kernel/runtime/boot.js");
|
|
184
|
+
|
|
185
|
+
const runtime = bootRuntime(
|
|
186
|
+
{ llm: false, autoLlm: false, format: "plain", verbose: false },
|
|
187
|
+
mkdtempSync(join(tmpdir(), "identity-colophon-")),
|
|
188
|
+
);
|
|
189
|
+
const { sections } = await collectColophon(runtime);
|
|
190
|
+
|
|
191
|
+
expect(sections).toHaveLength(1);
|
|
192
|
+
const own = sections[0];
|
|
193
|
+
expect(own?.kind).toBe("pragma");
|
|
194
|
+
expect(own?.title).toBe("recipes");
|
|
195
|
+
expect(own?.markdown).toBe("Made by the kitchen, one dish at a time.");
|
|
196
|
+
expect(own?.summary).toBe("Kitchen-made.");
|
|
197
|
+
expect(own?.source).toBe("built-in");
|
|
198
|
+
for (const text of [own?.title, own?.markdown, own?.summary]) {
|
|
199
|
+
expect(text).not.toMatch(THIS_DISTRIBUTION);
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
it("owns its own on-disk skills namespace", async () => {
|
|
204
|
+
// Config, state and cache were namespaced by the bin name; the SKILLS roots
|
|
205
|
+
// were not. Two distributions installed side by side therefore shared one
|
|
206
|
+
// `$XDG_DATA_HOME/pragma/skills` and one `<cwd>/.pragma/skills`, so a fork
|
|
207
|
+
// read the other's skills and could not install its own without collision.
|
|
208
|
+
process.env.XDG_DATA_HOME = join(tmpdir(), "identity-data");
|
|
209
|
+
const { skillRoots, installedSkillsDir } = await import(
|
|
210
|
+
"./capabilities/skill/discover.js"
|
|
211
|
+
);
|
|
212
|
+
expect(installedSkillsDir().endsWith(join("recipes", "skills"))).toBe(true);
|
|
213
|
+
expect(skillRoots("/work")).toEqual([
|
|
214
|
+
join("/work", ".recipes", "skills"),
|
|
215
|
+
installedSkillsDir(),
|
|
216
|
+
]);
|
|
217
|
+
for (const root of skillRoots("/work")) {
|
|
218
|
+
expect(root).not.toMatch(THIS_DISTRIBUTION);
|
|
219
|
+
}
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("introduces its MCP server on the wire under the fork's name", async () => {
|
|
223
|
+
// The wire half of the identity rule the covenant records
|
|
224
|
+
// (mcpSurface.serverInfo): serverInfo PROJECTS — name from the declared
|
|
225
|
+
// distribution, version from the package. Asserted through a real
|
|
226
|
+
// initialize handshake (in-memory transport), not on the constant, so a
|
|
227
|
+
// hardcoded serverInfo in buildServer would fail here even while
|
|
228
|
+
// MCP_SERVER_NAME projects correctly.
|
|
229
|
+
const { projectMcp } = await import("./testing/helpers/projectMcp.js");
|
|
230
|
+
const { VERSION } = await import("./constants.js");
|
|
231
|
+
|
|
232
|
+
const mcp = await projectMcp([]);
|
|
233
|
+
const wire = mcp.serverInfo();
|
|
234
|
+
await mcp.cleanup();
|
|
235
|
+
|
|
236
|
+
expect(wire).toEqual({ name: "recipes", version: VERSION });
|
|
237
|
+
expect(wire?.name).not.toMatch(THIS_DISTRIBUTION);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
it("registers an MCP server entry the fork's own binary answers to", async () => {
|
|
241
|
+
// The entry `setup mcp` writes is what a harness later EXECUTES. It was
|
|
242
|
+
// hardcoded while the key it is stored under derived from the same
|
|
243
|
+
// identity, so a fork registered a `recipes` server that ran `pragma` —
|
|
244
|
+
// and `doctor`'s `MCP configured` check, which re-derived the key by hand,
|
|
245
|
+
// could never see its own registration.
|
|
246
|
+
const { pragmaMcpEntry } = await import(
|
|
247
|
+
"./capabilities/setup/operations/setupMcp.js"
|
|
248
|
+
);
|
|
249
|
+
expect(pragmaMcpEntry("/work")).toEqual({
|
|
250
|
+
command: "recipes",
|
|
251
|
+
args: ["mcp"],
|
|
252
|
+
cwd: "/work",
|
|
253
|
+
});
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("generates a reference that never names this distribution", async () => {
|
|
257
|
+
// `docs/reference/` is the surface this package PUBLISHES as machine-derived
|
|
258
|
+
// truth, so it is the surface a fork's rename must reach in full. Every
|
|
259
|
+
// command a page quotes, every page title and every prose mention is
|
|
260
|
+
// composed from `BIN_NAME`. The failure names `page: line`, so it is a
|
|
261
|
+
// worklist and not just a count.
|
|
262
|
+
//
|
|
263
|
+
// ONE exemption, the same one the MCP orientation case above makes and for
|
|
264
|
+
// the same reason: the `pragma:` resource scheme is covenant-frozen PROTOCOL
|
|
265
|
+
// identity (`surface.v2.json`), inherited by a fork along with the
|
|
266
|
+
// `pragma/box` and `pragma/instanceCount` `_meta` keys it travels with, and
|
|
267
|
+
// `tools.md` reports it truthfully. Masked from the emitted surface, not by
|
|
268
|
+
// a literal, so a leak the kernel authored itself still fails here.
|
|
269
|
+
const { emitReference } = await import("./kernel/spec/emitReference.js");
|
|
270
|
+
const { emitSurface } = await import("./kernel/spec/emitSurface.js");
|
|
271
|
+
const { capabilities } = await import("./capabilities/index.js");
|
|
272
|
+
|
|
273
|
+
const { resources } = emitSurface(capabilities).mcpSurface;
|
|
274
|
+
expect(resources.length).toBeGreaterThan(0);
|
|
275
|
+
const unmask = (line: string): string =>
|
|
276
|
+
resources.reduce((text, template) => text.replace(template, ""), line);
|
|
277
|
+
|
|
278
|
+
const offenders: string[] = [];
|
|
279
|
+
for (const [page, content] of emitReference(capabilities)) {
|
|
280
|
+
for (const line of content.split("\n")) {
|
|
281
|
+
if (THIS_NAME.test(unmask(line))) offenders.push(`${page}: ${line}`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
expect(offenders).toEqual([]);
|
|
285
|
+
});
|
|
286
|
+
|
|
287
|
+
it("reads the graph with the fork's declared terms, not this distribution's", async () => {
|
|
288
|
+
// The couplings this tranche removed were all hardcoded terms, and a
|
|
289
|
+
// hardcoded term still passes every other test in this suite: the fixtures
|
|
290
|
+
// on both sides would simply agree. So capture what the readers EMIT.
|
|
291
|
+
const { DEFAULT_PREFIX_MAP } = await import("./kernel/render/prefixes.js");
|
|
292
|
+
const { VOCABULARY } = await import("./kernel/vocabulary.js");
|
|
293
|
+
const { readPrompts } = await import(
|
|
294
|
+
"./kernel/project/mcp/prompts/source.js"
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// The display/expansion map: the fork's namespaces, none of ours, and the
|
|
298
|
+
// W3C half the kernel owns unchanged.
|
|
299
|
+
expect(DEFAULT_PREFIX_MAP.rcp).toBe("https://example.invalid/recipes/");
|
|
300
|
+
expect(DEFAULT_PREFIX_MAP.ds).toBeUndefined();
|
|
301
|
+
expect(DEFAULT_PREFIX_MAP.rdfs).toBe(
|
|
302
|
+
"http://www.w3.org/2000/01/rdf-schema#",
|
|
303
|
+
);
|
|
304
|
+
|
|
305
|
+
// The tier noun is no longer code AT ALL (L-OPEN-9): both its verbs are a
|
|
306
|
+
// story the distribution declares, so a fork writes its own terms into the
|
|
307
|
+
// declaration directly and there is nothing left in the kernel for a
|
|
308
|
+
// hardcoded tier term to hide in. `distribution.test.ts` holds this
|
|
309
|
+
// distribution's declaration coherent with the vocabulary. What the KERNEL
|
|
310
|
+
// still reads domain terms with is the vocabulary projection (the index
|
|
311
|
+
// builder interpolates `VOCABULARY.altName`) and the prompts reader —
|
|
312
|
+
// captured below.
|
|
313
|
+
expect(VOCABULARY.altName).toBe("rcp:name");
|
|
314
|
+
|
|
315
|
+
// The generated prompt read, captured off a stub facade.
|
|
316
|
+
const queries: string[] = [];
|
|
317
|
+
const recorder = {
|
|
318
|
+
query: {
|
|
319
|
+
sparql: (query: string) => {
|
|
320
|
+
queries.push(query);
|
|
321
|
+
return Promise.resolve({ type: "select", bindings: [] });
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
} as never;
|
|
325
|
+
|
|
326
|
+
await expect(readPrompts(recorder)).resolves.toEqual([]);
|
|
327
|
+
|
|
328
|
+
const emitted = queries.join("\n");
|
|
329
|
+
expect(emitted).toContain("rcp:Prompt");
|
|
330
|
+
expect(emitted).toContain("rcp:promptBody");
|
|
331
|
+
expect(emitted).not.toMatch(THIS_DISTRIBUTION);
|
|
332
|
+
});
|
|
333
|
+
});
|
package/src/index.ts
CHANGED
|
@@ -142,6 +142,9 @@ end
|
|
|
142
142
|
function __pragma_minchars
|
|
143
143
|
test (string length -- (commandline -ct)) -ge $argv[1]
|
|
144
144
|
end
|
|
145
|
+
function __pragma_notflag
|
|
146
|
+
not string match -q -- '-*' (commandline -ct)
|
|
147
|
+
end
|
|
145
148
|
function __pragma_at
|
|
146
149
|
set -l cmd (commandline -opc)
|
|
147
150
|
set -l noun ''
|
|
@@ -185,14 +188,14 @@ complete -c pragma -n "__pragma_at block" -a "diff get list remove"
|
|
|
185
188
|
complete -c pragma -n "__pragma_at standard" -a "list"
|
|
186
189
|
|
|
187
190
|
# block/diff
|
|
188
|
-
complete -c pragma -n "__pragma_at block diff; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
|
|
191
|
+
complete -c pragma -n "__pragma_at block diff; and __pragma_notflag; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
|
|
189
192
|
|
|
190
193
|
# block/get
|
|
191
194
|
complete -c pragma -n "__pragma_at block get" -l view -x -a "anatomy full summary"
|
|
192
195
|
complete -c pragma -n "__pragma_at block get" -l out -rF
|
|
193
196
|
complete -c pragma -n "__pragma_at block get" -l with-meta
|
|
194
197
|
complete -c pragma -n "__pragma_at block get" -l note -x
|
|
195
|
-
complete -c pragma -n "__pragma_at block get; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
|
|
198
|
+
complete -c pragma -n "__pragma_at block get; and __pragma_notflag; and __pragma_minchars 2" -a "(pragma __complete -- (__pragma_words) 2>/dev/null)"
|
|
196
199
|
|
|
197
200
|
# block/list
|
|
198
201
|
complete -c pragma -n "__pragma_at block list" -l tags -x
|
|
@@ -108,7 +108,7 @@ describe("emitScripts — minChars gate + family opt-out", () => {
|
|
|
108
108
|
});
|
|
109
109
|
});
|
|
110
110
|
|
|
111
|
-
describe("emitScripts — binName parameterization
|
|
111
|
+
describe("emitScripts — binName parameterization", () => {
|
|
112
112
|
it("defaults to pragma", () => {
|
|
113
113
|
expect(scripts.bash).toContain("complete -F _pragma pragma");
|
|
114
114
|
expect(scripts.zsh.startsWith("#compdef pragma")).toBe(true);
|
|
@@ -5,12 +5,14 @@
|
|
|
5
5
|
* derivation the `__complete` resolver uses — the tiers cannot disagree) and
|
|
6
6
|
* renders one script per supported shell. The static tier answers STRUCTURE
|
|
7
7
|
* with zero exec: nouns, verbs, flag names, enum values, mutation + global
|
|
8
|
-
* flags, native file completion. Only `{kind:"
|
|
8
|
+
* flags, native file completion. Only `{kind:"names"}` value contexts shell
|
|
9
9
|
* out to `<bin> __complete`.
|
|
10
10
|
*
|
|
11
11
|
* PR6 boundary (`setup completions`): this module exports pure CONTENT only
|
|
12
12
|
* — shell detection, install paths, mkdir/write effects, and hints all
|
|
13
|
-
* belong to setup. `binName` is parameterized so
|
|
13
|
+
* belong to setup. `binName` is parameterized so a fork's rename reaches the
|
|
14
|
+
* script body (`completionScriptPath` derives the file NAME from the same
|
|
15
|
+
* constant, and the two must agree or the shell never loads the file);
|
|
14
16
|
* `minChars` and `disabledFamilies` are read from `completion` config by setup.
|
|
15
17
|
*
|
|
16
18
|
* `minChars` (default {@link DEFAULT_MIN_CHARS}) is baked into the scripts to
|