@canonical/pragma-cli 0.29.0 → 0.31.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +128 -47
- package/dist/pragma +0 -0
- package/package.json +15 -13
- package/src/completions/constants.ts +7 -0
- package/src/completions/handleQuery.test.ts +32 -0
- package/src/completions/handleQuery.ts +20 -1
- package/src/completions/queryCompletions.test.ts +5 -0
- package/src/completions/queryCompletions.ts +17 -3
- package/src/completions/querySocket.test.ts +31 -0
- package/src/completions/querySocket.ts +36 -9
- package/src/config/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 +217 -0
- package/src/config/readConfig.test.ts +104 -13
- package/src/config/readConfig.ts +12 -153
- package/src/config/readConfigLayers.test.ts +109 -0
- package/src/config/readConfigLayers.ts +118 -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 +83 -1
- package/src/config/writeConfig.ts +25 -55
- package/src/constants.ts +3 -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 +2 -1
- package/src/domains/block/formatters/list.test.ts +3 -3
- package/src/domains/block/formatters/lookup.test.ts +5 -5
- package/src/domains/block/mcp/specs.ts +16 -196
- package/src/domains/block/orchestration/enrichBlocks.ts +6 -3
- package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
- package/src/domains/block/stories.test.ts +28 -0
- package/src/domains/block/stories.ts +260 -0
- package/src/domains/config/commands/channel.test.ts +30 -3
- package/src/domains/config/commands/channel.ts +25 -8
- package/src/domains/config/commands/configScope.ts +46 -0
- package/src/domains/config/commands/framework.test.ts +32 -1
- package/src/domains/config/commands/framework.ts +26 -8
- package/src/domains/config/commands/show.ts +5 -45
- package/src/domains/config/commands/tier.test.ts +20 -2
- package/src/domains/config/commands/tier.ts +25 -8
- package/src/domains/config/commands/trace.test.ts +19 -2
- package/src/domains/config/commands/trace.ts +6 -3
- 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 +14 -11
- 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 +7 -7
- package/src/domains/config/mcp/specs.ts +33 -53
- 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/validateTier.test.ts +9 -0
- package/src/domains/config/operations/validateTier.ts +10 -2
- package/src/domains/config/stories.ts +42 -0
- package/src/domains/config/tasks/index.ts +1 -0
- package/src/domains/config/tasks/setChannel.ts +13 -28
- package/src/domains/config/tasks/setFramework.ts +11 -30
- package/src/domains/config/tasks/setTier.ts +10 -29
- package/src/domains/config/tasks/setTrace.ts +10 -28
- package/src/domains/config/tasks/writeConfigField.ts +41 -0
- package/src/domains/create/generatorPack/bundled.ts +49 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.test.ts +159 -0
- package/src/domains/create/generatorPack/compileGeneratorPack.ts +312 -0
- package/src/domains/create/index.ts +13 -7
- package/src/domains/create/mcp/specs.ts +6 -161
- package/src/domains/create/renderGeneratorUi.test.ts +103 -0
- package/src/domains/create/renderGeneratorUi.ts +65 -0
- package/src/domains/doctor/formatters/doctor.test.ts +50 -2
- package/src/domains/doctor/formatters/doctor.ts +90 -38
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +133 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +32 -26
- 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 +160 -7
- package/src/domains/doctor/operations/runChecks.test.ts +15 -6
- package/src/domains/doctor/operations/runChecks.ts +2 -0
- package/src/domains/doctor/operations/types.ts +23 -1
- package/src/domains/graph/commands/inspect.test.ts +2 -2
- package/src/domains/graph/commands/query.test.ts +5 -3
- package/src/domains/graph/formatters/inspect.ts +7 -7
- package/src/domains/graph/formatters/query.test.ts +87 -0
- package/src/domains/graph/formatters/query.ts +42 -5
- package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
- package/src/domains/graph/helpers/resolveUri.ts +28 -21
- package/src/domains/graph/operations/inspectUri.test.ts +5 -5
- package/src/domains/graphql/commands/build.test.ts +5 -3
- package/src/domains/graphql/commands/build.ts +8 -16
- package/src/domains/graphql/commands/check.test.ts +5 -3
- package/src/domains/graphql/commands/check.ts +7 -13
- package/src/domains/graphql/commands/index.ts +1 -0
- package/src/domains/graphql/commands/serve.test.ts +46 -0
- package/src/domains/graphql/commands/serve.ts +141 -0
- package/src/domains/graphql/constants.ts +10 -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 +2 -0
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +3 -3
- package/src/domains/graphql/helpers/parsePrefixes.ts +3 -3
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +9 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +24 -3
- package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
- package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
- package/src/domains/graphql/index.ts +9 -6
- package/src/domains/graphql/operations/compileSchema.test.ts +23 -47
- package/src/domains/graphql/operations/compileSchema.ts +23 -25
- package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
- package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.test.ts +167 -0
- package/src/domains/graphql/operations/ensureSchemaArtifact.ts +178 -0
- package/src/domains/graphql/operations/index.ts +10 -0
- package/src/domains/info/formatters/upgrade.test.ts +7 -4
- package/src/domains/info/formatters/upgrade.ts +3 -1
- package/src/domains/info/operations/collectEntityCounts.test.ts +156 -0
- package/src/domains/info/operations/collectEntityCounts.ts +123 -0
- package/src/domains/info/operations/collectInfo.test.ts +14 -3
- package/src/domains/info/operations/collectInfo.ts +12 -15
- package/src/domains/info/operations/index.ts +1 -0
- package/src/domains/info/types.ts +13 -0
- package/src/domains/llm/capabilities.test.ts +24 -4
- package/src/domains/llm/commands/capabilities.ts +12 -3
- package/src/domains/llm/commands/llm.ts +5 -1
- package/src/domains/llm/data/commandReference.ts +20 -11
- package/src/domains/llm/data/decisionTrees.ts +10 -5
- package/src/domains/llm/data/toolCatalog.ts +55 -18
- package/src/domains/llm/formatters/orientation.ts +5 -1
- package/src/domains/llm/llmCommand.test.ts +11 -3
- package/src/domains/llm/mcp/specs.ts +3 -4
- package/src/domains/llm/operations/collectContext.test.ts +64 -0
- package/src/domains/llm/operations/collectContext.ts +7 -17
- package/src/domains/modifier/commands/list.ts +5 -38
- package/src/domains/modifier/commands/lookup.ts +8 -79
- package/src/domains/modifier/commands/sample.ts +2 -1
- package/src/domains/modifier/mcp/specs.ts +15 -94
- package/src/domains/modifier/operations/lookup.ts +5 -3
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
- package/src/domains/modifier/stories.test.ts +20 -0
- package/src/domains/modifier/stories.ts +86 -0
- package/src/domains/ontology/commands/list.test.ts +5 -3
- package/src/domains/ontology/commands/list.ts +5 -58
- package/src/domains/ontology/commands/show.test.ts +85 -9
- package/src/domains/ontology/commands/show.ts +6 -58
- package/src/domains/ontology/formatters/index.ts +1 -0
- package/src/domains/ontology/formatters/list.ts +37 -12
- package/src/domains/ontology/formatters/show.ts +361 -64
- package/src/domains/ontology/helpers/buildClassTree.ts +89 -0
- package/src/domains/ontology/helpers/completeClassNames.ts +43 -0
- package/src/domains/ontology/helpers/expandOntologyIris.ts +79 -0
- package/src/domains/ontology/helpers/index.ts +14 -0
- package/src/domains/ontology/helpers/queryClasses.ts +32 -16
- package/src/domains/ontology/helpers/queryProperties.ts +39 -6
- package/src/domains/ontology/helpers/queryTboxFacts.ts +160 -0
- package/src/domains/ontology/mcp/specs.ts +7 -84
- package/src/domains/ontology/operations/listOntologies.test.ts +80 -3
- package/src/domains/ontology/operations/listOntologies.ts +126 -30
- package/src/domains/ontology/operations/showOntology.test.ts +131 -12
- package/src/domains/ontology/operations/showOntology.ts +289 -7
- package/src/domains/ontology/operations/showOntologyRaw.test.ts +4 -2
- package/src/domains/ontology/stories.test.ts +36 -0
- package/src/domains/ontology/stories.ts +153 -0
- package/src/domains/refs/operations/updateRefs.ts +2 -38
- package/src/domains/setup/commands/index.ts +1 -0
- package/src/domains/setup/commands/setup.ts +70 -0
- 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/index.ts +7 -3
- package/src/domains/setup/operations/setupLsp.test.ts +3 -2
- package/src/domains/setup/operations/setupLsp.ts +7 -2
- package/src/domains/setup/operations/setupMcp.test.ts +6 -1
- package/src/domains/shared/bootStore.bareCore.test.ts +63 -0
- package/src/domains/shared/bootStore.test.ts +82 -0
- package/src/domains/shared/bootStore.ts +57 -18
- 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 +11 -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 +23 -26
- package/src/domains/shared/extractLocalName.test.ts +3 -3
- package/src/domains/shared/formatters.ts +23 -2
- package/src/domains/shared/listTiers.ts +40 -0
- package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
- package/src/domains/shared/loaders/bundledLoader.ts +158 -12
- package/src/domains/shared/loaders/gitLoader.ts +1 -2
- package/src/domains/shared/loaders/index.ts +4 -1
- package/src/domains/shared/loaders/localLoader.ts +2 -4
- package/src/domains/shared/loaders/readPackageDir.test.ts +58 -0
- package/src/domains/shared/loaders/readPackageDir.ts +82 -9
- 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 +135 -0
- package/src/domains/shared/lookupMany.ts +50 -2
- package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
- package/src/domains/shared/mergeAndParseRefs.ts +44 -0
- package/src/domains/shared/namespaces.ts +13 -5
- package/src/domains/shared/packages.ts +10 -17
- package/src/domains/shared/parseSampleCount.test.ts +47 -0
- package/src/domains/shared/parseSampleCount.ts +45 -0
- package/src/domains/shared/prefixes.test.ts +211 -0
- package/src/domains/shared/prefixes.ts +182 -15
- package/src/domains/shared/renderers.ts +5 -5
- package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
- package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
- package/src/domains/shared/resolveLookupExitCode.test.ts +48 -0
- package/src/domains/shared/resolveLookupExitCode.ts +42 -0
- package/src/domains/shared/runtime.ts +4 -63
- package/src/domains/shared/semanticPackage.test.ts +89 -0
- package/src/domains/shared/semanticPackage.ts +34 -3
- package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
- package/src/domains/shared/stories/compileLookupCommand.ts +102 -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 +151 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +111 -0
- package/src/domains/shared/stories/pack/applyPackSearch.test.ts +57 -0
- package/src/domains/shared/stories/pack/applyPackSearch.ts +41 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +81 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +154 -0
- package/src/domains/shared/stories/pack/bundled/index.ts +23 -0
- package/src/domains/shared/stories/pack/bundled/standardPack.ts +164 -0
- package/src/domains/shared/stories/pack/bundled/tierPack.ts +50 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +406 -0
- package/src/domains/shared/stories/pack/collectPackStories.ts +158 -0
- package/src/domains/shared/stories/pack/compilePackCommands.ts +50 -0
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +751 -0
- package/src/domains/shared/stories/pack/compilePackStories.ts +724 -0
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +55 -0
- package/src/domains/shared/stories/pack/index.ts +42 -0
- package/src/domains/shared/stories/pack/reservedVerbs.test.ts +213 -0
- package/src/domains/shared/stories/pack/reservedVerbs.ts +170 -0
- package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +381 -0
- package/src/domains/shared/stories/pack/types.ts +224 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +742 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +859 -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 +179 -0
- package/src/domains/shared/suggestions/expandGlob.ts +1 -0
- package/src/domains/shared/suggestions/suggestNames.ts +43 -10
- package/src/domains/shared/types/index.ts +5 -9
- package/src/domains/shared/types/ontology.ts +119 -24
- package/src/domains/shared/types/standard.ts +9 -52
- package/src/domains/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 +0 -1
- 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/token/commands/lookup.test.ts +8 -0
- package/src/domains/token/commands/lookup.ts +12 -0
- package/src/domains/token/commands/sample.ts +2 -1
- package/src/domains/token/featureFlag.ts +17 -0
- package/src/domains/token/index.ts +5 -0
- package/src/domains/token/mcp/specs.ts +18 -2
- package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
- package/src/domains/trace/commands/resolveTraceLimit.test.ts +31 -0
- package/src/domains/trace/commands/show.ts +30 -4
- package/src/domains/trace/tracePlugin.test.ts +1 -1
- package/src/index.ts +1 -13
- package/src/mcp/registerResources.test.ts +382 -74
- package/src/mcp/registerResources.ts +5 -348
- 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 +22 -5
- package/src/mcp/tools/registerTools.test.ts +167 -34
- package/src/mcp/types.ts +4 -0
- package/src/pipeline/collectCommands.test.ts +151 -1
- package/src/pipeline/collectCommands.ts +35 -13
- package/src/pipeline/createProgram.test.ts +5 -1
- package/src/pipeline/createProgram.ts +2 -2
- package/src/pipeline/firstRun.test.ts +80 -0
- package/src/pipeline/firstRun.ts +99 -0
- package/src/pipeline/parseGlobalFlags.test.ts +102 -1
- package/src/pipeline/parseGlobalFlags.ts +98 -9
- package/src/pipeline/resolveCommandKind.test.ts +12 -0
- package/src/pipeline/resolveCommandKind.ts +4 -0
- package/src/pipeline/rootHelp.test.ts +87 -0
- package/src/pipeline/rootHelp.ts +265 -0
- package/src/pipeline/runCli.orchestration.test.ts +28 -12
- package/src/pipeline/runCli.test.ts +17 -4
- package/src/pipeline/runCli.ts +202 -25
- package/src/testing/cli.ts +30 -2
- package/src/testing/createTestMcpClient.ts +4 -1
- package/src/testing/dsFixtures.ts +21 -4
- package/src/testing/index.ts +7 -0
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
- package/src/testing/integration/cli-user-stories.test.ts +63 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
- package/src/testing/integration/edge-cases.test.ts +62 -50
- package/src/testing/integration/executor-execution.test.ts +103 -0
- package/src/testing/integration/executor-golden.test.ts +145 -0
- package/src/testing/integration/mcp-surface.test.ts +40 -15
- package/src/testing/integration/parity.test.ts +136 -82
- package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
- package/src/testing/setupXdgIsolation.ts +26 -0
- package/src/testing/standardParityFixtures.ts +35 -0
- package/src/testing/store.ts +4 -4
- package/src/testing/storyFixtures.ts +63 -0
- package/src/domains/create/commands/component.test.ts +0 -166
- package/src/domains/create/commands/component.ts +0 -123
- package/src/domains/create/commands/index.ts +0 -3
- package/src/domains/create/commands/package.test.ts +0 -35
- package/src/domains/create/commands/package.ts +0 -21
- package/src/domains/create/generators.ts +0 -8
- package/src/domains/standard/commands/categories.test.ts +0 -70
- package/src/domains/standard/commands/categories.ts +0 -48
- package/src/domains/standard/commands/index.ts +0 -6
- package/src/domains/standard/commands/list.test.ts +0 -105
- package/src/domains/standard/commands/list.ts +0 -90
- package/src/domains/standard/commands/lookup.test.ts +0 -135
- package/src/domains/standard/commands/lookup.ts +0 -99
- package/src/domains/standard/commands/sample.ts +0 -57
- package/src/domains/standard/formatters/categories.test.ts +0 -44
- package/src/domains/standard/formatters/categories.ts +0 -38
- package/src/domains/standard/formatters/index.ts +0 -10
- package/src/domains/standard/formatters/list.test.ts +0 -169
- package/src/domains/standard/formatters/list.ts +0 -184
- package/src/domains/standard/formatters/lookup.test.ts +0 -95
- package/src/domains/standard/formatters/lookup.ts +0 -101
- package/src/domains/standard/formatters/sample.ts +0 -15
- package/src/domains/standard/formatters/types.ts +0 -31
- package/src/domains/standard/index.ts +0 -40
- package/src/domains/standard/mcp/index.ts +0 -2
- package/src/domains/standard/mcp/specs.ts +0 -244
- package/src/domains/standard/operations/categories.test.ts +0 -61
- package/src/domains/standard/operations/categories.ts +0 -38
- package/src/domains/standard/operations/index.ts +0 -6
- package/src/domains/standard/operations/list.test.ts +0 -75
- package/src/domains/standard/operations/list.ts +0 -64
- package/src/domains/standard/operations/lookup.test.ts +0 -58
- package/src/domains/standard/operations/lookup.ts +0 -151
- package/src/domains/standard/operations/sample.ts +0 -35
- package/src/domains/standard/orchestration/buildStandardFilters.ts +0 -11
- package/src/domains/standard/orchestration/index.ts +0 -4
- package/src/domains/standard/orchestration/resolveStandardList.ts +0 -45
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +0 -32
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +0 -33
- package/src/domains/standard/orchestration/standardEmptyError.ts +0 -29
- package/src/domains/standard/standardConfig.ts +0 -42
- package/src/domains/standard/types.ts +0 -15
- package/src/domains/tier/commands/index.ts +0 -3
- package/src/domains/tier/commands/list.test.ts +0 -67
- package/src/domains/tier/commands/list.ts +0 -59
- package/src/domains/tier/formatters/index.ts +0 -3
- package/src/domains/tier/formatters/list.test.ts +0 -33
- package/src/domains/tier/formatters/list.ts +0 -45
- package/src/domains/tier/index.ts +0 -17
- package/src/domains/tier/mcp/index.ts +0 -2
- package/src/domains/tier/mcp/specs.ts +0 -38
- package/src/domains/tier/operations/index.ts +0 -3
- package/src/domains/tier/operations/list.test.ts +0 -32
- package/src/domains/tier/operations/list.ts +0 -33
- package/src/pipeline/runInteractiveCommand.ts +0 -198
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { delimiter, isAbsolute, join } from "node:path";
|
|
3
|
+
import type { DetectedHarness } from "@canonical/harnesses";
|
|
4
|
+
import { detectHarnesses, readMcpConfig } from "@canonical/harnesses";
|
|
5
|
+
import { runTask } from "@canonical/task/node";
|
|
6
|
+
import type { CheckContext, CheckItem, CheckResult } from "../types.js";
|
|
7
|
+
|
|
8
|
+
const NAME = "MCP commands";
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* Extract the stdio command from an MCP server config entry, if any.
|
|
12
|
+
* HTTP/SSE entries (e.g. `{ "type": "http", "url": ... }`) have no command
|
|
13
|
+
* and are not checked.
|
|
14
|
+
*
|
|
15
|
+
* @param entry - Raw server entry from a harness MCP config.
|
|
16
|
+
* @returns The command string, or `undefined` when the entry is not
|
|
17
|
+
* command-based.
|
|
18
|
+
*/
|
|
19
|
+
function commandOf(entry: unknown): string | undefined {
|
|
20
|
+
if (typeof entry !== "object" || entry === null) return undefined;
|
|
21
|
+
const command = (entry as { command?: unknown }).command;
|
|
22
|
+
return typeof command === "string" && command.length > 0
|
|
23
|
+
? command
|
|
24
|
+
: undefined;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Resolve a command the way a shell would: entries containing a path
|
|
29
|
+
* separator are checked as file paths (relative to the project root),
|
|
30
|
+
* bare names are searched across `PATH`.
|
|
31
|
+
*
|
|
32
|
+
* @param command - Command string from an MCP server entry.
|
|
33
|
+
* @param cwd - Project root for resolving relative paths.
|
|
34
|
+
* @returns Whether the command resolves to an existing file.
|
|
35
|
+
* @note Impure — reads `process.env.PATH` and the filesystem.
|
|
36
|
+
*/
|
|
37
|
+
function commandResolves(command: string, cwd: string): boolean {
|
|
38
|
+
if (command.includes("/") || command.includes("\\")) {
|
|
39
|
+
return existsSync(isAbsolute(command) ? command : join(cwd, command));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
return (process.env.PATH ?? "")
|
|
43
|
+
.split(delimiter)
|
|
44
|
+
.filter((dir) => dir.length > 0)
|
|
45
|
+
.some((dir) => existsSync(join(dir, command)));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Check that every command-based MCP server entry in detected harness
|
|
50
|
+
* configs resolves to an executable. A dead entry (e.g. a leftover server
|
|
51
|
+
* whose binary was never installed) makes every agent session try and fail
|
|
52
|
+
* to boot it, so it is flagged with the offending config path.
|
|
53
|
+
*
|
|
54
|
+
* @param ctx - Check context with the working directory.
|
|
55
|
+
* @returns A CheckResult: pass when all commands resolve, fail listing the
|
|
56
|
+
* broken entries, or skip when there is nothing to check.
|
|
57
|
+
* @note Impure
|
|
58
|
+
*/
|
|
59
|
+
export default async function checkMcpCommands(
|
|
60
|
+
ctx: CheckContext,
|
|
61
|
+
): Promise<CheckResult> {
|
|
62
|
+
let detected: DetectedHarness[];
|
|
63
|
+
try {
|
|
64
|
+
detected = await runTask(detectHarnesses(ctx.cwd));
|
|
65
|
+
} catch {
|
|
66
|
+
return {
|
|
67
|
+
name: NAME,
|
|
68
|
+
status: "skip",
|
|
69
|
+
detail: "harness detection failed",
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const withConfig = detected.filter((d) => d.configExists);
|
|
74
|
+
if (withConfig.length === 0) {
|
|
75
|
+
return {
|
|
76
|
+
name: NAME,
|
|
77
|
+
status: "skip",
|
|
78
|
+
detail: "no MCP configs found",
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const broken: CheckItem[] = [];
|
|
83
|
+
let checked = 0;
|
|
84
|
+
|
|
85
|
+
for (const d of withConfig) {
|
|
86
|
+
let servers: Record<string, unknown>;
|
|
87
|
+
try {
|
|
88
|
+
servers = await runTask(readMcpConfig(d.harness, ctx.cwd));
|
|
89
|
+
} catch {
|
|
90
|
+
// Config unreadable — the "MCP configured" check reports on this.
|
|
91
|
+
continue;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
for (const [serverName, entry] of Object.entries(servers)) {
|
|
95
|
+
const command = commandOf(entry);
|
|
96
|
+
if (!command) continue;
|
|
97
|
+
|
|
98
|
+
checked += 1;
|
|
99
|
+
if (!commandResolves(command, ctx.cwd)) {
|
|
100
|
+
broken.push({
|
|
101
|
+
label: `"${serverName}"`,
|
|
102
|
+
status: "fail",
|
|
103
|
+
detail: `"${command}" not found · ${d.configPath}`,
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (checked === 0) {
|
|
110
|
+
return {
|
|
111
|
+
name: NAME,
|
|
112
|
+
status: "skip",
|
|
113
|
+
detail: "no command-based MCP servers configured",
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (broken.length === 0) {
|
|
118
|
+
return {
|
|
119
|
+
name: NAME,
|
|
120
|
+
status: "pass",
|
|
121
|
+
detail: `${checked} command${checked === 1 ? "" : "s"} resolve on PATH`,
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
return {
|
|
126
|
+
name: NAME,
|
|
127
|
+
status: "fail",
|
|
128
|
+
detail: `${broken.length} of ${checked} unresolvable`,
|
|
129
|
+
items: broken,
|
|
130
|
+
remedy:
|
|
131
|
+
"Install the missing command or remove the entry from the MCP config — every agent session tries and fails to boot it.",
|
|
132
|
+
};
|
|
133
|
+
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { DetectedHarness } from "@canonical/harnesses";
|
|
2
2
|
import { detectHarnesses, readMcpConfig } from "@canonical/harnesses";
|
|
3
|
-
import { runTask } from "@canonical/task";
|
|
3
|
+
import { runTask } from "@canonical/task/node";
|
|
4
4
|
import type { CheckContext, CheckResult } from "../types.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
@@ -6,32 +6,25 @@
|
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
8
|
import { readConfig } from "#config";
|
|
9
|
-
import { parsePackageEntry } from "../../../refs/operations/parseRef.js";
|
|
10
|
-
import readGlobalRefs from "../../../refs/operations/readGlobalRefs.js";
|
|
11
9
|
import {
|
|
12
10
|
createBundledLoader,
|
|
13
11
|
createGitLoader,
|
|
14
12
|
createLocalLoader,
|
|
15
13
|
} from "../../../shared/loaders/index.js";
|
|
16
|
-
import {
|
|
14
|
+
import { mergeAndParseRefs } from "../../../shared/mergeAndParseRefs.js";
|
|
17
15
|
import { resolveSemanticPackages } from "../../../shared/semanticPackage.js";
|
|
18
|
-
import type { CheckContext, CheckResult } from "../types.js";
|
|
16
|
+
import type { CheckContext, CheckItem, CheckResult } from "../types.js";
|
|
17
|
+
|
|
18
|
+
/** Pluralize a count, e.g. `1 graph` / `2 graphs`. */
|
|
19
|
+
function count(n: number, unit: string): string {
|
|
20
|
+
return `${n} ${unit}${n === 1 ? "" : "s"}`;
|
|
21
|
+
}
|
|
19
22
|
|
|
20
23
|
export default async function checkPackageRefs(
|
|
21
24
|
ctx: CheckContext,
|
|
22
25
|
): Promise<CheckResult> {
|
|
23
26
|
const config = readConfig(ctx.cwd);
|
|
24
|
-
const
|
|
25
|
-
|
|
26
|
-
const hasProjectRefs = config.packages && config.packages.length > 0;
|
|
27
|
-
|
|
28
|
-
const rawEntries = hasProjectRefs
|
|
29
|
-
? config.packages!
|
|
30
|
-
: globalEntries.length > 0
|
|
31
|
-
? globalEntries
|
|
32
|
-
: DEFAULT_PACKAGES;
|
|
33
|
-
|
|
34
|
-
const refs = [...rawEntries].map(parsePackageEntry);
|
|
27
|
+
const refs = mergeAndParseRefs(config.packages);
|
|
35
28
|
const loaders = [
|
|
36
29
|
createLocalLoader(),
|
|
37
30
|
createGitLoader(),
|
|
@@ -39,32 +32,45 @@ export default async function checkPackageRefs(
|
|
|
39
32
|
];
|
|
40
33
|
const packages = await resolveSemanticPackages(refs, loaders);
|
|
41
34
|
|
|
42
|
-
const
|
|
43
|
-
let
|
|
35
|
+
const items: CheckItem[] = [];
|
|
36
|
+
let unresolved = 0;
|
|
44
37
|
|
|
45
38
|
for (const ref of refs) {
|
|
46
39
|
const resolved = packages.find((p) => p.name === ref.pkg);
|
|
47
40
|
|
|
48
41
|
if (resolved) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
items.push({
|
|
43
|
+
label: ref.pkg,
|
|
44
|
+
status: "pass",
|
|
45
|
+
detail: `${resolved.source} v${resolved.version} · ${count(resolved.graphs.length, "graph")}, ${count(resolved.skills.length, "skill")}`,
|
|
46
|
+
});
|
|
52
47
|
} else {
|
|
53
48
|
// Check if covered by bundled fallback
|
|
54
49
|
const bundled = packages.find((p) => p.name === "(bundled)");
|
|
55
50
|
if (bundled) {
|
|
56
|
-
|
|
51
|
+
items.push({
|
|
52
|
+
label: ref.pkg,
|
|
53
|
+
status: "pass",
|
|
54
|
+
detail: `bundled fallback v${bundled.version}`,
|
|
55
|
+
});
|
|
57
56
|
} else {
|
|
58
|
-
|
|
59
|
-
|
|
57
|
+
items.push({ label: ref.pkg, status: "fail", detail: "not resolved" });
|
|
58
|
+
unresolved += 1;
|
|
60
59
|
}
|
|
61
60
|
}
|
|
62
61
|
}
|
|
63
62
|
|
|
63
|
+
const total = refs.length;
|
|
64
|
+
const detail =
|
|
65
|
+
unresolved > 0
|
|
66
|
+
? `${unresolved} of ${count(total, "package")} not resolved`
|
|
67
|
+
: `${count(total, "package")} resolved`;
|
|
68
|
+
|
|
64
69
|
return {
|
|
65
70
|
name: "package refs",
|
|
66
|
-
status:
|
|
67
|
-
detail
|
|
68
|
-
|
|
71
|
+
status: unresolved > 0 ? "fail" : "pass",
|
|
72
|
+
detail,
|
|
73
|
+
items,
|
|
74
|
+
...(unresolved > 0 ? { remedy: "pragma update-refs" } : {}),
|
|
69
75
|
};
|
|
70
76
|
}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import type { DetectedHarness } from "@canonical/harnesses";
|
|
3
3
|
import { detectHarnesses } from "@canonical/harnesses";
|
|
4
|
-
import { runTask } from "@canonical/task";
|
|
4
|
+
import { runTask } from "@canonical/task/node";
|
|
5
5
|
import type { CheckContext, CheckResult } from "../types.js";
|
|
6
6
|
|
|
7
7
|
/**
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
/** @module Individual environment health checks invoked by runChecks. */
|
|
2
2
|
export { default as checkConfigFile } from "./checkConfigFile.js";
|
|
3
3
|
export { default as checkKeStore } from "./checkKeStore.js";
|
|
4
|
+
export { default as checkMcpCommands } from "./checkMcpCommands.js";
|
|
4
5
|
export { default as checkMcpConfigured } from "./checkMcpConfigured.js";
|
|
5
6
|
export { default as checkNodeVersion } from "./checkNodeVersion.js";
|
|
6
7
|
export { default as checkPackageRefs } from "./checkPackageRefs.js";
|
|
@@ -1,8 +1,12 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { mkdtempSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
5
|
|
|
3
6
|
vi.mock("#config", async (importOriginal) => ({
|
|
4
7
|
...(await importOriginal<typeof import("#config")>()),
|
|
5
8
|
configExists: vi.fn(),
|
|
9
|
+
readConfigLayers: vi.fn(),
|
|
6
10
|
}));
|
|
7
11
|
vi.mock("../../shared/bootStore.js", () => ({
|
|
8
12
|
bootStore: vi.fn(),
|
|
@@ -14,17 +18,18 @@ vi.mock("@canonical/harnesses", () => ({
|
|
|
14
18
|
detectHarnesses: vi.fn(),
|
|
15
19
|
readMcpConfig: vi.fn(),
|
|
16
20
|
}));
|
|
17
|
-
vi.mock("@canonical/task", () => ({
|
|
21
|
+
vi.mock("@canonical/task/node", () => ({
|
|
18
22
|
runTask: vi.fn((task: unknown) => task),
|
|
19
23
|
}));
|
|
20
24
|
|
|
21
25
|
import { detectHarnesses, readMcpConfig } from "@canonical/harnesses";
|
|
22
|
-
import { configExists } from "#config";
|
|
26
|
+
import { configExists, readConfigLayers } from "#config";
|
|
23
27
|
import { collectStoreSummary } from "../../info/operations/index.js";
|
|
24
28
|
import { bootStore } from "../../shared/bootStore.js";
|
|
25
29
|
import {
|
|
26
30
|
checkConfigFile,
|
|
27
31
|
checkKeStore,
|
|
32
|
+
checkMcpCommands,
|
|
28
33
|
checkMcpConfigured,
|
|
29
34
|
checkNodeVersion,
|
|
30
35
|
checkPragmaVersion,
|
|
@@ -55,14 +60,55 @@ describe("checkPragmaVersion", () => {
|
|
|
55
60
|
});
|
|
56
61
|
|
|
57
62
|
describe("checkConfigFile", () => {
|
|
58
|
-
it("passes when config exists", async () => {
|
|
59
|
-
vi.mocked(
|
|
63
|
+
it("passes when a project config exists", async () => {
|
|
64
|
+
vi.mocked(readConfigLayers).mockReturnValue({
|
|
65
|
+
config: { tier: undefined, channel: "normal" },
|
|
66
|
+
origins: {
|
|
67
|
+
tier: "default",
|
|
68
|
+
channel: "default",
|
|
69
|
+
packages: "default",
|
|
70
|
+
trace: "default",
|
|
71
|
+
framework: "default",
|
|
72
|
+
},
|
|
73
|
+
global: { path: "/home/u/.config/pragma/config.json", exists: false },
|
|
74
|
+
project: { path: "/repo/pragma.config.json", exists: true },
|
|
75
|
+
});
|
|
76
|
+
const result = await checkConfigFile(ctx);
|
|
77
|
+
expect(result.status).toBe("pass");
|
|
78
|
+
expect(result.detail).toContain("/repo/pragma.config.json");
|
|
79
|
+
});
|
|
80
|
+
|
|
81
|
+
it("passes when only the global config exists", async () => {
|
|
82
|
+
vi.mocked(readConfigLayers).mockReturnValue({
|
|
83
|
+
config: { tier: undefined, channel: "normal" },
|
|
84
|
+
origins: {
|
|
85
|
+
tier: "default",
|
|
86
|
+
channel: "default",
|
|
87
|
+
packages: "default",
|
|
88
|
+
trace: "default",
|
|
89
|
+
framework: "default",
|
|
90
|
+
},
|
|
91
|
+
global: { path: "/home/u/.config/pragma/config.json", exists: true },
|
|
92
|
+
project: { path: "/repo/pragma.config.json", exists: false },
|
|
93
|
+
});
|
|
60
94
|
const result = await checkConfigFile(ctx);
|
|
61
95
|
expect(result.status).toBe("pass");
|
|
96
|
+
expect(result.detail).toContain("global config active");
|
|
62
97
|
});
|
|
63
98
|
|
|
64
|
-
it("fails with remedy when
|
|
65
|
-
vi.mocked(
|
|
99
|
+
it("fails with remedy when neither layer exists", async () => {
|
|
100
|
+
vi.mocked(readConfigLayers).mockReturnValue({
|
|
101
|
+
config: { tier: undefined, channel: "normal" },
|
|
102
|
+
origins: {
|
|
103
|
+
tier: "default",
|
|
104
|
+
channel: "default",
|
|
105
|
+
packages: "default",
|
|
106
|
+
trace: "default",
|
|
107
|
+
framework: "default",
|
|
108
|
+
},
|
|
109
|
+
global: { path: "/home/u/.config/pragma/config.json", exists: false },
|
|
110
|
+
project: { path: "/repo/pragma.config.json", exists: false },
|
|
111
|
+
});
|
|
66
112
|
const result = await checkConfigFile(ctx);
|
|
67
113
|
expect(result.status).toBe("fail");
|
|
68
114
|
expect(result.remedy).toBeDefined();
|
|
@@ -145,6 +191,113 @@ describe("checkMcpConfigured", () => {
|
|
|
145
191
|
});
|
|
146
192
|
});
|
|
147
193
|
|
|
194
|
+
describe("checkMcpCommands", () => {
|
|
195
|
+
const harness = {
|
|
196
|
+
id: "claude-code",
|
|
197
|
+
name: "Claude Code",
|
|
198
|
+
configPath: () => "/test/.mcp.json",
|
|
199
|
+
skillsPath: () => "/test/.claude/skills",
|
|
200
|
+
detect: [],
|
|
201
|
+
version: "*",
|
|
202
|
+
configFormat: "json" as const,
|
|
203
|
+
mcpKey: "mcpServers",
|
|
204
|
+
};
|
|
205
|
+
const detected = {
|
|
206
|
+
harness,
|
|
207
|
+
confidence: "high" as const,
|
|
208
|
+
configExists: true,
|
|
209
|
+
configPath: "/test/.mcp.json",
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
afterEach(() => {
|
|
213
|
+
vi.unstubAllEnvs();
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
it("skips when no harnesses detected", async () => {
|
|
217
|
+
vi.mocked(detectHarnesses).mockReturnValue(Promise.resolve([]) as never);
|
|
218
|
+
const result = await checkMcpCommands(ctx);
|
|
219
|
+
expect(result.status).toBe("skip");
|
|
220
|
+
});
|
|
221
|
+
|
|
222
|
+
it("skips when only URL-based servers are configured", async () => {
|
|
223
|
+
vi.mocked(detectHarnesses).mockReturnValue(
|
|
224
|
+
Promise.resolve([detected]) as never,
|
|
225
|
+
);
|
|
226
|
+
vi.mocked(readMcpConfig).mockReturnValue(
|
|
227
|
+
Promise.resolve({
|
|
228
|
+
figma: { type: "http", url: "https://mcp.figma.com/mcp" },
|
|
229
|
+
}) as never,
|
|
230
|
+
);
|
|
231
|
+
|
|
232
|
+
const result = await checkMcpCommands(ctx);
|
|
233
|
+
expect(result.status).toBe("skip");
|
|
234
|
+
expect(result.detail).toContain("no command-based");
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
it("passes when every configured command resolves on PATH", async () => {
|
|
238
|
+
const binDir = mkdtempSync(join(tmpdir(), "pragma-doctor-"));
|
|
239
|
+
writeFileSync(join(binDir, "pragma"), "#!/bin/sh\n", { mode: 0o755 });
|
|
240
|
+
vi.stubEnv("PATH", binDir);
|
|
241
|
+
|
|
242
|
+
vi.mocked(detectHarnesses).mockReturnValue(
|
|
243
|
+
Promise.resolve([detected]) as never,
|
|
244
|
+
);
|
|
245
|
+
vi.mocked(readMcpConfig).mockReturnValue(
|
|
246
|
+
Promise.resolve({
|
|
247
|
+
pragma: { command: "pragma", args: ["mcp"] },
|
|
248
|
+
}) as never,
|
|
249
|
+
);
|
|
250
|
+
|
|
251
|
+
const result = await checkMcpCommands(ctx);
|
|
252
|
+
expect(result.status).toBe("pass");
|
|
253
|
+
expect(result.detail).toContain("1 command");
|
|
254
|
+
});
|
|
255
|
+
|
|
256
|
+
it("fails and names the entry whose command does not resolve", async () => {
|
|
257
|
+
const emptyDir = mkdtempSync(join(tmpdir(), "pragma-doctor-empty-"));
|
|
258
|
+
vi.stubEnv("PATH", emptyDir);
|
|
259
|
+
|
|
260
|
+
vi.mocked(detectHarnesses).mockReturnValue(
|
|
261
|
+
Promise.resolve([detected]) as never,
|
|
262
|
+
);
|
|
263
|
+
vi.mocked(readMcpConfig).mockReturnValue(
|
|
264
|
+
Promise.resolve({
|
|
265
|
+
sem: { command: "sem", args: ["mcp"] },
|
|
266
|
+
}) as never,
|
|
267
|
+
);
|
|
268
|
+
|
|
269
|
+
const result = await checkMcpCommands(ctx);
|
|
270
|
+
expect(result.status).toBe("fail");
|
|
271
|
+
const semItem = result.items?.find((i) => i.label === '"sem"');
|
|
272
|
+
expect(semItem).toBeDefined();
|
|
273
|
+
expect(semItem?.detail).toContain("/test/.mcp.json");
|
|
274
|
+
expect(result.remedy).toBeDefined();
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
it("flags only the broken entry among several", async () => {
|
|
278
|
+
const binDir = mkdtempSync(join(tmpdir(), "pragma-doctor-mixed-"));
|
|
279
|
+
writeFileSync(join(binDir, "pragma"), "#!/bin/sh\n", { mode: 0o755 });
|
|
280
|
+
vi.stubEnv("PATH", binDir);
|
|
281
|
+
|
|
282
|
+
vi.mocked(detectHarnesses).mockReturnValue(
|
|
283
|
+
Promise.resolve([detected]) as never,
|
|
284
|
+
);
|
|
285
|
+
vi.mocked(readMcpConfig).mockReturnValue(
|
|
286
|
+
Promise.resolve({
|
|
287
|
+
pragma: { command: "pragma", args: ["mcp"] },
|
|
288
|
+
sem: { command: "sem", args: ["mcp"] },
|
|
289
|
+
figma: { type: "http", url: "https://mcp.figma.com/mcp" },
|
|
290
|
+
}) as never,
|
|
291
|
+
);
|
|
292
|
+
|
|
293
|
+
const result = await checkMcpCommands(ctx);
|
|
294
|
+
expect(result.status).toBe("fail");
|
|
295
|
+
const labels = (result.items ?? []).map((i) => i.label);
|
|
296
|
+
expect(labels).toContain('"sem"');
|
|
297
|
+
expect(labels).not.toContain('"pragma"');
|
|
298
|
+
});
|
|
299
|
+
});
|
|
300
|
+
|
|
148
301
|
describe("checkSkillsSymlinked", () => {
|
|
149
302
|
it("skips when no harnesses detected", async () => {
|
|
150
303
|
vi.mocked(detectHarnesses).mockReturnValue(Promise.resolve([]) as never);
|
|
@@ -8,12 +8,14 @@ vi.mock("./checks/index.js", () => ({
|
|
|
8
8
|
checkKeStore: vi.fn(),
|
|
9
9
|
checkShellCompletions: vi.fn(),
|
|
10
10
|
checkMcpConfigured: vi.fn(),
|
|
11
|
+
checkMcpCommands: vi.fn(),
|
|
11
12
|
checkSkillsSymlinked: vi.fn(),
|
|
12
13
|
}));
|
|
13
14
|
|
|
14
15
|
import {
|
|
15
16
|
checkConfigFile,
|
|
16
17
|
checkKeStore,
|
|
18
|
+
checkMcpCommands,
|
|
17
19
|
checkMcpConfigured,
|
|
18
20
|
checkNodeVersion,
|
|
19
21
|
checkPackageRefs,
|
|
@@ -50,13 +52,14 @@ describe("runChecks", () => {
|
|
|
50
52
|
vi.mocked(checkKeStore).mockResolvedValue(pass("store"));
|
|
51
53
|
vi.mocked(checkShellCompletions).mockResolvedValue(pass("completions"));
|
|
52
54
|
vi.mocked(checkMcpConfigured).mockResolvedValue(pass("mcp"));
|
|
55
|
+
vi.mocked(checkMcpCommands).mockResolvedValue(pass("mcp commands"));
|
|
53
56
|
vi.mocked(checkSkillsSymlinked).mockResolvedValue(pass("skills"));
|
|
54
57
|
|
|
55
58
|
const data = await runChecks({ cwd: "/test" });
|
|
56
|
-
expect(data.passed).toBe(
|
|
59
|
+
expect(data.passed).toBe(9);
|
|
57
60
|
expect(data.failed).toBe(0);
|
|
58
61
|
expect(data.skipped).toBe(0);
|
|
59
|
-
expect(data.checks).toHaveLength(
|
|
62
|
+
expect(data.checks).toHaveLength(9);
|
|
60
63
|
});
|
|
61
64
|
|
|
62
65
|
it("counts failures and skips correctly", async () => {
|
|
@@ -67,10 +70,11 @@ describe("runChecks", () => {
|
|
|
67
70
|
vi.mocked(checkKeStore).mockResolvedValue(fail("store"));
|
|
68
71
|
vi.mocked(checkShellCompletions).mockResolvedValue(fail("completions"));
|
|
69
72
|
vi.mocked(checkMcpConfigured).mockResolvedValue(pass("mcp"));
|
|
73
|
+
vi.mocked(checkMcpCommands).mockResolvedValue(pass("mcp commands"));
|
|
70
74
|
vi.mocked(checkSkillsSymlinked).mockResolvedValue(skip("skills"));
|
|
71
75
|
|
|
72
76
|
const data = await runChecks({ cwd: "/test" });
|
|
73
|
-
expect(data.passed).toBe(
|
|
77
|
+
expect(data.passed).toBe(5);
|
|
74
78
|
expect(data.failed).toBe(3);
|
|
75
79
|
expect(data.skipped).toBe(1);
|
|
76
80
|
});
|
|
@@ -83,6 +87,7 @@ describe("runChecks", () => {
|
|
|
83
87
|
vi.mocked(checkKeStore).mockResolvedValue(pass("store"));
|
|
84
88
|
vi.mocked(checkShellCompletions).mockResolvedValue(pass("completions"));
|
|
85
89
|
vi.mocked(checkMcpConfigured).mockResolvedValue(pass("mcp"));
|
|
90
|
+
vi.mocked(checkMcpCommands).mockResolvedValue(pass("mcp commands"));
|
|
86
91
|
vi.mocked(checkSkillsSymlinked).mockResolvedValue(pass("skills"));
|
|
87
92
|
|
|
88
93
|
const data = await runChecks({ cwd: "/test" });
|
|
@@ -91,7 +96,7 @@ describe("runChecks", () => {
|
|
|
91
96
|
expect(data.checks[2].name).toBe("config");
|
|
92
97
|
expect(data.checks[3].name).toBe("refs");
|
|
93
98
|
expect(data.checks[4].name).toBe("store");
|
|
94
|
-
expect(data.checks[
|
|
99
|
+
expect(data.checks[8].name).toBe("skills");
|
|
95
100
|
});
|
|
96
101
|
|
|
97
102
|
it("preserves order even when checks resolve out of order", async () => {
|
|
@@ -109,6 +114,7 @@ describe("runChecks", () => {
|
|
|
109
114
|
vi.mocked(checkKeStore).mockResolvedValue(pass("store"));
|
|
110
115
|
vi.mocked(checkShellCompletions).mockResolvedValue(pass("completions"));
|
|
111
116
|
vi.mocked(checkMcpConfigured).mockResolvedValue(pass("mcp"));
|
|
117
|
+
vi.mocked(checkMcpCommands).mockResolvedValue(pass("mcp commands"));
|
|
112
118
|
vi.mocked(checkSkillsSymlinked).mockReturnValue(delayed(pass("skills"), 0));
|
|
113
119
|
|
|
114
120
|
const data = await runChecks({ cwd: "/test" });
|
|
@@ -120,6 +126,7 @@ describe("runChecks", () => {
|
|
|
120
126
|
"store",
|
|
121
127
|
"completions",
|
|
122
128
|
"mcp",
|
|
129
|
+
"mcp commands",
|
|
123
130
|
"skills",
|
|
124
131
|
]);
|
|
125
132
|
});
|
|
@@ -132,13 +139,14 @@ describe("runChecks", () => {
|
|
|
132
139
|
vi.mocked(checkKeStore).mockResolvedValue(pass("store"));
|
|
133
140
|
vi.mocked(checkShellCompletions).mockResolvedValue(pass("completions"));
|
|
134
141
|
vi.mocked(checkMcpConfigured).mockResolvedValue(pass("mcp"));
|
|
142
|
+
vi.mocked(checkMcpCommands).mockResolvedValue(pass("mcp commands"));
|
|
135
143
|
vi.mocked(checkSkillsSymlinked).mockResolvedValue(pass("skills"));
|
|
136
144
|
|
|
137
145
|
const data = await runChecks({ cwd: "/test" });
|
|
138
146
|
|
|
139
147
|
// The whole run still completes; the thrown check becomes a fail in place.
|
|
140
|
-
expect(data.checks).toHaveLength(
|
|
141
|
-
expect(data.passed).toBe(
|
|
148
|
+
expect(data.checks).toHaveLength(9);
|
|
149
|
+
expect(data.passed).toBe(8);
|
|
142
150
|
expect(data.failed).toBe(1);
|
|
143
151
|
const failed = data.checks[2];
|
|
144
152
|
expect(failed.status).toBe("fail");
|
|
@@ -157,6 +165,7 @@ describe("runChecks", () => {
|
|
|
157
165
|
vi.mocked(checkKeStore).mockRejectedValue("string failure");
|
|
158
166
|
vi.mocked(checkShellCompletions).mockResolvedValue(pass("completions"));
|
|
159
167
|
vi.mocked(checkMcpConfigured).mockResolvedValue(pass("mcp"));
|
|
168
|
+
vi.mocked(checkMcpCommands).mockResolvedValue(pass("mcp commands"));
|
|
160
169
|
vi.mocked(checkSkillsSymlinked).mockResolvedValue(pass("skills"));
|
|
161
170
|
|
|
162
171
|
const data = await runChecks({ cwd: "/test" });
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
checkConfigFile,
|
|
3
3
|
checkKeStore,
|
|
4
|
+
checkMcpCommands,
|
|
4
5
|
checkMcpConfigured,
|
|
5
6
|
checkNodeVersion,
|
|
6
7
|
checkPackageRefs,
|
|
@@ -28,6 +29,7 @@ function buildChecks(
|
|
|
28
29
|
["ke store", checkKeStore(ctx)],
|
|
29
30
|
["Shell completions", checkShellCompletions()],
|
|
30
31
|
["MCP configured", checkMcpConfigured(ctx)],
|
|
32
|
+
["MCP commands", checkMcpCommands(ctx)],
|
|
31
33
|
["Skills symlinked", checkSkillsSymlinked(ctx)],
|
|
32
34
|
];
|
|
33
35
|
}
|
|
@@ -1,8 +1,30 @@
|
|
|
1
|
+
/** Status of a doctor check or one of its sub-items. */
|
|
2
|
+
export type CheckStatus = "pass" | "fail" | "skip";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* A structured sub-item under a check — e.g. one resolved package under
|
|
6
|
+
* `package refs`, or one unresolvable server under `MCP commands`. Lets the
|
|
7
|
+
* formatter render an indented, aligned breakdown instead of cramming
|
|
8
|
+
* everything into a single `detail` string.
|
|
9
|
+
*/
|
|
10
|
+
export interface CheckItem {
|
|
11
|
+
/** Left-column label (e.g. the package or server name). */
|
|
12
|
+
readonly label: string;
|
|
13
|
+
/** Right-column detail (e.g. `git v0.1.2 · 362 graphs`). */
|
|
14
|
+
readonly detail?: string;
|
|
15
|
+
/** Optional per-item status, rendered as its own icon. */
|
|
16
|
+
readonly status?: CheckStatus;
|
|
17
|
+
}
|
|
18
|
+
|
|
1
19
|
/** Result of a single doctor check. */
|
|
2
20
|
export interface CheckResult {
|
|
3
21
|
readonly name: string;
|
|
4
|
-
readonly status:
|
|
22
|
+
readonly status: CheckStatus;
|
|
23
|
+
/** One-line headline shown next to the check name. */
|
|
5
24
|
readonly detail: string;
|
|
25
|
+
/** Optional structured breakdown, rendered as indented sub-items. */
|
|
26
|
+
readonly items?: readonly CheckItem[];
|
|
27
|
+
/** Remedial instruction shown inline under a failing check. */
|
|
6
28
|
readonly remedy?: string;
|
|
7
29
|
}
|
|
8
30
|
|
|
@@ -2,7 +2,7 @@ import type { CommandDefinition } from "@canonical/cli-core";
|
|
|
2
2
|
import { afterAll, beforeAll, describe, expect, it } from "vitest";
|
|
3
3
|
import createTestRuntime from "../../../testing/helpers/createTestRuntime.js";
|
|
4
4
|
import type { PragmaContext } from "../../shared/context.js";
|
|
5
|
-
import {
|
|
5
|
+
import { DEFAULT_PREFIX_MAP } from "../../shared/prefixes.js";
|
|
6
6
|
import type { PragmaRuntime } from "../../shared/runtime.js";
|
|
7
7
|
import type { InspectResult } from "../../shared/types/index.js";
|
|
8
8
|
import buildInspectCommand from "./inspect.js";
|
|
@@ -37,7 +37,7 @@ async function executeOutput(
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
describe("graph inspect command", () => {
|
|
40
|
-
const uri = `${
|
|
40
|
+
const uri = `${DEFAULT_PREFIX_MAP.ds}global.component.button`;
|
|
41
41
|
|
|
42
42
|
it("returns inspect results", async () => {
|
|
43
43
|
const ctx = makeCtx();
|
|
@@ -58,15 +58,17 @@ describe("graph query command", () => {
|
|
|
58
58
|
expect(text).toContain("n");
|
|
59
59
|
});
|
|
60
60
|
|
|
61
|
-
it("renders llm output", async () => {
|
|
61
|
+
it("renders llm output as a Markdown table for SELECT", async () => {
|
|
62
62
|
const ctx = makeCtx({
|
|
63
63
|
globalFlags: { llm: true, format: "text" as const, verbose: false },
|
|
64
64
|
});
|
|
65
65
|
const cmd = buildQueryCommand(ctx);
|
|
66
66
|
const { text } = await executeOutput(cmd, { sparql }, ctx);
|
|
67
67
|
|
|
68
|
-
|
|
69
|
-
expect(
|
|
68
|
+
// Condensed Markdown, not a raw JSON dump.
|
|
69
|
+
expect(text).toContain("| n |");
|
|
70
|
+
expect(text).toContain("| --- |");
|
|
71
|
+
expect(() => JSON.parse(text)).toThrow();
|
|
70
72
|
});
|
|
71
73
|
|
|
72
74
|
it("renders json output", async () => {
|