@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
|
@@ -1,303 +1,9 @@
|
|
|
1
|
-
import type { Store } from "@canonical/ke";
|
|
2
|
-
import type { McpServer } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
3
|
-
import { ResourceTemplate } from "@modelcontextprotocol/sdk/server/mcp.js";
|
|
4
|
-
import { PragmaError } from "#error";
|
|
5
|
-
import { PROPERTY_MAP } from "../constants.js";
|
|
6
|
-
import resolveUri from "../domains/graph/helpers/resolveUri.js";
|
|
7
|
-
import { buildQuery } from "../domains/shared/buildQuery.js";
|
|
8
|
-
import compactUri from "../domains/shared/compactUri.js";
|
|
9
|
-
import type { PragmaRuntime } from "../domains/shared/runtime.js";
|
|
10
|
-
|
|
11
|
-
// =============================================================================
|
|
12
|
-
// Types
|
|
13
|
-
// =============================================================================
|
|
14
|
-
|
|
15
|
-
interface PropertyValueLiteral {
|
|
16
|
-
readonly type: "literal";
|
|
17
|
-
readonly value: string;
|
|
18
|
-
readonly datatype?: string;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
interface PropertyValueUri {
|
|
22
|
-
readonly type: "uri";
|
|
23
|
-
readonly uri: string;
|
|
24
|
-
readonly prefixed: string;
|
|
25
|
-
readonly label: string | null;
|
|
26
|
-
}
|
|
27
|
-
|
|
28
|
-
type PropertyValue = PropertyValueLiteral | PropertyValueUri;
|
|
29
|
-
|
|
30
|
-
interface PropertyGroup {
|
|
31
|
-
readonly predicate: string;
|
|
32
|
-
readonly values: PropertyValue[];
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
interface ResourceEntity {
|
|
36
|
-
readonly uri: string;
|
|
37
|
-
readonly prefixed: string;
|
|
38
|
-
readonly types: string[];
|
|
39
|
-
readonly label: string | null;
|
|
40
|
-
readonly description: string | null;
|
|
41
|
-
readonly properties: PropertyGroup[];
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
// =============================================================================
|
|
45
|
-
// Helpers
|
|
46
|
-
// =============================================================================
|
|
47
|
-
|
|
48
|
-
const RDF_TYPE = "http://www.w3.org/1999/02/22-rdf-syntax-ns#type";
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Determine the namespace prefix for a full URI.
|
|
52
|
-
*/
|
|
53
|
-
function findPrefix(
|
|
54
|
-
fullUri: string,
|
|
55
|
-
prefixes: Readonly<Record<string, string>>,
|
|
56
|
-
): string | null {
|
|
57
|
-
for (const [prefix, namespace] of Object.entries(prefixes)) {
|
|
58
|
-
if (fullUri.startsWith(namespace)) return prefix;
|
|
59
|
-
}
|
|
60
|
-
return null;
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
/**
|
|
64
|
-
* Fetch a single property value for a URI using the PROPERTY_MAP.
|
|
65
|
-
*/
|
|
66
|
-
async function fetchProperty(
|
|
67
|
-
store: Store,
|
|
68
|
-
fullUri: string,
|
|
69
|
-
prefixes: Readonly<Record<string, string>>,
|
|
70
|
-
field: "label" | "description",
|
|
71
|
-
): Promise<string | null> {
|
|
72
|
-
const prefix = findPrefix(fullUri, prefixes);
|
|
73
|
-
if (!prefix) return null;
|
|
74
|
-
|
|
75
|
-
const propUri = PROPERTY_MAP[prefix]?.[field];
|
|
76
|
-
if (!propUri) return null;
|
|
77
|
-
|
|
78
|
-
const result = await store.query(
|
|
79
|
-
buildQuery(`
|
|
80
|
-
SELECT ?val
|
|
81
|
-
WHERE { <${fullUri}> <${propUri}> ?val }
|
|
82
|
-
LIMIT 1
|
|
83
|
-
`),
|
|
84
|
-
);
|
|
85
|
-
|
|
86
|
-
if (result.type === "select" && result.bindings.length > 0) {
|
|
87
|
-
return result.bindings[0]?.val ?? null;
|
|
88
|
-
}
|
|
89
|
-
return null;
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Fetch the human-readable label for a URI using the PROPERTY_MAP.
|
|
94
|
-
*/
|
|
95
|
-
async function fetchLabel(
|
|
96
|
-
store: Store,
|
|
97
|
-
fullUri: string,
|
|
98
|
-
prefixes: Readonly<Record<string, string>>,
|
|
99
|
-
): Promise<string | null> {
|
|
100
|
-
return fetchProperty(store, fullUri, prefixes, "label");
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
/**
|
|
104
|
-
* Fetch the human-readable description for a URI using the PROPERTY_MAP.
|
|
105
|
-
*/
|
|
106
|
-
async function fetchDescription(
|
|
107
|
-
store: Store,
|
|
108
|
-
fullUri: string,
|
|
109
|
-
prefixes: Readonly<Record<string, string>>,
|
|
110
|
-
): Promise<string | null> {
|
|
111
|
-
return fetchProperty(store, fullUri, prefixes, "description");
|
|
112
|
-
}
|
|
113
|
-
|
|
114
1
|
/**
|
|
115
|
-
*
|
|
116
|
-
* URIs start with http:// or https:// or are prefixed; literals are everything else.
|
|
117
|
-
*/
|
|
118
|
-
function isUri(value: string): boolean {
|
|
119
|
-
return (
|
|
120
|
-
value.startsWith("http://") ||
|
|
121
|
-
value.startsWith("https://") ||
|
|
122
|
-
value.startsWith("urn:")
|
|
123
|
-
);
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
/**
|
|
127
|
-
* Enumerate all distinct subject URIs in the graph.
|
|
128
|
-
*/
|
|
129
|
-
async function listAllSubjects(store: Store): Promise<string[]> {
|
|
130
|
-
const result = await store.query(
|
|
131
|
-
buildQuery("SELECT DISTINCT ?s WHERE { ?s ?p ?o } ORDER BY ?s"),
|
|
132
|
-
);
|
|
133
|
-
|
|
134
|
-
if (result.type !== "select") return [];
|
|
135
|
-
return result.bindings.map((b) => b.s ?? "").filter((s) => isUri(s));
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
/**
|
|
139
|
-
* Read a single entity: all its triples with level-1 object summaries.
|
|
140
|
-
*/
|
|
141
|
-
async function readEntity(
|
|
142
|
-
store: Store,
|
|
143
|
-
fullUri: string,
|
|
144
|
-
prefixes: Readonly<Record<string, string>>,
|
|
145
|
-
): Promise<ResourceEntity> {
|
|
146
|
-
const result = await store.query(
|
|
147
|
-
buildQuery(`
|
|
148
|
-
SELECT ?p ?o
|
|
149
|
-
WHERE { <${fullUri}> ?p ?o }
|
|
150
|
-
ORDER BY ?p ?o
|
|
151
|
-
`),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
if (result.type !== "select" || result.bindings.length === 0) {
|
|
155
|
-
throw PragmaError.notFound("entity", compactUri(fullUri, prefixes), {
|
|
156
|
-
recovery: {
|
|
157
|
-
message:
|
|
158
|
-
"Check the URI is correct and run a SPARQL query to find valid URIs.",
|
|
159
|
-
cli: "pragma graph query 'SELECT ?s WHERE { ?s ?p ?o } LIMIT 10'",
|
|
160
|
-
mcp: { tool: "graph_query" },
|
|
161
|
-
},
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
const types: string[] = [];
|
|
166
|
-
const groupMap = new Map<string, PropertyValue[]>();
|
|
167
|
-
|
|
168
|
-
for (const b of result.bindings) {
|
|
169
|
-
const predicate = b.p ?? "";
|
|
170
|
-
const object = b.o ?? "";
|
|
171
|
-
|
|
172
|
-
if (predicate === RDF_TYPE) {
|
|
173
|
-
types.push(compactUri(object, prefixes));
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
const compactPred = compactUri(predicate, prefixes);
|
|
178
|
-
const existing = groupMap.get(compactPred) ?? [];
|
|
179
|
-
|
|
180
|
-
if (isUri(object)) {
|
|
181
|
-
const label = await fetchLabel(store, object, prefixes);
|
|
182
|
-
existing.push({
|
|
183
|
-
type: "uri",
|
|
184
|
-
uri: object,
|
|
185
|
-
prefixed: compactUri(object, prefixes),
|
|
186
|
-
label,
|
|
187
|
-
});
|
|
188
|
-
} else {
|
|
189
|
-
existing.push({ type: "literal", value: object });
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
groupMap.set(compactPred, existing);
|
|
193
|
-
}
|
|
194
|
-
|
|
195
|
-
const [label, description] = await Promise.all([
|
|
196
|
-
fetchLabel(store, fullUri, prefixes),
|
|
197
|
-
fetchDescription(store, fullUri, prefixes),
|
|
198
|
-
]);
|
|
199
|
-
|
|
200
|
-
const properties: PropertyGroup[] = [...groupMap.entries()].map(
|
|
201
|
-
([predicate, values]) => ({ predicate, values }),
|
|
202
|
-
);
|
|
203
|
-
|
|
204
|
-
return {
|
|
205
|
-
uri: fullUri,
|
|
206
|
-
prefixed: compactUri(fullUri, prefixes),
|
|
207
|
-
types,
|
|
208
|
-
label,
|
|
209
|
-
description,
|
|
210
|
-
properties,
|
|
211
|
-
};
|
|
212
|
-
}
|
|
213
|
-
|
|
214
|
-
// =============================================================================
|
|
215
|
-
// Registration
|
|
216
|
-
// =============================================================================
|
|
217
|
-
|
|
218
|
-
/**
|
|
219
|
-
* Register graph-driven MCP resources on the server.
|
|
2
|
+
* Entry point for the MCP resource surface.
|
|
220
3
|
*
|
|
221
|
-
*
|
|
222
|
-
*
|
|
223
|
-
*
|
|
224
|
-
*
|
|
225
|
-
* @param server - The MCP server to register resources on.
|
|
226
|
-
* @param runtime - The pragma runtime providing the ke store and prefixes.
|
|
227
|
-
*
|
|
228
|
-
* @note Impure
|
|
4
|
+
* The implementation lives in `./resources/`, split across focused modules
|
|
5
|
+
* (index build, listing, autocomplete, reads). This file re-exports the
|
|
6
|
+
* registrar so existing importers keep a stable path.
|
|
229
7
|
*/
|
|
230
|
-
export default function registerResources(
|
|
231
|
-
server: McpServer,
|
|
232
|
-
runtime: PragmaRuntime,
|
|
233
|
-
): void {
|
|
234
|
-
const { store } = runtime;
|
|
235
|
-
const prefixes = store.prefixes;
|
|
236
|
-
|
|
237
|
-
const template = new ResourceTemplate("pragma:{+uri}", {
|
|
238
|
-
list: async () => {
|
|
239
|
-
const subjects = await listAllSubjects(store);
|
|
240
|
-
const resources = await Promise.all(
|
|
241
|
-
subjects.map(async (fullUri) => {
|
|
242
|
-
const prefixed = compactUri(fullUri, prefixes);
|
|
243
|
-
const label = await fetchLabel(store, fullUri, prefixes);
|
|
244
|
-
return {
|
|
245
|
-
uri: `pragma:${prefixed}`,
|
|
246
|
-
name: label ?? prefixed,
|
|
247
|
-
mimeType: "application/json" as const,
|
|
248
|
-
};
|
|
249
|
-
}),
|
|
250
|
-
);
|
|
251
|
-
return { resources };
|
|
252
|
-
},
|
|
253
|
-
complete: {
|
|
254
|
-
uri: async (value) => {
|
|
255
|
-
const subjects = await listAllSubjects(store);
|
|
256
|
-
const compacted = subjects.map((s) => compactUri(s, prefixes));
|
|
257
|
-
return compacted.filter((p) =>
|
|
258
|
-
p.toLowerCase().startsWith(value.toLowerCase()),
|
|
259
|
-
);
|
|
260
|
-
},
|
|
261
|
-
},
|
|
262
|
-
});
|
|
263
8
|
|
|
264
|
-
|
|
265
|
-
"graph-entity",
|
|
266
|
-
template,
|
|
267
|
-
{
|
|
268
|
-
description:
|
|
269
|
-
"An entity in the pragma knowledge graph. Returns all properties with level-1 relation summaries.",
|
|
270
|
-
mimeType: "application/json",
|
|
271
|
-
},
|
|
272
|
-
async (url, variables) => {
|
|
273
|
-
try {
|
|
274
|
-
const prefixedUri = variables.uri as string;
|
|
275
|
-
const fullUri = resolveUri(prefixedUri, prefixes);
|
|
276
|
-
const entity = await readEntity(store, fullUri, prefixes);
|
|
277
|
-
return {
|
|
278
|
-
contents: [
|
|
279
|
-
{
|
|
280
|
-
uri: url.href,
|
|
281
|
-
mimeType: "application/json",
|
|
282
|
-
text: JSON.stringify(entity, null, 2),
|
|
283
|
-
},
|
|
284
|
-
],
|
|
285
|
-
};
|
|
286
|
-
} catch (error) {
|
|
287
|
-
const message =
|
|
288
|
-
error instanceof PragmaError
|
|
289
|
-
? JSON.stringify({ code: error.code, message: error.message })
|
|
290
|
-
: String(error);
|
|
291
|
-
return {
|
|
292
|
-
contents: [
|
|
293
|
-
{
|
|
294
|
-
uri: url.href,
|
|
295
|
-
mimeType: "text/plain",
|
|
296
|
-
text: message,
|
|
297
|
-
},
|
|
298
|
-
],
|
|
299
|
-
};
|
|
300
|
-
}
|
|
301
|
-
},
|
|
302
|
-
);
|
|
303
|
-
}
|
|
9
|
+
export { default } from "./resources/registerResources.js";
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import type { Store } from "@canonical/ke";
|
|
2
|
+
import findNamespace from "../../domains/ontology/helpers/findNamespace.js";
|
|
3
|
+
import { buildQuery } from "../../domains/shared/buildQuery.js";
|
|
4
|
+
import compactUri from "../../domains/shared/compactUri.js";
|
|
5
|
+
import extractLocalName from "../../domains/shared/extractLocalName.js";
|
|
6
|
+
import classifyEntity from "./classifyEntity.js";
|
|
7
|
+
import { RDF_TYPE } from "./constants.js";
|
|
8
|
+
import {
|
|
9
|
+
collectDescriptionPredicates,
|
|
10
|
+
collectLabelPredicates,
|
|
11
|
+
resolveDescriptionPredicates,
|
|
12
|
+
resolveLabelPredicates,
|
|
13
|
+
} from "./displayPredicates.js";
|
|
14
|
+
import pickFirstValue from "./pickFirstValue.js";
|
|
15
|
+
import pickPrimaryType from "./pickPrimaryType.js";
|
|
16
|
+
import type { GraphEntity, GraphIndex } from "./types.js";
|
|
17
|
+
|
|
18
|
+
/** A per-subject map of predicate URI → asserted lexical values. */
|
|
19
|
+
type ValuesBySubject = Map<string, Map<string, string[]>>;
|
|
20
|
+
|
|
21
|
+
/** Wrap full URIs as a SPARQL `VALUES` list body. */
|
|
22
|
+
function toValuesList(uris: readonly string[]): string {
|
|
23
|
+
return [...new Set(uris)].map((uri) => `<${uri}>`).join(" ");
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Run one `?s ?p ?v` bulk query restricted to the given predicates and
|
|
28
|
+
* collect values per named-node subject. Blank-node subjects are skipped —
|
|
29
|
+
* they are never listed as resources.
|
|
30
|
+
*
|
|
31
|
+
* @note Queries ke store
|
|
32
|
+
*/
|
|
33
|
+
async function fetchValuesBySubject(
|
|
34
|
+
store: Store,
|
|
35
|
+
predicates: readonly string[],
|
|
36
|
+
): Promise<ValuesBySubject> {
|
|
37
|
+
const bySubject: ValuesBySubject = new Map();
|
|
38
|
+
if (predicates.length === 0) return bySubject;
|
|
39
|
+
|
|
40
|
+
const result = await store.query(
|
|
41
|
+
buildQuery(
|
|
42
|
+
`SELECT ?s ?p ?v WHERE { ?s ?p ?v . VALUES ?p { ${toValuesList(predicates)} } }`,
|
|
43
|
+
),
|
|
44
|
+
);
|
|
45
|
+
if (result.type !== "select") return bySubject;
|
|
46
|
+
|
|
47
|
+
for (const term of result.termBindings) {
|
|
48
|
+
if (term.s?.termType !== "NamedNode") continue;
|
|
49
|
+
const subject = term.s.value;
|
|
50
|
+
const predicate = term.p?.value ?? "";
|
|
51
|
+
const value = term.v?.value ?? "";
|
|
52
|
+
const byPredicate = bySubject.get(subject) ?? new Map<string, string[]>();
|
|
53
|
+
const values = byPredicate.get(predicate) ?? [];
|
|
54
|
+
values.push(value);
|
|
55
|
+
byPredicate.set(predicate, values);
|
|
56
|
+
bySubject.set(subject, byPredicate);
|
|
57
|
+
}
|
|
58
|
+
return bySubject;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* Build the in-memory graph index used by listing, autocomplete, and reads.
|
|
63
|
+
*
|
|
64
|
+
* Replaces the former per-subject label/type/description queries with three
|
|
65
|
+
* bulk queries — one for types, one for labels, one for descriptions — joined
|
|
66
|
+
* in memory. Only typed named-node subjects are indexed; untyped subjects and
|
|
67
|
+
* blank nodes are reachable via `graph_query` but are not listed as resources.
|
|
68
|
+
*
|
|
69
|
+
* @param store - The ke store to query.
|
|
70
|
+
* @param prefixes - The store's merged prefix map.
|
|
71
|
+
* @returns The classified entities plus label and instance-count lookups.
|
|
72
|
+
*
|
|
73
|
+
* @note Queries ke store
|
|
74
|
+
*/
|
|
75
|
+
export default async function buildGraphIndex(
|
|
76
|
+
store: Store,
|
|
77
|
+
prefixes: Readonly<Record<string, string>>,
|
|
78
|
+
): Promise<GraphIndex> {
|
|
79
|
+
const prefixEntries = Object.entries(prefixes);
|
|
80
|
+
|
|
81
|
+
const [typesResult, labelValues, descriptionValues] = await Promise.all([
|
|
82
|
+
store.query(buildQuery(`SELECT ?s ?type WHERE { ?s <${RDF_TYPE}> ?type }`)),
|
|
83
|
+
fetchValuesBySubject(store, collectLabelPredicates()),
|
|
84
|
+
fetchValuesBySubject(store, collectDescriptionPredicates()),
|
|
85
|
+
]);
|
|
86
|
+
|
|
87
|
+
const typesBySubject = new Map<string, string[]>();
|
|
88
|
+
const instanceCountByType = new Map<string, number>();
|
|
89
|
+
if (typesResult.type === "select") {
|
|
90
|
+
for (const term of typesResult.termBindings) {
|
|
91
|
+
if (term.s?.termType !== "NamedNode") continue;
|
|
92
|
+
const subject = term.s.value;
|
|
93
|
+
const typeUri = term.type?.value ?? "";
|
|
94
|
+
const types = typesBySubject.get(subject) ?? [];
|
|
95
|
+
types.push(typeUri);
|
|
96
|
+
typesBySubject.set(subject, types);
|
|
97
|
+
instanceCountByType.set(
|
|
98
|
+
typeUri,
|
|
99
|
+
(instanceCountByType.get(typeUri) ?? 0) + 1,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Phase 1: resolve every subject's label and description so that a class
|
|
105
|
+
// label is available before any individual that references it is built.
|
|
106
|
+
interface ResolvedSubject {
|
|
107
|
+
readonly subject: string;
|
|
108
|
+
readonly fullTypes: string[];
|
|
109
|
+
readonly box: GraphEntity["box"];
|
|
110
|
+
readonly category: GraphEntity["category"];
|
|
111
|
+
readonly label: string | null;
|
|
112
|
+
readonly description: string | null;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const labelByUri = new Map<string, string>();
|
|
116
|
+
const resolved: ResolvedSubject[] = [];
|
|
117
|
+
|
|
118
|
+
for (const [subject, fullTypes] of typesBySubject) {
|
|
119
|
+
const classification = classifyEntity(fullTypes);
|
|
120
|
+
if (classification === null) continue;
|
|
121
|
+
|
|
122
|
+
const prefix = findNamespace(subject, prefixEntries)?.prefix;
|
|
123
|
+
const label = pickFirstValue(
|
|
124
|
+
labelValues.get(subject) ?? new Map(),
|
|
125
|
+
resolveLabelPredicates(prefix),
|
|
126
|
+
);
|
|
127
|
+
const description = pickFirstValue(
|
|
128
|
+
descriptionValues.get(subject) ?? new Map(),
|
|
129
|
+
resolveDescriptionPredicates(prefix),
|
|
130
|
+
);
|
|
131
|
+
if (label !== null) labelByUri.set(subject, label);
|
|
132
|
+
|
|
133
|
+
resolved.push({
|
|
134
|
+
subject,
|
|
135
|
+
fullTypes,
|
|
136
|
+
box: classification.box,
|
|
137
|
+
category: classification.category,
|
|
138
|
+
label,
|
|
139
|
+
description,
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Phase 2: build entities, resolving each individual's grouping label from
|
|
144
|
+
// the now-complete label lookup.
|
|
145
|
+
const entities: GraphEntity[] = resolved.map((r) => {
|
|
146
|
+
const primaryTypeFull =
|
|
147
|
+
r.category === "individual" ? pickPrimaryType(r.fullTypes) : null;
|
|
148
|
+
return {
|
|
149
|
+
uri: r.subject,
|
|
150
|
+
prefixed: compactUri(r.subject, prefixes),
|
|
151
|
+
box: r.box,
|
|
152
|
+
category: r.category,
|
|
153
|
+
types: r.fullTypes.map((t) => compactUri(t, prefixes)),
|
|
154
|
+
primaryType:
|
|
155
|
+
primaryTypeFull === null ? null : compactUri(primaryTypeFull, prefixes),
|
|
156
|
+
primaryTypeLabel:
|
|
157
|
+
primaryTypeFull === null
|
|
158
|
+
? null
|
|
159
|
+
: (labelByUri.get(primaryTypeFull) ??
|
|
160
|
+
extractLocalName(primaryTypeFull)),
|
|
161
|
+
label: r.label,
|
|
162
|
+
description: r.description,
|
|
163
|
+
};
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
return { entities, labelByUri, instanceCountByType };
|
|
167
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import buildResourceList from "./buildResourceList.js";
|
|
3
|
+
import { ABOX_PER_CLASS_LIMIT } from "./constants.js";
|
|
4
|
+
import type { GraphEntity, GraphIndex } from "./types.js";
|
|
5
|
+
|
|
6
|
+
const gadgetClass: GraphEntity = {
|
|
7
|
+
uri: "https://ds.canonical.com/Gadget",
|
|
8
|
+
prefixed: "ds:Gadget",
|
|
9
|
+
box: "tbox",
|
|
10
|
+
category: "class",
|
|
11
|
+
types: ["owl:Class"],
|
|
12
|
+
primaryType: null,
|
|
13
|
+
primaryTypeLabel: null,
|
|
14
|
+
label: "Gadget",
|
|
15
|
+
description: null,
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
const nameProperty: GraphEntity = {
|
|
19
|
+
uri: "https://ds.canonical.com/name",
|
|
20
|
+
prefixed: "ds:name",
|
|
21
|
+
box: "tbox",
|
|
22
|
+
category: "property",
|
|
23
|
+
types: ["owl:DatatypeProperty"],
|
|
24
|
+
primaryType: null,
|
|
25
|
+
primaryTypeLabel: null,
|
|
26
|
+
label: null,
|
|
27
|
+
description: null,
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
function makeIndividual(n: number): GraphEntity {
|
|
31
|
+
return {
|
|
32
|
+
uri: `https://ds.canonical.com/w${n}`,
|
|
33
|
+
prefixed: `ds:w${n}`,
|
|
34
|
+
box: "abox",
|
|
35
|
+
category: "individual",
|
|
36
|
+
types: ["ds:Gadget"],
|
|
37
|
+
primaryType: "ds:Gadget",
|
|
38
|
+
primaryTypeLabel: "Gadget",
|
|
39
|
+
label: `Widget ${n}`,
|
|
40
|
+
description: null,
|
|
41
|
+
};
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
function makeIndex(entities: GraphEntity[]): GraphIndex {
|
|
45
|
+
return { entities, labelByUri: new Map(), instanceCountByType: new Map() };
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function boxOf(resource: { _meta: Record<string, unknown> }): unknown {
|
|
49
|
+
return resource._meta["pragma/box"];
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
describe("buildResourceList", () => {
|
|
53
|
+
it("orders all TBox schema before any ABox individual", () => {
|
|
54
|
+
const index = makeIndex([makeIndividual(0), nameProperty, gadgetClass]);
|
|
55
|
+
const boxes = buildResourceList(index).resources.map(boxOf);
|
|
56
|
+
expect(boxes.slice(0, 2)).toEqual(["tbox", "tbox"]);
|
|
57
|
+
expect(boxes.at(-1)).toBe("abox");
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
it("caps individuals per class and records the dropped remainder", () => {
|
|
61
|
+
const overflow = ABOX_PER_CLASS_LIMIT + 7;
|
|
62
|
+
const individuals = Array.from({ length: overflow }, (_, i) =>
|
|
63
|
+
makeIndividual(i),
|
|
64
|
+
);
|
|
65
|
+
const { resources, truncation } = buildResourceList(
|
|
66
|
+
makeIndex([gadgetClass, ...individuals]),
|
|
67
|
+
);
|
|
68
|
+
|
|
69
|
+
const shown = resources.filter((r) => boxOf(r) === "abox");
|
|
70
|
+
expect(shown).toHaveLength(ABOX_PER_CLASS_LIMIT);
|
|
71
|
+
expect(truncation.totalDropped).toBe(7);
|
|
72
|
+
expect(truncation.droppedByType.get("ds:Gadget")).toBe(7);
|
|
73
|
+
|
|
74
|
+
const cls = resources.find((r) => r.uri === "ds:Gadget");
|
|
75
|
+
expect(cls?._meta["pragma/instanceCount"]).toBe(overflow);
|
|
76
|
+
expect(cls?._meta["pragma/instancesShown"]).toBe(ABOX_PER_CLASS_LIMIT);
|
|
77
|
+
expect(cls?._meta["pragma/truncated"]).toBe(true);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("reports no truncation when under the cap", () => {
|
|
81
|
+
const { truncation } = buildResourceList(
|
|
82
|
+
makeIndex([gadgetClass, makeIndividual(0)]),
|
|
83
|
+
);
|
|
84
|
+
expect(truncation.totalDropped).toBe(0);
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("assigns higher annotation priority to classes than individuals", () => {
|
|
88
|
+
const { resources } = buildResourceList(
|
|
89
|
+
makeIndex([gadgetClass, makeIndividual(0)]),
|
|
90
|
+
);
|
|
91
|
+
const cls = resources.find((r) => r.uri === "ds:Gadget");
|
|
92
|
+
const individual = resources.find((r) => r.uri === "ds:w0");
|
|
93
|
+
expect(cls?.annotations.priority).toBeGreaterThan(
|
|
94
|
+
individual?.annotations.priority ?? 1,
|
|
95
|
+
);
|
|
96
|
+
});
|
|
97
|
+
});
|