@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
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import type { Store } from "@canonical/ke";
|
|
2
2
|
import { PragmaError } from "#error";
|
|
3
|
+
import resolveUri from "../../../graph/helpers/resolveUri.js";
|
|
3
4
|
import type {
|
|
4
5
|
ColumnDef,
|
|
5
6
|
LookupField,
|
|
@@ -9,45 +10,85 @@ import type {
|
|
|
9
10
|
} from "../../contracts.js";
|
|
10
11
|
import type { Formatters } from "../../formatters.js";
|
|
11
12
|
import lookupMany from "../../lookupMany.js";
|
|
13
|
+
import parseSampleCount, {
|
|
14
|
+
DEFAULT_SAMPLE_COUNT,
|
|
15
|
+
MAX_SAMPLE_COUNT,
|
|
16
|
+
MIN_SAMPLE_COUNT,
|
|
17
|
+
} from "../../parseSampleCount.js";
|
|
18
|
+
import pickRandom from "../../pickRandom.js";
|
|
12
19
|
import {
|
|
13
20
|
renderListLlm,
|
|
14
21
|
renderListPlain,
|
|
15
22
|
renderLookupLlm,
|
|
16
23
|
renderLookupPlain,
|
|
17
24
|
} from "../../renderers.js";
|
|
18
|
-
import
|
|
25
|
+
import createSampleFormatters from "../../sampleFormatters.js";
|
|
26
|
+
import {
|
|
27
|
+
expandGlob,
|
|
28
|
+
isGlobPattern,
|
|
29
|
+
suggestNames,
|
|
30
|
+
} from "../../suggestions/index.js";
|
|
31
|
+
import type { SampleOutput } from "../../types/index.js";
|
|
19
32
|
import requirePragmaContext from "../requirePragmaContext.js";
|
|
20
33
|
import type { LookupStory, ReadStory, StoryParam } from "../types.js";
|
|
21
34
|
import applyPackFilters from "./applyPackFilters.js";
|
|
22
|
-
import
|
|
35
|
+
import applyPackSearch from "./applyPackSearch.js";
|
|
36
|
+
import buildLookupQuery, {
|
|
37
|
+
buildExpandQuery,
|
|
38
|
+
buildLookupByIriQuery,
|
|
39
|
+
buildLookupNamesQuery,
|
|
40
|
+
} from "./buildLookupQuery.js";
|
|
23
41
|
import runSelectQuery from "./runSelectQuery.js";
|
|
24
42
|
import type {
|
|
25
43
|
StoryPackDefinition,
|
|
44
|
+
StoryPackDisclosure,
|
|
26
45
|
StoryPackFilter,
|
|
46
|
+
StoryPackList,
|
|
27
47
|
StoryPackLookup,
|
|
48
|
+
StoryPackSearch,
|
|
28
49
|
} from "./types.js";
|
|
50
|
+
import { isEmbeddableIri } from "./validateStoryPackDefinition.js";
|
|
29
51
|
|
|
30
52
|
/** A pack entity/row: SELECT variable name → string value. */
|
|
31
53
|
type PackRow = Record<string, string>;
|
|
32
54
|
|
|
55
|
+
/**
|
|
56
|
+
* A looked-up pack entity: flat SELECT fields plus any expanded child arrays
|
|
57
|
+
* (pack v1 nested projections). Scalar fields are strings; an expand's value
|
|
58
|
+
* is the array of its child rows.
|
|
59
|
+
*/
|
|
60
|
+
type PackEntity = Record<string, string | readonly PackRow[]>;
|
|
61
|
+
|
|
62
|
+
/** Resolved sample data: the drawn entities plus the population size. */
|
|
63
|
+
interface PackSampleData {
|
|
64
|
+
readonly samples: PackEntity[];
|
|
65
|
+
readonly totalCount: number;
|
|
66
|
+
}
|
|
67
|
+
|
|
33
68
|
/** The read stories a pack definition compiles to. */
|
|
34
69
|
export interface CompiledPackStories {
|
|
35
70
|
readonly list: ReadStory<PackRow[], PackRow[]>;
|
|
36
|
-
|
|
71
|
+
/** Extra list-shaped verbs (e.g. `categories`), in declaration order. */
|
|
72
|
+
readonly verbs: readonly ReadStory<PackRow[], PackRow[]>[];
|
|
73
|
+
readonly lookup?: LookupStory<PackEntity, PackEntity>;
|
|
74
|
+
/** The `sample` story, when the lookup declares the capability. */
|
|
75
|
+
readonly sample?: ReadStory<PackSampleData, SampleOutput<PackEntity>>;
|
|
37
76
|
}
|
|
38
77
|
|
|
39
78
|
/**
|
|
40
79
|
* Compile one validated story-pack definition into kernel read stories.
|
|
41
80
|
*
|
|
42
|
-
* The list story
|
|
43
|
-
*
|
|
44
|
-
* through the shared generic renderers
|
|
45
|
-
* map, so foreign namespaces display
|
|
81
|
+
* The list story (and any extra list-shaped verbs) run the pack's
|
|
82
|
+
* preferred SELECT verbatim; the lookup and sample stories run generated,
|
|
83
|
+
* injection-safe queries. All render through the shared generic renderers
|
|
84
|
+
* with the runtime's merged prefix map, so foreign namespaces display
|
|
85
|
+
* compacted.
|
|
46
86
|
*
|
|
47
87
|
* @param definition - A validated pack definition.
|
|
48
88
|
* @param source - Where the definition came from, for diagnostics.
|
|
49
89
|
* @param prefixes - The merged prefix map used for display.
|
|
50
|
-
* @returns The compiled list story
|
|
90
|
+
* @returns The compiled list story, extra verbs, and — when declared —
|
|
91
|
+
* the lookup and sample stories.
|
|
51
92
|
*/
|
|
52
93
|
export default function compilePackStories(
|
|
53
94
|
definition: StoryPackDefinition,
|
|
@@ -55,17 +96,71 @@ export default function compilePackStories(
|
|
|
55
96
|
prefixes: Readonly<Record<string, string>>,
|
|
56
97
|
): CompiledPackStories {
|
|
57
98
|
const { noun } = definition;
|
|
58
|
-
const heading = capitalize(noun);
|
|
59
99
|
const description = definition.description ?? `List ${noun} entries`;
|
|
60
100
|
|
|
61
|
-
const
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
101
|
+
const list = compileListVerb(definition.list, {
|
|
102
|
+
noun,
|
|
103
|
+
verb: "list",
|
|
104
|
+
heading: capitalize(noun),
|
|
105
|
+
description,
|
|
106
|
+
toolDescription:
|
|
107
|
+
definition.toolDescription ?? `${description} (story pack: ${source}).`,
|
|
108
|
+
source,
|
|
109
|
+
prefixes,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
const verbs = (definition.verbs ?? []).map((verb) => {
|
|
113
|
+
const verbDescription = verb.description ?? `List ${noun} ${verb.verb}`;
|
|
114
|
+
return compileListVerb(verb, {
|
|
115
|
+
noun,
|
|
116
|
+
verb: verb.verb,
|
|
117
|
+
heading: `${capitalize(noun)} ${verb.verb}`,
|
|
118
|
+
description: verbDescription,
|
|
119
|
+
toolDescription:
|
|
120
|
+
verb.toolDescription ?? `${verbDescription} (story pack: ${source}).`,
|
|
121
|
+
source,
|
|
122
|
+
prefixes,
|
|
123
|
+
});
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
const compiled = definition.lookup
|
|
127
|
+
? compileLookup(definition.lookup, noun, source, prefixes)
|
|
128
|
+
: undefined;
|
|
129
|
+
|
|
130
|
+
return {
|
|
131
|
+
list,
|
|
132
|
+
verbs,
|
|
133
|
+
...(compiled ? { lookup: compiled.lookup } : {}),
|
|
134
|
+
...(compiled?.sample ? { sample: compiled.sample } : {}),
|
|
135
|
+
};
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
/** Presentation facts for one compiled list-shaped verb. */
|
|
139
|
+
interface ListVerbMeta {
|
|
140
|
+
readonly noun: string;
|
|
141
|
+
readonly verb: string;
|
|
142
|
+
readonly heading: string;
|
|
143
|
+
readonly description: string;
|
|
144
|
+
readonly toolDescription: string;
|
|
145
|
+
readonly source: string;
|
|
146
|
+
readonly prefixes: Readonly<Record<string, string>>;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Compile one list-shaped story half (the `list` verb or an extra verb)
|
|
151
|
+
* into a kernel read story.
|
|
152
|
+
*/
|
|
153
|
+
function compileListVerb(
|
|
154
|
+
shape: StoryPackList,
|
|
155
|
+
meta: ListVerbMeta,
|
|
156
|
+
): ReadStory<PackRow[], PackRow[]> {
|
|
157
|
+
const { noun, verb, source, prefixes } = meta;
|
|
158
|
+
const listColumns: ColumnDef<PackRow>[] = shape.columns.map((column) => ({
|
|
159
|
+
key: column.field,
|
|
160
|
+
label: column.label ?? column.field,
|
|
161
|
+
}));
|
|
67
162
|
const listOptions: RenderListOptions<PackRow> = {
|
|
68
|
-
heading,
|
|
163
|
+
heading: meta.heading,
|
|
69
164
|
columns: listColumns,
|
|
70
165
|
prefixes,
|
|
71
166
|
};
|
|
@@ -75,40 +170,40 @@ export default function compilePackStories(
|
|
|
75
170
|
json: (rows) => JSON.stringify(rows, null, 2),
|
|
76
171
|
};
|
|
77
172
|
|
|
78
|
-
const filters =
|
|
79
|
-
const
|
|
80
|
-
|
|
173
|
+
const filters = shape.filters;
|
|
174
|
+
const search = shape.search;
|
|
175
|
+
// The generated example uses the first filter with a declared value set —
|
|
176
|
+
// a value-free filter has no representative value to show.
|
|
177
|
+
const filterExample = filters?.find((filter) => filter.values !== undefined);
|
|
178
|
+
return {
|
|
81
179
|
noun,
|
|
82
|
-
verb
|
|
83
|
-
description,
|
|
84
|
-
toolDescription:
|
|
85
|
-
|
|
86
|
-
params: projectFilterParams(filters),
|
|
180
|
+
verb,
|
|
181
|
+
description: meta.description,
|
|
182
|
+
toolDescription: meta.toolDescription,
|
|
183
|
+
params: [...projectFilterParams(filters), ...projectSearchParam(search)],
|
|
87
184
|
examples: [
|
|
88
|
-
`pragma ${noun}
|
|
185
|
+
`pragma ${noun} ${verb}`,
|
|
89
186
|
...(filterExample
|
|
90
187
|
? [
|
|
91
|
-
`pragma ${noun}
|
|
188
|
+
`pragma ${noun} ${verb} --${filterExample.param} ${quoteExampleValue(filterExample.values?.at(0) ?? "")}`,
|
|
92
189
|
]
|
|
93
190
|
: []),
|
|
94
|
-
`pragma ${noun}
|
|
191
|
+
`pragma ${noun} ${verb} --llm`,
|
|
95
192
|
],
|
|
96
193
|
resolve: async (rt, params) =>
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
194
|
+
applyPackSearch(
|
|
195
|
+
applyPackFilters(
|
|
196
|
+
await runSelectQuery(rt.store, shape.query, source),
|
|
197
|
+
filters,
|
|
198
|
+
params,
|
|
199
|
+
),
|
|
200
|
+
search,
|
|
100
201
|
params,
|
|
101
202
|
),
|
|
102
203
|
toOutput: (rows) => rows,
|
|
103
204
|
formatters: listFormatters,
|
|
104
205
|
toEnvelope: (rows) => ({ data: rows, meta: { count: rows.length } }),
|
|
105
206
|
};
|
|
106
|
-
|
|
107
|
-
const lookup = definition.lookup
|
|
108
|
-
? compileLookup(definition.lookup, noun, source, prefixes)
|
|
109
|
-
: undefined;
|
|
110
|
-
|
|
111
|
-
return { list, ...(lookup ? { lookup } : {}) };
|
|
112
207
|
}
|
|
113
208
|
|
|
114
209
|
function compileLookup(
|
|
@@ -116,35 +211,79 @@ function compileLookup(
|
|
|
116
211
|
noun: string,
|
|
117
212
|
source: string,
|
|
118
213
|
prefixes: Readonly<Record<string, string>>,
|
|
119
|
-
):
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
214
|
+
): {
|
|
215
|
+
lookup: LookupStory<PackEntity, PackEntity>;
|
|
216
|
+
sample?: ReadStory<PackSampleData, SampleOutput<PackEntity>>;
|
|
217
|
+
} {
|
|
218
|
+
const fields: LookupField<PackEntity>[] = (lookup.fields ?? []).map(
|
|
219
|
+
(field) => ({
|
|
220
|
+
label: field.label ?? field.name,
|
|
221
|
+
value: (entity) => entity[field.name],
|
|
222
|
+
}),
|
|
223
|
+
);
|
|
224
|
+
const flatSections: SectionDef<PackEntity>[] = (lookup.sections ?? []).map(
|
|
125
225
|
(section) => ({
|
|
126
226
|
key: section.name,
|
|
127
227
|
heading: section.label ?? section.name,
|
|
128
228
|
kind: section.kind ?? "field",
|
|
129
229
|
}),
|
|
130
230
|
);
|
|
131
|
-
|
|
132
|
-
|
|
231
|
+
// Expands render after the flat sections, as list/table collections.
|
|
232
|
+
const expandSections: SectionDef<PackEntity>[] = (lookup.expand ?? []).map(
|
|
233
|
+
(expand) => ({
|
|
234
|
+
key: expand.name,
|
|
235
|
+
heading: expand.heading ?? expand.name,
|
|
236
|
+
kind: expand.kind ?? "list",
|
|
237
|
+
...(expand.showWhenEmpty ? { showWhenEmpty: true } : {}),
|
|
238
|
+
}),
|
|
239
|
+
);
|
|
240
|
+
const sections = [...flatSections, ...expandSections];
|
|
241
|
+
const lookupOptions: RenderLookupOptions<PackEntity> = {
|
|
242
|
+
title: (entity) => scalar(entity.name) ?? scalar(entity.uri) ?? "(unnamed)",
|
|
133
243
|
fields,
|
|
134
244
|
sections,
|
|
135
245
|
prefixes,
|
|
136
246
|
};
|
|
137
|
-
const lookupFormatters: Formatters<
|
|
247
|
+
const lookupFormatters: Formatters<PackEntity> = {
|
|
138
248
|
plain: (entity) => renderLookupPlain(entity, lookupOptions),
|
|
139
249
|
llm: (entity) => renderLookupLlm(entity, lookupOptions),
|
|
140
250
|
json: (entity) => JSON.stringify(entity, null, 2),
|
|
141
251
|
};
|
|
142
252
|
|
|
143
|
-
|
|
253
|
+
// Disclosure capability: a declared level set derives a `--detail <level>`
|
|
254
|
+
// parameter (enum of the levels) on both surfaces, plus one legacy alias
|
|
255
|
+
// boolean per non-base level (`--digest`, `--detailed`) that implies its
|
|
256
|
+
// level — preserving the pre-pack flag ergonomics on both surfaces.
|
|
257
|
+
const disclosure = lookup.disclosure;
|
|
258
|
+
const detailParams: StoryParam[] = disclosure
|
|
259
|
+
? [
|
|
260
|
+
{
|
|
261
|
+
name: "detail",
|
|
262
|
+
type: "string",
|
|
263
|
+
description: `Disclosure level (${disclosure.levels.join(", ")})`,
|
|
264
|
+
enum: [...disclosure.levels],
|
|
265
|
+
},
|
|
266
|
+
...disclosure.levels.slice(1).map(
|
|
267
|
+
(level): StoryParam => ({
|
|
268
|
+
name: level,
|
|
269
|
+
type: "boolean",
|
|
270
|
+
description: `Shorthand for --detail ${level}`,
|
|
271
|
+
toolDescription: `Shorthand for detail="${level}"`,
|
|
272
|
+
default: false,
|
|
273
|
+
}),
|
|
274
|
+
),
|
|
275
|
+
]
|
|
276
|
+
: [];
|
|
277
|
+
|
|
278
|
+
const lookupStory: LookupStory<PackEntity, PackEntity> = {
|
|
144
279
|
noun,
|
|
145
|
-
description:
|
|
146
|
-
|
|
147
|
-
|
|
280
|
+
description:
|
|
281
|
+
lookup.description ?? `Look up ${noun} details by name, IRI, or glob`,
|
|
282
|
+
toolDescription:
|
|
283
|
+
lookup.toolDescription ??
|
|
284
|
+
`Get details for one or more ${noun} entries by name, prefixed name (ex:thing), absolute IRI, or glob pattern (story pack: ${source}).`,
|
|
285
|
+
namesDescription: `${capitalize(noun)} names, IRIs/prefixed names, or glob patterns`,
|
|
286
|
+
params: detailParams,
|
|
148
287
|
complete: async (partial, cmdCtx) => {
|
|
149
288
|
const ctx = requirePragmaContext(cmdCtx);
|
|
150
289
|
const names = await listEntityNames(ctx.store, lookup, source);
|
|
@@ -153,10 +292,40 @@ function compileLookup(
|
|
|
153
292
|
);
|
|
154
293
|
},
|
|
155
294
|
examples: [`pragma ${noun} lookup <name>`],
|
|
156
|
-
resolve: (rt, names) =>
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
295
|
+
resolve: async (rt, names, params, view) => {
|
|
296
|
+
const level = disclosure
|
|
297
|
+
? resolveDetailLevel(
|
|
298
|
+
params,
|
|
299
|
+
rt.config ?? {},
|
|
300
|
+
disclosure,
|
|
301
|
+
view?.surface ?? "cli",
|
|
302
|
+
)
|
|
303
|
+
: undefined;
|
|
304
|
+
// Glob queries (`react/*`) expand against the entity name list before
|
|
305
|
+
// resolution; a glob matching nothing becomes its own error entry.
|
|
306
|
+
const expanded = await expandPackLookupQueries(
|
|
307
|
+
rt.store,
|
|
308
|
+
lookup,
|
|
309
|
+
noun,
|
|
310
|
+
source,
|
|
311
|
+
names,
|
|
312
|
+
);
|
|
313
|
+
const result = await lookupMany(expanded.names, (query) =>
|
|
314
|
+
lookupPackEntity(
|
|
315
|
+
rt.store,
|
|
316
|
+
lookup,
|
|
317
|
+
noun,
|
|
318
|
+
query,
|
|
319
|
+
source,
|
|
320
|
+
level,
|
|
321
|
+
prefixes,
|
|
322
|
+
),
|
|
323
|
+
);
|
|
324
|
+
return {
|
|
325
|
+
...result,
|
|
326
|
+
errors: [...result.errors, ...expanded.globErrors],
|
|
327
|
+
};
|
|
328
|
+
},
|
|
160
329
|
toFmtInput: (entity) => entity,
|
|
161
330
|
formatters: lookupFormatters,
|
|
162
331
|
emptyNamesError: () =>
|
|
@@ -168,13 +337,159 @@ function compileLookup(
|
|
|
168
337
|
},
|
|
169
338
|
}),
|
|
170
339
|
};
|
|
340
|
+
|
|
341
|
+
const sample = lookup.sample
|
|
342
|
+
? compileSample(lookup, noun, source, prefixes, lookupFormatters)
|
|
343
|
+
: undefined;
|
|
344
|
+
|
|
345
|
+
return { lookup: lookupStory, ...(sample ? { sample } : {}) };
|
|
171
346
|
}
|
|
172
347
|
|
|
173
348
|
/**
|
|
174
|
-
*
|
|
349
|
+
* Compile the `sample` story for a lookup that declares the capability.
|
|
350
|
+
*
|
|
351
|
+
* Draws N random entity names (clamped 1–5) and resolves each through the
|
|
352
|
+
* SAME lookup path at the HIGHEST disclosure level, so exemplars carry the
|
|
353
|
+
* full shape — fields, expands, and all. Rendering reuses the lookup
|
|
354
|
+
* formatters per entity under the shared sample header.
|
|
355
|
+
*/
|
|
356
|
+
function compileSample(
|
|
357
|
+
lookup: StoryPackLookup,
|
|
358
|
+
noun: string,
|
|
359
|
+
source: string,
|
|
360
|
+
prefixes: Readonly<Record<string, string>>,
|
|
361
|
+
lookupFormatters: Formatters<PackEntity>,
|
|
362
|
+
): ReadStory<PackSampleData, SampleOutput<PackEntity>> {
|
|
363
|
+
const config =
|
|
364
|
+
lookup.sample === true || lookup.sample === undefined ? {} : lookup.sample;
|
|
365
|
+
const description =
|
|
366
|
+
config.description ??
|
|
367
|
+
`Return randomly selected complete ${noun} entries as exemplars for shape discovery`;
|
|
368
|
+
const toolDescription =
|
|
369
|
+
config.toolDescription ??
|
|
370
|
+
`Return ${MIN_SAMPLE_COUNT}–${MAX_SAMPLE_COUNT} randomly selected complete ${noun} entries as exemplars. Use BEFORE writing queries to see actual data shapes, property names, and value formats. Each call returns different instances.`;
|
|
371
|
+
const sampleFormatters = createSampleFormatters<PackEntity>(
|
|
372
|
+
`${noun} entries`,
|
|
373
|
+
(entity, mode) => lookupFormatters[mode](entity),
|
|
374
|
+
);
|
|
375
|
+
const toOutput = (data: PackSampleData): SampleOutput<PackEntity> => ({
|
|
376
|
+
...data,
|
|
377
|
+
nextSteps: sampleNextSteps(noun, data),
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
return {
|
|
381
|
+
noun,
|
|
382
|
+
verb: "sample",
|
|
383
|
+
description,
|
|
384
|
+
toolDescription,
|
|
385
|
+
params: [
|
|
386
|
+
{
|
|
387
|
+
name: "count",
|
|
388
|
+
type: "string",
|
|
389
|
+
description: `Number of samples to return (${MIN_SAMPLE_COUNT}–${MAX_SAMPLE_COUNT}, default ${config.count ?? DEFAULT_SAMPLE_COUNT})`,
|
|
390
|
+
positional: true,
|
|
391
|
+
},
|
|
392
|
+
],
|
|
393
|
+
examples: [`pragma ${noun} sample`, `pragma ${noun} sample 3`],
|
|
394
|
+
resolve: async (rt, params) => {
|
|
395
|
+
const count = parseSampleCount(params.count ?? config.count);
|
|
396
|
+
const names = await listEntityNames(rt.store, lookup, source);
|
|
397
|
+
// Exemplars resolve at the HIGHEST disclosure level so every expand
|
|
398
|
+
// is fetched — samples exist to show the full data shape.
|
|
399
|
+
const level = lookup.disclosure?.levels.at(-1);
|
|
400
|
+
const selected = pickRandom(names, count);
|
|
401
|
+
const samples = await Promise.all(
|
|
402
|
+
selected.map((name) =>
|
|
403
|
+
lookupPackEntity(
|
|
404
|
+
rt.store,
|
|
405
|
+
lookup,
|
|
406
|
+
noun,
|
|
407
|
+
name,
|
|
408
|
+
source,
|
|
409
|
+
level,
|
|
410
|
+
prefixes,
|
|
411
|
+
),
|
|
412
|
+
),
|
|
413
|
+
);
|
|
414
|
+
return { samples, totalCount: names.length };
|
|
415
|
+
},
|
|
416
|
+
toOutput,
|
|
417
|
+
formatters: sampleFormatters,
|
|
418
|
+
toEnvelope: (data) => ({
|
|
419
|
+
data: toOutput(data),
|
|
420
|
+
meta: { count: data.samples.length },
|
|
421
|
+
}),
|
|
422
|
+
};
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/** Agent-facing follow-ups appended to sample output. */
|
|
426
|
+
function sampleNextSteps(noun: string, data: PackSampleData): string[] {
|
|
427
|
+
return [
|
|
428
|
+
`These are ${data.samples.length} of ${data.totalCount} total ${noun} entries.`,
|
|
429
|
+
`Use ${noun}_lookup to inspect specific entries by name.`,
|
|
430
|
+
`Use ${noun}_list to browse all entries.`,
|
|
431
|
+
];
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
/**
|
|
435
|
+
* Partition lookup queries into concrete names and expanded globs.
|
|
436
|
+
*
|
|
437
|
+
* Literal names pass through unchanged. Glob patterns expand against the
|
|
438
|
+
* pack's entity name list; a glob matching nothing produces an
|
|
439
|
+
* `EMPTY_RESULTS` error entry (mirroring the shared lookup-glob contract)
|
|
440
|
+
* instead of failing the whole batch.
|
|
441
|
+
*
|
|
442
|
+
* @note Impure — fetches the entity name list when a glob is present.
|
|
443
|
+
*/
|
|
444
|
+
async function expandPackLookupQueries(
|
|
445
|
+
store: Store,
|
|
446
|
+
lookup: StoryPackLookup,
|
|
447
|
+
noun: string,
|
|
448
|
+
source: string,
|
|
449
|
+
queries: readonly string[],
|
|
450
|
+
): Promise<{
|
|
451
|
+
names: string[];
|
|
452
|
+
globErrors: { query: string; code: string; message: string }[];
|
|
453
|
+
}> {
|
|
454
|
+
if (!queries.some(isGlobPattern)) {
|
|
455
|
+
return { names: [...queries], globErrors: [] };
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
const allNames = await listEntityNames(store, lookup, source);
|
|
459
|
+
const names: string[] = [];
|
|
460
|
+
const globErrors: { query: string; code: string; message: string }[] = [];
|
|
461
|
+
for (const query of queries) {
|
|
462
|
+
if (!isGlobPattern(query)) {
|
|
463
|
+
names.push(query);
|
|
464
|
+
continue;
|
|
465
|
+
}
|
|
466
|
+
const expanded = expandGlob(query, allNames);
|
|
467
|
+
if (expanded.length === 0) {
|
|
468
|
+
globErrors.push({
|
|
469
|
+
query,
|
|
470
|
+
code: "EMPTY_RESULTS",
|
|
471
|
+
message: `No ${noun} entries matching "${query}".`,
|
|
472
|
+
});
|
|
473
|
+
} else {
|
|
474
|
+
names.push(...expanded);
|
|
475
|
+
}
|
|
476
|
+
}
|
|
477
|
+
return { names, globErrors };
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
/**
|
|
481
|
+
* Look up one pack entity by name, prefixed name, or absolute IRI.
|
|
482
|
+
*
|
|
483
|
+
* A query shaped like an IRI (`https://…` or `prefix:local`) is resolved
|
|
484
|
+
* through the runtime prefix map and addressed exactly via a generated
|
|
485
|
+
* `BIND(<iri> AS ?uri)` query; anything else matches the `by` property's
|
|
486
|
+
* value case-insensitively. Both query forms are generated — the resolved
|
|
487
|
+
* IRI is validated against the embeddable-IRI shape and names are escaped
|
|
488
|
+
* as SPARQL string literals, so user input never reaches query text raw.
|
|
175
489
|
*
|
|
176
490
|
* @throws PragmaError with code `ENTITY_NOT_FOUND` and ranked suggestions
|
|
177
|
-
* when no entity matches
|
|
491
|
+
* when no entity matches, or `INVALID_INPUT` when an IRI-shaped query
|
|
492
|
+
* uses an unknown prefix or malformed IRI.
|
|
178
493
|
* @note Impure — queries the ke store.
|
|
179
494
|
*/
|
|
180
495
|
async function lookupPackEntity(
|
|
@@ -183,26 +498,156 @@ async function lookupPackEntity(
|
|
|
183
498
|
noun: string,
|
|
184
499
|
query: string,
|
|
185
500
|
source: string,
|
|
186
|
-
|
|
501
|
+
level: string | undefined,
|
|
502
|
+
prefixes: Readonly<Record<string, string>>,
|
|
503
|
+
): Promise<PackEntity> {
|
|
187
504
|
const rows = await runSelectQuery(
|
|
188
505
|
store,
|
|
189
|
-
|
|
506
|
+
buildEntityQuery(lookup, query, prefixes),
|
|
190
507
|
source,
|
|
191
508
|
);
|
|
192
|
-
const
|
|
193
|
-
if (
|
|
194
|
-
|
|
509
|
+
const base = rows.at(0);
|
|
510
|
+
if (!base) {
|
|
511
|
+
const candidates = await listEntityNames(store, lookup, source);
|
|
512
|
+
throw PragmaError.notFound(noun, query, {
|
|
513
|
+
suggestions: suggestNames(query, candidates),
|
|
514
|
+
recovery: {
|
|
515
|
+
message: `List available ${noun} entries.`,
|
|
516
|
+
cli: `pragma ${noun} list`,
|
|
517
|
+
mcp: { tool: `${noun}_list` },
|
|
518
|
+
},
|
|
519
|
+
});
|
|
195
520
|
}
|
|
196
521
|
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
522
|
+
// Enrich with any declared multi-valued expands (pack v1 nested projections).
|
|
523
|
+
// Each expand runs a sub-SELECT bound to the resolved entity IRI (`base.uri`),
|
|
524
|
+
// which is store-derived, never user input. An expand tagged with a
|
|
525
|
+
// disclosure level is fetched only at/above that level — so a lower level
|
|
526
|
+
// never runs the sub-SELECT and the renderer omits the (absent) section.
|
|
527
|
+
const entity: PackEntity = { ...base };
|
|
528
|
+
const entityUri = base.uri;
|
|
529
|
+
if (entityUri) {
|
|
530
|
+
const levels = lookup.disclosure?.levels ?? [];
|
|
531
|
+
const activeIdx = level ? levels.indexOf(level) : Number.POSITIVE_INFINITY;
|
|
532
|
+
for (const expand of lookup.expand ?? []) {
|
|
533
|
+
const requiredIdx = expand.level ? levels.indexOf(expand.level) : 0;
|
|
534
|
+
if (activeIdx < requiredIdx) continue;
|
|
535
|
+
entity[expand.name] = await runSelectQuery(
|
|
536
|
+
store,
|
|
537
|
+
buildExpandQuery(expand, entityUri),
|
|
538
|
+
source,
|
|
539
|
+
);
|
|
540
|
+
}
|
|
541
|
+
}
|
|
542
|
+
return entity;
|
|
543
|
+
}
|
|
544
|
+
|
|
545
|
+
/**
|
|
546
|
+
* Build the base entity SELECT for a lookup query.
|
|
547
|
+
*
|
|
548
|
+
* Detection mirrors the retired built-in standard lookup: a query starting
|
|
549
|
+
* with `http(s)://` or containing `:` is treated as an IRI or prefixed
|
|
550
|
+
* name and resolved through the merged prefix map. The resolved IRI must
|
|
551
|
+
* still match the embeddable shape the pack validator holds authored
|
|
552
|
+
* terms to; anything else is rejected before it can reach query text.
|
|
553
|
+
*
|
|
554
|
+
* @throws PragmaError with code `INVALID_INPUT` when the query is
|
|
555
|
+
* IRI-shaped but uses an unknown prefix or resolves to a non-embeddable
|
|
556
|
+
* IRI.
|
|
557
|
+
*/
|
|
558
|
+
function buildEntityQuery(
|
|
559
|
+
lookup: StoryPackLookup,
|
|
560
|
+
query: string,
|
|
561
|
+
prefixes: Readonly<Record<string, string>>,
|
|
562
|
+
): string {
|
|
563
|
+
if (!looksLikeIri(query)) {
|
|
564
|
+
return buildLookupQuery(lookup, query);
|
|
565
|
+
}
|
|
566
|
+
// resolveUri validates prefix membership and rejects IRI-breaking
|
|
567
|
+
// characters; the embeddable-shape check adds the validator's stricter
|
|
568
|
+
// `scheme://` contract before the IRI is embedded as `<iri>`.
|
|
569
|
+
const resolved = resolveUri(query, prefixes);
|
|
570
|
+
if (!isEmbeddableIri(resolved)) {
|
|
571
|
+
throw PragmaError.invalidInput("name", query, {
|
|
572
|
+
recovery: {
|
|
573
|
+
message:
|
|
574
|
+
"Use an absolute IRI (https://…), a prefixed name (ex:thing), or a plain entity name.",
|
|
575
|
+
},
|
|
576
|
+
});
|
|
577
|
+
}
|
|
578
|
+
return buildLookupByIriQuery(lookup, resolved);
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
/** Whether a lookup query addresses an entity by IRI or prefixed name. */
|
|
582
|
+
function looksLikeIri(query: string): boolean {
|
|
583
|
+
return (
|
|
584
|
+
query.startsWith("http://") ||
|
|
585
|
+
query.startsWith("https://") ||
|
|
586
|
+
query.includes(":")
|
|
587
|
+
);
|
|
588
|
+
}
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Resolve the effective disclosure level for a lookup call.
|
|
592
|
+
*
|
|
593
|
+
* Precedence (highest → lowest): an explicit `--detail`/`detail` value, a
|
|
594
|
+
* legacy alias flag set to true (the HIGHEST requested level wins when
|
|
595
|
+
* several are set), the global `config.detail` default, the MCP full-data
|
|
596
|
+
* default, the pack's declared `disclosure.default`, then the base level
|
|
597
|
+
* (the first declared). A config/pack value that is not one of the pack's
|
|
598
|
+
* levels is ignored (a global default may target another pack).
|
|
599
|
+
*
|
|
600
|
+
* `config.detail` deliberately outranks the MCP default: the config value
|
|
601
|
+
* is something the user explicitly chose, while the MCP default is only a
|
|
602
|
+
* surface fallback — a user who configured `detail: summary` gets summary
|
|
603
|
+
* on both surfaces.
|
|
604
|
+
*
|
|
605
|
+
* The MCP default follows the ratified surface contract (see
|
|
606
|
+
* `resolveLookupDetailed`): the CLI opts in while MCP opts out, so an MCP
|
|
607
|
+
* call with no explicit or configured choice resolves to the highest
|
|
608
|
+
* level — agents want full data by default — and an alias explicitly set
|
|
609
|
+
* to false (e.g. `detailed: false`) opts down to the pack default or base
|
|
610
|
+
* level.
|
|
611
|
+
*/
|
|
612
|
+
function resolveDetailLevel(
|
|
613
|
+
params: Record<string, unknown>,
|
|
614
|
+
config: { readonly detail?: string },
|
|
615
|
+
disclosure: StoryPackDisclosure,
|
|
616
|
+
surface: "cli" | "mcp",
|
|
617
|
+
): string {
|
|
618
|
+
const { levels } = disclosure;
|
|
619
|
+
const explicit =
|
|
620
|
+
typeof params.detail === "string" ? params.detail : undefined;
|
|
621
|
+
if (explicit !== undefined && levels.includes(explicit)) {
|
|
622
|
+
return explicit;
|
|
623
|
+
}
|
|
624
|
+
|
|
625
|
+
const aliased = levels
|
|
626
|
+
.filter((level, index) => index > 0 && params[level] === true)
|
|
627
|
+
.at(-1);
|
|
628
|
+
if (aliased !== undefined) {
|
|
629
|
+
return aliased;
|
|
630
|
+
}
|
|
631
|
+
|
|
632
|
+
if (config.detail !== undefined && levels.includes(config.detail)) {
|
|
633
|
+
return config.detail;
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
if (surface === "mcp") {
|
|
637
|
+
const optedDown = levels.some(
|
|
638
|
+
(level, index) => index > 0 && params[level] === false,
|
|
639
|
+
);
|
|
640
|
+
const highest = levels.at(-1);
|
|
641
|
+
if (!optedDown && highest !== undefined) {
|
|
642
|
+
return highest;
|
|
643
|
+
}
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
const packDefault = disclosure.default;
|
|
647
|
+
if (packDefault !== undefined && levels.includes(packDefault)) {
|
|
648
|
+
return packDefault;
|
|
649
|
+
}
|
|
650
|
+
return levels[0] ?? "";
|
|
206
651
|
}
|
|
207
652
|
|
|
208
653
|
async function listEntityNames(
|
|
@@ -218,6 +663,13 @@ async function listEntityNames(
|
|
|
218
663
|
return rows.map((row) => row.name ?? "").filter((name) => name !== "");
|
|
219
664
|
}
|
|
220
665
|
|
|
666
|
+
/** Return a value only when it is a scalar string (expands hold arrays). */
|
|
667
|
+
function scalar(
|
|
668
|
+
value: string | readonly PackRow[] | undefined,
|
|
669
|
+
): string | undefined {
|
|
670
|
+
return typeof value === "string" ? value : undefined;
|
|
671
|
+
}
|
|
672
|
+
|
|
221
673
|
/**
|
|
222
674
|
* Quote a filter value for a shell help example when it is not a bare word.
|
|
223
675
|
*
|
|
@@ -232,8 +684,10 @@ function quoteExampleValue(value: string): string {
|
|
|
232
684
|
/**
|
|
233
685
|
* Project declared pack filters onto kernel story parameters.
|
|
234
686
|
*
|
|
235
|
-
*
|
|
236
|
-
* projects to CLI select choices and an MCP enum schema.
|
|
687
|
+
* A declared value set becomes the parameter's enum, which the kernel
|
|
688
|
+
* projects to CLI select choices and an MCP enum schema. A value-free
|
|
689
|
+
* filter projects as a plain string parameter — its valid values are
|
|
690
|
+
* data-driven and enforced only as a post-query row predicate.
|
|
237
691
|
*/
|
|
238
692
|
function projectFilterParams(
|
|
239
693
|
filters: readonly StoryPackFilter[] | undefined,
|
|
@@ -243,11 +697,28 @@ function projectFilterParams(
|
|
|
243
697
|
type: "string",
|
|
244
698
|
description:
|
|
245
699
|
filter.description ??
|
|
246
|
-
|
|
247
|
-
|
|
700
|
+
(filter.values
|
|
701
|
+
? `Filter by ${filter.variable} (${filter.values.join(", ")})`
|
|
702
|
+
: `Filter by ${filter.variable}`),
|
|
703
|
+
...(filter.values ? { enum: filter.values } : {}),
|
|
248
704
|
}));
|
|
249
705
|
}
|
|
250
706
|
|
|
707
|
+
/**
|
|
708
|
+
* Project a declared free-text search onto the `search` story parameter.
|
|
709
|
+
*/
|
|
710
|
+
function projectSearchParam(search: StoryPackSearch | undefined): StoryParam[] {
|
|
711
|
+
if (!search) return [];
|
|
712
|
+
return [
|
|
713
|
+
{
|
|
714
|
+
name: "search",
|
|
715
|
+
type: "string",
|
|
716
|
+
description:
|
|
717
|
+
search.description ?? `Search in ${search.variables.join(", ")}`,
|
|
718
|
+
},
|
|
719
|
+
];
|
|
720
|
+
}
|
|
721
|
+
|
|
251
722
|
function capitalize(value: string): string {
|
|
252
723
|
return value.charAt(0).toUpperCase() + value.slice(1);
|
|
253
724
|
}
|