@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
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { formatValidOptions, MAX_LISTED_OPTIONS } from "./validOptions.js";
|
|
3
|
+
|
|
4
|
+
/** `count` option names, distinct and ordered. */
|
|
5
|
+
function options(count: number): string[] {
|
|
6
|
+
return Array.from({ length: count }, (_, index) => `option-${index}`);
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
describe("formatValidOptions", () => {
|
|
10
|
+
it("names every option when the vocabulary is a choice", () => {
|
|
11
|
+
// Every real filter at or below the cap renders in full: the type filter
|
|
12
|
+
// at 7, the tier filter at 16, the coordinate set at 36.
|
|
13
|
+
const line = formatValidOptions(["color", "dimension", "duration"]);
|
|
14
|
+
|
|
15
|
+
expect(line).toBe("Valid options: color, dimension, duration");
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("names every option at exactly the cap", () => {
|
|
19
|
+
const line = formatValidOptions(options(MAX_LISTED_OPTIONS));
|
|
20
|
+
|
|
21
|
+
expect(line).toContain(`option-${MAX_LISTED_OPTIONS - 1}`);
|
|
22
|
+
expect(line).not.toContain("more");
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("withholds the tail one past the cap, and counts it", () => {
|
|
26
|
+
const line = formatValidOptions(options(MAX_LISTED_OPTIONS + 1));
|
|
27
|
+
|
|
28
|
+
expect(line).toContain(`(${MAX_LISTED_OPTIONS + 1})`);
|
|
29
|
+
expect(line).toContain("and 1 more");
|
|
30
|
+
expect(line).not.toContain(`option-${MAX_LISTED_OPTIONS}`);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("keeps a catalogue-sized vocabulary to a readable line", () => {
|
|
34
|
+
// The case that forced this: the variable filter's vocabulary is 1,156
|
|
35
|
+
// names, and naming them all rendered roughly 90KB — unreadable in a
|
|
36
|
+
// terminal, and large enough over MCP to threaten the payload budget.
|
|
37
|
+
const line = formatValidOptions(options(1156));
|
|
38
|
+
|
|
39
|
+
expect(line).toContain("(1156)");
|
|
40
|
+
expect(line).toContain("and 1116 more");
|
|
41
|
+
expect(line.length).toBeLessThan(2000);
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
it("carries a caller's own prefix", () => {
|
|
45
|
+
// The prefix seam is how a caller that renders its OWN options line names
|
|
46
|
+
// the flag. Nothing in the error path passes one any more: the renderers
|
|
47
|
+
// print the vocabulary once, from `validOptions`, and a recovery that
|
|
48
|
+
// enumerated it too printed the same catalogue twice.
|
|
49
|
+
const line = formatValidOptions(
|
|
50
|
+
options(1156),
|
|
51
|
+
"Values allowed for --variable",
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
expect(line.startsWith("Values allowed for --variable (1156)")).toBe(true);
|
|
55
|
+
expect(line.length).toBeLessThan(2000);
|
|
56
|
+
});
|
|
57
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Naming a filter's admissible values without printing a catalogue.
|
|
3
|
+
*
|
|
4
|
+
* ONE error, ONE list. The vocabulary rides an error's `validOptions`, and the
|
|
5
|
+
* renderers here are the only place it is spelled out — a filter that also
|
|
6
|
+
* enumerated it in its `recovery` message printed the same 40 names twice,
|
|
7
|
+
* about 1.4KB of duplicate terminal output on a single mistyped `--symbol`. A
|
|
8
|
+
* recovery says what to DO; the options line says what the values ARE.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
/**
|
|
12
|
+
* How many valid options a rendered error names before it stops listing them.
|
|
13
|
+
*
|
|
14
|
+
* Taken from the real vocabularies rather than picked round: the largest a
|
|
15
|
+
* reader can usefully scan is the coordinate set at 36, so every filter at or
|
|
16
|
+
* below this cap still renders in full. Above it sit the vocabularies that are
|
|
17
|
+
* catalogues rather than choices — variables at 1,156, symbols at 745, blocks
|
|
18
|
+
* at 252 — and naming those turned one mistyped filter value into roughly 90KB
|
|
19
|
+
* of terminal output, or an MCP response large enough to threaten its own
|
|
20
|
+
* payload budget. The JSON error envelope still carries every option, so a
|
|
21
|
+
* machine consumer loses nothing.
|
|
22
|
+
*/
|
|
23
|
+
export const MAX_LISTED_OPTIONS = 40;
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Render an admissible-value list, naming at most {@link MAX_LISTED_OPTIONS}.
|
|
27
|
+
*
|
|
28
|
+
* The count of what is withheld is the useful part of a long vocabulary: it
|
|
29
|
+
* tells the reader this is a catalogue to list, not a choice to read.
|
|
30
|
+
*
|
|
31
|
+
* @param options - Every accepted value, in the order the caller supplies.
|
|
32
|
+
* @param prefix - How the line introduces the values.
|
|
33
|
+
* @returns One line, with no trailing newline and no full stop.
|
|
34
|
+
*/
|
|
35
|
+
export function formatValidOptions(
|
|
36
|
+
options: readonly string[],
|
|
37
|
+
prefix = "Valid options",
|
|
38
|
+
): string {
|
|
39
|
+
if (options.length <= MAX_LISTED_OPTIONS) {
|
|
40
|
+
return `${prefix}: ${options.join(", ")}`;
|
|
41
|
+
}
|
|
42
|
+
const shown = options.slice(0, MAX_LISTED_OPTIONS).join(", ");
|
|
43
|
+
const rest = options.length - MAX_LISTED_OPTIONS;
|
|
44
|
+
return `${prefix} (${options.length}), first ${MAX_LISTED_OPTIONS}: ${shown}, and ${rest} more`;
|
|
45
|
+
}
|
|
@@ -259,6 +259,8 @@ describe("validateStories — package stories NEVER throw (PROTECTED)", () => {
|
|
|
259
259
|
const before = capabilities.find((module) => module.name === "token");
|
|
260
260
|
expect(before?.verbs.map((verb) => verb.path.join(" "))).toEqual([
|
|
261
261
|
"token list",
|
|
262
|
+
"token values",
|
|
263
|
+
"token consumers",
|
|
262
264
|
"token lookup",
|
|
263
265
|
"token sample",
|
|
264
266
|
]);
|
|
@@ -10,11 +10,16 @@
|
|
|
10
10
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
11
11
|
import { RECOVERY_CLI_PREFIX } from "../../constants.js";
|
|
12
12
|
import { buildFixtureRuntime } from "../../testing/helpers/packRuntime.js";
|
|
13
|
+
import { PragmaError } from "../error/index.js";
|
|
13
14
|
import type { PragmaRuntime } from "../runtime/types.js";
|
|
15
|
+
import { kebabCase } from "../spec/emitSurface.js";
|
|
16
|
+
import type { VerbSpec } from "../spec/types.js";
|
|
14
17
|
import { compileListable, compilePack, compileStoryModule } from "./compile.js";
|
|
18
|
+
import { encodeCursor, pageFingerprint } from "./cursor.js";
|
|
19
|
+
import { DEFAULT_LIST_LIMIT, MAX_LIST_WINDOW } from "./paging.js";
|
|
15
20
|
import type { LookupOutput } from "./resolveEntity.js";
|
|
16
21
|
import { parsePackDefinition } from "./schema.js";
|
|
17
|
-
import type { PackDefinition, PackRow } from "./types.js";
|
|
22
|
+
import type { PackDefinition, PackPage, PackRow } from "./types.js";
|
|
18
23
|
import { distributionSource } from "./types.js";
|
|
19
24
|
import { assertUniqueVerbs, verbKey } from "./uniqueness.js";
|
|
20
25
|
|
|
@@ -135,7 +140,14 @@ describe("pack compiler — round-trip + shape (PROTECTED, storeless)", () => {
|
|
|
135
140
|
distributionSource("bundled:widget"),
|
|
136
141
|
PREFIXES,
|
|
137
142
|
);
|
|
138
|
-
|
|
143
|
+
// The page pair is the KERNEL's, on every list-shaped verb whether the
|
|
144
|
+
// story mentions it or not — see compile.ts#PAGE_PARAMS.
|
|
145
|
+
expect(list?.params.map((p) => p.name)).toEqual([
|
|
146
|
+
"kind",
|
|
147
|
+
"search",
|
|
148
|
+
"limit",
|
|
149
|
+
"after",
|
|
150
|
+
]);
|
|
139
151
|
const kind = list?.params.find((p) => p.name === "kind");
|
|
140
152
|
expect(kind?.kind).toBe("enum");
|
|
141
153
|
expect(list?.capability.needsStore).toBe(true);
|
|
@@ -248,8 +260,8 @@ describe("the grammar rejects what the compiler cannot build (PROTECTED)", () =>
|
|
|
248
260
|
|
|
249
261
|
it("rejects a filter param declared twice, on list or on an extra verb", () => {
|
|
250
262
|
const twice = [
|
|
251
|
-
{ param: "kind", variable: "uri" },
|
|
252
|
-
{ param: "kind", variable: "uri" },
|
|
263
|
+
{ param: "kind", variable: "uri", values: ["a"] },
|
|
264
|
+
{ param: "kind", variable: "uri", values: ["a"] },
|
|
253
265
|
];
|
|
254
266
|
expect(
|
|
255
267
|
parse({ noun: "widget", list: { ...listShape, filters: twice } }),
|
|
@@ -263,6 +275,51 @@ describe("the grammar rejects what the compiler cannot build (PROTECTED)", () =>
|
|
|
263
275
|
).toThrow(/filter param "kind" is declared twice/);
|
|
264
276
|
});
|
|
265
277
|
|
|
278
|
+
it("admits a camelCase filter param, and it reaches the flag the projector spells", () => {
|
|
279
|
+
// The grammar and the projector have to agree about what a param name may
|
|
280
|
+
// be. `emitVerb` writes a flag as `--${kebabCase(param)}`, so a two-word
|
|
281
|
+
// dimension only has a readable spelling if the param may carry the hump —
|
|
282
|
+
// and the gate used to refuse the one spelling that projects well while
|
|
283
|
+
// admitting `channelof`, which projects to `--channelof`.
|
|
284
|
+
const definition = parsePackDefinition(
|
|
285
|
+
{
|
|
286
|
+
noun: "widget",
|
|
287
|
+
list: {
|
|
288
|
+
query:
|
|
289
|
+
"SELECT ?uri ?channelOf WHERE { ?uri ex:channelOf ?channelOf }",
|
|
290
|
+
columns: [{ field: "uri" }, { field: "channelOf" }],
|
|
291
|
+
filters: [
|
|
292
|
+
{ param: "channelOf", variable: "channelOf", values: ["a"] },
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
},
|
|
296
|
+
"pkg/stories/widget.json",
|
|
297
|
+
);
|
|
298
|
+
const list = compilePack(
|
|
299
|
+
definition,
|
|
300
|
+
distributionSource("pkg/stories/widget.json"),
|
|
301
|
+
PREFIXES,
|
|
302
|
+
).find((verb) => verbKey(verb.path) === "widget list") as VerbSpec;
|
|
303
|
+
// The MCP key keeps the declared spelling; the CLI flag is its kebab form.
|
|
304
|
+
expect(list.params.map((param) => param.name)).toContain("channelOf");
|
|
305
|
+
expect(`--${kebabCase("channelOf")}`).toBe("--channel-of");
|
|
306
|
+
});
|
|
307
|
+
|
|
308
|
+
it("still refuses a filter param that does not start lowercase", () => {
|
|
309
|
+
// A leading capital would kebab to a LEADING DASH, and Commander would
|
|
310
|
+
// register a flag with an empty name — outside every error envelope the
|
|
311
|
+
// CLI owns. The widening is the hump, not the initial.
|
|
312
|
+
expect(
|
|
313
|
+
parse({
|
|
314
|
+
noun: "widget",
|
|
315
|
+
list: {
|
|
316
|
+
...listShape,
|
|
317
|
+
filters: [{ param: "ChannelOf", variable: "uri", values: ["a"] }],
|
|
318
|
+
},
|
|
319
|
+
}),
|
|
320
|
+
).toThrow();
|
|
321
|
+
});
|
|
322
|
+
|
|
266
323
|
it("still accepts the distinct forms, and they compile to unique keys/params", () => {
|
|
267
324
|
const definition = parsePackDefinition(
|
|
268
325
|
{
|
|
@@ -270,8 +327,8 @@ describe("the grammar rejects what the compiler cannot build (PROTECTED)", () =>
|
|
|
270
327
|
list: {
|
|
271
328
|
...listShape,
|
|
272
329
|
filters: [
|
|
273
|
-
{ param: "kind", variable: "uri" },
|
|
274
|
-
{ param: "tier", variable: "uri" },
|
|
330
|
+
{ param: "kind", variable: "uri", values: ["a"] },
|
|
331
|
+
{ param: "tier", variable: "uri", values: ["b"] },
|
|
275
332
|
],
|
|
276
333
|
},
|
|
277
334
|
verbs: [{ ...listShape, verb: "categories" }],
|
|
@@ -291,7 +348,12 @@ describe("the grammar rejects what the compiler cannot build (PROTECTED)", () =>
|
|
|
291
348
|
"widget sample",
|
|
292
349
|
]);
|
|
293
350
|
expect(() => assertUniqueVerbs(verbs)).not.toThrow();
|
|
294
|
-
expect(verbs.at(0)?.params.map((p) => p.name)).toEqual([
|
|
351
|
+
expect(verbs.at(0)?.params.map((p) => p.name)).toEqual([
|
|
352
|
+
"kind",
|
|
353
|
+
"tier",
|
|
354
|
+
"limit",
|
|
355
|
+
"after",
|
|
356
|
+
]);
|
|
295
357
|
});
|
|
296
358
|
|
|
297
359
|
it("names the shape a noun must have, not the regex", () => {
|
|
@@ -331,6 +393,24 @@ describe("pack compiler — SPARQL fetch path (PROTECTED)", () => {
|
|
|
331
393
|
(await rt.store.get()).store.dispose();
|
|
332
394
|
});
|
|
333
395
|
|
|
396
|
+
/**
|
|
397
|
+
* The INVALID_INPUT recovery a call raises, which is where a pack error puts
|
|
398
|
+
* the sentence a caller acts on — the factory's own message is the uniform
|
|
399
|
+
* `Invalid <field> "<value>".`
|
|
400
|
+
*/
|
|
401
|
+
const recoveryOf = async (call: () => Promise<unknown>): Promise<string> => {
|
|
402
|
+
try {
|
|
403
|
+
await call();
|
|
404
|
+
} catch (error) {
|
|
405
|
+
if (error instanceof PragmaError) {
|
|
406
|
+
expect(error.code).toBe("INVALID_INPUT");
|
|
407
|
+
return error.recovery?.message ?? "";
|
|
408
|
+
}
|
|
409
|
+
throw error;
|
|
410
|
+
}
|
|
411
|
+
throw new Error("expected the call to be refused");
|
|
412
|
+
};
|
|
413
|
+
|
|
334
414
|
const run = <R>(verbLabel: string, params: Record<string, unknown>) => {
|
|
335
415
|
const verb = compilePack(
|
|
336
416
|
WIDGET_PACK,
|
|
@@ -342,19 +422,112 @@ describe("pack compiler — SPARQL fetch path (PROTECTED)", () => {
|
|
|
342
422
|
};
|
|
343
423
|
|
|
344
424
|
it("lists rows in the uniform pack shape", async () => {
|
|
345
|
-
const
|
|
346
|
-
expect(rows.map((r) => r.name)).toEqual(["Button", "Label"]);
|
|
347
|
-
expect(rows[0]?.uri).toBe("https://example.org/widgets#button");
|
|
425
|
+
const page = await run<PackPage>("list", {});
|
|
426
|
+
expect(page.rows.map((r) => r.name)).toEqual(["Button", "Label"]);
|
|
427
|
+
expect(page.rows[0]?.uri).toBe("https://example.org/widgets#button");
|
|
428
|
+
// The whole population fits the default page, so nothing is withheld and
|
|
429
|
+
// there is no cursor to spend.
|
|
430
|
+
expect(page.nextAfter).toBeUndefined();
|
|
431
|
+
expect(page.limit).toBe(DEFAULT_LIST_LIMIT);
|
|
348
432
|
});
|
|
349
433
|
|
|
350
|
-
it("filters by an enum value
|
|
351
|
-
const
|
|
352
|
-
expect(rows.map((r) => r.name)).toEqual(["Button"]);
|
|
434
|
+
it("filters by an enum value, compiled into the query", async () => {
|
|
435
|
+
const page = await run<PackPage>("list", { kind: "input" });
|
|
436
|
+
expect(page.rows.map((r) => r.name)).toEqual(["Button"]);
|
|
353
437
|
});
|
|
354
438
|
|
|
355
439
|
it("searches case-insensitively", async () => {
|
|
356
|
-
const
|
|
357
|
-
expect(rows.map((r) => r.name)).toEqual(["Label"]);
|
|
440
|
+
const page = await run<PackPage>("list", { search: "lab" });
|
|
441
|
+
expect(page.rows.map((r) => r.name)).toEqual(["Label"]);
|
|
442
|
+
});
|
|
443
|
+
|
|
444
|
+
it("pages inside the query: a page, its successor, and their union", async () => {
|
|
445
|
+
const whole = await run<PackPage>("list", {});
|
|
446
|
+
const first = await run<PackPage>("list", { limit: 1 });
|
|
447
|
+
expect(first.rows).toEqual([whole.rows[0]]);
|
|
448
|
+
expect(first.nextAfter).toBeTypeOf("string");
|
|
449
|
+
const second = await run<PackPage>("list", {
|
|
450
|
+
limit: 1,
|
|
451
|
+
after: first.nextAfter,
|
|
452
|
+
});
|
|
453
|
+
expect(second.rows).toEqual([whole.rows[1]]);
|
|
454
|
+
// The last page reports no successor, and one past it is a calm empty page
|
|
455
|
+
// rather than an error.
|
|
456
|
+
expect(second.nextAfter).toBeUndefined();
|
|
457
|
+
const past = await run<PackPage>("list", {
|
|
458
|
+
limit: 1,
|
|
459
|
+
after: encodeCursor(
|
|
460
|
+
9,
|
|
461
|
+
pageFingerprint([WIDGET_PACK.list?.query ?? "", "[]", "null"]),
|
|
462
|
+
),
|
|
463
|
+
});
|
|
464
|
+
expect(past.rows).toEqual([]);
|
|
465
|
+
});
|
|
466
|
+
|
|
467
|
+
it("refuses a cursor issued for a different read", async () => {
|
|
468
|
+
const filtered = await run<PackPage>("list", { limit: 1, kind: "input" });
|
|
469
|
+
const unfiltered = await run<PackPage>("list", { limit: 1 });
|
|
470
|
+
// Spending the unfiltered read's cursor on the filtered one would answer a
|
|
471
|
+
// page of the wrong question, with nothing in the payload to show it.
|
|
472
|
+
await expect(
|
|
473
|
+
recoveryOf(() =>
|
|
474
|
+
run<PackPage>("list", {
|
|
475
|
+
limit: 1,
|
|
476
|
+
kind: "input",
|
|
477
|
+
after: unfiltered.nextAfter,
|
|
478
|
+
}),
|
|
479
|
+
),
|
|
480
|
+
).resolves.toMatch(/issued for a different read/);
|
|
481
|
+
expect(filtered.nextAfter).toBeUndefined();
|
|
482
|
+
});
|
|
483
|
+
|
|
484
|
+
it("refuses a limit that is not a whole number of rows", async () => {
|
|
485
|
+
await expect(
|
|
486
|
+
recoveryOf(() => run<PackPage>("list", { limit: 0 })),
|
|
487
|
+
).resolves.toMatch(new RegExp(`1 to ${MAX_LIST_WINDOW}`));
|
|
488
|
+
});
|
|
489
|
+
|
|
490
|
+
it("refuses a limit above the ceiling, naming it", async () => {
|
|
491
|
+
// `--limit 9007199254740991` is the natural "give me everything" an agent
|
|
492
|
+
// writes, and it used to reach the store's own `LIMIT` — which must fit a
|
|
493
|
+
// 32-bit integer — and come back as a parse error dressed as
|
|
494
|
+
// INTERNAL_ERROR with "report this issue". A typed refusal that says what
|
|
495
|
+
// it would accept is the answer to a legitimate question asked too big.
|
|
496
|
+
for (const limit of [MAX_LIST_WINDOW + 1, 2 ** 32 - 1, 1e21, 2 ** 53 - 1]) {
|
|
497
|
+
await expect(
|
|
498
|
+
recoveryOf(() => run<PackPage>("list", { limit })),
|
|
499
|
+
).resolves.toMatch(new RegExp(`1 to ${MAX_LIST_WINDOW}`));
|
|
500
|
+
}
|
|
501
|
+
// The ceiling itself is admitted: a refusal one row early would be a
|
|
502
|
+
// different bug.
|
|
503
|
+
await expect(
|
|
504
|
+
run<PackPage>("list", { limit: MAX_LIST_WINDOW }),
|
|
505
|
+
).resolves.toBeDefined();
|
|
506
|
+
});
|
|
507
|
+
|
|
508
|
+
it("refuses a cursor whose offset is above the ceiling", async () => {
|
|
509
|
+
// The fingerprint covers the query and the arguments, not the offset — so
|
|
510
|
+
// a real cursor with only `o` edited is fingerprint-valid, and that is the
|
|
511
|
+
// path by which an unbounded offset reached the query's own `OFFSET`.
|
|
512
|
+
const fingerprint = pageFingerprint([
|
|
513
|
+
WIDGET_PACK.list?.query ?? "",
|
|
514
|
+
"[]",
|
|
515
|
+
"null",
|
|
516
|
+
]);
|
|
517
|
+
await expect(
|
|
518
|
+
recoveryOf(() =>
|
|
519
|
+
run<PackPage>("list", {
|
|
520
|
+
limit: 1,
|
|
521
|
+
after: encodeCursor(2 ** 53 - 1, fingerprint),
|
|
522
|
+
}),
|
|
523
|
+
),
|
|
524
|
+
).resolves.toMatch(/cursor from a previous page/);
|
|
525
|
+
// An offset the kernel can serve is still served, empty page and all.
|
|
526
|
+
const edge = await run<PackPage>("list", {
|
|
527
|
+
limit: 1,
|
|
528
|
+
after: encodeCursor(MAX_LIST_WINDOW, fingerprint),
|
|
529
|
+
});
|
|
530
|
+
expect(edge.rows).toEqual([]);
|
|
358
531
|
});
|
|
359
532
|
|
|
360
533
|
it("looks up an entity by name with its fields and expands (detailed)", async () => {
|
|
@@ -15,6 +15,7 @@
|
|
|
15
15
|
*/
|
|
16
16
|
|
|
17
17
|
import { BIN_NAME } from "../../constants.js";
|
|
18
|
+
import { PragmaError } from "../error/index.js";
|
|
18
19
|
import { compactUri, DEFAULT_PREFIX_MAP } from "../render/index.js";
|
|
19
20
|
import type { PragmaRuntime } from "../runtime/index.js";
|
|
20
21
|
import { asVerb } from "../spec/asVerb.js";
|
|
@@ -25,6 +26,7 @@ import type {
|
|
|
25
26
|
ParamSpec,
|
|
26
27
|
VerbSpec,
|
|
27
28
|
} from "../spec/types.js";
|
|
29
|
+
import { DEFAULT_LIST_LIMIT, MAX_LIST_WINDOW } from "./paging.js";
|
|
28
30
|
import {
|
|
29
31
|
listFormatters,
|
|
30
32
|
lookupFormatters,
|
|
@@ -37,14 +39,18 @@ import {
|
|
|
37
39
|
MIN_SAMPLE_COUNT,
|
|
38
40
|
sampleDefaultCount,
|
|
39
41
|
} from "./sample.js";
|
|
40
|
-
import
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
42
|
+
import { storyIssues } from "./storyRules.js";
|
|
43
|
+
import {
|
|
44
|
+
EVERY_TIER,
|
|
45
|
+
type PackDefinition,
|
|
46
|
+
type PackFilter,
|
|
47
|
+
type PackList,
|
|
48
|
+
type PackLookup,
|
|
49
|
+
type PackPage,
|
|
50
|
+
type PackSearch,
|
|
51
|
+
type PackTierScope,
|
|
52
|
+
type StorySource,
|
|
53
|
+
TIER_PARAM,
|
|
48
54
|
} from "./types.js";
|
|
49
55
|
|
|
50
56
|
// The run bodies pull the SPARQL/GraphQL fetch layer; they are dynamic-imported
|
|
@@ -78,6 +84,8 @@ export function compilePack(
|
|
|
78
84
|
const { noun } = definition;
|
|
79
85
|
const verbs: VerbSpec[] = [];
|
|
80
86
|
|
|
87
|
+
const tierScope = definition.tierScope;
|
|
88
|
+
|
|
81
89
|
if (definition.list) {
|
|
82
90
|
verbs.push(
|
|
83
91
|
compileListVerb(definition.list, {
|
|
@@ -87,6 +95,7 @@ export function compilePack(
|
|
|
87
95
|
doc: definition.toolDescription,
|
|
88
96
|
source,
|
|
89
97
|
prefixes,
|
|
98
|
+
...(tierScope ? { tierScope } : {}),
|
|
90
99
|
}),
|
|
91
100
|
);
|
|
92
101
|
}
|
|
@@ -100,12 +109,15 @@ export function compilePack(
|
|
|
100
109
|
doc: verb.toolDescription,
|
|
101
110
|
source,
|
|
102
111
|
prefixes,
|
|
112
|
+
...(tierScope ? { tierScope } : {}),
|
|
103
113
|
}),
|
|
104
114
|
);
|
|
105
115
|
}
|
|
106
116
|
|
|
107
117
|
if (definition.lookup) {
|
|
108
|
-
verbs.push(
|
|
118
|
+
verbs.push(
|
|
119
|
+
compileLookupVerb(definition.lookup, noun, source, prefixes, tierScope),
|
|
120
|
+
);
|
|
109
121
|
if (definition.lookup.sample) {
|
|
110
122
|
verbs.push(compileSampleVerb(definition.lookup, noun, source, prefixes));
|
|
111
123
|
}
|
|
@@ -171,16 +183,33 @@ export function compileListable(
|
|
|
171
183
|
* through here alike, so a module-level projection derived from the story
|
|
172
184
|
* (today: {@link compileListable}) cannot reach one tier and miss the other.
|
|
173
185
|
*
|
|
186
|
+
* That door is also where the COMPILABILITY rules are checked
|
|
187
|
+
* ({@link ./storyRules.storyIssues}), for the same reason. The zod grammar runs
|
|
188
|
+
* for config- and package-declared stories only, so a rule stated only there
|
|
189
|
+
* was a declaration-time refusal for a third-party author and a first-call
|
|
190
|
+
* CONFIG_ERROR for the distribution's own stories. The rules are pure string
|
|
191
|
+
* work over text the story already carries, which is what makes them affordable
|
|
192
|
+
* on this path; a story that passed zod has passed them already, so the throw
|
|
193
|
+
* below is reachable only for a story that never saw zod.
|
|
194
|
+
*
|
|
174
195
|
* @param definition - A validated pack definition.
|
|
175
196
|
* @param source - Where the definition came from, for diagnostics.
|
|
176
197
|
* @param prefixes - The merged prefix map used for display compaction.
|
|
177
198
|
* @returns The module: the compiled verbs plus the story's module-level data.
|
|
199
|
+
* @throws PragmaError CONFIG_ERROR when the definition cannot be compiled,
|
|
200
|
+
* naming the field and the rule.
|
|
178
201
|
*/
|
|
179
202
|
export function compileStoryModule(
|
|
180
203
|
definition: PackDefinition,
|
|
181
204
|
source: StorySource,
|
|
182
205
|
prefixes: Readonly<Record<string, string>>,
|
|
183
206
|
): CapabilityModule {
|
|
207
|
+
const issue = storyIssues(definition)[0];
|
|
208
|
+
if (issue) {
|
|
209
|
+
throw PragmaError.configError(
|
|
210
|
+
`Invalid story in ${source.label} at ${issue.path.join(".")}: ${issue.message}`,
|
|
211
|
+
);
|
|
212
|
+
}
|
|
184
213
|
const listable = compileListable(definition);
|
|
185
214
|
return {
|
|
186
215
|
name: definition.noun,
|
|
@@ -200,6 +229,8 @@ interface ListVerbMeta {
|
|
|
200
229
|
readonly doc?: string;
|
|
201
230
|
readonly source: StorySource;
|
|
202
231
|
readonly prefixes: Readonly<Record<string, string>>;
|
|
232
|
+
/** The noun's declared tier hierarchy, when its entities are tiered. */
|
|
233
|
+
readonly tierScope?: PackTierScope;
|
|
203
234
|
}
|
|
204
235
|
|
|
205
236
|
/** Compile the `list` verb or an extra list-shaped verb. */
|
|
@@ -207,9 +238,11 @@ function compileListVerb(shape: PackList, meta: ListVerbMeta): VerbSpec {
|
|
|
207
238
|
const params = [
|
|
208
239
|
...projectFilters(shape.filters),
|
|
209
240
|
...projectSearch(shape.search),
|
|
241
|
+
...tierParams(meta.tierScope),
|
|
242
|
+
...PAGE_PARAMS,
|
|
210
243
|
];
|
|
211
244
|
const filterExample = shape.filters?.find((f) => f.values !== undefined);
|
|
212
|
-
const verb: VerbSpec<Record<string, unknown>,
|
|
245
|
+
const verb: VerbSpec<Record<string, unknown>, PackPage> = {
|
|
213
246
|
path: [meta.noun, meta.verb],
|
|
214
247
|
summary: meta.summary,
|
|
215
248
|
...(meta.doc ? { doc: meta.doc } : {}),
|
|
@@ -237,7 +270,10 @@ function compileListVerb(shape: PackList, meta: ListVerbMeta): VerbSpec {
|
|
|
237
270
|
capability: READ_CAPABILITY,
|
|
238
271
|
run: (params: Record<string, unknown>, rt: PragmaRuntime) =>
|
|
239
272
|
runBodies().then((m) =>
|
|
240
|
-
m.makeListRun(shape, {
|
|
273
|
+
m.makeListRun(shape, {
|
|
274
|
+
source: meta.source,
|
|
275
|
+
...(meta.tierScope ? { tierScope: meta.tierScope } : {}),
|
|
276
|
+
})(params, rt),
|
|
241
277
|
),
|
|
242
278
|
};
|
|
243
279
|
return asVerb(verb);
|
|
@@ -249,6 +285,7 @@ function compileLookupVerb(
|
|
|
249
285
|
noun: string,
|
|
250
286
|
source: StorySource,
|
|
251
287
|
prefixes: Readonly<Record<string, string>>,
|
|
288
|
+
tierScope?: PackTierScope,
|
|
252
289
|
): VerbSpec {
|
|
253
290
|
// Derive-by-default: every lookup completes its `<name>` from the pack index
|
|
254
291
|
// (empty type = any, the reader handles it), UNLESS the pack opts out
|
|
@@ -282,21 +319,55 @@ function compileLookupVerb(
|
|
|
282
319
|
summary:
|
|
283
320
|
lookup.description ?? `Look up ${noun} details by name, IRI, or glob.`,
|
|
284
321
|
...(lookup.toolDescription ? { doc: lookup.toolDescription } : {}),
|
|
285
|
-
params: [nameParam],
|
|
322
|
+
params: [nameParam, ...tierParams(tierScope)],
|
|
286
323
|
output: { formatters: lookupFormatters(lookup, prefixes) },
|
|
287
|
-
examples: [
|
|
324
|
+
examples: [
|
|
325
|
+
{ cmd: `${BIN_NAME} ${noun} lookup <name>` },
|
|
326
|
+
...(tierScope
|
|
327
|
+
? [
|
|
328
|
+
{
|
|
329
|
+
cmd: `${BIN_NAME} ${noun} lookup --tier all <name>`,
|
|
330
|
+
note: "every tier, not just the ones in scope",
|
|
331
|
+
},
|
|
332
|
+
]
|
|
333
|
+
: []),
|
|
334
|
+
],
|
|
288
335
|
...(lookup.disclosure
|
|
289
336
|
? { disclosure: disclosureSpec(lookup.disclosure) }
|
|
290
337
|
: {}),
|
|
291
338
|
capability: READ_CAPABILITY,
|
|
292
339
|
run: (params: Record<string, unknown>, rt: PragmaRuntime) =>
|
|
293
340
|
runBodies().then((m) =>
|
|
294
|
-
m.makeLookupRun(lookup, noun, source, prefixes)(params, rt),
|
|
341
|
+
m.makeLookupRun(lookup, noun, source, prefixes, tierScope)(params, rt),
|
|
295
342
|
),
|
|
296
343
|
};
|
|
297
344
|
return asVerb(verb);
|
|
298
345
|
}
|
|
299
346
|
|
|
347
|
+
/**
|
|
348
|
+
* The `--tier` parameter a TIERED noun's reads carry (`tier` over MCP).
|
|
349
|
+
*
|
|
350
|
+
* Kernel-added, like the page: being tiered is declared, but the argument that
|
|
351
|
+
* steers the scope is the kernel's to name — one spelling across every tiered
|
|
352
|
+
* noun, so an agent that learned `--tier` on `block list` can use it on
|
|
353
|
+
* `concept list` without asking. The help text names the default and the
|
|
354
|
+
* escape, because a scope a caller cannot see is exactly the hidden behaviour
|
|
355
|
+
* CONSTITUTION §VI rules out.
|
|
356
|
+
*
|
|
357
|
+
* Not `repeatable`: a repeated `--tier` would be two chains, and the scope is
|
|
358
|
+
* one chain. The run body refuses an array rather than quietly taking the last.
|
|
359
|
+
*/
|
|
360
|
+
function tierParams(tierScope: PackTierScope | undefined): ParamSpec[] {
|
|
361
|
+
if (!tierScope) return [];
|
|
362
|
+
return [
|
|
363
|
+
{
|
|
364
|
+
kind: "string",
|
|
365
|
+
name: TIER_PARAM,
|
|
366
|
+
doc: `Read this tier and its ancestors (default: the top-level tiers; "${EVERY_TIER}" for every tier).`,
|
|
367
|
+
},
|
|
368
|
+
];
|
|
369
|
+
}
|
|
370
|
+
|
|
300
371
|
/** Compile the `sample` verb (N random exemplars at the highest level). */
|
|
301
372
|
function compileSampleVerb(
|
|
302
373
|
lookup: PackLookup,
|
|
@@ -355,6 +426,30 @@ function disclosureSpec(disclosure: PackLookup["disclosure"]): DisclosureSpec {
|
|
|
355
426
|
};
|
|
356
427
|
}
|
|
357
428
|
|
|
429
|
+
/**
|
|
430
|
+
* The page parameters EVERY list-shaped verb carries — `list` and every extra
|
|
431
|
+
* verb alike.
|
|
432
|
+
*
|
|
433
|
+
* Not declared per story, and not declarable: a page is a property of a
|
|
434
|
+
* list-shaped read, so a story that forgot to declare one would be the one
|
|
435
|
+
* answer an agent could not bound. The default is named in the help text
|
|
436
|
+
* because a cap a caller cannot see is exactly the hidden behaviour
|
|
437
|
+
* CONSTITUTION §VI rules out — the number a caller gets when they pass nothing
|
|
438
|
+
* has to be readable in `--help`.
|
|
439
|
+
*/
|
|
440
|
+
const PAGE_PARAMS: readonly ParamSpec[] = [
|
|
441
|
+
{
|
|
442
|
+
kind: "number",
|
|
443
|
+
name: "limit",
|
|
444
|
+
doc: `Maximum rows to return, 1 to ${MAX_LIST_WINDOW} (default ${DEFAULT_LIST_LIMIT}).`,
|
|
445
|
+
},
|
|
446
|
+
{
|
|
447
|
+
kind: "string",
|
|
448
|
+
name: "after",
|
|
449
|
+
doc: "Continue from a previous page: the cursor that page reported.",
|
|
450
|
+
},
|
|
451
|
+
];
|
|
452
|
+
|
|
358
453
|
/** Project declared filters onto verb params (enum for a value set, else string). */
|
|
359
454
|
function projectFilters(
|
|
360
455
|
filters: readonly PackFilter[] | undefined,
|