@canonical/pragma-cli 0.18.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 +377 -0
- package/package.json +78 -0
- package/src/bin.test.ts +52 -0
- package/src/bin.ts +19 -0
- package/src/compile-validation.runner.ts +137 -0
- package/src/compile-validation.ts +192 -0
- package/src/completions/computeSocketPath.test.ts +26 -0
- package/src/completions/computeSocketPath.ts +16 -0
- package/src/completions/constants.ts +18 -0
- package/src/completions/handleQuery.test.ts +93 -0
- package/src/completions/handleQuery.ts +30 -0
- package/src/completions/index.ts +14 -0
- package/src/completions/queryCompletions.test.ts +100 -0
- package/src/completions/queryCompletions.ts +61 -0
- package/src/completions/querySocket.test.ts +70 -0
- package/src/completions/querySocket.ts +44 -0
- package/src/completions/startCompletionsServer.test.ts +74 -0
- package/src/completions/startCompletionsServer.ts +75 -0
- package/src/completions/startCompletionsServer.unit.test.ts +131 -0
- package/src/completions/waitForSocket.test.ts +27 -0
- package/src/completions/waitForSocket.ts +27 -0
- package/src/config/configExists.ts +14 -0
- package/src/config/index.ts +6 -0
- package/src/config/readConfig.test.ts +152 -0
- package/src/config/readConfig.ts +64 -0
- package/src/config/resolveConfigPath.test.ts +20 -0
- package/src/config/resolveConfigPath.ts +11 -0
- package/src/config/types.ts +26 -0
- package/src/config/writeConfig.ts +49 -0
- package/src/constants.ts +78 -0
- package/src/domains/block/blockConfig.ts +53 -0
- package/src/domains/block/commands/index.ts +4 -0
- package/src/domains/block/commands/list.test.ts +111 -0
- package/src/domains/block/commands/list.ts +69 -0
- package/src/domains/block/commands/lookup.test.ts +187 -0
- package/src/domains/block/commands/lookup.ts +199 -0
- package/src/domains/block/formatters/index.ts +4 -0
- package/src/domains/block/formatters/list.test.ts +107 -0
- package/src/domains/block/formatters/list.ts +64 -0
- package/src/domains/block/formatters/lookup.test.ts +317 -0
- package/src/domains/block/formatters/lookup.ts +354 -0
- package/src/domains/block/formatters/types.ts +16 -0
- package/src/domains/block/helpers/index.ts +4 -0
- package/src/domains/block/helpers/resolveAspects.test.ts +64 -0
- package/src/domains/block/helpers/resolveAspects.ts +35 -0
- package/src/domains/block/index.ts +25 -0
- package/src/domains/block/mcp/index.ts +2 -0
- package/src/domains/block/mcp/specs.ts +199 -0
- package/src/domains/block/operations/index.ts +4 -0
- package/src/domains/block/operations/list.test.ts +80 -0
- package/src/domains/block/operations/list.ts +72 -0
- package/src/domains/block/operations/lookup.test.ts +108 -0
- package/src/domains/block/operations/lookup.ts +359 -0
- package/src/domains/block/orchestration/blockEmptyError.ts +19 -0
- package/src/domains/block/orchestration/buildBlockFilters.ts +12 -0
- package/src/domains/block/orchestration/enrichBlocks.ts +37 -0
- package/src/domains/block/orchestration/index.ts +5 -0
- package/src/domains/block/orchestration/resolveBlockList.ts +39 -0
- package/src/domains/block/orchestration/resolveBlockLookup.ts +21 -0
- package/src/domains/block/types.ts +30 -0
- package/src/domains/config/commands/channel.test.ts +101 -0
- package/src/domains/config/commands/channel.ts +80 -0
- package/src/domains/config/commands/index.ts +4 -0
- package/src/domains/config/commands/show.test.ts +61 -0
- package/src/domains/config/commands/show.ts +51 -0
- package/src/domains/config/commands/tier.test.ts +84 -0
- package/src/domains/config/commands/tier.ts +78 -0
- package/src/domains/config/formatters/channel.test.ts +56 -0
- package/src/domains/config/formatters/channel.ts +34 -0
- package/src/domains/config/formatters/index.ts +4 -0
- package/src/domains/config/formatters/show.test.ts +126 -0
- package/src/domains/config/formatters/show.ts +66 -0
- package/src/domains/config/formatters/tier.test.ts +70 -0
- package/src/domains/config/formatters/tier.ts +40 -0
- package/src/domains/config/index.ts +23 -0
- package/src/domains/config/mcp/index.ts +2 -0
- package/src/domains/config/mcp/specs.ts +108 -0
- package/src/domains/config/operations/index.ts +5 -0
- package/src/domains/config/operations/show.test.ts +60 -0
- package/src/domains/config/operations/show.ts +42 -0
- package/src/domains/config/operations/types.ts +13 -0
- package/src/domains/config/operations/validateChannel.test.ts +36 -0
- package/src/domains/config/operations/validateChannel.ts +24 -0
- package/src/domains/config/operations/validateTier.test.ts +56 -0
- package/src/domains/config/operations/validateTier.ts +35 -0
- package/src/domains/create/commands/component.test.ts +166 -0
- package/src/domains/create/commands/component.ts +129 -0
- package/src/domains/create/commands/index.ts +3 -0
- package/src/domains/create/commands/package.test.ts +35 -0
- package/src/domains/create/commands/package.ts +21 -0
- package/src/domains/create/index.ts +15 -0
- package/src/domains/create/mcp/index.ts +2 -0
- package/src/domains/create/mcp/specs.ts +174 -0
- package/src/domains/doctor/commands/doctor.test.ts +75 -0
- package/src/domains/doctor/commands/doctor.ts +43 -0
- package/src/domains/doctor/commands/index.ts +2 -0
- package/src/domains/doctor/formatters/doctor.test.ts +117 -0
- package/src/domains/doctor/formatters/doctor.ts +84 -0
- package/src/domains/doctor/formatters/index.ts +2 -0
- package/src/domains/doctor/index.ts +9 -0
- package/src/domains/doctor/mcp/index.ts +2 -0
- package/src/domains/doctor/mcp/specs.ts +88 -0
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +28 -0
- package/src/domains/doctor/operations/checks/checkKeStore.ts +38 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +65 -0
- package/src/domains/doctor/operations/checks/checkNodeVersion.ts +25 -0
- package/src/domains/doctor/operations/checks/checkPragmaVersion.ts +20 -0
- package/src/domains/doctor/operations/checks/checkShellCompletions.ts +42 -0
- package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +64 -0
- package/src/domains/doctor/operations/checks/index.ts +8 -0
- package/src/domains/doctor/operations/checks.test.ts +151 -0
- package/src/domains/doctor/operations/index.ts +3 -0
- package/src/domains/doctor/operations/runChecks.test.ts +89 -0
- package/src/domains/doctor/operations/runChecks.ts +38 -0
- package/src/domains/doctor/operations/types.ts +20 -0
- package/src/domains/graph/commands/index.ts +4 -0
- package/src/domains/graph/commands/inspect.test.ts +94 -0
- package/src/domains/graph/commands/inspect.ts +63 -0
- package/src/domains/graph/commands/query.test.ts +92 -0
- package/src/domains/graph/commands/query.ts +60 -0
- package/src/domains/graph/formatters/index.ts +4 -0
- package/src/domains/graph/formatters/inspect.ts +45 -0
- package/src/domains/graph/formatters/query.ts +37 -0
- package/src/domains/graph/helpers/index.ts +3 -0
- package/src/domains/graph/helpers/resolveUri.ts +70 -0
- package/src/domains/graph/index.ts +22 -0
- package/src/domains/graph/mcp/index.ts +2 -0
- package/src/domains/graph/mcp/specs.ts +82 -0
- package/src/domains/graph/operations/executeQuery.test.ts +68 -0
- package/src/domains/graph/operations/executeQuery.ts +32 -0
- package/src/domains/graph/operations/index.ts +4 -0
- package/src/domains/graph/operations/inspectUri.test.ts +101 -0
- package/src/domains/graph/operations/inspectUri.ts +61 -0
- package/src/domains/info/commands/index.ts +4 -0
- package/src/domains/info/commands/info.test.ts +88 -0
- package/src/domains/info/commands/info.ts +47 -0
- package/src/domains/info/commands/upgrade.test.ts +183 -0
- package/src/domains/info/commands/upgrade.ts +150 -0
- package/src/domains/info/constants.test.ts +22 -0
- package/src/domains/info/constants.ts +15 -0
- package/src/domains/info/formatters/index.ts +8 -0
- package/src/domains/info/formatters/info.test.ts +173 -0
- package/src/domains/info/formatters/info.ts +116 -0
- package/src/domains/info/formatters/upgrade.test.ts +133 -0
- package/src/domains/info/formatters/upgrade.ts +68 -0
- package/src/domains/info/index.ts +7 -0
- package/src/domains/info/operations/checkRegistryVersion.test.ts +124 -0
- package/src/domains/info/operations/checkRegistryVersion.ts +45 -0
- package/src/domains/info/operations/collectInfo.test.ts +111 -0
- package/src/domains/info/operations/collectInfo.ts +68 -0
- package/src/domains/info/operations/collectStoreSummary.test.ts +61 -0
- package/src/domains/info/operations/collectStoreSummary.ts +42 -0
- package/src/domains/info/operations/index.ts +5 -0
- package/src/domains/info/types.test-d.ts +35 -0
- package/src/domains/info/types.ts +46 -0
- package/src/domains/llm/commands/index.ts +3 -0
- package/src/domains/llm/commands/llm.ts +37 -0
- package/src/domains/llm/data/commandReference.ts +25 -0
- package/src/domains/llm/data/decisionTrees.ts +46 -0
- package/src/domains/llm/data/index.ts +4 -0
- package/src/domains/llm/formatters/index.ts +3 -0
- package/src/domains/llm/formatters/orientation.ts +53 -0
- package/src/domains/llm/index.ts +8 -0
- package/src/domains/llm/llmCommand.test.ts +93 -0
- package/src/domains/llm/mcp/index.ts +2 -0
- package/src/domains/llm/mcp/specs.ts +87 -0
- package/src/domains/llm/operations/collectContext.ts +47 -0
- package/src/domains/llm/operations/index.ts +3 -0
- package/src/domains/llm/types.ts +32 -0
- package/src/domains/modifier/commands/index.ts +4 -0
- package/src/domains/modifier/commands/list.test.ts +57 -0
- package/src/domains/modifier/commands/list.ts +33 -0
- package/src/domains/modifier/commands/lookup.test.ts +91 -0
- package/src/domains/modifier/commands/lookup.ts +105 -0
- package/src/domains/modifier/formatters/index.ts +4 -0
- package/src/domains/modifier/formatters/list.test.ts +38 -0
- package/src/domains/modifier/formatters/list.ts +35 -0
- package/src/domains/modifier/formatters/lookup.test.ts +33 -0
- package/src/domains/modifier/formatters/lookup.ts +38 -0
- package/src/domains/modifier/index.ts +22 -0
- package/src/domains/modifier/mcp/index.ts +2 -0
- package/src/domains/modifier/mcp/specs.ts +97 -0
- package/src/domains/modifier/operations/index.ts +4 -0
- package/src/domains/modifier/operations/list.test.ts +39 -0
- package/src/domains/modifier/operations/list.ts +41 -0
- package/src/domains/modifier/operations/lookup.test.ts +37 -0
- package/src/domains/modifier/operations/lookup.ts +57 -0
- package/src/domains/modifier/orchestration/index.ts +3 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.ts +10 -0
- package/src/domains/modifier/orchestration/resolveModifierList.ts +16 -0
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +20 -0
- package/src/domains/modifier/types.ts +5 -0
- package/src/domains/ontology/commands/index.ts +4 -0
- package/src/domains/ontology/commands/list.test.ts +82 -0
- package/src/domains/ontology/commands/list.ts +46 -0
- package/src/domains/ontology/commands/show.test.ts +103 -0
- package/src/domains/ontology/commands/show.ts +66 -0
- package/src/domains/ontology/formatters/index.ts +4 -0
- package/src/domains/ontology/formatters/list.ts +37 -0
- package/src/domains/ontology/formatters/show.ts +74 -0
- package/src/domains/ontology/helpers/findNamespace.ts +23 -0
- package/src/domains/ontology/helpers/index.ts +7 -0
- package/src/domains/ontology/helpers/queryClasses.ts +50 -0
- package/src/domains/ontology/helpers/queryProperties.ts +57 -0
- package/src/domains/ontology/helpers/resolvePrefix.ts +45 -0
- package/src/domains/ontology/index.ts +26 -0
- package/src/domains/ontology/mcp/index.ts +2 -0
- package/src/domains/ontology/mcp/specs.ts +93 -0
- package/src/domains/ontology/operations/index.ts +5 -0
- package/src/domains/ontology/operations/listOntologies.test.ts +45 -0
- package/src/domains/ontology/operations/listOntologies.ts +101 -0
- package/src/domains/ontology/operations/showOntology.test.ts +65 -0
- package/src/domains/ontology/operations/showOntology.ts +41 -0
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +39 -0
- package/src/domains/ontology/operations/showOntologyRaw.ts +50 -0
- package/src/domains/setup/commands/all.ts +58 -0
- package/src/domains/setup/commands/completions.ts +85 -0
- package/src/domains/setup/commands/entrypoints.test.ts +170 -0
- package/src/domains/setup/commands/index.ts +6 -0
- package/src/domains/setup/commands/lsp.ts +49 -0
- package/src/domains/setup/commands/mcp.ts +119 -0
- package/src/domains/setup/commands/skills.test.ts +64 -0
- package/src/domains/setup/commands/skills.ts +106 -0
- package/src/domains/setup/formatters/index.ts +2 -0
- package/src/domains/setup/formatters/skills.test.ts +103 -0
- package/src/domains/setup/formatters/skills.ts +112 -0
- package/src/domains/setup/formatters/types.ts +7 -0
- package/src/domains/setup/helpers/completionScripts.test.ts +58 -0
- package/src/domains/setup/helpers/completionScripts.ts +92 -0
- package/src/domains/setup/helpers/constants.ts +2 -0
- package/src/domains/setup/helpers/detectShell.test.ts +48 -0
- package/src/domains/setup/helpers/detectShell.ts +20 -0
- package/src/domains/setup/helpers/index.ts +12 -0
- package/src/domains/setup/helpers/runSetupTask.test.ts +279 -0
- package/src/domains/setup/helpers/runSetupTask.ts +229 -0
- package/src/domains/setup/index.ts +33 -0
- package/src/domains/setup/operations/index.ts +6 -0
- package/src/domains/setup/operations/setupAll.test.ts +48 -0
- package/src/domains/setup/operations/setupAll.ts +41 -0
- package/src/domains/setup/operations/setupCompletions.test.ts +94 -0
- package/src/domains/setup/operations/setupCompletions.ts +54 -0
- package/src/domains/setup/operations/setupLsp.test.ts +29 -0
- package/src/domains/setup/operations/setupLsp.ts +17 -0
- package/src/domains/setup/operations/setupMcp.test.ts +115 -0
- package/src/domains/setup/operations/setupMcp.ts +108 -0
- package/src/domains/setup/operations/setupSkills.test.ts +191 -0
- package/src/domains/setup/operations/setupSkills.ts +160 -0
- package/src/domains/setup/types.ts +18 -0
- package/src/domains/shared/ToolSpec.ts +49 -0
- package/src/domains/shared/bootStore.ts +77 -0
- package/src/domains/shared/buildQuery.ts +18 -0
- package/src/domains/shared/compactUri.test.ts +21 -0
- package/src/domains/shared/compactUri.ts +16 -0
- package/src/domains/shared/context.ts +15 -0
- package/src/domains/shared/contracts.ts +113 -0
- package/src/domains/shared/extractLocalName.test.ts +21 -0
- package/src/domains/shared/extractLocalName.ts +14 -0
- package/src/domains/shared/filters/buildChannelFilter.test.ts +52 -0
- package/src/domains/shared/filters/buildChannelFilter.ts +41 -0
- package/src/domains/shared/filters/buildFilterConfig.ts +16 -0
- package/src/domains/shared/filters/buildFilters.test.ts +33 -0
- package/src/domains/shared/filters/buildFilters.ts +30 -0
- package/src/domains/shared/filters/buildTierFilter.test.ts +78 -0
- package/src/domains/shared/filters/buildTierFilter.ts +69 -0
- package/src/domains/shared/filters/describeFilters.ts +14 -0
- package/src/domains/shared/filters/index.ts +10 -0
- package/src/domains/shared/formatters.ts +35 -0
- package/src/domains/shared/lookupMany.ts +45 -0
- package/src/domains/shared/namespaces.ts +18 -0
- package/src/domains/shared/packages.ts +76 -0
- package/src/domains/shared/prefixes.ts +37 -0
- package/src/domains/shared/runtime.ts +62 -0
- package/src/domains/shared/types.test-d.ts +127 -0
- package/src/domains/shared/types.ts +348 -0
- package/src/domains/skill/commands/index.ts +2 -0
- package/src/domains/skill/commands/list.test.ts +111 -0
- package/src/domains/skill/commands/list.ts +69 -0
- package/src/domains/skill/formatters/index.ts +2 -0
- package/src/domains/skill/formatters/list.test.ts +140 -0
- package/src/domains/skill/formatters/list.ts +119 -0
- package/src/domains/skill/formatters/types.ts +8 -0
- package/src/domains/skill/helpers/index.ts +6 -0
- package/src/domains/skill/helpers/parseFrontmatter.test.ts +122 -0
- package/src/domains/skill/helpers/parseFrontmatter.ts +112 -0
- package/src/domains/skill/helpers/resolveSkillSources.ts +34 -0
- package/src/domains/skill/index.ts +19 -0
- package/src/domains/skill/mcp/index.ts +2 -0
- package/src/domains/skill/mcp/specs.ts +38 -0
- package/src/domains/skill/operations/discover.test.ts +145 -0
- package/src/domains/skill/operations/discover.ts +68 -0
- package/src/domains/skill/operations/index.ts +4 -0
- package/src/domains/skill/operations/list.test.ts +68 -0
- package/src/domains/skill/operations/list.ts +47 -0
- package/src/domains/skill/types.ts +25 -0
- package/src/domains/standard/commands/categories.test.ts +70 -0
- package/src/domains/standard/commands/categories.ts +48 -0
- package/src/domains/standard/commands/index.ts +5 -0
- package/src/domains/standard/commands/list.test.ts +105 -0
- package/src/domains/standard/commands/list.ts +81 -0
- package/src/domains/standard/commands/lookup.test.ts +135 -0
- package/src/domains/standard/commands/lookup.ts +127 -0
- package/src/domains/standard/formatters/categories.test.ts +44 -0
- package/src/domains/standard/formatters/categories.ts +38 -0
- package/src/domains/standard/formatters/index.ts +6 -0
- package/src/domains/standard/formatters/list.test.ts +166 -0
- package/src/domains/standard/formatters/list.ts +156 -0
- package/src/domains/standard/formatters/lookup.test.ts +95 -0
- package/src/domains/standard/formatters/lookup.ts +86 -0
- package/src/domains/standard/formatters/types.ts +31 -0
- package/src/domains/standard/index.ts +32 -0
- package/src/domains/standard/mcp/index.ts +2 -0
- package/src/domains/standard/mcp/specs.ts +197 -0
- package/src/domains/standard/operations/categories.test.ts +61 -0
- package/src/domains/standard/operations/categories.ts +38 -0
- package/src/domains/standard/operations/index.ts +5 -0
- package/src/domains/standard/operations/list.test.ts +75 -0
- package/src/domains/standard/operations/list.ts +64 -0
- package/src/domains/standard/operations/lookup.test.ts +58 -0
- package/src/domains/standard/operations/lookup.ts +142 -0
- package/src/domains/standard/orchestration/buildStandardFilters.ts +11 -0
- package/src/domains/standard/orchestration/index.ts +4 -0
- package/src/domains/standard/orchestration/resolveStandardList.ts +42 -0
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +20 -0
- package/src/domains/standard/orchestration/standardEmptyError.ts +19 -0
- package/src/domains/standard/types.ts +15 -0
- package/src/domains/tier/commands/index.ts +3 -0
- package/src/domains/tier/commands/list.test.ts +67 -0
- package/src/domains/tier/commands/list.ts +43 -0
- package/src/domains/tier/formatters/index.ts +3 -0
- package/src/domains/tier/formatters/list.test.ts +33 -0
- package/src/domains/tier/formatters/list.ts +40 -0
- package/src/domains/tier/index.ts +17 -0
- package/src/domains/tier/mcp/index.ts +2 -0
- package/src/domains/tier/mcp/specs.ts +38 -0
- package/src/domains/tier/operations/index.ts +3 -0
- package/src/domains/tier/operations/list.test.ts +32 -0
- package/src/domains/tier/operations/list.ts +33 -0
- package/src/domains/token/commands/add-config.test.ts +68 -0
- package/src/domains/token/commands/add-config.ts +62 -0
- package/src/domains/token/commands/index.ts +5 -0
- package/src/domains/token/commands/list.test.ts +82 -0
- package/src/domains/token/commands/list.ts +45 -0
- package/src/domains/token/commands/lookup.test.ts +125 -0
- package/src/domains/token/commands/lookup.ts +121 -0
- package/src/domains/token/formatters/index.ts +4 -0
- package/src/domains/token/formatters/list.test.ts +37 -0
- package/src/domains/token/formatters/list.ts +37 -0
- package/src/domains/token/formatters/lookup.test.ts +61 -0
- package/src/domains/token/formatters/lookup.ts +72 -0
- package/src/domains/token/index.ts +32 -0
- package/src/domains/token/mcp/index.ts +2 -0
- package/src/domains/token/mcp/specs.ts +145 -0
- package/src/domains/token/operations/add-config.test.ts +68 -0
- package/src/domains/token/operations/add-config.ts +61 -0
- package/src/domains/token/operations/index.ts +7 -0
- package/src/domains/token/operations/list.test.ts +50 -0
- package/src/domains/token/operations/list.ts +52 -0
- package/src/domains/token/operations/lookup.test.ts +42 -0
- package/src/domains/token/operations/lookup.ts +66 -0
- package/src/domains/token/orchestration/index.ts +3 -0
- package/src/domains/token/orchestration/resolveTokenList.ts +17 -0
- package/src/domains/token/orchestration/resolveTokenLookup.ts +20 -0
- package/src/domains/token/orchestration/tokenEmptyError.ts +17 -0
- package/src/domains/token/types.ts +6 -0
- package/src/error/PragmaError.test.ts +121 -0
- package/src/error/PragmaError.ts +164 -0
- package/src/error/constants.ts +12 -0
- package/src/error/index.ts +4 -0
- package/src/error/types.ts +41 -0
- package/src/index.ts +141 -0
- package/src/mcp/createMcpServer.test.ts +41 -0
- package/src/mcp/createMcpServer.ts +53 -0
- package/src/mcp/error/buildRecovery.test.ts +37 -0
- package/src/mcp/error/buildRecovery.ts +17 -0
- package/src/mcp/error/index.ts +12 -0
- package/src/mcp/error/serializeError.test.ts +121 -0
- package/src/mcp/error/serializeError.ts +31 -0
- package/src/mcp/error/serializeErrorPayload.test.ts +38 -0
- package/src/mcp/error/serializeErrorPayload.ts +27 -0
- package/src/mcp/error/types.ts +51 -0
- package/src/mcp/index.ts +15 -0
- package/src/mcp/registerResources.test.ts +236 -0
- package/src/mcp/registerResources.ts +303 -0
- package/src/mcp/runMcpServer.test.ts +66 -0
- package/src/mcp/runMcpServer.ts +24 -0
- package/src/mcp/tools/index.ts +55 -0
- package/src/mcp/tools/registerFromSpec.ts +97 -0
- package/src/mcp/tools/registerTools.test.ts +1124 -0
- package/src/mcp/types.ts +62 -0
- package/src/mcp/utils/estimateTokens.test.ts +14 -0
- package/src/mcp/utils/estimateTokens.ts +9 -0
- package/src/mcp/utils/index.ts +11 -0
- package/src/mcp/utils/wrapTool.test.ts +108 -0
- package/src/mcp/utils/wrapTool.ts +81 -0
- package/src/package-manager/constants.ts +29 -0
- package/src/package-manager/detectInstallSource.test.ts +23 -0
- package/src/package-manager/detectInstallSource.ts +111 -0
- package/src/package-manager/detectLocalInstall.test.ts +16 -0
- package/src/package-manager/detectLocalInstall.ts +43 -0
- package/src/package-manager/detectPackageManager.test.ts +81 -0
- package/src/package-manager/detectPackageManager.ts +30 -0
- package/src/package-manager/index.ts +6 -0
- package/src/package-manager/types.ts +12 -0
- package/src/pipeline/collectCommands.test.ts +37 -0
- package/src/pipeline/collectCommands.ts +47 -0
- package/src/pipeline/constants.ts +14 -0
- package/src/pipeline/createProgram.test.ts +112 -0
- package/src/pipeline/createProgram.ts +70 -0
- package/src/pipeline/formatTerminal.test.ts +43 -0
- package/src/pipeline/formatTerminal.ts +53 -0
- package/src/pipeline/index.ts +19 -0
- package/src/pipeline/mapExitCode.test.ts +41 -0
- package/src/pipeline/mapExitCode.ts +12 -0
- package/src/pipeline/parseGlobalFlags.ts +21 -0
- package/src/pipeline/renderError.test.ts +151 -0
- package/src/pipeline/renderError.ts +114 -0
- package/src/pipeline/resolveCommandKind.test.ts +68 -0
- package/src/pipeline/resolveCommandKind.ts +71 -0
- package/src/pipeline/runCli.orchestration.test.ts +418 -0
- package/src/pipeline/runCli.test.ts +70 -0
- package/src/pipeline/runCli.ts +215 -0
- package/src/pipeline/types.ts +17 -0
- package/src/testing/cli.ts +43 -0
- package/src/testing/createTestMcpClient.ts +64 -0
- package/src/testing/dsFixtures.ts +506 -0
- package/src/testing/fixtures/canonical-config.json +4 -0
- package/src/testing/fixtures/canonical.ttl +437 -0
- package/src/testing/fixtures/filtered-config.json +4 -0
- package/src/testing/fixtures.ts +34 -0
- package/src/testing/helpers/assertParity.ts +28 -0
- package/src/testing/helpers/createTestMcpClient.ts +40 -0
- package/src/testing/helpers/createTestRuntime.ts +67 -0
- package/src/testing/index.ts +8 -0
- package/src/testing/integration/agent-session.test.ts +148 -0
- package/src/testing/integration/boot.test.ts +67 -0
- package/src/testing/integration/mcp-surface.test.ts +574 -0
- package/src/testing/integration/parity.test.ts +192 -0
- package/src/testing/store.ts +39 -0
- package/src/testing/types.ts +20 -0
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* E1 WASM Embedding Validation — Compiled Binary Entry Point
|
|
3
|
+
*
|
|
4
|
+
* This file is compiled via `bun build --compile` and exercised as a
|
|
5
|
+
* standalone executable. It validates that the Oxigraph WASM module loads
|
|
6
|
+
* and queries correctly when embedded in a compiled Bun binary.
|
|
7
|
+
*
|
|
8
|
+
* Tests exercised:
|
|
9
|
+
* 1. Dynamic import of @canonical/ke (triggers WASM loading)
|
|
10
|
+
* 2. createStore() with file sources
|
|
11
|
+
* 3. SPARQL SELECT query with prefix expansion
|
|
12
|
+
* 4. sparql tagged template with branded URI interpolation
|
|
13
|
+
* 5. ASK query
|
|
14
|
+
* 6. CONSTRUCT query
|
|
15
|
+
* 7. Cache round-trip (write + re-load)
|
|
16
|
+
*
|
|
17
|
+
* @note This file is impure — it writes temp files and reads from disk.
|
|
18
|
+
* It is a build validation program, not a library module.
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
import { existsSync, mkdirSync, rmSync, writeFileSync } from "node:fs";
|
|
22
|
+
import { tmpdir } from "node:os";
|
|
23
|
+
import { join } from "node:path";
|
|
24
|
+
|
|
25
|
+
async function run() {
|
|
26
|
+
const start = performance.now();
|
|
27
|
+
|
|
28
|
+
// --- Setup temp directory with TTL data ---
|
|
29
|
+
const workDir = join(tmpdir(), `e1-validate-${Date.now()}`);
|
|
30
|
+
mkdirSync(workDir, { recursive: true });
|
|
31
|
+
|
|
32
|
+
const ttlPath = join(workDir, "people.ttl");
|
|
33
|
+
writeFileSync(
|
|
34
|
+
ttlPath,
|
|
35
|
+
`
|
|
36
|
+
@prefix schema: <http://schema.org/> .
|
|
37
|
+
@prefix ex: <http://example.org/> .
|
|
38
|
+
|
|
39
|
+
ex:alice a schema:Person ;
|
|
40
|
+
schema:name "Alice" ;
|
|
41
|
+
schema:age 30 ;
|
|
42
|
+
schema:email "alice@example.org" .
|
|
43
|
+
|
|
44
|
+
ex:bob a schema:Person ;
|
|
45
|
+
schema:name "Bob" ;
|
|
46
|
+
schema:age 25 ;
|
|
47
|
+
schema:knows ex:alice .
|
|
48
|
+
|
|
49
|
+
ex:charlie a schema:Person ;
|
|
50
|
+
schema:name "Charlie" ;
|
|
51
|
+
schema:age 35 ;
|
|
52
|
+
schema:knows ex:alice, ex:bob .
|
|
53
|
+
`,
|
|
54
|
+
);
|
|
55
|
+
|
|
56
|
+
const cachePath = join(workDir, ".cache", "store.nq");
|
|
57
|
+
|
|
58
|
+
// --- 1. Load ke (triggers WASM) ---
|
|
59
|
+
console.log("[validate] Loading @canonical/ke (triggers WASM)...");
|
|
60
|
+
const wasmStart = performance.now();
|
|
61
|
+
const { createStore, sparql, createNamespace } = await import(
|
|
62
|
+
"@canonical/ke"
|
|
63
|
+
);
|
|
64
|
+
console.log(
|
|
65
|
+
`[validate] WASM loaded in ${(performance.now() - wasmStart).toFixed(1)}ms`,
|
|
66
|
+
);
|
|
67
|
+
|
|
68
|
+
// --- 2. createStore from file sources ---
|
|
69
|
+
console.log("[validate] Creating store from file sources...");
|
|
70
|
+
const storeStart = performance.now();
|
|
71
|
+
const store = await createStore({
|
|
72
|
+
sources: [ttlPath],
|
|
73
|
+
prefixes: {
|
|
74
|
+
schema: "http://schema.org/",
|
|
75
|
+
ex: "http://example.org/",
|
|
76
|
+
},
|
|
77
|
+
cache: cachePath,
|
|
78
|
+
});
|
|
79
|
+
console.log(
|
|
80
|
+
`[validate] Store created in ${(performance.now() - storeStart).toFixed(1)}ms`,
|
|
81
|
+
);
|
|
82
|
+
|
|
83
|
+
// --- 3. SPARQL SELECT ---
|
|
84
|
+
console.log("[validate] Running SPARQL SELECT...");
|
|
85
|
+
const selectResult = await store.query(
|
|
86
|
+
sparql`SELECT ?name ?age WHERE { ?p a schema:Person ; schema:name ?name ; schema:age ?age . } ORDER BY ?name`,
|
|
87
|
+
);
|
|
88
|
+
|
|
89
|
+
if (selectResult.type !== "select") {
|
|
90
|
+
throw new Error(`Expected select result, got ${selectResult.type}`);
|
|
91
|
+
}
|
|
92
|
+
console.log(
|
|
93
|
+
`[validate] Query returned ${selectResult.bindings.length} bindings`,
|
|
94
|
+
);
|
|
95
|
+
for (const b of selectResult.bindings) {
|
|
96
|
+
console.log(
|
|
97
|
+
` name=${JSON.stringify(b.name)} age=${JSON.stringify(b.age)}`,
|
|
98
|
+
);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
if (selectResult.bindings.length !== 3) {
|
|
102
|
+
throw new Error(`Expected 3 bindings, got ${selectResult.bindings.length}`);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// --- 4. Branded URI interpolation ---
|
|
106
|
+
console.log("[validate] Testing branded URI interpolation...");
|
|
107
|
+
const ex = createNamespace("http://example.org/");
|
|
108
|
+
const aliceURI = ex("alice");
|
|
109
|
+
const knowsResult = await store.query(
|
|
110
|
+
sparql`SELECT ?friend WHERE { ${aliceURI} schema:knows ?friend . }`,
|
|
111
|
+
);
|
|
112
|
+
if (knowsResult.type !== "select") {
|
|
113
|
+
throw new Error(`Expected select result, got ${knowsResult.type}`);
|
|
114
|
+
}
|
|
115
|
+
console.log(
|
|
116
|
+
`[validate] Branded URI query: ${knowsResult.bindings.length} bindings`,
|
|
117
|
+
);
|
|
118
|
+
|
|
119
|
+
// --- 5. ASK query ---
|
|
120
|
+
console.log("[validate] Testing ASK query...");
|
|
121
|
+
const askResult = await store.query(sparql`ASK { ex:alice a schema:Person }`);
|
|
122
|
+
if (askResult.type !== "ask") {
|
|
123
|
+
throw new Error(`Expected ask result, got ${askResult.type}`);
|
|
124
|
+
}
|
|
125
|
+
if (!askResult.result) {
|
|
126
|
+
throw new Error("ASK should return true");
|
|
127
|
+
}
|
|
128
|
+
console.log(`[validate] ASK result: ${askResult.result}`);
|
|
129
|
+
|
|
130
|
+
// --- 6. CONSTRUCT query ---
|
|
131
|
+
console.log("[validate] Testing CONSTRUCT query...");
|
|
132
|
+
const constructResult = await store.query(
|
|
133
|
+
sparql`CONSTRUCT { ?p schema:name ?n } WHERE { ?p a schema:Person ; schema:name ?n }`,
|
|
134
|
+
);
|
|
135
|
+
if (constructResult.type !== "construct") {
|
|
136
|
+
throw new Error(`Expected construct result, got ${constructResult.type}`);
|
|
137
|
+
}
|
|
138
|
+
if (constructResult.triples.length !== 3) {
|
|
139
|
+
throw new Error(
|
|
140
|
+
`Expected 3 triples, got ${constructResult.triples.length}`,
|
|
141
|
+
);
|
|
142
|
+
}
|
|
143
|
+
console.log(
|
|
144
|
+
`[validate] CONSTRUCT returned ${constructResult.triples.length} triples`,
|
|
145
|
+
);
|
|
146
|
+
|
|
147
|
+
// --- 7. Cache round-trip ---
|
|
148
|
+
console.log("[validate] Testing cache round-trip...");
|
|
149
|
+
if (!existsSync(cachePath)) {
|
|
150
|
+
throw new Error("Cache file was not written");
|
|
151
|
+
}
|
|
152
|
+
const cacheStart = performance.now();
|
|
153
|
+
const cachedStore = await createStore({
|
|
154
|
+
sources: [ttlPath],
|
|
155
|
+
prefixes: {
|
|
156
|
+
schema: "http://schema.org/",
|
|
157
|
+
ex: "http://example.org/",
|
|
158
|
+
},
|
|
159
|
+
cache: cachePath,
|
|
160
|
+
});
|
|
161
|
+
const cacheTime = performance.now() - cacheStart;
|
|
162
|
+
console.log(`[validate] Cached store loaded in ${cacheTime.toFixed(1)}ms`);
|
|
163
|
+
|
|
164
|
+
const cachedResult = await cachedStore.query(
|
|
165
|
+
sparql`SELECT ?name WHERE { ?p a schema:Person ; schema:name ?name . } ORDER BY ?name`,
|
|
166
|
+
);
|
|
167
|
+
if (cachedResult.type !== "select") {
|
|
168
|
+
throw new Error(`Expected select result, got ${cachedResult.type}`);
|
|
169
|
+
}
|
|
170
|
+
if (cachedResult.bindings.length !== 3) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Cached store: expected 3, got ${cachedResult.bindings.length}`,
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
console.log(
|
|
176
|
+
`[validate] Cache round-trip: ${cachedResult.bindings.length} bindings`,
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// --- Cleanup ---
|
|
180
|
+
store.dispose();
|
|
181
|
+
cachedStore.dispose();
|
|
182
|
+
rmSync(workDir, { recursive: true, force: true });
|
|
183
|
+
|
|
184
|
+
const totalTime = performance.now() - start;
|
|
185
|
+
console.log(`\n[validate] Total time: ${totalTime.toFixed(1)}ms`);
|
|
186
|
+
console.log("[validate] ALL CHECKS PASSED");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
run().catch((err) => {
|
|
190
|
+
console.error("[validate] FAILED:", err);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import computeSocketPath from "./computeSocketPath.js";
|
|
3
|
+
|
|
4
|
+
describe("computeSocketPath", () => {
|
|
5
|
+
it("returns a path starting with the socket prefix", () => {
|
|
6
|
+
const path = computeSocketPath("/home/user/project");
|
|
7
|
+
expect(path).toMatch(/^\/tmp\/pragma-completions-/);
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
it("returns a path ending with .sock", () => {
|
|
11
|
+
const path = computeSocketPath("/home/user/project");
|
|
12
|
+
expect(path).toMatch(/\.sock$/);
|
|
13
|
+
});
|
|
14
|
+
|
|
15
|
+
it("is deterministic — same cwd produces same path", () => {
|
|
16
|
+
const a = computeSocketPath("/home/user/project");
|
|
17
|
+
const b = computeSocketPath("/home/user/project");
|
|
18
|
+
expect(a).toBe(b);
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it("produces different paths for different cwds", () => {
|
|
22
|
+
const a = computeSocketPath("/home/user/project-a");
|
|
23
|
+
const b = computeSocketPath("/home/user/project-b");
|
|
24
|
+
expect(a).not.toBe(b);
|
|
25
|
+
});
|
|
26
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
import { SOCKET_PREFIX } from "./constants.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compute the Unix domain socket path for a project's completions server.
|
|
6
|
+
*
|
|
7
|
+
* Derives a deterministic path from a SHA-256 hash of the working directory
|
|
8
|
+
* so each project gets its own completions server socket.
|
|
9
|
+
*
|
|
10
|
+
* @param cwd - The project working directory to hash.
|
|
11
|
+
* @returns The absolute socket file path under `/tmp/`.
|
|
12
|
+
*/
|
|
13
|
+
export default function computeSocketPath(cwd: string): string {
|
|
14
|
+
const hash = createHash("sha256").update(cwd).digest("hex").slice(0, 16);
|
|
15
|
+
return `${SOCKET_PREFIX}${hash}.sock`;
|
|
16
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completions server constants.
|
|
3
|
+
*
|
|
4
|
+
* Shared by both the server (idle timeout, socket naming) and
|
|
5
|
+
* client (poll timing, spawn timeout) sides of the completions system.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
/** Server auto-exit timeout in milliseconds after last request. */
|
|
9
|
+
export const IDLE_TIMEOUT_MS = 10_000;
|
|
10
|
+
|
|
11
|
+
/** Path prefix for Unix domain socket files. */
|
|
12
|
+
export const SOCKET_PREFIX = "/tmp/pragma-completions-";
|
|
13
|
+
|
|
14
|
+
/** Interval between socket existence checks in milliseconds. */
|
|
15
|
+
export const POLL_INTERVAL_MS = 50;
|
|
16
|
+
|
|
17
|
+
/** Maximum time to wait for a spawned server to become ready. */
|
|
18
|
+
export const SPAWN_TIMEOUT_MS = 5_000;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
CommandContext,
|
|
3
|
+
CommandDefinition,
|
|
4
|
+
CompletionTree,
|
|
5
|
+
} from "@canonical/cli-core";
|
|
6
|
+
import { buildCompleters, createExitResult } from "@canonical/cli-core";
|
|
7
|
+
import { describe, expect, it } from "vitest";
|
|
8
|
+
import handleQuery from "./handleQuery.js";
|
|
9
|
+
|
|
10
|
+
const ctx: CommandContext = {
|
|
11
|
+
cwd: "/test",
|
|
12
|
+
globalFlags: { llm: false, format: "text", verbose: false },
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
function makeTree(): CompletionTree {
|
|
16
|
+
const commands: CommandDefinition[] = [
|
|
17
|
+
{
|
|
18
|
+
path: ["block", "list"],
|
|
19
|
+
description: "List blocks",
|
|
20
|
+
parameters: [],
|
|
21
|
+
execute: async () => createExitResult(0),
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
path: ["block", "get"],
|
|
25
|
+
description: "Get block details",
|
|
26
|
+
parameters: [
|
|
27
|
+
{
|
|
28
|
+
name: "name",
|
|
29
|
+
description: "Block name",
|
|
30
|
+
type: "string",
|
|
31
|
+
positional: true,
|
|
32
|
+
required: true,
|
|
33
|
+
complete: async (partial) => {
|
|
34
|
+
const names = ["Button", "Card", "Modal"];
|
|
35
|
+
return names.filter((n) =>
|
|
36
|
+
n.toLowerCase().startsWith(partial.toLowerCase()),
|
|
37
|
+
);
|
|
38
|
+
},
|
|
39
|
+
},
|
|
40
|
+
],
|
|
41
|
+
execute: async () => createExitResult(0),
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
path: ["standard", "list"],
|
|
45
|
+
description: "List standards",
|
|
46
|
+
parameters: [],
|
|
47
|
+
execute: async () => createExitResult(0),
|
|
48
|
+
},
|
|
49
|
+
];
|
|
50
|
+
return buildCompleters(commands);
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("handleQuery", () => {
|
|
54
|
+
it("returns all nouns for empty input", async () => {
|
|
55
|
+
const result = await handleQuery("", makeTree(), ctx);
|
|
56
|
+
expect(result).toBe("block\nstandard");
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it("returns filtered nouns for partial input", async () => {
|
|
60
|
+
const result = await handleQuery("blo", makeTree(), ctx);
|
|
61
|
+
expect(result).toBe("block");
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("returns all verbs when noun is complete with trailing space", async () => {
|
|
65
|
+
const result = await handleQuery("block ", makeTree(), ctx);
|
|
66
|
+
expect(result).toBe("get\nlist");
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
it("returns filtered verbs for partial verb", async () => {
|
|
70
|
+
const result = await handleQuery("block g", makeTree(), ctx);
|
|
71
|
+
expect(result).toBe("get");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("invokes level 3 completer for argument completion", async () => {
|
|
75
|
+
const result = await handleQuery("block get Bu", makeTree(), ctx);
|
|
76
|
+
expect(result).toBe("Button");
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it("returns all level 3 candidates for empty argument partial", async () => {
|
|
80
|
+
const result = await handleQuery("block get ", makeTree(), ctx);
|
|
81
|
+
expect(result).toBe("Button\nCard\nModal");
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("returns empty string for unknown noun", async () => {
|
|
85
|
+
const result = await handleQuery("unknown verb", makeTree(), ctx);
|
|
86
|
+
expect(result).toBe("");
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
it("returns empty string for verb with no completers", async () => {
|
|
90
|
+
const result = await handleQuery("block list something", makeTree(), ctx);
|
|
91
|
+
expect(result).toBe("");
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { CommandContext, CompletionTree } from "@canonical/cli-core";
|
|
2
|
+
import { resolveCompletion } from "@canonical/cli-core";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Resolve a partial CLI input against a completion tree and return
|
|
6
|
+
* newline-separated candidates.
|
|
7
|
+
*
|
|
8
|
+
* Splits the partial into words, delegates to resolveCompletion for
|
|
9
|
+
* level detection (noun/verb/argument), invokes the matched completer,
|
|
10
|
+
* and joins the results with newlines. Returns an empty string when
|
|
11
|
+
* no completer matches.
|
|
12
|
+
*
|
|
13
|
+
* @param partial - The raw partial input string from the shell.
|
|
14
|
+
* @param tree - The completion tree built from registered commands.
|
|
15
|
+
* @param ctx - The command context providing store and config.
|
|
16
|
+
* @returns Newline-separated completion candidates, or empty string.
|
|
17
|
+
*
|
|
18
|
+
* @note Queries ke store
|
|
19
|
+
*/
|
|
20
|
+
export default async function handleQuery(
|
|
21
|
+
partial: string,
|
|
22
|
+
tree: CompletionTree,
|
|
23
|
+
ctx: CommandContext,
|
|
24
|
+
): Promise<string> {
|
|
25
|
+
const words = partial.length === 0 ? [""] : partial.split(" ");
|
|
26
|
+
const result = resolveCompletion(tree, words);
|
|
27
|
+
if (!result.completer) return "";
|
|
28
|
+
const candidates = await result.completer(result.partial, ctx);
|
|
29
|
+
return candidates.join("\n");
|
|
30
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Completions server public API.
|
|
3
|
+
*
|
|
4
|
+
* Re-exports the server entry point, client entry point, socket path
|
|
5
|
+
* computation, and query handler for use by runCli and the pragma
|
|
6
|
+
* package barrel.
|
|
7
|
+
*
|
|
8
|
+
* @module
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
export { default as computeSocketPath } from "./computeSocketPath.js";
|
|
12
|
+
export { default as handleQuery } from "./handleQuery.js";
|
|
13
|
+
export { default as queryCompletions } from "./queryCompletions.js";
|
|
14
|
+
export { default as startCompletionsServer } from "./startCompletionsServer.js";
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const existsSyncMock = vi.fn();
|
|
4
|
+
const unlinkSyncMock = vi.fn();
|
|
5
|
+
const computeSocketPathMock = vi.fn(() => "/tmp/pragma-completions.sock");
|
|
6
|
+
const querySocketMock = vi.fn();
|
|
7
|
+
const waitForSocketMock = vi.fn();
|
|
8
|
+
const spawnMock = vi.fn();
|
|
9
|
+
|
|
10
|
+
vi.mock("node:fs", () => ({
|
|
11
|
+
existsSync: existsSyncMock,
|
|
12
|
+
unlinkSync: unlinkSyncMock,
|
|
13
|
+
}));
|
|
14
|
+
|
|
15
|
+
vi.mock("./computeSocketPath.js", () => ({
|
|
16
|
+
default: computeSocketPathMock,
|
|
17
|
+
}));
|
|
18
|
+
|
|
19
|
+
vi.mock("./querySocket.js", () => ({
|
|
20
|
+
default: querySocketMock,
|
|
21
|
+
}));
|
|
22
|
+
|
|
23
|
+
vi.mock("./waitForSocket.js", () => ({
|
|
24
|
+
default: waitForSocketMock,
|
|
25
|
+
}));
|
|
26
|
+
|
|
27
|
+
describe("queryCompletions", () => {
|
|
28
|
+
let stdoutSpy: ReturnType<typeof vi.spyOn>;
|
|
29
|
+
|
|
30
|
+
beforeEach(() => {
|
|
31
|
+
vi.clearAllMocks();
|
|
32
|
+
stdoutSpy = vi
|
|
33
|
+
.spyOn(process.stdout, "write")
|
|
34
|
+
.mockImplementation(() => true);
|
|
35
|
+
vi.stubGlobal("Bun", { spawn: spawnMock });
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
afterEach(() => {
|
|
39
|
+
stdoutSpy.mockRestore();
|
|
40
|
+
vi.unstubAllGlobals();
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("uses an existing server and prints results", async () => {
|
|
44
|
+
existsSyncMock.mockReturnValue(true);
|
|
45
|
+
querySocketMock.mockResolvedValue("block\nstandard");
|
|
46
|
+
|
|
47
|
+
const { default: queryCompletions } = await import("./queryCompletions.js");
|
|
48
|
+
await queryCompletions("blo");
|
|
49
|
+
|
|
50
|
+
expect(querySocketMock).toHaveBeenCalledWith(
|
|
51
|
+
"/tmp/pragma-completions.sock",
|
|
52
|
+
"blo",
|
|
53
|
+
);
|
|
54
|
+
expect(stdoutSpy).toHaveBeenCalledWith("block\nstandard\n");
|
|
55
|
+
expect(spawnMock).not.toHaveBeenCalled();
|
|
56
|
+
});
|
|
57
|
+
|
|
58
|
+
it("cleans up a stale socket, spawns a server, and retries", async () => {
|
|
59
|
+
existsSyncMock.mockReturnValue(true);
|
|
60
|
+
querySocketMock
|
|
61
|
+
.mockRejectedValueOnce(new Error("stale socket"))
|
|
62
|
+
.mockResolvedValueOnce("block");
|
|
63
|
+
waitForSocketMock.mockResolvedValue(true);
|
|
64
|
+
|
|
65
|
+
const { default: queryCompletions } = await import("./queryCompletions.js");
|
|
66
|
+
await queryCompletions("blo");
|
|
67
|
+
|
|
68
|
+
expect(unlinkSyncMock).toHaveBeenCalledWith("/tmp/pragma-completions.sock");
|
|
69
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
70
|
+
expect(waitForSocketMock).toHaveBeenCalled();
|
|
71
|
+
expect(querySocketMock).toHaveBeenCalledTimes(2);
|
|
72
|
+
expect(stdoutSpy).toHaveBeenCalledWith("block\n");
|
|
73
|
+
});
|
|
74
|
+
|
|
75
|
+
it("returns silently when the spawned server never becomes ready", async () => {
|
|
76
|
+
existsSyncMock.mockReturnValue(false);
|
|
77
|
+
waitForSocketMock.mockResolvedValue(false);
|
|
78
|
+
|
|
79
|
+
const { default: queryCompletions } = await import("./queryCompletions.js");
|
|
80
|
+
await queryCompletions("blo");
|
|
81
|
+
|
|
82
|
+
expect(spawnMock).toHaveBeenCalledTimes(1);
|
|
83
|
+
expect(querySocketMock).not.toHaveBeenCalled();
|
|
84
|
+
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
it("swallows post-spawn query errors and empty responses", async () => {
|
|
88
|
+
existsSyncMock.mockReturnValue(false);
|
|
89
|
+
waitForSocketMock.mockResolvedValue(true);
|
|
90
|
+
querySocketMock
|
|
91
|
+
.mockResolvedValueOnce("")
|
|
92
|
+
.mockRejectedValueOnce(new Error("boom"));
|
|
93
|
+
|
|
94
|
+
const { default: queryCompletions } = await import("./queryCompletions.js");
|
|
95
|
+
await queryCompletions("blo");
|
|
96
|
+
await queryCompletions("blo");
|
|
97
|
+
|
|
98
|
+
expect(stdoutSpy).not.toHaveBeenCalled();
|
|
99
|
+
});
|
|
100
|
+
});
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
2
|
+
import { resolve } from "node:path";
|
|
3
|
+
import computeSocketPath from "./computeSocketPath.js";
|
|
4
|
+
import { SPAWN_TIMEOUT_MS } from "./constants.js";
|
|
5
|
+
import querySocket from "./querySocket.js";
|
|
6
|
+
import waitForSocket from "./waitForSocket.js";
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Client entry point for shell tab completion.
|
|
10
|
+
*
|
|
11
|
+
* Checks for a running completions server, spawns one if absent,
|
|
12
|
+
* connects via Unix socket, and prints candidates to stdout.
|
|
13
|
+
* Designed to be called by `pragma --completions <partial>`.
|
|
14
|
+
* Fails silently on error — shell completion must never block the terminal.
|
|
15
|
+
*
|
|
16
|
+
* @param partial - The partial CLI input to complete.
|
|
17
|
+
*
|
|
18
|
+
* @note Impure
|
|
19
|
+
*/
|
|
20
|
+
export default async function queryCompletions(partial: string): Promise<void> {
|
|
21
|
+
const socketPath = computeSocketPath(process.cwd());
|
|
22
|
+
|
|
23
|
+
// Try an existing server first.
|
|
24
|
+
if (existsSync(socketPath)) {
|
|
25
|
+
try {
|
|
26
|
+
const response = await querySocket(socketPath, partial);
|
|
27
|
+
if (response.length > 0) {
|
|
28
|
+
process.stdout.write(`${response}\n`);
|
|
29
|
+
}
|
|
30
|
+
return;
|
|
31
|
+
} catch {
|
|
32
|
+
// Stale socket from a crashed server — clean up and spawn fresh.
|
|
33
|
+
try {
|
|
34
|
+
unlinkSync(socketPath);
|
|
35
|
+
} catch {
|
|
36
|
+
// Socket may have been removed concurrently.
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// No running server — spawn one.
|
|
42
|
+
const binPath = resolve(import.meta.dirname, "../bin.ts");
|
|
43
|
+
Bun.spawn(["bun", "run", binPath, "_completions-server"], {
|
|
44
|
+
stdio: ["ignore", "ignore", "ignore"],
|
|
45
|
+
cwd: process.cwd(),
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
const ready = await waitForSocket(socketPath, SPAWN_TIMEOUT_MS);
|
|
49
|
+
if (!ready) {
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const response = await querySocket(socketPath, partial);
|
|
55
|
+
if (response.length > 0) {
|
|
56
|
+
process.stdout.write(`${response}\n`);
|
|
57
|
+
}
|
|
58
|
+
} catch {
|
|
59
|
+
// Silent — shell completion must never signal failure.
|
|
60
|
+
}
|
|
61
|
+
}
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import querySocket from "./querySocket.js";
|
|
3
|
+
|
|
4
|
+
const connectMock = vi.fn();
|
|
5
|
+
|
|
6
|
+
describe("querySocket", () => {
|
|
7
|
+
beforeEach(() => {
|
|
8
|
+
vi.stubGlobal("Bun", {
|
|
9
|
+
connect: connectMock,
|
|
10
|
+
});
|
|
11
|
+
connectMock.mockReset();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
afterEach(() => {
|
|
15
|
+
vi.unstubAllGlobals();
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
it("writes the partial with a newline and trims the trailing newline", async () => {
|
|
19
|
+
const write = vi.fn();
|
|
20
|
+
const flush = vi.fn();
|
|
21
|
+
|
|
22
|
+
connectMock.mockImplementation(
|
|
23
|
+
(options: {
|
|
24
|
+
socket: {
|
|
25
|
+
open: (socket: {
|
|
26
|
+
write: (value: string) => void;
|
|
27
|
+
flush: () => void;
|
|
28
|
+
}) => void;
|
|
29
|
+
data: (_socket: unknown, data: Uint8Array) => void;
|
|
30
|
+
close: () => void;
|
|
31
|
+
};
|
|
32
|
+
}) => {
|
|
33
|
+
options.socket.open({ write, flush });
|
|
34
|
+
options.socket.data({}, Buffer.from("block\n"));
|
|
35
|
+
options.socket.close();
|
|
36
|
+
return Promise.resolve({});
|
|
37
|
+
},
|
|
38
|
+
);
|
|
39
|
+
|
|
40
|
+
await expect(querySocket("/tmp/pragma.sock", "blo")).resolves.toBe("block");
|
|
41
|
+
|
|
42
|
+
expect(write).toHaveBeenCalledWith("blo\n");
|
|
43
|
+
expect(flush).toHaveBeenCalledTimes(1);
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
it("rejects when the socket emits an error", async () => {
|
|
47
|
+
const socketError = new Error("socket failed");
|
|
48
|
+
|
|
49
|
+
connectMock.mockImplementation(
|
|
50
|
+
(options: {
|
|
51
|
+
socket: { error: (_socket: unknown, err: Error) => void };
|
|
52
|
+
}) => {
|
|
53
|
+
options.socket.error({}, socketError);
|
|
54
|
+
return Promise.resolve({});
|
|
55
|
+
},
|
|
56
|
+
);
|
|
57
|
+
|
|
58
|
+
await expect(querySocket("/tmp/pragma.sock", "blo")).rejects.toThrow(
|
|
59
|
+
"socket failed",
|
|
60
|
+
);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
it("rejects when Bun.connect fails before the socket opens", async () => {
|
|
64
|
+
connectMock.mockReturnValue(Promise.reject(new Error("connect failed")));
|
|
65
|
+
|
|
66
|
+
await expect(querySocket("/tmp/pragma.sock", "blo")).rejects.toThrow(
|
|
67
|
+
"connect failed",
|
|
68
|
+
);
|
|
69
|
+
});
|
|
70
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Connect to a Unix domain socket, send a partial string, and
|
|
3
|
+
* collect the response.
|
|
4
|
+
*
|
|
5
|
+
* Opens a Bun TCP connection to the socket, writes the partial
|
|
6
|
+
* terminated by a newline, and accumulates response chunks until
|
|
7
|
+
* the server closes the connection.
|
|
8
|
+
*
|
|
9
|
+
* @param socketPath - Absolute path to the Unix domain socket.
|
|
10
|
+
* @param partial - The partial CLI input to send for completion.
|
|
11
|
+
* @returns The server's response with trailing newline stripped.
|
|
12
|
+
*
|
|
13
|
+
* @note Impure
|
|
14
|
+
*/
|
|
15
|
+
export default function querySocket(
|
|
16
|
+
socketPath: string,
|
|
17
|
+
partial: string,
|
|
18
|
+
): Promise<string> {
|
|
19
|
+
return new Promise<string>((resolve, reject) => {
|
|
20
|
+
const chunks: Buffer[] = [];
|
|
21
|
+
Bun.connect({
|
|
22
|
+
unix: socketPath,
|
|
23
|
+
socket: {
|
|
24
|
+
open(socket) {
|
|
25
|
+
socket.write(`${partial}\n`);
|
|
26
|
+
socket.flush();
|
|
27
|
+
},
|
|
28
|
+
data(_socket, data) {
|
|
29
|
+
chunks.push(Buffer.from(data));
|
|
30
|
+
},
|
|
31
|
+
close() {
|
|
32
|
+
resolve(
|
|
33
|
+
Buffer.concat(chunks)
|
|
34
|
+
.toString("utf-8")
|
|
35
|
+
.replace(/\r?\n$/, ""),
|
|
36
|
+
);
|
|
37
|
+
},
|
|
38
|
+
error(_socket, err) {
|
|
39
|
+
reject(err);
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
}).catch(reject);
|
|
43
|
+
});
|
|
44
|
+
}
|