@canonical/pragma-cli 0.30.0 → 0.31.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 +64 -35
- package/dist/pragma +0 -0
- package/package.json +15 -13
- package/src/completions/constants.ts +7 -0
- package/src/completions/handleQuery.test.ts +32 -0
- package/src/completions/handleQuery.ts +20 -1
- package/src/completions/queryCompletions.test.ts +5 -0
- package/src/completions/queryCompletions.ts +17 -3
- package/src/completions/querySocket.test.ts +31 -0
- package/src/completions/querySocket.ts +36 -9
- package/src/config/parseConfigValues.ts +2 -0
- package/src/config/readConfigLayers.test.ts +2 -0
- package/src/config/readConfigLayers.ts +3 -0
- package/src/config/types.ts +7 -0
- package/src/constants.ts +3 -1
- package/src/domains/block/commands/sample.ts +2 -1
- package/src/domains/block/formatters/list.test.ts +3 -3
- package/src/domains/block/formatters/lookup.test.ts +5 -5
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
- package/src/domains/block/stories.ts +9 -3
- package/src/domains/config/commands/channel.test.ts +16 -0
- package/src/domains/config/commands/channel.ts +14 -1
- package/src/domains/config/commands/framework.test.ts +14 -0
- package/src/domains/config/commands/framework.ts +13 -1
- package/src/domains/config/commands/tier.test.ts +7 -0
- package/src/domains/config/commands/tier.ts +14 -1
- package/src/domains/config/operations/validateTier.test.ts +9 -0
- package/src/domains/config/operations/validateTier.ts +10 -2
- package/src/domains/create/generatorPack/bundled.ts +49 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
- package/src/domains/create/index.ts +13 -7
- package/src/domains/create/mcp/specs.ts +6 -161
- package/src/domains/create/renderGeneratorUi.test.ts +103 -0
- package/src/domains/create/renderGeneratorUi.ts +65 -0
- package/src/domains/doctor/formatters/doctor.test.ts +50 -2
- package/src/domains/doctor/formatters/doctor.ts +90 -38
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +9 -6
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -12
- package/src/domains/doctor/operations/checks.test.ts +6 -4
- package/src/domains/doctor/operations/types.ts +23 -1
- package/src/domains/graph/commands/inspect.test.ts +2 -2
- package/src/domains/graph/commands/query.test.ts +5 -3
- package/src/domains/graph/formatters/inspect.ts +7 -7
- package/src/domains/graph/formatters/query.test.ts +87 -0
- package/src/domains/graph/formatters/query.ts +42 -5
- package/src/domains/graph/operations/inspectUri.test.ts +5 -5
- package/src/domains/graphql/commands/serve.test.ts +12 -6
- package/src/domains/graphql/commands/serve.ts +16 -6
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
- package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
- package/src/domains/graphql/operations/compileSchema.test.ts +2 -2
- package/src/domains/graphql/operations/createSchemaServer.test.ts +2 -2
- package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
- package/src/domains/graphql/operations/index.ts +5 -0
- package/src/domains/info/formatters/upgrade.test.ts +7 -4
- package/src/domains/info/formatters/upgrade.ts +3 -1
- package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
- package/src/domains/info/operations/collectEntityCounts.ts +123 -0
- package/src/domains/info/operations/index.ts +1 -0
- package/src/domains/info/types.ts +13 -0
- package/src/domains/llm/capabilities.test.ts +23 -3
- package/src/domains/llm/commands/capabilities.ts +12 -3
- package/src/domains/llm/commands/llm.ts +5 -1
- package/src/domains/llm/data/commandReference.ts +20 -11
- package/src/domains/llm/data/decisionTrees.ts +10 -5
- package/src/domains/llm/data/toolCatalog.ts +49 -18
- package/src/domains/llm/formatters/orientation.ts +5 -1
- package/src/domains/llm/llmCommand.test.ts +11 -3
- package/src/domains/llm/mcp/specs.ts +3 -4
- package/src/domains/llm/operations/collectContext.test.ts +64 -0
- package/src/domains/llm/operations/collectContext.ts +7 -17
- package/src/domains/modifier/commands/sample.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +5 -3
- package/src/domains/modifier/stories.ts +9 -0
- package/src/domains/ontology/commands/list.test.ts +5 -3
- package/src/domains/ontology/commands/show.test.ts +85 -9
- package/src/domains/ontology/formatters/index.ts +1 -0
- package/src/domains/ontology/formatters/list.ts +37 -12
- package/src/domains/ontology/formatters/show.ts +358 -67
- package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
- package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
- package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
- package/src/domains/ontology/helpers/index.ts +14 -0
- package/src/domains/ontology/helpers/queryClasses.ts +32 -16
- package/src/domains/ontology/helpers/queryProperties.ts +39 -6
- package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
- package/src/domains/ontology/operations/listOntologies.ts +126 -30
- package/src/domains/ontology/operations/showOntology.test.ts +131 -12
- package/src/domains/ontology/operations/showOntology.ts +289 -7
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
- package/src/domains/ontology/stories.ts +53 -8
- package/src/domains/refs/operations/updateRefs.ts +0 -1
- package/src/domains/setup/commands/index.ts +1 -0
- package/src/domains/setup/commands/setup.ts +70 -0
- package/src/domains/setup/index.ts +7 -3
- package/src/domains/setup/operations/setupLsp.test.ts +3 -2
- package/src/domains/setup/operations/setupLsp.ts +7 -2
- package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
- package/src/domains/shared/bootStore.test.ts +82 -0
- package/src/domains/shared/bootStore.ts +56 -20
- package/src/domains/shared/emptyErrorRecovery.test.ts +23 -26
- package/src/domains/shared/extractLocalName.test.ts +3 -3
- package/src/domains/shared/formatters.ts +23 -2
- package/src/domains/shared/listTiers.ts +40 -0
- package/src/domains/shared/loaders/bundledLoader.ts +48 -0
- package/src/domains/shared/loaders/gitLoader.ts +1 -2
- package/src/domains/shared/loaders/localLoader.ts +2 -18
- package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
- package/src/domains/shared/loaders/readPackageDir.ts +46 -8
- package/src/domains/shared/lookupMany.test.ts +15 -0
- package/src/domains/shared/lookupMany.ts +22 -6
- package/src/domains/shared/namespaces.ts +13 -5
- package/src/domains/shared/parseSampleCount.test.ts +47 -0
- package/src/domains/shared/parseSampleCount.ts +45 -0
- package/src/domains/shared/prefixes.test.ts +211 -0
- package/src/domains/shared/prefixes.ts +182 -15
- package/src/domains/shared/renderers.ts +5 -5
- package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
- package/src/domains/shared/resolveLookupExitCode.ts +42 -0
- package/src/domains/shared/semanticPackage.test.ts +89 -0
- package/src/domains/shared/semanticPackage.ts +17 -2
- package/src/domains/shared/stories/compileLookupCommand.ts +3 -1
- package/src/domains/shared/stories/compileLookupTool.ts +1 -1
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +49 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +46 -11
- package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
- package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +30 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +75 -1
- package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
- package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
- package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +305 -7
- package/src/domains/shared/stories/pack/collectPackStories.ts +90 -8
- package/src/domains/shared/stories/pack/compilePackCommands.ts +17 -7
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +607 -2
- package/src/domains/shared/stories/pack/compilePackStories.ts +544 -73
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +20 -8
- package/src/domains/shared/stories/pack/index.ts +14 -0
- package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
- package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +293 -220
- package/src/domains/shared/stories/pack/types.ts +123 -3
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +502 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +475 -24
- package/src/domains/shared/stories/types.ts +9 -1
- package/src/domains/shared/types/index.ts +5 -9
- package/src/domains/shared/types/ontology.ts +119 -24
- package/src/domains/shared/types/standard.ts +9 -52
- package/src/domains/token/commands/lookup.test.ts +8 -0
- package/src/domains/token/commands/lookup.ts +12 -0
- package/src/domains/token/commands/sample.ts +2 -1
- package/src/domains/token/featureFlag.ts +17 -0
- package/src/domains/token/index.ts +5 -0
- package/src/domains/token/mcp/specs.ts +13 -1
- package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
- package/src/domains/trace/commands/show.ts +30 -4
- package/src/index.ts +1 -13
- package/src/mcp/registerResources.test.ts +2 -2
- package/src/mcp/resources/pickPrimaryType.test.ts +5 -5
- package/src/mcp/tools/index.ts +18 -10
- package/src/mcp/tools/registerTools.test.ts +106 -30
- package/src/pipeline/collectCommands.test.ts +151 -1
- package/src/pipeline/collectCommands.ts +34 -19
- package/src/pipeline/createProgram.test.ts +5 -1
- package/src/pipeline/createProgram.ts +2 -2
- package/src/pipeline/firstRun.test.ts +80 -0
- package/src/pipeline/firstRun.ts +99 -0
- package/src/pipeline/parseGlobalFlags.test.ts +102 -1
- package/src/pipeline/parseGlobalFlags.ts +98 -9
- package/src/pipeline/resolveCommandKind.test.ts +12 -0
- package/src/pipeline/resolveCommandKind.ts +4 -0
- package/src/pipeline/rootHelp.test.ts +87 -0
- package/src/pipeline/rootHelp.ts +265 -0
- package/src/pipeline/runCli.orchestration.test.ts +28 -12
- package/src/pipeline/runCli.test.ts +17 -4
- package/src/pipeline/runCli.ts +162 -19
- package/src/testing/cli.ts +30 -2
- package/src/testing/dsFixtures.ts +21 -4
- package/src/testing/index.ts +1 -4
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +4 -4
- package/src/testing/integration/cli-user-stories.test.ts +63 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +2 -2
- package/src/testing/integration/edge-cases.test.ts +62 -50
- package/src/testing/integration/executor-execution.test.ts +6 -4
- package/src/testing/integration/executor-golden.test.ts +2 -2
- package/src/testing/integration/mcp-surface.test.ts +40 -15
- package/src/testing/integration/parity.test.ts +119 -70
- package/src/testing/setupXdgIsolation.ts +16 -5
- package/src/testing/standardParityFixtures.ts +26 -83
- package/src/testing/store.ts +4 -4
- package/src/domains/create/commands/component.test.ts +0 -166
- package/src/domains/create/commands/component.ts +0 -123
- package/src/domains/create/commands/index.ts +0 -3
- package/src/domains/create/commands/package.test.ts +0 -35
- package/src/domains/create/commands/package.ts +0 -21
- package/src/domains/create/generators.ts +0 -8
- package/src/domains/standard/commands/categories.test.ts +0 -70
- package/src/domains/standard/commands/categories.ts +0 -48
- package/src/domains/standard/commands/index.ts +0 -6
- package/src/domains/standard/commands/list.test.ts +0 -105
- package/src/domains/standard/commands/list.ts +0 -11
- package/src/domains/standard/commands/lookup.test.ts +0 -135
- package/src/domains/standard/commands/lookup.ts +0 -14
- package/src/domains/standard/commands/sample.ts +0 -57
- package/src/domains/standard/formatters/categories.test.ts +0 -44
- package/src/domains/standard/formatters/categories.ts +0 -38
- package/src/domains/standard/formatters/index.ts +0 -10
- package/src/domains/standard/formatters/list.test.ts +0 -169
- package/src/domains/standard/formatters/list.ts +0 -184
- package/src/domains/standard/formatters/lookup.test.ts +0 -95
- package/src/domains/standard/formatters/lookup.ts +0 -101
- package/src/domains/standard/formatters/sample.ts +0 -15
- package/src/domains/standard/formatters/types.ts +0 -31
- package/src/domains/standard/index.ts +0 -40
- package/src/domains/standard/mcp/index.ts +0 -2
- package/src/domains/standard/mcp/specs.ts +0 -89
- package/src/domains/standard/operations/categories.test.ts +0 -61
- package/src/domains/standard/operations/categories.ts +0 -38
- package/src/domains/standard/operations/index.ts +0 -6
- package/src/domains/standard/operations/list.test.ts +0 -75
- package/src/domains/standard/operations/list.ts +0 -64
- package/src/domains/standard/operations/lookup.test.ts +0 -58
- package/src/domains/standard/operations/lookup.ts +0 -151
- package/src/domains/standard/operations/sample.ts +0 -35
- package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
- package/src/domains/standard/orchestration/index.ts +0 -4
- package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
- package/src/domains/standard/standardConfig.ts +0 -42
- package/src/domains/standard/stories.test.ts +0 -24
- package/src/domains/standard/stories.ts +0 -151
- package/src/domains/standard/types.ts +0 -15
- package/src/domains/tier/commands/index.ts +0 -3
- package/src/domains/tier/commands/list.test.ts +0 -67
- package/src/domains/tier/commands/list.ts +0 -9
- package/src/domains/tier/formatters/index.ts +0 -3
- package/src/domains/tier/formatters/list.test.ts +0 -33
- package/src/domains/tier/formatters/list.ts +0 -45
- package/src/domains/tier/index.ts +0 -17
- package/src/domains/tier/mcp/index.ts +0 -2
- package/src/domains/tier/mcp/specs.ts +0 -12
- package/src/domains/tier/operations/index.ts +0 -3
- package/src/domains/tier/operations/list.test.ts +0 -32
- package/src/domains/tier/operations/list.ts +0 -33
- package/src/domains/tier/stories.test.ts +0 -22
- package/src/domains/tier/stories.ts +0 -52
|
@@ -7,11 +7,12 @@ import {
|
|
|
7
7
|
RECIPE_STORY,
|
|
8
8
|
RECIPE_TTL,
|
|
9
9
|
} from "#testing";
|
|
10
|
-
import {
|
|
10
|
+
import { DEFAULT_PREFIX_MAP } from "../../prefixes.js";
|
|
11
11
|
import type { PragmaRuntime } from "../../types/index.js";
|
|
12
12
|
import compilePackStories from "./compilePackStories.js";
|
|
13
|
+
import type { StoryPackDefinition } from "./types.js";
|
|
13
14
|
|
|
14
|
-
const PREFIXES = { ...
|
|
15
|
+
const PREFIXES = { ...DEFAULT_PREFIX_MAP, ...RECIPE_PREFIXES };
|
|
15
16
|
|
|
16
17
|
let store: Store;
|
|
17
18
|
let cleanup: () => void;
|
|
@@ -88,6 +89,193 @@ describe("compilePackStories — list filters", () => {
|
|
|
88
89
|
});
|
|
89
90
|
});
|
|
90
91
|
|
|
92
|
+
// Pack v1: value-free filters (data-driven value sets) and free-text search.
|
|
93
|
+
describe("compilePackStories — value-free filters and search", () => {
|
|
94
|
+
/** RECIPE_STORY with a value-free category filter and a name search. */
|
|
95
|
+
const SEARCHABLE_STORY: StoryPackDefinition = {
|
|
96
|
+
...RECIPE_STORY,
|
|
97
|
+
list: {
|
|
98
|
+
...RECIPE_STORY.list,
|
|
99
|
+
filters: [{ param: "category", variable: "category" }],
|
|
100
|
+
search: { variables: ["name"] },
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
it("projects a value-free filter as a plain string parameter", () => {
|
|
105
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
106
|
+
const param = list.params.find((p) => p.name === "category");
|
|
107
|
+
expect(param?.type).toBe("string");
|
|
108
|
+
expect(param?.enum).toBeUndefined();
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("filters rows by case-insensitive equality without a declared set", async () => {
|
|
112
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
113
|
+
const rows = await list.resolve(rt, { category: "SOUP" });
|
|
114
|
+
expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("returns no rows (not an error) for an unknown filter value", async () => {
|
|
118
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
119
|
+
await expect(list.resolve(rt, { category: "dinner" })).resolves.toEqual([]);
|
|
120
|
+
});
|
|
121
|
+
|
|
122
|
+
it("projects a `search` string parameter", () => {
|
|
123
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
124
|
+
const param = list.params.find((p) => p.name === "search");
|
|
125
|
+
expect(param?.type).toBe("string");
|
|
126
|
+
expect(param?.description).toContain("name");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("keeps rows whose searched variables contain the term", async () => {
|
|
130
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
131
|
+
const rows = await list.resolve(rt, { search: "gaz" });
|
|
132
|
+
expect(rows.map((row) => row.name)).toEqual(["Gazpacho"]);
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
it("applies search after filters, conjunctively", async () => {
|
|
136
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
137
|
+
const rows = await list.resolve(rt, {
|
|
138
|
+
category: "breakfast",
|
|
139
|
+
search: "gaz",
|
|
140
|
+
});
|
|
141
|
+
expect(rows).toEqual([]);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
it("skips the generated filter example when no filter declares values", () => {
|
|
145
|
+
const { list } = compilePackStories(SEARCHABLE_STORY, "test", PREFIXES);
|
|
146
|
+
expect(
|
|
147
|
+
list.examples.some((example) => example.includes("--category")),
|
|
148
|
+
).toBe(false);
|
|
149
|
+
});
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Pack v1: extra list-shaped verbs compiled through the list machinery.
|
|
153
|
+
describe("compilePackStories — extra verbs", () => {
|
|
154
|
+
const STORY_WITH_VERB: StoryPackDefinition = {
|
|
155
|
+
...RECIPE_STORY,
|
|
156
|
+
verbs: [
|
|
157
|
+
{
|
|
158
|
+
verb: "categories",
|
|
159
|
+
description: "List recipe categories",
|
|
160
|
+
query:
|
|
161
|
+
"SELECT ?name (COUNT(?uri) AS ?count) WHERE { ?uri a ex:Recipe ; ex:category ?name } GROUP BY ?name ORDER BY ?name",
|
|
162
|
+
columns: [{ field: "name" }, { field: "count" }],
|
|
163
|
+
},
|
|
164
|
+
],
|
|
165
|
+
};
|
|
166
|
+
|
|
167
|
+
it("compiles each declared verb into a read story on the noun", async () => {
|
|
168
|
+
const { verbs } = compilePackStories(STORY_WITH_VERB, "test", PREFIXES);
|
|
169
|
+
expect(verbs).toHaveLength(1);
|
|
170
|
+
const categories = verbs.at(0);
|
|
171
|
+
expect(categories?.noun).toBe("recipe");
|
|
172
|
+
expect(categories?.verb).toBe("categories");
|
|
173
|
+
expect(categories?.description).toBe("List recipe categories");
|
|
174
|
+
const rows = await categories?.resolve(rt, {});
|
|
175
|
+
expect(rows).toEqual([
|
|
176
|
+
{ name: "breakfast", count: "1" },
|
|
177
|
+
{ name: "soup", count: "1" },
|
|
178
|
+
]);
|
|
179
|
+
expect(categories?.toEnvelope(rows ?? [])).toEqual({
|
|
180
|
+
data: rows,
|
|
181
|
+
meta: { count: 2 },
|
|
182
|
+
});
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
it("renders extra verbs through the generic list renderer", async () => {
|
|
186
|
+
const { verbs } = compilePackStories(STORY_WITH_VERB, "test", PREFIXES);
|
|
187
|
+
const categories = verbs.at(0);
|
|
188
|
+
const rows = (await categories?.resolve(rt, {})) ?? [];
|
|
189
|
+
const llm = categories?.formatters.llm(rows) ?? "";
|
|
190
|
+
expect(llm).toContain("## Recipe categories (2)");
|
|
191
|
+
expect(llm).toContain("breakfast");
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
it("compiles no extra verbs when none are declared", () => {
|
|
195
|
+
const { verbs } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
196
|
+
expect(verbs).toEqual([]);
|
|
197
|
+
});
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
// Pack v1: the generic sample verb.
|
|
201
|
+
describe("compilePackStories — sample", () => {
|
|
202
|
+
const STORY_WITH_SAMPLE: StoryPackDefinition = {
|
|
203
|
+
...RECIPE_STORY,
|
|
204
|
+
lookup: {
|
|
205
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
206
|
+
...RECIPE_STORY.lookup!,
|
|
207
|
+
sample: true,
|
|
208
|
+
},
|
|
209
|
+
};
|
|
210
|
+
|
|
211
|
+
it("draws N entities resolved through the lookup path", async () => {
|
|
212
|
+
const { sample } = compilePackStories(STORY_WITH_SAMPLE, "test", PREFIXES);
|
|
213
|
+
expect(sample?.noun).toBe("recipe");
|
|
214
|
+
expect(sample?.verb).toBe("sample");
|
|
215
|
+
const data = await sample?.resolve(rt, { count: "2" });
|
|
216
|
+
expect(data?.totalCount).toBe(2);
|
|
217
|
+
expect(data?.samples.map((entity) => entity.name).sort()).toEqual([
|
|
218
|
+
"Gazpacho",
|
|
219
|
+
"Pancakes",
|
|
220
|
+
]);
|
|
221
|
+
// Full lookup shape, not just names.
|
|
222
|
+
expect(
|
|
223
|
+
data?.samples.every((entity) => typeof entity.instructions === "string"),
|
|
224
|
+
).toBe(true);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
it("clamps the requested count and rejects non-integers", async () => {
|
|
228
|
+
const { sample } = compilePackStories(STORY_WITH_SAMPLE, "test", PREFIXES);
|
|
229
|
+
const data = await sample?.resolve(rt, { count: "99" });
|
|
230
|
+
expect(data?.samples.length).toBeLessThanOrEqual(5);
|
|
231
|
+
await expect(sample?.resolve(rt, { count: "abc" })).rejects.toMatchObject({
|
|
232
|
+
code: "INVALID_INPUT",
|
|
233
|
+
});
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("envelopes samples with totalCount and nextSteps", async () => {
|
|
237
|
+
const { sample } = compilePackStories(STORY_WITH_SAMPLE, "test", PREFIXES);
|
|
238
|
+
const data = await sample?.resolve(rt, {});
|
|
239
|
+
if (!data || !sample) throw new Error("expected sample data");
|
|
240
|
+
const envelope = sample.toEnvelope(data);
|
|
241
|
+
expect(envelope.meta).toEqual({ count: data.samples.length });
|
|
242
|
+
const payload = envelope.data as {
|
|
243
|
+
totalCount: number;
|
|
244
|
+
nextSteps: string[];
|
|
245
|
+
};
|
|
246
|
+
expect(payload.totalCount).toBe(2);
|
|
247
|
+
expect(payload.nextSteps.join(" ")).toContain("recipe_lookup");
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("renders samples via the lookup formatters under a count header", async () => {
|
|
251
|
+
const { sample } = compilePackStories(STORY_WITH_SAMPLE, "test", PREFIXES);
|
|
252
|
+
const data = await sample?.resolve(rt, { count: "2" });
|
|
253
|
+
if (!data || !sample) throw new Error("expected sample data");
|
|
254
|
+
const llm = sample.formatters.llm(sample.toOutput(data, {}));
|
|
255
|
+
expect(llm).toContain("Showing 2 of 2 recipe entries");
|
|
256
|
+
expect(llm).toContain("## ");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
it("honors a declared default count", async () => {
|
|
260
|
+
const configured: StoryPackDefinition = {
|
|
261
|
+
...RECIPE_STORY,
|
|
262
|
+
lookup: {
|
|
263
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
264
|
+
...RECIPE_STORY.lookup!,
|
|
265
|
+
sample: { count: 1 },
|
|
266
|
+
},
|
|
267
|
+
};
|
|
268
|
+
const { sample } = compilePackStories(configured, "test", PREFIXES);
|
|
269
|
+
const data = await sample?.resolve(rt, {});
|
|
270
|
+
expect(data?.samples).toHaveLength(1);
|
|
271
|
+
});
|
|
272
|
+
|
|
273
|
+
it("compiles no sample story when the capability is not declared", () => {
|
|
274
|
+
const { sample } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
275
|
+
expect(sample).toBeUndefined();
|
|
276
|
+
});
|
|
277
|
+
});
|
|
278
|
+
|
|
91
279
|
describe("compilePackStories — lookup", () => {
|
|
92
280
|
it("looks an entity up by name, case-insensitively", async () => {
|
|
93
281
|
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
@@ -144,3 +332,420 @@ describe("compilePackStories — lookup", () => {
|
|
|
144
332
|
await expect(lookup?.complete?.("pan", ctx)).resolves.toEqual(["Pancakes"]);
|
|
145
333
|
});
|
|
146
334
|
});
|
|
335
|
+
|
|
336
|
+
// Pack v1: IRI/prefixed-name addressing and glob expansion on lookups.
|
|
337
|
+
describe("compilePackStories — lookup by IRI and glob", () => {
|
|
338
|
+
it("resolves an absolute IRI to the exact entity", async () => {
|
|
339
|
+
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
340
|
+
const result = await lookup?.resolve(
|
|
341
|
+
rt,
|
|
342
|
+
["http://example.org/recipes/pancakes"],
|
|
343
|
+
{},
|
|
344
|
+
);
|
|
345
|
+
expect(result?.errors).toEqual([]);
|
|
346
|
+
expect(result?.results.at(0)?.name).toBe("Pancakes");
|
|
347
|
+
});
|
|
348
|
+
|
|
349
|
+
it("resolves a prefixed name through the merged prefix map", async () => {
|
|
350
|
+
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
351
|
+
const result = await lookup?.resolve(rt, ["ex:gazpacho"], {});
|
|
352
|
+
expect(result?.errors).toEqual([]);
|
|
353
|
+
expect(result?.results.at(0)?.name).toBe("Gazpacho");
|
|
354
|
+
});
|
|
355
|
+
|
|
356
|
+
it("collects an INVALID_INPUT entry for an unknown prefix", async () => {
|
|
357
|
+
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
358
|
+
const result = await lookup?.resolve(rt, ["zz:nonexistent"], {});
|
|
359
|
+
expect(result?.results).toHaveLength(0);
|
|
360
|
+
expect(result?.errors.at(0)?.code).toBe("INVALID_INPUT");
|
|
361
|
+
});
|
|
362
|
+
|
|
363
|
+
it("collects ENTITY_NOT_FOUND for an IRI naming no entity", async () => {
|
|
364
|
+
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
365
|
+
const result = await lookup?.resolve(rt, ["ex:missing"], {});
|
|
366
|
+
expect(result?.results).toHaveLength(0);
|
|
367
|
+
expect(result?.errors.at(0)?.code).toBe("ENTITY_NOT_FOUND");
|
|
368
|
+
});
|
|
369
|
+
|
|
370
|
+
it("expands glob queries against the entity name list", async () => {
|
|
371
|
+
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
372
|
+
const result = await lookup?.resolve(rt, ["*a*"], {});
|
|
373
|
+
expect(result?.errors).toEqual([]);
|
|
374
|
+
expect(result?.results.map((entity) => entity.name).sort()).toEqual([
|
|
375
|
+
"Gazpacho",
|
|
376
|
+
"Pancakes",
|
|
377
|
+
]);
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
it("reports EMPTY_RESULTS for a glob matching nothing", async () => {
|
|
381
|
+
const { lookup } = compilePackStories(RECIPE_STORY, "test", PREFIXES);
|
|
382
|
+
const result = await lookup?.resolve(rt, ["Zebra*", "Pancakes"], {});
|
|
383
|
+
expect(result?.results.at(0)?.name).toBe("Pancakes");
|
|
384
|
+
const error = result?.errors.at(0);
|
|
385
|
+
expect(error?.code).toBe("EMPTY_RESULTS");
|
|
386
|
+
expect(error?.query).toBe("Zebra*");
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ---------------------------------------------------------------------------
|
|
391
|
+
// Pack v1: multi-valued expand projections (nested SPARQL)
|
|
392
|
+
// ---------------------------------------------------------------------------
|
|
393
|
+
|
|
394
|
+
const RECIPE_TTL_WITH_INGREDIENTS = `${RECIPE_TTL}
|
|
395
|
+
ex:pancakes ex:ingredient [ ex:label "Flour" ; ex:amount "200g" ] .
|
|
396
|
+
ex:pancakes ex:ingredient [ ex:label "Milk" ; ex:amount "300ml" ] .
|
|
397
|
+
ex:gazpacho ex:ingredient [ ex:label "Tomato" ; ex:amount "1kg" ] .
|
|
398
|
+
`;
|
|
399
|
+
|
|
400
|
+
/** RECIPE_STORY with a multi-valued `ingredients` expand. */
|
|
401
|
+
const RECIPE_STORY_WITH_EXPAND: StoryPackDefinition = {
|
|
402
|
+
...RECIPE_STORY,
|
|
403
|
+
lookup: {
|
|
404
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
405
|
+
...RECIPE_STORY.lookup!,
|
|
406
|
+
expand: [
|
|
407
|
+
{
|
|
408
|
+
name: "ingredients",
|
|
409
|
+
heading: "Ingredients",
|
|
410
|
+
kind: "table",
|
|
411
|
+
relation: "ex:ingredient",
|
|
412
|
+
select: [
|
|
413
|
+
{ name: "label", property: "ex:label" },
|
|
414
|
+
{ name: "amount", property: "ex:amount" },
|
|
415
|
+
],
|
|
416
|
+
},
|
|
417
|
+
],
|
|
418
|
+
},
|
|
419
|
+
};
|
|
420
|
+
|
|
421
|
+
describe("compilePackStories — expand", () => {
|
|
422
|
+
let expandStore: Store;
|
|
423
|
+
let expandCleanup: () => void;
|
|
424
|
+
let expandRt: PragmaRuntime;
|
|
425
|
+
|
|
426
|
+
beforeAll(async () => {
|
|
427
|
+
const result = await createTestStore({
|
|
428
|
+
ttl: RECIPE_TTL_WITH_INGREDIENTS,
|
|
429
|
+
prefixes: RECIPE_PREFIXES,
|
|
430
|
+
});
|
|
431
|
+
expandStore = result.store;
|
|
432
|
+
expandCleanup = result.cleanup;
|
|
433
|
+
expandRt = { store: expandStore } as PragmaRuntime;
|
|
434
|
+
});
|
|
435
|
+
|
|
436
|
+
afterAll(() => expandCleanup());
|
|
437
|
+
|
|
438
|
+
it("attaches expanded child rows as an array on the entity", async () => {
|
|
439
|
+
const { lookup } = compilePackStories(
|
|
440
|
+
RECIPE_STORY_WITH_EXPAND,
|
|
441
|
+
"test",
|
|
442
|
+
PREFIXES,
|
|
443
|
+
);
|
|
444
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {});
|
|
445
|
+
const entity = result?.results.at(0) as
|
|
446
|
+
| { ingredients?: Array<Record<string, string>> }
|
|
447
|
+
| undefined;
|
|
448
|
+
expect(entity?.ingredients).toHaveLength(2);
|
|
449
|
+
const labels = entity?.ingredients?.map((i) => i.label).sort();
|
|
450
|
+
expect(labels).toEqual(["Flour", "Milk"]);
|
|
451
|
+
const flour = entity?.ingredients?.find((i) => i.label === "Flour");
|
|
452
|
+
expect(flour?.amount).toBe("200g");
|
|
453
|
+
});
|
|
454
|
+
|
|
455
|
+
it("renders the expand as a section through the generic renderer", async () => {
|
|
456
|
+
const { lookup } = compilePackStories(
|
|
457
|
+
RECIPE_STORY_WITH_EXPAND,
|
|
458
|
+
"test",
|
|
459
|
+
PREFIXES,
|
|
460
|
+
);
|
|
461
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {});
|
|
462
|
+
const entity = result?.results.at(0);
|
|
463
|
+
if (!entity || !lookup) throw new Error("expected entity");
|
|
464
|
+
const llm = lookup.formatters.llm(
|
|
465
|
+
lookup.toFmtInput(entity, { surface: "cli", detailed: true, params: {} }),
|
|
466
|
+
);
|
|
467
|
+
expect(llm).toContain("Ingredients");
|
|
468
|
+
expect(llm).toContain("Flour");
|
|
469
|
+
expect(llm).toContain("200g");
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
it("returns an empty array when the entity has no children", async () => {
|
|
473
|
+
const noExpand: StoryPackDefinition = {
|
|
474
|
+
...RECIPE_STORY_WITH_EXPAND,
|
|
475
|
+
lookup: {
|
|
476
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
477
|
+
...RECIPE_STORY_WITH_EXPAND.lookup!,
|
|
478
|
+
expand: [
|
|
479
|
+
{
|
|
480
|
+
name: "steps",
|
|
481
|
+
relation: "ex:step",
|
|
482
|
+
select: [{ name: "text", property: "ex:text" }],
|
|
483
|
+
},
|
|
484
|
+
],
|
|
485
|
+
},
|
|
486
|
+
};
|
|
487
|
+
const { lookup } = compilePackStories(noExpand, "test", PREFIXES);
|
|
488
|
+
const result = await lookup?.resolve(expandRt, ["Gazpacho"], {});
|
|
489
|
+
const entity = result?.results.at(0) as { steps?: unknown[] } | undefined;
|
|
490
|
+
expect(entity?.steps).toEqual([]);
|
|
491
|
+
});
|
|
492
|
+
|
|
493
|
+
// -------------------------------------------------------------------------
|
|
494
|
+
// Disclosure capability: level-gated expands + derived --detail
|
|
495
|
+
// -------------------------------------------------------------------------
|
|
496
|
+
|
|
497
|
+
/** RECIPE_STORY_WITH_EXPAND with `ingredients` gated to a "detailed" level. */
|
|
498
|
+
const RECIPE_STORY_DISCLOSURE: StoryPackDefinition = {
|
|
499
|
+
...RECIPE_STORY_WITH_EXPAND,
|
|
500
|
+
lookup: {
|
|
501
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
502
|
+
...RECIPE_STORY_WITH_EXPAND.lookup!,
|
|
503
|
+
expand: [
|
|
504
|
+
{
|
|
505
|
+
name: "ingredients",
|
|
506
|
+
heading: "Ingredients",
|
|
507
|
+
kind: "table",
|
|
508
|
+
relation: "ex:ingredient",
|
|
509
|
+
select: [
|
|
510
|
+
{ name: "label", property: "ex:label" },
|
|
511
|
+
{ name: "amount", property: "ex:amount" },
|
|
512
|
+
],
|
|
513
|
+
level: "detailed",
|
|
514
|
+
},
|
|
515
|
+
],
|
|
516
|
+
disclosure: { levels: ["summary", "detailed"] },
|
|
517
|
+
},
|
|
518
|
+
};
|
|
519
|
+
|
|
520
|
+
it("derives a `detail` param enumerated from the declared levels", () => {
|
|
521
|
+
const { lookup } = compilePackStories(
|
|
522
|
+
RECIPE_STORY_DISCLOSURE,
|
|
523
|
+
"test",
|
|
524
|
+
PREFIXES,
|
|
525
|
+
);
|
|
526
|
+
const param = lookup?.params?.find((p) => p.name === "detail");
|
|
527
|
+
expect(param?.enum).toEqual(["summary", "detailed"]);
|
|
528
|
+
});
|
|
529
|
+
|
|
530
|
+
it("omits a level-gated expand at the base level (no fetch)", async () => {
|
|
531
|
+
const { lookup } = compilePackStories(
|
|
532
|
+
RECIPE_STORY_DISCLOSURE,
|
|
533
|
+
"test",
|
|
534
|
+
PREFIXES,
|
|
535
|
+
);
|
|
536
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {});
|
|
537
|
+
const entity = result?.results.at(0) as
|
|
538
|
+
| { ingredients?: unknown }
|
|
539
|
+
| undefined;
|
|
540
|
+
expect(entity?.ingredients).toBeUndefined();
|
|
541
|
+
});
|
|
542
|
+
|
|
543
|
+
it("includes a level-gated expand when --detail selects its level", async () => {
|
|
544
|
+
const { lookup } = compilePackStories(
|
|
545
|
+
RECIPE_STORY_DISCLOSURE,
|
|
546
|
+
"test",
|
|
547
|
+
PREFIXES,
|
|
548
|
+
);
|
|
549
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {
|
|
550
|
+
detail: "detailed",
|
|
551
|
+
});
|
|
552
|
+
const entity = result?.results.at(0) as
|
|
553
|
+
| { ingredients?: unknown[] }
|
|
554
|
+
| undefined;
|
|
555
|
+
expect(entity?.ingredients).toHaveLength(2);
|
|
556
|
+
});
|
|
557
|
+
|
|
558
|
+
it("honors the global config.detail default when no flag is passed", async () => {
|
|
559
|
+
const { lookup } = compilePackStories(
|
|
560
|
+
RECIPE_STORY_DISCLOSURE,
|
|
561
|
+
"test",
|
|
562
|
+
PREFIXES,
|
|
563
|
+
);
|
|
564
|
+
const configuredRt = {
|
|
565
|
+
store: expandStore,
|
|
566
|
+
config: { detail: "detailed" },
|
|
567
|
+
} as PragmaRuntime;
|
|
568
|
+
const result = await lookup?.resolve(configuredRt, ["Pancakes"], {});
|
|
569
|
+
const entity = result?.results.at(0) as
|
|
570
|
+
| { ingredients?: unknown[] }
|
|
571
|
+
| undefined;
|
|
572
|
+
expect(entity?.ingredients).toHaveLength(2);
|
|
573
|
+
});
|
|
574
|
+
|
|
575
|
+
it("lets an explicit --detail override the config default", async () => {
|
|
576
|
+
const { lookup } = compilePackStories(
|
|
577
|
+
RECIPE_STORY_DISCLOSURE,
|
|
578
|
+
"test",
|
|
579
|
+
PREFIXES,
|
|
580
|
+
);
|
|
581
|
+
const configuredRt = {
|
|
582
|
+
store: expandStore,
|
|
583
|
+
config: { detail: "detailed" },
|
|
584
|
+
} as PragmaRuntime;
|
|
585
|
+
const result = await lookup?.resolve(configuredRt, ["Pancakes"], {
|
|
586
|
+
detail: "summary",
|
|
587
|
+
});
|
|
588
|
+
const entity = result?.results.at(0) as
|
|
589
|
+
| { ingredients?: unknown }
|
|
590
|
+
| undefined;
|
|
591
|
+
expect(entity?.ingredients).toBeUndefined();
|
|
592
|
+
});
|
|
593
|
+
|
|
594
|
+
// ---------------------------------------------------------------------
|
|
595
|
+
// Legacy disclosure alias flags + the MCP full-data default
|
|
596
|
+
// ---------------------------------------------------------------------
|
|
597
|
+
|
|
598
|
+
/** Three levels so alias precedence (highest wins) is observable. */
|
|
599
|
+
const RECIPE_STORY_THREE_LEVELS: StoryPackDefinition = {
|
|
600
|
+
...RECIPE_STORY_WITH_EXPAND,
|
|
601
|
+
lookup: {
|
|
602
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
603
|
+
...RECIPE_STORY_WITH_EXPAND.lookup!,
|
|
604
|
+
expand: [
|
|
605
|
+
{
|
|
606
|
+
name: "ingredients",
|
|
607
|
+
relation: "ex:ingredient",
|
|
608
|
+
select: [{ name: "label", property: "ex:label" }],
|
|
609
|
+
level: "digest",
|
|
610
|
+
},
|
|
611
|
+
],
|
|
612
|
+
disclosure: { levels: ["summary", "digest", "detailed"] },
|
|
613
|
+
},
|
|
614
|
+
};
|
|
615
|
+
|
|
616
|
+
it("derives a boolean alias flag per non-base level", () => {
|
|
617
|
+
const { lookup } = compilePackStories(
|
|
618
|
+
RECIPE_STORY_THREE_LEVELS,
|
|
619
|
+
"test",
|
|
620
|
+
PREFIXES,
|
|
621
|
+
);
|
|
622
|
+
const names = (lookup?.params ?? []).map((param) => param.name);
|
|
623
|
+
expect(names).toEqual(["detail", "digest", "detailed"]);
|
|
624
|
+
const digest = lookup?.params?.find((param) => param.name === "digest");
|
|
625
|
+
expect(digest?.type).toBe("boolean");
|
|
626
|
+
expect(digest?.default).toBe(false);
|
|
627
|
+
});
|
|
628
|
+
|
|
629
|
+
it("an alias flag implies its level", async () => {
|
|
630
|
+
const { lookup } = compilePackStories(
|
|
631
|
+
RECIPE_STORY_THREE_LEVELS,
|
|
632
|
+
"test",
|
|
633
|
+
PREFIXES,
|
|
634
|
+
);
|
|
635
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {
|
|
636
|
+
digest: true,
|
|
637
|
+
});
|
|
638
|
+
const entity = result?.results.at(0) as
|
|
639
|
+
| { ingredients?: unknown[] }
|
|
640
|
+
| undefined;
|
|
641
|
+
expect(entity?.ingredients).toHaveLength(2);
|
|
642
|
+
});
|
|
643
|
+
|
|
644
|
+
it("an explicit --detail beats an alias flag", async () => {
|
|
645
|
+
const { lookup } = compilePackStories(
|
|
646
|
+
RECIPE_STORY_THREE_LEVELS,
|
|
647
|
+
"test",
|
|
648
|
+
PREFIXES,
|
|
649
|
+
);
|
|
650
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {
|
|
651
|
+
detail: "summary",
|
|
652
|
+
detailed: true,
|
|
653
|
+
});
|
|
654
|
+
const entity = result?.results.at(0) as
|
|
655
|
+
| { ingredients?: unknown }
|
|
656
|
+
| undefined;
|
|
657
|
+
expect(entity?.ingredients).toBeUndefined();
|
|
658
|
+
});
|
|
659
|
+
|
|
660
|
+
it("the highest requested alias level wins when several are set", async () => {
|
|
661
|
+
/** Gate the expand at `detailed` so digest-vs-detailed is observable. */
|
|
662
|
+
const gatedAtDetailed: StoryPackDefinition = {
|
|
663
|
+
...RECIPE_STORY_THREE_LEVELS,
|
|
664
|
+
lookup: {
|
|
665
|
+
// biome-ignore lint/style/noNonNullAssertion: fixture always declares lookup
|
|
666
|
+
...RECIPE_STORY_THREE_LEVELS.lookup!,
|
|
667
|
+
expand: [
|
|
668
|
+
{
|
|
669
|
+
name: "ingredients",
|
|
670
|
+
relation: "ex:ingredient",
|
|
671
|
+
select: [{ name: "label", property: "ex:label" }],
|
|
672
|
+
level: "detailed",
|
|
673
|
+
},
|
|
674
|
+
],
|
|
675
|
+
},
|
|
676
|
+
};
|
|
677
|
+
const { lookup } = compilePackStories(gatedAtDetailed, "test", PREFIXES);
|
|
678
|
+
const result = await lookup?.resolve(expandRt, ["Pancakes"], {
|
|
679
|
+
digest: true,
|
|
680
|
+
detailed: true,
|
|
681
|
+
});
|
|
682
|
+
const entity = result?.results.at(0) as
|
|
683
|
+
| { ingredients?: unknown[] }
|
|
684
|
+
| undefined;
|
|
685
|
+
expect(entity?.ingredients).toHaveLength(2);
|
|
686
|
+
});
|
|
687
|
+
|
|
688
|
+
it("an MCP call with no explicit choice gets the highest level", async () => {
|
|
689
|
+
const { lookup } = compilePackStories(
|
|
690
|
+
RECIPE_STORY_DISCLOSURE,
|
|
691
|
+
"test",
|
|
692
|
+
PREFIXES,
|
|
693
|
+
);
|
|
694
|
+
const result = await lookup?.resolve(
|
|
695
|
+
expandRt,
|
|
696
|
+
["Pancakes"],
|
|
697
|
+
{},
|
|
698
|
+
{
|
|
699
|
+
surface: "mcp",
|
|
700
|
+
detailed: false,
|
|
701
|
+
params: {},
|
|
702
|
+
},
|
|
703
|
+
);
|
|
704
|
+
const entity = result?.results.at(0) as
|
|
705
|
+
| { ingredients?: unknown[] }
|
|
706
|
+
| undefined;
|
|
707
|
+
expect(entity?.ingredients).toHaveLength(2);
|
|
708
|
+
});
|
|
709
|
+
|
|
710
|
+
it("the global config.detail outranks the MCP full-data default", async () => {
|
|
711
|
+
// Counterpart of the CLI config.detail test above: a user-configured
|
|
712
|
+
// global `detail: summary` must win over the MCP surface fallback.
|
|
713
|
+
const { lookup } = compilePackStories(
|
|
714
|
+
RECIPE_STORY_DISCLOSURE,
|
|
715
|
+
"test",
|
|
716
|
+
PREFIXES,
|
|
717
|
+
);
|
|
718
|
+
const configuredRt = {
|
|
719
|
+
store: expandStore,
|
|
720
|
+
config: { detail: "summary" },
|
|
721
|
+
} as PragmaRuntime;
|
|
722
|
+
const result = await lookup?.resolve(
|
|
723
|
+
configuredRt,
|
|
724
|
+
["Pancakes"],
|
|
725
|
+
{},
|
|
726
|
+
{ surface: "mcp", detailed: false, params: {} },
|
|
727
|
+
);
|
|
728
|
+
const entity = result?.results.at(0) as
|
|
729
|
+
| { ingredients?: unknown }
|
|
730
|
+
| undefined;
|
|
731
|
+
expect(entity?.ingredients).toBeUndefined();
|
|
732
|
+
});
|
|
733
|
+
|
|
734
|
+
it("an MCP alias explicitly false opts down to the base level", async () => {
|
|
735
|
+
const { lookup } = compilePackStories(
|
|
736
|
+
RECIPE_STORY_DISCLOSURE,
|
|
737
|
+
"test",
|
|
738
|
+
PREFIXES,
|
|
739
|
+
);
|
|
740
|
+
const result = await lookup?.resolve(
|
|
741
|
+
expandRt,
|
|
742
|
+
["Pancakes"],
|
|
743
|
+
{ detailed: false },
|
|
744
|
+
{ surface: "mcp", detailed: false, params: {} },
|
|
745
|
+
);
|
|
746
|
+
const entity = result?.results.at(0) as
|
|
747
|
+
| { ingredients?: unknown }
|
|
748
|
+
| undefined;
|
|
749
|
+
expect(entity?.ingredients).toBeUndefined();
|
|
750
|
+
});
|
|
751
|
+
});
|