@canonical/pragma-cli 0.32.0 → 0.33.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 +14 -13
- package/src/bin.ts +2 -2
- package/src/capabilities/block/blockList.test.ts +79 -0
- package/src/capabilities/block/blockList.verb.ts +18 -5
- package/src/capabilities/block/pack.ts +12 -1
- package/src/capabilities/block/parity.test.ts +9 -2
- package/src/capabilities/capabilities/capabilities.test.ts +10 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +1 -1
- package/src/capabilities/capabilities/catalog.ts +7 -3
- package/src/capabilities/capabilities/hints.ts +8 -17
- package/src/capabilities/capabilities/types.ts +2 -0
- 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 +4 -1
- package/src/capabilities/colophon/markdownTerminal.ts +33 -14
- package/src/capabilities/colophon/pragmaColophon.ts +4 -4
- package/src/capabilities/colophon/types.ts +1 -1
- package/src/capabilities/config/fields.ts +21 -92
- package/src/capabilities/config/set.test.ts +6 -6
- package/src/capabilities/config/set.verb.ts +5 -5
- package/src/capabilities/config/show.render.test.ts +65 -0
- package/src/capabilities/config/show.render.ts +56 -10
- package/src/capabilities/config/show.verb.ts +5 -5
- package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
- package/src/capabilities/create/byteEquality.test.ts +7 -4
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +1 -1
- package/src/capabilities/create/create.test.ts +32 -3
- package/src/capabilities/create/create.verb.ts +69 -11
- package/src/capabilities/doctor/checks/checkConfigFile.ts +1 -1
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +10 -4
- 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 +116 -34
- package/src/capabilities/doctor/doctor.test.ts +31 -1
- package/src/capabilities/doctor/runChecks.ts +28 -4
- package/src/capabilities/doctor/types.ts +15 -0
- package/src/capabilities/ontology/index.ts +10 -3
- package/src/capabilities/ontology/ontology.test.ts +157 -5
- package/src/capabilities/ontology/verbs.ts +192 -93
- 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 +61 -0
- package/src/capabilities/reference.test.ts +32 -0
- package/src/capabilities/resources/provider.ts +28 -14
- package/src/capabilities/resources/resources.test.ts +99 -0
- 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 +157 -61
- 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 +7 -1
- 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 +57 -5
- package/src/capabilities/setup/types.ts +59 -2
- package/src/capabilities/shared/bands.ts +16 -0
- package/src/capabilities/sources/collectStatus.ts +19 -4
- package/src/capabilities/sources/runUpdate.ts +58 -1
- package/src/capabilities/sources/sources.test.ts +187 -1
- package/src/capabilities/sources/status.render.test.ts +65 -0
- package/src/capabilities/sources/status.render.ts +50 -23
- package/src/capabilities/surface.test.ts +34 -9
- package/src/capabilities/tier/lookup.render.test.ts +72 -0
- package/src/capabilities/tier/lookup.render.ts +33 -8
- package/src/capabilities/tier/lookup.test.ts +15 -0
- package/src/capabilities/tier/runLookup.ts +28 -0
- package/src/constants.test.ts +1 -1
- package/src/constants.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +13 -14
- package/src/kernel/completion/bashDrive.test.ts +3 -3
- package/src/kernel/completion/complete.test.ts +3 -5
- package/src/kernel/completion/entitySource.ts +14 -6
- package/src/kernel/completion/model.test.ts +2 -2
- package/src/kernel/completion/model.ts +0 -1
- package/src/kernel/completion/parse.test.ts +2 -4
- package/src/kernel/error/PragmaError.test.ts +4 -4
- package/src/kernel/error/renderError.ts +1 -1
- package/src/kernel/packs/compile.ts +1 -1
- package/src/kernel/project/cli/buildProgram.test.ts +51 -7
- package/src/kernel/project/cli/buildProgram.ts +11 -0
- package/src/kernel/project/cli/globalFlags.test.ts +12 -4
- package/src/kernel/project/cli/globalFlags.ts +14 -14
- package/src/kernel/project/cli/rootHelp.ts +4 -2
- package/src/kernel/project/mcp/instructions.ts +1 -1
- package/src/kernel/project/mcp/mcpError.ts +52 -0
- package/src/kernel/project/mcp/prompts/prompts.test.ts +8 -0
- package/src/kernel/project/mcp/prompts/provider.ts +36 -6
- package/src/kernel/render/formatters.ts +5 -4
- package/src/kernel/render/renderers.test.ts +34 -0
- 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 +28 -0
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +103 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +124 -36
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +3 -6
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -2
- package/src/kernel/runtime/graphpack/graphpack.test.ts +69 -1
- package/src/kernel/runtime/graphpack/read.ts +28 -13
- package/src/kernel/runtime/graphpack/types.ts +14 -0
- package/src/kernel/runtime/loadSession.ts +4 -12
- package/src/kernel/runtime/refs/resolve.ts +63 -3
- package/src/kernel/runtime/storeReadiness.ts +61 -0
- package/src/kernel/runtime/types.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +3 -1
- package/src/kernel/spec/emitReference.test.ts +196 -0
- package/src/kernel/spec/emitReference.ts +491 -0
- package/src/kernel/spec/emitSurface.ts +4 -2
- package/src/kernel/spec/index.ts +2 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/testing/PARITY_GAPS.ts +1 -1
- package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
- package/src/testing/behavioral/completion.test.ts +2 -2
- package/src/testing/behavioral/disclosure.test.ts +12 -0
- package/src/testing/behavioral/docExamples.test.ts +283 -0
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +2 -1
- package/src/testing/behavioral/journeys.cli.test.ts +3 -1
- package/src/testing/behavioral/journeys.defaultPack.test.ts +571 -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 +176 -0
- package/src/testing/behavioral/mcpSurface.test.ts +1 -1
- package/src/testing/behavioral/staleCommands.test.ts +76 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +2 -2
- package/src/testing/eval/cases/assembly.ts +3 -3
- package/src/testing/eval/cases/effectful.ts +6 -3
- package/src/testing/eval/cases/readNouns.ts +4 -1
- package/src/testing/fixtures/fixtureCapability.ts +156 -0
- package/src/testing/fixtures/graph/defaultPack.ts +178 -0
- package/src/testing/fixtures/graph/owlExport.ts +95 -0
- package/src/testing/helpers/extractSnippets.ts +29 -0
- package/src/testing/helpers/golden.ts +1 -1
- package/src/capabilities/config/field.test.ts +0 -188
package/README.md
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
# @canonical/pragma-cli
|
|
2
|
+
|
|
3
|
+
`pragma` is a command-line tool **and** a Model Context Protocol (MCP) server over Canonical's design-system knowledge graph. A single grammar of capabilities is projected two ways — as CLI commands for humans and as MCP tools for agents — both reading the same local, content-addressed store built from your design-system packages.
|
|
4
|
+
|
|
5
|
+
## Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install -g @canonical/pragma-cli
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
This installs the `pragma` binary: a prebuilt, standalone executable (`dist/pragma`). The package publishes for **Linux x64 only** (`"os": ["linux"]`, `"cpu": ["x64"]`) — other platforms are not yet shipped.
|
|
12
|
+
|
|
13
|
+
Check your environment and confirm the install:
|
|
14
|
+
|
|
15
|
+
```bash
|
|
16
|
+
pragma doctor
|
|
17
|
+
```
|
|
18
|
+
|
|
19
|
+
## Quickstart
|
|
20
|
+
|
|
21
|
+
```bash
|
|
22
|
+
pragma sources update
|
|
23
|
+
pragma sources status
|
|
24
|
+
pragma block list
|
|
25
|
+
pragma block lookup Button
|
|
26
|
+
pragma capabilities
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
`pragma sources update` resolves the design-system packages named in your `pragma.config.ts`, builds one local store, and writes `pragma.lock.json`. Once built, every read command answers from the store with no network access.
|
|
30
|
+
|
|
31
|
+
To register pragma with a detected AI harness as an MCP server:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
pragma setup mcp
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
See [docs/mcp-integration.md](./docs/mcp-integration.md) for the full MCP surface, and the [command & tool reference](./docs/reference/index.md) for every command and tool.
|
|
38
|
+
|
|
39
|
+
## Two planes
|
|
40
|
+
|
|
41
|
+
pragma is extended along two independent planes:
|
|
42
|
+
|
|
43
|
+
- **Data plane — the content.** Your design system is a set of semantic packages listed in `pragma.config.ts` (`packages: [...]`). `pragma sources update` compiles them into the local knowledge graph the read commands query. Point pragma at different packages and every `list` / `lookup` / `sample` command answers from that graph.
|
|
44
|
+
- **Behaviour plane — the capabilities.** Every command and MCP tool is one entry in a single capability grammar, projected to both the CLI and the MCP server. The [reference](./docs/reference/index.md) is generated from that grammar, so it can never drift from the code.
|
|
45
|
+
|
|
46
|
+
## Relationship to summon
|
|
47
|
+
|
|
48
|
+
`@canonical/summon` is a separate scaffolding product. pragma reuses summon's generator core so that its scaffolding commands —
|
|
49
|
+
|
|
50
|
+
```bash
|
|
51
|
+
pragma create component src/components/Button --framework react
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
— produce output byte-identical to summon's own `component`, `package`, and `application` generators. summon is not a runtime dependency: the generators are embedded in the `pragma` binary, so `pragma create component` runs from a clean install.
|
|
55
|
+
|
|
56
|
+
## MCP
|
|
57
|
+
|
|
58
|
+
Run pragma as an MCP server over stdio via `pragma setup mcp` (automatic harness registration) or the manual `pragma mcp` entry point. The server exposes the read and scaffold tools, a `pragma:{+uri}` resource surface for entity reads, the design system's workflow prompts, and handshake instructions describing the discovery sequence. Mutating tools are plan-first — they return the plan they would apply until called with `confirm: true`. See [docs/mcp-integration.md](./docs/mcp-integration.md).
|
|
59
|
+
|
|
60
|
+
## Documentation
|
|
61
|
+
|
|
62
|
+
- [Getting started](./docs/getting-started.md)
|
|
63
|
+
- [MCP integration](./docs/mcp-integration.md)
|
|
64
|
+
- [Configuration model](./docs/config-model.md)
|
|
65
|
+
- [Architecture](./docs/architecture.md)
|
|
66
|
+
- [Skills](./docs/skills.md)
|
|
67
|
+
- [Command & tool reference](./docs/reference/index.md)
|
|
68
|
+
- [Changelog](./CHANGELOG.md)
|
|
69
|
+
|
|
70
|
+
## License
|
|
71
|
+
|
|
72
|
+
GPL-3.0
|
package/dist/pragma
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonical/pragma-cli",
|
|
3
3
|
"description": "Next-generation CLI and MCP kernel for Canonical's design system (pragma v2).",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.33.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -36,6 +36,7 @@
|
|
|
36
36
|
},
|
|
37
37
|
"scripts": {
|
|
38
38
|
"build": "bun run scripts/build.ts",
|
|
39
|
+
"gen:reference": "bun run scripts/genReference.ts",
|
|
39
40
|
"build:compile": "bun build --compile --minify --target=bun-linux-x64 src/bin.ts --outfile dist/pragma",
|
|
40
41
|
"check": "bun run check:biome && bun run check:ts && bun run check:webarchitect",
|
|
41
42
|
"check:webarchitect": "webarchitect tool-ts",
|
|
@@ -51,14 +52,14 @@
|
|
|
51
52
|
"eval": "bun run src/testing/eval/report.ts"
|
|
52
53
|
},
|
|
53
54
|
"dependencies": {
|
|
54
|
-
"@canonical/harnesses": "^0.
|
|
55
|
-
"@canonical/ke": "^0.
|
|
56
|
-
"@canonical/ke-graphql": "^0.
|
|
57
|
-
"@canonical/summon-application": "^0.
|
|
58
|
-
"@canonical/summon-component": "^0.
|
|
59
|
-
"@canonical/summon-core": "^0.
|
|
60
|
-
"@canonical/summon-package": "^0.
|
|
61
|
-
"@canonical/task": "^0.
|
|
55
|
+
"@canonical/harnesses": "^0.33.0",
|
|
56
|
+
"@canonical/ke": "^0.33.0",
|
|
57
|
+
"@canonical/ke-graphql": "^0.33.0",
|
|
58
|
+
"@canonical/summon-application": "^0.33.0",
|
|
59
|
+
"@canonical/summon-component": "^0.33.0",
|
|
60
|
+
"@canonical/summon-core": "^0.33.0",
|
|
61
|
+
"@canonical/summon-package": "^0.33.0",
|
|
62
|
+
"@canonical/task": "^0.33.0",
|
|
62
63
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
63
64
|
"chalk": "^5.6.2",
|
|
64
65
|
"commander": "^14.0.3",
|
|
@@ -68,14 +69,14 @@
|
|
|
68
69
|
},
|
|
69
70
|
"devDependencies": {
|
|
70
71
|
"@biomejs/biome": "2.4.9",
|
|
71
|
-
"@canonical/biome-config": "^0.
|
|
72
|
-
"@canonical/typescript-config": "^0.
|
|
73
|
-
"@canonical/webarchitect": "^0.
|
|
72
|
+
"@canonical/biome-config": "^0.33.0",
|
|
73
|
+
"@canonical/typescript-config": "^0.33.0",
|
|
74
|
+
"@canonical/webarchitect": "^0.33.0",
|
|
74
75
|
"@types/bun": "^1.3.10",
|
|
75
76
|
"@types/node": "^24.12.0",
|
|
76
77
|
"@vitest/coverage-v8": "^4.0.18",
|
|
77
78
|
"typescript": "^5.9.3",
|
|
78
79
|
"vitest": "^4.0.18"
|
|
79
80
|
},
|
|
80
|
-
"gitHead": "
|
|
81
|
+
"gitHead": "fca2bc4440fff2d511fc8db6686f9b19222fdbc7"
|
|
81
82
|
}
|
package/src/bin.ts
CHANGED
|
@@ -77,7 +77,7 @@ async function main(): Promise<void> {
|
|
|
77
77
|
if (
|
|
78
78
|
!explicitHelp &&
|
|
79
79
|
rawFormat !== undefined &&
|
|
80
|
-
!["plain", "json", "text"].includes(rawFormat)
|
|
80
|
+
!["plain", "llm", "json", "text"].includes(rawFormat)
|
|
81
81
|
) {
|
|
82
82
|
const [
|
|
83
83
|
{ PragmaError },
|
|
@@ -89,7 +89,7 @@ async function main(): Promise<void> {
|
|
|
89
89
|
import("./kernel/project/cli/exitCodes.js"),
|
|
90
90
|
]);
|
|
91
91
|
const error = PragmaError.invalidInput("format", rawFormat, {
|
|
92
|
-
validOptions: ["plain", "json"],
|
|
92
|
+
validOptions: ["plain", "llm", "json"],
|
|
93
93
|
});
|
|
94
94
|
const rendered =
|
|
95
95
|
globalFlags.format === "json"
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
2
|
+
import { verbKey } from "../../kernel/packs/uniqueness.js";
|
|
3
|
+
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
4
|
+
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
5
|
+
import { OWL_EXPORT_TTL } from "../../testing/fixtures/graph/owlExport.js";
|
|
6
|
+
import { bootFixtureRuntime } from "../../testing/helpers/fixtureGraph.js";
|
|
7
|
+
import type { BlockRow } from "./blockList.verb.js";
|
|
8
|
+
import { blockModule } from "./index.js";
|
|
9
|
+
|
|
10
|
+
const listVerb = blockModule.verbs.find(
|
|
11
|
+
(verb) => verbKey(verb.path) === "block list",
|
|
12
|
+
) as VerbSpec;
|
|
13
|
+
|
|
14
|
+
const names = (rows: BlockRow[]): string[] =>
|
|
15
|
+
rows.map((row) => row.name).sort();
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* The OWL-export fixture ships an UNTIERED `ds:Component` (`ds:datePicker`, no
|
|
19
|
+
* `ds:tier`) beside a tiered one (`ds:button`). The pre-fix `block list` gated
|
|
20
|
+
* on a REQUIRED `ds:tier` triple pattern, so `--all-tiers` — which drops the
|
|
21
|
+
* FILTER but not the pattern — still hid the untiered block, contradicting the
|
|
22
|
+
* flag's promise to show every block (A2). A tier-scoped view legitimately
|
|
23
|
+
* omits an untiered block (it joins the list through its tier); `--all-tiers`
|
|
24
|
+
* is the escape that reveals it.
|
|
25
|
+
*/
|
|
26
|
+
describe("block list — untiered blocks under --all-tiers (A2)", () => {
|
|
27
|
+
describe("no tier configured", () => {
|
|
28
|
+
let runtime: PragmaRuntime;
|
|
29
|
+
let dispose: () => Promise<void>;
|
|
30
|
+
|
|
31
|
+
beforeAll(async () => {
|
|
32
|
+
const fixture = await bootFixtureRuntime({ ttl: OWL_EXPORT_TTL });
|
|
33
|
+
runtime = fixture.runtime;
|
|
34
|
+
dispose = fixture.dispose;
|
|
35
|
+
});
|
|
36
|
+
afterAll(() => dispose());
|
|
37
|
+
|
|
38
|
+
it("the default view still omits the untiered block", async () => {
|
|
39
|
+
const rows = (await listVerb.run({}, runtime)) as BlockRow[];
|
|
40
|
+
expect(names(rows)).toEqual(["Button"]);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("--all-tiers reveals the untiered block", async () => {
|
|
44
|
+
const rows = (await listVerb.run(
|
|
45
|
+
{ allTiers: true },
|
|
46
|
+
runtime,
|
|
47
|
+
)) as BlockRow[];
|
|
48
|
+
expect(names(rows)).toEqual(["Button", "Date Picker"]);
|
|
49
|
+
});
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
describe("a tier IS configured", () => {
|
|
53
|
+
let runtime: PragmaRuntime;
|
|
54
|
+
let dispose: () => Promise<void>;
|
|
55
|
+
|
|
56
|
+
beforeAll(async () => {
|
|
57
|
+
const fixture = await bootFixtureRuntime({
|
|
58
|
+
ttl: OWL_EXPORT_TTL,
|
|
59
|
+
config: { tier: "global", channel: "normal" },
|
|
60
|
+
});
|
|
61
|
+
runtime = fixture.runtime;
|
|
62
|
+
dispose = fixture.dispose;
|
|
63
|
+
});
|
|
64
|
+
afterAll(() => dispose());
|
|
65
|
+
|
|
66
|
+
it("a scoped tier lists its tiered block, not the untiered one", async () => {
|
|
67
|
+
const rows = (await listVerb.run({}, runtime)) as BlockRow[];
|
|
68
|
+
expect(names(rows)).toEqual(["Button"]);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
it("--all-tiers overrides the scope and reveals the untiered block", async () => {
|
|
72
|
+
const rows = (await listVerb.run(
|
|
73
|
+
{ allTiers: true },
|
|
74
|
+
runtime,
|
|
75
|
+
)) as BlockRow[];
|
|
76
|
+
expect(names(rows)).toContain("Date Picker");
|
|
77
|
+
});
|
|
78
|
+
});
|
|
79
|
+
});
|
|
@@ -84,14 +84,25 @@ function buildQuery(
|
|
|
84
84
|
): string {
|
|
85
85
|
const tierFilter = allTiers ? "" : buildTierFilter(tier);
|
|
86
86
|
const channelFilter = buildChannelFilter(channel);
|
|
87
|
+
// Under --all-tiers, `ds:name`/`ds:tier` become OPTIONAL so a real block that
|
|
88
|
+
// ships without either is no longer hidden — the flag promises "all blocks",
|
|
89
|
+
// yet the pre-fix query gated on those as REQUIRED patterns, dropping the
|
|
90
|
+
// FILTER but not the pattern (A2). In the scoped/default view they stay
|
|
91
|
+
// required: a block joins the list through its tier, so an untiered block is
|
|
92
|
+
// intentionally out of a tier-scoped view (and --all-tiers is the escape).
|
|
93
|
+
const nameTierPatterns = allTiers
|
|
94
|
+
? [
|
|
95
|
+
" OPTIONAL { ?component ds:name ?name }",
|
|
96
|
+
" OPTIONAL { ?component ds:tier ?tier }",
|
|
97
|
+
]
|
|
98
|
+
: [" ?component ds:name ?name ; ds:tier ?tier ."];
|
|
87
99
|
return [
|
|
88
100
|
"SELECT ?component ?type ?name ?tier",
|
|
89
101
|
' (GROUP_CONCAT(DISTINCT ?modName; separator=", ") AS ?modifiers)',
|
|
90
102
|
"WHERE {",
|
|
91
103
|
" VALUES ?type { ds:Component ds:Pattern ds:Layout ds:Subcomponent }",
|
|
92
|
-
" ?component a ?type
|
|
93
|
-
|
|
94
|
-
" ds:tier ?tier .",
|
|
104
|
+
" ?component a ?type .",
|
|
105
|
+
...nameTierPatterns,
|
|
95
106
|
tierFilter ? ` ${tierFilter}` : "",
|
|
96
107
|
` ${channelFilter}`,
|
|
97
108
|
" OPTIONAL { ?component ds:hasModifierFamily ?mod . ?mod ds:name ?modName }",
|
|
@@ -118,7 +129,7 @@ const listVerb: VerbSpec<Record<string, unknown>, BlockRow[]> = {
|
|
|
118
129
|
examples: [
|
|
119
130
|
{ cmd: "pragma block list" },
|
|
120
131
|
{ cmd: "pragma block list --all-tiers" },
|
|
121
|
-
{ cmd: "pragma block list --llm" },
|
|
132
|
+
{ cmd: "pragma block list --format llm" },
|
|
122
133
|
],
|
|
123
134
|
capability: {
|
|
124
135
|
needsStore: true,
|
|
@@ -138,7 +149,9 @@ const listVerb: VerbSpec<Record<string, unknown>, BlockRow[]> = {
|
|
|
138
149
|
const rows = await runSelect(rt, query, "block");
|
|
139
150
|
return rows.map((row) => ({
|
|
140
151
|
uri: row.component ?? "",
|
|
141
|
-
|
|
152
|
+
// An unnamed block still needs a token to show — fall back to its IRI's
|
|
153
|
+
// local name rather than rendering a blank row (A2).
|
|
154
|
+
name: row.name ?? localName(row.component ?? ""),
|
|
142
155
|
type: normalizeType(row.type),
|
|
143
156
|
tier: localName(row.tier ?? ""),
|
|
144
157
|
modifiers: row.modifiers ?? "",
|
|
@@ -9,6 +9,14 @@
|
|
|
9
9
|
* which rendered anatomy and modifiers without a flag. A derived name that maps
|
|
10
10
|
* onto no schema field is omitted (OPTIONAL parity), so a graph lacking
|
|
11
11
|
* whenToUse/whenNotToUse degrades gracefully.
|
|
12
|
+
*
|
|
13
|
+
* Disclosure declares the FULL canonical ladder `[summary, standard, detailed]`
|
|
14
|
+
* (B4), the same set `standard` declares, so a config `detail=standard` names a
|
|
15
|
+
* level `block` advertises rather than one it silently accepts-then-degrades.
|
|
16
|
+
* Block carries no `standard`-tier content of its own — gating is by canonical
|
|
17
|
+
* index, so `standard` resolves to the base view — but the declared set now
|
|
18
|
+
* matches its sibling reads. The per-noun DEFAULT stays domain-tuned (`block`
|
|
19
|
+
* rich-by-default, `standard` terse-by-default); only the level SET is aligned.
|
|
12
20
|
*/
|
|
13
21
|
|
|
14
22
|
import type { PackDefinition } from "../../kernel/packs/types.js";
|
|
@@ -145,7 +153,10 @@ export const blockPack: PackDefinition = {
|
|
|
145
153
|
],
|
|
146
154
|
},
|
|
147
155
|
],
|
|
148
|
-
disclosure: {
|
|
156
|
+
disclosure: {
|
|
157
|
+
levels: ["summary", "standard", "detailed"],
|
|
158
|
+
default: "detailed",
|
|
159
|
+
},
|
|
149
160
|
sample: {
|
|
150
161
|
fixedCount: true,
|
|
151
162
|
toolDescription:
|
|
@@ -173,8 +173,15 @@ describe("block list — hand-written tier/channel filtering", () => {
|
|
|
173
173
|
]);
|
|
174
174
|
});
|
|
175
175
|
|
|
176
|
-
it("--all-tiers
|
|
176
|
+
it("--all-tiers also reveals the untiered subcomponent (A2)", async () => {
|
|
177
|
+
// Button Icon is a ds:Subcomponent with NO ds:tier: the scoped/default view
|
|
178
|
+
// omits it (a block joins the list through its tier), but --all-tiers must
|
|
179
|
+
// surface every block, including untiered ones.
|
|
177
180
|
const rows = (await listVerb.run({ allTiers: true }, rt)) as BlockRow[];
|
|
178
|
-
expect(rows.map((r) => r.name).sort()).toEqual([
|
|
181
|
+
expect(rows.map((r) => r.name).sort()).toEqual([
|
|
182
|
+
"Button",
|
|
183
|
+
"Button Icon",
|
|
184
|
+
"Modal",
|
|
185
|
+
]);
|
|
179
186
|
});
|
|
180
187
|
});
|
|
@@ -99,10 +99,19 @@ describe("capabilities catalog — grammar-derived, drift-guarded (PROTECTED)",
|
|
|
99
99
|
expect(data.limits.condensed_available).toBe(false);
|
|
100
100
|
});
|
|
101
101
|
|
|
102
|
-
it("carries the
|
|
102
|
+
it("carries the four orientation conventions", () => {
|
|
103
103
|
expect(data.conventions.system.length).toBeGreaterThan(0);
|
|
104
104
|
expect(data.conventions.model).toContain("tier");
|
|
105
105
|
expect(data.conventions.querying).toContain("RDF");
|
|
106
|
+
expect(data.conventions.mutations.length).toBeGreaterThan(0);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it("surfaces the plan-first/confirm convention so the confirm gate isn't trial-and-error (D2)", () => {
|
|
110
|
+
// A mutating tool without `confirm` returns a plan and writes nothing; agents
|
|
111
|
+
// must learn this from orientation, not by tripping the gate.
|
|
112
|
+
const { mutations } = data.conventions;
|
|
113
|
+
expect(mutations).toMatch(/plan/i);
|
|
114
|
+
expect(mutations).toContain("confirm");
|
|
106
115
|
});
|
|
107
116
|
});
|
|
108
117
|
|
|
@@ -22,7 +22,7 @@ const capabilitiesVerb: VerbSpec<Record<string, unknown>, CapabilitiesData> = {
|
|
|
22
22
|
path: ["capabilities"],
|
|
23
23
|
summary:
|
|
24
24
|
"Discover pragma conventions, the annotated tool catalog, and the discovery sequence.",
|
|
25
|
-
doc: "Storeless orientation for agents. Returns the conventions (KG / tier-channel / SPARQL model), a
|
|
25
|
+
doc: "Storeless orientation for agents. Returns the conventions (KG / tier-channel / SPARQL model), a four-stage discovery sequence, and every live tool with a behavioural use_when hint and category — all derived from the live grammar, so it never drifts. Call it first at session start.",
|
|
26
26
|
params: [],
|
|
27
27
|
output: { formatters: capabilitiesFormatters },
|
|
28
28
|
examples: [
|
|
@@ -26,16 +26,20 @@ import type {
|
|
|
26
26
|
} from "./types.js";
|
|
27
27
|
|
|
28
28
|
/**
|
|
29
|
-
* The
|
|
30
|
-
* the KG / tier-channel / SPARQL
|
|
29
|
+
* The four orientation conventions. The first three (system/model/querying) are
|
|
30
|
+
* verbatim from the old shell — they describe the KG / tier-channel / SPARQL
|
|
31
|
+
* model and are still accurate for v2; `mutations` is new in v2, surfacing the
|
|
32
|
+
* plan-first/confirm gate.
|
|
31
33
|
*/
|
|
32
34
|
export const CONVENTIONS = {
|
|
33
35
|
system:
|
|
34
36
|
"Pragma is a CLI and MCP server for querying a design system knowledge graph — blocks, tokens, modifiers, standards, and ontologies.",
|
|
35
37
|
model:
|
|
36
|
-
"Data is scoped by tier (hierarchical, e.g. global > apps > apps/lxd) and channel (normal, experimental, prerelease). Set these via
|
|
38
|
+
"Data is scoped by tier (hierarchical, e.g. global > apps > apps/lxd) and channel (normal, experimental, prerelease). Set these via config_set (e.g. config_set tier apps/lxd, config_set channel experimental).",
|
|
37
39
|
querying:
|
|
38
40
|
"All queries run against an RDF triple store. Prefixed IRIs (e.g. ds:global.component.button) identify entities. Use ontology_list to discover namespaces.",
|
|
41
|
+
mutations:
|
|
42
|
+
"Mutating tools are plan-first: call once WITHOUT confirm to get a plan (meta.planOnly, no writes), then repeat the call with confirm: true to execute.",
|
|
39
43
|
} as const;
|
|
40
44
|
|
|
41
45
|
/** The output modes v2 renders (dropped "text" → "plain"; condensed retired). */
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* exists, or a hint's category disagrees with the tool's real `mutates` flag.
|
|
8
8
|
*
|
|
9
9
|
* `use_when` strings for the surviving tools are ported verbatim from the old
|
|
10
|
-
* shell's `domains/llm/data/toolCatalog.ts`; the net-new v2 tools (
|
|
10
|
+
* shell's `domains/llm/data/toolCatalog.ts`; the net-new v2 tools (config_set,
|
|
11
11
|
* graph_query, setup, upgrade, doctor, capabilities, prompt_*, token_add-config,
|
|
12
12
|
* sources_*, tier_lookup, create_*) are authored here.
|
|
13
13
|
*/
|
|
@@ -93,10 +93,15 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
|
|
|
93
93
|
use_when:
|
|
94
94
|
"Discovering loaded namespaces, prefixes, and class/property counts",
|
|
95
95
|
},
|
|
96
|
+
ontology_lookup: {
|
|
97
|
+
category: "read",
|
|
98
|
+
use_when:
|
|
99
|
+
"Exploring the full schema of a namespace by name — classes, properties, and hierarchy",
|
|
100
|
+
},
|
|
96
101
|
ontology_show: {
|
|
97
102
|
category: "read",
|
|
98
103
|
use_when:
|
|
99
|
-
"
|
|
104
|
+
"Deprecated alias of ontology_lookup — exploring a namespace's schema (prefer ontology_lookup)",
|
|
100
105
|
},
|
|
101
106
|
prompt_list: {
|
|
102
107
|
category: "read",
|
|
@@ -166,24 +171,10 @@ export const TOOL_HINTS: Record<string, ToolHint> = {
|
|
|
166
171
|
},
|
|
167
172
|
|
|
168
173
|
// — Write ——————————————————————————————————————————————————————————————————
|
|
169
|
-
config_tier: {
|
|
170
|
-
category: "write",
|
|
171
|
-
use_when: "Setting or resetting the active tier filter",
|
|
172
|
-
},
|
|
173
|
-
config_channel: {
|
|
174
|
-
category: "write",
|
|
175
|
-
use_when:
|
|
176
|
-
"Setting the release channel (normal, experimental, or prerelease)",
|
|
177
|
-
},
|
|
178
|
-
config_detail: {
|
|
179
|
-
category: "write",
|
|
180
|
-
use_when:
|
|
181
|
-
"Setting the default progressive-disclosure level (summary, standard, detailed)",
|
|
182
|
-
},
|
|
183
174
|
config_set: {
|
|
184
175
|
category: "write",
|
|
185
176
|
use_when:
|
|
186
|
-
"Setting any config field by name
|
|
177
|
+
"Setting any config field by name — tier, channel, or detail (e.g. `config set tier apps/lxd`)",
|
|
187
178
|
},
|
|
188
179
|
create_component: {
|
|
189
180
|
category: "write",
|
|
@@ -47,6 +47,8 @@ export interface CapabilitiesData {
|
|
|
47
47
|
readonly system: string;
|
|
48
48
|
readonly model: string;
|
|
49
49
|
readonly querying: string;
|
|
50
|
+
/** The plan-first/confirm gate every mutating tool follows (D2). */
|
|
51
|
+
readonly mutations: string;
|
|
50
52
|
};
|
|
51
53
|
readonly discovery_sequence: readonly DiscoveryStage[];
|
|
52
54
|
readonly tools: readonly CatalogTool[];
|
|
@@ -78,10 +78,37 @@ describe("colophon render golden — color-free (chalk.level = 0)", () => {
|
|
|
78
78
|
});
|
|
79
79
|
});
|
|
80
80
|
|
|
81
|
-
|
|
82
|
-
|
|
81
|
+
/** Run `body` with stdout's `isTTY` forced to `value`, then restore it. */
|
|
82
|
+
function withStdoutTty(value: boolean | undefined, body: () => void): void {
|
|
83
|
+
const stream = process.stdout as { isTTY?: boolean };
|
|
84
|
+
const saved = stream.isTTY;
|
|
85
|
+
stream.isTTY = value;
|
|
86
|
+
try {
|
|
87
|
+
body();
|
|
88
|
+
} finally {
|
|
89
|
+
stream.isTTY = saved;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
describe("colophon render — color ON (attended TTY)", () => {
|
|
94
|
+
it("plain emits ANSI escapes on a color-capable TTY", () => {
|
|
83
95
|
chalk.level = 1;
|
|
84
|
-
|
|
85
|
-
|
|
96
|
+
withStdoutTty(true, () => {
|
|
97
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: asserting the literal ESC byte is the point
|
|
98
|
+
expect(colophonFormatters.plain(FIXTURE)).toMatch(/\x1b\[/);
|
|
99
|
+
});
|
|
100
|
+
});
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
describe("colophon render — piped output is ANSI-free (F1)", () => {
|
|
104
|
+
it("plain emits ZERO ANSI off a TTY even when chalk reports color (CI/FORCE_COLOR)", () => {
|
|
105
|
+
// The CI leak: `supports-color` sets a non-zero level off a TTY under
|
|
106
|
+
// GITHUB_ACTIONS / FORCE_COLOR, so gating on `chalk.level` alone bled ANSI
|
|
107
|
+
// into `colophon --format plain | tee`. The isTTY gate closes it.
|
|
108
|
+
chalk.level = 3;
|
|
109
|
+
withStdoutTty(undefined, () => {
|
|
110
|
+
// biome-ignore lint/suspicious/noControlCharactersInRegex: asserting NO ESC byte survives
|
|
111
|
+
expect(colophonFormatters.plain(FIXTURE)).not.toMatch(/\x1b\[/);
|
|
112
|
+
});
|
|
86
113
|
});
|
|
87
114
|
});
|
|
@@ -9,23 +9,32 @@
|
|
|
9
9
|
* (which carries no leading H1) is never double-titled.
|
|
10
10
|
*/
|
|
11
11
|
|
|
12
|
+
import { defaultStyle, type RenderStyle } from "../../kernel/render/style.js";
|
|
12
13
|
import type { Formatters } from "../../kernel/spec/types.js";
|
|
13
14
|
import { renderMarkdownToTerminal } from "./markdownTerminal.js";
|
|
14
15
|
import type { ColophonData, ColophonSection } from "./types.js";
|
|
15
16
|
|
|
16
17
|
/** Render one section (title as H1 + body) as styled terminal Markdown. */
|
|
17
|
-
function plainSection(section: ColophonSection): string {
|
|
18
|
-
return renderMarkdownToTerminal(
|
|
18
|
+
function plainSection(section: ColophonSection, style: RenderStyle): string {
|
|
19
|
+
return renderMarkdownToTerminal(
|
|
20
|
+
`# ${section.title}\n\n${section.markdown}`,
|
|
21
|
+
style,
|
|
22
|
+
);
|
|
19
23
|
}
|
|
20
24
|
|
|
21
|
-
/** Render one section as condensed Markdown for `--llm` (summary preferred). */
|
|
25
|
+
/** Render one section as condensed Markdown for `--format llm` (summary preferred). */
|
|
22
26
|
function llmSection(section: ColophonSection): string {
|
|
23
27
|
return `## ${section.title}\n\n${section.summary ?? section.markdown}`;
|
|
24
28
|
}
|
|
25
29
|
|
|
26
30
|
export const colophonFormatters: Formatters<ColophonData> = {
|
|
27
31
|
plain(data) {
|
|
28
|
-
|
|
32
|
+
// Resolve the TTY decision ONCE (impure: reads stdout.isTTY + chalk level),
|
|
33
|
+
// then render every section through the same inert-off-a-TTY styler.
|
|
34
|
+
const style = defaultStyle();
|
|
35
|
+
return data.sections
|
|
36
|
+
.map((section) => plainSection(section, style))
|
|
37
|
+
.join("\n\n");
|
|
29
38
|
},
|
|
30
39
|
|
|
31
40
|
llm(data) {
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* Pins the covenant-exact emitted slice (`{ v:"colophon", mcp:"colophon" }`),
|
|
5
5
|
* proves the collector is storeless and combines pragma's built-in section with
|
|
6
6
|
* the active pack's (the bundled `block` design-system colophon), exercises the
|
|
7
|
-
* three formatter modes + `--format
|
|
7
|
+
* three formatter modes + `--format` precedence, holds CLI-json ≡ MCP
|
|
8
8
|
* parity, and checks the pack-grammar accepts a `colophon` field (rejecting a
|
|
9
9
|
* non-string).
|
|
10
10
|
*/
|
|
@@ -47,7 +47,7 @@ const FLAGS: GlobalFlags = {
|
|
|
47
47
|
verbose: false,
|
|
48
48
|
};
|
|
49
49
|
const FLAGS_JSON: GlobalFlags = { ...FLAGS, format: "json" };
|
|
50
|
-
const FLAGS_LLM: GlobalFlags = { ...FLAGS, llm: true };
|
|
50
|
+
const FLAGS_LLM: GlobalFlags = { ...FLAGS, format: "llm", llm: true };
|
|
51
51
|
const NO_MUT = { dryRun: false, undo: false, yes: false };
|
|
52
52
|
|
|
53
53
|
/** A deterministic two-section fixture for pure formatter assertions. */
|
|
@@ -170,7 +170,7 @@ describe("colophon — formatter modes", () => {
|
|
|
170
170
|
expect((envelope.data as ColophonData).sections[0]?.kind).toBe("pragma");
|
|
171
171
|
});
|
|
172
172
|
|
|
173
|
-
it("--llm selects the condensed Markdown form", async () => {
|
|
173
|
+
it("--format llm selects the condensed Markdown form", async () => {
|
|
174
174
|
const outcome = await executeVerb(
|
|
175
175
|
colophonVerb,
|
|
176
176
|
{},
|
|
@@ -20,7 +20,10 @@ const colophonVerb: VerbSpec<Record<string, unknown>, ColophonData> = {
|
|
|
20
20
|
output: { formatters: colophonFormatters },
|
|
21
21
|
examples: [
|
|
22
22
|
{ cmd: "pragma colophon", note: "the toolchain + active domain story" },
|
|
23
|
-
{
|
|
23
|
+
{
|
|
24
|
+
cmd: "pragma colophon --format llm",
|
|
25
|
+
note: "condensed Markdown for agents",
|
|
26
|
+
},
|
|
24
27
|
],
|
|
25
28
|
capability: {
|
|
26
29
|
needsStore: false,
|