@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
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Doc-example tests: every `pragma` command shown in the published README and
|
|
3
|
+
* the getting-started guide must be REAL. Two tiers, both driven off the live
|
|
4
|
+
* grammar (never a hand-copied list):
|
|
5
|
+
*
|
|
6
|
+
* - Tier 1 (grammar) — every extracted command's noun/verb resolves and every
|
|
7
|
+
* `--flag` is one the verb (or the global/mutation set) declares. Storeless.
|
|
8
|
+
* - Tier 2 (execution) — a curated set of read commands is PARSED through the
|
|
9
|
+
* real CLI grammar (so the param bag is derived from the documented string,
|
|
10
|
+
* never hand-supplied) and then booted against the canonical fixture graph
|
|
11
|
+
* and asserted to exit 0, proving the documented reads run, not just parse.
|
|
12
|
+
*/
|
|
13
|
+
|
|
14
|
+
import { readFileSync } from "node:fs";
|
|
15
|
+
import { fileURLToPath } from "node:url";
|
|
16
|
+
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
17
|
+
import { capabilities } from "../../capabilities/index.js";
|
|
18
|
+
import {
|
|
19
|
+
executeVerb,
|
|
20
|
+
extractParams,
|
|
21
|
+
} from "../../kernel/project/cli/dispatch.js";
|
|
22
|
+
import {
|
|
23
|
+
nounVerbMap,
|
|
24
|
+
resolveUnknownCommand,
|
|
25
|
+
} from "../../kernel/project/cli/suggest.js";
|
|
26
|
+
import { kebabCase } from "../../kernel/spec/emitSurface.js";
|
|
27
|
+
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
28
|
+
import {
|
|
29
|
+
CANONICAL_CONFIG,
|
|
30
|
+
CANONICAL_TTL,
|
|
31
|
+
} from "../fixtures/graph/canonical.js";
|
|
32
|
+
import { extractPragmaCommands } from "../helpers/extractSnippets.js";
|
|
33
|
+
import { bootFixtureRuntime } from "../helpers/fixtureGraph.js";
|
|
34
|
+
import { projectCli } from "../helpers/projectCli.js";
|
|
35
|
+
|
|
36
|
+
/** Read a shipped doc exactly as a reader (or lychee) would. */
|
|
37
|
+
function readDoc(relPath: string): string {
|
|
38
|
+
return readFileSync(
|
|
39
|
+
fileURLToPath(new URL(relPath, import.meta.url)),
|
|
40
|
+
"utf-8",
|
|
41
|
+
);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/** Every hand-written doc whose fenced `pragma` examples must be real grammar. */
|
|
45
|
+
const HAND_WRITTEN_DOCS = [
|
|
46
|
+
"../../../README.md",
|
|
47
|
+
"../../../docs/getting-started.md",
|
|
48
|
+
"../../../docs/mcp-integration.md",
|
|
49
|
+
"../../../docs/config-model.md",
|
|
50
|
+
"../../../docs/architecture.md",
|
|
51
|
+
"../../../docs/skills.md",
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
const commands = HAND_WRITTEN_DOCS.flatMap((doc) =>
|
|
55
|
+
extractPragmaCommands(readDoc(doc)),
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
/** Every non-hidden verb, flattened from the live catalog. */
|
|
59
|
+
const allVerbs: VerbSpec[] = capabilities
|
|
60
|
+
.flatMap((module) => [...module.verbs])
|
|
61
|
+
.filter((verb) => !verb.hidden);
|
|
62
|
+
const nouns = nounVerbMap(allVerbs);
|
|
63
|
+
|
|
64
|
+
/** `"noun verb"` (or `"noun"` for a self-verb) → the runnable spec. */
|
|
65
|
+
const specByKey = new Map<string, VerbSpec>();
|
|
66
|
+
for (const verb of allVerbs) {
|
|
67
|
+
const sub = verb.path[1];
|
|
68
|
+
specByKey.set(
|
|
69
|
+
sub === undefined ? verb.path[0] : `${verb.path[0]} ${sub}`,
|
|
70
|
+
verb,
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/** Flags valid on every command, beyond a verb's own declared flags. */
|
|
75
|
+
const AMBIENT_FLAGS = new Set([
|
|
76
|
+
"--llm",
|
|
77
|
+
"--format",
|
|
78
|
+
"--verbose",
|
|
79
|
+
"--detail",
|
|
80
|
+
"--dry-run",
|
|
81
|
+
"--undo",
|
|
82
|
+
"--yes",
|
|
83
|
+
"--help",
|
|
84
|
+
"--version",
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
/** The positional tokens of a command (drops `pragma` and every `-`-prefixed token). */
|
|
88
|
+
function readPositionals(command: string): string[] {
|
|
89
|
+
return command
|
|
90
|
+
.split(/\s+/)
|
|
91
|
+
.slice(1)
|
|
92
|
+
.filter((token) => !token.startsWith("-"));
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Resolve a command's `VerbSpec` from its positionals (verb pair, else self-verb). */
|
|
96
|
+
function resolveSpec(positionals: readonly string[]): VerbSpec | undefined {
|
|
97
|
+
const noun = positionals.at(0);
|
|
98
|
+
const verb = positionals.at(1);
|
|
99
|
+
if (noun === undefined) return undefined;
|
|
100
|
+
const paired =
|
|
101
|
+
verb !== undefined ? specByKey.get(`${noun} ${verb}`) : undefined;
|
|
102
|
+
return paired ?? specByKey.get(noun);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** The set of flags a verb accepts: its own declared flags plus the ambient set. */
|
|
106
|
+
function collectValidFlags(verb: VerbSpec): Set<string> {
|
|
107
|
+
const flags = new Set(AMBIENT_FLAGS);
|
|
108
|
+
for (const param of verb.params) {
|
|
109
|
+
if (!param.positional) flags.add(`--${kebabCase(param.name)}`);
|
|
110
|
+
}
|
|
111
|
+
return flags;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
describe("doc examples — Tier 1: every documented command is grammatical", () => {
|
|
115
|
+
it("extracts commands from the docs", () => {
|
|
116
|
+
// A guard against a broken extractor silently passing everything else.
|
|
117
|
+
expect(commands.length).toBeGreaterThan(0);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
it("resolves every command's noun and verb", () => {
|
|
121
|
+
for (const command of commands) {
|
|
122
|
+
const unknown = resolveUnknownCommand(readPositionals(command), nouns);
|
|
123
|
+
expect(unknown, `unresolved command: ${command}`).toBeUndefined();
|
|
124
|
+
}
|
|
125
|
+
});
|
|
126
|
+
|
|
127
|
+
it("uses only flags the verb or the ambient set declares", () => {
|
|
128
|
+
for (const command of commands) {
|
|
129
|
+
const spec = resolveSpec(readPositionals(command));
|
|
130
|
+
expect(spec, `no spec for: ${command}`).toBeDefined();
|
|
131
|
+
if (!spec) continue;
|
|
132
|
+
const allowed = collectValidFlags(spec);
|
|
133
|
+
for (const token of command.split(/\s+/)) {
|
|
134
|
+
if (!token.startsWith("--")) continue;
|
|
135
|
+
const flag = token.split("=").at(0) ?? token;
|
|
136
|
+
expect(allowed.has(flag), `unknown flag ${flag} in: ${command}`).toBe(
|
|
137
|
+
true,
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
});
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* One curated read: its documented form, the verb key it must route to, and the
|
|
146
|
+
* param bag the real parser must derive from {@link ReadCase.command} (the
|
|
147
|
+
* oracle Tier 2 asserts the parse against before executing).
|
|
148
|
+
*/
|
|
149
|
+
interface ReadCase {
|
|
150
|
+
readonly command: string;
|
|
151
|
+
readonly key: string;
|
|
152
|
+
readonly params: Record<string, unknown>;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** Reads that can run storelessly or against the canonical fixture graph. */
|
|
156
|
+
const READ_CASES: readonly ReadCase[] = [
|
|
157
|
+
{ command: "pragma block list", key: "block list", params: {} },
|
|
158
|
+
{
|
|
159
|
+
command: "pragma block lookup Button",
|
|
160
|
+
key: "block lookup",
|
|
161
|
+
params: { name: ["Button"] },
|
|
162
|
+
},
|
|
163
|
+
{ command: "pragma standard list", key: "standard list", params: {} },
|
|
164
|
+
{ command: "pragma token list", key: "token list", params: {} },
|
|
165
|
+
{ command: "pragma tier list", key: "tier list", params: {} },
|
|
166
|
+
{ command: "pragma ontology list", key: "ontology list", params: {} },
|
|
167
|
+
{ command: "pragma config show", key: "config show", params: {} },
|
|
168
|
+
{ command: "pragma sources status", key: "sources status", params: {} },
|
|
169
|
+
{ command: "pragma doctor", key: "doctor", params: {} },
|
|
170
|
+
{ command: "pragma info", key: "info", params: {} },
|
|
171
|
+
{ command: "pragma capabilities", key: "capabilities", params: {} },
|
|
172
|
+
{ command: "pragma colophon", key: "colophon", params: {} },
|
|
173
|
+
];
|
|
174
|
+
|
|
175
|
+
const NO_MUTATION = { dryRun: false, undo: false, yes: false };
|
|
176
|
+
|
|
177
|
+
/**
|
|
178
|
+
* Thrown by the capture hook to halt a parse at the resolved verb — before the
|
|
179
|
+
* real dispatch (and its runtime boot) fires — carrying the routed path plus the
|
|
180
|
+
* Commander-parsed operands and options.
|
|
181
|
+
*/
|
|
182
|
+
class ParsedCommand extends Error {
|
|
183
|
+
constructor(
|
|
184
|
+
readonly path: readonly string[],
|
|
185
|
+
readonly positionals: string[],
|
|
186
|
+
readonly opts: Record<string, unknown>,
|
|
187
|
+
) {
|
|
188
|
+
super("parsed");
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
/**
|
|
193
|
+
* Parse a documented `pragma …` line through the REAL CLI grammar and return the
|
|
194
|
+
* verb it routes to plus the coerced param bag {@link extractParams} derives —
|
|
195
|
+
* WITHOUT running it. A `preAction` hook captures the resolved command and
|
|
196
|
+
* throws before its action, so no runtime boots. Deriving the bag from the
|
|
197
|
+
* STRING (rather than hand-supplying it) is what closes the Tier-2 gap: a
|
|
198
|
+
* documented positional in the wrong slot now yields a different bag, instead of
|
|
199
|
+
* a hand-fed bag masking the mistake.
|
|
200
|
+
*
|
|
201
|
+
* @param command - The documented `pragma …` command line.
|
|
202
|
+
* @returns The routed verb key, its spec, and the parser-derived param bag.
|
|
203
|
+
*/
|
|
204
|
+
async function parseDocumented(
|
|
205
|
+
command: string,
|
|
206
|
+
): Promise<{ key: string; spec: VerbSpec; params: Record<string, unknown> }> {
|
|
207
|
+
const program = projectCli(capabilities);
|
|
208
|
+
program.hook("preAction", (_root, action) => {
|
|
209
|
+
const parentName = action.parent?.name();
|
|
210
|
+
const routed =
|
|
211
|
+
parentName !== undefined && parentName !== "pragma"
|
|
212
|
+
? [parentName, action.name()]
|
|
213
|
+
: [action.name()];
|
|
214
|
+
throw new ParsedCommand(routed, [...action.args], action.opts());
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const argv = command.split(/\s+/).slice(1); // drop the leading `pragma`
|
|
218
|
+
try {
|
|
219
|
+
await program.parseAsync(argv, { from: "user" });
|
|
220
|
+
} catch (error) {
|
|
221
|
+
if (!(error instanceof ParsedCommand)) throw error;
|
|
222
|
+
const key = error.path.join(" ");
|
|
223
|
+
const spec = specByKey.get(key);
|
|
224
|
+
if (spec === undefined) {
|
|
225
|
+
throw new Error(`documented command routed to unknown verb: ${key}`);
|
|
226
|
+
}
|
|
227
|
+
return {
|
|
228
|
+
key,
|
|
229
|
+
spec,
|
|
230
|
+
params: extractParams(spec.params, error.positionals, error.opts),
|
|
231
|
+
};
|
|
232
|
+
}
|
|
233
|
+
throw new Error(`documented command reached no verb action: ${command}`);
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
describe("doc examples — Tier 2: curated read commands run green", () => {
|
|
237
|
+
let fixture: Awaited<ReturnType<typeof bootFixtureRuntime>> | undefined;
|
|
238
|
+
const documented = new Set(commands);
|
|
239
|
+
|
|
240
|
+
beforeAll(async () => {
|
|
241
|
+
fixture = await bootFixtureRuntime({
|
|
242
|
+
ttl: CANONICAL_TTL,
|
|
243
|
+
config: CANONICAL_CONFIG,
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
afterAll(async () => {
|
|
248
|
+
await fixture?.dispose();
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
for (const readCase of READ_CASES) {
|
|
252
|
+
it(`\`${readCase.command}\` parses to its verb and exits 0`, async () => {
|
|
253
|
+
expect(
|
|
254
|
+
documented.has(readCase.command),
|
|
255
|
+
`not documented: ${readCase.command}`,
|
|
256
|
+
).toBe(true);
|
|
257
|
+
|
|
258
|
+
// Derive the verb + param bag from the DOCUMENTED string via the real
|
|
259
|
+
// grammar, then assert both against the oracle: a mis-slotted positional
|
|
260
|
+
// would route elsewhere or yield a different bag, failing here.
|
|
261
|
+
const parsed = await parseDocumented(readCase.command);
|
|
262
|
+
expect(parsed.key, `routed to the wrong verb: ${readCase.command}`).toBe(
|
|
263
|
+
readCase.key,
|
|
264
|
+
);
|
|
265
|
+
expect(
|
|
266
|
+
parsed.params,
|
|
267
|
+
`parsed bag ≠ expected for: ${readCase.command}`,
|
|
268
|
+
).toEqual(readCase.params);
|
|
269
|
+
|
|
270
|
+
if (!fixture) return;
|
|
271
|
+
const outcome = await executeVerb(
|
|
272
|
+
parsed.spec,
|
|
273
|
+
parsed.params,
|
|
274
|
+
NO_MUTATION,
|
|
275
|
+
fixture.runtime,
|
|
276
|
+
);
|
|
277
|
+
expect(
|
|
278
|
+
outcome.exitCode,
|
|
279
|
+
`${readCase.command} → ${outcome.stderr ?? ""}`,
|
|
280
|
+
).toBe(0);
|
|
281
|
+
});
|
|
282
|
+
}
|
|
283
|
+
});
|
|
@@ -32,9 +32,10 @@ import { listVerbs, lookupVerbs } from "./liveReadSurface.js";
|
|
|
32
32
|
|
|
33
33
|
/**
|
|
34
34
|
* List verbs with a STRING or ENUM (narrowable, non-boolean) flag — e.g.
|
|
35
|
-
* `standard_list --category`.
|
|
36
|
-
*
|
|
37
|
-
*
|
|
35
|
+
* `standard_list --category`. Boolean flags are excluded: they reject a string
|
|
36
|
+
* value at the schema layer rather than narrowing to an empty result. (`block
|
|
37
|
+
* list --all-tiers` used to be the example; it retired with the hand-written
|
|
38
|
+
* filtering in L-OPEN-9.)
|
|
38
39
|
*/
|
|
39
40
|
const filteredListVerbs = listVerbs
|
|
40
41
|
.map((v) => {
|
|
@@ -46,7 +46,8 @@ const MATRIX: readonly {
|
|
|
46
46
|
{
|
|
47
47
|
label: "validOptions",
|
|
48
48
|
error: PragmaError.invalidInput("format", "yaml", {
|
|
49
|
-
|
|
49
|
+
// The live `--format` set (mirrors bin.ts): plain, llm, json.
|
|
50
|
+
validOptions: ["plain", "llm", "json"],
|
|
50
51
|
}),
|
|
51
52
|
},
|
|
52
53
|
{
|
|
@@ -60,7 +61,7 @@ const MATRIX: readonly {
|
|
|
60
61
|
},
|
|
61
62
|
{
|
|
62
63
|
label: "recovery (mcp-only, no cli)",
|
|
63
|
-
error: PragmaError.storeUnavailable("
|
|
64
|
+
error: PragmaError.storeUnavailable("the store has not been built", {
|
|
64
65
|
recovery: { message: "Please report this issue." },
|
|
65
66
|
}),
|
|
66
67
|
},
|
|
@@ -37,14 +37,15 @@ describe("exit-code covenant, spawn-observed (A4, e2e)", () => {
|
|
|
37
37
|
expect(result.stderr).toContain('Invalid format "yaml"');
|
|
38
38
|
});
|
|
39
39
|
|
|
40
|
-
it("3 — store unavailable (configured
|
|
40
|
+
it("3 — store unavailable (configured packs, never built)", () => {
|
|
41
41
|
const cwd = mkdtempSync(join(tmpdir(), "pragma-exit3-"));
|
|
42
|
-
// A pragma.config.ts with custom `
|
|
43
|
-
// "project" — with
|
|
44
|
-
// (no network probing) rather than
|
|
42
|
+
// A pragma.config.ts with custom `packs` flips `origins.packs` to
|
|
43
|
+
// "project" — with nothing built for this project, resolveSources refuses
|
|
44
|
+
// to boot (no network probing) rather than serve the embedded pack, which
|
|
45
|
+
// is a DIFFERENT graph from the one this project asked for.
|
|
45
46
|
writeFileSync(
|
|
46
47
|
join(cwd, "pragma.config.ts"),
|
|
47
|
-
'export default {
|
|
48
|
+
'export default { packs: [{ name: "never-built", source: "file:///nonexistent" }] };\n',
|
|
48
49
|
);
|
|
49
50
|
const result = runCli(["block", "list"], { cwd });
|
|
50
51
|
expect(result.exitCode).toBe(3);
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
* Harness-foundation smoke tests (commit 1).
|
|
3
3
|
*
|
|
4
4
|
* Validates the framework pieces PR4 adds — NOT per-noun behavior (that's
|
|
5
|
-
* commits 2/4's job). Proves: (1) `bootFixtureRuntime` boots a real,
|
|
6
|
-
*
|
|
5
|
+
* commits 2/4's job). Proves: (1) `bootFixtureRuntime` boots a real, built-pack
|
|
6
|
+
* store that BOTH the CLI and MCP surfaces resolve identically
|
|
7
7
|
* (via the shared canonical graph, one real lookup); (2) the compiler/runtime
|
|
8
8
|
* is ontology-agnostic — a pack over the foreign `ex:` recipe namespace
|
|
9
9
|
* registers its 2 tools and resolves on both surfaces; (3) `runCli` can spawn
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
|
|
13
13
|
import { homedir, tmpdir } from "node:os";
|
|
14
14
|
import { describe, expect, it } from "vitest";
|
|
15
|
-
import {
|
|
15
|
+
import { storyModules } from "../../capabilities/distribution.js";
|
|
16
16
|
import { VERSION } from "../../constants.js";
|
|
17
17
|
import { compilePack } from "../../kernel/packs/compile.js";
|
|
18
18
|
import { verbKey } from "../../kernel/packs/uniqueness.js";
|
|
@@ -31,8 +31,13 @@ import { plain } from "../helpers/golden.js";
|
|
|
31
31
|
import { assertCliMcpParity } from "../helpers/parity.js";
|
|
32
32
|
import { runCli } from "../helpers/runCli.js";
|
|
33
33
|
|
|
34
|
+
const blockModule = storyModules.get("block");
|
|
35
|
+
if (!blockModule) {
|
|
36
|
+
throw new Error('pragma.conf.ts declares no story for "block"');
|
|
37
|
+
}
|
|
38
|
+
|
|
34
39
|
describe("bootFixtureRuntime — the shared canonical graph (commit-1 smoke)", () => {
|
|
35
|
-
it("boots a
|
|
40
|
+
it("boots a built-pack store both CLI and MCP resolve identically", async () => {
|
|
36
41
|
const fixture = await bootFixtureRuntime({
|
|
37
42
|
ttl: CANONICAL_TTL,
|
|
38
43
|
config: CANONICAL_CONFIG,
|
|
@@ -68,7 +73,11 @@ describe("foreign-namespace generic-pack proof (compiler is DS-agnostic)", () =>
|
|
|
68
73
|
try {
|
|
69
74
|
const recipeModule: CapabilityModule = {
|
|
70
75
|
name: "recipe",
|
|
71
|
-
verbs: compilePack(
|
|
76
|
+
verbs: compilePack(
|
|
77
|
+
recipePack,
|
|
78
|
+
{ label: "test:recipe", origin: "package" },
|
|
79
|
+
RECIPE_PREFIXES,
|
|
80
|
+
),
|
|
72
81
|
};
|
|
73
82
|
expect(recipeModule.verbs.map((v) => verbKey(v.path)).sort()).toEqual([
|
|
74
83
|
"recipe list",
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* B9 — the
|
|
3
|
-
* verb, `block list`).
|
|
2
|
+
* B9 — the `block list` CLI journey, re-pinned on the DECLARED contract.
|
|
4
3
|
*
|
|
5
4
|
* BACKING ADAPTATION: the plan lists this as "fixture + spawn"; the PR4 quality
|
|
6
5
|
* bar (R7 — keep the spawn-e2e layer to exactly A1-A4/A7) overrides that, so
|
|
@@ -9,42 +8,86 @@
|
|
|
9
8
|
* against the fixture, rather than spawning. It still exercises the true CLI
|
|
10
9
|
* seam (typed params -> dispatch -> render), just without forking a process.
|
|
11
10
|
*
|
|
11
|
+
* CONTRACT CHANGE (L-OPEN-9): this journey used to enumerate four different
|
|
12
|
+
* answers — one per tier config, one per channel, one for `--all-tiers`. That
|
|
13
|
+
* hand-written filtering is gone; `block list` is the block story's compiled,
|
|
14
|
+
* UNFILTERED list. So the journey pins the honest NEW behaviour, which is a
|
|
15
|
+
* STRONGER claim than the old one: the SAME graph under FOUR different configs
|
|
16
|
+
* gives ONE answer, containing every block in the store. `config.tier` and
|
|
17
|
+
* `config.channel` no longer reach the query at all, and the owner-signed
|
|
18
|
+
* consequence — experimental/alpha blocks are visible to everyone — is asserted
|
|
19
|
+
* here rather than left to prose.
|
|
20
|
+
*
|
|
12
21
|
* CONTENT ADAPTATION (R2 — verified against the live `block lookup`, not
|
|
13
22
|
* assumed): the plan's wording describes an unscoped `block lookup` returning
|
|
14
23
|
* multiple tier-disambiguated matches — that mechanism doesn't exist in v2
|
|
15
24
|
* (`block lookup` resolves by name GLOBALLY with no tier awareness at all;
|
|
16
|
-
* PARITY_GAPS `block-lookup-not-tier-scoped`).
|
|
17
|
-
* tier
|
|
18
|
-
* different tier configs on the SAME canonical graph.
|
|
25
|
+
* PARITY_GAPS `block-lookup-not-tier-scoped`). Since L-OPEN-9 neither block
|
|
26
|
+
* verb is tier-aware, so both halves of the noun now agree.
|
|
19
27
|
*/
|
|
20
28
|
|
|
21
29
|
import { afterAll, describe, expect, it } from "vitest";
|
|
22
|
-
import {
|
|
30
|
+
import { storyModules } from "../../capabilities/distribution.js";
|
|
23
31
|
import { verbKey } from "../../kernel/packs/uniqueness.js";
|
|
24
32
|
import { executeVerb } from "../../kernel/project/cli/dispatch.js";
|
|
25
33
|
import { bootRuntime } from "../../kernel/runtime/boot.js";
|
|
26
34
|
import type { VerbSpec } from "../../kernel/spec/types.js";
|
|
27
35
|
import {
|
|
36
|
+
ALL_VISIBLE_CONFIG,
|
|
28
37
|
CANONICAL_CONFIG,
|
|
29
38
|
CANONICAL_TTL,
|
|
39
|
+
FILTERED_CONFIG,
|
|
30
40
|
} from "../fixtures/graph/canonical.js";
|
|
31
41
|
import {
|
|
32
42
|
bootFixtureRuntime,
|
|
33
43
|
type FixtureGraph,
|
|
44
|
+
type FixtureGraphOptions,
|
|
34
45
|
} from "../helpers/fixtureGraph.js";
|
|
35
46
|
import { JSON_FLAGS, NO_MUTATION } from "../helpers/parity.js";
|
|
36
47
|
|
|
48
|
+
const blockModule = storyModules.get("block");
|
|
49
|
+
if (!blockModule) {
|
|
50
|
+
throw new Error('pragma.conf.ts declares no story for "block"');
|
|
51
|
+
}
|
|
52
|
+
|
|
37
53
|
const listVerb = blockModule.verbs.find(
|
|
38
54
|
(v) => verbKey(v.path) === "block list",
|
|
39
55
|
) as VerbSpec;
|
|
40
56
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
57
|
+
/**
|
|
58
|
+
* Every block the canonical fixture carries: two global components, one scoped
|
|
59
|
+
* to `apps/lxd`, one gated to the `beta` release channel, and one UNTIERED
|
|
60
|
+
* subcomponent. Under the hand-written verb NO single invocation returned all
|
|
61
|
+
* five — the tier chain hid `LXD Panel`, the channel hid `Beta Widget`, and only
|
|
62
|
+
* `--all-tiers` revealed `Button Icon`. The declared list returns all five, and
|
|
63
|
+
* returns them whatever the config says.
|
|
64
|
+
*/
|
|
65
|
+
const EVERY_BLOCK = [
|
|
66
|
+
"Beta Widget",
|
|
67
|
+
"Button",
|
|
68
|
+
"Button Icon",
|
|
69
|
+
"LXD Panel",
|
|
70
|
+
"Modal",
|
|
71
|
+
];
|
|
72
|
+
|
|
73
|
+
const fixtures: FixtureGraph[] = [];
|
|
74
|
+
afterAll(async () => {
|
|
75
|
+
await Promise.all(fixtures.map((f) => f.dispose()));
|
|
76
|
+
});
|
|
77
|
+
|
|
78
|
+
/** Boot a tracked canonical fixture under `config`, for auto-disposal. */
|
|
79
|
+
async function bootWith(
|
|
80
|
+
config: FixtureGraphOptions["config"],
|
|
81
|
+
): Promise<FixtureGraph> {
|
|
82
|
+
const fixture = await bootFixtureRuntime({ ttl: CANONICAL_TTL, config });
|
|
83
|
+
fixtures.push(fixture);
|
|
84
|
+
return fixture;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
async function blockListNames(fixture: FixtureGraph): Promise<string[]> {
|
|
45
88
|
const out = await executeVerb(
|
|
46
89
|
listVerb,
|
|
47
|
-
{
|
|
90
|
+
{},
|
|
48
91
|
NO_MUTATION,
|
|
49
92
|
bootRuntime(JSON_FLAGS, fixture.cwd),
|
|
50
93
|
);
|
|
@@ -52,78 +95,78 @@ async function blockListNames(
|
|
|
52
95
|
return rows.map((r) => r.name).sort();
|
|
53
96
|
}
|
|
54
97
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
98
|
+
describe("block list — the declared, unfiltered list (B9, in-process CLI dispatch)", () => {
|
|
99
|
+
it("with no tier configured, lists every block in the store", async () => {
|
|
100
|
+
expect(await blockListNames(await bootWith(CANONICAL_CONFIG))).toEqual(
|
|
101
|
+
EVERY_BLOCK,
|
|
102
|
+
);
|
|
103
|
+
});
|
|
59
104
|
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
fixtures.push(fixture);
|
|
67
|
-
// normal channel drops the beta-only block; no tier set -> every tier.
|
|
68
|
-
expect(await blockListNames(fixture)).toEqual([
|
|
69
|
-
"Button",
|
|
70
|
-
"LXD Panel",
|
|
71
|
-
"Modal",
|
|
72
|
-
]);
|
|
105
|
+
it("a configured tier does NOT narrow the list — tier scopes nothing", async () => {
|
|
106
|
+
// The old verb answered ["Button", "Modal"] here: `global` excluded the
|
|
107
|
+
// apps/lxd block, and the required `ds:tier` join hid the untiered
|
|
108
|
+
// subcomponent. Nothing filters now, so the tier config is inert.
|
|
109
|
+
const fixture = await bootWith({ tier: "global", channel: "normal" });
|
|
110
|
+
expect(await blockListNames(fixture)).toEqual(EVERY_BLOCK);
|
|
73
111
|
});
|
|
74
112
|
|
|
75
|
-
it("
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
113
|
+
it("the deepest tier in the chain gets the same answer as the shallowest", async () => {
|
|
114
|
+
// Tier-chain inheritance was the point of the hand-written query
|
|
115
|
+
// (`apps/lxd` -> [global, apps, apps/lxd]). With no filtering there is no
|
|
116
|
+
// chain to walk, so `apps/lxd` and `global` are indistinguishable.
|
|
117
|
+
expect(await blockListNames(await bootWith(FILTERED_CONFIG))).toEqual(
|
|
118
|
+
EVERY_BLOCK,
|
|
119
|
+
);
|
|
82
120
|
});
|
|
121
|
+
});
|
|
83
122
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
expect(await blockListNames(fixture)).toEqual([
|
|
93
|
-
"Button",
|
|
94
|
-
"LXD Panel",
|
|
95
|
-
"Modal",
|
|
96
|
-
]);
|
|
123
|
+
describe("block list — channel visibility is gone too (B9)", () => {
|
|
124
|
+
it("the `normal` channel shows the beta-gated block — the signed-off consequence", async () => {
|
|
125
|
+
// `ds:betaWidget` carries `ds:release ds:beta`. The hand-written verb hid it
|
|
126
|
+
// from every channel but `prerelease`; the declared list shows it to
|
|
127
|
+
// everyone. This is the owner-signed visible consequence of L-OPEN-9,
|
|
128
|
+
// pinned so it stays a decision on record rather than a silent regression.
|
|
129
|
+
const names = await blockListNames(await bootWith(CANONICAL_CONFIG));
|
|
130
|
+
expect(names).toContain("Beta Widget");
|
|
97
131
|
});
|
|
98
132
|
|
|
99
|
-
it("
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
});
|
|
104
|
-
fixtures.push(fixture);
|
|
105
|
-
// Scoped (no flag) excludes LXD Panel; --all-tiers restores it.
|
|
106
|
-
expect(await blockListNames(fixture, false)).toEqual(["Button", "Modal"]);
|
|
107
|
-
expect(await blockListNames(fixture, true)).toEqual([
|
|
108
|
-
"Button",
|
|
109
|
-
"LXD Panel",
|
|
110
|
-
"Modal",
|
|
111
|
-
]);
|
|
133
|
+
it("`prerelease` — once the only config that saw everything — is now unremarkable", async () => {
|
|
134
|
+
expect(await blockListNames(await bootWith(ALL_VISIBLE_CONFIG))).toEqual(
|
|
135
|
+
EVERY_BLOCK,
|
|
136
|
+
);
|
|
112
137
|
});
|
|
113
138
|
});
|
|
114
139
|
|
|
115
|
-
describe("block list —
|
|
116
|
-
it("
|
|
117
|
-
const fixture = await
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
140
|
+
describe("block list — the row shape the declared columns emit (B9)", () => {
|
|
141
|
+
it("derives name/type/tier in SPARQL, and leaves an untiered block's tier unset", async () => {
|
|
142
|
+
const fixture = await bootWith(CANONICAL_CONFIG);
|
|
143
|
+
const out = await executeVerb(
|
|
144
|
+
listVerb,
|
|
145
|
+
{},
|
|
146
|
+
NO_MUTATION,
|
|
147
|
+
bootRuntime(JSON_FLAGS, fixture.cwd),
|
|
148
|
+
);
|
|
149
|
+
const rows = JSON.parse(out.stdout as string).data as {
|
|
150
|
+
name: string;
|
|
151
|
+
type?: string;
|
|
152
|
+
tier?: string;
|
|
153
|
+
uri: string;
|
|
154
|
+
modifiers?: string;
|
|
155
|
+
}[];
|
|
156
|
+
const byName = new Map(rows.map((row) => [row.name, row]));
|
|
157
|
+
|
|
158
|
+
// `type` is the LOWERCASED local name of the matched class — the BIND that
|
|
159
|
+
// replaced the hand-written `normalizeType`.
|
|
160
|
+
expect(byName.get("Button")?.type).toBe("component");
|
|
161
|
+
expect(byName.get("Button Icon")?.type).toBe("subcomponent");
|
|
162
|
+
// `tier` is the tier IRI's local name; an untiered block simply has none.
|
|
163
|
+
expect(byName.get("Button")?.tier).toBe("global");
|
|
164
|
+
expect(byName.get("Button Icon")?.tier).toBeUndefined();
|
|
165
|
+
expect(byName.get("Button")?.uri).toBe("https://ds.canonical.com/button");
|
|
166
|
+
// GROUP_CONCAT of the modifier family names (order-independent).
|
|
167
|
+
expect(byName.get("Button")?.modifiers?.split(", ").sort()).toEqual([
|
|
168
|
+
"density",
|
|
169
|
+
"importance",
|
|
127
170
|
]);
|
|
128
171
|
});
|
|
129
172
|
});
|