@intellectronica/ruler 0.3.41 → 0.3.42
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 +40 -28
- package/dist/agents/AbstractAgent.d.ts +53 -0
- package/dist/agents/AgentsMdAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.js +3 -1
- package/dist/agents/AmazonQCliAgent.d.ts +13 -0
- package/dist/agents/AmpAgent.d.ts +6 -0
- package/dist/agents/AntigravityAgent.d.ts +10 -0
- package/dist/agents/AugmentCodeAgent.d.ts +13 -0
- package/dist/agents/ClaudeAgent.d.ts +13 -0
- package/dist/agents/ClineAgent.d.ts +9 -0
- package/dist/agents/CodexCliAgent.d.ts +31 -0
- package/dist/agents/CopilotAgent.d.ts +20 -0
- package/dist/agents/CrushAgent.d.ts +14 -0
- package/dist/agents/CrushAgent.js +5 -2
- package/dist/agents/CursorAgent.d.ts +17 -0
- package/dist/agents/FactoryDroidAgent.d.ts +13 -0
- package/dist/agents/FirebaseAgent.d.ts +11 -0
- package/dist/agents/FirebenderAgent.d.ts +36 -0
- package/dist/agents/GeminiCliAgent.d.ts +11 -0
- package/dist/agents/GeminiCliAgent.js +2 -2
- package/dist/agents/GooseAgent.d.ts +12 -0
- package/dist/agents/IAgent.d.ts +72 -0
- package/dist/agents/JetBrainsAiAssistantAgent.d.ts +10 -0
- package/dist/agents/JulesAgent.d.ts +5 -0
- package/dist/agents/JunieAgent.d.ts +12 -0
- package/dist/agents/KiloCodeAgent.d.ts +14 -0
- package/dist/agents/KiroAgent.d.ts +8 -0
- package/dist/agents/MistralVibeAgent.d.ts +31 -0
- package/dist/agents/OpenCodeAgent.d.ts +11 -0
- package/dist/agents/OpenCodeAgent.js +14 -9
- package/dist/agents/OpenHandsAgent.d.ts +8 -0
- package/dist/agents/PiAgent.d.ts +9 -0
- package/dist/agents/QwenCodeAgent.d.ts +10 -0
- package/dist/agents/QwenCodeAgent.js +2 -2
- package/dist/agents/RooCodeAgent.d.ts +16 -0
- package/dist/agents/TraeAgent.d.ts +10 -0
- package/dist/agents/WarpAgent.d.ts +12 -0
- package/dist/agents/WindsurfAgent.d.ts +13 -0
- package/dist/agents/ZedAgent.d.ts +21 -0
- package/dist/agents/ZedAgent.js +5 -2
- package/dist/agents/agent-utils.d.ts +5 -0
- package/dist/agents/agent-utils.js +8 -5
- package/dist/agents/index.d.ts +9 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.js +1 -2
- package/dist/cli/handlers.d.ts +41 -0
- package/dist/cli/handlers.js +75 -59
- package/dist/cli/index.d.ts +2 -0
- package/dist/constants.d.ts +35 -0
- package/dist/core/ConfigLoader.d.ts +57 -0
- package/dist/core/ConfigLoader.js +106 -39
- package/dist/core/FileSystemUtils.d.ts +51 -0
- package/dist/core/FileSystemUtils.js +37 -17
- package/dist/core/GitignoreUtils.d.ts +15 -0
- package/dist/core/GitignoreUtils.js +32 -1
- package/dist/core/RuleProcessor.d.ts +8 -0
- package/dist/core/SkillsProcessor.d.ts +127 -0
- package/dist/core/SkillsProcessor.js +104 -218
- package/dist/core/SkillsUtils.d.ts +26 -0
- package/dist/core/SubagentsProcessor.d.ts +38 -0
- package/dist/core/SubagentsUtils.d.ts +34 -0
- package/dist/core/UnifiedConfigLoader.d.ts +10 -0
- package/dist/core/UnifiedConfigLoader.js +61 -31
- package/dist/core/UnifiedConfigTypes.d.ts +95 -0
- package/dist/core/agent-selection.d.ts +12 -0
- package/dist/core/agent-selection.js +11 -3
- package/dist/core/apply-engine.d.ts +69 -0
- package/dist/core/apply-engine.js +57 -50
- package/dist/core/config-utils.d.ts +14 -0
- package/dist/core/config-utils.js +9 -3
- package/dist/core/hash.d.ts +2 -0
- package/dist/core/path-utils.d.ts +1 -0
- package/dist/core/path-utils.js +42 -0
- package/dist/core/revert-engine.d.ts +36 -0
- package/dist/core/revert-engine.js +70 -9
- package/dist/lib.d.ts +13 -0
- package/dist/lib.js +16 -3
- package/dist/mcp/capabilities.d.ts +20 -0
- package/dist/mcp/merge.d.ts +10 -0
- package/dist/mcp/merge.js +19 -1
- package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenCodeMcp.js +21 -9
- package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenHandsMcp.js +31 -15
- package/dist/mcp/validate.d.ts +7 -0
- package/dist/mcp/validate.js +6 -1
- package/dist/paths/mcp.d.ts +8 -0
- package/dist/paths/mcp.js +33 -4
- package/dist/revert.d.ts +6 -0
- package/dist/revert.js +39 -27
- package/dist/types.d.ts +87 -0
- package/dist/vscode/settings.d.ts +40 -0
- package/package.json +6 -4
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.isPathInsideOrEqual = isPathInsideOrEqual;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
function isPathInsideOrEqual(parentPath, targetPath) {
|
|
39
|
+
const relative = path.relative(path.resolve(parentPath), path.resolve(targetPath));
|
|
40
|
+
return (relative === '' ||
|
|
41
|
+
(!relative.startsWith('..') && !path.isAbsolute(relative)));
|
|
42
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { IAgent } from '../agents/IAgent';
|
|
2
|
+
import { IAgentConfig } from './ConfigLoader';
|
|
3
|
+
/**
|
|
4
|
+
* Result of reverting an agent configuration
|
|
5
|
+
*/
|
|
6
|
+
export interface RevertAgentResult {
|
|
7
|
+
restored: number;
|
|
8
|
+
removed: number;
|
|
9
|
+
backupsRemoved: number;
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Result of cleaning up auxiliary files
|
|
13
|
+
*/
|
|
14
|
+
export interface CleanUpResult {
|
|
15
|
+
additionalFilesRemoved: number;
|
|
16
|
+
directoriesRemoved: number;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Reverts configuration for a single agent.
|
|
20
|
+
* @param agent The agent to revert
|
|
21
|
+
* @param projectRoot Root directory of the project
|
|
22
|
+
* @param agentConfig Agent-specific configuration
|
|
23
|
+
* @param keepBackups Whether to keep backup files
|
|
24
|
+
* @param verbose Whether to enable verbose logging
|
|
25
|
+
* @param dryRun Whether to perform a dry run
|
|
26
|
+
* @returns Promise resolving to revert statistics
|
|
27
|
+
*/
|
|
28
|
+
export declare function revertAgentConfiguration(agent: IAgent, projectRoot: string, agentConfig: IAgentConfig | undefined, keepBackups: boolean, verbose: boolean, dryRun: boolean): Promise<RevertAgentResult>;
|
|
29
|
+
/**
|
|
30
|
+
* Cleans up auxiliary files and directories.
|
|
31
|
+
* @param projectRoot Root directory of the project
|
|
32
|
+
* @param verbose Whether to enable verbose logging
|
|
33
|
+
* @param dryRun Whether to perform a dry run
|
|
34
|
+
* @returns Promise resolving to cleanup statistics
|
|
35
|
+
*/
|
|
36
|
+
export declare function cleanUpAuxiliaryFiles(projectRoot: string, verbose: boolean, dryRun: boolean): Promise<CleanUpResult>;
|
|
@@ -40,7 +40,12 @@ const fs_1 = require("fs");
|
|
|
40
40
|
const agent_utils_1 = require("../agents/agent-utils");
|
|
41
41
|
const mcp_1 = require("../paths/mcp");
|
|
42
42
|
const constants_1 = require("../constants");
|
|
43
|
+
const GitignoreUtils_1 = require("./GitignoreUtils");
|
|
43
44
|
const settings_1 = require("../vscode/settings");
|
|
45
|
+
const path_utils_1 = require("./path-utils");
|
|
46
|
+
const RULER_START_MARKER = '# START Ruler Generated Files';
|
|
47
|
+
const RULER_END_MARKER = '# END Ruler Generated Files';
|
|
48
|
+
const RULER_GENERATED_MARKER = '<!-- Generated by Ruler -->';
|
|
44
49
|
/**
|
|
45
50
|
* Checks if a file exists.
|
|
46
51
|
*/
|
|
@@ -53,6 +58,54 @@ async function fileExists(filePath) {
|
|
|
53
58
|
return false;
|
|
54
59
|
}
|
|
55
60
|
}
|
|
61
|
+
async function ignoreFileHasRulerGeneratedPath(ignoreFilePath, generatedPath) {
|
|
62
|
+
try {
|
|
63
|
+
const content = await fs_1.promises.readFile(ignoreFilePath, 'utf8');
|
|
64
|
+
const lines = content.split('\n');
|
|
65
|
+
let inRulerBlock = false;
|
|
66
|
+
for (const line of lines) {
|
|
67
|
+
const trimmed = line.trim();
|
|
68
|
+
if (trimmed === RULER_START_MARKER) {
|
|
69
|
+
inRulerBlock = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (trimmed === RULER_END_MARKER) {
|
|
73
|
+
inRulerBlock = false;
|
|
74
|
+
continue;
|
|
75
|
+
}
|
|
76
|
+
if (inRulerBlock &&
|
|
77
|
+
(trimmed === generatedPath || trimmed === generatedPath.slice(1))) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
catch {
|
|
83
|
+
return false;
|
|
84
|
+
}
|
|
85
|
+
return false;
|
|
86
|
+
}
|
|
87
|
+
async function hasRulerGeneratedProvenance(filePath, projectRoot) {
|
|
88
|
+
try {
|
|
89
|
+
const content = await fs_1.promises.readFile(filePath, 'utf8');
|
|
90
|
+
if (content.startsWith(RULER_GENERATED_MARKER)) {
|
|
91
|
+
return true;
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
catch {
|
|
95
|
+
return false;
|
|
96
|
+
}
|
|
97
|
+
const relativePath = `/${path.relative(projectRoot, filePath).replace(/\\/g, '/')}`;
|
|
98
|
+
const ignoreFiles = [
|
|
99
|
+
await (0, GitignoreUtils_1.resolveIgnoreFilePath)(projectRoot, '.gitignore'),
|
|
100
|
+
await (0, GitignoreUtils_1.resolveIgnoreFilePath)(projectRoot, '.git/info/exclude'),
|
|
101
|
+
];
|
|
102
|
+
for (const ignoreFile of ignoreFiles) {
|
|
103
|
+
if (await ignoreFileHasRulerGeneratedPath(ignoreFile, relativePath)) {
|
|
104
|
+
return true;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
return false;
|
|
108
|
+
}
|
|
56
109
|
/**
|
|
57
110
|
* Restores a file from its backup if the backup exists.
|
|
58
111
|
*/
|
|
@@ -76,7 +129,7 @@ async function restoreFromBackup(filePath, verbose, dryRun) {
|
|
|
76
129
|
/**
|
|
77
130
|
* Removes a file if it exists and has no backup (meaning it was generated by ruler).
|
|
78
131
|
*/
|
|
79
|
-
async function removeGeneratedFile(filePath, verbose, dryRun) {
|
|
132
|
+
async function removeGeneratedFile(filePath, verbose, dryRun, projectRoot) {
|
|
80
133
|
const fileExistsFlag = await fileExists(filePath);
|
|
81
134
|
const backupExists = await fileExists(`${filePath}.bak`);
|
|
82
135
|
if (!fileExistsFlag) {
|
|
@@ -87,6 +140,11 @@ async function removeGeneratedFile(filePath, verbose, dryRun) {
|
|
|
87
140
|
(0, constants_1.logVerbose)(`File has backup, skipping removal: ${filePath}`, verbose);
|
|
88
141
|
return false;
|
|
89
142
|
}
|
|
143
|
+
if (projectRoot &&
|
|
144
|
+
!(await hasRulerGeneratedProvenance(filePath, projectRoot))) {
|
|
145
|
+
(0, constants_1.logVerbose)(`Preserving file without backup or Ruler provenance: ${filePath}`, verbose);
|
|
146
|
+
return false;
|
|
147
|
+
}
|
|
90
148
|
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
91
149
|
if (dryRun) {
|
|
92
150
|
(0, constants_1.logVerbose)(`${prefix} Would remove generated file: ${filePath}`, verbose);
|
|
@@ -267,6 +325,9 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
267
325
|
filesRemoved++;
|
|
268
326
|
}
|
|
269
327
|
}
|
|
328
|
+
else if (!(await hasRulerGeneratedProvenance(fullPath, projectRoot))) {
|
|
329
|
+
(0, constants_1.logVerbose)(`Preserving additional file without backup or Ruler provenance: ${fullPath}`, verbose);
|
|
330
|
+
}
|
|
270
331
|
else {
|
|
271
332
|
if (dryRun) {
|
|
272
333
|
(0, constants_1.logVerbose)(`${prefix} Would remove additional file: ${fullPath}`, verbose);
|
|
@@ -288,7 +349,7 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
288
349
|
const restored = await restoreFromBackup(settingsPath, verbose, dryRun);
|
|
289
350
|
if (restored) {
|
|
290
351
|
filesRemoved++;
|
|
291
|
-
(0, constants_1.logVerbose)(`${
|
|
352
|
+
(0, constants_1.logVerbose)(`${prefix} Restored VSCode settings from backup`, verbose);
|
|
292
353
|
}
|
|
293
354
|
}
|
|
294
355
|
else if (await fileExists(settingsPath)) {
|
|
@@ -299,10 +360,10 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
299
360
|
delete settings['augment.advanced'];
|
|
300
361
|
const remainingKeys = Object.keys(settings);
|
|
301
362
|
if (remainingKeys.length === 0) {
|
|
302
|
-
(0, constants_1.logVerbose)(`${
|
|
363
|
+
(0, constants_1.logVerbose)(`${prefix} Would remove empty VSCode settings file`, verbose);
|
|
303
364
|
}
|
|
304
365
|
else {
|
|
305
|
-
(0, constants_1.logVerbose)(`${
|
|
366
|
+
(0, constants_1.logVerbose)(`${prefix} Would remove augment.advanced section from ${settingsPath}`, verbose);
|
|
306
367
|
}
|
|
307
368
|
filesRemoved++;
|
|
308
369
|
}
|
|
@@ -314,11 +375,11 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
314
375
|
const remainingKeys = Object.keys(settings);
|
|
315
376
|
if (remainingKeys.length === 0) {
|
|
316
377
|
await fs_1.promises.unlink(settingsPath);
|
|
317
|
-
(0, constants_1.logVerbose)(`${
|
|
378
|
+
(0, constants_1.logVerbose)(`${prefix} Removed empty VSCode settings file`, verbose);
|
|
318
379
|
}
|
|
319
380
|
else {
|
|
320
381
|
await (0, settings_1.writeVSCodeSettings)(settingsPath, settings);
|
|
321
|
-
(0, constants_1.logVerbose)(`${
|
|
382
|
+
(0, constants_1.logVerbose)(`${prefix} Removed augment.advanced section from VSCode settings`, verbose);
|
|
322
383
|
}
|
|
323
384
|
filesRemoved++;
|
|
324
385
|
}
|
|
@@ -363,7 +424,7 @@ async function revertAgentConfiguration(agent, projectRoot, agentConfig, keepBac
|
|
|
363
424
|
}
|
|
364
425
|
}
|
|
365
426
|
else {
|
|
366
|
-
const removed = await removeGeneratedFile(outputPath, verbose, dryRun);
|
|
427
|
+
const removed = await removeGeneratedFile(outputPath, verbose, dryRun, projectRoot);
|
|
367
428
|
if (removed) {
|
|
368
429
|
result.removed++;
|
|
369
430
|
}
|
|
@@ -371,7 +432,7 @@ async function revertAgentConfiguration(agent, projectRoot, agentConfig, keepBac
|
|
|
371
432
|
}
|
|
372
433
|
// Handle MCP files
|
|
373
434
|
const mcpPath = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
374
|
-
if (mcpPath &&
|
|
435
|
+
if (mcpPath && (0, path_utils_1.isPathInsideOrEqual)(projectRoot, mcpPath)) {
|
|
375
436
|
if (agent.getName() === 'AugmentCode' &&
|
|
376
437
|
mcpPath.endsWith('.vscode/settings.json')) {
|
|
377
438
|
(0, constants_1.logVerbose)(`Skipping MCP handling for AugmentCode settings.json - handled separately`, verbose);
|
|
@@ -388,7 +449,7 @@ async function revertAgentConfiguration(agent, projectRoot, agentConfig, keepBac
|
|
|
388
449
|
}
|
|
389
450
|
}
|
|
390
451
|
else {
|
|
391
|
-
const mcpRemoved = await removeGeneratedFile(mcpPath, verbose, dryRun);
|
|
452
|
+
const mcpRemoved = await removeGeneratedFile(mcpPath, verbose, dryRun, projectRoot);
|
|
392
453
|
if (mcpRemoved) {
|
|
393
454
|
result.removed++;
|
|
394
455
|
}
|
package/dist/lib.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { allAgents } from './agents';
|
|
2
|
+
import { McpStrategy } from './types';
|
|
3
|
+
export { allAgents };
|
|
4
|
+
/**
|
|
5
|
+
* Applies ruler configurations for all supported AI agents.
|
|
6
|
+
* @param projectRoot Root directory of the project
|
|
7
|
+
*/
|
|
8
|
+
/**
|
|
9
|
+
* Applies ruler configurations for selected AI agents.
|
|
10
|
+
* @param projectRoot Root directory of the project
|
|
11
|
+
* @param includedAgents Optional list of agent name filters (case-insensitive substrings)
|
|
12
|
+
*/
|
|
13
|
+
export declare function applyAllAgentConfigs(projectRoot: string, includedAgents?: string[], configPath?: string, cliMcpEnabled?: boolean, cliMcpStrategy?: McpStrategy, cliGitignoreEnabled?: boolean, verbose?: boolean, dryRun?: boolean, localOnly?: boolean, nested?: boolean, backup?: boolean, skillsEnabled?: boolean, cliGitignoreLocal?: boolean, subagentsEnabled?: boolean): Promise<void>;
|
package/dist/lib.js
CHANGED
|
@@ -53,6 +53,17 @@ function resolveSkillsEnabled(cliFlag, configSetting) {
|
|
|
53
53
|
? configSetting
|
|
54
54
|
: true; // default to enabled
|
|
55
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Resolves backup enabled state based on precedence:
|
|
58
|
+
* CLI flag > ruler.toml > default (enabled).
|
|
59
|
+
*/
|
|
60
|
+
function resolveBackupEnabled(cliFlag, configSetting) {
|
|
61
|
+
return cliFlag !== undefined
|
|
62
|
+
? cliFlag
|
|
63
|
+
: configSetting !== undefined
|
|
64
|
+
? configSetting
|
|
65
|
+
: true; // default to enabled
|
|
66
|
+
}
|
|
56
67
|
/**
|
|
57
68
|
* Resolves subagents enabled state based on precedence:
|
|
58
69
|
* CLI flag > ruler.toml > default (disabled).
|
|
@@ -82,7 +93,7 @@ function resolveSubagentsCleanupOrphaned(configSetting) {
|
|
|
82
93
|
* @param projectRoot Root directory of the project
|
|
83
94
|
* @param includedAgents Optional list of agent name filters (case-insensitive substrings)
|
|
84
95
|
*/
|
|
85
|
-
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup
|
|
96
|
+
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup, skillsEnabled, cliGitignoreLocal, subagentsEnabled) {
|
|
86
97
|
// Load configuration and rules
|
|
87
98
|
(0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
|
|
88
99
|
if (configPath) {
|
|
@@ -123,6 +134,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
123
134
|
// Propagate subagents (mirrors skills handling for nested mode).
|
|
124
135
|
const subagentsEnabledResolved = resolveSubagentsEnabled(subagentsEnabled, rootConfig.subagents?.enabled);
|
|
125
136
|
const subagentsCleanupOrphaned = resolveSubagentsCleanupOrphaned(rootConfig.subagents?.cleanup_orphaned);
|
|
137
|
+
const backupEnabledResolved = resolveBackupEnabled(backup, rootConfig.backup?.enabled);
|
|
126
138
|
{
|
|
127
139
|
const { propagateSubagents } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
|
|
128
140
|
for (const configEntry of hierarchicalConfigs) {
|
|
@@ -131,7 +143,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
131
143
|
await propagateSubagents(nestedRoot, selectedAgents, subagentsEnabledResolved, subagentsCleanupOrphaned, verbose, dryRun);
|
|
132
144
|
}
|
|
133
145
|
}
|
|
134
|
-
generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy,
|
|
146
|
+
generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backupEnabledResolved);
|
|
135
147
|
}
|
|
136
148
|
else {
|
|
137
149
|
const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
|
|
@@ -151,11 +163,12 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
151
163
|
// Propagate subagents (mirrors skills handling).
|
|
152
164
|
const subagentsEnabledResolvedSingle = resolveSubagentsEnabled(subagentsEnabled, singleConfig.config.subagents?.enabled);
|
|
153
165
|
const subagentsCleanupOrphanedSingle = resolveSubagentsCleanupOrphaned(singleConfig.config.subagents?.cleanup_orphaned);
|
|
166
|
+
const backupEnabledResolvedSingle = resolveBackupEnabled(backup, singleConfig.config.backup?.enabled);
|
|
154
167
|
{
|
|
155
168
|
const { propagateSubagents } = await Promise.resolve().then(() => __importStar(require('./core/SubagentsProcessor')));
|
|
156
169
|
await propagateSubagents(projectRoot, selectedAgents, subagentsEnabledResolvedSingle, subagentsCleanupOrphanedSingle, verbose, dryRun);
|
|
157
170
|
}
|
|
158
|
-
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy,
|
|
171
|
+
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backupEnabledResolvedSingle);
|
|
159
172
|
}
|
|
160
173
|
// Add skills-generated paths to gitignore if skills are enabled
|
|
161
174
|
let allGeneratedPaths = generatedPaths;
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import { IAgent } from '../agents/IAgent';
|
|
2
|
+
/**
|
|
3
|
+
* MCP capability types for agents
|
|
4
|
+
*/
|
|
5
|
+
export interface McpCapabilities {
|
|
6
|
+
supportsStdio: boolean;
|
|
7
|
+
supportsRemote: boolean;
|
|
8
|
+
}
|
|
9
|
+
/**
|
|
10
|
+
* Derives MCP capabilities for an agent
|
|
11
|
+
*/
|
|
12
|
+
export declare function getAgentMcpCapabilities(agent: IAgent): McpCapabilities;
|
|
13
|
+
/**
|
|
14
|
+
* Checks if an agent supports any MCP functionality
|
|
15
|
+
*/
|
|
16
|
+
export declare function agentSupportsMcp(agent: IAgent): boolean;
|
|
17
|
+
/**
|
|
18
|
+
* Filters MCP configuration based on agent capabilities
|
|
19
|
+
*/
|
|
20
|
+
export declare function filterMcpConfigForAgent(mcpConfig: Record<string, unknown>, agent: IAgent): Record<string, unknown> | null;
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { McpStrategy } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* Merge native and incoming MCP server configurations according to strategy.
|
|
4
|
+
* @param base Existing native MCP config object.
|
|
5
|
+
* @param incoming Ruler MCP config object.
|
|
6
|
+
* @param strategy Merge strategy: 'merge' to union servers, 'overwrite' to replace.
|
|
7
|
+
* @param serverKey The key to use for servers in the output (e.g., 'servers' for Copilot, 'mcpServers' for others).
|
|
8
|
+
* @returns Merged MCP config object.
|
|
9
|
+
*/
|
|
10
|
+
export declare function mergeMcp(base: Record<string, unknown>, incoming: Record<string, unknown>, strategy: McpStrategy, serverKey: string): Record<string, unknown>;
|
package/dist/mcp/merge.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.mergeMcp = mergeMcp;
|
|
4
|
+
const MCP_SERVER_KEYS = [
|
|
5
|
+
'mcp',
|
|
6
|
+
'mcpServers',
|
|
7
|
+
'servers',
|
|
8
|
+
'mcp_servers',
|
|
9
|
+
'context_servers',
|
|
10
|
+
];
|
|
4
11
|
/**
|
|
5
12
|
* Merge native and incoming MCP server configurations according to strategy.
|
|
6
13
|
* @param base Existing native MCP config object.
|
|
@@ -17,7 +24,14 @@ function mergeMcp(base, incoming, strategy, serverKey) {
|
|
|
17
24
|
incoming.mcpServers ||
|
|
18
25
|
incoming.mcp ||
|
|
19
26
|
{};
|
|
27
|
+
const preservedBase = { ...base };
|
|
28
|
+
for (const key of MCP_SERVER_KEYS) {
|
|
29
|
+
if (key !== serverKey) {
|
|
30
|
+
delete preservedBase[key];
|
|
31
|
+
}
|
|
32
|
+
}
|
|
20
33
|
return {
|
|
34
|
+
...preservedBase,
|
|
21
35
|
[serverKey]: incomingServers,
|
|
22
36
|
};
|
|
23
37
|
}
|
|
@@ -31,7 +45,11 @@ function mergeMcp(base, incoming, strategy, serverKey) {
|
|
|
31
45
|
{};
|
|
32
46
|
const mergedServers = { ...baseServers, ...incomingServers };
|
|
33
47
|
const newBase = { ...base };
|
|
34
|
-
|
|
48
|
+
for (const key of MCP_SERVER_KEYS) {
|
|
49
|
+
if (key !== serverKey) {
|
|
50
|
+
delete newBase[key];
|
|
51
|
+
}
|
|
52
|
+
}
|
|
35
53
|
return {
|
|
36
54
|
...newBase,
|
|
37
55
|
[serverKey]: mergedServers,
|
|
@@ -91,16 +91,26 @@ function transformToOpenCodeFormat(rulerMcp) {
|
|
|
91
91
|
mcp: openCodeServers,
|
|
92
92
|
};
|
|
93
93
|
}
|
|
94
|
-
async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true) {
|
|
94
|
+
async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup = true, strategy = 'merge') {
|
|
95
95
|
const rulerMcp = rulerMcpData || {};
|
|
96
96
|
// Read existing OpenCode config if it exists
|
|
97
97
|
let existingConfig = {};
|
|
98
|
+
let existingContent;
|
|
98
99
|
try {
|
|
99
|
-
|
|
100
|
-
existingConfig = JSON.parse(existingContent);
|
|
100
|
+
existingContent = await fs.readFile(openCodeConfigPath, 'utf8');
|
|
101
101
|
}
|
|
102
|
-
catch {
|
|
103
|
-
|
|
102
|
+
catch (error) {
|
|
103
|
+
if (error.code !== 'ENOENT') {
|
|
104
|
+
throw new Error(`Could not read OpenCode config at ${openCodeConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
if (existingContent !== undefined) {
|
|
108
|
+
try {
|
|
109
|
+
existingConfig = JSON.parse(existingContent);
|
|
110
|
+
}
|
|
111
|
+
catch (error) {
|
|
112
|
+
throw new Error(`Invalid OpenCode config at ${openCodeConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
113
|
+
}
|
|
104
114
|
}
|
|
105
115
|
// Transform ruler MCP to OpenCode format
|
|
106
116
|
const transformedConfig = transformToOpenCodeFormat(rulerMcp);
|
|
@@ -108,10 +118,12 @@ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath, backup =
|
|
|
108
118
|
const finalConfig = {
|
|
109
119
|
...existingConfig,
|
|
110
120
|
$schema: transformedConfig.$schema,
|
|
111
|
-
mcp:
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
121
|
+
mcp: strategy === 'overwrite'
|
|
122
|
+
? transformedConfig.mcp
|
|
123
|
+
: {
|
|
124
|
+
...existingConfig.mcp,
|
|
125
|
+
...transformedConfig.mcp,
|
|
126
|
+
},
|
|
115
127
|
};
|
|
116
128
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openCodeConfigPath));
|
|
117
129
|
if (backup) {
|
|
@@ -89,7 +89,7 @@ function normalizeRemoteServerArray(entries) {
|
|
|
89
89
|
// All entries are strings, keep as is
|
|
90
90
|
return entries;
|
|
91
91
|
}
|
|
92
|
-
async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true) {
|
|
92
|
+
async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup = true, strategy = 'merge') {
|
|
93
93
|
const rulerMcp = rulerMcpData || {};
|
|
94
94
|
// Always use the legacy Ruler MCP config format as input (top-level "mcpServers" key)
|
|
95
95
|
const rulerServers = rulerMcp.mcpServers || {};
|
|
@@ -100,12 +100,22 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
100
100
|
return;
|
|
101
101
|
}
|
|
102
102
|
let config = {};
|
|
103
|
+
let tomlContent;
|
|
103
104
|
try {
|
|
104
|
-
|
|
105
|
-
config = (0, toml_1.parse)(tomlContent);
|
|
105
|
+
tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
|
|
106
106
|
}
|
|
107
|
-
catch {
|
|
108
|
-
|
|
107
|
+
catch (error) {
|
|
108
|
+
if (error.code !== 'ENOENT') {
|
|
109
|
+
throw new Error(`Could not read OpenHands config at ${openHandsConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
if (tomlContent !== undefined) {
|
|
113
|
+
try {
|
|
114
|
+
config = (0, toml_1.parse)(tomlContent);
|
|
115
|
+
}
|
|
116
|
+
catch (error) {
|
|
117
|
+
throw new Error(`Invalid OpenHands config at ${openHandsConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
118
|
+
}
|
|
109
119
|
}
|
|
110
120
|
if (!config.mcp) {
|
|
111
121
|
config.mcp = {};
|
|
@@ -119,18 +129,24 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
119
129
|
if (!config.mcp.shttp_servers) {
|
|
120
130
|
config.mcp.shttp_servers = [];
|
|
121
131
|
}
|
|
122
|
-
// Build maps for merging existing servers
|
|
123
|
-
const existingStdioServers = new Map(
|
|
132
|
+
// Build maps for merging existing servers, or start fresh when overwriting.
|
|
133
|
+
const existingStdioServers = new Map(strategy === 'overwrite'
|
|
134
|
+
? []
|
|
135
|
+
: config.mcp.stdio_servers.map((s) => [s.name, s]));
|
|
124
136
|
const existingSseServers = new Map();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
137
|
+
if (strategy !== 'overwrite') {
|
|
138
|
+
config.mcp.sse_servers.forEach((entry) => {
|
|
139
|
+
const url = typeof entry === 'string' ? entry : entry.url;
|
|
140
|
+
existingSseServers.set(url, entry);
|
|
141
|
+
});
|
|
142
|
+
}
|
|
129
143
|
const existingShttpServers = new Map();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
144
|
+
if (strategy !== 'overwrite') {
|
|
145
|
+
config.mcp.shttp_servers.forEach((entry) => {
|
|
146
|
+
const url = typeof entry === 'string' ? entry : entry.url;
|
|
147
|
+
existingShttpServers.set(url, entry);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
134
150
|
for (const [name, serverDef] of Object.entries(rulerServers)) {
|
|
135
151
|
if (isRulerMcpServer(serverDef)) {
|
|
136
152
|
if (serverDef.command) {
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Validate the structure of the Ruler MCP JSON config.
|
|
3
|
+
* Minimal validation: ensure 'mcpServers' property exists and is an object.
|
|
4
|
+
* @param data Parsed JSON object from .ruler/mcp.json.
|
|
5
|
+
* @throws Error if validation fails.
|
|
6
|
+
*/
|
|
7
|
+
export declare function validateMcp(data: unknown): void;
|
package/dist/mcp/validate.js
CHANGED
|
@@ -8,10 +8,15 @@ exports.validateMcp = validateMcp;
|
|
|
8
8
|
* @throws Error if validation fails.
|
|
9
9
|
*/
|
|
10
10
|
function validateMcp(data) {
|
|
11
|
+
const mcpServers = data && typeof data === 'object'
|
|
12
|
+
? data.mcpServers
|
|
13
|
+
: undefined;
|
|
11
14
|
if (!data ||
|
|
12
15
|
typeof data !== 'object' ||
|
|
13
16
|
!('mcpServers' in data) ||
|
|
14
|
-
|
|
17
|
+
!mcpServers ||
|
|
18
|
+
typeof mcpServers !== 'object' ||
|
|
19
|
+
Array.isArray(mcpServers)) {
|
|
15
20
|
throw new Error('[ruler] Invalid MCP config: must contain an object property "mcpServers" (Ruler style)');
|
|
16
21
|
}
|
|
17
22
|
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
/** Determine the native MCP config path for a given agent. */
|
|
2
|
+
export declare function getNativeMcpPath(adapterName: string, projectRoot: string): Promise<string | null>;
|
|
3
|
+
/** Read native MCP config from disk, or return empty object if missing. */
|
|
4
|
+
export declare function readNativeMcp(filePath: string): Promise<Record<string, unknown>>;
|
|
5
|
+
/** Read native Codex TOML MCP config from disk, or return empty object if missing. */
|
|
6
|
+
export declare function readNativeMcpToml(filePath: string, parseToml: (text: string) => Record<string, unknown>): Promise<Record<string, unknown>>;
|
|
7
|
+
/** Write native MCP config to disk, creating parent directories as needed. */
|
|
8
|
+
export declare function writeNativeMcp(filePath: string, data: unknown): Promise<void>;
|
package/dist/paths/mcp.js
CHANGED
|
@@ -35,6 +35,7 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.getNativeMcpPath = getNativeMcpPath;
|
|
37
37
|
exports.readNativeMcp = readNativeMcp;
|
|
38
|
+
exports.readNativeMcpToml = readNativeMcpToml;
|
|
38
39
|
exports.writeNativeMcp = writeNativeMcp;
|
|
39
40
|
const path = __importStar(require("path"));
|
|
40
41
|
const fs_1 = require("fs");
|
|
@@ -111,14 +112,42 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
111
112
|
// default to first candidate if none exist
|
|
112
113
|
return candidates.length > 0 ? candidates[0] : null;
|
|
113
114
|
}
|
|
114
|
-
/** Read native MCP config from disk, or return empty object if missing
|
|
115
|
+
/** Read native MCP config from disk, or return empty object if missing. */
|
|
115
116
|
async function readNativeMcp(filePath) {
|
|
117
|
+
let text;
|
|
118
|
+
try {
|
|
119
|
+
text = await fs_1.promises.readFile(filePath, 'utf8');
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
if (error.code === 'ENOENT') {
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
126
|
+
}
|
|
116
127
|
try {
|
|
117
|
-
const text = await fs_1.promises.readFile(filePath, 'utf8');
|
|
118
128
|
return JSON.parse(text);
|
|
119
129
|
}
|
|
120
|
-
catch {
|
|
121
|
-
|
|
130
|
+
catch (error) {
|
|
131
|
+
throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
/** Read native Codex TOML MCP config from disk, or return empty object if missing. */
|
|
135
|
+
async function readNativeMcpToml(filePath, parseToml) {
|
|
136
|
+
let text;
|
|
137
|
+
try {
|
|
138
|
+
text = await fs_1.promises.readFile(filePath, 'utf8');
|
|
139
|
+
}
|
|
140
|
+
catch (error) {
|
|
141
|
+
if (error.code === 'ENOENT') {
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
145
|
+
}
|
|
146
|
+
try {
|
|
147
|
+
return parseToml(text);
|
|
148
|
+
}
|
|
149
|
+
catch (error) {
|
|
150
|
+
throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
151
|
}
|
|
123
152
|
}
|
|
124
153
|
/** Write native MCP config to disk, creating parent directories as needed. */
|
package/dist/revert.d.ts
ADDED
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
import { allAgents } from './agents';
|
|
2
|
+
export { allAgents };
|
|
3
|
+
/**
|
|
4
|
+
* Reverts ruler configurations for selected AI agents.
|
|
5
|
+
*/
|
|
6
|
+
export declare function revertAllAgentConfigs(projectRoot: string, includedAgents?: string[], configPath?: string, keepBackups?: boolean, verbose?: boolean, dryRun?: boolean, localOnly?: boolean): Promise<void>;
|