@canonical/pragma-cli 0.32.0 → 0.34.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +72 -0
- package/dist/pragma +0 -0
- package/package.json +19 -15
- package/pragma.conf.ts +664 -0
- package/src/bin.ts +15 -6
- package/src/capabilities/block.parity.test.ts +250 -0
- package/src/capabilities/capabilities/capabilities.render.ts +3 -2
- package/src/capabilities/capabilities/capabilities.test.ts +10 -1
- package/src/capabilities/capabilities/capabilities.verb.ts +32 -14
- package/src/capabilities/capabilities/catalog.ts +20 -8
- package/src/capabilities/capabilities/hints.ts +21 -36
- package/src/capabilities/capabilities/types.ts +2 -0
- package/src/capabilities/colophon/collectColophon.ts +34 -19
- package/src/capabilities/colophon/colophon.render.test.ts +31 -4
- package/src/capabilities/colophon/colophon.render.ts +13 -4
- package/src/capabilities/colophon/colophon.test.ts +3 -3
- package/src/capabilities/colophon/colophon.verb.ts +11 -4
- package/src/capabilities/colophon/markdownTerminal.ts +33 -14
- package/src/capabilities/colophon/types.ts +16 -7
- package/src/capabilities/config/collectConfigShow.test.ts +57 -0
- package/src/capabilities/config/collectConfigShow.ts +41 -4
- package/src/capabilities/config/fields.ts +10 -108
- package/src/capabilities/config/set.test.ts +6 -6
- package/src/capabilities/config/set.verb.ts +12 -8
- package/src/capabilities/config/show.render.test.ts +137 -0
- package/src/capabilities/config/show.render.ts +57 -16
- package/src/capabilities/config/show.verb.ts +9 -8
- package/src/capabilities/config/types.ts +8 -0
- package/src/capabilities/create/byteEquality.subprocess.test.ts +1 -1
- package/src/capabilities/create/byteEquality.test.ts +82 -101
- package/src/capabilities/create/compiledCreate.subprocess.test.ts +53 -16
- package/src/capabilities/create/constants.ts +75 -0
- package/src/capabilities/create/create.mcp.test.ts +2 -2
- package/src/capabilities/create/create.test.ts +192 -16
- package/src/capabilities/create/create.verb.ts +208 -79
- package/src/capabilities/create/index.ts +2 -10
- package/src/capabilities/create/pickGenerator.ts +34 -28
- package/src/capabilities/create/templates.embedded.generated.ts +4 -29
- package/src/capabilities/create/types.ts +10 -6
- package/src/capabilities/{standard/parity.test.ts → distribution.parity.test.ts} +24 -14
- package/src/capabilities/distribution.test.ts +120 -0
- package/src/capabilities/distribution.ts +59 -0
- package/src/capabilities/doctor/checks/checkConfigFile.ts +6 -5
- package/src/capabilities/doctor/checks/checkKeStore.ts +2 -2
- package/src/capabilities/doctor/checks/checkMcpCommands.ts +6 -0
- package/src/capabilities/doctor/checks/checkMcpConfigured.ts +15 -8
- package/src/capabilities/doctor/checks/checkPackageRefs.ts +79 -45
- package/src/capabilities/doctor/checks/checkPragmaVersion.ts +3 -2
- package/src/capabilities/doctor/checks/checkShellCompletions.test.ts +61 -11
- package/src/capabilities/doctor/checks/checkShellCompletions.ts +63 -23
- package/src/capabilities/doctor/checks/checkSkillsSymlinked.ts +3 -2
- package/src/capabilities/doctor/checks/deriveBand.test.ts +42 -0
- package/src/capabilities/doctor/checks/deriveBand.ts +28 -0
- package/src/capabilities/doctor/doctor.render.test.ts +170 -0
- package/src/capabilities/doctor/doctor.render.ts +117 -34
- package/src/capabilities/doctor/doctor.test.ts +68 -9
- package/src/capabilities/doctor/doctor.verb.ts +6 -2
- package/src/capabilities/doctor/runChecks.ts +33 -9
- package/src/capabilities/doctor/types.ts +16 -1
- package/src/capabilities/emptyRecovery.test.ts +32 -15
- package/src/capabilities/graph/inspect.verb.ts +3 -2
- package/src/capabilities/graph/query.verb.ts +3 -2
- package/src/capabilities/graph/runQuery.ts +8 -5
- package/src/capabilities/index.ts +22 -18
- package/src/capabilities/info/collectInfo.ts +10 -6
- package/src/capabilities/info/info.render.ts +3 -2
- package/src/capabilities/info/info.verb.ts +3 -2
- package/src/capabilities/lazy.test.ts +93 -2
- package/src/capabilities/ontology/index.ts +10 -3
- package/src/capabilities/ontology/ontology.test.ts +157 -5
- package/src/capabilities/ontology/verbs.ts +203 -104
- package/src/capabilities/prompt/prompt.render.test.ts +94 -0
- package/src/capabilities/prompt/prompt.render.ts +23 -6
- package/src/capabilities/prompt/prompt.test.ts +68 -1
- package/src/capabilities/prompt/verbs.ts +24 -11
- package/src/capabilities/reference.test.ts +32 -0
- package/src/capabilities/resources/provider.ts +58 -25
- package/src/capabilities/resources/resources.test.ts +176 -21
- package/src/capabilities/scopeTypeSync.test.ts +37 -0
- package/src/capabilities/setup/index.ts +7 -1
- package/src/capabilities/setup/operations/setupCompletions.ts +49 -16
- package/src/capabilities/setup/operations/setupGenerator.ts +166 -63
- package/src/capabilities/setup/operations/setupLsp.ts +56 -12
- package/src/capabilities/setup/operations/setupMcp.ts +216 -39
- package/src/capabilities/setup/operations/setupSkills.ts +10 -4
- package/src/capabilities/setup/setup.render.test.ts +109 -0
- package/src/capabilities/setup/setup.render.ts +61 -14
- package/src/capabilities/setup/setup.test.ts +352 -1
- package/src/capabilities/setup/setup.verb.ts +68 -9
- package/src/capabilities/setup/shell.ts +29 -3
- package/src/capabilities/setup/types.ts +59 -2
- package/src/capabilities/shared/bands.ts +16 -0
- package/src/capabilities/skill/discover.ts +11 -5
- package/src/capabilities/skill/render.ts +4 -5
- package/src/capabilities/skill/verbs.ts +7 -7
- package/src/capabilities/sources/collectStatus.ts +69 -49
- package/src/capabilities/sources/index.ts +1 -1
- package/src/capabilities/sources/installSkills.ts +2 -2
- package/src/capabilities/sources/runUpdate.ts +146 -98
- package/src/capabilities/sources/sources.test.ts +264 -84
- package/src/capabilities/sources/status.render.test.ts +94 -0
- package/src/capabilities/sources/status.render.ts +55 -33
- package/src/capabilities/sources/status.verb.ts +15 -8
- package/src/capabilities/sources/types.ts +18 -25
- package/src/capabilities/sources/update.render.ts +9 -2
- package/src/capabilities/sources/update.verb.ts +12 -23
- package/src/capabilities/surface.test.ts +65 -17
- package/src/capabilities/tier.parity.test.ts +144 -0
- package/src/capabilities/upgrade/upgrade.verb.ts +7 -3
- package/src/constants.test.ts +9 -7
- package/src/constants.ts +49 -23
- package/src/identity.test.ts +333 -0
- package/src/index.ts +2 -2
- package/src/kernel/completion/__snapshots__/emitScripts.test.ts.snap +18 -16
- package/src/kernel/completion/complete.test.ts +3 -5
- package/src/kernel/completion/emitScripts.test.ts +1 -1
- package/src/kernel/completion/emitScripts.ts +4 -2
- package/src/kernel/completion/entitySource.test.ts +185 -41
- package/src/kernel/completion/entitySource.ts +131 -113
- package/src/kernel/completion/model.test.ts +3 -3
- package/src/kernel/completion/model.ts +11 -1
- package/src/kernel/completion/parse.test.ts +2 -4
- package/src/kernel/completion/safety.test.ts +82 -36
- package/src/kernel/completion/shellDrive.test.ts +592 -0
- package/src/kernel/completion/templates/fish.ts +28 -6
- package/src/kernel/completion/types.ts +3 -3
- package/src/kernel/config/__snapshots__/firstRun.test.ts.snap +1 -1
- package/src/kernel/config/defaults.test.ts +85 -0
- package/src/kernel/config/defaults.ts +12 -23
- package/src/kernel/config/evaluateProjectConfig.ts +2 -3
- package/src/kernel/config/findProjectConfig.ts +4 -3
- package/src/kernel/config/firstRun.ts +12 -5
- package/src/kernel/config/globalConfig.ts +2 -2
- package/src/kernel/config/index.ts +4 -5
- package/src/kernel/config/paths.ts +17 -17
- package/src/kernel/config/readConfig.test.ts +242 -6
- package/src/kernel/config/readConfig.ts +8 -8
- package/src/kernel/config/schema.test.ts +50 -0
- package/src/kernel/config/schema.ts +97 -10
- package/src/kernel/config/types.ts +73 -19
- package/src/kernel/copy.test.ts +453 -0
- package/src/kernel/error/PragmaError.test.ts +5 -5
- package/src/kernel/error/constants.ts +3 -2
- package/src/kernel/error/index.ts +1 -1
- package/src/kernel/error/recovery.test.ts +22 -45
- package/src/kernel/error/recovery.ts +22 -27
- package/src/kernel/error/renderError.ts +1 -1
- package/src/kernel/error/types.ts +5 -4
- package/src/kernel/packs/collect.test.ts +195 -11
- package/src/kernel/packs/collect.ts +213 -58
- package/src/kernel/packs/compile.test.ts +163 -7
- package/src/kernel/packs/compile.ts +15 -12
- package/src/kernel/packs/disclosure.test.ts +5 -2
- package/src/kernel/packs/graphql/fetchGraphqlLookup.ts +11 -10
- package/src/kernel/packs/graphqlSource.test.ts +6 -3
- package/src/kernel/packs/iri.ts +5 -7
- package/src/kernel/packs/renderPack.ts +5 -3
- package/src/kernel/packs/resolveEntity.ts +21 -17
- package/src/kernel/packs/runBodies.ts +15 -14
- package/src/kernel/packs/schema.ts +96 -10
- package/src/kernel/packs/sparql/runSelect.test.ts +104 -10
- package/src/kernel/packs/sparql/runSelect.ts +73 -34
- package/src/kernel/packs/toolDescription.test.ts +10 -2
- package/src/kernel/packs/types.ts +39 -4
- package/src/kernel/packs/uniqueness.ts +4 -4
- package/src/kernel/project/cli/buildProgram.test.ts +60 -11
- package/src/kernel/project/cli/buildProgram.ts +11 -0
- package/src/kernel/project/cli/dispatch.test.ts +67 -0
- package/src/kernel/project/cli/dispatch.ts +34 -12
- package/src/kernel/project/cli/globalFlags.test.ts +12 -4
- package/src/kernel/project/cli/globalFlags.ts +14 -14
- package/src/kernel/project/cli/helpFormat.ts +1 -1
- package/src/kernel/project/cli/rootHelp.ts +109 -107
- package/src/kernel/project/mcp/buildServer.ts +11 -6
- package/src/kernel/project/mcp/instructions.ts +12 -7
- package/src/kernel/project/mcp/mcpError.ts +52 -0
- package/src/kernel/project/mcp/parity.test.ts +39 -1
- package/src/kernel/project/mcp/prompts/prompts.test.ts +128 -5
- package/src/kernel/project/mcp/prompts/provider.ts +51 -7
- package/src/kernel/project/mcp/prompts/source.ts +68 -67
- package/src/kernel/project/mcp/registerVerb.ts +25 -6
- package/src/kernel/project/mcp/serve.ts +11 -3
- package/src/kernel/project/mcp/toolDescriptions.test.ts +6 -2
- package/src/kernel/render/disclosure.ts +4 -2
- package/src/kernel/render/formatters.ts +5 -4
- package/src/kernel/render/prefixes.test.ts +36 -0
- package/src/kernel/render/prefixes.ts +40 -13
- package/src/kernel/render/renderers.test.ts +35 -1
- package/src/kernel/render/renderers.ts +25 -7
- package/src/kernel/render/style.test.ts +31 -0
- package/src/kernel/render/style.ts +74 -0
- package/src/kernel/runtime/graphpack/build.ts +57 -3
- package/src/kernel/runtime/graphpack/buildIndex.test.ts +149 -0
- package/src/kernel/runtime/graphpack/buildIndex.ts +173 -50
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +5 -8
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +3 -4
- package/src/kernel/runtime/graphpack/embedded/pack.stories.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +37 -11
- package/src/kernel/runtime/graphpack/graphpack.test.ts +274 -6
- package/src/kernel/runtime/graphpack/hash.ts +3 -2
- package/src/kernel/runtime/graphpack/index.ts +1 -1
- package/src/kernel/runtime/graphpack/manifest.ts +114 -12
- package/src/kernel/runtime/graphpack/read.ts +32 -22
- package/src/kernel/runtime/graphpack/schemas.ts +74 -0
- package/src/kernel/runtime/graphpack/stories.ts +84 -0
- package/src/kernel/runtime/graphpack/types.ts +72 -45
- package/src/kernel/runtime/loadSession.ts +4 -12
- package/src/kernel/runtime/paths.ts +64 -15
- package/src/kernel/runtime/probe.test.ts +8 -4
- package/src/kernel/runtime/readEntity.ts +6 -5
- package/src/kernel/runtime/refs/gitOps.ts +1 -19
- package/src/kernel/runtime/refs/parseRef.ts +7 -7
- package/src/kernel/runtime/refs/resolve.ts +141 -65
- package/src/kernel/runtime/resolveSources.test.ts +63 -24
- package/src/kernel/runtime/resolveSources.ts +36 -25
- package/src/kernel/runtime/storeReadiness.ts +62 -0
- package/src/kernel/runtime/storeless.test.ts +1 -1
- package/src/kernel/runtime/types.ts +1 -1
- package/src/kernel/runtime/wasmEmbed.test.ts +25 -20
- package/src/kernel/spec/emitReference.test.ts +196 -0
- package/src/kernel/spec/emitReference.ts +624 -0
- package/src/kernel/spec/emitSurface.ts +13 -7
- package/src/kernel/spec/index.ts +2 -0
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/surfaceConformance.ts +8 -0
- package/src/kernel/spec/types.ts +29 -9
- package/src/kernel/spec/validate.ts +2 -1
- package/src/kernel/vocabulary.test.ts +164 -0
- package/src/kernel/vocabulary.ts +146 -0
- package/src/testing/PARITY_GAPS.ts +9 -3
- package/src/testing/behavioral/README.md +3 -3
- package/src/testing/behavioral/autoLlm.e2e.test.ts +12 -2
- package/src/testing/behavioral/completion.test.ts +21 -8
- package/src/testing/behavioral/disclosure.test.ts +12 -0
- package/src/testing/behavioral/docExamples.test.ts +283 -0
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +4 -3
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +3 -2
- package/src/testing/behavioral/exitCodes.e2e.test.ts +6 -5
- package/src/testing/behavioral/harness.foundation.test.ts +14 -5
- package/src/testing/behavioral/journeys.cli.test.ts +116 -73
- package/src/testing/behavioral/journeys.defaultPack.test.ts +477 -0
- package/src/testing/behavioral/journeys.firstInstall.test.ts +167 -0
- package/src/testing/behavioral/journeys.interactiveTty.e2e.test.ts +66 -0
- package/src/testing/behavioral/journeys.livePack.e2e.test.ts +129 -0
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +179 -0
- package/src/testing/behavioral/journeys.packageStories.test.ts +188 -0
- package/src/testing/behavioral/liveReadSurface.ts +4 -3
- package/src/testing/behavioral/mcpSurface.test.ts +10 -5
- package/src/testing/behavioral/staleCommands.test.ts +86 -0
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +9 -9
- package/src/testing/eval/cases/assembly.ts +16 -9
- package/src/testing/eval/cases/effectful.ts +31 -16
- package/src/testing/eval/cases/readNouns.ts +23 -18
- package/src/testing/eval/cases/stable.ts +5 -1
- package/src/testing/eval/eval.test.ts +7 -1
- package/src/testing/fixtures/fixtureCapability.ts +201 -1
- package/src/testing/fixtures/graph/canonical.ts +6 -3
- package/src/testing/fixtures/graph/defaultPack.ts +179 -0
- package/src/testing/fixtures/graph/owlExport.ts +97 -0
- package/src/testing/helpers/extractSnippets.ts +29 -0
- package/src/testing/helpers/fixtureGraph.ts +33 -16
- package/src/testing/helpers/golden.ts +1 -1
- package/src/testing/helpers/packRuntime.ts +2 -3
- package/src/testing/helpers/projectCli.ts +2 -1
- package/src/testing/helpers/projectMcp.ts +6 -0
- package/src/testing/perf/budgets.test.ts +4 -0
- package/src/testing/perf/budgets.ts +22 -7
- package/src/testing/perf/globalSetup.ts +121 -6
- package/src/types.ts +2 -2
- package/src/capabilities/block/blockList.verb.ts +0 -150
- package/src/capabilities/block/index.ts +0 -25
- package/src/capabilities/block/pack.ts +0 -155
- package/src/capabilities/block/parity.test.ts +0 -180
- package/src/capabilities/block/tierChain.ts +0 -78
- package/src/capabilities/colophon/pragmaColophon.ts +0 -66
- package/src/capabilities/config/field.test.ts +0 -188
- package/src/capabilities/modifier/index.ts +0 -15
- package/src/capabilities/modifier/pack.ts +0 -64
- package/src/capabilities/standard/index.ts +0 -18
- package/src/capabilities/standard/pack.ts +0 -128
- package/src/capabilities/tier/index.ts +0 -19
- package/src/capabilities/tier/lookup.render.ts +0 -34
- package/src/capabilities/tier/lookup.test.ts +0 -68
- package/src/capabilities/tier/lookup.verb.ts +0 -44
- package/src/capabilities/tier/pack.ts +0 -28
- package/src/capabilities/tier/runLookup.ts +0 -59
- package/src/capabilities/token/addConfig.render.ts +0 -27
- package/src/capabilities/token/addConfig.test.ts +0 -62
- package/src/capabilities/token/addConfig.verb.ts +0 -40
- package/src/capabilities/token/index.ts +0 -19
- package/src/capabilities/token/pack.ts +0 -58
- package/src/capabilities/token/runAddConfig.ts +0 -95
- package/src/kernel/completion/bashDrive.test.ts +0 -73
- package/src/kernel/runtime/graphpack/embedded/sample.ttl +0 -39
- package/src/kernel/runtime/lock.ts +0 -90
|
@@ -1,24 +1,139 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Vitest global setup: build the compiled `dist/pragma` once, before the suite,
|
|
3
|
-
* if it is missing.
|
|
3
|
+
* if it is missing OR older than the sources it was built from.
|
|
4
4
|
*
|
|
5
5
|
* Shared by both configs, because two suites spawn the binary: the perf budgets
|
|
6
6
|
* (src/testing/perf/**, `test:perf`) and the storeless-guarantee guards in
|
|
7
7
|
* src/kernel/completion/safety.test.ts (the main `test:vitest` pass). Wiring it
|
|
8
8
|
* into both means `bun run test` on a clean checkout provisions the binary with
|
|
9
9
|
* no manual build step — neither suite may assume it is pre-built.
|
|
10
|
+
*
|
|
11
|
+
* STALENESS, not mere existence. `existsSync` alone meant every spawned-binary
|
|
12
|
+
* guard ran against whatever binary happened to be on disk: a mutation to
|
|
13
|
+
* `bin.ts` making the storeless `__complete` path write to `$HOME` and boot the
|
|
14
|
+
* store — precisely what `safety.test.ts` is PROTECTED to forbid — was live
|
|
15
|
+
* when run from source and invisible through the gate. The guard whose own
|
|
16
|
+
* docblock warns that an unreachable bundle "would leave the whole suite green"
|
|
17
|
+
* was itself green against a binary that predated the change under test.
|
|
18
|
+
*
|
|
19
|
+
* STALENESS INCLUDES THE EMBEDDED WORKSPACE DEPS. The binary bundles every
|
|
20
|
+
* `@canonical/*` workspace package it (transitively) imports — task, summon-*,
|
|
21
|
+
* ke, … — so an edit to `packages/runtime/task` is a change to what
|
|
22
|
+
* `dist/pragma` runs, yet this package's own `src` never moves. The gate
|
|
23
|
+
* therefore also watches each workspace-linked dependency (found by following
|
|
24
|
+
* the `node_modules/<name>` symlinks, transitively) — its `src` (the authored
|
|
25
|
+
* source), `dist` (what the bundler actually embeds, per the dep's export map)
|
|
26
|
+
* and `package.json`. Registry deps resolve into a `node_modules` store path
|
|
27
|
+
* and are skipped: their content only changes with a lockfile change, which
|
|
28
|
+
* `bun install` surfaces as a fresh symlink mtime anyway. Content hashing of
|
|
29
|
+
* the build inputs was considered and DECLINED (PR7 ruling R5): an mtime
|
|
30
|
+
* comparison is a handful of stats, needs no state file to compare against,
|
|
31
|
+
* and the failure mode of a false "stale" is one redundant 10 s build — while
|
|
32
|
+
* hashing every input on every suite start costs more than the rebuild it
|
|
33
|
+
* avoids. CI is fresh-checkout either way; this gate exists for developer
|
|
34
|
+
* boxes that have built before.
|
|
10
35
|
*/
|
|
11
36
|
|
|
12
37
|
import { spawnSync } from "node:child_process";
|
|
13
|
-
import {
|
|
38
|
+
import { lstatSync, readdirSync, realpathSync, statSync } from "node:fs";
|
|
39
|
+
import { join, sep } from "node:path";
|
|
14
40
|
import { fileURLToPath } from "node:url";
|
|
15
41
|
|
|
42
|
+
/** Everything the compiled binary is built FROM, relative to the package root. */
|
|
43
|
+
const INPUTS = ["src", "scripts", "pragma.conf.ts", "package.json"];
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* What feeds the binary inside one workspace dependency: its authored source,
|
|
47
|
+
* the compiled output the bundler embeds, and its manifest. Deliberately NOT
|
|
48
|
+
* the whole package dir — that would watch `coverage/` and `node_modules/`,
|
|
49
|
+
* whose mtimes move on every test run and would force a rebuild loop.
|
|
50
|
+
*/
|
|
51
|
+
const DEP_INPUTS = ["src", "dist", "package.json"];
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* The newest modification time under `path`, or 0 when it does not exist.
|
|
55
|
+
* A nested `node_modules` is never descended into (a dep's own deps are
|
|
56
|
+
* enumerated as roots of their own by {@link workspaceDepRoots}).
|
|
57
|
+
*
|
|
58
|
+
* @param path - A file or directory.
|
|
59
|
+
* @returns Epoch milliseconds of the newest entry beneath it.
|
|
60
|
+
* @note Impure — stats the source tree.
|
|
61
|
+
*/
|
|
62
|
+
function newestMtime(path: string): number {
|
|
63
|
+
let stats: ReturnType<typeof statSync>;
|
|
64
|
+
try {
|
|
65
|
+
stats = statSync(path);
|
|
66
|
+
} catch {
|
|
67
|
+
return 0;
|
|
68
|
+
}
|
|
69
|
+
if (!stats.isDirectory()) return stats.mtimeMs;
|
|
70
|
+
let newest = stats.mtimeMs;
|
|
71
|
+
for (const entry of readdirSync(path)) {
|
|
72
|
+
if (entry === "node_modules") continue;
|
|
73
|
+
newest = Math.max(newest, newestMtime(join(path, entry)));
|
|
74
|
+
}
|
|
75
|
+
return newest;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Every workspace package the binary can embed, transitively: follow each
|
|
80
|
+
* `node_modules/<name>` symlink whose target lives OUTSIDE any `node_modules`
|
|
81
|
+
* store (that is what distinguishes a workspace link from a registry install),
|
|
82
|
+
* then repeat from the target's own `node_modules`. Cycles are cut by the
|
|
83
|
+
* visited set.
|
|
84
|
+
*
|
|
85
|
+
* @param pkgRoot - This package's root directory.
|
|
86
|
+
* @returns Absolute real paths of the linked workspace dependency roots.
|
|
87
|
+
* @note Impure — reads `node_modules` link farms.
|
|
88
|
+
*/
|
|
89
|
+
function workspaceDepRoots(pkgRoot: string): string[] {
|
|
90
|
+
const visited = new Set<string>([realpathSync(pkgRoot)]);
|
|
91
|
+
const queue = [realpathSync(pkgRoot)];
|
|
92
|
+
while (queue.length > 0) {
|
|
93
|
+
const dir = queue.pop() as string;
|
|
94
|
+
const nm = join(dir, "node_modules");
|
|
95
|
+
let entries: string[];
|
|
96
|
+
try {
|
|
97
|
+
entries = readdirSync(nm);
|
|
98
|
+
} catch {
|
|
99
|
+
continue;
|
|
100
|
+
}
|
|
101
|
+
const links = entries.flatMap((entry) => {
|
|
102
|
+
if (!entry.startsWith("@")) return [join(nm, entry)];
|
|
103
|
+
try {
|
|
104
|
+
return readdirSync(join(nm, entry)).map((e) => join(nm, entry, e));
|
|
105
|
+
} catch {
|
|
106
|
+
return [];
|
|
107
|
+
}
|
|
108
|
+
});
|
|
109
|
+
for (const link of links) {
|
|
110
|
+
let real: string;
|
|
111
|
+
try {
|
|
112
|
+
if (!lstatSync(link).isSymbolicLink()) continue;
|
|
113
|
+
real = realpathSync(link);
|
|
114
|
+
} catch {
|
|
115
|
+
continue;
|
|
116
|
+
}
|
|
117
|
+
if (real.split(sep).includes("node_modules")) continue;
|
|
118
|
+
if (visited.has(real)) continue;
|
|
119
|
+
visited.add(real);
|
|
120
|
+
queue.push(real);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
const root = realpathSync(pkgRoot);
|
|
124
|
+
return [...visited].filter((dir) => dir !== root);
|
|
125
|
+
}
|
|
126
|
+
|
|
16
127
|
export default function setup(): void {
|
|
17
128
|
const root = fileURLToPath(new URL("../../../", import.meta.url));
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
129
|
+
const built = newestMtime(join(root, "dist", "pragma"));
|
|
130
|
+
const fresh =
|
|
131
|
+
built > 0 &&
|
|
132
|
+
INPUTS.every((input) => newestMtime(join(root, input)) < built) &&
|
|
133
|
+
workspaceDepRoots(root).every((dep) =>
|
|
134
|
+
DEP_INPUTS.every((input) => newestMtime(join(dep, input)) < built),
|
|
135
|
+
);
|
|
136
|
+
if (fresh) return;
|
|
22
137
|
|
|
23
138
|
const result = spawnSync("bun", ["run", "scripts/build.ts"], {
|
|
24
139
|
cwd: root,
|
package/src/types.ts
CHANGED
|
@@ -12,8 +12,8 @@ export type { DetailLevel, OutputFormat } from "./constants.js";
|
|
|
12
12
|
// — Config authoring ————————————————————————————————————————————————————————
|
|
13
13
|
export type {
|
|
14
14
|
Channel,
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
PackDeclaration,
|
|
16
|
+
PackSource,
|
|
17
17
|
PragmaConfig,
|
|
18
18
|
} from "./kernel/config/types.js";
|
|
19
19
|
// — Error model —————————————————————————————————————————————————————————————
|
|
@@ -1,150 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `block list` — the ONE hand-written read (Risk5).
|
|
3
|
-
*
|
|
4
|
-
* A block list is config-filtered in ways packs cannot express declaratively:
|
|
5
|
-
* the tier CHAIN (a tier makes its whole parent chain visible), the channel
|
|
6
|
-
* (release-level visibility), and the `--all-tiers` escape. Everything else in
|
|
7
|
-
* PR3 is a declarative pack; this verb ports that behaviour directly. It reads
|
|
8
|
-
* the effective tier/channel from config and runs one generated SELECT (no user
|
|
9
|
-
* input in the query — the tier is config, the channel a closed enum).
|
|
10
|
-
*/
|
|
11
|
-
|
|
12
|
-
import { runSelect } from "../../kernel/packs/sparql/runSelect.js";
|
|
13
|
-
import type {
|
|
14
|
-
ColumnDef,
|
|
15
|
-
RenderListOptions,
|
|
16
|
-
} from "../../kernel/render/contracts.js";
|
|
17
|
-
import { DEFAULT_PREFIX_MAP } from "../../kernel/render/prefixes.js";
|
|
18
|
-
import {
|
|
19
|
-
renderListLlm,
|
|
20
|
-
renderListPlain,
|
|
21
|
-
} from "../../kernel/render/renderers.js";
|
|
22
|
-
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
23
|
-
import { asVerb } from "../../kernel/spec/asVerb.js";
|
|
24
|
-
import type { Formatters, VerbSpec } from "../../kernel/spec/types.js";
|
|
25
|
-
import { buildChannelFilter, buildTierFilter } from "./tierChain.js";
|
|
26
|
-
|
|
27
|
-
/** One block summary row. */
|
|
28
|
-
export interface BlockRow {
|
|
29
|
-
readonly uri: string;
|
|
30
|
-
readonly name: string;
|
|
31
|
-
readonly type: string;
|
|
32
|
-
readonly tier: string;
|
|
33
|
-
readonly modifiers: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
/** Local name of an IRI (`…/global` → `global`; `…#Thing` → `Thing`). */
|
|
37
|
-
function localName(uri: string): string {
|
|
38
|
-
const hash = uri.lastIndexOf("#");
|
|
39
|
-
if (hash !== -1) return uri.slice(hash + 1);
|
|
40
|
-
const slash = uri.lastIndexOf("/");
|
|
41
|
-
return slash !== -1 ? uri.slice(slash + 1) : uri;
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
/** Normalize a block's rdf:type to one of the four block kinds. */
|
|
45
|
-
function normalizeType(typeUri: string | undefined): BlockRow["type"] {
|
|
46
|
-
const local = localName(typeUri ?? "").toLowerCase();
|
|
47
|
-
return local === "pattern" || local === "layout" || local === "subcomponent"
|
|
48
|
-
? local
|
|
49
|
-
: "component";
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
const listColumns: ColumnDef<BlockRow>[] = [
|
|
53
|
-
{ key: "name", label: "Name" },
|
|
54
|
-
{ key: "type", label: "Type" },
|
|
55
|
-
{ key: "tier", label: "Tier" },
|
|
56
|
-
{ key: "modifiers", label: "Modifiers", showWhenEmpty: false },
|
|
57
|
-
{ key: "uri", label: "IRI" },
|
|
58
|
-
];
|
|
59
|
-
|
|
60
|
-
const listOptions: RenderListOptions<BlockRow> = {
|
|
61
|
-
heading: "Blocks",
|
|
62
|
-
columns: listColumns,
|
|
63
|
-
prefixes: DEFAULT_PREFIX_MAP,
|
|
64
|
-
// Zero blocks is a calm success (exit 0), not an error — the list is scoped by
|
|
65
|
-
// the active tier chain and channel, so the message names that scope and the
|
|
66
|
-
// hint offers the escape hatches (a cold store fails earlier with
|
|
67
|
-
// STORE_UNAVAILABLE, so reaching here means the store is built).
|
|
68
|
-
emptyMessage: "No blocks found under the current tier and channel.",
|
|
69
|
-
emptyHint:
|
|
70
|
-
"Use --all-tiers to ignore the tier filter, or run `pragma sources update` if the store is empty.",
|
|
71
|
-
};
|
|
72
|
-
|
|
73
|
-
const listFormatters: Formatters<BlockRow[]> = {
|
|
74
|
-
plain: (rows) => renderListPlain(rows, listOptions),
|
|
75
|
-
llm: (rows) => renderListLlm(rows, listOptions),
|
|
76
|
-
json: (rows) => JSON.stringify(rows, null, 2),
|
|
77
|
-
};
|
|
78
|
-
|
|
79
|
-
/** Build the config-filtered block SELECT for the resolved tier/channel. */
|
|
80
|
-
function buildQuery(
|
|
81
|
-
tier: string | undefined,
|
|
82
|
-
channel: import("../../kernel/config/types.js").Channel,
|
|
83
|
-
allTiers: boolean,
|
|
84
|
-
): string {
|
|
85
|
-
const tierFilter = allTiers ? "" : buildTierFilter(tier);
|
|
86
|
-
const channelFilter = buildChannelFilter(channel);
|
|
87
|
-
return [
|
|
88
|
-
"SELECT ?component ?type ?name ?tier",
|
|
89
|
-
' (GROUP_CONCAT(DISTINCT ?modName; separator=", ") AS ?modifiers)',
|
|
90
|
-
"WHERE {",
|
|
91
|
-
" VALUES ?type { ds:Component ds:Pattern ds:Layout ds:Subcomponent }",
|
|
92
|
-
" ?component a ?type ;",
|
|
93
|
-
" ds:name ?name ;",
|
|
94
|
-
" ds:tier ?tier .",
|
|
95
|
-
tierFilter ? ` ${tierFilter}` : "",
|
|
96
|
-
` ${channelFilter}`,
|
|
97
|
-
" OPTIONAL { ?component ds:hasModifierFamily ?mod . ?mod ds:name ?modName }",
|
|
98
|
-
"}",
|
|
99
|
-
"GROUP BY ?component ?type ?name ?tier",
|
|
100
|
-
"ORDER BY ?name",
|
|
101
|
-
]
|
|
102
|
-
.filter((line) => line !== "")
|
|
103
|
-
.join("\n");
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const listVerb: VerbSpec<Record<string, unknown>, BlockRow[]> = {
|
|
107
|
-
path: ["block", "list"],
|
|
108
|
-
summary: "List blocks visible under the current tier and channel.",
|
|
109
|
-
doc: "List design system blocks visible under the active tier chain and channel. Optionally list across every tier, ignoring the tier filter.",
|
|
110
|
-
params: [
|
|
111
|
-
{
|
|
112
|
-
kind: "boolean",
|
|
113
|
-
name: "allTiers",
|
|
114
|
-
doc: "Show blocks from all tiers, ignoring the tier filter.",
|
|
115
|
-
},
|
|
116
|
-
],
|
|
117
|
-
output: { formatters: listFormatters },
|
|
118
|
-
examples: [
|
|
119
|
-
{ cmd: "pragma block list" },
|
|
120
|
-
{ cmd: "pragma block list --all-tiers" },
|
|
121
|
-
{ cmd: "pragma block list --llm" },
|
|
122
|
-
],
|
|
123
|
-
capability: {
|
|
124
|
-
needsStore: true,
|
|
125
|
-
mutates: false,
|
|
126
|
-
mcp: {
|
|
127
|
-
expose: true,
|
|
128
|
-
annotations: { readOnlyHint: true, openWorldHint: false },
|
|
129
|
-
},
|
|
130
|
-
},
|
|
131
|
-
run: async (params: Record<string, unknown>, rt: PragmaRuntime) => {
|
|
132
|
-
const layers = await rt.loadConfig();
|
|
133
|
-
const query = buildQuery(
|
|
134
|
-
layers.config.tier,
|
|
135
|
-
layers.config.channel,
|
|
136
|
-
params.allTiers === true,
|
|
137
|
-
);
|
|
138
|
-
const rows = await runSelect(rt, query, "block");
|
|
139
|
-
return rows.map((row) => ({
|
|
140
|
-
uri: row.component ?? "",
|
|
141
|
-
name: row.name ?? "",
|
|
142
|
-
type: normalizeType(row.type),
|
|
143
|
-
tier: localName(row.tier ?? ""),
|
|
144
|
-
modifiers: row.modifiers ?? "",
|
|
145
|
-
}));
|
|
146
|
-
},
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
/** The hand-written `block list` verb. */
|
|
150
|
-
export const blockListVerb = asVerb(listVerb);
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The `block` capability module — a COMPOSITE: the hand-written `block list`
|
|
3
|
-
* (tier chain + channel + --all-tiers) plus the bundled pack's GraphQL `lookup`.
|
|
4
|
-
*
|
|
5
|
-
* This is the one noun where a declarative pack and a hand-written verb share a
|
|
6
|
-
* module; the pack ships lookup-only, so there is no `(noun, verb)` collision.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { compilePack } from "../../kernel/packs/compile.js";
|
|
10
|
-
import { DEFAULT_PREFIX_MAP } from "../../kernel/render/prefixes.js";
|
|
11
|
-
import type { CapabilityModule } from "../../kernel/spec/types.js";
|
|
12
|
-
import { blockListVerb } from "./blockList.verb.js";
|
|
13
|
-
import { blockPack } from "./pack.js";
|
|
14
|
-
|
|
15
|
-
/** The `block` capability module (`list` hand-written, `lookup` from the pack). */
|
|
16
|
-
export const blockModule: CapabilityModule = {
|
|
17
|
-
name: "block",
|
|
18
|
-
verbs: [
|
|
19
|
-
blockListVerb,
|
|
20
|
-
...compilePack(blockPack, "bundled:block", DEFAULT_PREFIX_MAP),
|
|
21
|
-
],
|
|
22
|
-
// The bundled design-system domain colophon (authored on the flagship UI-block
|
|
23
|
-
// noun), surfaced by `pragma colophon` after pragma's own.
|
|
24
|
-
colophon: blockPack.colophon,
|
|
25
|
-
};
|
|
@@ -1,155 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* The bundled `block` pack — the lookup verb only.
|
|
3
|
-
*
|
|
4
|
-
* `block lookup` is served over the GraphQL fetch path (ONE generated document
|
|
5
|
-
* over the `UIBlock` interface covering Component/Pattern/Layout/Subcomponent);
|
|
6
|
-
* `block list` deliberately stays HAND-WRITTEN (tier-chain inheritance + channel
|
|
7
|
-
* + `--all-tiers` — see ./blockList.verb). Base level mirrors the old summary
|
|
8
|
-
* view (name/tier/summary); the default is `detailed`, matching the old CLI
|
|
9
|
-
* which rendered anatomy and modifiers without a flag. A derived name that maps
|
|
10
|
-
* onto no schema field is omitted (OPTIONAL parity), so a graph lacking
|
|
11
|
-
* whenToUse/whenNotToUse degrades gracefully.
|
|
12
|
-
*/
|
|
13
|
-
|
|
14
|
-
import type { PackDefinition } from "../../kernel/packs/types.js";
|
|
15
|
-
|
|
16
|
-
/**
|
|
17
|
-
* The bundled design-system domain colophon — the ontology + graph story,
|
|
18
|
-
* surfaced by `pragma colophon` after pragma's own. Authored on the flagship
|
|
19
|
-
* UI-block noun because `block` most embodies the design system; it narrates the
|
|
20
|
-
* DOMAIN (what the graph models), not the toolchain (pragma's built-in section).
|
|
21
|
-
*/
|
|
22
|
-
const DESIGN_SYSTEM_COLOPHON = `The Canonical design system is a **knowledge graph**, not a component library.
|
|
23
|
-
Every block, token, modifier, standard, and tier is a node in an RDF store,
|
|
24
|
-
described by the \`ds:\` ontology and queried the same way whether you reach it
|
|
25
|
-
over GraphQL or raw SPARQL.
|
|
26
|
-
|
|
27
|
-
## What the graph models
|
|
28
|
-
|
|
29
|
-
- **Blocks** — components, patterns, layouts, and subcomponents (the \`ds:UIBlock\`
|
|
30
|
-
family). A block carries its anatomy, guidelines, and \`when to use\` / \`when
|
|
31
|
-
not to use\` narrative as graph properties, not prose in a wiki.
|
|
32
|
-
- **Modifiers** — families of variant values (\`ds:hasModifierFamily\` →
|
|
33
|
-
\`ds:hasModifier\`) a block composes, so a variant is a relationship, not a
|
|
34
|
-
string.
|
|
35
|
-
- **Tokens** — the themeable design values, resolved per theme.
|
|
36
|
-
- **Standards** — the do / don't coding guidance, categorized and linked to the
|
|
37
|
-
blocks they govern.
|
|
38
|
-
|
|
39
|
-
## How it fits together
|
|
40
|
-
|
|
41
|
-
- **Tiers** are a hierarchy (\`global\` > \`apps\` > \`apps/lxd\`): a lower tier
|
|
42
|
-
inherits and overrides the blocks of its ancestors, so scoping a query to a
|
|
43
|
-
tier walks that chain.
|
|
44
|
-
- **Channels** (\`normal\`, \`experimental\`, \`prerelease\`) gate visibility, so an
|
|
45
|
-
in-progress block never leaks into a stable answer.
|
|
46
|
-
|
|
47
|
-
## Why RDF
|
|
48
|
-
|
|
49
|
-
One graph makes every relationship first-class and queryable: \`block lookup\`
|
|
50
|
-
follows edges to modifiers and subcomponents, \`graph query\` runs arbitrary
|
|
51
|
-
SPARQL, and \`ontology show\` reads the schema itself. The store is built once
|
|
52
|
-
by \`sources update\` and addressed by content hash, so the domain you query is
|
|
53
|
-
exactly the domain that was published.`;
|
|
54
|
-
|
|
55
|
-
export const blockPack: PackDefinition = {
|
|
56
|
-
noun: "block",
|
|
57
|
-
description: "Look up design system blocks.",
|
|
58
|
-
colophon: DESIGN_SYSTEM_COLOPHON,
|
|
59
|
-
lookup: {
|
|
60
|
-
source: "graphql",
|
|
61
|
-
toolDescription:
|
|
62
|
-
'Get detailed information about one or more design system blocks including anatomy, modifiers, and properties. Use when you need the full spec of specific blocks by name — detail: "summary" trims to the base view. Example: block_lookup { names: ["Button"] }.',
|
|
63
|
-
by: "ds:name",
|
|
64
|
-
types: ["ds:Component", "ds:Pattern", "ds:Layout", "ds:Subcomponent"],
|
|
65
|
-
graphqlType: "UIBlock",
|
|
66
|
-
fields: [
|
|
67
|
-
{ name: "tier", property: "ds:tier", label: "Tier" },
|
|
68
|
-
{
|
|
69
|
-
name: "figmaLink",
|
|
70
|
-
property: "ds:figmaLink",
|
|
71
|
-
label: "Figma",
|
|
72
|
-
level: "detailed",
|
|
73
|
-
},
|
|
74
|
-
],
|
|
75
|
-
sections: [
|
|
76
|
-
{ name: "summary", property: "ds:summary", label: "Summary" },
|
|
77
|
-
{
|
|
78
|
-
name: "whenToUse",
|
|
79
|
-
property: "ds:whenToUse",
|
|
80
|
-
label: "When to use",
|
|
81
|
-
level: "detailed",
|
|
82
|
-
},
|
|
83
|
-
{
|
|
84
|
-
name: "whenNotToUse",
|
|
85
|
-
property: "ds:whenNotToUse",
|
|
86
|
-
label: "When not to use",
|
|
87
|
-
level: "detailed",
|
|
88
|
-
},
|
|
89
|
-
{
|
|
90
|
-
name: "guidelines",
|
|
91
|
-
property: "ds:guidelines",
|
|
92
|
-
label: "Guidelines",
|
|
93
|
-
level: "detailed",
|
|
94
|
-
},
|
|
95
|
-
{
|
|
96
|
-
name: "anatomyDsl",
|
|
97
|
-
property: "ds:anatomyDsl",
|
|
98
|
-
label: "Anatomy (DSL)",
|
|
99
|
-
kind: "code",
|
|
100
|
-
level: "detailed",
|
|
101
|
-
},
|
|
102
|
-
{
|
|
103
|
-
name: "anatomyClassic",
|
|
104
|
-
property: "ds:anatomyClassic",
|
|
105
|
-
label: "Anatomy (classic)",
|
|
106
|
-
kind: "code",
|
|
107
|
-
level: "detailed",
|
|
108
|
-
},
|
|
109
|
-
],
|
|
110
|
-
expand: [
|
|
111
|
-
{
|
|
112
|
-
name: "modifierFamilies",
|
|
113
|
-
heading: "Modifier Families",
|
|
114
|
-
relation: "ds:hasModifierFamily",
|
|
115
|
-
level: "detailed",
|
|
116
|
-
select: [
|
|
117
|
-
{ name: "name", property: "ds:name" },
|
|
118
|
-
{
|
|
119
|
-
name: "values",
|
|
120
|
-
relation: "ds:hasModifier",
|
|
121
|
-
select: [{ name: "name", property: "ds:name" }],
|
|
122
|
-
},
|
|
123
|
-
],
|
|
124
|
-
},
|
|
125
|
-
{
|
|
126
|
-
name: "properties",
|
|
127
|
-
heading: "Properties",
|
|
128
|
-
relation: "ds:hasProperty",
|
|
129
|
-
kind: "table",
|
|
130
|
-
level: "detailed",
|
|
131
|
-
select: [
|
|
132
|
-
{ name: "name", property: "ds:name" },
|
|
133
|
-
{ name: "type", property: "ds:propertyType" },
|
|
134
|
-
{ name: "optional", property: "ds:optional" },
|
|
135
|
-
],
|
|
136
|
-
},
|
|
137
|
-
{
|
|
138
|
-
name: "subcomponents",
|
|
139
|
-
heading: "Subcomponents",
|
|
140
|
-
relation: "ds:hasSubcomponent",
|
|
141
|
-
level: "detailed",
|
|
142
|
-
select: [
|
|
143
|
-
{ name: "name", property: "ds:name" },
|
|
144
|
-
{ name: "uri", property: "ds:name", graphqlField: "uri" },
|
|
145
|
-
],
|
|
146
|
-
},
|
|
147
|
-
],
|
|
148
|
-
disclosure: { levels: ["summary", "detailed"], default: "detailed" },
|
|
149
|
-
sample: {
|
|
150
|
-
fixedCount: true,
|
|
151
|
-
toolDescription:
|
|
152
|
-
"Return randomly selected complete design-system blocks as exemplars. Use BEFORE writing queries to see actual data shapes, anatomy, and property names.",
|
|
153
|
-
},
|
|
154
|
-
},
|
|
155
|
-
};
|
|
@@ -1,180 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Block content-parity (PROTECTED) — on a FIXTURE graph, not the live one.
|
|
3
|
-
*
|
|
4
|
-
* The hand-written block lookup is gone; parity is asserted against the fixture
|
|
5
|
-
* graph directly: Button and Modal resolve through the GraphQL `block lookup`
|
|
6
|
-
* with the same content a direct SPARQL oracle returns (summary, guidance,
|
|
7
|
-
* anatomy, tier, modifier families with values, properties, subcomponents). The
|
|
8
|
-
* fixture DECLARES whenToUse/whenNotToUse (which the live graph superseded with
|
|
9
|
-
* ds:usage), so those sections are exercised here. The hand-written `block list`
|
|
10
|
-
* is checked for its tier/channel filtering and --all-tiers.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
14
|
-
import type { LookupOutput } from "../../kernel/packs/resolveEntity.js";
|
|
15
|
-
import { verbKey } from "../../kernel/packs/uniqueness.js";
|
|
16
|
-
import type { PragmaRuntime } from "../../kernel/runtime/types.js";
|
|
17
|
-
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
18
|
-
import {
|
|
19
|
-
BLOCK_PREFIXES,
|
|
20
|
-
BLOCK_TTL,
|
|
21
|
-
} from "../../testing/fixtures/blockGraph.js";
|
|
22
|
-
import { buildFixtureRuntime } from "../../testing/helpers/packRuntime.js";
|
|
23
|
-
import type { BlockRow } from "./blockList.verb.js";
|
|
24
|
-
import { blockModule } from "./index.js";
|
|
25
|
-
|
|
26
|
-
const DS = "https://ds.canonical.com/";
|
|
27
|
-
|
|
28
|
-
const listVerb = blockModule.verbs.find(
|
|
29
|
-
(v) => verbKey(v.path) === "block list",
|
|
30
|
-
) as VerbSpec;
|
|
31
|
-
const lookupVerb = blockModule.verbs.find(
|
|
32
|
-
(v) => verbKey(v.path) === "block lookup",
|
|
33
|
-
) as VerbSpec;
|
|
34
|
-
|
|
35
|
-
let rt: PragmaRuntime;
|
|
36
|
-
|
|
37
|
-
beforeAll(async () => {
|
|
38
|
-
({ rt } = await buildFixtureRuntime({
|
|
39
|
-
ttl: BLOCK_TTL,
|
|
40
|
-
prefixes: BLOCK_PREFIXES,
|
|
41
|
-
detail: "detailed",
|
|
42
|
-
}));
|
|
43
|
-
});
|
|
44
|
-
|
|
45
|
-
afterAll(async () => {
|
|
46
|
-
(await rt.store.get()).store.dispose();
|
|
47
|
-
});
|
|
48
|
-
|
|
49
|
-
async function lookup(name: string): Promise<Record<string, unknown>> {
|
|
50
|
-
const out = (await lookupVerb.run({ name: [name] }, rt)) as LookupOutput;
|
|
51
|
-
expect(out.errors).toEqual([]);
|
|
52
|
-
return out.results.at(0) as Record<string, unknown>;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/** Oracle: a single scalar from the graph, queried directly. */
|
|
56
|
-
async function oracle(subject: string, predicate: string): Promise<string> {
|
|
57
|
-
const result = await rt.query.sparql(
|
|
58
|
-
`SELECT ?v WHERE { <${subject}> ${predicate} ?v }`,
|
|
59
|
-
);
|
|
60
|
-
return result.type === "select" ? (result.bindings[0]?.v ?? "") : "";
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
describe("block lookup — Button content parity (GraphQL, detailed)", () => {
|
|
64
|
-
it("resolves the full Button spec with content matching the SPARQL oracle", async () => {
|
|
65
|
-
const button = await lookup("Button");
|
|
66
|
-
expect(button.uri).toBe(`${DS}button`);
|
|
67
|
-
expect(button.name).toBe("Button");
|
|
68
|
-
expect(button.tier).toBe(`${DS}global`);
|
|
69
|
-
|
|
70
|
-
// Guidance + anatomy sections — each cross-checked against the graph.
|
|
71
|
-
expect(button.summary).toBe(await oracle(`${DS}button`, "ds:summary"));
|
|
72
|
-
expect(button.whenToUse).toBe(await oracle(`${DS}button`, "ds:whenToUse"));
|
|
73
|
-
expect(button.whenNotToUse).toBe(
|
|
74
|
-
await oracle(`${DS}button`, "ds:whenNotToUse"),
|
|
75
|
-
);
|
|
76
|
-
expect(button.guidelines).toBe(
|
|
77
|
-
await oracle(`${DS}button`, "ds:guidelines"),
|
|
78
|
-
);
|
|
79
|
-
expect(button.anatomyDsl).toBe(
|
|
80
|
-
await oracle(`${DS}button`, "ds:anatomyDsl"),
|
|
81
|
-
);
|
|
82
|
-
expect(button.figmaLink).toBe(await oracle(`${DS}button`, "ds:figmaLink"));
|
|
83
|
-
});
|
|
84
|
-
|
|
85
|
-
it("resolves the nested modifier families with values (inverse union)", async () => {
|
|
86
|
-
const button = await lookup("Button");
|
|
87
|
-
const families = button.modifierFamilies as {
|
|
88
|
-
name: string;
|
|
89
|
-
values?: string[];
|
|
90
|
-
}[];
|
|
91
|
-
expect(families.map((f) => f.name).sort()).toEqual([
|
|
92
|
-
"density",
|
|
93
|
-
"importance",
|
|
94
|
-
]);
|
|
95
|
-
const importance = families.find((f) => f.name === "importance");
|
|
96
|
-
expect(importance?.values?.sort()).toEqual(["primary", "secondary"]);
|
|
97
|
-
});
|
|
98
|
-
|
|
99
|
-
it("resolves properties and the subtype-scoped subcomponents identity field", async () => {
|
|
100
|
-
const button = await lookup("Button");
|
|
101
|
-
expect(button.properties).toEqual([
|
|
102
|
-
{ name: "disabled", type: "boolean", optional: "true" },
|
|
103
|
-
]);
|
|
104
|
-
expect(button.subcomponents).toEqual([
|
|
105
|
-
{ name: "Button Icon", uri: `${DS}button.icon` },
|
|
106
|
-
]);
|
|
107
|
-
});
|
|
108
|
-
|
|
109
|
-
it("renders every declared section in the llm output", async () => {
|
|
110
|
-
const out = (await lookupVerb.run(
|
|
111
|
-
{ name: ["Button"] },
|
|
112
|
-
rt,
|
|
113
|
-
)) as LookupOutput;
|
|
114
|
-
const llm = lookupVerb.output.formatters.llm(out);
|
|
115
|
-
expect(llm).toContain("## Button");
|
|
116
|
-
expect(llm).toContain("### When to use");
|
|
117
|
-
expect(llm).toContain("### Anatomy (DSL)");
|
|
118
|
-
expect(llm).toContain("### Modifier Families");
|
|
119
|
-
expect(llm).toContain("primary");
|
|
120
|
-
});
|
|
121
|
-
});
|
|
122
|
-
|
|
123
|
-
describe("block lookup — Modal content parity (GraphQL, detailed)", () => {
|
|
124
|
-
it("resolves the full Modal spec (a different block, same document engine)", async () => {
|
|
125
|
-
const modal = await lookup("Modal");
|
|
126
|
-
expect(modal.uri).toBe(`${DS}modal`);
|
|
127
|
-
expect(modal.name).toBe("Modal");
|
|
128
|
-
expect(modal.summary).toBe(await oracle(`${DS}modal`, "ds:summary"));
|
|
129
|
-
expect(modal.whenToUse).toBe(await oracle(`${DS}modal`, "ds:whenToUse"));
|
|
130
|
-
const families = modal.modifierFamilies as {
|
|
131
|
-
name: string;
|
|
132
|
-
values?: string[];
|
|
133
|
-
}[];
|
|
134
|
-
expect(families.map((f) => f.name)).toEqual(["size"]);
|
|
135
|
-
expect(families[0]?.values?.sort()).toEqual(["large", "small"]);
|
|
136
|
-
expect(modal.properties).toEqual([
|
|
137
|
-
{ name: "open", type: "boolean", optional: "false" },
|
|
138
|
-
]);
|
|
139
|
-
// Modal has no subcomponents — the subtype-scoped selection is simply absent.
|
|
140
|
-
expect(modal.subcomponents).toEqual([]);
|
|
141
|
-
});
|
|
142
|
-
});
|
|
143
|
-
|
|
144
|
-
describe("block lookup — disclosure trims to the base view at summary", () => {
|
|
145
|
-
it("summary keeps name/tier/summary and drops the detailed sections/expands", async () => {
|
|
146
|
-
const out = (await lookupVerb.run(
|
|
147
|
-
{ name: ["Button"] },
|
|
148
|
-
{ ...rt, globalFlags: { ...rt.globalFlags, detail: "summary" } },
|
|
149
|
-
)) as LookupOutput;
|
|
150
|
-
const button = out.results.at(0) as Record<string, unknown>;
|
|
151
|
-
expect(button.name).toBe("Button");
|
|
152
|
-
expect(button.summary).toBeDefined();
|
|
153
|
-
expect(button.whenToUse).toBeUndefined();
|
|
154
|
-
expect(button.anatomyDsl).toBeUndefined();
|
|
155
|
-
expect(button.modifierFamilies).toBeUndefined();
|
|
156
|
-
expect(button.figmaLink).toBeUndefined();
|
|
157
|
-
});
|
|
158
|
-
});
|
|
159
|
-
|
|
160
|
-
describe("block list — hand-written tier/channel filtering", () => {
|
|
161
|
-
it("lists visible blocks with the summary row shape", async () => {
|
|
162
|
-
const rows = (await listVerb.run({}, rt)) as BlockRow[];
|
|
163
|
-
const byName = new Map(rows.map((r) => [r.name, r]));
|
|
164
|
-
expect([...byName.keys()].sort()).toEqual(["Button", "Modal"]);
|
|
165
|
-
const button = byName.get("Button");
|
|
166
|
-
expect(button?.type).toBe("component");
|
|
167
|
-
expect(button?.tier).toBe("global");
|
|
168
|
-
expect(button?.uri).toBe(`${DS}button`);
|
|
169
|
-
// GROUP_CONCAT of the family names (order-independent).
|
|
170
|
-
expect(button?.modifiers.split(", ").sort()).toEqual([
|
|
171
|
-
"density",
|
|
172
|
-
"importance",
|
|
173
|
-
]);
|
|
174
|
-
});
|
|
175
|
-
|
|
176
|
-
it("--all-tiers returns the same global blocks (no tier configured)", async () => {
|
|
177
|
-
const rows = (await listVerb.run({ allTiers: true }, rt)) as BlockRow[];
|
|
178
|
-
expect(rows.map((r) => r.name).sort()).toEqual(["Button", "Modal"]);
|
|
179
|
-
});
|
|
180
|
-
});
|