@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.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 +67 -15
- package/dist/pragma +0 -0
- package/package.json +16 -17
- package/src/config/defaultConfig.test.ts +33 -0
- package/src/config/defaultConfig.ts +59 -0
- package/src/config/defaults.json +17 -0
- package/src/config/findProjectConfigPath.test.ts +53 -0
- package/src/config/findProjectConfigPath.ts +36 -0
- package/src/config/index.ts +17 -2
- package/src/config/mergeConfigFileUpdate.ts +58 -0
- package/src/config/parseConfigValues.ts +215 -0
- package/src/config/readConfig.test.ts +104 -13
- package/src/config/readConfig.ts +12 -117
- package/src/config/readConfigLayers.test.ts +107 -0
- package/src/config/readConfigLayers.ts +115 -0
- package/src/config/resolveGlobalConfigPath.ts +13 -0
- package/src/config/resolveWriteConfigPath.test.ts +53 -0
- package/src/config/resolveWriteConfigPath.ts +31 -0
- package/src/config/types.ts +88 -2
- package/src/config/writeConfig.ts +25 -43
- package/src/constants.ts +12 -1
- package/src/domains/block/commands/index.ts +2 -1
- package/src/domains/block/commands/list.ts +5 -90
- package/src/domains/block/commands/lookup.ts +8 -167
- package/src/domains/block/commands/sample.ts +55 -0
- package/src/domains/block/formatters/index.ts +2 -1
- package/src/domains/block/formatters/sample.ts +21 -0
- package/src/domains/block/index.ts +3 -3
- package/src/domains/block/mcp/specs.ts +36 -168
- package/src/domains/block/operations/index.ts +2 -1
- package/src/domains/block/operations/lookup.ts +9 -0
- package/src/domains/block/operations/sample.ts +41 -0
- package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
- package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
- package/src/domains/block/stories.test.ts +28 -0
- package/src/domains/block/stories.ts +254 -0
- package/src/domains/config/commands/channel.test.ts +14 -3
- package/src/domains/config/commands/channel.ts +11 -7
- package/src/domains/config/commands/configScope.ts +46 -0
- package/src/domains/config/commands/framework.test.ts +105 -0
- package/src/domains/config/commands/framework.ts +91 -0
- package/src/domains/config/commands/index.ts +2 -0
- package/src/domains/config/commands/show.ts +5 -45
- package/src/domains/config/commands/tier.test.ts +13 -2
- package/src/domains/config/commands/tier.ts +11 -7
- package/src/domains/config/commands/trace.test.ts +165 -0
- package/src/domains/config/commands/trace.ts +83 -0
- package/src/domains/config/formatters/channel.test.ts +20 -6
- package/src/domains/config/formatters/channel.ts +14 -11
- package/src/domains/config/formatters/framework.ts +37 -0
- package/src/domains/config/formatters/index.ts +2 -0
- package/src/domains/config/formatters/show.test.ts +27 -0
- package/src/domains/config/formatters/show.ts +27 -4
- package/src/domains/config/formatters/tier.test.ts +26 -10
- package/src/domains/config/formatters/tier.ts +19 -11
- package/src/domains/config/formatters/trace.ts +28 -0
- package/src/domains/config/index.ts +15 -2
- package/src/domains/config/mcp/specs.ts +33 -53
- package/src/domains/config/operations/index.ts +1 -0
- package/src/domains/config/operations/show.test.ts +18 -0
- package/src/domains/config/operations/show.ts +7 -1
- package/src/domains/config/operations/types.ts +7 -0
- package/src/domains/config/operations/validateFramework.ts +24 -0
- package/src/domains/config/stories.ts +42 -0
- package/src/domains/config/tasks/index.ts +3 -0
- package/src/domains/config/tasks/setChannel.ts +13 -22
- package/src/domains/config/tasks/setFramework.ts +21 -0
- package/src/domains/config/tasks/setTier.ts +10 -23
- package/src/domains/config/tasks/setTrace.ts +20 -0
- package/src/domains/config/tasks/writeConfigField.ts +41 -0
- package/src/domains/create/commands/component.ts +5 -5
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
- package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
- package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
- package/src/domains/doctor/operations/checks/index.ts +1 -0
- package/src/domains/doctor/operations/checks.test.ts +162 -8
- package/src/domains/doctor/operations/runChecks.test.ts +85 -4
- package/src/domains/doctor/operations/runChecks.ts +50 -12
- package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
- package/src/domains/graph/helpers/resolveUri.ts +28 -21
- package/src/domains/graphql/commands/build.test.ts +196 -0
- package/src/domains/graphql/commands/build.ts +117 -0
- package/src/domains/graphql/commands/check.test.ts +141 -0
- package/src/domains/graphql/commands/check.ts +81 -0
- package/src/domains/graphql/commands/index.ts +5 -0
- package/src/domains/graphql/commands/serve.test.ts +40 -0
- package/src/domains/graphql/commands/serve.ts +131 -0
- package/src/domains/graphql/constants.ts +10 -0
- package/src/domains/graphql/formatters/index.ts +3 -0
- package/src/domains/graphql/formatters/report.ts +78 -0
- package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
- package/src/domains/graphql/helpers/gatherSources.ts +58 -0
- package/src/domains/graphql/helpers/index.ts +6 -0
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
- package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
- package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
- package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
- package/src/domains/graphql/index.ts +24 -0
- package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
- package/src/domains/graphql/operations/compileSchema.ts +107 -0
- package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
- package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
- package/src/domains/graphql/operations/index.ts +12 -0
- package/src/domains/graphql/types.ts +29 -0
- package/src/domains/info/operations/collectInfo.test.ts +16 -5
- package/src/domains/info/operations/collectInfo.ts +16 -19
- package/src/domains/llm/capabilities.test.ts +91 -0
- package/src/domains/llm/commands/capabilities.ts +28 -0
- package/src/domains/llm/commands/index.ts +1 -0
- package/src/domains/llm/data/commandReference.ts +90 -18
- package/src/domains/llm/data/decisionTrees.ts +15 -13
- package/src/domains/llm/data/index.ts +2 -0
- package/src/domains/llm/data/toolCatalog.ts +190 -0
- package/src/domains/llm/formatters/capabilities.ts +50 -0
- package/src/domains/llm/formatters/index.ts +1 -0
- package/src/domains/llm/index.ts +3 -2
- package/src/domains/llm/mcp/index.ts +1 -1
- package/src/domains/llm/mcp/specs.ts +59 -52
- package/src/domains/llm/types.ts +40 -0
- package/src/domains/modifier/commands/index.ts +2 -1
- package/src/domains/modifier/commands/list.ts +5 -38
- package/src/domains/modifier/commands/lookup.ts +8 -79
- package/src/domains/modifier/commands/sample.ts +53 -0
- package/src/domains/modifier/formatters/index.ts +2 -1
- package/src/domains/modifier/formatters/sample.ts +15 -0
- package/src/domains/modifier/index.ts +7 -3
- package/src/domains/modifier/mcp/specs.ts +38 -70
- package/src/domains/modifier/operations/index.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +9 -0
- package/src/domains/modifier/operations/sample.ts +32 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
- package/src/domains/modifier/stories.test.ts +20 -0
- package/src/domains/modifier/stories.ts +77 -0
- package/src/domains/ontology/commands/list.ts +5 -58
- package/src/domains/ontology/commands/show.ts +6 -58
- package/src/domains/ontology/formatters/show.ts +7 -1
- package/src/domains/ontology/mcp/specs.ts +7 -84
- package/src/domains/ontology/stories.test.ts +36 -0
- package/src/domains/ontology/stories.ts +108 -0
- package/src/domains/refs/operations/index.ts +1 -1
- package/src/domains/refs/operations/paths.ts +29 -1
- package/src/domains/refs/operations/updateRefs.test.ts +6 -3
- package/src/domains/refs/operations/updateRefs.ts +2 -36
- package/src/domains/setup/commands/skills.test.ts +6 -2
- package/src/domains/setup/commands/skills.ts +6 -2
- package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
- package/src/domains/setup/helpers/runSetupTask.ts +62 -70
- package/src/domains/setup/operations/setupMcp.test.ts +6 -1
- package/src/domains/shared/bootStore.ts +92 -50
- package/src/domains/shared/compactUri.test.ts +11 -0
- package/src/domains/shared/compactUri.ts +10 -5
- package/src/domains/shared/context.ts +2 -2
- package/src/domains/shared/contracts.ts +12 -0
- package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
- package/src/domains/shared/createInteractivePromptSession.ts +236 -0
- package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
- package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
- package/src/domains/shared/loaders/bundledLoader.ts +206 -0
- package/src/domains/shared/loaders/gitLoader.ts +31 -0
- package/src/domains/shared/loaders/index.ts +9 -0
- package/src/domains/shared/loaders/localLoader.ts +87 -0
- package/src/domains/shared/loaders/readPackageDir.ts +147 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
- package/src/domains/shared/lookupMany.test.ts +120 -0
- package/src/domains/shared/lookupMany.ts +43 -3
- package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
- package/src/domains/shared/mergeAndParseRefs.ts +44 -0
- package/src/domains/shared/packages.ts +14 -120
- package/src/domains/shared/pickRandom.ts +22 -0
- package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
- package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
- package/src/domains/shared/runtime.ts +7 -63
- package/src/domains/shared/sampleFormatters.ts +47 -0
- package/src/domains/shared/semanticPackage.ts +112 -0
- package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
- package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
- package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
- package/src/domains/shared/stories/compileLookupTool.ts +96 -0
- package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
- package/src/domains/shared/stories/compileReadCommand.ts +48 -0
- package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
- package/src/domains/shared/stories/compileReadTool.ts +38 -0
- package/src/domains/shared/stories/condense.test.ts +18 -0
- package/src/domains/shared/stories/condense.ts +15 -0
- package/src/domains/shared/stories/index.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.ts +22 -0
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
- package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
- package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
- package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
- package/src/domains/shared/stories/pack/index.ts +28 -0
- package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
- package/src/domains/shared/stories/pack/types.ts +104 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
- package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
- package/src/domains/shared/stories/projectCliParameters.ts +57 -0
- package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
- package/src/domains/shared/stories/projectToolParams.ts +32 -0
- package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
- package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
- package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
- package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
- package/src/domains/shared/stories/types.ts +171 -0
- package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
- package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
- package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
- package/src/domains/shared/suggestions/expandGlob.ts +52 -0
- package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
- package/src/domains/shared/suggestions/index.ts +7 -0
- package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
- package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
- package/src/domains/shared/suggestions/suggestNames.ts +130 -0
- package/src/domains/shared/types/index.ts +1 -0
- package/src/domains/shared/types/runtime.ts +4 -1
- package/src/domains/shared/types/sample.ts +17 -0
- package/src/domains/skill/commands/index.ts +3 -1
- package/src/domains/skill/commands/list.test.ts +6 -2
- package/src/domains/skill/commands/list.ts +5 -63
- package/src/domains/skill/commands/lookup.test.ts +118 -0
- package/src/domains/skill/commands/lookup.ts +14 -0
- package/src/domains/skill/formatters/index.ts +1 -0
- package/src/domains/skill/formatters/lookup.test.ts +46 -0
- package/src/domains/skill/formatters/lookup.ts +57 -0
- package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
- package/src/domains/skill/index.ts +7 -3
- package/src/domains/skill/mcp/specs.ts +10 -29
- package/src/domains/skill/operations/discover.test.ts +61 -30
- package/src/domains/skill/operations/discover.ts +28 -37
- package/src/domains/skill/operations/index.ts +2 -1
- package/src/domains/skill/operations/list.test.ts +15 -7
- package/src/domains/skill/operations/lookup.test.ts +91 -0
- package/src/domains/skill/operations/lookup.ts +70 -0
- package/src/domains/skill/stories.test.ts +43 -0
- package/src/domains/skill/stories.ts +103 -0
- package/src/domains/skill/types.ts +8 -0
- package/src/domains/standard/commands/index.ts +2 -1
- package/src/domains/standard/commands/list.ts +5 -84
- package/src/domains/standard/commands/lookup.ts +8 -93
- package/src/domains/standard/commands/sample.ts +57 -0
- package/src/domains/standard/formatters/index.ts +2 -1
- package/src/domains/standard/formatters/sample.ts +15 -0
- package/src/domains/standard/index.ts +8 -1
- package/src/domains/standard/mcp/specs.ts +44 -152
- package/src/domains/standard/operations/index.ts +2 -1
- package/src/domains/standard/operations/lookup.ts +9 -0
- package/src/domains/standard/operations/sample.ts +35 -0
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
- package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
- package/src/domains/standard/stories.test.ts +24 -0
- package/src/domains/standard/stories.ts +151 -0
- package/src/domains/tier/commands/list.ts +5 -55
- package/src/domains/tier/mcp/specs.ts +5 -31
- package/src/domains/tier/stories.test.ts +22 -0
- package/src/domains/tier/stories.ts +52 -0
- package/src/domains/token/commands/index.ts +2 -1
- package/src/domains/token/commands/sample.ts +53 -0
- package/src/domains/token/formatters/index.ts +2 -1
- package/src/domains/token/formatters/sample.ts +16 -0
- package/src/domains/token/index.ts +8 -1
- package/src/domains/token/mcp/specs.ts +52 -2
- package/src/domains/token/operations/index.ts +2 -1
- package/src/domains/token/operations/lookup.ts +9 -0
- package/src/domains/token/operations/sample.ts +32 -0
- package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
- package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
- package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
- package/src/domains/trace/commands/clear.ts +71 -0
- package/src/domains/trace/commands/index.ts +5 -0
- package/src/domains/trace/commands/sessions.ts +32 -0
- package/src/domains/trace/commands/show.ts +145 -0
- package/src/domains/trace/formatters/index.ts +9 -0
- package/src/domains/trace/formatters/sessions.ts +80 -0
- package/src/domains/trace/formatters/show.ts +122 -0
- package/src/domains/trace/index.ts +17 -0
- package/src/domains/trace/operations/index.ts +8 -0
- package/src/domains/trace/operations/listSessions.test.ts +55 -0
- package/src/domains/trace/operations/listSessions.ts +52 -0
- package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
- package/src/domains/trace/operations/readTraceLog.ts +104 -0
- package/src/domains/trace/tracePlugin.test.ts +171 -0
- package/src/domains/trace/tracePlugin.ts +159 -0
- package/src/domains/trace/types.ts +65 -0
- package/src/error/PragmaError.ts +15 -2
- package/src/error/types.ts +15 -1
- package/src/mcp/error/serializeErrorPayload.ts +7 -0
- package/src/mcp/error/types.ts +8 -0
- package/src/mcp/registerResources.test.ts +405 -75
- package/src/mcp/registerResources.ts +5 -299
- package/src/mcp/resources/buildGraphIndex.ts +167 -0
- package/src/mcp/resources/buildResourceList.test.ts +97 -0
- package/src/mcp/resources/buildResourceList.ts +176 -0
- package/src/mcp/resources/classifyEntity.test.ts +40 -0
- package/src/mcp/resources/classifyEntity.ts +27 -0
- package/src/mcp/resources/constants.ts +105 -0
- package/src/mcp/resources/displayPredicates.ts +62 -0
- package/src/mcp/resources/pickFirstValue.test.ts +29 -0
- package/src/mcp/resources/pickFirstValue.ts +26 -0
- package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
- package/src/mcp/resources/pickPrimaryType.ts +26 -0
- package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
- package/src/mcp/resources/rankUriCompletions.ts +64 -0
- package/src/mcp/resources/readEntity.ts +169 -0
- package/src/mcp/resources/registerResources.ts +126 -0
- package/src/mcp/resources/types.ts +128 -0
- package/src/mcp/tools/index.ts +9 -0
- package/src/mcp/tools/registerTools.test.ts +84 -12
- package/src/mcp/types.ts +4 -0
- package/src/mcp/utils/wrapTool.test.ts +1 -0
- package/src/pipeline/collectCommands.ts +15 -2
- package/src/pipeline/renderError.ts +15 -0
- package/src/pipeline/resolveCommandKind.ts +8 -1
- package/src/pipeline/runCli.ts +51 -6
- package/src/testing/createTestMcpClient.ts +5 -1
- package/src/testing/graphqlFixtures.ts +72 -0
- package/src/testing/index.ts +17 -0
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
- package/src/testing/integration/executor-execution.test.ts +101 -0
- package/src/testing/integration/executor-golden.test.ts +145 -0
- package/src/testing/integration/mcp-surface.test.ts +11 -5
- package/src/testing/integration/parity.test.ts +17 -12
- package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
- package/src/testing/setupXdgIsolation.ts +15 -0
- package/src/testing/standardParityFixtures.ts +92 -0
- package/src/testing/storyFixtures.ts +63 -0
- package/src/pipeline/runInteractiveCommand.ts +0 -198
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Declarative story-pack definitions — read stories as data.
|
|
3
|
+
*
|
|
4
|
+
* A story pack maps a noun to preferred queries: a SPARQL SELECT for the
|
|
5
|
+
* list story and a generated, injection-safe lookup. Definitions are
|
|
6
|
+
* declared in `pragma.config.json` (`stories`) or shipped by semantic
|
|
7
|
+
* packages (`stories/*.json`) and compiled through the read-story kernel
|
|
8
|
+
* into CLI commands and MCP tools at boot.
|
|
9
|
+
*
|
|
10
|
+
* @experimental The story-pack format (v0) is experimental: field names,
|
|
11
|
+
* lookup generation, and rendering semantics may change while GraphQL
|
|
12
|
+
* document queries and SHACL-derived defaults are designed.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/** A list column: a SELECT variable to display. */
|
|
16
|
+
export interface StoryPackColumn {
|
|
17
|
+
/** SELECT variable name (without `?`). */
|
|
18
|
+
readonly field: string;
|
|
19
|
+
/** Column heading (defaults to the field name). */
|
|
20
|
+
readonly label?: string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/** A looked-up value: an output name bound to a property of the entity. */
|
|
24
|
+
export interface StoryPackField {
|
|
25
|
+
/** Output field name on the looked-up entity. */
|
|
26
|
+
readonly name: string;
|
|
27
|
+
/** Property to read — a prefixed name (`ex:category`) or absolute IRI. */
|
|
28
|
+
readonly property: string;
|
|
29
|
+
/** Display label (defaults to the field name). */
|
|
30
|
+
readonly label?: string;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/** A long-form lookup section. */
|
|
34
|
+
export interface StoryPackSection extends StoryPackField {
|
|
35
|
+
/** Rendering kind (v0 supports inline fields and code blocks). */
|
|
36
|
+
readonly kind?: "field" | "code";
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* A declarative list filter: a CLI/MCP parameter constraining one SELECT
|
|
41
|
+
* variable to a declared value set.
|
|
42
|
+
*
|
|
43
|
+
* Filters are row predicates applied AFTER the author query runs — the
|
|
44
|
+
* query text is never modified, so filter input cannot inject SPARQL,
|
|
45
|
+
* and the author's ORDER BY is preserved.
|
|
46
|
+
*/
|
|
47
|
+
export interface StoryPackFilter {
|
|
48
|
+
/**
|
|
49
|
+
* Parameter name — a single lowercase word, exposed as `--param` on the
|
|
50
|
+
* CLI and as the parameter key on the MCP tool.
|
|
51
|
+
*/
|
|
52
|
+
readonly param: string;
|
|
53
|
+
/** SELECT variable the filter constrains (without `?`). */
|
|
54
|
+
readonly variable: string;
|
|
55
|
+
/** Allowed values; anything else is rejected with INVALID_INPUT. */
|
|
56
|
+
readonly values: readonly string[];
|
|
57
|
+
/** Help text (defaults to a generated description). */
|
|
58
|
+
readonly description?: string;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/** The list half of a story pack. */
|
|
62
|
+
export interface StoryPackList {
|
|
63
|
+
/** SPARQL SELECT producing one row per item. */
|
|
64
|
+
readonly query: string;
|
|
65
|
+
/** Columns to render, referencing SELECT variables. */
|
|
66
|
+
readonly columns: readonly StoryPackColumn[];
|
|
67
|
+
/** Declarative filters projected to CLI flags and MCP parameters. */
|
|
68
|
+
readonly filters?: readonly StoryPackFilter[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* The lookup half of a story pack. The query is generated from `type` and
|
|
73
|
+
* `by` — user-supplied names are escaped by the generator, never
|
|
74
|
+
* interpolated by the author.
|
|
75
|
+
*/
|
|
76
|
+
export interface StoryPackLookup {
|
|
77
|
+
/** Property whose value names the entity — prefixed name or IRI. */
|
|
78
|
+
readonly by: string;
|
|
79
|
+
/** Optional class constraint — prefixed name or IRI. */
|
|
80
|
+
readonly type?: string;
|
|
81
|
+
/** Inline fields shown under the entity title. */
|
|
82
|
+
readonly fields?: readonly StoryPackField[];
|
|
83
|
+
/** Long-form sections shown after the fields. */
|
|
84
|
+
readonly sections?: readonly StoryPackSection[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
/** One declarative read story: a noun with its preferred queries. */
|
|
88
|
+
export interface StoryPackDefinition {
|
|
89
|
+
/** Command noun (kebab-case), e.g. `"recipe"` → `pragma recipe list`. */
|
|
90
|
+
readonly noun: string;
|
|
91
|
+
/** CLI description for the list command. */
|
|
92
|
+
readonly description?: string;
|
|
93
|
+
/** MCP tool description (defaults to the CLI description). */
|
|
94
|
+
readonly toolDescription?: string;
|
|
95
|
+
readonly list: StoryPackList;
|
|
96
|
+
readonly lookup?: StoryPackLookup;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** A story definition with the source it came from, for diagnostics. */
|
|
100
|
+
export interface StoryPackSource {
|
|
101
|
+
/** Where the definition was declared (config path or package file). */
|
|
102
|
+
readonly source: string;
|
|
103
|
+
readonly definition: StoryPackDefinition;
|
|
104
|
+
}
|
|
@@ -0,0 +1,240 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { PragmaError } from "#error";
|
|
3
|
+
import { RECIPE_STORY } from "#testing";
|
|
4
|
+
import validateStoryPackDefinition from "./validateStoryPackDefinition.js";
|
|
5
|
+
|
|
6
|
+
describe("validateStoryPackDefinition", () => {
|
|
7
|
+
it("accepts a valid definition and round-trips it", () => {
|
|
8
|
+
const validated = validateStoryPackDefinition(
|
|
9
|
+
JSON.parse(JSON.stringify(RECIPE_STORY)),
|
|
10
|
+
"test",
|
|
11
|
+
);
|
|
12
|
+
expect(validated).toEqual(RECIPE_STORY);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("rejects a non-kebab noun", () => {
|
|
16
|
+
expect(() =>
|
|
17
|
+
validateStoryPackDefinition({ ...RECIPE_STORY, noun: "Recipe" }, "test"),
|
|
18
|
+
).toThrow(PragmaError);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("rejects a list without a SELECT query", () => {
|
|
22
|
+
expect(() =>
|
|
23
|
+
validateStoryPackDefinition(
|
|
24
|
+
{
|
|
25
|
+
...RECIPE_STORY,
|
|
26
|
+
list: { ...RECIPE_STORY.list, query: "DELETE WHERE { ?s ?p ?o }" },
|
|
27
|
+
},
|
|
28
|
+
"test",
|
|
29
|
+
),
|
|
30
|
+
).toThrow(/SELECT/);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("rejects empty columns", () => {
|
|
34
|
+
expect(() =>
|
|
35
|
+
validateStoryPackDefinition(
|
|
36
|
+
{ ...RECIPE_STORY, list: { ...RECIPE_STORY.list, columns: [] } },
|
|
37
|
+
"test",
|
|
38
|
+
),
|
|
39
|
+
).toThrow(/columns/);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("rejects a lookup term that is neither prefixed nor an IRI", () => {
|
|
43
|
+
expect(() =>
|
|
44
|
+
validateStoryPackDefinition(
|
|
45
|
+
{
|
|
46
|
+
...RECIPE_STORY,
|
|
47
|
+
lookup: { by: "just a name" },
|
|
48
|
+
},
|
|
49
|
+
"test",
|
|
50
|
+
),
|
|
51
|
+
).toThrow(/prefixed name/);
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it("rejects an unknown section kind", () => {
|
|
55
|
+
expect(() =>
|
|
56
|
+
validateStoryPackDefinition(
|
|
57
|
+
{
|
|
58
|
+
...RECIPE_STORY,
|
|
59
|
+
lookup: {
|
|
60
|
+
by: "ex:name",
|
|
61
|
+
sections: [{ name: "x", property: "ex:x", kind: "table" }],
|
|
62
|
+
},
|
|
63
|
+
},
|
|
64
|
+
"test",
|
|
65
|
+
),
|
|
66
|
+
).toThrow(/kind/);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("names the source in errors", () => {
|
|
70
|
+
try {
|
|
71
|
+
validateStoryPackDefinition({}, "/pkg/stories/broken.json");
|
|
72
|
+
expect.unreachable("should have thrown");
|
|
73
|
+
} catch (error) {
|
|
74
|
+
expect(error).toBeInstanceOf(PragmaError);
|
|
75
|
+
expect((error as PragmaError).message).toContain(
|
|
76
|
+
"/pkg/stories/broken.json",
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
describe("validateStoryPackDefinition — term and query hardening", () => {
|
|
83
|
+
function withLookup(lookup: Record<string, unknown>): unknown {
|
|
84
|
+
const base = JSON.parse(JSON.stringify(RECIPE_STORY)) as Record<
|
|
85
|
+
string,
|
|
86
|
+
unknown
|
|
87
|
+
>;
|
|
88
|
+
return { ...base, lookup: { ...(base.lookup as object), ...lookup } };
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
it("rejects a slashed prefixed name in class position", () => {
|
|
92
|
+
expect(() =>
|
|
93
|
+
validateStoryPackDefinition(
|
|
94
|
+
withLookup({ type: "ex:Recipe/Sub" }),
|
|
95
|
+
"test",
|
|
96
|
+
),
|
|
97
|
+
).toThrow(/must be a prefixed name/);
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
it("accepts a property path in predicate position", () => {
|
|
101
|
+
const validated = validateStoryPackDefinition(
|
|
102
|
+
withLookup({ by: "ex:hasName/ex:value" }),
|
|
103
|
+
"test",
|
|
104
|
+
);
|
|
105
|
+
expect(validated.lookup?.by).toBe("ex:hasName/ex:value");
|
|
106
|
+
});
|
|
107
|
+
|
|
108
|
+
it("rejects a property path with a malformed segment", () => {
|
|
109
|
+
expect(() =>
|
|
110
|
+
validateStoryPackDefinition(
|
|
111
|
+
withLookup({ by: "ex:hasName/not a name" }),
|
|
112
|
+
"test",
|
|
113
|
+
),
|
|
114
|
+
).toThrow(/property path segment/);
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
it("rejects an IRI that cannot embed in SPARQL", () => {
|
|
118
|
+
expect(() =>
|
|
119
|
+
validateStoryPackDefinition(
|
|
120
|
+
withLookup({ type: "https://example.org/a b>c" }),
|
|
121
|
+
"test",
|
|
122
|
+
),
|
|
123
|
+
).toThrow(/not a valid IRI/);
|
|
124
|
+
});
|
|
125
|
+
|
|
126
|
+
it("rejects a PREFIX-led non-SELECT list query", () => {
|
|
127
|
+
const base = JSON.parse(JSON.stringify(RECIPE_STORY)) as Record<
|
|
128
|
+
string,
|
|
129
|
+
unknown
|
|
130
|
+
>;
|
|
131
|
+
const list = {
|
|
132
|
+
...(base.list as Record<string, unknown>),
|
|
133
|
+
query:
|
|
134
|
+
"PREFIX ex: <http://example.org/> CONSTRUCT { ?s ?p ?o } WHERE { ?s ?p ?o }",
|
|
135
|
+
};
|
|
136
|
+
expect(() =>
|
|
137
|
+
validateStoryPackDefinition({ ...base, list }, "test"),
|
|
138
|
+
).toThrow(/must be a SPARQL SELECT/);
|
|
139
|
+
});
|
|
140
|
+
});
|
|
141
|
+
|
|
142
|
+
describe("validateStoryPackDefinition — list filters", () => {
|
|
143
|
+
const base = JSON.parse(JSON.stringify(RECIPE_STORY)) as Record<
|
|
144
|
+
string,
|
|
145
|
+
unknown
|
|
146
|
+
>;
|
|
147
|
+
|
|
148
|
+
function withFilters(filters: unknown): unknown {
|
|
149
|
+
const list = { ...(base.list as Record<string, unknown>), filters };
|
|
150
|
+
return { ...base, list };
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
it("accepts declared filters", () => {
|
|
154
|
+
const validated = validateStoryPackDefinition(
|
|
155
|
+
withFilters([
|
|
156
|
+
{ param: "category", variable: "category", values: ["soup"] },
|
|
157
|
+
]),
|
|
158
|
+
"test",
|
|
159
|
+
);
|
|
160
|
+
expect(validated.list.filters?.at(0)?.param).toBe("category");
|
|
161
|
+
});
|
|
162
|
+
|
|
163
|
+
it("rejects an empty filters array", () => {
|
|
164
|
+
expect(() => validateStoryPackDefinition(withFilters([]), "test")).toThrow(
|
|
165
|
+
/list.filters/,
|
|
166
|
+
);
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
it("rejects reserved parameter names", () => {
|
|
170
|
+
expect(() =>
|
|
171
|
+
validateStoryPackDefinition(
|
|
172
|
+
withFilters([{ param: "format", variable: "category", values: ["x"] }]),
|
|
173
|
+
"test",
|
|
174
|
+
),
|
|
175
|
+
).toThrow(/reserved/);
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
it("rejects the MCP-appended condensed parameter name", () => {
|
|
179
|
+
expect(() =>
|
|
180
|
+
validateStoryPackDefinition(
|
|
181
|
+
withFilters([
|
|
182
|
+
{ param: "condensed", variable: "category", values: ["x"] },
|
|
183
|
+
]),
|
|
184
|
+
"test",
|
|
185
|
+
),
|
|
186
|
+
).toThrow(/reserved/);
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
it("rejects a hyphenated parameter name that breaks CLI readback", () => {
|
|
190
|
+
expect(() =>
|
|
191
|
+
validateStoryPackDefinition(
|
|
192
|
+
withFilters([
|
|
193
|
+
{ param: "meal-type", variable: "category", values: ["x"] },
|
|
194
|
+
]),
|
|
195
|
+
"test",
|
|
196
|
+
),
|
|
197
|
+
).toThrow(/single lowercase word/);
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("rejects duplicate parameter names", () => {
|
|
201
|
+
expect(() =>
|
|
202
|
+
validateStoryPackDefinition(
|
|
203
|
+
withFilters([
|
|
204
|
+
{ param: "category", variable: "category", values: ["x"] },
|
|
205
|
+
{ param: "category", variable: "name", values: ["y"] },
|
|
206
|
+
]),
|
|
207
|
+
"test",
|
|
208
|
+
),
|
|
209
|
+
).toThrow(/duplicate filter param/);
|
|
210
|
+
});
|
|
211
|
+
|
|
212
|
+
it("rejects a filter variable the query never mentions", () => {
|
|
213
|
+
expect(() =>
|
|
214
|
+
validateStoryPackDefinition(
|
|
215
|
+
withFilters([{ param: "season", variable: "season", values: ["x"] }]),
|
|
216
|
+
"test",
|
|
217
|
+
),
|
|
218
|
+
).toThrow(/does not appear in "list.query"/);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
it("rejects case-insensitive duplicate values", () => {
|
|
222
|
+
expect(() =>
|
|
223
|
+
validateStoryPackDefinition(
|
|
224
|
+
withFilters([
|
|
225
|
+
{ param: "category", variable: "category", values: ["Soup", "soup"] },
|
|
226
|
+
]),
|
|
227
|
+
"test",
|
|
228
|
+
),
|
|
229
|
+
).toThrow(/duplicate value/);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
it("rejects empty values arrays", () => {
|
|
233
|
+
expect(() =>
|
|
234
|
+
validateStoryPackDefinition(
|
|
235
|
+
withFilters([{ param: "category", variable: "category", values: [] }]),
|
|
236
|
+
"test",
|
|
237
|
+
),
|
|
238
|
+
).toThrow(/values/);
|
|
239
|
+
});
|
|
240
|
+
});
|