@intellectronica/ruler 0.3.42 → 0.3.43
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 +97 -10
- package/dist/agents/AbstractAgent.js +3 -2
- package/dist/agents/AgentsMdAgent.js +3 -2
- package/dist/agents/AiderAgent.js +4 -3
- package/dist/agents/AmazonQCliAgent.js +6 -4
- package/dist/agents/AugmentCodeAgent.js +3 -2
- package/dist/agents/CodexCliAgent.js +1 -1
- package/dist/agents/CrushAgent.d.ts +1 -1
- package/dist/agents/CrushAgent.js +15 -6
- package/dist/agents/FirebenderAgent.js +5 -4
- package/dist/agents/GeminiCliAgent.d.ts +1 -0
- package/dist/agents/GeminiCliAgent.js +11 -5
- package/dist/agents/IAgent.d.ts +2 -0
- package/dist/agents/MistralVibeAgent.js +14 -3
- package/dist/agents/OpenCodeAgent.d.ts +1 -1
- package/dist/agents/OpenCodeAgent.js +10 -3
- package/dist/agents/QwenCodeAgent.d.ts +1 -0
- package/dist/agents/QwenCodeAgent.js +9 -3
- package/dist/agents/RooCodeAgent.js +3 -2
- package/dist/agents/ZedAgent.js +3 -3
- package/dist/constants.d.ts +1 -1
- package/dist/constants.js +1 -1
- package/dist/core/ConfigLoader.d.ts +2 -0
- package/dist/core/ConfigLoader.js +73 -6
- package/dist/core/FileSystemUtils.d.ts +4 -2
- package/dist/core/FileSystemUtils.js +120 -3
- package/dist/core/GitignoreUtils.d.ts +10 -0
- package/dist/core/GitignoreUtils.js +62 -31
- package/dist/core/SkillsProcessor.d.ts +2 -2
- package/dist/core/SkillsProcessor.js +46 -37
- package/dist/core/SubagentsProcessor.js +8 -5
- package/dist/core/UnifiedConfigLoader.js +54 -2
- package/dist/core/UnifiedConfigTypes.d.ts +3 -1
- package/dist/core/agent-selection.js +6 -4
- package/dist/core/apply-engine.d.ts +1 -0
- package/dist/core/apply-engine.js +38 -15
- package/dist/core/revert-engine.d.ts +2 -1
- package/dist/core/revert-engine.js +73 -26
- package/dist/lib.js +9 -6
- package/dist/mcp/merge.js +28 -26
- package/dist/mcp/propagateOpenCodeMcp.d.ts +1 -1
- package/dist/mcp/propagateOpenCodeMcp.js +10 -3
- package/dist/mcp/propagateOpenHandsMcp.d.ts +1 -1
- package/dist/mcp/propagateOpenHandsMcp.js +18 -7
- package/dist/paths/mcp.d.ts +1 -1
- package/dist/paths/mcp.js +11 -4
- package/dist/revert.js +27 -27
- package/dist/vscode/settings.d.ts +1 -1
- package/dist/vscode/settings.js +3 -3
- package/package.json +4 -3
|
@@ -105,6 +105,7 @@ async function createHierarchicalConfiguration(rulerDir, files, config, cliConfi
|
|
|
105
105
|
config,
|
|
106
106
|
concatenatedRules,
|
|
107
107
|
rulerMcpJson,
|
|
108
|
+
projectRoot: directoryRoot,
|
|
108
109
|
};
|
|
109
110
|
}
|
|
110
111
|
async function loadConfigForRulerDir(rulerDir, cliConfigPath, resolvedNested, localOnly) {
|
|
@@ -139,6 +140,12 @@ function cloneLoadedConfig(config) {
|
|
|
139
140
|
clonedAgentConfigs[agent] = {
|
|
140
141
|
...agentConfig,
|
|
141
142
|
mcp: agentConfig.mcp ? { ...agentConfig.mcp } : undefined,
|
|
143
|
+
mcpServers: agentConfig.mcpServers
|
|
144
|
+
? Object.fromEntries(Object.entries(agentConfig.mcpServers).map(([name, server]) => [
|
|
145
|
+
name,
|
|
146
|
+
{ ...server },
|
|
147
|
+
]))
|
|
148
|
+
: undefined,
|
|
142
149
|
};
|
|
143
150
|
}
|
|
144
151
|
return {
|
|
@@ -194,9 +201,10 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
194
201
|
const { dirs: rulerDirs, primaryDir } = await findRulerDirectories(projectRoot, localOnly, false);
|
|
195
202
|
// Warn about legacy mcp.json
|
|
196
203
|
await warnAboutLegacyMcpJson(primaryDir);
|
|
204
|
+
const effectiveProjectRoot = FileSystemUtils.resolveProjectRootForRulerDir(projectRoot, primaryDir);
|
|
197
205
|
// Load the ruler.toml configuration
|
|
198
206
|
const config = await (0, ConfigLoader_1.loadConfig)({
|
|
199
|
-
projectRoot,
|
|
207
|
+
projectRoot: effectiveProjectRoot,
|
|
200
208
|
configPath,
|
|
201
209
|
checkGlobal: !localOnly,
|
|
202
210
|
});
|
|
@@ -210,7 +218,7 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
210
218
|
// Load unified config to get merged MCP configuration
|
|
211
219
|
const { loadUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('./UnifiedConfigLoader')));
|
|
212
220
|
const unifiedConfig = await loadUnifiedConfig({
|
|
213
|
-
projectRoot,
|
|
221
|
+
projectRoot: effectiveProjectRoot,
|
|
214
222
|
configPath,
|
|
215
223
|
checkGlobal: !localOnly,
|
|
216
224
|
});
|
|
@@ -225,6 +233,7 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
225
233
|
config,
|
|
226
234
|
concatenatedRules,
|
|
227
235
|
rulerMcpJson,
|
|
236
|
+
projectRoot: effectiveProjectRoot,
|
|
228
237
|
};
|
|
229
238
|
}
|
|
230
239
|
/**
|
|
@@ -282,7 +291,7 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
282
291
|
(0, constants_1.logInfo)(`Applying rules for ${agent.getName()}...`, dryRun);
|
|
283
292
|
(0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
|
|
284
293
|
const agentConfig = config.agentConfigs[agent.getIdentifier()];
|
|
285
|
-
const agentRulerMcpJson = rulerMcpJson;
|
|
294
|
+
const agentRulerMcpJson = mergeAgentMcpServers(rulerMcpJson, agentConfig);
|
|
286
295
|
// Collect output paths for .gitignore
|
|
287
296
|
const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
288
297
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
|
|
@@ -358,6 +367,19 @@ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson,
|
|
|
358
367
|
function shouldUseEngineManagedMcp(agent) {
|
|
359
368
|
return (agent.getIdentifier() === 'codex' || agent.getIdentifier() === 'opencode');
|
|
360
369
|
}
|
|
370
|
+
function mergeAgentMcpServers(rulerMcpJson, agentConfig) {
|
|
371
|
+
const baseServers = rulerMcpJson?.mcpServers && typeof rulerMcpJson.mcpServers === 'object'
|
|
372
|
+
? rulerMcpJson.mcpServers
|
|
373
|
+
: {};
|
|
374
|
+
const agentServers = agentConfig?.mcpServers ?? {};
|
|
375
|
+
const mergedServers = {
|
|
376
|
+
...baseServers,
|
|
377
|
+
...agentServers,
|
|
378
|
+
};
|
|
379
|
+
return Object.keys(mergedServers).length > 0
|
|
380
|
+
? { mcpServers: mergedServers }
|
|
381
|
+
: null;
|
|
382
|
+
}
|
|
361
383
|
async function resolveMcpDestination(agent, agentConfig, projectRoot) {
|
|
362
384
|
if (agentConfig?.outputPathConfig) {
|
|
363
385
|
return path.resolve(projectRoot, agentConfig.outputPathConfig);
|
|
@@ -412,10 +434,10 @@ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig,
|
|
|
412
434
|
}
|
|
413
435
|
const agentMcpJson = sanitizeMcpTimeoutsForAgent(agent, filteredMcpJson, dryRun);
|
|
414
436
|
if (agent.getIdentifier() === 'openhands') {
|
|
415
|
-
return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, cliMcpStrategy ?? agentConfig?.mcp?.strategy ?? config.mcp?.strategy, dryRun, verbose, backup);
|
|
437
|
+
return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, projectRoot, cliMcpStrategy ?? agentConfig?.mcp?.strategy ?? config.mcp?.strategy, dryRun, verbose, backup);
|
|
416
438
|
}
|
|
417
439
|
if (agent.getIdentifier() === 'opencode') {
|
|
418
|
-
return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, cliMcpStrategy ?? agentConfig?.mcp?.strategy ?? config.mcp?.strategy, dryRun, verbose, backup);
|
|
440
|
+
return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, projectRoot, cliMcpStrategy ?? agentConfig?.mcp?.strategy ?? config.mcp?.strategy, dryRun, verbose, backup);
|
|
419
441
|
}
|
|
420
442
|
// Agents that handle MCP configuration internally should not have external MCP handling
|
|
421
443
|
if (agent.getIdentifier() === 'zed' ||
|
|
@@ -425,22 +447,22 @@ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig,
|
|
|
425
447
|
(0, constants_1.logVerbose)(`Skipping external MCP config for ${agent.getName()} - handled internally by agent`, verbose);
|
|
426
448
|
return;
|
|
427
449
|
}
|
|
428
|
-
return await applyStandardMcpConfiguration(agent, agentMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
|
|
450
|
+
return await applyStandardMcpConfiguration(agent, agentMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup);
|
|
429
451
|
}
|
|
430
|
-
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, strategy, dryRun, verbose, backup = true) {
|
|
452
|
+
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, projectRoot, strategy, dryRun, verbose, backup = true) {
|
|
431
453
|
if (dryRun) {
|
|
432
454
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
|
|
433
455
|
}
|
|
434
456
|
else {
|
|
435
|
-
await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest, backup, strategy);
|
|
457
|
+
await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest, backup, strategy, projectRoot);
|
|
436
458
|
}
|
|
437
459
|
}
|
|
438
|
-
async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, strategy, dryRun, verbose, backup = true) {
|
|
460
|
+
async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, projectRoot, strategy, dryRun, verbose, backup = true) {
|
|
439
461
|
if (dryRun) {
|
|
440
462
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
|
|
441
463
|
}
|
|
442
464
|
else {
|
|
443
|
-
await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup, strategy);
|
|
465
|
+
await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup, strategy, projectRoot);
|
|
444
466
|
}
|
|
445
467
|
}
|
|
446
468
|
/**
|
|
@@ -536,7 +558,7 @@ function transformMcpForFactoryDroid(mcpJson) {
|
|
|
536
558
|
transformedMcp.mcpServers = transformedServers;
|
|
537
559
|
return transformedMcp;
|
|
538
560
|
}
|
|
539
|
-
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
561
|
+
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
540
562
|
const strategy = cliMcpStrategy ??
|
|
541
563
|
agentConfig?.mcp?.strategy ??
|
|
542
564
|
config.mcp?.strategy ??
|
|
@@ -554,7 +576,8 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
554
576
|
else {
|
|
555
577
|
// Transform MCP config for agent-specific compatibility
|
|
556
578
|
let mcpToMerge = filteredMcpJson;
|
|
557
|
-
if (agent.getIdentifier() === 'claude'
|
|
579
|
+
if (agent.getIdentifier() === 'claude' ||
|
|
580
|
+
agent.getIdentifier() === 'aider') {
|
|
558
581
|
mcpToMerge = transformMcpForClaude(filteredMcpJson);
|
|
559
582
|
}
|
|
560
583
|
else if (agent.getIdentifier() === 'kilocode') {
|
|
@@ -633,13 +656,13 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
633
656
|
if (currentContent !== newContent) {
|
|
634
657
|
if (backup) {
|
|
635
658
|
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
636
|
-
await backupFile(dest);
|
|
659
|
+
await backupFile(dest, projectRoot);
|
|
637
660
|
}
|
|
638
661
|
if (isCodexToml) {
|
|
639
|
-
await FileSystemUtils.writeGeneratedFile(dest, (0, toml_1.stringify)(toWrite));
|
|
662
|
+
await FileSystemUtils.writeGeneratedFile(dest, (0, toml_1.stringify)(toWrite), projectRoot);
|
|
640
663
|
}
|
|
641
664
|
else {
|
|
642
|
-
await (0, mcp_1.writeNativeMcp)(dest, toWrite);
|
|
665
|
+
await (0, mcp_1.writeNativeMcp)(dest, toWrite, projectRoot);
|
|
643
666
|
}
|
|
644
667
|
}
|
|
645
668
|
else {
|
|
@@ -20,7 +20,7 @@ export interface CleanUpResult {
|
|
|
20
20
|
* @param agent The agent to revert
|
|
21
21
|
* @param projectRoot Root directory of the project
|
|
22
22
|
* @param agentConfig Agent-specific configuration
|
|
23
|
-
* @param keepBackups Whether
|
|
23
|
+
* @param keepBackups Whether restored backup files should be preserved
|
|
24
24
|
* @param verbose Whether to enable verbose logging
|
|
25
25
|
* @param dryRun Whether to perform a dry run
|
|
26
26
|
* @returns Promise resolving to revert statistics
|
|
@@ -34,3 +34,4 @@ export declare function revertAgentConfiguration(agent: IAgent, projectRoot: str
|
|
|
34
34
|
* @returns Promise resolving to cleanup statistics
|
|
35
35
|
*/
|
|
36
36
|
export declare function cleanUpAuxiliaryFiles(projectRoot: string, verbose: boolean, dryRun: boolean): Promise<CleanUpResult>;
|
|
37
|
+
export declare function cleanUpAgentDirectories(agent: IAgent, projectRoot: string, agentConfig: IAgentConfig | undefined, verbose: boolean, dryRun: boolean): Promise<number>;
|
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.revertAgentConfiguration = revertAgentConfiguration;
|
|
37
37
|
exports.cleanUpAuxiliaryFiles = cleanUpAuxiliaryFiles;
|
|
38
|
+
exports.cleanUpAgentDirectories = cleanUpAgentDirectories;
|
|
38
39
|
const path = __importStar(require("path"));
|
|
39
40
|
const fs_1 = require("fs");
|
|
40
41
|
const agent_utils_1 = require("../agents/agent-utils");
|
|
@@ -43,9 +44,20 @@ const constants_1 = require("../constants");
|
|
|
43
44
|
const GitignoreUtils_1 = require("./GitignoreUtils");
|
|
44
45
|
const settings_1 = require("../vscode/settings");
|
|
45
46
|
const path_utils_1 = require("./path-utils");
|
|
47
|
+
const FileSystemUtils_1 = require("./FileSystemUtils");
|
|
46
48
|
const RULER_START_MARKER = '# START Ruler Generated Files';
|
|
47
49
|
const RULER_END_MARKER = '# END Ruler Generated Files';
|
|
48
50
|
const RULER_GENERATED_MARKER = '<!-- Generated by Ruler -->';
|
|
51
|
+
const RULER_SOURCE_MARKER_PREFIXES = [
|
|
52
|
+
'<!-- Source: .ruler/',
|
|
53
|
+
'<!-- Source: ruler/',
|
|
54
|
+
];
|
|
55
|
+
async function resolveMcpPathForRevert(agent, projectRoot, agentConfig) {
|
|
56
|
+
if (agentConfig?.outputPathConfig) {
|
|
57
|
+
return path.resolve(projectRoot, agentConfig.outputPathConfig);
|
|
58
|
+
}
|
|
59
|
+
return await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
60
|
+
}
|
|
49
61
|
/**
|
|
50
62
|
* Checks if a file exists.
|
|
51
63
|
*/
|
|
@@ -90,6 +102,10 @@ async function hasRulerGeneratedProvenance(filePath, projectRoot) {
|
|
|
90
102
|
if (content.startsWith(RULER_GENERATED_MARKER)) {
|
|
91
103
|
return true;
|
|
92
104
|
}
|
|
105
|
+
const trimmedContent = content.trimStart();
|
|
106
|
+
if (RULER_SOURCE_MARKER_PREFIXES.some((prefix) => trimmedContent.startsWith(prefix))) {
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
93
109
|
}
|
|
94
110
|
catch {
|
|
95
111
|
return false;
|
|
@@ -109,7 +125,7 @@ async function hasRulerGeneratedProvenance(filePath, projectRoot) {
|
|
|
109
125
|
/**
|
|
110
126
|
* Restores a file from its backup if the backup exists.
|
|
111
127
|
*/
|
|
112
|
-
async function restoreFromBackup(filePath, verbose, dryRun) {
|
|
128
|
+
async function restoreFromBackup(filePath, verbose, dryRun, projectRoot) {
|
|
113
129
|
const backupPath = `${filePath}.bak`;
|
|
114
130
|
const backupExists = await fileExists(backupPath);
|
|
115
131
|
if (!backupExists) {
|
|
@@ -121,6 +137,10 @@ async function restoreFromBackup(filePath, verbose, dryRun) {
|
|
|
121
137
|
(0, constants_1.logVerbose)(`${prefix} Would restore: ${filePath} from backup`, verbose);
|
|
122
138
|
}
|
|
123
139
|
else {
|
|
140
|
+
if (projectRoot) {
|
|
141
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(filePath, projectRoot, 'Refusing to restore backup through symlinked output path');
|
|
142
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(backupPath, projectRoot, 'Refusing to restore from backup through symlinked backup path');
|
|
143
|
+
}
|
|
124
144
|
await fs_1.promises.copyFile(backupPath, filePath);
|
|
125
145
|
(0, constants_1.logVerbose)(`${prefix} Restored: ${filePath} from backup`, verbose);
|
|
126
146
|
}
|
|
@@ -150,6 +170,9 @@ async function removeGeneratedFile(filePath, verbose, dryRun, projectRoot) {
|
|
|
150
170
|
(0, constants_1.logVerbose)(`${prefix} Would remove generated file: ${filePath}`, verbose);
|
|
151
171
|
}
|
|
152
172
|
else {
|
|
173
|
+
if (projectRoot) {
|
|
174
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(filePath, projectRoot, 'Refusing to remove generated file through symlinked path');
|
|
175
|
+
}
|
|
153
176
|
await fs_1.promises.unlink(filePath);
|
|
154
177
|
(0, constants_1.logVerbose)(`${prefix} Removed generated file: ${filePath}`, verbose);
|
|
155
178
|
}
|
|
@@ -158,7 +181,7 @@ async function removeGeneratedFile(filePath, verbose, dryRun, projectRoot) {
|
|
|
158
181
|
/**
|
|
159
182
|
* Removes backup files.
|
|
160
183
|
*/
|
|
161
|
-
async function removeBackupFile(filePath, verbose, dryRun) {
|
|
184
|
+
async function removeBackupFile(filePath, verbose, dryRun, projectRoot) {
|
|
162
185
|
const backupPath = `${filePath}.bak`;
|
|
163
186
|
const backupExists = await fileExists(backupPath);
|
|
164
187
|
if (!backupExists) {
|
|
@@ -169,6 +192,9 @@ async function removeBackupFile(filePath, verbose, dryRun) {
|
|
|
169
192
|
(0, constants_1.logVerbose)(`${prefix} Would remove backup file: ${backupPath}`, verbose);
|
|
170
193
|
}
|
|
171
194
|
else {
|
|
195
|
+
if (projectRoot) {
|
|
196
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(backupPath, projectRoot, 'Refusing to remove backup file through symlinked path');
|
|
197
|
+
}
|
|
172
198
|
await fs_1.promises.unlink(backupPath);
|
|
173
199
|
(0, constants_1.logVerbose)(`${prefix} Removed backup file: ${backupPath}`, verbose);
|
|
174
200
|
}
|
|
@@ -320,7 +346,7 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
320
346
|
}
|
|
321
347
|
const backupExists = await fileExists(`${fullPath}.bak`);
|
|
322
348
|
if (backupExists) {
|
|
323
|
-
const restored = await restoreFromBackup(fullPath, verbose, dryRun);
|
|
349
|
+
const restored = await restoreFromBackup(fullPath, verbose, dryRun, projectRoot);
|
|
324
350
|
if (restored) {
|
|
325
351
|
filesRemoved++;
|
|
326
352
|
}
|
|
@@ -346,7 +372,7 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
346
372
|
const settingsPath = (0, settings_1.getVSCodeSettingsPath)(projectRoot);
|
|
347
373
|
const backupPath = `${settingsPath}.bak`;
|
|
348
374
|
if (await fileExists(backupPath)) {
|
|
349
|
-
const restored = await restoreFromBackup(settingsPath, verbose, dryRun);
|
|
375
|
+
const restored = await restoreFromBackup(settingsPath, verbose, dryRun, projectRoot);
|
|
350
376
|
if (restored) {
|
|
351
377
|
filesRemoved++;
|
|
352
378
|
(0, constants_1.logVerbose)(`${prefix} Restored VSCode settings from backup`, verbose);
|
|
@@ -374,11 +400,12 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
374
400
|
delete settings['augment.advanced'];
|
|
375
401
|
const remainingKeys = Object.keys(settings);
|
|
376
402
|
if (remainingKeys.length === 0) {
|
|
403
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(settingsPath, projectRoot, 'Refusing to remove VSCode settings through symlinked path');
|
|
377
404
|
await fs_1.promises.unlink(settingsPath);
|
|
378
405
|
(0, constants_1.logVerbose)(`${prefix} Removed empty VSCode settings file`, verbose);
|
|
379
406
|
}
|
|
380
407
|
else {
|
|
381
|
-
await (0, settings_1.writeVSCodeSettings)(settingsPath, settings);
|
|
408
|
+
await (0, settings_1.writeVSCodeSettings)(settingsPath, settings, projectRoot);
|
|
382
409
|
(0, constants_1.logVerbose)(`${prefix} Removed augment.advanced section from VSCode settings`, verbose);
|
|
383
410
|
}
|
|
384
411
|
filesRemoved++;
|
|
@@ -399,7 +426,7 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
399
426
|
* @param agent The agent to revert
|
|
400
427
|
* @param projectRoot Root directory of the project
|
|
401
428
|
* @param agentConfig Agent-specific configuration
|
|
402
|
-
* @param keepBackups Whether
|
|
429
|
+
* @param keepBackups Whether restored backup files should be preserved
|
|
403
430
|
* @param verbose Whether to enable verbose logging
|
|
404
431
|
* @param dryRun Whether to perform a dry run
|
|
405
432
|
* @returns Promise resolving to revert statistics
|
|
@@ -411,13 +438,20 @@ async function revertAgentConfiguration(agent, projectRoot, agentConfig, keepBac
|
|
|
411
438
|
backupsRemoved: 0,
|
|
412
439
|
};
|
|
413
440
|
const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
441
|
+
const processedPaths = new Set();
|
|
414
442
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
|
|
415
|
-
|
|
416
|
-
const
|
|
443
|
+
const processPath = async (outputPath) => {
|
|
444
|
+
const resolvedPath = path.resolve(projectRoot, outputPath);
|
|
445
|
+
if (processedPaths.has(resolvedPath)) {
|
|
446
|
+
(0, constants_1.logVerbose)(`Skipping already processed path: ${outputPath}`, verbose);
|
|
447
|
+
return;
|
|
448
|
+
}
|
|
449
|
+
processedPaths.add(resolvedPath);
|
|
450
|
+
const restored = await restoreFromBackup(outputPath, verbose, dryRun, projectRoot);
|
|
417
451
|
if (restored) {
|
|
418
452
|
result.restored++;
|
|
419
453
|
if (!keepBackups) {
|
|
420
|
-
const backupRemoved = await removeBackupFile(outputPath, verbose, dryRun);
|
|
454
|
+
const backupRemoved = await removeBackupFile(outputPath, verbose, dryRun, projectRoot);
|
|
421
455
|
if (backupRemoved) {
|
|
422
456
|
result.backupsRemoved++;
|
|
423
457
|
}
|
|
@@ -429,31 +463,19 @@ async function revertAgentConfiguration(agent, projectRoot, agentConfig, keepBac
|
|
|
429
463
|
result.removed++;
|
|
430
464
|
}
|
|
431
465
|
}
|
|
466
|
+
};
|
|
467
|
+
for (const outputPath of outputPaths) {
|
|
468
|
+
await processPath(outputPath);
|
|
432
469
|
}
|
|
433
470
|
// Handle MCP files
|
|
434
|
-
const mcpPath = await (
|
|
471
|
+
const mcpPath = await resolveMcpPathForRevert(agent, projectRoot, agentConfig);
|
|
435
472
|
if (mcpPath && (0, path_utils_1.isPathInsideOrEqual)(projectRoot, mcpPath)) {
|
|
436
473
|
if (agent.getName() === 'AugmentCode' &&
|
|
437
474
|
mcpPath.endsWith('.vscode/settings.json')) {
|
|
438
475
|
(0, constants_1.logVerbose)(`Skipping MCP handling for AugmentCode settings.json - handled separately`, verbose);
|
|
439
476
|
}
|
|
440
477
|
else {
|
|
441
|
-
|
|
442
|
-
if (mcpRestored) {
|
|
443
|
-
result.restored++;
|
|
444
|
-
if (!keepBackups) {
|
|
445
|
-
const mcpBackupRemoved = await removeBackupFile(mcpPath, verbose, dryRun);
|
|
446
|
-
if (mcpBackupRemoved) {
|
|
447
|
-
result.backupsRemoved++;
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
else {
|
|
452
|
-
const mcpRemoved = await removeGeneratedFile(mcpPath, verbose, dryRun, projectRoot);
|
|
453
|
-
if (mcpRemoved) {
|
|
454
|
-
result.removed++;
|
|
455
|
-
}
|
|
456
|
-
}
|
|
478
|
+
await processPath(mcpPath);
|
|
457
479
|
}
|
|
458
480
|
}
|
|
459
481
|
return result;
|
|
@@ -473,3 +495,28 @@ async function cleanUpAuxiliaryFiles(projectRoot, verbose, dryRun) {
|
|
|
473
495
|
directoriesRemoved,
|
|
474
496
|
};
|
|
475
497
|
}
|
|
498
|
+
async function cleanUpAgentDirectories(agent, projectRoot, agentConfig, verbose, dryRun) {
|
|
499
|
+
const candidateFiles = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
500
|
+
const mcpPath = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
501
|
+
if (mcpPath && (0, path_utils_1.isPathInsideOrEqual)(projectRoot, mcpPath)) {
|
|
502
|
+
candidateFiles.push(mcpPath);
|
|
503
|
+
}
|
|
504
|
+
const candidateDirs = [
|
|
505
|
+
...new Set(candidateFiles.map((filePath) => path.dirname(filePath))),
|
|
506
|
+
];
|
|
507
|
+
let directoriesRemoved = 0;
|
|
508
|
+
const resolvedProjectRoot = path.resolve(projectRoot);
|
|
509
|
+
for (const candidateDir of candidateDirs) {
|
|
510
|
+
let currentDir = path.resolve(candidateDir);
|
|
511
|
+
while (currentDir !== resolvedProjectRoot &&
|
|
512
|
+
(0, path_utils_1.isPathInsideOrEqual)(resolvedProjectRoot, currentDir)) {
|
|
513
|
+
const removed = await removeEmptyDirectory(currentDir, verbose, dryRun);
|
|
514
|
+
if (!removed) {
|
|
515
|
+
break;
|
|
516
|
+
}
|
|
517
|
+
directoriesRemoved++;
|
|
518
|
+
currentDir = path.dirname(currentDir);
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
return directoriesRemoved;
|
|
522
|
+
}
|
package/dist/lib.js
CHANGED
|
@@ -102,6 +102,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
102
102
|
let selectedAgents;
|
|
103
103
|
let generatedPaths;
|
|
104
104
|
let loadedConfig;
|
|
105
|
+
let outputProjectRoot = projectRoot;
|
|
105
106
|
if (nested) {
|
|
106
107
|
const hierarchicalConfigs = await (0, apply_engine_1.loadNestedConfigurations)(projectRoot, configPath, localOnly, nested);
|
|
107
108
|
if (hierarchicalConfigs.length === 0) {
|
|
@@ -147,6 +148,8 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
147
148
|
}
|
|
148
149
|
else {
|
|
149
150
|
const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
|
|
151
|
+
const singleProjectRoot = singleConfig.projectRoot;
|
|
152
|
+
outputProjectRoot = singleProjectRoot;
|
|
150
153
|
loadedConfig = singleConfig.config;
|
|
151
154
|
singleConfig.config.cliAgents = includedAgents;
|
|
152
155
|
(0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(singleConfig.config.agentConfigs).length} agent configs`, verbose);
|
|
@@ -158,7 +161,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
158
161
|
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, singleConfig.config.skills?.enabled);
|
|
159
162
|
if (skillsEnabledResolved) {
|
|
160
163
|
const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
161
|
-
await propagateSkills(
|
|
164
|
+
await propagateSkills(singleProjectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
|
|
162
165
|
}
|
|
163
166
|
// Propagate subagents (mirrors skills handling).
|
|
164
167
|
const subagentsEnabledResolvedSingle = resolveSubagentsEnabled(subagentsEnabled, singleConfig.config.subagents?.enabled);
|
|
@@ -166,9 +169,9 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
166
169
|
const backupEnabledResolvedSingle = resolveBackupEnabled(backup, singleConfig.config.backup?.enabled);
|
|
167
170
|
{
|
|
168
171
|
const { propagateSubagents } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
|
|
169
|
-
await propagateSubagents(
|
|
172
|
+
await propagateSubagents(singleProjectRoot, selectedAgents, subagentsEnabledResolvedSingle, subagentsCleanupOrphanedSingle, verbose, dryRun);
|
|
170
173
|
}
|
|
171
|
-
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig,
|
|
174
|
+
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, singleProjectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backupEnabledResolvedSingle);
|
|
172
175
|
}
|
|
173
176
|
// Add skills-generated paths to gitignore if skills are enabled
|
|
174
177
|
let allGeneratedPaths = generatedPaths;
|
|
@@ -176,17 +179,17 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
176
179
|
if (skillsEnabledForGitignore) {
|
|
177
180
|
// Skills enabled by default or explicitly
|
|
178
181
|
const { getSkillsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
179
|
-
const skillsPaths = await getSkillsGitignorePaths(
|
|
182
|
+
const skillsPaths = await getSkillsGitignorePaths(outputProjectRoot, selectedAgents);
|
|
180
183
|
allGeneratedPaths = [...allGeneratedPaths, ...skillsPaths];
|
|
181
184
|
}
|
|
182
185
|
// Add subagents-generated paths to gitignore if subagents are enabled.
|
|
183
186
|
const subagentsEnabledForGitignore = resolveSubagentsEnabled(subagentsEnabled, loadedConfig.subagents?.enabled);
|
|
184
187
|
if (subagentsEnabledForGitignore) {
|
|
185
188
|
const { getSubagentsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
|
|
186
|
-
const subagentPaths = await getSubagentsGitignorePaths(
|
|
189
|
+
const subagentPaths = await getSubagentsGitignorePaths(outputProjectRoot, selectedAgents);
|
|
187
190
|
allGeneratedPaths = [...allGeneratedPaths, ...subagentPaths];
|
|
188
191
|
}
|
|
189
|
-
await (0, apply_engine_1.updateGitignore)(
|
|
192
|
+
await (0, apply_engine_1.updateGitignore)(outputProjectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun, cliGitignoreLocal);
|
|
190
193
|
}
|
|
191
194
|
/**
|
|
192
195
|
* Normalizes per-agent config keys to agent identifiers for consistent lookup.
|
package/dist/mcp/merge.js
CHANGED
|
@@ -8,6 +8,29 @@ const MCP_SERVER_KEYS = [
|
|
|
8
8
|
'mcp_servers',
|
|
9
9
|
'context_servers',
|
|
10
10
|
];
|
|
11
|
+
function isRecord(value) {
|
|
12
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
13
|
+
}
|
|
14
|
+
function collectMcpServers(config, serverKey) {
|
|
15
|
+
const servers = {};
|
|
16
|
+
const aliases = MCP_SERVER_KEYS.filter((key) => key !== serverKey);
|
|
17
|
+
for (const key of [...aliases, serverKey]) {
|
|
18
|
+
const value = config[key];
|
|
19
|
+
if (isRecord(value)) {
|
|
20
|
+
Object.assign(servers, value);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
return servers;
|
|
24
|
+
}
|
|
25
|
+
function removeServerAliases(config, serverKey) {
|
|
26
|
+
const result = { ...config };
|
|
27
|
+
for (const key of MCP_SERVER_KEYS) {
|
|
28
|
+
if (key !== serverKey) {
|
|
29
|
+
delete result[key];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
11
34
|
/**
|
|
12
35
|
* Merge native and incoming MCP server configurations according to strategy.
|
|
13
36
|
* @param base Existing native MCP config object.
|
|
@@ -18,38 +41,17 @@ const MCP_SERVER_KEYS = [
|
|
|
18
41
|
*/
|
|
19
42
|
function mergeMcp(base, incoming, strategy, serverKey) {
|
|
20
43
|
if (strategy === 'overwrite') {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
const incomingServers = incoming[serverKey] ||
|
|
24
|
-
incoming.mcpServers ||
|
|
25
|
-
incoming.mcp ||
|
|
26
|
-
{};
|
|
27
|
-
const preservedBase = { ...base };
|
|
28
|
-
for (const key of MCP_SERVER_KEYS) {
|
|
29
|
-
if (key !== serverKey) {
|
|
30
|
-
delete preservedBase[key];
|
|
31
|
-
}
|
|
32
|
-
}
|
|
44
|
+
const incomingServers = collectMcpServers(incoming, serverKey);
|
|
45
|
+
const preservedBase = removeServerAliases(base, serverKey);
|
|
33
46
|
return {
|
|
34
47
|
...preservedBase,
|
|
35
48
|
[serverKey]: incomingServers,
|
|
36
49
|
};
|
|
37
50
|
}
|
|
38
|
-
const baseServers = base
|
|
39
|
-
|
|
40
|
-
base.mcp ||
|
|
41
|
-
{};
|
|
42
|
-
const incomingServers = incoming[serverKey] ||
|
|
43
|
-
incoming.mcpServers ||
|
|
44
|
-
incoming.mcp ||
|
|
45
|
-
{};
|
|
51
|
+
const baseServers = collectMcpServers(base, serverKey);
|
|
52
|
+
const incomingServers = collectMcpServers(incoming, serverKey);
|
|
46
53
|
const mergedServers = { ...baseServers, ...incomingServers };
|
|
47
|
-
const newBase =
|
|
48
|
-
for (const key of MCP_SERVER_KEYS) {
|
|
49
|
-
if (key !== serverKey) {
|
|
50
|
-
delete newBase[key];
|
|
51
|
-
}
|
|
52
|
-
}
|
|
54
|
+
const newBase = removeServerAliases(base, serverKey);
|
|
53
55
|
return {
|
|
54
56
|
...newBase,
|
|
55
57
|
[serverKey]: mergedServers,
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { McpStrategy } from '../types';
|
|
2
|
-
export declare function propagateMcpToOpenCode(rulerMcpData: Record<string, unknown> | null, openCodeConfigPath: string, backup?: boolean, strategy?: McpStrategy): Promise<void>;
|
|
2
|
+
export declare function propagateMcpToOpenCode(rulerMcpData: Record<string, unknown> | null, openCodeConfigPath: string, backup?: boolean, strategy?: McpStrategy, containmentRoot?: string): Promise<void>;
|
|
@@ -91,8 +91,11 @@ function transformToOpenCodeFormat(rulerMcp) {
|
|
|
91
91
|
mcp: openCodeServers,
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
-
async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true, strategy = 'merge') {
|
|
94
|
+
async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true, strategy = 'merge', containmentRoot) {
|
|
95
95
|
const rulerMcp = rulerMcpData || {};
|
|
96
|
+
if (containmentRoot) {
|
|
97
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(openCodeConfigPath, containmentRoot, 'Refusing to write generated file outside project');
|
|
98
|
+
}
|
|
96
99
|
// Read existing OpenCode config if it exists
|
|
97
100
|
let existingConfig = {};
|
|
98
101
|
let existingContent;
|
|
@@ -125,10 +128,14 @@ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup =
|
|
|
125
128
|
...transformedConfig.mcp,
|
|
126
129
|
},
|
|
127
130
|
};
|
|
131
|
+
const finalContent = JSON.stringify(finalConfig, null, 2) + '\n';
|
|
132
|
+
if (existingContent === finalContent) {
|
|
133
|
+
return;
|
|
134
|
+
}
|
|
128
135
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openCodeConfigPath));
|
|
129
136
|
if (backup) {
|
|
130
137
|
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
131
|
-
await backupFile(openCodeConfigPath);
|
|
138
|
+
await backupFile(openCodeConfigPath, containmentRoot);
|
|
132
139
|
}
|
|
133
|
-
await
|
|
140
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(openCodeConfigPath, finalContent, containmentRoot);
|
|
134
141
|
}
|
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
import { McpStrategy } from '../types';
|
|
2
|
-
export declare function propagateMcpToOpenHands(rulerMcpData: Record<string, unknown> | null, openHandsConfigPath: string, backup?: boolean, strategy?: McpStrategy): Promise<void>;
|
|
2
|
+
export declare function propagateMcpToOpenHands(rulerMcpData: Record<string, unknown> | null, openHandsConfigPath: string, backup?: boolean, strategy?: McpStrategy, containmentRoot?: string): Promise<void>;
|
|
@@ -67,10 +67,14 @@ function extractApiKey(headers) {
|
|
|
67
67
|
}
|
|
68
68
|
return null;
|
|
69
69
|
}
|
|
70
|
-
function createRemoteServerEntry(url, headers) {
|
|
70
|
+
function createRemoteServerEntry(name, url, headers) {
|
|
71
|
+
const hasHeaders = headers && Object.keys(headers).length > 0;
|
|
71
72
|
const apiKey = extractApiKey(headers);
|
|
72
|
-
if (
|
|
73
|
-
|
|
73
|
+
if (hasHeaders) {
|
|
74
|
+
if (apiKey) {
|
|
75
|
+
return { url, api_key: apiKey };
|
|
76
|
+
}
|
|
77
|
+
throw new Error(`OpenHands MCP remote server "${name}" has unsupported headers. OpenHands config.toml can only represent a Bearer Authorization header as api_key.`);
|
|
74
78
|
}
|
|
75
79
|
return url;
|
|
76
80
|
}
|
|
@@ -89,8 +93,11 @@ function normalizeRemoteServerArray(entries) {
|
|
|
89
93
|
// All entries are strings, keep as is
|
|
90
94
|
return entries;
|
|
91
95
|
}
|
|
92
|
-
async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true, strategy = 'merge') {
|
|
96
|
+
async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true, strategy = 'merge', containmentRoot) {
|
|
93
97
|
const rulerMcp = rulerMcpData || {};
|
|
98
|
+
if (containmentRoot) {
|
|
99
|
+
await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(openHandsConfigPath, containmentRoot, 'Refusing to write generated file outside project');
|
|
100
|
+
}
|
|
94
101
|
// Always use the legacy Ruler MCP config format as input (top-level "mcpServers" key)
|
|
95
102
|
const rulerServers = rulerMcp.mcpServers || {};
|
|
96
103
|
// Return early if no servers to process
|
|
@@ -162,7 +169,7 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
162
169
|
else if (serverDef.url) {
|
|
163
170
|
// Remote server
|
|
164
171
|
const classification = classifyRemoteServer(serverDef.url);
|
|
165
|
-
const entry = createRemoteServerEntry(serverDef.url, serverDef.headers);
|
|
172
|
+
const entry = createRemoteServerEntry(name, serverDef.url, serverDef.headers);
|
|
166
173
|
if (classification === 'sse') {
|
|
167
174
|
existingSseServers.set(serverDef.url, entry);
|
|
168
175
|
}
|
|
@@ -176,10 +183,14 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
176
183
|
config.mcp.stdio_servers = Array.from(existingStdioServers.values());
|
|
177
184
|
config.mcp.sse_servers = normalizeRemoteServerArray(Array.from(existingSseServers.values()));
|
|
178
185
|
config.mcp.shttp_servers = normalizeRemoteServerArray(Array.from(existingShttpServers.values()));
|
|
186
|
+
const finalContent = (0, toml_1.stringify)(config);
|
|
187
|
+
if (tomlContent === finalContent) {
|
|
188
|
+
return;
|
|
189
|
+
}
|
|
179
190
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
|
|
180
191
|
if (backup) {
|
|
181
192
|
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
182
|
-
await backupFile(openHandsConfigPath);
|
|
193
|
+
await backupFile(openHandsConfigPath, containmentRoot);
|
|
183
194
|
}
|
|
184
|
-
await
|
|
195
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(openHandsConfigPath, finalContent, containmentRoot);
|
|
185
196
|
}
|
package/dist/paths/mcp.d.ts
CHANGED
|
@@ -5,4 +5,4 @@ export declare function readNativeMcp(filePath: string): Promise<Record<string,
|
|
|
5
5
|
/** Read native Codex TOML MCP config from disk, or return empty object if missing. */
|
|
6
6
|
export declare function readNativeMcpToml(filePath: string, parseToml: (text: string) => Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
7
7
|
/** Write native MCP config to disk, creating parent directories as needed. */
|
|
8
|
-
export declare function writeNativeMcp(filePath: string, data: unknown): Promise<void>;
|
|
8
|
+
export declare function writeNativeMcp(filePath: string, data: unknown, containmentRoot?: string): Promise<void>;
|
package/dist/paths/mcp.js
CHANGED
|
@@ -39,6 +39,10 @@ exports.readNativeMcpToml = readNativeMcpToml;
|
|
|
39
39
|
exports.writeNativeMcp = writeNativeMcp;
|
|
40
40
|
const path = __importStar(require("path"));
|
|
41
41
|
const fs_1 = require("fs");
|
|
42
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
42
46
|
/** Determine the native MCP config path for a given agent. */
|
|
43
47
|
async function getNativeMcpPath(adapterName, projectRoot) {
|
|
44
48
|
const candidates = [];
|
|
@@ -125,7 +129,11 @@ async function readNativeMcp(filePath) {
|
|
|
125
129
|
throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
126
130
|
}
|
|
127
131
|
try {
|
|
128
|
-
|
|
132
|
+
const parsed = JSON.parse(text);
|
|
133
|
+
if (!isRecord(parsed)) {
|
|
134
|
+
throw new Error('must be a JSON object');
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
129
137
|
}
|
|
130
138
|
catch (error) {
|
|
131
139
|
throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
@@ -151,8 +159,7 @@ async function readNativeMcpToml(filePath, parseToml) {
|
|
|
151
159
|
}
|
|
152
160
|
}
|
|
153
161
|
/** Write native MCP config to disk, creating parent directories as needed. */
|
|
154
|
-
async function writeNativeMcp(filePath, data) {
|
|
155
|
-
await fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
162
|
+
async function writeNativeMcp(filePath, data, containmentRoot) {
|
|
156
163
|
const text = JSON.stringify(data, null, 2) + '\n';
|
|
157
|
-
await
|
|
164
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(filePath, text, containmentRoot);
|
|
158
165
|
}
|