@canonical/pragma-cli 0.36.0 → 0.38.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 +3 -2
- package/bundled-skills/adoption-a1-styles/SKILL.md +24 -0
- package/bundled-skills/adoption-a2-components/SKILL.md +22 -6
- package/bundled-skills/anatomy-author/ANATOMY_DSL_SPEC.md +1397 -327
- package/bundled-skills/anatomy-author/SKILL.md +840 -137
- package/bundled-skills/design-auditor/SKILL.md +133 -9
- package/bundled-skills/implement-component/SKILL.md +192 -23
- package/bundled-skills/specify-component/SKILL.md +124 -12
- package/bundled-skills/specify-pattern/SKILL.md +47 -8
- package/dist/package.json +23 -20
- package/dist/pragma.conf.js +963 -40
- package/dist/pragma.conf.js.map +1 -1
- package/dist/src/capabilities/capabilities/catalog.js +14 -5
- package/dist/src/capabilities/capabilities/catalog.js.map +1 -1
- package/dist/src/capabilities/capabilities/hints.js +33 -5
- package/dist/src/capabilities/capabilities/hints.js.map +1 -1
- package/dist/src/capabilities/config/show.render.js +30 -2
- package/dist/src/capabilities/config/show.render.js.map +1 -1
- package/dist/src/capabilities/create/create.verb.js +2 -1
- package/dist/src/capabilities/create/create.verb.js.map +1 -1
- package/dist/src/capabilities/create/createSurface.generated.js +23 -1
- package/dist/src/capabilities/create/createSurface.generated.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js +11 -6
- package/dist/src/capabilities/doctor/checks/checkNodeVersion.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js +1 -1
- package/dist/src/capabilities/doctor/checks/harnessInventory.js.map +1 -1
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js +38 -0
- package/dist/src/capabilities/doctor/checks/isSupportedNodeVersion.js.map +1 -0
- package/dist/src/capabilities/doctor/checks/targetHealth.js +104 -14
- package/dist/src/capabilities/doctor/checks/targetHealth.js.map +1 -1
- package/dist/src/capabilities/graph/connect.render.js +101 -0
- package/dist/src/capabilities/graph/connect.render.js.map +1 -0
- package/dist/src/capabilities/graph/connect.types.js +21 -0
- package/dist/src/capabilities/graph/connect.types.js.map +1 -0
- package/dist/src/capabilities/graph/connect.verb.js +147 -0
- package/dist/src/capabilities/graph/connect.verb.js.map +1 -0
- package/dist/src/capabilities/graph/index.js +5 -3
- package/dist/src/capabilities/graph/index.js.map +1 -1
- package/dist/src/capabilities/graph/queryText.js +206 -0
- package/dist/src/capabilities/graph/queryText.js.map +1 -0
- package/dist/src/capabilities/graph/relationIndex.js +302 -0
- package/dist/src/capabilities/graph/relationIndex.js.map +1 -0
- package/dist/src/capabilities/graph/runConnect.js +456 -0
- package/dist/src/capabilities/graph/runConnect.js.map +1 -0
- package/dist/src/capabilities/graph/runQuery.js +21 -4
- package/dist/src/capabilities/graph/runQuery.js.map +1 -1
- package/dist/src/capabilities/setup/buildPlan.js +6 -2
- package/dist/src/capabilities/setup/buildPlan.js.map +1 -1
- package/dist/src/capabilities/setup/operations/index.js +7 -2
- package/dist/src/capabilities/setup/operations/index.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupCompletions.js +6 -2
- package/dist/src/capabilities/setup/operations/setupCompletions.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupConfig.js +4 -2
- package/dist/src/capabilities/setup/operations/setupConfig.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupGenerator.js +72 -14
- package/dist/src/capabilities/setup/operations/setupGenerator.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupLsp.js +372 -62
- package/dist/src/capabilities/setup/operations/setupLsp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupMcp.js +150 -5
- package/dist/src/capabilities/setup/operations/setupMcp.js.map +1 -1
- package/dist/src/capabilities/setup/operations/setupSkills.js +4 -2
- package/dist/src/capabilities/setup/operations/setupSkills.js.map +1 -1
- package/dist/src/capabilities/setup/operations/writability.js +114 -0
- package/dist/src/capabilities/setup/operations/writability.js.map +1 -0
- package/dist/src/capabilities/setup/plan.js.map +1 -1
- package/dist/src/capabilities/setup/setup.verb.js +80 -31
- package/dist/src/capabilities/setup/setup.verb.js.map +1 -1
- package/dist/src/capabilities/setup/targets.js +124 -42
- package/dist/src/capabilities/setup/targets.js.map +1 -1
- package/dist/src/capabilities/shared/index.js +1 -1
- package/dist/src/capabilities/shared/index.js.map +1 -1
- package/dist/src/constants.js +7 -1
- package/dist/src/constants.js.map +1 -1
- package/dist/src/kernel/error/renderError.js +3 -2
- package/dist/src/kernel/error/renderError.js.map +1 -1
- package/dist/src/kernel/error/validOptions.js +41 -0
- package/dist/src/kernel/error/validOptions.js.map +1 -0
- package/dist/src/kernel/packs/compile.js +90 -6
- package/dist/src/kernel/packs/compile.js.map +1 -1
- package/dist/src/kernel/packs/cursor.js +135 -0
- package/dist/src/kernel/packs/cursor.js.map +1 -0
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js +9 -2
- package/dist/src/kernel/packs/graphql/buildLookupDocument.js.map +1 -1
- package/dist/src/kernel/packs/paging.js +108 -0
- package/dist/src/kernel/packs/paging.js.map +1 -0
- package/dist/src/kernel/packs/renderPack.js +154 -5
- package/dist/src/kernel/packs/renderPack.js.map +1 -1
- package/dist/src/kernel/packs/resolveEntity.js +118 -19
- package/dist/src/kernel/packs/resolveEntity.js.map +1 -1
- package/dist/src/kernel/packs/runBodies.js +156 -18
- package/dist/src/kernel/packs/runBodies.js.map +1 -1
- package/dist/src/kernel/packs/schema.js +160 -12
- package/dist/src/kernel/packs/schema.js.map +1 -1
- package/dist/src/kernel/packs/sparql/authorQuery.js +227 -0
- package/dist/src/kernel/packs/sparql/authorQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js +251 -0
- package/dist/src/kernel/packs/sparql/buildListQuery.js.map +1 -0
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js +125 -22
- package/dist/src/kernel/packs/sparql/buildLookupQuery.js.map +1 -1
- package/dist/src/kernel/packs/sparql/filterValues.js +145 -0
- package/dist/src/kernel/packs/sparql/filterValues.js.map +1 -0
- package/dist/src/kernel/packs/sparql/index.js +24 -14
- package/dist/src/kernel/packs/sparql/index.js.map +1 -1
- package/dist/src/kernel/packs/sparql/runSelect.js +10 -4
- package/dist/src/kernel/packs/sparql/runSelect.js.map +1 -1
- package/dist/src/kernel/packs/sparql/searchTerm.js +25 -0
- package/dist/src/kernel/packs/sparql/searchTerm.js.map +1 -0
- package/dist/src/kernel/packs/storyRules.js +151 -0
- package/dist/src/kernel/packs/storyRules.js.map +1 -0
- package/dist/src/kernel/packs/tierScope.js +310 -0
- package/dist/src/kernel/packs/tierScope.js.map +1 -0
- package/dist/src/kernel/packs/types.js +80 -0
- package/dist/src/kernel/packs/types.js.map +1 -1
- package/dist/src/kernel/project/cli/buildProgram.js +10 -1
- package/dist/src/kernel/project/cli/buildProgram.js.map +1 -1
- package/dist/src/kernel/project/cli/dispatch.js +104 -3
- package/dist/src/kernel/project/cli/dispatch.js.map +1 -1
- package/dist/src/kernel/project/cli/rootHelp.js +8 -5
- package/dist/src/kernel/project/cli/rootHelp.js.map +1 -1
- package/dist/src/kernel/project/cli/suggestNames.js +31 -7
- package/dist/src/kernel/project/cli/suggestNames.js.map +1 -1
- package/dist/src/kernel/project/mcp/registerVerb.js +5 -1
- package/dist/src/kernel/project/mcp/registerVerb.js.map +1 -1
- package/dist/src/kernel/render/renderers.js +34 -2
- package/dist/src/kernel/render/renderers.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js +2 -3
- package/dist/src/kernel/runtime/graphpack/embedded/pack.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.index.generated.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js +4 -0
- package/dist/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.js.map +1 -0
- package/dist/src/kernel/runtime/graphpack/embedded.js +13 -6
- package/dist/src/kernel/runtime/graphpack/embedded.js.map +1 -1
- package/dist/src/kernel/runtime/graphpack/read.js +17 -10
- package/dist/src/kernel/runtime/graphpack/read.js.map +1 -1
- package/dist/src/kernel/runtime/loadSession.js +3 -1
- package/dist/src/kernel/runtime/loadSession.js.map +1 -1
- package/dist/src/kernel/runtime/probe.js +1 -1
- package/dist/src/kernel/runtime/probe.js.map +1 -1
- package/dist/src/kernel/runtime/readEntity.js +19 -4
- package/dist/src/kernel/runtime/readEntity.js.map +1 -1
- package/dist/src/kernel/runtime/refs/parseRef.js +17 -1
- package/dist/src/kernel/runtime/refs/parseRef.js.map +1 -1
- package/dist/src/kernel/runtime/refs/resolve.js +38 -5
- package/dist/src/kernel/runtime/refs/resolve.js.map +1 -1
- package/dist/src/kernel/spec/emitReference.js +1 -1
- package/dist/src/kernel/spec/emitReference.js.map +1 -1
- package/package.json +23 -20
- package/pragma.conf.ts +983 -40
- package/src/capabilities/block.parity.test.ts +207 -27
- package/src/capabilities/block.shipped.exec.test.ts +156 -14
- package/src/capabilities/block.tierRank.exec.test.ts +46 -9
- package/src/capabilities/capabilities/catalog.ts +14 -5
- package/src/capabilities/capabilities/hints.ts +40 -5
- package/src/capabilities/config/collectConfigShow.test.ts +1 -0
- package/src/capabilities/config/set.test.ts +24 -25
- package/src/capabilities/config/show.render.test.ts +9 -4
- package/src/capabilities/config/show.render.ts +30 -2
- package/src/capabilities/create/byteEquality.test.ts +28 -1
- package/src/capabilities/create/create.test.ts +1 -0
- package/src/capabilities/create/create.verb.ts +2 -1
- package/src/capabilities/create/createGrammar.test.ts +4 -1
- package/src/capabilities/create/createSurface.generated.ts +23 -1
- package/src/capabilities/create/crossCli.subprocess.test.ts +10 -3
- package/src/capabilities/distribution.parity.test.ts +12 -11
- package/src/capabilities/distribution.test.ts +77 -7
- package/src/capabilities/doctor/checks/checkNodeVersion.test.ts +49 -0
- package/src/capabilities/doctor/checks/checkNodeVersion.ts +11 -7
- package/src/capabilities/doctor/checks/harnessInventory.test.ts +13 -6
- package/src/capabilities/doctor/checks/harnessInventory.ts +8 -4
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.test.ts +111 -0
- package/src/capabilities/doctor/checks/isSupportedNodeVersion.ts +34 -0
- package/src/capabilities/doctor/checks/mcpCommand.test.ts +7 -1
- package/src/capabilities/doctor/checks/targetHealth.ts +114 -13
- package/src/capabilities/doctor/doctor.test.ts +327 -36
- package/src/capabilities/emptyRecovery.test.ts +84 -28
- package/src/capabilities/graph/connect.render.ts +141 -0
- package/src/capabilities/graph/connect.shipped.exec.test.ts +370 -0
- package/src/capabilities/graph/connect.test.ts +361 -0
- package/src/capabilities/graph/connect.types.ts +136 -0
- package/src/capabilities/graph/connect.verb.ts +165 -0
- package/src/capabilities/graph/index.ts +5 -3
- package/src/capabilities/graph/query.shipped.exec.test.ts +66 -0
- package/src/capabilities/graph/query.test.ts +17 -0
- package/src/capabilities/graph/queryText.test.ts +121 -0
- package/src/capabilities/graph/queryText.ts +240 -0
- package/src/capabilities/graph/relationIndex.ts +372 -0
- package/src/capabilities/graph/runConnect.ts +577 -0
- package/src/capabilities/graph/runQuery.ts +21 -6
- package/src/capabilities/lazy.test.ts +32 -2
- package/src/capabilities/listBudget.shipped.exec.test.ts +239 -0
- package/src/capabilities/listLookup.shipped.exec.test.ts +387 -0
- package/src/capabilities/listQuery.shipped.exec.test.ts +400 -0
- package/src/capabilities/resources/resources.test.ts +38 -2
- package/src/capabilities/setup/buildPlan.ts +7 -1
- package/src/capabilities/setup/operations/index.ts +14 -0
- package/src/capabilities/setup/operations/setupCompletions.ts +9 -2
- package/src/capabilities/setup/operations/setupConfig.ts +7 -2
- package/src/capabilities/setup/operations/setupGenerator.ts +93 -15
- package/src/capabilities/setup/operations/setupLsp.ts +544 -85
- package/src/capabilities/setup/operations/setupMcp.ts +212 -4
- package/src/capabilities/setup/operations/setupSkills.ts +7 -2
- package/src/capabilities/setup/operations/writability.test.ts +151 -0
- package/src/capabilities/setup/operations/writability.ts +135 -0
- package/src/capabilities/setup/plan.ts +11 -0
- package/src/capabilities/setup/setup.test.ts +992 -16
- package/src/capabilities/setup/setup.verb.ts +84 -36
- package/src/capabilities/setup/targets.ts +152 -48
- package/src/capabilities/setup/types.ts +41 -5
- package/src/capabilities/shared/index.ts +1 -0
- package/src/capabilities/sources/sources.test.ts +55 -0
- package/src/capabilities/surface.test.ts +59 -11
- package/src/capabilities/tier.parity.test.ts +2 -2
- package/src/capabilities/token.parity.test.ts +784 -0
- package/src/capabilities/tokenGraph.cli.test.ts +279 -0
- package/src/capabilities/tokenGraph.shipped.exec.test.ts +653 -0
- package/src/capabilities/variable.parity.test.ts +448 -0
- package/src/constants.ts +8 -0
- package/src/kernel/completion/shellDrive.test.ts +32 -30
- package/src/kernel/config/defaults.test.ts +21 -2
- package/src/kernel/config/readConfig.test.ts +1 -1
- package/src/kernel/error/renderError.ts +3 -2
- package/src/kernel/error/validOptions.test.ts +57 -0
- package/src/kernel/error/validOptions.ts +45 -0
- package/src/kernel/packs/collect.test.ts +2 -0
- package/src/kernel/packs/compile.test.ts +188 -15
- package/src/kernel/packs/compile.ts +109 -14
- package/src/kernel/packs/cursor.ts +135 -0
- package/src/kernel/packs/filteredEmpty.test.ts +213 -0
- package/src/kernel/packs/graphql/buildLookupDocument.ts +16 -9
- package/src/kernel/packs/graphqlSource.test.ts +152 -0
- package/src/kernel/packs/listNotice.test.ts +156 -0
- package/src/kernel/packs/paging.ts +110 -0
- package/src/kernel/packs/renderPack.ts +186 -13
- package/src/kernel/packs/resolveEntity.test.ts +62 -3
- package/src/kernel/packs/resolveEntity.ts +186 -26
- package/src/kernel/packs/runBodies.ts +196 -26
- package/src/kernel/packs/schema.ts +180 -13
- package/src/kernel/packs/sparql/authorQuery.test.ts +179 -0
- package/src/kernel/packs/sparql/authorQuery.ts +257 -0
- package/src/kernel/packs/sparql/buildListQuery.test.ts +344 -0
- package/src/kernel/packs/sparql/buildListQuery.ts +344 -0
- package/src/kernel/packs/sparql/buildLookupQuery.test.ts +138 -0
- package/src/kernel/packs/sparql/buildLookupQuery.ts +140 -20
- package/src/kernel/packs/sparql/filterValues.test.ts +86 -0
- package/src/kernel/packs/sparql/filterValues.ts +182 -0
- package/src/kernel/packs/sparql/index.ts +30 -14
- package/src/kernel/packs/sparql/runSelect.test.ts +63 -0
- package/src/kernel/packs/sparql/runSelect.ts +42 -2
- package/src/kernel/packs/sparql/searchTerm.ts +29 -0
- package/src/kernel/packs/storyRules.ts +180 -0
- package/src/kernel/packs/tierScope.test.ts +487 -0
- package/src/kernel/packs/tierScope.ts +409 -0
- package/src/kernel/packs/toolDescription.test.ts +13 -10
- package/src/kernel/packs/types.ts +311 -9
- package/src/kernel/project/cli/buildProgram.ts +17 -1
- package/src/kernel/project/cli/dispatch.test.ts +87 -0
- package/src/kernel/project/cli/dispatch.ts +114 -4
- package/src/kernel/project/cli/rootHelp.ts +8 -5
- package/src/kernel/project/cli/suggest.test.ts +35 -0
- package/src/kernel/project/cli/suggestNames.ts +34 -7
- package/src/kernel/project/mcp/registerVerb.ts +5 -1
- package/src/kernel/render/contracts.ts +2 -0
- package/src/kernel/render/prefixes.test.ts +2 -0
- package/src/kernel/render/renderers.test.ts +43 -0
- package/src/kernel/render/renderers.ts +34 -1
- package/src/kernel/runtime/graphpack/embedded/pack.generated.ts +2 -3
- package/src/kernel/runtime/graphpack/embedded/pack.index.generated.ts +1 -1
- package/src/kernel/runtime/graphpack/embedded/pack.manifest.generated.ts +3 -0
- package/src/kernel/runtime/graphpack/embedded.ts +14 -6
- package/src/kernel/runtime/graphpack/graphpack.test.ts +31 -5
- package/src/kernel/runtime/graphpack/read.ts +17 -13
- package/src/kernel/runtime/loadSession.ts +3 -1
- package/src/kernel/runtime/probe.ts +1 -1
- package/src/kernel/runtime/readEntity.ts +19 -4
- package/src/kernel/runtime/refs/parseRef.test.ts +49 -1
- package/src/kernel/runtime/refs/parseRef.ts +25 -1
- package/src/kernel/runtime/refs/resolve.ts +39 -5
- package/src/kernel/runtime/types.ts +30 -0
- package/src/kernel/spec/emitReference.ts +1 -1
- package/src/kernel/spec/surfaceConformance.test.ts +2 -2
- package/src/kernel/spec/types.ts +17 -0
- package/src/testing/PARITY_GAPS.ts +4 -4
- package/src/testing/behavioral/agentSession.mcp.test.ts +43 -39
- package/src/testing/behavioral/completion.test.ts +7 -6
- package/src/testing/behavioral/disclosure.test.ts +39 -37
- package/src/testing/behavioral/errorMatrix.mcp.test.ts +82 -64
- package/src/testing/behavioral/errorMatrix.storeless.test.ts +11 -10
- package/src/testing/behavioral/journeys.cli.test.ts +71 -32
- package/src/testing/behavioral/journeys.defaultPack.test.ts +6 -4
- package/src/testing/behavioral/journeys.firstInstall.test.ts +70 -5
- package/src/testing/behavioral/journeys.mcpLifecycle.mcp.test.ts +4 -3
- package/src/testing/behavioral/journeys.packageStories.test.ts +2 -1
- package/src/testing/behavioral/mcpSurface.test.ts +28 -25
- package/src/testing/behavioral/parity.test.ts +25 -23
- package/src/testing/eval/__snapshots__/eval.test.ts.snap +20 -0
- package/src/testing/eval/cases/effectful.ts +7 -1
- package/src/testing/eval/cases/readNouns.ts +100 -11
- package/src/testing/fixtures/blockGraph.ts +89 -5
- package/src/testing/fixtures/connectGraph.ts +159 -0
- package/src/testing/fixtures/graph/canonical.ts +128 -32
- package/src/testing/helpers/fsProbe.ts +88 -0
- package/src/testing/helpers/runCli.test.ts +44 -0
- package/src/testing/helpers/runCli.ts +27 -3
- package/src/testing/perf/budgets.test.ts +3 -1
- package/src/testing/perf/budgets.ts +138 -54
- package/dist/src/kernel/packs/sparql/applyFilters.js +0 -167
- package/dist/src/kernel/packs/sparql/applyFilters.js.map +0 -1
- package/dist/src/kernel/packs/sparql/applySearch.js +0 -25
- package/dist/src/kernel/packs/sparql/applySearch.js.map +0 -1
- package/src/kernel/packs/sparql/applyFilters.ts +0 -203
- package/src/kernel/packs/sparql/applySearch.ts +0 -33
|
@@ -6,13 +6,19 @@
|
|
|
6
6
|
* `lookup` both compile from the declared story, so parity is asserted directly
|
|
7
7
|
* against the graph. Two halves:
|
|
8
8
|
*
|
|
9
|
-
* - `block list` — the
|
|
10
|
-
*
|
|
11
|
-
* query's required `ds:tier` join dropped (A2, formerly
|
|
12
|
-
* `--all-tiers`)
|
|
13
|
-
*
|
|
14
|
-
*
|
|
15
|
-
*
|
|
9
|
+
* - `block list` — the TIER-SCOPED list. Every block the scope admits,
|
|
10
|
+
* whatever its channel, including the UNTIERED subcomponent the old
|
|
11
|
+
* hand-written query's required `ds:tier` join dropped (A2, formerly
|
|
12
|
+
* reachable only under `--all-tiers`) — an entity the scope cannot place is
|
|
13
|
+
* not one it may hide, so no scope drops it either. This fixture declares one
|
|
14
|
+
* tier, `global`, so the scope admits everything in it and the row-shape
|
|
15
|
+
* cases below read what they always read; what the scope decides is asserted
|
|
16
|
+
* in `kernel/packs/tierScope.test.ts` and, over the shipped pack's fifteen
|
|
17
|
+
* tiers, in `block.tierRank.exec.test.ts`. The row shape the hand-written
|
|
18
|
+
* verb built in TypeScript — the name fallback, the lowercased type, the
|
|
19
|
+
* tier's local name — is now derived IN SPARQL by `BIND`/`COALESCE`, and this
|
|
20
|
+
* file is what holds those BINDs to the shapes the old `blockList.verb.ts`
|
|
21
|
+
* produced.
|
|
16
22
|
* - `block lookup` — unchanged GraphQL content parity, moved here from the
|
|
17
23
|
* deleted `capabilities/block/parity.test.ts`. Button and Modal resolve with
|
|
18
24
|
* the same content a direct SPARQL oracle returns (summary, guidance,
|
|
@@ -26,7 +32,7 @@
|
|
|
26
32
|
|
|
27
33
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
28
34
|
import type { LookupOutput } from "../kernel/packs/resolveEntity.js";
|
|
29
|
-
import type {
|
|
35
|
+
import type { PackPage } from "../kernel/packs/types.js";
|
|
30
36
|
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
31
37
|
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
32
38
|
import type { VerbSpec } from "../kernel/spec/types.js";
|
|
@@ -66,13 +72,13 @@ async function lookup(name: string): Promise<Record<string, unknown>> {
|
|
|
66
72
|
return out.results.at(0) as Record<string, unknown>;
|
|
67
73
|
}
|
|
68
74
|
|
|
69
|
-
describe("block list parity — the declared,
|
|
75
|
+
describe("block list parity — the declared, tier-scoped list", () => {
|
|
70
76
|
it("lists EVERY block, including the untiered subcomponent (A2)", async () => {
|
|
71
77
|
// `ds:button.icon` is a `ds:Subcomponent` with NO `ds:tier`. The
|
|
72
78
|
// hand-written query inner-joined `?c ds:tier ?t` in its default view and
|
|
73
79
|
// surfaced it only under `--all-tiers`; the declared query's OPTIONAL join
|
|
74
80
|
// means there is no view it can fall out of.
|
|
75
|
-
const rows = (await verb("list").run({}, rt)) as
|
|
81
|
+
const rows = ((await verb("list").run({}, rt)) as PackPage).rows;
|
|
76
82
|
expect(rows.map((row) => row.name)).toEqual([
|
|
77
83
|
"Button",
|
|
78
84
|
"Button Icon",
|
|
@@ -81,7 +87,7 @@ describe("block list parity — the declared, unfiltered list", () => {
|
|
|
81
87
|
});
|
|
82
88
|
|
|
83
89
|
it("derives the summary row shape in SPARQL, matching the old hand-built row", async () => {
|
|
84
|
-
const rows = (await verb("list").run({}, rt)) as
|
|
90
|
+
const rows = ((await verb("list").run({}, rt)) as PackPage).rows;
|
|
85
91
|
const byName = new Map(rows.map((row) => [row.name, row]));
|
|
86
92
|
const button = byName.get("Button");
|
|
87
93
|
// `type`: the LOWERCASED local name of the matched class — the BIND that
|
|
@@ -99,30 +105,73 @@ describe("block list parity — the declared, unfiltered list", () => {
|
|
|
99
105
|
});
|
|
100
106
|
|
|
101
107
|
it("an untiered block simply carries no tier — it is not dropped or blanked", async () => {
|
|
102
|
-
const rows = (await verb("list").run({}, rt)) as
|
|
108
|
+
const rows = ((await verb("list").run({}, rt)) as PackPage).rows;
|
|
103
109
|
const icon = rows.find((row) => row.name === "Button Icon");
|
|
104
110
|
expect(icon).toBeDefined();
|
|
105
111
|
expect(icon?.tier).toBeUndefined();
|
|
106
112
|
expect(icon?.uri).toBe(`${DS}button.icon`);
|
|
107
113
|
});
|
|
108
114
|
|
|
109
|
-
it("
|
|
110
|
-
// The
|
|
111
|
-
//
|
|
112
|
-
|
|
115
|
+
it("READS the configured tier as its scope, and the channel not at all", async () => {
|
|
116
|
+
// The `tier` key used to scope nothing: this suite pinned the list as
|
|
117
|
+
// CONFIG-INDEPENDENT, which was the signed-off consequence of the
|
|
118
|
+
// hand-written filtering's removal. The tier scope reverses exactly that
|
|
119
|
+
// half and only that half — `tier` decides which tiers a read answers
|
|
120
|
+
// from, `channel` still decides nothing (it selects an npm dist-tag).
|
|
121
|
+
//
|
|
122
|
+
// This fixture declares ONE tier (`global`, the base), so the scope it
|
|
123
|
+
// resolves to is the same set by either route; what this holds is that the
|
|
124
|
+
// config is READ and REPORTED. Narrowing down a real chain is asserted
|
|
125
|
+
// where a chain exists — `tierScope.test.ts` over a nested fixture, and
|
|
126
|
+
// `block.tierRank.exec.test.ts` over the shipped pack's fifteen tiers.
|
|
127
|
+
const withConfig = (tier: string, channel: "normal" | "experimental") =>
|
|
128
|
+
({
|
|
129
|
+
...rt,
|
|
130
|
+
loadConfig: async () => ({
|
|
131
|
+
...(await rt.loadConfig()),
|
|
132
|
+
config: { ...(await rt.loadConfig()).config, tier, channel },
|
|
133
|
+
}),
|
|
134
|
+
}) as PragmaRuntime;
|
|
135
|
+
|
|
136
|
+
const scoped = (await verb("list").run(
|
|
137
|
+
{},
|
|
138
|
+
withConfig("global", "experimental"),
|
|
139
|
+
)) as PackPage;
|
|
140
|
+
expect(scoped.scope).toEqual({ tiers: ["global"] });
|
|
141
|
+
expect(scoped.rows.map((row) => row.name)).toEqual([
|
|
142
|
+
"Button",
|
|
143
|
+
"Button Icon",
|
|
144
|
+
"Modal",
|
|
145
|
+
]);
|
|
146
|
+
|
|
147
|
+
// `all` turns the scope off, and the page then reports none — the shape
|
|
148
|
+
// every unscoped story's page has always had.
|
|
149
|
+
const all = (await verb("list").run(
|
|
150
|
+
{},
|
|
151
|
+
withConfig("all", "normal"),
|
|
152
|
+
)) as PackPage;
|
|
153
|
+
expect(all.scope).toBeUndefined();
|
|
154
|
+
expect(all.rows).toEqual(
|
|
155
|
+
((await verb("list").run({}, rt)) as PackPage).rows,
|
|
156
|
+
);
|
|
157
|
+
});
|
|
158
|
+
|
|
159
|
+
it("refuses a configured tier no tier answers to, naming the ones that do", async () => {
|
|
160
|
+
// A `tier` nothing in the pack carries used to be inert — accepted by the
|
|
161
|
+
// validator and read by nobody. Now it decides a scope, so a value that
|
|
162
|
+
// names no tier is a misconfiguration worth saying out loud rather than
|
|
163
|
+
// silently answering from a scope the reader did not ask for.
|
|
164
|
+
const stale = {
|
|
113
165
|
...rt,
|
|
114
166
|
loadConfig: async () => ({
|
|
115
167
|
...(await rt.loadConfig()),
|
|
116
|
-
config: {
|
|
117
|
-
...(await rt.loadConfig()).config,
|
|
118
|
-
tier: "apps/lxd",
|
|
119
|
-
channel: "experimental" as const,
|
|
120
|
-
},
|
|
168
|
+
config: { ...(await rt.loadConfig()).config, tier: "apps/lxd" },
|
|
121
169
|
}),
|
|
122
170
|
} as PragmaRuntime;
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
171
|
+
await expect(verb("list").run({}, stale)).rejects.toMatchObject({
|
|
172
|
+
code: "INVALID_INPUT",
|
|
173
|
+
validOptions: ["all", "global"],
|
|
174
|
+
});
|
|
126
175
|
});
|
|
127
176
|
|
|
128
177
|
it("zero rows is a calm empty list, not an error (the declared-family semantics)", async () => {
|
|
@@ -139,12 +188,12 @@ ds:Component a owl:Class .
|
|
|
139
188
|
prefixes: BLOCK_PREFIXES,
|
|
140
189
|
});
|
|
141
190
|
try {
|
|
142
|
-
const
|
|
143
|
-
expect(rows).toEqual([]);
|
|
191
|
+
const page = (await verb("list").run({}, emptyRt)) as PackPage;
|
|
192
|
+
expect(page.rows).toEqual([]);
|
|
144
193
|
// The same semantics, on the empty-state seam: the calm message + its
|
|
145
194
|
// runnable hint now reach the user via stderr (`notice`), keeping
|
|
146
195
|
// plain stdout pure data.
|
|
147
|
-
const notice = verb("list").output.formatters.notice?.(
|
|
196
|
+
const notice = verb("list").output.formatters.notice?.(page);
|
|
148
197
|
expect(notice).toContain("No blocks in the store.");
|
|
149
198
|
expect(notice).toContain("pragma sources update");
|
|
150
199
|
} finally {
|
|
@@ -208,11 +257,142 @@ describe("block lookup — Button content parity (GraphQL, detailed)", () => {
|
|
|
208
257
|
expect(llm).toContain("#### When not to use");
|
|
209
258
|
expect(llm).not.toContain("\n### When to use\n");
|
|
210
259
|
expect(llm).toContain("### Anatomy (DSL)");
|
|
260
|
+
// The anatomy is a NOTATION, and its one unguessable rule is stated where
|
|
261
|
+
// the reader is looking: a bracketed value reads as a set of equals and is
|
|
262
|
+
// in fact an ordered fallback chain.
|
|
263
|
+
expect(llm).toContain(
|
|
264
|
+
"### Anatomy (DSL)\nA list value is the fallback chain the implementation reads, first wins.",
|
|
265
|
+
);
|
|
211
266
|
expect(llm).toContain("### Modifier Families");
|
|
212
267
|
expect(llm).toContain("primary");
|
|
213
268
|
});
|
|
214
269
|
});
|
|
215
270
|
|
|
271
|
+
describe("block lookup — the tokens a block consumes (SPARQL-lane expand)", () => {
|
|
272
|
+
it("lists one row per binding, with all six identity columns", async () => {
|
|
273
|
+
const button = await lookup("Button");
|
|
274
|
+
// Six columns and not five: the anatomy's `styleKey` and `styleState`
|
|
275
|
+
// carry no rdfs:domain, so the GraphQL document this lookup otherwise
|
|
276
|
+
// builds derives no field for either and dropped both WITHOUT a word —
|
|
277
|
+
// leaving `appearance.background` rank 1 and rank 2 looking like one fact
|
|
278
|
+
// repeated. That is why this expand declares the SPARQL lane.
|
|
279
|
+
expect(button.tokens).toEqual([
|
|
280
|
+
{
|
|
281
|
+
symbol: "color.background",
|
|
282
|
+
key: "appearance.background",
|
|
283
|
+
state: "default",
|
|
284
|
+
rank: "1",
|
|
285
|
+
node: "$root",
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
symbol: "color.background.hover",
|
|
289
|
+
key: "appearance.background",
|
|
290
|
+
state: "hover",
|
|
291
|
+
rank: "2",
|
|
292
|
+
node: "$root",
|
|
293
|
+
},
|
|
294
|
+
{
|
|
295
|
+
symbol: "color.text",
|
|
296
|
+
key: "typography.color",
|
|
297
|
+
state: "default",
|
|
298
|
+
rank: "1",
|
|
299
|
+
node: "$root",
|
|
300
|
+
},
|
|
301
|
+
{
|
|
302
|
+
symbol: "color.icon",
|
|
303
|
+
key: "appearance.background",
|
|
304
|
+
state: "default",
|
|
305
|
+
rank: "1",
|
|
306
|
+
via: "Button Icon",
|
|
307
|
+
node: "$root/icon",
|
|
308
|
+
},
|
|
309
|
+
]);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
it("orders the rows by node, then key, then rank", async () => {
|
|
313
|
+
// The fixture declares them in another order on purpose: the store's own
|
|
314
|
+
// scan order is undefined, and a table of a node's styles is only readable
|
|
315
|
+
// one node at a time, one key at a time, each key's fallback chain in the
|
|
316
|
+
// order the implementation walks it.
|
|
317
|
+
const button = await lookup("Button");
|
|
318
|
+
const rows = button.tokens as {
|
|
319
|
+
node: string;
|
|
320
|
+
key: string;
|
|
321
|
+
rank: string;
|
|
322
|
+
}[];
|
|
323
|
+
expect(rows.map((row) => [row.node, row.key, row.rank])).toEqual([
|
|
324
|
+
["$root", "appearance.background", "1"],
|
|
325
|
+
["$root", "appearance.background", "2"],
|
|
326
|
+
["$root", "typography.color", "1"],
|
|
327
|
+
["$root/icon", "appearance.background", "1"],
|
|
328
|
+
]);
|
|
329
|
+
});
|
|
330
|
+
|
|
331
|
+
it("blanks the via on the block's own tree and prints it otherwise", async () => {
|
|
332
|
+
// `ds:viaBlock` is asserted on EVERY record and equals the block on the
|
|
333
|
+
// block's own tree, so printing it raw put "Button" in every one of
|
|
334
|
+
// Button's own rows — a cell that is only ever news when it differs.
|
|
335
|
+
const button = await lookup("Button");
|
|
336
|
+
const rows = button.tokens as { via?: string; node: string }[];
|
|
337
|
+
for (const row of rows.filter((row) => row.node === "$root")) {
|
|
338
|
+
expect(row.via).toBeUndefined();
|
|
339
|
+
}
|
|
340
|
+
expect(rows.find((row) => row.node === "$root/icon")?.via).toBe(
|
|
341
|
+
"Button Icon",
|
|
342
|
+
);
|
|
343
|
+
|
|
344
|
+
// Modal consumes nothing of its own: its one row is Button's binding,
|
|
345
|
+
// reached through the Button its anatomy embeds, so the via is the whole
|
|
346
|
+
// point of the row.
|
|
347
|
+
const modal = await lookup("Modal");
|
|
348
|
+
expect(modal.tokens).toEqual([
|
|
349
|
+
{
|
|
350
|
+
symbol: "color.background",
|
|
351
|
+
key: "appearance.background",
|
|
352
|
+
state: "default",
|
|
353
|
+
rank: "1",
|
|
354
|
+
via: "Button",
|
|
355
|
+
node: "$root/footer",
|
|
356
|
+
},
|
|
357
|
+
]);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
it("renders the section under its own heading, with the reading note", async () => {
|
|
361
|
+
const llm = verb("lookup").output.formatters.llm(
|
|
362
|
+
(await verb("lookup").run({ name: ["Button"] }, rt)) as never,
|
|
363
|
+
);
|
|
364
|
+
// The same sentence `Anatomy (DSL)` carries, because the rank column is
|
|
365
|
+
// that notation's fallback chain seen from the other side.
|
|
366
|
+
expect(llm).toContain(
|
|
367
|
+
"### Tokens\nA list value in the anatomy is the fallback chain the implementation reads, first wins; rank is the position in that chain.",
|
|
368
|
+
);
|
|
369
|
+
expect(llm).toContain("symbol: color.text | key: typography.color");
|
|
370
|
+
});
|
|
371
|
+
|
|
372
|
+
it("prints no Tokens heading for a block that consumes nothing", async () => {
|
|
373
|
+
// What every other section on this lookup does when it is empty: no
|
|
374
|
+
// heading at all, rather than a heading with nothing under it. The key is
|
|
375
|
+
// still present in the payload as an empty array, so the machine shape
|
|
376
|
+
// stays uniform.
|
|
377
|
+
const icon = await lookup("Button Icon");
|
|
378
|
+
expect(icon.tokens).toEqual([]);
|
|
379
|
+
const llm = verb("lookup").output.formatters.llm(
|
|
380
|
+
(await verb("lookup").run({ name: ["Button Icon"] }, rt)) as never,
|
|
381
|
+
);
|
|
382
|
+
expect(llm).not.toContain("### Tokens");
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
it("is withheld at the summary level with the other detailed expands", async () => {
|
|
386
|
+
const out = (await verb("lookup").run(
|
|
387
|
+
{ name: ["Button"] },
|
|
388
|
+
{ ...rt, globalFlags: { ...rt.globalFlags, detail: "summary" } },
|
|
389
|
+
)) as LookupOutput;
|
|
390
|
+
expect(
|
|
391
|
+
(out.results.at(0) as Record<string, unknown>).tokens,
|
|
392
|
+
).toBeUndefined();
|
|
393
|
+
});
|
|
394
|
+
});
|
|
395
|
+
|
|
216
396
|
describe("block lookup — Modal content parity (GraphQL, detailed)", () => {
|
|
217
397
|
it("resolves the full Modal spec (a different block, same document engine)", async () => {
|
|
218
398
|
const modal = await lookup("Modal");
|
|
@@ -131,6 +131,44 @@ const DECLARED_PROPERTIES: readonly DeclaredTerm[] = [
|
|
|
131
131
|
]),
|
|
132
132
|
];
|
|
133
133
|
|
|
134
|
+
/**
|
|
135
|
+
* Standard RDF vocabulary, which no domain ontology declares and every graph
|
|
136
|
+
* carries — the same exemption, for the same reason, that the token stories'
|
|
137
|
+
* shipped sweep makes: the distribution's own vocabulary declaration leaves
|
|
138
|
+
* `rdfs:label` out because "the kernel treats standard vocabulary as
|
|
139
|
+
* universal", and the shipped pack accordingly carries no T-box triple about
|
|
140
|
+
* it at all. Nothing upstream can retire a W3C term, which is the defect this
|
|
141
|
+
* file exists to catch.
|
|
142
|
+
*
|
|
143
|
+
* A readable list rather than a widened ASK, so the sweep stays a hard
|
|
144
|
+
* requirement on every DOMAIN term.
|
|
145
|
+
*/
|
|
146
|
+
const STANDARD_VOCABULARY = new Set(["rdfs:label", "rdfs:comment"]);
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* The property STEPS one declaration names — more than one when it is a path.
|
|
150
|
+
*
|
|
151
|
+
* A SPARQL-lane declaration may read a grandchild through a path
|
|
152
|
+
* (`ds:consumesSymbol/rdfs:label`), and every domain step of it is a term the
|
|
153
|
+
* ontology has to define: a retired step anywhere along a path renders exactly
|
|
154
|
+
* as silently as a retired single term. Split rather than skipped — a path
|
|
155
|
+
* spliced whole into the ASK's subject position would not even parse, so the
|
|
156
|
+
* check would have raised instead of judging, and skipping paths outright
|
|
157
|
+
* would leave their steps unguarded.
|
|
158
|
+
*
|
|
159
|
+
* An absolute IRI is left whole: a `/` inside `https://…` is not a path
|
|
160
|
+
* separator, which is the grammar's own test for a property path.
|
|
161
|
+
*/
|
|
162
|
+
function pathSteps(property: string): readonly string[] {
|
|
163
|
+
if (!property.includes("/") || property.includes("://")) {
|
|
164
|
+
return STANDARD_VOCABULARY.has(property) ? [] : [property];
|
|
165
|
+
}
|
|
166
|
+
return property
|
|
167
|
+
.split("/")
|
|
168
|
+
.map((step) => step.replace(/^\^/, "").replace(/[*+?]$/, ""))
|
|
169
|
+
.filter((step) => step !== "" && !STANDARD_VOCABULARY.has(step));
|
|
170
|
+
}
|
|
171
|
+
|
|
134
172
|
/** The IRI of the block whose usage narrative is asserted on below. */
|
|
135
173
|
const BUTTON = "https://ds.canonical.com/global.component.button";
|
|
136
174
|
|
|
@@ -167,25 +205,129 @@ describe("the `block` story declares vocabulary the shipped ontology defines (PR
|
|
|
167
205
|
expect(DECLARED_PROPERTIES.length).toBeGreaterThan(0);
|
|
168
206
|
});
|
|
169
207
|
|
|
170
|
-
it.each(
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
208
|
+
it.each(DECLARED_PROPERTIES)(
|
|
209
|
+
"$what reads $property, which the shipped ontology defines",
|
|
210
|
+
async ({ what, property }) => {
|
|
211
|
+
for (const hop of pathSteps(property)) {
|
|
212
|
+
const result = await rt.query.sparql(
|
|
213
|
+
[
|
|
214
|
+
"ASK {",
|
|
215
|
+
` ${hop} a ?kind .`,
|
|
216
|
+
" VALUES ?kind { owl:DatatypeProperty owl:ObjectProperty }",
|
|
217
|
+
"}",
|
|
218
|
+
].join("\n"),
|
|
219
|
+
);
|
|
220
|
+
expect(result.type).toBe("ask");
|
|
221
|
+
expect(
|
|
222
|
+
result.type === "ask" ? result.result : false,
|
|
223
|
+
`${what} reads ${property}, whose hop ${hop} the shipped ontology does not define — it can never render, for any block, on any install`,
|
|
224
|
+
).toBe(true);
|
|
225
|
+
}
|
|
226
|
+
},
|
|
227
|
+
);
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
describe("block lookup lists the tokens a block consumes (PROTECTED)", () => {
|
|
231
|
+
it("renders one row per binding record, with all six identity columns", async () => {
|
|
232
|
+
// The binding records are the design system's, derived from the anatomies,
|
|
233
|
+
// and they ride the embedded snapshot — so the ONE thing a fixture cannot
|
|
234
|
+
// prove is that the shipped corpus reaches this expand at all. Button is
|
|
235
|
+
// asserted on rather than any block: it is the block whose anatomy every
|
|
236
|
+
// other lookup test reads, and it carries bindings on its own tree AND on
|
|
237
|
+
// the tree of the icon it embeds.
|
|
238
|
+
const records = await scalar(
|
|
239
|
+
`SELECT (COUNT(*) AS ?n) WHERE { <${BUTTON}> ds:hasTokenBinding ?r }`,
|
|
240
|
+
);
|
|
241
|
+
expect(Number(records)).toBeGreaterThan(0);
|
|
242
|
+
|
|
243
|
+
const llm = await renderLookup(BUTTON);
|
|
244
|
+
expect(llm).toContain("### Tokens");
|
|
245
|
+
// The note, because a rank column without it is a number with no stated
|
|
246
|
+
// meaning.
|
|
247
|
+
expect(llm).toContain("rank is the position in that chain");
|
|
248
|
+
|
|
249
|
+
// One rendered row per record, and every row carrying the two columns the
|
|
250
|
+
// GraphQL lane cannot derive (neither `anatomy:styleKey` nor
|
|
251
|
+
// `anatomy:styleState` has an rdfs:domain, so no field exists for either
|
|
252
|
+
// on `TokenBinding`). Their absence is what made two of Button's rows
|
|
253
|
+
// byte-identical, so their presence is the assertion.
|
|
254
|
+
const rows = (llm.split("### Tokens")[1] ?? "")
|
|
255
|
+
.split("\n")
|
|
256
|
+
.filter((line) => line.startsWith("- symbol: "));
|
|
257
|
+
expect(rows).toHaveLength(Number(records));
|
|
258
|
+
for (const row of rows) {
|
|
259
|
+
expect(row).toContain(" | key: ");
|
|
260
|
+
expect(row).toContain(" | node: ");
|
|
261
|
+
}
|
|
262
|
+
// And no two rows the same — the defect a dropped identity column causes.
|
|
263
|
+
expect(new Set(rows).size).toBe(rows.length);
|
|
264
|
+
});
|
|
265
|
+
|
|
266
|
+
it("blanks the via on a block's own tree and prints an inherited one", async () => {
|
|
267
|
+
// `ds:viaBlock` is asserted on every record and its own definition says it
|
|
268
|
+
// equals the block in the own-tree case, so an unconditional column put
|
|
269
|
+
// the block's own name in every one of its rows.
|
|
270
|
+
const ownTree = await scalar(
|
|
271
|
+
[
|
|
272
|
+
"SELECT (COUNT(*) AS ?n) WHERE {",
|
|
273
|
+
` <${BUTTON}> ds:hasTokenBinding ?r .`,
|
|
274
|
+
` ?r ds:viaBlock <${BUTTON}> .`,
|
|
275
|
+
"}",
|
|
276
|
+
].join("\n"),
|
|
277
|
+
);
|
|
278
|
+
expect(Number(ownTree)).toBeGreaterThan(0);
|
|
279
|
+
|
|
280
|
+
const llm = await renderLookup(BUTTON);
|
|
281
|
+
const rows = (llm.split("### Tokens")[1] ?? "")
|
|
282
|
+
.split("\n")
|
|
283
|
+
.filter((line) => line.startsWith("- symbol: "));
|
|
284
|
+
expect(rows.filter((row) => !row.includes(" | via: "))).toHaveLength(
|
|
285
|
+
Number(ownTree),
|
|
286
|
+
);
|
|
287
|
+
|
|
288
|
+
// A block whose bindings are ALL inherited, read from the graph rather
|
|
289
|
+
// than named here: every row of its section must carry a via.
|
|
290
|
+
const inheritor = await scalar(
|
|
177
291
|
[
|
|
178
|
-
"
|
|
179
|
-
` ${
|
|
180
|
-
"
|
|
292
|
+
"SELECT ?b WHERE {",
|
|
293
|
+
` VALUES ?class { ${TYPE_VALUES} }`,
|
|
294
|
+
" ?b a ?class ; ds:hasTokenBinding ?r .",
|
|
295
|
+
" FILTER NOT EXISTS { ?b ds:hasTokenBinding/ds:viaBlock ?b }",
|
|
181
296
|
"}",
|
|
297
|
+
"ORDER BY ?b",
|
|
298
|
+
"LIMIT 1",
|
|
182
299
|
].join("\n"),
|
|
183
300
|
);
|
|
184
|
-
expect(result.type).toBe("ask");
|
|
185
301
|
expect(
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
).
|
|
302
|
+
inheritor,
|
|
303
|
+
"no shipped block carries only inherited bindings",
|
|
304
|
+
).toBeDefined();
|
|
305
|
+
const inheritedRows = (
|
|
306
|
+
(await renderLookup(inheritor as string)).split("### Tokens")[1] ?? ""
|
|
307
|
+
)
|
|
308
|
+
.split("\n")
|
|
309
|
+
.filter((line) => line.startsWith("- symbol: "));
|
|
310
|
+
expect(inheritedRows.length).toBeGreaterThan(0);
|
|
311
|
+
for (const row of inheritedRows) expect(row).toContain(" | via: ");
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
it("prints no Tokens heading for a block that consumes nothing", async () => {
|
|
315
|
+
// The shape every other section on this lookup has when it is empty. Read
|
|
316
|
+
// from the graph, because which blocks have no bindings is upstream's to
|
|
317
|
+
// change.
|
|
318
|
+
const uri = await scalar(
|
|
319
|
+
[
|
|
320
|
+
"SELECT ?b WHERE {",
|
|
321
|
+
` VALUES ?class { ${TYPE_VALUES} }`,
|
|
322
|
+
" ?b a ?class .",
|
|
323
|
+
" FILTER NOT EXISTS { ?b ds:hasTokenBinding ?r }",
|
|
324
|
+
"}",
|
|
325
|
+
"ORDER BY ?b",
|
|
326
|
+
"LIMIT 1",
|
|
327
|
+
].join("\n"),
|
|
328
|
+
);
|
|
329
|
+
expect(uri, "every shipped block carries bindings").toBeDefined();
|
|
330
|
+
expect(await renderLookup(uri as string)).not.toContain("### Tokens");
|
|
189
331
|
});
|
|
190
332
|
});
|
|
191
333
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
* Which block a SHARED name means, EXECUTED against the shipped pack
|
|
3
3
|
* (PROTECTED).
|
|
4
4
|
*
|
|
5
|
-
*
|
|
5
|
+
* 32 of the design system's block names are carried by two or three blocks
|
|
6
6
|
* apiece, across tiers. `block lookup` used to answer each of them with whichever
|
|
7
7
|
* IRI sorted first and say nothing about the rest, so `button` returned
|
|
8
8
|
* Launchpad's Button — `apps_launchpad…` sorts before `global…` — and an agent
|
|
@@ -27,6 +27,8 @@
|
|
|
27
27
|
*/
|
|
28
28
|
|
|
29
29
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
30
|
+
import { MAX_LIST_WINDOW } from "../kernel/packs/paging.js";
|
|
31
|
+
import type { PackPage } from "../kernel/packs/types.js";
|
|
30
32
|
import { verbKey } from "../kernel/packs/uniqueness.js";
|
|
31
33
|
import { bootRuntime } from "../kernel/runtime/boot.js";
|
|
32
34
|
import type { PragmaRuntime } from "../kernel/runtime/types.js";
|
|
@@ -80,11 +82,23 @@ async function rows(query: string): Promise<Record<string, string>[]> {
|
|
|
80
82
|
: [];
|
|
81
83
|
}
|
|
82
84
|
|
|
83
|
-
/**
|
|
85
|
+
/**
|
|
86
|
+
* One `block lookup <name>`: the IRI it answered with, and the ones it did not.
|
|
87
|
+
*
|
|
88
|
+
* ASKED FOR EVERY TIER, and that is load-bearing for this whole file. The claim
|
|
89
|
+
* under test is about RANKING — every block a shared name reaches comes back,
|
|
90
|
+
* best first — and the ranking orders a population the TIER SCOPE would
|
|
91
|
+
* otherwise cut: `block lookup button` answers with the global Button alone
|
|
92
|
+
* now, because the Launchpad one is out of the default scope. Both rules are
|
|
93
|
+
* about the same 25 collisions and they are not the same rule, so this suite
|
|
94
|
+
* widens the read to the population whose order it is judging. That the scope
|
|
95
|
+
* NARROWS it by default is asserted where that is the claim
|
|
96
|
+
* (`tierScope.test.ts`, and the shipped-pack cases below).
|
|
97
|
+
*/
|
|
84
98
|
async function lookup(
|
|
85
99
|
name: string,
|
|
86
100
|
): Promise<{ chosen: string; others: string[] }> {
|
|
87
|
-
const out = (await lookupVerb.run({ name: [name] }, rt)) as {
|
|
101
|
+
const out = (await lookupVerb.run({ name: [name], tier: "all" }, rt)) as {
|
|
88
102
|
results: { uri?: string }[];
|
|
89
103
|
};
|
|
90
104
|
// Every entity the name reached, best first. `chosen` is simply the head —
|
|
@@ -97,8 +111,12 @@ async function lookup(
|
|
|
97
111
|
* Every block name the shipped graph gives to more than one block, with each
|
|
98
112
|
* block's IRI, its tier's declared name and whether it is a subcomponent.
|
|
99
113
|
*
|
|
100
|
-
* Read with the SAME
|
|
101
|
-
* only for a lookup (`Button` vs
|
|
114
|
+
* Read with the SAME equality the resolve filters by — case-folded AND
|
|
115
|
+
* end-trimmed — so a pair that collides only for a lookup (`Button` vs
|
|
116
|
+
* `button`, `Timeline` vs the padded `Timeline ` 66 shipped names are spelled
|
|
117
|
+
* like) is in this set too. An oracle that folded less than the resolve would
|
|
118
|
+
* hand the sweep below a name reaching more blocks than the oracle listed, and
|
|
119
|
+
* read the resolve's correct answer as a failure.
|
|
102
120
|
*/
|
|
103
121
|
async function collisions(): Promise<
|
|
104
122
|
Map<string, { uri: string; tier: string; part: boolean }[]>
|
|
@@ -110,7 +128,7 @@ async function collisions(): Promise<
|
|
|
110
128
|
" ?uri a ?class .",
|
|
111
129
|
` ?uri ${blockLookup.by} ?name .`,
|
|
112
130
|
` OPTIONAL { ?uri ${scope.via} ?tier . ?tier ${scope.by} ?tn . }`,
|
|
113
|
-
|
|
131
|
+
' BIND(LCASE(REPLACE(STR(?name), "^\\\\s+|\\\\s+$", "")) AS ?key)',
|
|
114
132
|
' BIND(COALESCE(?tn, "") AS ?tierName)',
|
|
115
133
|
" BIND(EXISTS { ?uri a ds:Subcomponent } AS ?part)",
|
|
116
134
|
"}",
|
|
@@ -234,10 +252,29 @@ describe("a shared block name reaches every block that carries it (PROTECTED)",
|
|
|
234
252
|
});
|
|
235
253
|
|
|
236
254
|
describe("`block list` orders its ties totally (PROTECTED)", () => {
|
|
237
|
-
/**
|
|
255
|
+
/**
|
|
256
|
+
* The entity IRIs of one `block list` run, in the order it printed them —
|
|
257
|
+
* over EVERY tier, like the lookup cases above, and as ONE page over the whole
|
|
258
|
+
* population, because the order under test is the order of the whole
|
|
259
|
+
* population. A total order over the 313-block population is the property; the
|
|
260
|
+
* default scope's rows are a subsequence of it, so judging the order on the
|
|
261
|
+
* wider read judges both. A default-paged read used to be the whole population
|
|
262
|
+
* and quietly stopped being it: `DEFAULT_LIST_LIMIT` is 300, so the tail of
|
|
263
|
+
* the alphabet — `Timestamp`, `Tooltip`, and their launchpad twins — fell off
|
|
264
|
+
* the page and the cases below read a missing row as a lost row. The page is
|
|
265
|
+
* asserted COMPLETE rather than assumed so, so that the next time the
|
|
266
|
+
* population outgrows the window this says which it was.
|
|
267
|
+
*/
|
|
238
268
|
async function listOrder(): Promise<string[]> {
|
|
239
|
-
const
|
|
240
|
-
|
|
269
|
+
const page = (await listVerb.run(
|
|
270
|
+
{ tier: "all", limit: MAX_LIST_WINDOW },
|
|
271
|
+
rt,
|
|
272
|
+
)) as PackPage;
|
|
273
|
+
expect(
|
|
274
|
+
page.nextAfter,
|
|
275
|
+
`block list did not fit in one ${MAX_LIST_WINDOW}-row page`,
|
|
276
|
+
).toBeUndefined();
|
|
277
|
+
return page.rows.map((row) => String(row.uri));
|
|
241
278
|
}
|
|
242
279
|
|
|
243
280
|
it("repeats the same order across runs", async () => {
|
|
@@ -31,10 +31,19 @@ import type {
|
|
|
31
31
|
* each other. `system` projects the distribution's identity rather than naming
|
|
32
32
|
* a domain — the live tool catalog the same handshake carries already says
|
|
33
33
|
* which nouns exist. `querying` is verbatim from the old shell (the SPARQL
|
|
34
|
-
* model is still accurate for v2); `
|
|
35
|
-
*
|
|
36
|
-
*
|
|
37
|
-
*
|
|
34
|
+
* model is still accurate for v2); `mutations` is new in v2, surfacing the
|
|
35
|
+
* plan-first/confirm gate.
|
|
36
|
+
*
|
|
37
|
+
* `model` is the one that has had to be rewritten twice, and both times because
|
|
38
|
+
* it was describing a gate that had moved. It stopped claiming reads were
|
|
39
|
+
* tier/channel-scoped when the hand-written filtering was removed (the
|
|
40
|
+
* hierarchy became graph data, not a query gate) — and "Reads are unscoped —
|
|
41
|
+
* every list shows everything" became false the day the tier scope landed. An
|
|
42
|
+
* agent reading it took a scoped `block_list` for the whole design system,
|
|
43
|
+
* which is the exact misreading the sentence existed to prevent. So it now says
|
|
44
|
+
* what is true of each half separately: the TIER hierarchy gates a read of a
|
|
45
|
+
* tiered noun and names the argument that widens it, while the CHANNEL is still
|
|
46
|
+
* data on the entity and gates nothing.
|
|
38
47
|
*/
|
|
39
48
|
export const CONVENTIONS = {
|
|
40
49
|
// `help` is authored as a bare phrase (`--help` renders it as one), so the
|
|
@@ -43,7 +52,7 @@ export const CONVENTIONS = {
|
|
|
43
52
|
// otherwise reads "recipe graph.. A CLI and MCP server…".
|
|
44
53
|
system: `${BIN_NAME} — ${PROGRAM_DESCRIPTION} (a CLI and MCP server over a knowledge graph).`,
|
|
45
54
|
model:
|
|
46
|
-
|
|
55
|
+
'The tier hierarchy (global > apps > apps_lxd) SCOPES every read of a tiered entity: lists answer from the top-level tiers, lookups prefer them. Pass tier: "<name>" for a tier plus its ancestors, or tier: "all" for every tier; each answer states its scope. Channels scope nothing.',
|
|
47
56
|
querying:
|
|
48
57
|
"All queries run against an RDF triple store. Prefixed IRIs (e.g. prefix:name) identify entities. Use ontology_list to discover the active namespaces.",
|
|
49
58
|
mutations:
|