@canonical/pragma-cli 0.27.1-experimental.0 → 0.30.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +67 -15
- package/dist/pragma +0 -0
- package/package.json +16 -17
- package/src/config/defaultConfig.test.ts +33 -0
- package/src/config/defaultConfig.ts +59 -0
- package/src/config/defaults.json +17 -0
- package/src/config/findProjectConfigPath.test.ts +53 -0
- package/src/config/findProjectConfigPath.ts +36 -0
- package/src/config/index.ts +17 -2
- package/src/config/mergeConfigFileUpdate.ts +58 -0
- package/src/config/parseConfigValues.ts +215 -0
- package/src/config/readConfig.test.ts +104 -13
- package/src/config/readConfig.ts +12 -117
- package/src/config/readConfigLayers.test.ts +107 -0
- package/src/config/readConfigLayers.ts +115 -0
- package/src/config/resolveGlobalConfigPath.ts +13 -0
- package/src/config/resolveWriteConfigPath.test.ts +53 -0
- package/src/config/resolveWriteConfigPath.ts +31 -0
- package/src/config/types.ts +88 -2
- package/src/config/writeConfig.ts +25 -43
- package/src/constants.ts +12 -1
- package/src/domains/block/commands/index.ts +2 -1
- package/src/domains/block/commands/list.ts +5 -90
- package/src/domains/block/commands/lookup.ts +8 -167
- package/src/domains/block/commands/sample.ts +55 -0
- package/src/domains/block/formatters/index.ts +2 -1
- package/src/domains/block/formatters/sample.ts +21 -0
- package/src/domains/block/index.ts +3 -3
- package/src/domains/block/mcp/specs.ts +36 -168
- package/src/domains/block/operations/index.ts +2 -1
- package/src/domains/block/operations/lookup.ts +9 -0
- package/src/domains/block/operations/sample.ts +41 -0
- package/src/domains/block/orchestration/blockEmptyError.test.ts +26 -0
- package/src/domains/block/orchestration/resolveBlockLookup.ts +11 -3
- package/src/domains/block/stories.test.ts +28 -0
- package/src/domains/block/stories.ts +254 -0
- package/src/domains/config/commands/channel.test.ts +14 -3
- package/src/domains/config/commands/channel.ts +11 -7
- package/src/domains/config/commands/configScope.ts +46 -0
- package/src/domains/config/commands/framework.test.ts +105 -0
- package/src/domains/config/commands/framework.ts +91 -0
- package/src/domains/config/commands/index.ts +2 -0
- package/src/domains/config/commands/show.ts +5 -45
- package/src/domains/config/commands/tier.test.ts +13 -2
- package/src/domains/config/commands/tier.ts +11 -7
- package/src/domains/config/commands/trace.test.ts +165 -0
- package/src/domains/config/commands/trace.ts +83 -0
- package/src/domains/config/formatters/channel.test.ts +20 -6
- package/src/domains/config/formatters/channel.ts +14 -11
- package/src/domains/config/formatters/framework.ts +37 -0
- package/src/domains/config/formatters/index.ts +2 -0
- package/src/domains/config/formatters/show.test.ts +27 -0
- package/src/domains/config/formatters/show.ts +27 -4
- package/src/domains/config/formatters/tier.test.ts +26 -10
- package/src/domains/config/formatters/tier.ts +19 -11
- package/src/domains/config/formatters/trace.ts +28 -0
- package/src/domains/config/index.ts +15 -2
- package/src/domains/config/mcp/specs.ts +33 -53
- package/src/domains/config/operations/index.ts +1 -0
- package/src/domains/config/operations/show.test.ts +18 -0
- package/src/domains/config/operations/show.ts +7 -1
- package/src/domains/config/operations/types.ts +7 -0
- package/src/domains/config/operations/validateFramework.ts +24 -0
- package/src/domains/config/stories.ts +42 -0
- package/src/domains/config/tasks/index.ts +3 -0
- package/src/domains/config/tasks/setChannel.ts +13 -22
- package/src/domains/config/tasks/setFramework.ts +21 -0
- package/src/domains/config/tasks/setTier.ts +10 -23
- package/src/domains/config/tasks/setTrace.ts +20 -0
- package/src/domains/config/tasks/writeConfigField.ts +41 -0
- package/src/domains/create/commands/component.ts +5 -5
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -5
- package/src/domains/doctor/operations/checks/checkKeStore.ts +5 -5
- package/src/domains/doctor/operations/checks/checkMcpCommands.ts +130 -0
- package/src/domains/doctor/operations/checks/checkMcpConfigured.ts +1 -1
- package/src/domains/doctor/operations/checks/checkPackageRefs.ts +30 -48
- package/src/domains/doctor/operations/checks/checkSkillsSymlinked.ts +1 -1
- package/src/domains/doctor/operations/checks/index.ts +1 -0
- package/src/domains/doctor/operations/checks.test.ts +162 -8
- package/src/domains/doctor/operations/runChecks.test.ts +85 -4
- package/src/domains/doctor/operations/runChecks.ts +50 -12
- package/src/domains/graph/helpers/resolveUri.test.ts +50 -0
- package/src/domains/graph/helpers/resolveUri.ts +28 -21
- package/src/domains/graphql/commands/build.test.ts +196 -0
- package/src/domains/graphql/commands/build.ts +117 -0
- package/src/domains/graphql/commands/check.test.ts +141 -0
- package/src/domains/graphql/commands/check.ts +81 -0
- package/src/domains/graphql/commands/index.ts +5 -0
- package/src/domains/graphql/commands/serve.test.ts +40 -0
- package/src/domains/graphql/commands/serve.ts +131 -0
- package/src/domains/graphql/constants.ts +10 -0
- package/src/domains/graphql/formatters/index.ts +3 -0
- package/src/domains/graphql/formatters/report.ts +78 -0
- package/src/domains/graphql/helpers/gatherSources.test.ts +92 -0
- package/src/domains/graphql/helpers/gatherSources.ts +58 -0
- package/src/domains/graphql/helpers/index.ts +6 -0
- package/src/domains/graphql/helpers/parsePrefixes.test.ts +44 -0
- package/src/domains/graphql/helpers/parsePrefixes.ts +38 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.test.ts +56 -0
- package/src/domains/graphql/helpers/resolveSourceFiles.ts +45 -0
- package/src/domains/graphql/helpers/routeGraphql.test.ts +43 -0
- package/src/domains/graphql/helpers/routeGraphql.ts +28 -0
- package/src/domains/graphql/index.ts +24 -0
- package/src/domains/graphql/operations/compileSchema.test.ts +95 -0
- package/src/domains/graphql/operations/compileSchema.ts +107 -0
- package/src/domains/graphql/operations/createSchemaServer.test.ts +50 -0
- package/src/domains/graphql/operations/createSchemaServer.ts +75 -0
- package/src/domains/graphql/operations/index.ts +12 -0
- package/src/domains/graphql/types.ts +29 -0
- package/src/domains/info/operations/collectInfo.test.ts +16 -5
- package/src/domains/info/operations/collectInfo.ts +16 -19
- package/src/domains/llm/capabilities.test.ts +91 -0
- package/src/domains/llm/commands/capabilities.ts +28 -0
- package/src/domains/llm/commands/index.ts +1 -0
- package/src/domains/llm/data/commandReference.ts +90 -18
- package/src/domains/llm/data/decisionTrees.ts +15 -13
- package/src/domains/llm/data/index.ts +2 -0
- package/src/domains/llm/data/toolCatalog.ts +190 -0
- package/src/domains/llm/formatters/capabilities.ts +50 -0
- package/src/domains/llm/formatters/index.ts +1 -0
- package/src/domains/llm/index.ts +3 -2
- package/src/domains/llm/mcp/index.ts +1 -1
- package/src/domains/llm/mcp/specs.ts +59 -52
- package/src/domains/llm/types.ts +40 -0
- package/src/domains/modifier/commands/index.ts +2 -1
- package/src/domains/modifier/commands/list.ts +5 -38
- package/src/domains/modifier/commands/lookup.ts +8 -79
- package/src/domains/modifier/commands/sample.ts +53 -0
- package/src/domains/modifier/formatters/index.ts +2 -1
- package/src/domains/modifier/formatters/sample.ts +15 -0
- package/src/domains/modifier/index.ts +7 -3
- package/src/domains/modifier/mcp/specs.ts +38 -70
- package/src/domains/modifier/operations/index.ts +2 -1
- package/src/domains/modifier/operations/lookup.ts +9 -0
- package/src/domains/modifier/operations/sample.ts +32 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.test.ts +26 -0
- package/src/domains/modifier/orchestration/modifierEmptyError.ts +7 -2
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +15 -2
- package/src/domains/modifier/stories.test.ts +20 -0
- package/src/domains/modifier/stories.ts +77 -0
- package/src/domains/ontology/commands/list.ts +5 -58
- package/src/domains/ontology/commands/show.ts +6 -58
- package/src/domains/ontology/formatters/show.ts +7 -1
- package/src/domains/ontology/mcp/specs.ts +7 -84
- package/src/domains/ontology/stories.test.ts +36 -0
- package/src/domains/ontology/stories.ts +108 -0
- package/src/domains/refs/operations/index.ts +1 -1
- package/src/domains/refs/operations/paths.ts +29 -1
- package/src/domains/refs/operations/updateRefs.test.ts +6 -3
- package/src/domains/refs/operations/updateRefs.ts +2 -36
- package/src/domains/setup/commands/skills.test.ts +6 -2
- package/src/domains/setup/commands/skills.ts +6 -2
- package/src/domains/setup/helpers/runSetupTask.test.ts +148 -150
- package/src/domains/setup/helpers/runSetupTask.ts +62 -70
- package/src/domains/setup/operations/setupMcp.test.ts +6 -1
- package/src/domains/shared/bootStore.ts +92 -50
- package/src/domains/shared/compactUri.test.ts +11 -0
- package/src/domains/shared/compactUri.ts +10 -5
- package/src/domains/shared/context.ts +2 -2
- package/src/domains/shared/contracts.ts +12 -0
- package/src/domains/shared/createInteractivePromptSession.test.ts +439 -0
- package/src/domains/shared/createInteractivePromptSession.ts +236 -0
- package/src/domains/shared/emptyErrorRecovery.test.ts +109 -0
- package/src/domains/shared/loaders/bundledLoader.test.ts +248 -0
- package/src/domains/shared/loaders/bundledLoader.ts +206 -0
- package/src/domains/shared/loaders/gitLoader.ts +31 -0
- package/src/domains/shared/loaders/index.ts +9 -0
- package/src/domains/shared/loaders/localLoader.ts +87 -0
- package/src/domains/shared/loaders/readPackageDir.ts +147 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.test.ts +84 -0
- package/src/domains/shared/loaders/storyAssetsPlugin.ts +46 -0
- package/src/domains/shared/lookupMany.test.ts +120 -0
- package/src/domains/shared/lookupMany.ts +43 -3
- package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
- package/src/domains/shared/mergeAndParseRefs.ts +44 -0
- package/src/domains/shared/packages.ts +14 -120
- package/src/domains/shared/pickRandom.ts +22 -0
- package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
- package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
- package/src/domains/shared/runtime.ts +7 -63
- package/src/domains/shared/sampleFormatters.ts +47 -0
- package/src/domains/shared/semanticPackage.ts +112 -0
- package/src/domains/shared/stories/compileLookupCommand.test.ts +145 -0
- package/src/domains/shared/stories/compileLookupCommand.ts +100 -0
- package/src/domains/shared/stories/compileLookupTool.test.ts +105 -0
- package/src/domains/shared/stories/compileLookupTool.ts +96 -0
- package/src/domains/shared/stories/compileReadCommand.test.ts +121 -0
- package/src/domains/shared/stories/compileReadCommand.ts +48 -0
- package/src/domains/shared/stories/compileReadTool.test.ts +64 -0
- package/src/domains/shared/stories/compileReadTool.ts +38 -0
- package/src/domains/shared/stories/condense.test.ts +18 -0
- package/src/domains/shared/stories/condense.ts +15 -0
- package/src/domains/shared/stories/index.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.test.ts +20 -0
- package/src/domains/shared/stories/normalizeNames.ts +22 -0
- package/src/domains/shared/stories/pack/applyPackFilters.test.ts +102 -0
- package/src/domains/shared/stories/pack/applyPackFilters.ts +76 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.test.ts +51 -0
- package/src/domains/shared/stories/pack/buildLookupQuery.ts +80 -0
- package/src/domains/shared/stories/pack/collectPackStories.test.ts +108 -0
- package/src/domains/shared/stories/pack/collectPackStories.ts +76 -0
- package/src/domains/shared/stories/pack/compilePackCommands.ts +40 -0
- package/src/domains/shared/stories/pack/compilePackStories.test.ts +146 -0
- package/src/domains/shared/stories/pack/compilePackStories.ts +253 -0
- package/src/domains/shared/stories/pack/compilePackToolSpecs.ts +43 -0
- package/src/domains/shared/stories/pack/index.ts +28 -0
- package/src/domains/shared/stories/pack/runSelectQuery.ts +31 -0
- package/src/domains/shared/stories/pack/standardParity.test.ts +308 -0
- package/src/domains/shared/stories/pack/types.ts +104 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.test.ts +240 -0
- package/src/domains/shared/stories/pack/validateStoryPackDefinition.ts +408 -0
- package/src/domains/shared/stories/projectCliParameters.test.ts +59 -0
- package/src/domains/shared/stories/projectCliParameters.ts +57 -0
- package/src/domains/shared/stories/projectToolParams.test.ts +50 -0
- package/src/domains/shared/stories/projectToolParams.ts +32 -0
- package/src/domains/shared/stories/requirePragmaContext.test.ts +23 -0
- package/src/domains/shared/stories/requirePragmaContext.ts +23 -0
- package/src/domains/shared/stories/resolveLookupDetailed.test.ts +57 -0
- package/src/domains/shared/stories/resolveLookupDetailed.ts +27 -0
- package/src/domains/shared/stories/types.ts +171 -0
- package/src/domains/shared/suggestions/detectCrossDomain.test.ts +43 -0
- package/src/domains/shared/suggestions/detectCrossDomain.ts +116 -0
- package/src/domains/shared/suggestions/expandGlob.test.ts +92 -0
- package/src/domains/shared/suggestions/expandGlob.ts +52 -0
- package/src/domains/shared/suggestions/expandLookupQueries.ts +57 -0
- package/src/domains/shared/suggestions/index.ts +7 -0
- package/src/domains/shared/suggestions/listDomainNames.ts +61 -0
- package/src/domains/shared/suggestions/suggestNames.test.ts +70 -0
- package/src/domains/shared/suggestions/suggestNames.ts +130 -0
- package/src/domains/shared/types/index.ts +1 -0
- package/src/domains/shared/types/runtime.ts +4 -1
- package/src/domains/shared/types/sample.ts +17 -0
- package/src/domains/skill/commands/index.ts +3 -1
- package/src/domains/skill/commands/list.test.ts +6 -2
- package/src/domains/skill/commands/list.ts +5 -63
- package/src/domains/skill/commands/lookup.test.ts +118 -0
- package/src/domains/skill/commands/lookup.ts +14 -0
- package/src/domains/skill/formatters/index.ts +1 -0
- package/src/domains/skill/formatters/lookup.test.ts +46 -0
- package/src/domains/skill/formatters/lookup.ts +57 -0
- package/src/domains/skill/helpers/resolveSkillSources.ts +48 -16
- package/src/domains/skill/index.ts +7 -3
- package/src/domains/skill/mcp/specs.ts +10 -29
- package/src/domains/skill/operations/discover.test.ts +61 -30
- package/src/domains/skill/operations/discover.ts +28 -37
- package/src/domains/skill/operations/index.ts +2 -1
- package/src/domains/skill/operations/list.test.ts +15 -7
- package/src/domains/skill/operations/lookup.test.ts +91 -0
- package/src/domains/skill/operations/lookup.ts +70 -0
- package/src/domains/skill/stories.test.ts +43 -0
- package/src/domains/skill/stories.ts +103 -0
- package/src/domains/skill/types.ts +8 -0
- package/src/domains/standard/commands/index.ts +2 -1
- package/src/domains/standard/commands/list.ts +5 -84
- package/src/domains/standard/commands/lookup.ts +8 -93
- package/src/domains/standard/commands/sample.ts +57 -0
- package/src/domains/standard/formatters/index.ts +2 -1
- package/src/domains/standard/formatters/sample.ts +15 -0
- package/src/domains/standard/index.ts +8 -1
- package/src/domains/standard/mcp/specs.ts +44 -152
- package/src/domains/standard/operations/index.ts +2 -1
- package/src/domains/standard/operations/lookup.ts +9 -0
- package/src/domains/standard/operations/sample.ts +35 -0
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +15 -2
- package/src/domains/standard/orchestration/standardEmptyError.test.ts +33 -0
- package/src/domains/standard/orchestration/standardEmptyError.ts +16 -6
- package/src/domains/standard/stories.test.ts +24 -0
- package/src/domains/standard/stories.ts +151 -0
- package/src/domains/tier/commands/list.ts +5 -55
- package/src/domains/tier/mcp/specs.ts +5 -31
- package/src/domains/tier/stories.test.ts +22 -0
- package/src/domains/tier/stories.ts +52 -0
- package/src/domains/token/commands/index.ts +2 -1
- package/src/domains/token/commands/sample.ts +53 -0
- package/src/domains/token/formatters/index.ts +2 -1
- package/src/domains/token/formatters/sample.ts +16 -0
- package/src/domains/token/index.ts +8 -1
- package/src/domains/token/mcp/specs.ts +52 -2
- package/src/domains/token/operations/index.ts +2 -1
- package/src/domains/token/operations/lookup.ts +9 -0
- package/src/domains/token/operations/sample.ts +32 -0
- package/src/domains/token/orchestration/resolveTokenLookup.ts +15 -2
- package/src/domains/token/orchestration/tokenEmptyError.test.ts +33 -0
- package/src/domains/token/orchestration/tokenEmptyError.ts +5 -2
- package/src/domains/trace/commands/clear.ts +71 -0
- package/src/domains/trace/commands/index.ts +5 -0
- package/src/domains/trace/commands/sessions.ts +32 -0
- package/src/domains/trace/commands/show.ts +145 -0
- package/src/domains/trace/formatters/index.ts +9 -0
- package/src/domains/trace/formatters/sessions.ts +80 -0
- package/src/domains/trace/formatters/show.ts +122 -0
- package/src/domains/trace/index.ts +17 -0
- package/src/domains/trace/operations/index.ts +8 -0
- package/src/domains/trace/operations/listSessions.test.ts +55 -0
- package/src/domains/trace/operations/listSessions.ts +52 -0
- package/src/domains/trace/operations/readTraceLog.test.ts +100 -0
- package/src/domains/trace/operations/readTraceLog.ts +104 -0
- package/src/domains/trace/tracePlugin.test.ts +171 -0
- package/src/domains/trace/tracePlugin.ts +159 -0
- package/src/domains/trace/types.ts +65 -0
- package/src/error/PragmaError.ts +15 -2
- package/src/error/types.ts +15 -1
- package/src/mcp/error/serializeErrorPayload.ts +7 -0
- package/src/mcp/error/types.ts +8 -0
- package/src/mcp/registerResources.test.ts +405 -75
- package/src/mcp/registerResources.ts +5 -299
- package/src/mcp/resources/buildGraphIndex.ts +167 -0
- package/src/mcp/resources/buildResourceList.test.ts +97 -0
- package/src/mcp/resources/buildResourceList.ts +176 -0
- package/src/mcp/resources/classifyEntity.test.ts +40 -0
- package/src/mcp/resources/classifyEntity.ts +27 -0
- package/src/mcp/resources/constants.ts +105 -0
- package/src/mcp/resources/displayPredicates.ts +62 -0
- package/src/mcp/resources/pickFirstValue.test.ts +29 -0
- package/src/mcp/resources/pickFirstValue.ts +26 -0
- package/src/mcp/resources/pickPrimaryType.test.ts +41 -0
- package/src/mcp/resources/pickPrimaryType.ts +26 -0
- package/src/mcp/resources/rankUriCompletions.test.ts +52 -0
- package/src/mcp/resources/rankUriCompletions.ts +64 -0
- package/src/mcp/resources/readEntity.ts +169 -0
- package/src/mcp/resources/registerResources.ts +126 -0
- package/src/mcp/resources/types.ts +128 -0
- package/src/mcp/tools/index.ts +9 -0
- package/src/mcp/tools/registerTools.test.ts +84 -12
- package/src/mcp/types.ts +4 -0
- package/src/mcp/utils/wrapTool.test.ts +1 -0
- package/src/pipeline/collectCommands.ts +15 -2
- package/src/pipeline/renderError.ts +15 -0
- package/src/pipeline/resolveCommandKind.ts +8 -1
- package/src/pipeline/runCli.ts +51 -6
- package/src/testing/createTestMcpClient.ts +5 -1
- package/src/testing/graphqlFixtures.ts +72 -0
- package/src/testing/index.ts +17 -0
- package/src/testing/integration/__snapshots__/executor-golden.test.ts.snap +1207 -0
- package/src/testing/integration/cross-binary-byte-equality.test.ts +129 -0
- package/src/testing/integration/executor-execution.test.ts +101 -0
- package/src/testing/integration/executor-golden.test.ts +145 -0
- package/src/testing/integration/mcp-surface.test.ts +11 -5
- package/src/testing/integration/parity.test.ts +17 -12
- package/src/testing/regression/0001-skill-discovery-granularity.test.ts +71 -0
- package/src/testing/setupXdgIsolation.ts +15 -0
- package/src/testing/standardParityFixtures.ts +92 -0
- package/src/testing/storyFixtures.ts +63 -0
- package/src/pipeline/runInteractiveCommand.ts +0 -198
|
@@ -90,7 +90,7 @@ export default function buildComponentCommand(): CommandDefinition {
|
|
|
90
90
|
validOptions: ["react", "svelte", "lit"],
|
|
91
91
|
recovery: {
|
|
92
92
|
message: "Provide a framework for component generation.",
|
|
93
|
-
cli: "pragma create component react
|
|
93
|
+
cli: "pragma create component react src/components/Button",
|
|
94
94
|
},
|
|
95
95
|
});
|
|
96
96
|
}
|
|
@@ -109,10 +109,10 @@ export default function buildComponentCommand(): CommandDefinition {
|
|
|
109
109
|
},
|
|
110
110
|
meta: {
|
|
111
111
|
examples: [
|
|
112
|
-
"pragma create component react
|
|
113
|
-
"pragma create component svelte
|
|
114
|
-
"pragma create component lit
|
|
115
|
-
"pragma create component react
|
|
112
|
+
"pragma create component react src/components/Button",
|
|
113
|
+
"pragma create component svelte src/lib/components/Toggle",
|
|
114
|
+
"pragma create component lit src/lib/Accordion",
|
|
115
|
+
"pragma create component react src/components/Button --dry-run",
|
|
116
116
|
],
|
|
117
117
|
},
|
|
118
118
|
parameterGroups: {
|
|
@@ -1,21 +1,35 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readConfigLayers } from "#config";
|
|
2
2
|
import type { CheckContext, CheckResult } from "../types.js";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Check
|
|
5
|
+
* Check which config layer is active for the working directory.
|
|
6
|
+
*
|
|
7
|
+
* Passes when a project `pragma.config.json` is found up the tree, or —
|
|
8
|
+
* pragma is global-first — when only the global XDG config exists. Fails
|
|
9
|
+
* only when neither layer is configured.
|
|
6
10
|
*
|
|
7
11
|
* @param ctx - Check context with the working directory.
|
|
8
|
-
* @returns A CheckResult indicating pass or fail.
|
|
12
|
+
* @returns A CheckResult indicating pass or fail with the active layer.
|
|
9
13
|
* @note Impure
|
|
10
14
|
*/
|
|
11
15
|
export default async function checkConfigFile(
|
|
12
16
|
ctx: CheckContext,
|
|
13
17
|
): Promise<CheckResult> {
|
|
14
|
-
|
|
18
|
+
const layers = readConfigLayers(ctx.cwd);
|
|
19
|
+
|
|
20
|
+
if (layers.project.exists) {
|
|
21
|
+
return {
|
|
22
|
+
name: "pragma.config.json",
|
|
23
|
+
status: "pass",
|
|
24
|
+
detail: `found (${layers.project.path})`,
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
if (layers.global.exists) {
|
|
15
29
|
return {
|
|
16
30
|
name: "pragma.config.json",
|
|
17
31
|
status: "pass",
|
|
18
|
-
detail:
|
|
32
|
+
detail: `no project config — global config active (${layers.global.path})`,
|
|
19
33
|
};
|
|
20
34
|
}
|
|
21
35
|
|
|
@@ -14,10 +14,10 @@ export default async function checkKeStore(
|
|
|
14
14
|
ctx: CheckContext,
|
|
15
15
|
): Promise<CheckResult> {
|
|
16
16
|
const start = performance.now();
|
|
17
|
-
let
|
|
17
|
+
let result: Awaited<ReturnType<typeof bootStore>> | undefined;
|
|
18
18
|
try {
|
|
19
|
-
|
|
20
|
-
const summary = await collectStoreSummary(store);
|
|
19
|
+
result = await bootStore({ cwd: ctx.cwd });
|
|
20
|
+
const summary = await collectStoreSummary(result.store);
|
|
21
21
|
const elapsed = Math.round(performance.now() - start);
|
|
22
22
|
return {
|
|
23
23
|
name: "ke store",
|
|
@@ -30,9 +30,9 @@ export default async function checkKeStore(
|
|
|
30
30
|
status: "fail",
|
|
31
31
|
detail: "failed to boot",
|
|
32
32
|
remedy:
|
|
33
|
-
"Ensure design system packages are installed: bun add -D @canonical/
|
|
33
|
+
"Ensure design system packages are installed: bun add -D @canonical/design-system @canonical/anatomy-dsl @canonical/code-standards",
|
|
34
34
|
};
|
|
35
35
|
} finally {
|
|
36
|
-
store
|
|
36
|
+
result?.store.dispose();
|
|
37
37
|
}
|
|
38
38
|
}
|
|
@@ -0,0 +1,130 @@
|
|
|
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, 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: string[] = [];
|
|
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
|
+
`"${serverName}" ("${command}" not found, ${d.configPath})`,
|
|
102
|
+
);
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
if (checked === 0) {
|
|
108
|
+
return {
|
|
109
|
+
name: NAME,
|
|
110
|
+
status: "skip",
|
|
111
|
+
detail: "no command-based MCP servers configured",
|
|
112
|
+
};
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
if (broken.length === 0) {
|
|
116
|
+
return {
|
|
117
|
+
name: NAME,
|
|
118
|
+
status: "pass",
|
|
119
|
+
detail: `${checked} command${checked === 1 ? "" : "s"} resolve on PATH`,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
name: NAME,
|
|
125
|
+
status: "fail",
|
|
126
|
+
detail: `unresolvable: ${broken.join("; ")}`,
|
|
127
|
+
remedy:
|
|
128
|
+
"Install the missing command or remove the entry from the MCP config — every agent session tries and fails to boot it.",
|
|
129
|
+
};
|
|
130
|
+
}
|
|
@@ -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
|
/**
|
|
@@ -1,68 +1,50 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Doctor check: report how each semantic package is resolved.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
4
|
+
* Uses the loader chain (local > git > bundled) to show where each
|
|
5
|
+
* package resolves from, its version, and graph/skill counts.
|
|
6
6
|
*/
|
|
7
7
|
|
|
8
|
-
import { existsSync } from "node:fs";
|
|
9
8
|
import { readConfig } from "#config";
|
|
10
9
|
import {
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
import
|
|
16
|
-
import {
|
|
10
|
+
createBundledLoader,
|
|
11
|
+
createGitLoader,
|
|
12
|
+
createLocalLoader,
|
|
13
|
+
} from "../../../shared/loaders/index.js";
|
|
14
|
+
import { mergeAndParseRefs } from "../../../shared/mergeAndParseRefs.js";
|
|
15
|
+
import { resolveSemanticPackages } from "../../../shared/semanticPackage.js";
|
|
17
16
|
import type { CheckContext, CheckResult } from "../types.js";
|
|
18
17
|
|
|
19
18
|
export default async function checkPackageRefs(
|
|
20
19
|
ctx: CheckContext,
|
|
21
20
|
): Promise<CheckResult> {
|
|
22
21
|
const config = readConfig(ctx.cwd);
|
|
23
|
-
const
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
name: "package refs",
|
|
31
|
-
status: "pass",
|
|
32
|
-
detail: `${DEFAULT_PACKAGES.length} packages (all npm, defaults)`,
|
|
33
|
-
};
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
const entries: ReadonlyArray<RawPackageEntry> =
|
|
37
|
-
config.packages ?? globalEntries;
|
|
22
|
+
const refs = mergeAndParseRefs(config.packages);
|
|
23
|
+
const loaders = [
|
|
24
|
+
createLocalLoader(),
|
|
25
|
+
createGitLoader(),
|
|
26
|
+
createBundledLoader(),
|
|
27
|
+
];
|
|
28
|
+
const packages = await resolveSemanticPackages(refs, loaders);
|
|
38
29
|
|
|
39
30
|
const details: string[] = [];
|
|
40
31
|
let hasIssue = false;
|
|
41
32
|
|
|
42
|
-
for (const
|
|
43
|
-
const
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
case "git": {
|
|
58
|
-
const cached = existsSync(gitCacheDir(ref.pkg, ref.ref));
|
|
59
|
-
if (cached) {
|
|
60
|
-
details.push(`${ref.pkg}: git #${ref.ref} (cached)`);
|
|
61
|
-
} else {
|
|
62
|
-
details.push(`${ref.pkg}: git #${ref.ref} NOT CACHED`);
|
|
63
|
-
hasIssue = true;
|
|
64
|
-
}
|
|
65
|
-
break;
|
|
33
|
+
for (const ref of refs) {
|
|
34
|
+
const resolved = packages.find((p) => p.name === ref.pkg);
|
|
35
|
+
|
|
36
|
+
if (resolved) {
|
|
37
|
+
details.push(
|
|
38
|
+
`${ref.pkg}: ${resolved.source} v${resolved.version} (${resolved.graphs.length} graphs, ${resolved.skills.length} skills)`,
|
|
39
|
+
);
|
|
40
|
+
} else {
|
|
41
|
+
// Check if covered by bundled fallback
|
|
42
|
+
const bundled = packages.find((p) => p.name === "(bundled)");
|
|
43
|
+
if (bundled) {
|
|
44
|
+
details.push(`${ref.pkg}: bundled v${bundled.version}`);
|
|
45
|
+
} else {
|
|
46
|
+
details.push(`${ref.pkg}: NOT RESOLVED`);
|
|
47
|
+
hasIssue = true;
|
|
66
48
|
}
|
|
67
49
|
}
|
|
68
50
|
}
|
|
@@ -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();
|
|
@@ -75,7 +121,10 @@ describe("checkKeStore", () => {
|
|
|
75
121
|
query: vi.fn(),
|
|
76
122
|
dispose: vi.fn(),
|
|
77
123
|
};
|
|
78
|
-
vi.mocked(bootStore).mockResolvedValue(
|
|
124
|
+
vi.mocked(bootStore).mockResolvedValue({
|
|
125
|
+
store: mockStore,
|
|
126
|
+
packages: [],
|
|
127
|
+
} as never);
|
|
79
128
|
vi.mocked(collectStoreSummary).mockResolvedValue({
|
|
80
129
|
tripleCount: 12847,
|
|
81
130
|
graphNames: [],
|
|
@@ -142,6 +191,111 @@ describe("checkMcpConfigured", () => {
|
|
|
142
191
|
});
|
|
143
192
|
});
|
|
144
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
|
+
expect(result.detail).toContain('"sem"');
|
|
272
|
+
expect(result.detail).toContain("/test/.mcp.json");
|
|
273
|
+
expect(result.remedy).toBeDefined();
|
|
274
|
+
});
|
|
275
|
+
|
|
276
|
+
it("flags only the broken entry among several", async () => {
|
|
277
|
+
const binDir = mkdtempSync(join(tmpdir(), "pragma-doctor-mixed-"));
|
|
278
|
+
writeFileSync(join(binDir, "pragma"), "#!/bin/sh\n", { mode: 0o755 });
|
|
279
|
+
vi.stubEnv("PATH", binDir);
|
|
280
|
+
|
|
281
|
+
vi.mocked(detectHarnesses).mockReturnValue(
|
|
282
|
+
Promise.resolve([detected]) as never,
|
|
283
|
+
);
|
|
284
|
+
vi.mocked(readMcpConfig).mockReturnValue(
|
|
285
|
+
Promise.resolve({
|
|
286
|
+
pragma: { command: "pragma", args: ["mcp"] },
|
|
287
|
+
sem: { command: "sem", args: ["mcp"] },
|
|
288
|
+
figma: { type: "http", url: "https://mcp.figma.com/mcp" },
|
|
289
|
+
}) as never,
|
|
290
|
+
);
|
|
291
|
+
|
|
292
|
+
const result = await checkMcpCommands(ctx);
|
|
293
|
+
expect(result.status).toBe("fail");
|
|
294
|
+
expect(result.detail).toContain('"sem"');
|
|
295
|
+
expect(result.detail).not.toContain('"pragma"');
|
|
296
|
+
});
|
|
297
|
+
});
|
|
298
|
+
|
|
145
299
|
describe("checkSkillsSymlinked", () => {
|
|
146
300
|
it("skips when no harnesses detected", async () => {
|
|
147
301
|
vi.mocked(detectHarnesses).mockReturnValue(Promise.resolve([]) as never);
|