@intellectronica/ruler 0.3.41 → 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 +135 -36
- package/dist/agents/AbstractAgent.d.ts +53 -0
- package/dist/agents/AbstractAgent.js +3 -2
- package/dist/agents/AgentsMdAgent.d.ts +14 -0
- package/dist/agents/AgentsMdAgent.js +3 -2
- package/dist/agents/AiderAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.js +7 -4
- package/dist/agents/AmazonQCliAgent.d.ts +13 -0
- package/dist/agents/AmazonQCliAgent.js +6 -4
- 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/AugmentCodeAgent.js +3 -2
- 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/CodexCliAgent.js +1 -1
- package/dist/agents/CopilotAgent.d.ts +20 -0
- package/dist/agents/CrushAgent.d.ts +14 -0
- package/dist/agents/CrushAgent.js +18 -6
- 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/FirebenderAgent.js +5 -4
- package/dist/agents/GeminiCliAgent.d.ts +12 -0
- package/dist/agents/GeminiCliAgent.js +13 -7
- package/dist/agents/GooseAgent.d.ts +12 -0
- package/dist/agents/IAgent.d.ts +74 -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/MistralVibeAgent.js +14 -3
- package/dist/agents/OpenCodeAgent.d.ts +11 -0
- package/dist/agents/OpenCodeAgent.js +24 -12
- package/dist/agents/OpenHandsAgent.d.ts +8 -0
- package/dist/agents/PiAgent.d.ts +9 -0
- package/dist/agents/QwenCodeAgent.d.ts +11 -0
- package/dist/agents/QwenCodeAgent.js +11 -5
- package/dist/agents/RooCodeAgent.d.ts +16 -0
- package/dist/agents/RooCodeAgent.js +3 -2
- 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 +8 -5
- 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/constants.js +1 -1
- package/dist/core/ConfigLoader.d.ts +59 -0
- package/dist/core/ConfigLoader.js +178 -44
- package/dist/core/FileSystemUtils.d.ts +53 -0
- package/dist/core/FileSystemUtils.js +157 -20
- package/dist/core/GitignoreUtils.d.ts +25 -0
- package/dist/core/GitignoreUtils.js +94 -32
- package/dist/core/RuleProcessor.d.ts +8 -0
- package/dist/core/SkillsProcessor.d.ts +127 -0
- package/dist/core/SkillsProcessor.js +118 -223
- package/dist/core/SkillsUtils.d.ts +26 -0
- package/dist/core/SubagentsProcessor.d.ts +38 -0
- package/dist/core/SubagentsProcessor.js +8 -5
- package/dist/core/SubagentsUtils.d.ts +34 -0
- package/dist/core/UnifiedConfigLoader.d.ts +10 -0
- package/dist/core/UnifiedConfigLoader.js +115 -33
- package/dist/core/UnifiedConfigTypes.d.ts +97 -0
- package/dist/core/agent-selection.d.ts +12 -0
- package/dist/core/agent-selection.js +17 -7
- package/dist/core/apply-engine.d.ts +70 -0
- package/dist/core/apply-engine.js +88 -58
- 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 +37 -0
- package/dist/core/revert-engine.js +142 -34
- package/dist/lib.d.ts +13 -0
- package/dist/lib.js +24 -8
- package/dist/mcp/capabilities.d.ts +20 -0
- package/dist/mcp/merge.d.ts +10 -0
- package/dist/mcp/merge.js +36 -16
- package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenCodeMcp.js +30 -11
- package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenHandsMcp.js +48 -21
- 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 +44 -8
- package/dist/revert.d.ts +6 -0
- package/dist/revert.js +58 -46
- package/dist/types.d.ts +87 -0
- package/dist/vscode/settings.d.ts +40 -0
- package/dist/vscode/settings.js +3 -3
- package/package.json +8 -5
|
@@ -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) {
|
|
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
|
|
@@ -100,12 +107,22 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
100
107
|
return;
|
|
101
108
|
}
|
|
102
109
|
let config = {};
|
|
110
|
+
let tomlContent;
|
|
103
111
|
try {
|
|
104
|
-
|
|
105
|
-
|
|
112
|
+
tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
if (error.code !== 'ENOENT') {
|
|
116
|
+
throw new Error(`Could not read OpenHands config at ${openHandsConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
117
|
+
}
|
|
106
118
|
}
|
|
107
|
-
|
|
108
|
-
|
|
119
|
+
if (tomlContent !== undefined) {
|
|
120
|
+
try {
|
|
121
|
+
config = (0, toml_1.parse)(tomlContent);
|
|
122
|
+
}
|
|
123
|
+
catch (error) {
|
|
124
|
+
throw new Error(`Invalid OpenHands config at ${openHandsConfigPath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
125
|
+
}
|
|
109
126
|
}
|
|
110
127
|
if (!config.mcp) {
|
|
111
128
|
config.mcp = {};
|
|
@@ -119,18 +136,24 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
119
136
|
if (!config.mcp.shttp_servers) {
|
|
120
137
|
config.mcp.shttp_servers = [];
|
|
121
138
|
}
|
|
122
|
-
// Build maps for merging existing servers
|
|
123
|
-
const existingStdioServers = new Map(
|
|
139
|
+
// Build maps for merging existing servers, or start fresh when overwriting.
|
|
140
|
+
const existingStdioServers = new Map(strategy === 'overwrite'
|
|
141
|
+
? []
|
|
142
|
+
: config.mcp.stdio_servers.map((s) => [s.name, s]));
|
|
124
143
|
const existingSseServers = new Map();
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
144
|
+
if (strategy !== 'overwrite') {
|
|
145
|
+
config.mcp.sse_servers.forEach((entry) => {
|
|
146
|
+
const url = typeof entry === 'string' ? entry : entry.url;
|
|
147
|
+
existingSseServers.set(url, entry);
|
|
148
|
+
});
|
|
149
|
+
}
|
|
129
150
|
const existingShttpServers = new Map();
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
151
|
+
if (strategy !== 'overwrite') {
|
|
152
|
+
config.mcp.shttp_servers.forEach((entry) => {
|
|
153
|
+
const url = typeof entry === 'string' ? entry : entry.url;
|
|
154
|
+
existingShttpServers.set(url, entry);
|
|
155
|
+
});
|
|
156
|
+
}
|
|
134
157
|
for (const [name, serverDef] of Object.entries(rulerServers)) {
|
|
135
158
|
if (isRulerMcpServer(serverDef)) {
|
|
136
159
|
if (serverDef.command) {
|
|
@@ -146,7 +169,7 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
146
169
|
else if (serverDef.url) {
|
|
147
170
|
// Remote server
|
|
148
171
|
const classification = classifyRemoteServer(serverDef.url);
|
|
149
|
-
const entry = createRemoteServerEntry(serverDef.url, serverDef.headers);
|
|
172
|
+
const entry = createRemoteServerEntry(name, serverDef.url, serverDef.headers);
|
|
150
173
|
if (classification === 'sse') {
|
|
151
174
|
existingSseServers.set(serverDef.url, entry);
|
|
152
175
|
}
|
|
@@ -160,10 +183,14 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
160
183
|
config.mcp.stdio_servers = Array.from(existingStdioServers.values());
|
|
161
184
|
config.mcp.sse_servers = normalizeRemoteServerArray(Array.from(existingSseServers.values()));
|
|
162
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
|
+
}
|
|
163
190
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
|
|
164
191
|
if (backup) {
|
|
165
192
|
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
166
|
-
await backupFile(openHandsConfigPath);
|
|
193
|
+
await backupFile(openHandsConfigPath, containmentRoot);
|
|
167
194
|
}
|
|
168
|
-
await
|
|
195
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(openHandsConfigPath, finalContent, containmentRoot);
|
|
169
196
|
}
|
|
@@ -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, containmentRoot?: string): Promise<void>;
|
package/dist/paths/mcp.js
CHANGED
|
@@ -35,9 +35,14 @@ 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");
|
|
42
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
43
|
+
function isRecord(value) {
|
|
44
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
45
|
+
}
|
|
41
46
|
/** Determine the native MCP config path for a given agent. */
|
|
42
47
|
async function getNativeMcpPath(adapterName, projectRoot) {
|
|
43
48
|
const candidates = [];
|
|
@@ -111,19 +116,50 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
111
116
|
// default to first candidate if none exist
|
|
112
117
|
return candidates.length > 0 ? candidates[0] : null;
|
|
113
118
|
}
|
|
114
|
-
/** Read native MCP config from disk, or return empty object if missing
|
|
119
|
+
/** Read native MCP config from disk, or return empty object if missing. */
|
|
115
120
|
async function readNativeMcp(filePath) {
|
|
121
|
+
let text;
|
|
122
|
+
try {
|
|
123
|
+
text = await fs_1.promises.readFile(filePath, 'utf8');
|
|
124
|
+
}
|
|
125
|
+
catch (error) {
|
|
126
|
+
if (error.code === 'ENOENT') {
|
|
127
|
+
return {};
|
|
128
|
+
}
|
|
129
|
+
throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
130
|
+
}
|
|
131
|
+
try {
|
|
132
|
+
const parsed = JSON.parse(text);
|
|
133
|
+
if (!isRecord(parsed)) {
|
|
134
|
+
throw new Error('must be a JSON object');
|
|
135
|
+
}
|
|
136
|
+
return parsed;
|
|
137
|
+
}
|
|
138
|
+
catch (error) {
|
|
139
|
+
throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/** Read native Codex TOML MCP config from disk, or return empty object if missing. */
|
|
143
|
+
async function readNativeMcpToml(filePath, parseToml) {
|
|
144
|
+
let text;
|
|
145
|
+
try {
|
|
146
|
+
text = await fs_1.promises.readFile(filePath, 'utf8');
|
|
147
|
+
}
|
|
148
|
+
catch (error) {
|
|
149
|
+
if (error.code === 'ENOENT') {
|
|
150
|
+
return {};
|
|
151
|
+
}
|
|
152
|
+
throw new Error(`Could not read MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
153
|
+
}
|
|
116
154
|
try {
|
|
117
|
-
|
|
118
|
-
return JSON.parse(text);
|
|
155
|
+
return parseToml(text);
|
|
119
156
|
}
|
|
120
|
-
catch {
|
|
121
|
-
|
|
157
|
+
catch (error) {
|
|
158
|
+
throw new Error(`Invalid MCP config at ${filePath}: ${error instanceof Error ? error.message : String(error)}`);
|
|
122
159
|
}
|
|
123
160
|
}
|
|
124
161
|
/** Write native MCP config to disk, creating parent directories as needed. */
|
|
125
|
-
async function writeNativeMcp(filePath, data) {
|
|
126
|
-
await fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
|
|
162
|
+
async function writeNativeMcp(filePath, data, containmentRoot) {
|
|
127
163
|
const text = JSON.stringify(data, null, 2) + '\n';
|
|
128
|
-
await
|
|
164
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(filePath, text, containmentRoot);
|
|
129
165
|
}
|
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>;
|
package/dist/revert.js
CHANGED
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.allAgents = void 0;
|
|
37
37
|
exports.revertAllAgentConfigs = revertAllAgentConfigs;
|
|
38
|
-
const path = __importStar(require("path"));
|
|
39
38
|
const fs_1 = require("fs");
|
|
40
39
|
const FileSystemUtils = __importStar(require("./core/FileSystemUtils"));
|
|
41
40
|
const ConfigLoader_1 = require("./core/ConfigLoader");
|
|
@@ -45,21 +44,25 @@ const constants_1 = require("./constants");
|
|
|
45
44
|
const revert_engine_1 = require("./core/revert-engine");
|
|
46
45
|
const agent_selection_1 = require("./core/agent-selection");
|
|
47
46
|
const config_utils_1 = require("./core/config-utils");
|
|
47
|
+
const GitignoreUtils_1 = require("./core/GitignoreUtils");
|
|
48
48
|
const agents = agents_1.allAgents;
|
|
49
|
+
const MANAGED_IGNORE_FILES = ['.gitignore', '.git/info/exclude'];
|
|
49
50
|
/**
|
|
50
51
|
* Reverts ruler configurations for selected AI agents.
|
|
51
52
|
*/
|
|
52
53
|
async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, keepBackups = false, verbose = false, dryRun = false, localOnly = false) {
|
|
53
|
-
(0, constants_1.logVerbose)(`Loading configuration for revert from project root: ${projectRoot}`, verbose);
|
|
54
|
-
const config = await (0, ConfigLoader_1.loadConfig)({
|
|
55
|
-
projectRoot,
|
|
56
|
-
cliAgents: includedAgents,
|
|
57
|
-
configPath,
|
|
58
|
-
});
|
|
59
54
|
const rulerDir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
|
|
60
55
|
if (!rulerDir) {
|
|
61
56
|
throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
|
|
62
57
|
}
|
|
58
|
+
const effectiveProjectRoot = FileSystemUtils.resolveProjectRootForRulerDir(projectRoot, rulerDir);
|
|
59
|
+
(0, constants_1.logVerbose)(`Loading configuration for revert from project root: ${effectiveProjectRoot}`, verbose);
|
|
60
|
+
const config = await (0, ConfigLoader_1.loadConfig)({
|
|
61
|
+
projectRoot: effectiveProjectRoot,
|
|
62
|
+
cliAgents: includedAgents,
|
|
63
|
+
configPath,
|
|
64
|
+
checkGlobal: !localOnly,
|
|
65
|
+
});
|
|
63
66
|
(0, constants_1.logVerbose)(`Found .ruler directory at: ${rulerDir}`, verbose);
|
|
64
67
|
// Normalize per-agent config keys to agent identifiers
|
|
65
68
|
config.agentConfigs = (0, config_utils_1.mapRawAgentConfigs)(config.agentConfigs, agents);
|
|
@@ -77,18 +80,19 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
77
80
|
// Fall back to the old logic without validation
|
|
78
81
|
if (config.cliAgents && config.cliAgents.length > 0) {
|
|
79
82
|
const filters = config.cliAgents.map((n) => n.toLowerCase());
|
|
80
|
-
|
|
81
|
-
|
|
83
|
+
const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
|
|
84
|
+
selected = agents.filter((agent) => filters.some((f) => (0, agent_selection_1.agentMatchesFilter)(agent, f, validAgentIdentifiers)));
|
|
82
85
|
}
|
|
83
86
|
else if (config.defaultAgents && config.defaultAgents.length > 0) {
|
|
84
87
|
const defaults = config.defaultAgents.map((n) => n.toLowerCase());
|
|
88
|
+
const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
|
|
85
89
|
selected = agents.filter((agent) => {
|
|
86
90
|
const identifier = agent.getIdentifier();
|
|
87
91
|
const override = config.agentConfigs[identifier]?.enabled;
|
|
88
92
|
if (override !== undefined) {
|
|
89
93
|
return override;
|
|
90
94
|
}
|
|
91
|
-
return defaults.some((d) =>
|
|
95
|
+
return defaults.some((d) => (0, agent_selection_1.agentMatchesFilter)(agent, d, validAgentIdentifiers));
|
|
92
96
|
});
|
|
93
97
|
}
|
|
94
98
|
else {
|
|
@@ -100,28 +104,36 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
100
104
|
}
|
|
101
105
|
}
|
|
102
106
|
(0, constants_1.logVerbose)(`Selected agents: ${selected.map((a) => a.getName()).join(', ')}`, verbose);
|
|
107
|
+
const isFullRevert = !config.cliAgents || config.cliAgents.length === 0;
|
|
103
108
|
// Revert configurations for each agent
|
|
104
109
|
let totalFilesProcessed = 0;
|
|
105
110
|
let totalFilesRestored = 0;
|
|
106
111
|
let totalFilesRemoved = 0;
|
|
107
112
|
let totalBackupsRemoved = 0;
|
|
113
|
+
let totalDirectoriesRemoved = 0;
|
|
108
114
|
for (const agent of selected) {
|
|
109
115
|
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
110
116
|
console.log(`${prefix} Reverting ${agent.getName()}...`);
|
|
111
117
|
const agentConfig = config.agentConfigs[agent.getIdentifier()];
|
|
112
|
-
const result = await (0, revert_engine_1.revertAgentConfiguration)(agent,
|
|
118
|
+
const result = await (0, revert_engine_1.revertAgentConfiguration)(agent, effectiveProjectRoot, agentConfig, keepBackups, verbose, dryRun);
|
|
113
119
|
totalFilesProcessed += result.restored + result.removed;
|
|
114
120
|
totalFilesRestored += result.restored;
|
|
115
121
|
totalFilesRemoved += result.removed;
|
|
116
122
|
totalBackupsRemoved += result.backupsRemoved;
|
|
123
|
+
if (!isFullRevert) {
|
|
124
|
+
totalDirectoriesRemoved += await (0, revert_engine_1.cleanUpAgentDirectories)(agent, effectiveProjectRoot, agentConfig, verbose, dryRun);
|
|
125
|
+
}
|
|
117
126
|
}
|
|
118
|
-
// Clean up auxiliary files and directories
|
|
119
|
-
const cleanupResult =
|
|
127
|
+
// Clean up auxiliary files and directories only when reverting all agents.
|
|
128
|
+
const cleanupResult = isFullRevert
|
|
129
|
+
? await (0, revert_engine_1.cleanUpAuxiliaryFiles)(effectiveProjectRoot, verbose, dryRun)
|
|
130
|
+
: { additionalFilesRemoved: 0, directoriesRemoved: 0 };
|
|
120
131
|
totalFilesRemoved += cleanupResult.additionalFilesRemoved;
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
132
|
+
totalDirectoriesRemoved += cleanupResult.directoriesRemoved;
|
|
133
|
+
// Clean managed ignore blocks if reverting all agents.
|
|
134
|
+
const cleanedIgnoreFiles = isFullRevert
|
|
135
|
+
? await cleanManagedIgnoreFiles(effectiveProjectRoot, verbose, dryRun)
|
|
136
|
+
: [];
|
|
125
137
|
// Display summary
|
|
126
138
|
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
127
139
|
if (dryRun) {
|
|
@@ -133,53 +145,53 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
133
145
|
console.log(` Files processed: ${totalFilesProcessed}`);
|
|
134
146
|
console.log(` Files restored from backup: ${totalFilesRestored}`);
|
|
135
147
|
console.log(` Generated files removed: ${totalFilesRemoved}`);
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
if (cleanupResult.directoriesRemoved > 0) {
|
|
140
|
-
console.log(` Empty directories removed: ${cleanupResult.directoriesRemoved}`);
|
|
148
|
+
console.log(` Backup files removed: ${totalBackupsRemoved}`);
|
|
149
|
+
if (totalDirectoriesRemoved > 0) {
|
|
150
|
+
console.log(` Empty directories removed: ${totalDirectoriesRemoved}`);
|
|
141
151
|
}
|
|
142
|
-
|
|
143
|
-
console.log(`
|
|
152
|
+
for (const ignoreFile of cleanedIgnoreFiles) {
|
|
153
|
+
console.log(` ${ignoreFile} cleaned: yes`);
|
|
144
154
|
}
|
|
145
155
|
}
|
|
146
156
|
/**
|
|
147
|
-
* Removes the ruler-managed block from
|
|
157
|
+
* Removes the ruler-managed block from ignore files Ruler can update.
|
|
148
158
|
*/
|
|
149
|
-
async function
|
|
150
|
-
const
|
|
159
|
+
async function cleanManagedIgnoreFiles(projectRoot, verbose, dryRun) {
|
|
160
|
+
const cleanedFiles = [];
|
|
161
|
+
for (const ignoreFile of MANAGED_IGNORE_FILES) {
|
|
162
|
+
if (await cleanIgnoreFile(projectRoot, ignoreFile, verbose, dryRun)) {
|
|
163
|
+
cleanedFiles.push(ignoreFile);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
return cleanedFiles;
|
|
167
|
+
}
|
|
168
|
+
async function cleanIgnoreFile(projectRoot, ignoreFile, verbose, dryRun) {
|
|
169
|
+
const ignorePath = await (0, GitignoreUtils_1.resolveIgnoreFilePath)(projectRoot, ignoreFile);
|
|
151
170
|
try {
|
|
152
|
-
await fs_1.promises.access(
|
|
171
|
+
await fs_1.promises.access(ignorePath);
|
|
153
172
|
}
|
|
154
173
|
catch {
|
|
155
|
-
(0, constants_1.logVerbose)(
|
|
174
|
+
(0, constants_1.logVerbose)(`No ${ignoreFile} file found`, verbose);
|
|
156
175
|
return false;
|
|
157
176
|
}
|
|
158
|
-
const content = await fs_1.promises.readFile(
|
|
159
|
-
const
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const endIndex = content.indexOf(endMarker);
|
|
163
|
-
if (startIndex === -1 || endIndex === -1) {
|
|
164
|
-
(0, constants_1.logVerbose)('No ruler-managed block found in .gitignore', verbose);
|
|
177
|
+
const content = await fs_1.promises.readFile(ignorePath, 'utf8');
|
|
178
|
+
const cleaned = (0, GitignoreUtils_1.removeCompleteRulerBlocks)(content);
|
|
179
|
+
if (!cleaned.removed) {
|
|
180
|
+
(0, constants_1.logVerbose)(`No ruler-managed block found in ${ignoreFile}`, verbose);
|
|
165
181
|
return false;
|
|
166
182
|
}
|
|
167
183
|
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
168
184
|
if (dryRun) {
|
|
169
|
-
(0, constants_1.logVerbose)(`${prefix} Would remove ruler block from
|
|
185
|
+
(0, constants_1.logVerbose)(`${prefix} Would remove ruler block from ${ignoreFile}`, verbose);
|
|
170
186
|
}
|
|
171
187
|
else {
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
newContent = newContent.replace(/\n{3,}/g, '\n\n'); // Replace 3+ newlines with 2
|
|
176
|
-
if (newContent.trim() === '') {
|
|
177
|
-
await fs_1.promises.unlink(gitignorePath);
|
|
178
|
-
(0, constants_1.logVerbose)(`${prefix} Removed empty .gitignore file`, verbose);
|
|
188
|
+
if (cleaned.content.trim() === '') {
|
|
189
|
+
await fs_1.promises.unlink(ignorePath);
|
|
190
|
+
(0, constants_1.logVerbose)(`${prefix} Removed empty ${ignoreFile} file`, verbose);
|
|
179
191
|
}
|
|
180
192
|
else {
|
|
181
|
-
await fs_1.promises.writeFile(
|
|
182
|
-
(0, constants_1.logVerbose)(`${prefix} Removed ruler block from
|
|
193
|
+
await fs_1.promises.writeFile(ignorePath, cleaned.content);
|
|
194
|
+
(0, constants_1.logVerbose)(`${prefix} Removed ruler block from ${ignoreFile}`, verbose);
|
|
183
195
|
}
|
|
184
196
|
}
|
|
185
197
|
return true;
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Types for Model Context Protocol (MCP) server configuration.
|
|
3
|
+
*/
|
|
4
|
+
export type McpStrategy = 'merge' | 'overwrite';
|
|
5
|
+
/** MCP configuration for an agent or global. */
|
|
6
|
+
export interface McpConfig {
|
|
7
|
+
/** Enable or disable MCP propagation (merge or overwrite). */
|
|
8
|
+
enabled?: boolean;
|
|
9
|
+
/** Merge strategy: 'merge' to merge servers, 'overwrite' to replace config. */
|
|
10
|
+
strategy?: McpStrategy;
|
|
11
|
+
}
|
|
12
|
+
/** Global MCP configuration section (same as agent-specific config). */
|
|
13
|
+
export type GlobalMcpConfig = McpConfig;
|
|
14
|
+
/** Gitignore configuration for automatic .gitignore file updates. */
|
|
15
|
+
export interface GitignoreConfig {
|
|
16
|
+
/** Enable or disable automatic .gitignore updates. */
|
|
17
|
+
enabled?: boolean;
|
|
18
|
+
/** Write managed ignore entries to .git/info/exclude instead of .gitignore. */
|
|
19
|
+
local?: boolean;
|
|
20
|
+
}
|
|
21
|
+
/** Backup configuration for .bak file generation. */
|
|
22
|
+
export interface BackupConfig {
|
|
23
|
+
/** Enable or disable creation of .bak backup files. */
|
|
24
|
+
enabled?: boolean;
|
|
25
|
+
}
|
|
26
|
+
/** Skills configuration for automatic skills distribution. */
|
|
27
|
+
export interface SkillsConfig {
|
|
28
|
+
/** Enable or disable skills support. */
|
|
29
|
+
enabled?: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** Information about a discovered skill. */
|
|
32
|
+
export interface SkillInfo {
|
|
33
|
+
/** Name of the skill (directory name). */
|
|
34
|
+
name: string;
|
|
35
|
+
/** Absolute path to the skill directory. */
|
|
36
|
+
path: string;
|
|
37
|
+
/** Whether the directory contains a SKILL.md file. */
|
|
38
|
+
hasSkillMd: boolean;
|
|
39
|
+
/** Whether this is a valid skill. */
|
|
40
|
+
valid: boolean;
|
|
41
|
+
/** Error message if invalid. */
|
|
42
|
+
error?: string;
|
|
43
|
+
}
|
|
44
|
+
/** Subagents configuration for automatic subagent distribution. */
|
|
45
|
+
export interface SubagentsConfig {
|
|
46
|
+
/** Enable or disable subagents support. */
|
|
47
|
+
enabled?: boolean;
|
|
48
|
+
/**
|
|
49
|
+
* When true, Ruler may delete previously generated native subagent
|
|
50
|
+
* directories that are stale (disabled, no source definitions, or
|
|
51
|
+
* deselected targets). Defaults to false (non-destructive).
|
|
52
|
+
*/
|
|
53
|
+
cleanup_orphaned?: boolean;
|
|
54
|
+
/**
|
|
55
|
+
* When true, `.ruler/agents/*.md` are also concatenated into the
|
|
56
|
+
* generated top-level rule files (CLAUDE.md, AGENTS.md, Copilot
|
|
57
|
+
* instructions, etc.). When false (default), `.ruler/agents/` is
|
|
58
|
+
* skipped during rule concatenation, mirroring `.ruler/skills/`.
|
|
59
|
+
*/
|
|
60
|
+
include_in_rules?: boolean;
|
|
61
|
+
}
|
|
62
|
+
/** Frontmatter fields recognised on a source subagent definition. */
|
|
63
|
+
export interface SubagentFrontmatter {
|
|
64
|
+
name: string;
|
|
65
|
+
description: string;
|
|
66
|
+
tools?: string[];
|
|
67
|
+
model?: string;
|
|
68
|
+
readonly?: boolean;
|
|
69
|
+
is_background?: boolean;
|
|
70
|
+
}
|
|
71
|
+
/** Information about a discovered subagent. */
|
|
72
|
+
export interface SubagentInfo {
|
|
73
|
+
/** Name of the subagent (filename stem and frontmatter `name`). */
|
|
74
|
+
name: string;
|
|
75
|
+
/** Absolute path to the source `.md` file. */
|
|
76
|
+
path: string;
|
|
77
|
+
/** Relative `.md` path under `.ruler/agents/` (preserves nested layout). */
|
|
78
|
+
sourceRelativePath?: string;
|
|
79
|
+
/** Parsed frontmatter (only present when valid). */
|
|
80
|
+
frontmatter?: SubagentFrontmatter;
|
|
81
|
+
/** Body content after the frontmatter delimiter. */
|
|
82
|
+
body?: string;
|
|
83
|
+
/** Whether this subagent passed validation. */
|
|
84
|
+
valid: boolean;
|
|
85
|
+
/** Error message if invalid. */
|
|
86
|
+
error?: string;
|
|
87
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { McpStrategy } from '../types';
|
|
2
|
+
/**
|
|
3
|
+
* VSCode settings.json structure for Augment MCP configuration
|
|
4
|
+
*/
|
|
5
|
+
export interface VSCodeSettings {
|
|
6
|
+
'augment.advanced'?: {
|
|
7
|
+
mcpServers?: AugmentMcpServer[];
|
|
8
|
+
[key: string]: unknown;
|
|
9
|
+
};
|
|
10
|
+
[key: string]: unknown;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Augment MCP server configuration format
|
|
14
|
+
*/
|
|
15
|
+
export interface AugmentMcpServer {
|
|
16
|
+
name: string;
|
|
17
|
+
command: string;
|
|
18
|
+
args?: string[];
|
|
19
|
+
env?: Record<string, string>;
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read VSCode settings.json file
|
|
23
|
+
*/
|
|
24
|
+
export declare function readVSCodeSettings(settingsPath: string): Promise<VSCodeSettings>;
|
|
25
|
+
/**
|
|
26
|
+
* Write VSCode settings.json file
|
|
27
|
+
*/
|
|
28
|
+
export declare function writeVSCodeSettings(settingsPath: string, settings: VSCodeSettings, containmentRoot?: string): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Transform ruler MCP config to Augment MCP server array format
|
|
31
|
+
*/
|
|
32
|
+
export declare function transformRulerToAugmentMcp(rulerMcpJson: Record<string, unknown>): AugmentMcpServer[];
|
|
33
|
+
/**
|
|
34
|
+
* Merge MCP servers into VSCode settings using the specified strategy
|
|
35
|
+
*/
|
|
36
|
+
export declare function mergeAugmentMcpServers(existingSettings: VSCodeSettings, newServers: AugmentMcpServer[], strategy: McpStrategy): VSCodeSettings;
|
|
37
|
+
/**
|
|
38
|
+
* Get the VSCode settings.json path for a project (local)
|
|
39
|
+
*/
|
|
40
|
+
export declare function getVSCodeSettingsPath(projectRoot: string): string;
|
package/dist/vscode/settings.js
CHANGED
|
@@ -40,6 +40,7 @@ exports.mergeAugmentMcpServers = mergeAugmentMcpServers;
|
|
|
40
40
|
exports.getVSCodeSettingsPath = getVSCodeSettingsPath;
|
|
41
41
|
const fs_1 = require("fs");
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
43
44
|
/**
|
|
44
45
|
* Read VSCode settings.json file
|
|
45
46
|
*/
|
|
@@ -58,9 +59,8 @@ async function readVSCodeSettings(settingsPath) {
|
|
|
58
59
|
/**
|
|
59
60
|
* Write VSCode settings.json file
|
|
60
61
|
*/
|
|
61
|
-
async function writeVSCodeSettings(settingsPath, settings) {
|
|
62
|
-
await
|
|
63
|
-
await fs_1.promises.writeFile(settingsPath, JSON.stringify(settings, null, 4));
|
|
62
|
+
async function writeVSCodeSettings(settingsPath, settings, containmentRoot) {
|
|
63
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(settingsPath, JSON.stringify(settings, null, 4), containmentRoot);
|
|
64
64
|
}
|
|
65
65
|
/**
|
|
66
66
|
* Transform ruler MCP config to Augment MCP server array format
|
package/package.json
CHANGED
|
@@ -1,17 +1,20 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.43",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
|
+
"types": "dist/lib.d.ts",
|
|
6
7
|
"scripts": {
|
|
7
8
|
"lint": "eslint \"src/**/*.{ts,tsx}\"",
|
|
8
|
-
"format": "prettier --write \"src/**/*.{ts,tsx,json,md}\"",
|
|
9
|
-
"
|
|
9
|
+
"format": "prettier --write package.json package-lock.json tsconfig.json README.md \".github/**/*.yml\" \"src/**/*.{ts,tsx,json,md}\" \"tests/**/*.{ts,tsx,json,md}\"",
|
|
10
|
+
"format:check": "prettier --check package.json package-lock.json tsconfig.json README.md \".github/**/*.yml\" \"src/**/*.{ts,tsx,json,md}\" \"tests/**/*.{ts,tsx,json,md}\"",
|
|
11
|
+
"test": "jest --coverage",
|
|
10
12
|
"test:watch": "jest --watch",
|
|
11
13
|
"test:coverage": "jest --coverage",
|
|
12
14
|
"test:integration": "jest tests/e2e/ruler.integration.test.ts --verbose",
|
|
13
15
|
"build": "tsc",
|
|
14
|
-
"
|
|
16
|
+
"check:package-lock": "node -e \"const pkg=require('./package.json'); const lock=require('./package-lock.json'); const root=lock.packages?.['']; if (lock.version !== pkg.version || root?.version !== pkg.version) { console.error('package-lock.json version metadata must match package.json version'); process.exit(1); }\"",
|
|
17
|
+
"prepublishOnly": "npm run build"
|
|
15
18
|
},
|
|
16
19
|
"repository": {
|
|
17
20
|
"type": "git",
|
|
@@ -66,7 +69,7 @@
|
|
|
66
69
|
},
|
|
67
70
|
"dependencies": {
|
|
68
71
|
"@iarna/toml": "^2.2.5",
|
|
69
|
-
"js-yaml": "^4.
|
|
72
|
+
"js-yaml": "^4.2.0",
|
|
70
73
|
"yargs": "^18.0.0",
|
|
71
74
|
"zod": "^4.1.12"
|
|
72
75
|
}
|