@canonical/pragma-cli 0.29.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 +13 -13
- 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 -153
- 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 +76 -1
- package/src/config/writeConfig.ts +25 -55
- package/src/domains/block/commands/list.ts +5 -90
- package/src/domains/block/commands/lookup.ts +8 -167
- package/src/domains/block/mcp/specs.ts +16 -196
- package/src/domains/block/orchestration/resolveBlockLookup.ts +2 -1
- 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 +18 -1
- package/src/domains/config/commands/framework.ts +13 -7
- 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 +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/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/commands/component.ts +5 -5
- package/src/domains/doctor/operations/checks/checkConfigFile.ts +19 -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 +2 -14
- 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 +158 -7
- package/src/domains/doctor/operations/runChecks.test.ts +15 -6
- package/src/domains/doctor/operations/runChecks.ts +2 -0
- 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 +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 +40 -0
- package/src/domains/graphql/commands/serve.ts +131 -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/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 +21 -45
- 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/index.ts +5 -0
- package/src/domains/info/operations/collectInfo.test.ts +14 -3
- package/src/domains/info/operations/collectInfo.ts +12 -15
- package/src/domains/llm/capabilities.test.ts +4 -4
- package/src/domains/llm/data/toolCatalog.ts +6 -0
- package/src/domains/modifier/commands/list.ts +5 -38
- package/src/domains/modifier/commands/lookup.ts +8 -79
- package/src/domains/modifier/mcp/specs.ts +15 -94
- package/src/domains/modifier/orchestration/resolveModifierLookup.ts +2 -1
- 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/updateRefs.ts +2 -37
- 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 +6 -3
- 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/loaders/bundledLoader.test.ts +248 -0
- package/src/domains/shared/loaders/bundledLoader.ts +110 -12
- package/src/domains/shared/loaders/gitLoader.ts +2 -2
- package/src/domains/shared/loaders/index.ts +4 -1
- package/src/domains/shared/loaders/localLoader.ts +18 -4
- package/src/domains/shared/loaders/readPackageDir.ts +36 -1
- 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 +34 -2
- package/src/domains/shared/mergeAndParseRefs.test.ts +89 -0
- package/src/domains/shared/mergeAndParseRefs.ts +44 -0
- package/src/domains/shared/packages.ts +10 -17
- package/src/domains/shared/resolveConfiguredGraphs.test.ts +27 -0
- package/src/domains/shared/resolveConfiguredGraphs.ts +41 -0
- package/src/domains/shared/runtime.ts +4 -63
- package/src/domains/shared/semanticPackage.ts +17 -1
- 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/expandGlob.ts +1 -0
- package/src/domains/shared/suggestions/suggestNames.ts +43 -10
- 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/standard/commands/list.ts +5 -84
- package/src/domains/standard/commands/lookup.ts +8 -93
- package/src/domains/standard/mcp/specs.ts +16 -171
- package/src/domains/standard/orchestration/resolveStandardLookup.ts +2 -1
- 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/mcp/specs.ts +5 -1
- package/src/domains/token/orchestration/resolveTokenLookup.ts +2 -1
- package/src/domains/trace/tracePlugin.test.ts +1 -1
- 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 +9 -0
- package/src/mcp/tools/registerTools.test.ts +65 -8
- package/src/mcp/types.ts +4 -0
- package/src/pipeline/collectCommands.ts +8 -1
- package/src/pipeline/runCli.ts +40 -6
- package/src/testing/createTestMcpClient.ts +4 -1
- package/src/testing/index.ts +10 -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 +1 -1
- 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
|
@@ -8,19 +8,28 @@ import type { ConfigShowData } from "../operations/types.js";
|
|
|
8
8
|
* - **llm** renders a markdown section with bullet points.
|
|
9
9
|
* - **json** serializes the raw ConfigShowData.
|
|
10
10
|
*/
|
|
11
|
+
/** Render a `[layer]` marker for values a config file supplied. */
|
|
12
|
+
function originMarker(origin: "default" | "global" | "project"): string {
|
|
13
|
+
return origin === "default" ? "" : ` [${origin}]`;
|
|
14
|
+
}
|
|
15
|
+
|
|
11
16
|
const formatters: Formatters<ConfigShowData> = {
|
|
12
17
|
plain(data) {
|
|
13
18
|
const lines: string[] = [];
|
|
14
19
|
|
|
15
20
|
if (data.tier !== undefined) {
|
|
16
21
|
const chain = data.tierChain.join(" → ");
|
|
17
|
-
lines.push(
|
|
22
|
+
lines.push(
|
|
23
|
+
`tier: ${data.tier} (${chain})${originMarker(data.origins.tier)}`,
|
|
24
|
+
);
|
|
18
25
|
} else {
|
|
19
26
|
lines.push("tier: (none — all tiers visible)");
|
|
20
27
|
}
|
|
21
28
|
|
|
22
29
|
const releases = data.includedReleases.join(" + ");
|
|
23
|
-
lines.push(
|
|
30
|
+
lines.push(
|
|
31
|
+
`channel: ${data.channel} (${releases})${originMarker(data.origins.channel)}`,
|
|
32
|
+
);
|
|
24
33
|
lines.push(`installed via: ${data.installSource}`);
|
|
25
34
|
|
|
26
35
|
if (data.configFileExists) {
|
|
@@ -28,6 +37,11 @@ const formatters: Formatters<ConfigShowData> = {
|
|
|
28
37
|
} else {
|
|
29
38
|
lines.push("config file: (not found)");
|
|
30
39
|
}
|
|
40
|
+
if (data.globalConfigExists) {
|
|
41
|
+
lines.push(`global config: ${data.globalConfigPath}`);
|
|
42
|
+
} else {
|
|
43
|
+
lines.push("global config: (not found)");
|
|
44
|
+
}
|
|
31
45
|
|
|
32
46
|
return lines.join("\n");
|
|
33
47
|
},
|
|
@@ -40,13 +54,17 @@ const formatters: Formatters<ConfigShowData> = {
|
|
|
40
54
|
|
|
41
55
|
if (data.tier !== undefined) {
|
|
42
56
|
const chain = data.tierChain.join(" > ");
|
|
43
|
-
lines.push(
|
|
57
|
+
lines.push(
|
|
58
|
+
`- **Tier:** ${data.tier} (${chain})${originMarker(data.origins.tier)}`,
|
|
59
|
+
);
|
|
44
60
|
} else {
|
|
45
61
|
lines.push("- **Tier:** none (all tiers visible)");
|
|
46
62
|
}
|
|
47
63
|
|
|
48
64
|
const releases = data.includedReleases.join(", ");
|
|
49
|
-
lines.push(
|
|
65
|
+
lines.push(
|
|
66
|
+
`- **Channel:** ${data.channel} (${releases})${originMarker(data.origins.channel)}`,
|
|
67
|
+
);
|
|
50
68
|
lines.push(`- **Installed via:** ${data.installSource}`);
|
|
51
69
|
|
|
52
70
|
if (data.configFileExists) {
|
|
@@ -54,6 +72,11 @@ const formatters: Formatters<ConfigShowData> = {
|
|
|
54
72
|
} else {
|
|
55
73
|
lines.push("- **Config file:** not found");
|
|
56
74
|
}
|
|
75
|
+
if (data.globalConfigExists) {
|
|
76
|
+
lines.push(`- **Global config:** \`${data.globalConfigPath}\``);
|
|
77
|
+
} else {
|
|
78
|
+
lines.push("- **Global config:** not found");
|
|
79
|
+
}
|
|
57
80
|
|
|
58
81
|
return lines.join("\n");
|
|
59
82
|
},
|
|
@@ -2,36 +2,52 @@ import { describe, expect, it } from "vitest";
|
|
|
2
2
|
import formatters from "./tier.js";
|
|
3
3
|
|
|
4
4
|
describe("tierFormatters.set", () => {
|
|
5
|
-
const data = {
|
|
5
|
+
const data = {
|
|
6
|
+
field: "tier",
|
|
7
|
+
value: "apps/lxd",
|
|
8
|
+
path: "/repo/pragma.config.json",
|
|
9
|
+
};
|
|
6
10
|
|
|
7
|
-
it("plain renders set message", () => {
|
|
8
|
-
expect(formatters.set.plain(data)).toBe(
|
|
11
|
+
it("plain renders set message with the written path", () => {
|
|
12
|
+
expect(formatters.set.plain(data)).toBe(
|
|
13
|
+
'Set tier to "apps/lxd".\nWrote /repo/pragma.config.json',
|
|
14
|
+
);
|
|
9
15
|
});
|
|
10
16
|
|
|
11
|
-
it("llm renders set message", () => {
|
|
12
|
-
expect(formatters.set.llm(data)).toBe(
|
|
17
|
+
it("llm renders set message with the written path", () => {
|
|
18
|
+
expect(formatters.set.llm(data)).toBe(
|
|
19
|
+
'Set tier to "apps/lxd".\nWrote /repo/pragma.config.json',
|
|
20
|
+
);
|
|
13
21
|
});
|
|
14
22
|
|
|
15
23
|
it("json returns valid JSON", () => {
|
|
16
24
|
const parsed = JSON.parse(formatters.set.json(data));
|
|
17
25
|
expect(parsed.field).toBe("tier");
|
|
18
26
|
expect(parsed.value).toBe("apps/lxd");
|
|
27
|
+
expect(parsed.path).toBe("/repo/pragma.config.json");
|
|
19
28
|
});
|
|
20
29
|
});
|
|
21
30
|
|
|
22
31
|
describe("tierFormatters.reset", () => {
|
|
23
|
-
|
|
24
|
-
|
|
32
|
+
const data = { field: "tier", path: "/repo/pragma.config.json" };
|
|
33
|
+
|
|
34
|
+
it("plain renders reset message with the written path", () => {
|
|
35
|
+
expect(formatters.reset.plain(data)).toBe(
|
|
36
|
+
"Reset tier to default.\nWrote /repo/pragma.config.json",
|
|
37
|
+
);
|
|
25
38
|
});
|
|
26
39
|
|
|
27
|
-
it("llm renders reset message", () => {
|
|
28
|
-
expect(formatters.reset.llm(
|
|
40
|
+
it("llm renders reset message with the written path", () => {
|
|
41
|
+
expect(formatters.reset.llm(data)).toBe(
|
|
42
|
+
"Reset tier to default.\nWrote /repo/pragma.config.json",
|
|
43
|
+
);
|
|
29
44
|
});
|
|
30
45
|
|
|
31
46
|
it("json returns valid JSON", () => {
|
|
32
|
-
const parsed = JSON.parse(formatters.reset.json(
|
|
47
|
+
const parsed = JSON.parse(formatters.reset.json(data));
|
|
33
48
|
expect(parsed.field).toBe("tier");
|
|
34
49
|
expect(parsed.reset).toBe(true);
|
|
50
|
+
expect(parsed.path).toBe("/repo/pragma.config.json");
|
|
35
51
|
});
|
|
36
52
|
});
|
|
37
53
|
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
import type { Formatters } from "../../shared/formatters.js";
|
|
2
2
|
|
|
3
|
+
/** Payload for a persisted config write, including the file written. */
|
|
4
|
+
interface TierWrite {
|
|
5
|
+
readonly field: string;
|
|
6
|
+
readonly value: string;
|
|
7
|
+
readonly path: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
3
10
|
/**
|
|
4
11
|
* Formatter sets for `pragma config tier` output, grouped by branch:
|
|
5
12
|
*
|
|
@@ -9,19 +16,20 @@ import type { Formatters } from "../../shared/formatters.js";
|
|
|
9
16
|
*/
|
|
10
17
|
const tierFormatters = {
|
|
11
18
|
set: {
|
|
12
|
-
plain: (d: {
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
JSON.stringify({ field: d.field, value: d.value }),
|
|
18
|
-
} satisfies Formatters<{ field: string; value: string }>,
|
|
19
|
+
plain: (d: TierWrite) => `Set ${d.field} to "${d.value}".\nWrote ${d.path}`,
|
|
20
|
+
llm: (d: TierWrite) => `Set ${d.field} to "${d.value}".\nWrote ${d.path}`,
|
|
21
|
+
json: (d: TierWrite) =>
|
|
22
|
+
JSON.stringify({ field: d.field, value: d.value, path: d.path }),
|
|
23
|
+
} satisfies Formatters<TierWrite>,
|
|
19
24
|
|
|
20
25
|
reset: {
|
|
21
|
-
plain: (
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
26
|
+
plain: (d: { field: string; path: string }) =>
|
|
27
|
+
`Reset tier to default.\nWrote ${d.path}`,
|
|
28
|
+
llm: (d: { field: string; path: string }) =>
|
|
29
|
+
`Reset tier to default.\nWrote ${d.path}`,
|
|
30
|
+
json: (d: { field: string; path: string }) =>
|
|
31
|
+
JSON.stringify({ field: "tier", reset: true, path: d.path }),
|
|
32
|
+
} satisfies Formatters<{ field: string; path: string }>,
|
|
25
33
|
|
|
26
34
|
query: {
|
|
27
35
|
plain: (tier: string | undefined) =>
|
|
@@ -8,13 +8,13 @@ import type { Formatters } from "../../shared/formatters.js";
|
|
|
8
8
|
*/
|
|
9
9
|
const traceFormatters = {
|
|
10
10
|
set: {
|
|
11
|
-
plain: (d: { field: string; value: boolean }) =>
|
|
12
|
-
d.value ? "Tracing enabled." : "Tracing disabled."
|
|
13
|
-
llm: (d: { field: string; value: boolean }) =>
|
|
14
|
-
d.value ? "Tracing enabled." : "Tracing disabled."
|
|
15
|
-
json: (d: { field: string; value: boolean }) =>
|
|
16
|
-
JSON.stringify({ field: d.field, value: d.value }),
|
|
17
|
-
} satisfies Formatters<{ field: string; value: boolean }>,
|
|
11
|
+
plain: (d: { field: string; value: boolean; path: string }) =>
|
|
12
|
+
`${d.value ? "Tracing enabled." : "Tracing disabled."}\nWrote ${d.path}`,
|
|
13
|
+
llm: (d: { field: string; value: boolean; path: string }) =>
|
|
14
|
+
`${d.value ? "Tracing enabled." : "Tracing disabled."}\nWrote ${d.path}`,
|
|
15
|
+
json: (d: { field: string; value: boolean; path: string }) =>
|
|
16
|
+
JSON.stringify({ field: d.field, value: d.value, path: d.path }),
|
|
17
|
+
} satisfies Formatters<{ field: string; value: boolean; path: string }>,
|
|
18
18
|
|
|
19
19
|
query: {
|
|
20
20
|
plain: (status: string) => status,
|
|
@@ -1,54 +1,20 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* MCP tool specs for the config domain
|
|
2
|
+
* MCP tool specs for the config domain — config_show, config_tier,
|
|
3
|
+
* config_channel.
|
|
3
4
|
*
|
|
4
|
-
* config_show
|
|
5
|
-
*
|
|
5
|
+
* config_show is compiled from the config read story in `../stories.ts`
|
|
6
|
+
* so both surfaces share resolution and formatters; the mutating tier and
|
|
7
|
+
* channel tools are spec'd by hand.
|
|
6
8
|
*/
|
|
7
9
|
|
|
8
10
|
import { readConfig, writeConfig } from "#config";
|
|
9
|
-
import {
|
|
11
|
+
import { compileReadTool } from "../../shared/stories/index.js";
|
|
10
12
|
import type { ToolSpec } from "../../shared/ToolSpec.js";
|
|
11
|
-
import {
|
|
12
|
-
import {
|
|
13
|
-
resolveConfigShow,
|
|
14
|
-
validateChannel,
|
|
15
|
-
validateTier,
|
|
16
|
-
} from "../operations/index.js";
|
|
13
|
+
import { validateChannel, validateTier } from "../operations/index.js";
|
|
14
|
+
import { configShowStory } from "../stories.js";
|
|
17
15
|
|
|
18
16
|
const specs: readonly ToolSpec[] = [
|
|
19
|
-
|
|
20
|
-
name: "config_show",
|
|
21
|
-
description:
|
|
22
|
-
"Show current pragma configuration (tier and channel settings).",
|
|
23
|
-
params: {
|
|
24
|
-
condensed: {
|
|
25
|
-
type: "boolean",
|
|
26
|
-
description: "Token-optimized output",
|
|
27
|
-
optional: true,
|
|
28
|
-
},
|
|
29
|
-
},
|
|
30
|
-
readOnly: true,
|
|
31
|
-
async execute(rt, { condensed }) {
|
|
32
|
-
const install = detectInstallSource();
|
|
33
|
-
const data = resolveConfigShow(rt.config, {
|
|
34
|
-
packageManager: install.packageManager,
|
|
35
|
-
installSource: install.label,
|
|
36
|
-
configFilePath: "pragma.config.json",
|
|
37
|
-
configFileExists: true,
|
|
38
|
-
});
|
|
39
|
-
|
|
40
|
-
if (condensed) {
|
|
41
|
-
const text = showFormatters.llm(data);
|
|
42
|
-
return {
|
|
43
|
-
condensed: true,
|
|
44
|
-
text,
|
|
45
|
-
tokens: `~${Math.ceil(text.length / 4)}`,
|
|
46
|
-
};
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
return { data };
|
|
50
|
-
},
|
|
51
|
-
},
|
|
17
|
+
compileReadTool(configShowStory),
|
|
52
18
|
{
|
|
53
19
|
name: "config_tier",
|
|
54
20
|
description:
|
|
@@ -64,18 +30,25 @@ const specs: readonly ToolSpec[] = [
|
|
|
64
30
|
description: "Reset tier to default",
|
|
65
31
|
optional: true,
|
|
66
32
|
},
|
|
33
|
+
global: {
|
|
34
|
+
type: "boolean",
|
|
35
|
+
description: "Write to the global config instead of the project file",
|
|
36
|
+
optional: true,
|
|
37
|
+
},
|
|
67
38
|
},
|
|
68
39
|
readOnly: false,
|
|
69
|
-
async execute(rt, { path, reset }) {
|
|
40
|
+
async execute(rt, { path, reset, global: globalScope }) {
|
|
41
|
+
const scope = globalScope === true ? "global" : undefined;
|
|
42
|
+
|
|
70
43
|
if (reset) {
|
|
71
|
-
writeConfig(rt.cwd, { tier: undefined });
|
|
72
|
-
return { data: { tier: null, action: "reset" } };
|
|
44
|
+
const written = writeConfig(rt.cwd, { tier: undefined }, scope);
|
|
45
|
+
return { data: { tier: null, action: "reset", path: written } };
|
|
73
46
|
}
|
|
74
47
|
|
|
75
48
|
if (path) {
|
|
76
49
|
await validateTier(rt.store, path as string);
|
|
77
|
-
writeConfig(rt.cwd, { tier: path as string });
|
|
78
|
-
return { data: { tier: path, action: "set" } };
|
|
50
|
+
const written = writeConfig(rt.cwd, { tier: path as string }, scope);
|
|
51
|
+
return { data: { tier: path, action: "set", path: written } };
|
|
79
52
|
}
|
|
80
53
|
|
|
81
54
|
const config = readConfig(rt.cwd);
|
|
@@ -97,18 +70,25 @@ const specs: readonly ToolSpec[] = [
|
|
|
97
70
|
description: "Reset channel to normal",
|
|
98
71
|
optional: true,
|
|
99
72
|
},
|
|
73
|
+
global: {
|
|
74
|
+
type: "boolean",
|
|
75
|
+
description: "Write to the global config instead of the project file",
|
|
76
|
+
optional: true,
|
|
77
|
+
},
|
|
100
78
|
},
|
|
101
79
|
readOnly: false,
|
|
102
|
-
async execute(rt, { value, reset }) {
|
|
80
|
+
async execute(rt, { value, reset, global: globalScope }) {
|
|
81
|
+
const scope = globalScope === true ? "global" : undefined;
|
|
82
|
+
|
|
103
83
|
if (reset) {
|
|
104
|
-
writeConfig(rt.cwd, { channel: undefined });
|
|
105
|
-
return { data: { channel: "normal", action: "reset" } };
|
|
84
|
+
const written = writeConfig(rt.cwd, { channel: undefined }, scope);
|
|
85
|
+
return { data: { channel: "normal", action: "reset", path: written } };
|
|
106
86
|
}
|
|
107
87
|
|
|
108
88
|
if (value) {
|
|
109
89
|
const channel = validateChannel(value as string);
|
|
110
|
-
writeConfig(rt.cwd, { channel });
|
|
111
|
-
return { data: { channel, action: "set" } };
|
|
90
|
+
const written = writeConfig(rt.cwd, { channel }, scope);
|
|
91
|
+
return { data: { channel, action: "set", path: written } };
|
|
112
92
|
}
|
|
113
93
|
|
|
114
94
|
const config = readConfig(rt.cwd);
|
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
import { describe, expect, it } from "vitest";
|
|
2
|
+
import type { ConfigOrigins } from "#config";
|
|
2
3
|
import resolveConfigShow from "./show.js";
|
|
3
4
|
|
|
5
|
+
const PROJECT_ORIGINS: ConfigOrigins = {
|
|
6
|
+
tier: "project",
|
|
7
|
+
channel: "project",
|
|
8
|
+
packages: "default",
|
|
9
|
+
trace: "default",
|
|
10
|
+
framework: "default",
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
const PROVENANCE = {
|
|
14
|
+
globalConfigPath: "/home/u/.config/pragma/config.json",
|
|
15
|
+
globalConfigExists: false,
|
|
16
|
+
origins: PROJECT_ORIGINS,
|
|
17
|
+
};
|
|
18
|
+
|
|
4
19
|
describe("resolveConfigShow", () => {
|
|
5
20
|
it("resolves tier chain and included releases", () => {
|
|
6
21
|
const data = resolveConfigShow(
|
|
@@ -10,6 +25,7 @@ describe("resolveConfigShow", () => {
|
|
|
10
25
|
installSource: "bun (global)",
|
|
11
26
|
configFilePath: "/tmp/pragma.config.json",
|
|
12
27
|
configFileExists: true,
|
|
28
|
+
...PROVENANCE,
|
|
13
29
|
},
|
|
14
30
|
);
|
|
15
31
|
|
|
@@ -30,6 +46,7 @@ describe("resolveConfigShow", () => {
|
|
|
30
46
|
installSource: "bun (local)",
|
|
31
47
|
configFilePath: "/tmp/pragma.config.json",
|
|
32
48
|
configFileExists: false,
|
|
49
|
+
...PROVENANCE,
|
|
33
50
|
},
|
|
34
51
|
);
|
|
35
52
|
|
|
@@ -47,6 +64,7 @@ describe("resolveConfigShow", () => {
|
|
|
47
64
|
installSource: "pnpm (global)",
|
|
48
65
|
configFilePath: "/tmp/pragma.config.json",
|
|
49
66
|
configFileExists: true,
|
|
67
|
+
...PROVENANCE,
|
|
50
68
|
},
|
|
51
69
|
);
|
|
52
70
|
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { PragmaConfig } from "#config";
|
|
1
|
+
import type { ConfigOrigins, PragmaConfig } from "#config";
|
|
2
2
|
import { CHANNEL_RELEASES } from "../../shared/filters/buildChannelFilter.js";
|
|
3
3
|
import { resolveTierChain } from "../../shared/filters/buildTierFilter.js";
|
|
4
4
|
import type { ConfigShowData } from "./types.js";
|
|
@@ -20,6 +20,9 @@ export default function resolveConfigShow(
|
|
|
20
20
|
installSource: string;
|
|
21
21
|
configFilePath: string;
|
|
22
22
|
configFileExists: boolean;
|
|
23
|
+
globalConfigPath: string;
|
|
24
|
+
globalConfigExists: boolean;
|
|
25
|
+
origins: ConfigOrigins;
|
|
23
26
|
},
|
|
24
27
|
): ConfigShowData {
|
|
25
28
|
// Don't throw for stale config — show always displays what's configured.
|
|
@@ -38,5 +41,8 @@ export default function resolveConfigShow(
|
|
|
38
41
|
installSource: opts.installSource,
|
|
39
42
|
configFilePath: opts.configFilePath,
|
|
40
43
|
configFileExists: opts.configFileExists,
|
|
44
|
+
globalConfigPath: opts.globalConfigPath,
|
|
45
|
+
globalConfigExists: opts.globalConfigExists,
|
|
46
|
+
origins: opts.origins,
|
|
41
47
|
};
|
|
42
48
|
}
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { ConfigOrigins } from "#config";
|
|
1
2
|
import type { Channel } from "#constants";
|
|
2
3
|
|
|
3
4
|
/** Data returned by `pragma config show`, including resolved tier chain and channel releases. */
|
|
@@ -8,6 +9,12 @@ export interface ConfigShowData {
|
|
|
8
9
|
readonly includedReleases: readonly string[];
|
|
9
10
|
readonly packageManager: string;
|
|
10
11
|
readonly installSource: string;
|
|
12
|
+
/** Nearest project config file (or the would-be path at cwd). */
|
|
11
13
|
readonly configFilePath: string;
|
|
12
14
|
readonly configFileExists: boolean;
|
|
15
|
+
/** The global XDG config file. */
|
|
16
|
+
readonly globalConfigPath: string;
|
|
17
|
+
readonly globalConfigExists: boolean;
|
|
18
|
+
/** Which layer supplied each effective field. */
|
|
19
|
+
readonly origins: ConfigOrigins;
|
|
13
20
|
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Config read story — the single declaration of `config show` for both
|
|
3
|
+
* surfaces.
|
|
4
|
+
*
|
|
5
|
+
* Both surfaces resolve from the filesystem, so the MCP tool now reports
|
|
6
|
+
* the real config path and existence (it previously hardcoded
|
|
7
|
+
* `pragma.config.json` / `true` regardless of the working directory).
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { readConfigLayers } from "#config";
|
|
11
|
+
import { detectInstallSource } from "#package-manager";
|
|
12
|
+
import type { ReadStory } from "../shared/stories/index.js";
|
|
13
|
+
import { showFormatters } from "./formatters/index.js";
|
|
14
|
+
import { resolveConfigShow } from "./operations/index.js";
|
|
15
|
+
import type { ConfigShowData } from "./operations/types.js";
|
|
16
|
+
|
|
17
|
+
/** The `config show` / `config_show` read story. */
|
|
18
|
+
export const configShowStory: ReadStory<ConfigShowData, ConfigShowData> = {
|
|
19
|
+
noun: "config",
|
|
20
|
+
verb: "show",
|
|
21
|
+
description: "Display resolved configuration",
|
|
22
|
+
toolDescription:
|
|
23
|
+
"Show current pragma configuration (tier and channel settings).",
|
|
24
|
+
params: [],
|
|
25
|
+
examples: ["pragma config show"],
|
|
26
|
+
resolve: async (rt) => {
|
|
27
|
+
const layers = readConfigLayers(rt.cwd);
|
|
28
|
+
const install = detectInstallSource();
|
|
29
|
+
return resolveConfigShow(layers.config, {
|
|
30
|
+
packageManager: install.packageManager,
|
|
31
|
+
installSource: install.label,
|
|
32
|
+
configFilePath: layers.project.path,
|
|
33
|
+
configFileExists: layers.project.exists,
|
|
34
|
+
globalConfigPath: layers.global.path,
|
|
35
|
+
globalConfigExists: layers.global.exists,
|
|
36
|
+
origins: layers.origins,
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
toOutput: (data) => data,
|
|
40
|
+
formatters: showFormatters,
|
|
41
|
+
toEnvelope: (data) => ({ data }),
|
|
42
|
+
};
|
|
@@ -3,3 +3,4 @@ export { default as setChannelTask } from "./setChannel.js";
|
|
|
3
3
|
export { default as setFrameworkTask } from "./setFramework.js";
|
|
4
4
|
export { default as setTierTask } from "./setTier.js";
|
|
5
5
|
export { default as setTraceTask } from "./setTrace.js";
|
|
6
|
+
export { default as writeConfigFieldTask } from "./writeConfigField.js";
|
|
@@ -1,36 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import type { Task } from "@canonical/task";
|
|
2
|
+
import type { ConfigScope } from "../../../config/index.js";
|
|
3
|
+
import type { Channel } from "../../../constants.js";
|
|
4
|
+
import writeConfigField from "./writeConfigField.js";
|
|
4
5
|
|
|
5
6
|
/**
|
|
6
7
|
* Build a config-write task for setting or resetting the release channel.
|
|
7
8
|
*
|
|
8
|
-
* @param cwd - Working directory
|
|
9
|
-
* @param channel - Channel
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
9
|
+
* @param cwd - Working directory the target config layer is resolved from.
|
|
10
|
+
* @param channel - Channel to persist, or `undefined` to reset.
|
|
11
|
+
* @param scope - Optional explicit target layer (`"global"` | `"local"`).
|
|
12
|
+
* @returns Task yielding the persisted field/value payload and target path.
|
|
13
|
+
* @note - Impure — reads and writes the target config file.
|
|
12
14
|
*/
|
|
13
15
|
export default function setChannel(
|
|
14
16
|
cwd: string,
|
|
15
|
-
channel:
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
// `trace`) survive. `readConfig` normalises defaults (tier: undefined,
|
|
20
|
-
// channel: "normal"); strip those to keep the file minimal, then apply
|
|
21
|
-
// the channel change (`undefined` resets it).
|
|
22
|
-
const current = readConfig(cwd);
|
|
23
|
-
const { tier, channel: _current, ...rest } = current;
|
|
24
|
-
const next = {
|
|
25
|
-
...rest,
|
|
26
|
-
...(tier !== undefined ? { tier } : {}),
|
|
27
|
-
...(channel !== undefined ? { channel } : {}),
|
|
28
|
-
};
|
|
29
|
-
|
|
30
|
-
yield* $(
|
|
31
|
-
writeFile(resolveConfigPath(cwd), `${JSON.stringify(next, null, 2)}\n`),
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
return { field: "channel", value: channel };
|
|
35
|
-
});
|
|
17
|
+
channel: Channel | undefined,
|
|
18
|
+
scope?: ConfigScope,
|
|
19
|
+
): Task<{ field: "channel"; value: Channel | undefined; path: string }> {
|
|
20
|
+
return writeConfigField(cwd, "channel", channel, scope);
|
|
36
21
|
}
|
|
@@ -1,40 +1,21 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import type {
|
|
3
|
-
import {
|
|
4
|
-
import
|
|
1
|
+
import type { Task } from "@canonical/task";
|
|
2
|
+
import type { ConfigScope } from "../../../config/index.js";
|
|
3
|
+
import type { Framework } from "../../../constants.js";
|
|
4
|
+
import writeConfigField from "./writeConfigField.js";
|
|
5
5
|
|
|
6
6
|
/**
|
|
7
7
|
* Build a config-write task for setting or resetting the preferred framework.
|
|
8
8
|
*
|
|
9
|
-
* @param cwd - Working directory
|
|
9
|
+
* @param cwd - Working directory the target config layer is resolved from.
|
|
10
10
|
* @param framework - Framework to persist, or `undefined` to reset.
|
|
11
|
-
* @
|
|
12
|
-
* @
|
|
11
|
+
* @param scope - Optional explicit target layer (`"global"` | `"local"`).
|
|
12
|
+
* @returns Task yielding the persisted field/value payload and target path.
|
|
13
|
+
* @note - Impure — reads and writes the target config file.
|
|
13
14
|
*/
|
|
14
15
|
export default function setFramework(
|
|
15
16
|
cwd: string,
|
|
16
17
|
framework: Framework | undefined,
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
// `trace`) survive. `readConfig` normalises defaults (tier: undefined,
|
|
21
|
-
// channel: "normal"); strip those to keep the file minimal, then apply
|
|
22
|
-
// the framework change (`undefined` resets it).
|
|
23
|
-
const current = readConfig(cwd);
|
|
24
|
-
// Destructure out `framework` too, so a reset (framework === undefined)
|
|
25
|
-
// actually removes it rather than letting the old value survive in `rest`.
|
|
26
|
-
const { tier, channel, framework: _current, ...rest } = current;
|
|
27
|
-
const next = {
|
|
28
|
-
...rest,
|
|
29
|
-
...(tier !== undefined ? { tier } : {}),
|
|
30
|
-
...(channel !== "normal" ? { channel } : {}),
|
|
31
|
-
...(framework !== undefined ? { framework } : {}),
|
|
32
|
-
};
|
|
33
|
-
|
|
34
|
-
yield* $(
|
|
35
|
-
writeFile(resolveConfigPath(cwd), `${JSON.stringify(next, null, 2)}\n`),
|
|
36
|
-
);
|
|
37
|
-
|
|
38
|
-
return { field: "framework", value: framework };
|
|
39
|
-
});
|
|
18
|
+
scope?: ConfigScope,
|
|
19
|
+
): Task<{ field: "framework"; value: Framework | undefined; path: string }> {
|
|
20
|
+
return writeConfigField(cwd, "framework", framework, scope);
|
|
40
21
|
}
|
|
@@ -1,39 +1,20 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import
|
|
1
|
+
import type { Task } from "@canonical/task";
|
|
2
|
+
import type { ConfigScope } from "../../../config/index.js";
|
|
3
|
+
import writeConfigField from "./writeConfigField.js";
|
|
4
4
|
|
|
5
5
|
/**
|
|
6
6
|
* Build a config-write task for setting or resetting the active tier.
|
|
7
7
|
*
|
|
8
|
-
* @param cwd - Working directory
|
|
8
|
+
* @param cwd - Working directory the target config layer is resolved from.
|
|
9
9
|
* @param tier - Tier path to persist, or `undefined` to reset.
|
|
10
|
-
* @
|
|
11
|
-
* @
|
|
10
|
+
* @param scope - Optional explicit target layer (`"global"` | `"local"`).
|
|
11
|
+
* @returns Task yielding the persisted field/value payload and target path.
|
|
12
|
+
* @note - Impure — reads and writes the target config file.
|
|
12
13
|
*/
|
|
13
14
|
export default function setTier(
|
|
14
15
|
cwd: string,
|
|
15
16
|
tier: string | undefined,
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
}> {
|
|
20
|
-
return gen(function* () {
|
|
21
|
-
// Merge onto the full existing config so other fields (e.g. `packages`,
|
|
22
|
-
// `trace`) survive. `readConfig` normalises defaults (tier: undefined,
|
|
23
|
-
// channel: "normal"); strip those to keep the file minimal, then apply
|
|
24
|
-
// the tier change (`undefined` resets it).
|
|
25
|
-
const current = readConfig(cwd);
|
|
26
|
-
const { tier: _current, channel, ...rest } = current;
|
|
27
|
-
const next = {
|
|
28
|
-
...rest,
|
|
29
|
-
...(tier !== undefined ? { tier } : {}),
|
|
30
|
-
...(channel !== "normal" ? { channel } : {}),
|
|
31
|
-
};
|
|
32
|
-
|
|
33
|
-
yield* $(
|
|
34
|
-
writeFile(resolveConfigPath(cwd), `${JSON.stringify(next, null, 2)}\n`),
|
|
35
|
-
);
|
|
36
|
-
|
|
37
|
-
return { field: "tier", value: tier };
|
|
38
|
-
});
|
|
17
|
+
scope?: ConfigScope,
|
|
18
|
+
): Task<{ field: "tier"; value: string | undefined; path: string }> {
|
|
19
|
+
return writeConfigField(cwd, "tier", tier, scope);
|
|
39
20
|
}
|