@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
package/README.md
CHANGED
|
@@ -22,7 +22,7 @@ See [docs/getting-started.md](docs/getting-started.md) for a full walkthrough.
|
|
|
22
22
|
|
|
23
23
|
## CLI Commands
|
|
24
24
|
|
|
25
|
-
pragma organizes commands into 14 domains. Every command supports three output modes: plain text (default), `--llm` (condensed Markdown), and `--format json` (structured JSON).
|
|
25
|
+
pragma organizes commands into 17 domains — counting every directory under `src/domains/` except `shared/`, which holds cross-domain infrastructure rather than commands. The 14 day-to-day domains are documented below; the remaining three are specialised: `graphql` (compile TTL ontologies into GraphQL schema artifacts), `refs` (`pragma update-refs`, package reference data), and `trace` (query access tracing). Every command supports three output modes: plain text (default), `--llm` (condensed Markdown), and `--format json` (structured JSON).
|
|
26
26
|
|
|
27
27
|
### Block
|
|
28
28
|
|
|
@@ -87,6 +87,7 @@ pragma organizes commands into 14 domains. Every command supports three output m
|
|
|
87
87
|
| Command | Description |
|
|
88
88
|
|---------|-------------|
|
|
89
89
|
| `pragma skill list` | List discovered agent skills from installed packages |
|
|
90
|
+
| `pragma skill lookup <name...>` | Show full SKILL.md instructions for skills by name |
|
|
90
91
|
|
|
91
92
|
### LLM
|
|
92
93
|
|
|
@@ -173,11 +174,11 @@ Or add manually to your `.mcp.json`:
|
|
|
173
174
|
}
|
|
174
175
|
```
|
|
175
176
|
|
|
176
|
-
### MCP Tools (
|
|
177
|
+
### MCP Tools (30)
|
|
177
178
|
|
|
178
179
|
All tools return a consistent envelope: `{ ok: true, data, meta }` for success, `{ ok: true, condensed: true, text, tokens }` for condensed mode, or `{ ok: false, error }` with structured recovery on failure.
|
|
179
180
|
|
|
180
|
-
#### Read Tools (
|
|
181
|
+
#### Read Tools (21)
|
|
181
182
|
|
|
182
183
|
| Tool | Description |
|
|
183
184
|
|------|-------------|
|
|
@@ -201,6 +202,7 @@ All tools return a consistent envelope: `{ ok: true, data, meta }` for success,
|
|
|
201
202
|
| `graph_query` | Execute raw SPARQL against the ke store |
|
|
202
203
|
| `graph_inspect` | Inspect all triples for a URI |
|
|
203
204
|
| `skill_list` | List discovered agent skills |
|
|
205
|
+
| `skill_lookup` | Get full SKILL.md content for skills by name |
|
|
204
206
|
|
|
205
207
|
#### Write Tools (5)
|
|
206
208
|
|
|
@@ -295,14 +297,64 @@ Every subject URI in the ke graph is exposed as a discoverable MCP resource via
|
|
|
295
297
|
|
|
296
298
|
## Configuration
|
|
297
299
|
|
|
298
|
-
|
|
300
|
+
Configuration is layered: built-in defaults, then the global file at
|
|
301
|
+
`$XDG_CONFIG_HOME/pragma/config.json` (default `~/.config/pragma/config.json`),
|
|
302
|
+
then the nearest `pragma.config.json` at or above the current directory —
|
|
303
|
+
each field won by the most specific layer that sets it. Install pragma
|
|
304
|
+
globally, configure once, and override per project:
|
|
299
305
|
|
|
300
|
-
```
|
|
301
|
-
|
|
302
|
-
|
|
306
|
+
```json
|
|
307
|
+
{
|
|
308
|
+
"tier": "apps/lxd",
|
|
309
|
+
"channel": "normal"
|
|
310
|
+
}
|
|
311
|
+
```
|
|
312
|
+
|
|
313
|
+
`pragma config tier|channel|framework|trace` writes to the nearest existing
|
|
314
|
+
project file, or to the global file when no project is configured; pass
|
|
315
|
+
`--local` or `--global` to pick the layer explicitly. Every write echoes the
|
|
316
|
+
file it wrote, and `pragma config show` reports which layer supplied each
|
|
317
|
+
value.
|
|
318
|
+
|
|
319
|
+
### Custom read stories (experimental)
|
|
320
|
+
|
|
321
|
+
A config (or a semantic package, via `stories/*.json`) can declare
|
|
322
|
+
declarative read stories — any ontology loaded through `packages` gets
|
|
323
|
+
`pragma <noun> list` / `<noun> lookup` on both the CLI and MCP:
|
|
324
|
+
|
|
325
|
+
```json
|
|
326
|
+
{
|
|
327
|
+
"prefixes": { "ex": "http://example.org/recipes/" },
|
|
328
|
+
"packages": [{ "name": "my-recipes", "source": "file:///data/recipes" }],
|
|
329
|
+
"stories": [
|
|
330
|
+
{
|
|
331
|
+
"noun": "recipe",
|
|
332
|
+
"description": "List recipes",
|
|
333
|
+
"list": {
|
|
334
|
+
"query": "SELECT ?uri ?name ?category WHERE { ?uri a ex:Recipe ; ex:name ?name ; ex:category ?category } ORDER BY ?name",
|
|
335
|
+
"columns": [{ "field": "name" }, { "field": "category" }],
|
|
336
|
+
"filters": [
|
|
337
|
+
{ "param": "category", "variable": "category", "values": ["breakfast", "soup"] }
|
|
338
|
+
]
|
|
339
|
+
},
|
|
340
|
+
"lookup": {
|
|
341
|
+
"type": "ex:Recipe",
|
|
342
|
+
"by": "ex:name",
|
|
343
|
+
"fields": [{ "name": "category", "property": "ex:category" }],
|
|
344
|
+
"sections": [{ "name": "instructions", "property": "ex:instructions" }]
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
]
|
|
348
|
+
}
|
|
303
349
|
```
|
|
304
350
|
|
|
305
|
-
|
|
351
|
+
The lookup query is generated (user input is escaped, never interpolated by
|
|
352
|
+
the pack), the store stays read-only, and config-declared stories override
|
|
353
|
+
package-shipped ones on noun collisions. Declared `filters` become
|
|
354
|
+
`pragma recipe list --category soup` on the CLI and an enum parameter on the
|
|
355
|
+
MCP tool; they are row predicates applied after the query runs, so filter
|
|
356
|
+
input can never inject SPARQL and the query's ordering is preserved. The
|
|
357
|
+
format is experimental and may change.
|
|
306
358
|
|
|
307
359
|
## Error Handling
|
|
308
360
|
|
|
@@ -336,7 +388,7 @@ With `--format json`, errors are returned as structured JSON with error code, su
|
|
|
336
388
|
|
|
337
389
|
pragma is structured as a domain-driven application using the federation pattern from `@canonical/cli-core`:
|
|
338
390
|
|
|
339
|
-
1. **Domains** —
|
|
391
|
+
1. **Domains** — 17 domain modules (block, config, create, doctor, graph, graphql, info, llm, modifier, ontology, refs, setup, skill, standard, tier, token, trace — every directory under `src/domains/` except the `shared/` infrastructure folder), each contributing `CommandDefinition`s
|
|
340
392
|
2. **Operations** — data retrieval functions that query the ke triple store via SPARQL
|
|
341
393
|
3. **Formatters** — three-mode output adapters (plain/llm/json) for each operation
|
|
342
394
|
4. **Commands** — thin wiring layer connecting Commander.js parameters to operations + formatters
|
|
@@ -364,13 +416,13 @@ bun build --compile --minify src/bin.ts --outfile dist/pragma
|
|
|
364
416
|
|
|
365
417
|
## Dependencies
|
|
366
418
|
|
|
367
|
-
- [`@canonical/ke`](
|
|
419
|
+
- [`@canonical/ke`](../../runtime/ke/) — triple store runtime (Oxigraph WASM)
|
|
368
420
|
- [`@canonical/cli-core`](../core/) — shared CLI machinery (Commander.js registration, help formatting)
|
|
369
|
-
- [`@canonical/task`](../../
|
|
370
|
-
- [`@canonical/harnesses`](../../
|
|
371
|
-
- [`@canonical/summon-core`](../../
|
|
372
|
-
- [`@canonical/summon-component`](../../
|
|
373
|
-
- [`@canonical/summon-package`](../../
|
|
421
|
+
- [`@canonical/task`](../../runtime/task/) — reversible task trees for setup/create with undo support
|
|
422
|
+
- [`@canonical/harnesses`](../../runtime/harnesses/) — AI harness detection (Claude Code, Cursor, Windsurf)
|
|
423
|
+
- [`@canonical/summon-core`](../../summon/core/) — code generation runtime
|
|
424
|
+
- [`@canonical/summon-component`](../../summon/component/) — component generator
|
|
425
|
+
- [`@canonical/summon-package`](../../summon/package/) — package generator
|
|
374
426
|
|
|
375
427
|
## License
|
|
376
428
|
|
package/dist/pragma
CHANGED
|
Binary file
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@canonical/pragma-cli",
|
|
3
3
|
"description": "CLI and MCP server for Canonical's design system.",
|
|
4
|
-
"version": "0.
|
|
4
|
+
"version": "0.30.0",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"module": "src/index.ts",
|
|
7
7
|
"types": "src/index.ts",
|
|
@@ -44,7 +44,7 @@
|
|
|
44
44
|
"access": "public"
|
|
45
45
|
},
|
|
46
46
|
"scripts": {
|
|
47
|
-
"build": "bun
|
|
47
|
+
"build": "bun run scripts/build.ts",
|
|
48
48
|
"build:compile": "bun build --compile --minify --target=bun-linux-x64 src/bin.ts --outfile dist/pragma",
|
|
49
49
|
"prepublishOnly": "bun run build",
|
|
50
50
|
"check": "bun run check:biome && bun run check:ts && bun run check:webarchitect",
|
|
@@ -60,9 +60,9 @@
|
|
|
60
60
|
},
|
|
61
61
|
"devDependencies": {
|
|
62
62
|
"@biomejs/biome": "2.4.9",
|
|
63
|
-
"@canonical/biome-config": "^0.
|
|
64
|
-
"@canonical/typescript-config": "^0.
|
|
65
|
-
"@canonical/webarchitect": "^0.
|
|
63
|
+
"@canonical/biome-config": "^0.30.0",
|
|
64
|
+
"@canonical/typescript-config": "^0.30.0",
|
|
65
|
+
"@canonical/webarchitect": "^0.30.0",
|
|
66
66
|
"@types/bun": "^1.3.10",
|
|
67
67
|
"@types/node": "^24.12.0",
|
|
68
68
|
"@types/react": "^19.2.14",
|
|
@@ -70,19 +70,18 @@
|
|
|
70
70
|
"typescript": "^5.9.3",
|
|
71
71
|
"vitest": "^4.0.18"
|
|
72
72
|
},
|
|
73
|
-
"
|
|
73
|
+
"dependencies": {
|
|
74
74
|
"@canonical/anatomy-dsl": "^0.2.2",
|
|
75
|
+
"@canonical/cli-core": "^0.30.0",
|
|
75
76
|
"@canonical/code-standards": "^0.1.2",
|
|
76
|
-
"@canonical/design-system": "^0.1.2"
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
"@canonical/
|
|
80
|
-
"@canonical/
|
|
81
|
-
"@canonical/
|
|
82
|
-
"@canonical/summon-
|
|
83
|
-
"@canonical/
|
|
84
|
-
"@canonical/summon-package": "^0.27.1-experimental.0",
|
|
85
|
-
"@canonical/task": "^0.27.1-experimental.0",
|
|
77
|
+
"@canonical/design-system": "^0.1.2",
|
|
78
|
+
"@canonical/harnesses": "^0.30.0",
|
|
79
|
+
"@canonical/ke": "^0.30.0",
|
|
80
|
+
"@canonical/ke-graphql": "^0.30.0",
|
|
81
|
+
"@canonical/summon-component": "^0.30.0",
|
|
82
|
+
"@canonical/summon-core": "^0.30.0",
|
|
83
|
+
"@canonical/summon-package": "^0.30.0",
|
|
84
|
+
"@canonical/task": "^0.30.0",
|
|
86
85
|
"@modelcontextprotocol/sdk": "^1.12.0",
|
|
87
86
|
"chalk": "^5.6.2",
|
|
88
87
|
"commander": "^14.0.3",
|
|
@@ -91,5 +90,5 @@
|
|
|
91
90
|
"react": "^19.2.4",
|
|
92
91
|
"zod": "^3.25.0"
|
|
93
92
|
},
|
|
94
|
-
"gitHead": "
|
|
93
|
+
"gitHead": "40fe86ac955473536ac14f074812a9821f5b65c0"
|
|
95
94
|
}
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { describe, expect, it } from "vitest";
|
|
2
|
+
import { VALID_CHANNELS } from "../constants.js";
|
|
3
|
+
import { parsePackageEntry } from "../domains/refs/operations/parseRef.js";
|
|
4
|
+
import { DEFAULT_CONFIG } from "./defaultConfig.js";
|
|
5
|
+
|
|
6
|
+
describe("DEFAULT_CONFIG", () => {
|
|
7
|
+
it("declares a valid channel", () => {
|
|
8
|
+
expect(VALID_CHANNELS).toContain(DEFAULT_CONFIG.channel);
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it("declares at least one package, all parseable", () => {
|
|
12
|
+
expect(DEFAULT_CONFIG.packages.length).toBeGreaterThan(0);
|
|
13
|
+
for (const entry of DEFAULT_CONFIG.packages) {
|
|
14
|
+
const ref = parsePackageEntry(entry);
|
|
15
|
+
expect(ref.pkg.length).toBeGreaterThan(0);
|
|
16
|
+
}
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
it("carries the canonical design-system package by default", () => {
|
|
20
|
+
const names = DEFAULT_CONFIG.packages.map((entry) =>
|
|
21
|
+
typeof entry === "string" ? entry : entry.name,
|
|
22
|
+
);
|
|
23
|
+
expect(names).toContain("@canonical/design-system");
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it("does not declare fields the document must not layer", () => {
|
|
27
|
+
// tier/trace/framework defaults are semantic absences, and packages
|
|
28
|
+
// must reach the merge via DEFAULT_PACKAGES (see defaultConfig.ts).
|
|
29
|
+
expect(DEFAULT_CONFIG.tier).toBeUndefined();
|
|
30
|
+
expect(DEFAULT_CONFIG.trace).toBeUndefined();
|
|
31
|
+
expect(DEFAULT_CONFIG.framework).toBeUndefined();
|
|
32
|
+
});
|
|
33
|
+
});
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { Channel } from "../constants.js";
|
|
2
|
+
import type { RawPackageEntry } from "../domains/refs/operations/parseRef.js";
|
|
3
|
+
import { PragmaError } from "../error/PragmaError.js";
|
|
4
|
+
import defaultsDocument from "./defaults.json" with { type: "json" };
|
|
5
|
+
import parseConfigValues from "./parseConfigValues.js";
|
|
6
|
+
import type { ConfigFileValues } from "./types.js";
|
|
7
|
+
|
|
8
|
+
/** The built-in defaults with the fields every install relies on present. */
|
|
9
|
+
interface DefaultConfig extends ConfigFileValues {
|
|
10
|
+
/** Release channel active when no config file sets one. */
|
|
11
|
+
readonly channel: Channel;
|
|
12
|
+
/** Semantic packages loaded when no config or global refs declare any. */
|
|
13
|
+
readonly packages: ReadonlyArray<RawPackageEntry>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Validate the embedded defaults document into the built-in config layer.
|
|
18
|
+
*
|
|
19
|
+
* The document goes through the same parser as user config files, so an
|
|
20
|
+
* invalid edit to `defaults.json` fails loudly at first import (and in
|
|
21
|
+
* tests) instead of silently changing behavior.
|
|
22
|
+
*
|
|
23
|
+
* @throws PragmaError with code `CONFIG_ERROR` when the document is
|
|
24
|
+
* invalid or misses a required default.
|
|
25
|
+
*/
|
|
26
|
+
function parseDefaultsDocument(): DefaultConfig {
|
|
27
|
+
const values = parseConfigValues(
|
|
28
|
+
JSON.stringify(defaultsDocument),
|
|
29
|
+
"built-in defaults (src/config/defaults.json)",
|
|
30
|
+
);
|
|
31
|
+
if (values.channel === undefined) {
|
|
32
|
+
throw PragmaError.configError("Built-in defaults must declare a channel.", {
|
|
33
|
+
recovery: { message: "Restore `channel` in src/config/defaults.json." },
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
if (values.packages === undefined || values.packages.length === 0) {
|
|
37
|
+
throw PragmaError.configError(
|
|
38
|
+
"Built-in defaults must declare at least one semantic package.",
|
|
39
|
+
{
|
|
40
|
+
recovery: {
|
|
41
|
+
message: "Restore `packages` in src/config/defaults.json.",
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return { ...values, channel: values.channel, packages: values.packages };
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/**
|
|
50
|
+
* The built-in configuration layer, sourced from the embedded declarative
|
|
51
|
+
* `defaults.json` document rather than hardcoded values.
|
|
52
|
+
*
|
|
53
|
+
* `packages` is deliberately NOT surfaced through the layered `pick()` in
|
|
54
|
+
* `readConfigLayers` — it feeds `DEFAULT_PACKAGES` (and from there the
|
|
55
|
+
* ref merge), where global `refs.json` entries still merge over it by
|
|
56
|
+
* package name. Sourcing it as an ordinary config layer would make it
|
|
57
|
+
* look user-declared and shut global refs out.
|
|
58
|
+
*/
|
|
59
|
+
export const DEFAULT_CONFIG: DefaultConfig = parseDefaultsDocument();
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"channel": "normal",
|
|
3
|
+
"packages": [
|
|
4
|
+
{
|
|
5
|
+
"name": "@canonical/design-system",
|
|
6
|
+
"source": "git+https://github.com/canonical/design-system.git#main"
|
|
7
|
+
},
|
|
8
|
+
{
|
|
9
|
+
"name": "@canonical/anatomy-dsl",
|
|
10
|
+
"source": "git+https://github.com/canonical/anatomy-dsl.git#main"
|
|
11
|
+
},
|
|
12
|
+
{
|
|
13
|
+
"name": "@canonical/code-standards",
|
|
14
|
+
"source": "git+https://github.com/canonical/web-code-standards.git#main"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { tmpdir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { afterEach, beforeEach, describe, expect, it } from "vitest";
|
|
5
|
+
import findProjectConfigPath from "./findProjectConfigPath.js";
|
|
6
|
+
|
|
7
|
+
describe("findProjectConfigPath", () => {
|
|
8
|
+
let root: string;
|
|
9
|
+
|
|
10
|
+
beforeEach(() => {
|
|
11
|
+
root = mkdtempSync(join(tmpdir(), "pragma-find-config-"));
|
|
12
|
+
mkdirSync(join(root, ".git"));
|
|
13
|
+
mkdirSync(join(root, "packages/app/src"), { recursive: true });
|
|
14
|
+
});
|
|
15
|
+
|
|
16
|
+
afterEach(() => {
|
|
17
|
+
rmSync(root, { recursive: true, force: true });
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it("finds the config in the starting directory", () => {
|
|
21
|
+
writeFileSync(join(root, "packages/app/pragma.config.json"), "{}");
|
|
22
|
+
expect(findProjectConfigPath(join(root, "packages/app"))).toBe(
|
|
23
|
+
join(root, "packages/app/pragma.config.json"),
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
it("walks up to the nearest config", () => {
|
|
28
|
+
writeFileSync(join(root, "pragma.config.json"), "{}");
|
|
29
|
+
expect(findProjectConfigPath(join(root, "packages/app/src"))).toBe(
|
|
30
|
+
join(root, "pragma.config.json"),
|
|
31
|
+
);
|
|
32
|
+
});
|
|
33
|
+
|
|
34
|
+
it("prefers the nearest config over a higher one", () => {
|
|
35
|
+
writeFileSync(join(root, "pragma.config.json"), "{}");
|
|
36
|
+
writeFileSync(join(root, "packages/app/pragma.config.json"), "{}");
|
|
37
|
+
expect(findProjectConfigPath(join(root, "packages/app/src"))).toBe(
|
|
38
|
+
join(root, "packages/app/pragma.config.json"),
|
|
39
|
+
);
|
|
40
|
+
});
|
|
41
|
+
|
|
42
|
+
it("stops at the repository root (.git) without escaping", () => {
|
|
43
|
+
// The fixture root has .git and no config; nothing above it may leak in.
|
|
44
|
+
expect(findProjectConfigPath(join(root, "packages/app/src"))).toBe(
|
|
45
|
+
undefined,
|
|
46
|
+
);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
it("checks the .git directory's own level before stopping", () => {
|
|
50
|
+
writeFileSync(join(root, "pragma.config.json"), "{}");
|
|
51
|
+
expect(findProjectConfigPath(root)).toBe(join(root, "pragma.config.json"));
|
|
52
|
+
});
|
|
53
|
+
});
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve } from "node:path";
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Find the nearest `pragma.config.json` at or above `cwd`.
|
|
7
|
+
*
|
|
8
|
+
* Walks up the directory tree so pragma works from anywhere inside a
|
|
9
|
+
* project. The walk is bounded: it stops after checking the first
|
|
10
|
+
* directory that contains `.git` (the repository root), after checking
|
|
11
|
+
* the home directory, and at the filesystem root — a config outside the
|
|
12
|
+
* repository or home never leaks in.
|
|
13
|
+
*
|
|
14
|
+
* @param cwd - Directory to start from.
|
|
15
|
+
* @returns Absolute path of the nearest config file, or `undefined`.
|
|
16
|
+
* @note Impure — probes the filesystem while walking up.
|
|
17
|
+
*/
|
|
18
|
+
export default function findProjectConfigPath(cwd: string): string | undefined {
|
|
19
|
+
const home = homedir();
|
|
20
|
+
let dir = resolve(cwd);
|
|
21
|
+
|
|
22
|
+
while (true) {
|
|
23
|
+
const candidate = join(dir, "pragma.config.json");
|
|
24
|
+
if (existsSync(candidate)) {
|
|
25
|
+
return candidate;
|
|
26
|
+
}
|
|
27
|
+
if (existsSync(join(dir, ".git")) || dir === home) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
const parent = dirname(dir);
|
|
31
|
+
if (parent === dir) {
|
|
32
|
+
return undefined;
|
|
33
|
+
}
|
|
34
|
+
dir = parent;
|
|
35
|
+
}
|
|
36
|
+
}
|
package/src/config/index.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
|
-
/** @module Configuration
|
|
1
|
+
/** @module Configuration reading, writing, layered resolution, and paths. */
|
|
2
2
|
export { default as configExists } from "./configExists.js";
|
|
3
|
+
export { default as findProjectConfigPath } from "./findProjectConfigPath.js";
|
|
4
|
+
export { default as mergeConfigFileUpdate } from "./mergeConfigFileUpdate.js";
|
|
5
|
+
export { default as parseConfigValues } from "./parseConfigValues.js";
|
|
3
6
|
export { default as readConfig } from "./readConfig.js";
|
|
7
|
+
export { default as readConfigLayers } from "./readConfigLayers.js";
|
|
4
8
|
export { default as resolveConfigPath } from "./resolveConfigPath.js";
|
|
5
|
-
export
|
|
9
|
+
export { default as resolveGlobalConfigPath } from "./resolveGlobalConfigPath.js";
|
|
10
|
+
export { default as resolveWriteConfigPath } from "./resolveWriteConfigPath.js";
|
|
11
|
+
export type {
|
|
12
|
+
ConfigFileValues,
|
|
13
|
+
ConfigLayer,
|
|
14
|
+
ConfigLayers,
|
|
15
|
+
ConfigOrigin,
|
|
16
|
+
ConfigOrigins,
|
|
17
|
+
ConfigScope,
|
|
18
|
+
ConfigUpdate,
|
|
19
|
+
PragmaConfig,
|
|
20
|
+
} from "./types.js";
|
|
6
21
|
export { default as writeConfig } from "./writeConfig.js";
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
import type { ConfigUpdate } from "./types.js";
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Compute a config file's new contents after applying an update.
|
|
6
|
+
*
|
|
7
|
+
* Reads the target file itself (not the merged layers — a write must
|
|
8
|
+
* never copy values from another layer into this file), applies the
|
|
9
|
+
* update per field (`undefined` with the key present removes the field),
|
|
10
|
+
* and returns the serialized JSON. Unknown keys already in the file are
|
|
11
|
+
* preserved verbatim.
|
|
12
|
+
*
|
|
13
|
+
* @param configPath - Absolute path of the config file to update.
|
|
14
|
+
* @param update - Fields to set or remove.
|
|
15
|
+
* @returns The file's new contents, newline-terminated.
|
|
16
|
+
* @throws The raw filesystem/parse error when the existing file is
|
|
17
|
+
* present but unreadable or not valid JSON — never silently clobbers.
|
|
18
|
+
* @note Impure — reads the current file contents.
|
|
19
|
+
*/
|
|
20
|
+
export default function mergeConfigFileUpdate(
|
|
21
|
+
configPath: string,
|
|
22
|
+
update: ConfigUpdate,
|
|
23
|
+
): string {
|
|
24
|
+
let existing: Record<string, unknown> = {};
|
|
25
|
+
try {
|
|
26
|
+
const raw = readFileSync(configPath, "utf-8");
|
|
27
|
+
existing = JSON.parse(raw) as Record<string, unknown>;
|
|
28
|
+
} catch (err: unknown) {
|
|
29
|
+
// Only swallow "file not found" — surface parse or permission errors.
|
|
30
|
+
const isNotFound =
|
|
31
|
+
err instanceof Error &&
|
|
32
|
+
"code" in err &&
|
|
33
|
+
(err as NodeJS.ErrnoException).code === "ENOENT";
|
|
34
|
+
if (!isNotFound) {
|
|
35
|
+
throw err;
|
|
36
|
+
}
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
applyField(existing, update, "tier");
|
|
40
|
+
applyField(existing, update, "channel");
|
|
41
|
+
applyField(existing, update, "packages");
|
|
42
|
+
applyField(existing, update, "trace");
|
|
43
|
+
applyField(existing, update, "framework");
|
|
44
|
+
|
|
45
|
+
return `${JSON.stringify(existing, null, 2)}\n`;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function applyField(
|
|
49
|
+
existing: Record<string, unknown>,
|
|
50
|
+
update: ConfigUpdate,
|
|
51
|
+
field: keyof ConfigUpdate,
|
|
52
|
+
): void {
|
|
53
|
+
if (update[field] !== undefined) {
|
|
54
|
+
existing[field] = update[field];
|
|
55
|
+
} else if (field in update) {
|
|
56
|
+
delete existing[field];
|
|
57
|
+
}
|
|
58
|
+
}
|