@intellectronica/ruler 0.2.9 → 0.2.11
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 +22 -17
- package/dist/agents/AugmentCodeAgent.js +9 -19
- package/dist/agents/KiloCodeAgent.js +64 -0
- package/dist/cli/commands.js +7 -3
- package/dist/lib.js +25 -1
- package/dist/paths/mcp.js +4 -2
- package/dist/revert.js +173 -60
- package/dist/vscode/settings.js +117 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -35,21 +35,22 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
35
35
|
|
|
36
36
|
## Supported AI Agents
|
|
37
37
|
|
|
38
|
-
| Agent | File(s)
|
|
39
|
-
| ---------------- |
|
|
40
|
-
| GitHub Copilot | `.github/copilot-instructions.md`
|
|
41
|
-
| Claude Code | `CLAUDE.md`
|
|
42
|
-
| OpenAI Codex CLI | `AGENTS.md`
|
|
43
|
-
| Jules | `AGENTS.md`
|
|
44
|
-
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc`
|
|
45
|
-
| Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md`
|
|
46
|
-
| Cline | `.clinerules`
|
|
47
|
-
| Aider | `ruler_aider_instructions.md
|
|
48
|
-
| Firebase Studio | `.idx/airules.md`
|
|
49
|
-
| Open Hands | `.openhands/microagents/repo.md`
|
|
50
|
-
| Gemini CLI | `GEMINI.md`
|
|
51
|
-
| Junie | `.junie/guidelines.md`
|
|
52
|
-
| AugmentCode | `.augment
|
|
38
|
+
| Agent | Rules File(s) | MCP Configuration |
|
|
39
|
+
| ---------------- | ------------------------------------------------ | ------------------------------------------------ |
|
|
40
|
+
| GitHub Copilot | `.github/copilot-instructions.md` | `.vscode/mcp.json` |
|
|
41
|
+
| Claude Code | `CLAUDE.md` | `claude_desktop_config.json` |
|
|
42
|
+
| OpenAI Codex CLI | `AGENTS.md` | `~/.codex/config.json` |
|
|
43
|
+
| Jules | `AGENTS.md` | - |
|
|
44
|
+
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json`, `~/.cursor/mcp.json` |
|
|
45
|
+
| Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | `~/.codeium/windsurf/mcp_config.json` |
|
|
46
|
+
| Cline | `.clinerules` | - |
|
|
47
|
+
| Aider | `ruler_aider_instructions.md`, `.aider.conf.yml` | `.mcp.json` |
|
|
48
|
+
| Firebase Studio | `.idx/airules.md` | - |
|
|
49
|
+
| Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml` |
|
|
50
|
+
| Gemini CLI | `GEMINI.md` | `.gemini/settings.json` |
|
|
51
|
+
| Junie | `.junie/guidelines.md` | - |
|
|
52
|
+
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
|
|
53
|
+
| Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
|
|
53
54
|
|
|
54
55
|
## Getting Started
|
|
55
56
|
|
|
@@ -145,7 +146,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
145
146
|
| Option | Description |
|
|
146
147
|
| ------------------------------ | --------------------------------------------------------- |
|
|
147
148
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
148
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode) |
|
|
149
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode) |
|
|
149
150
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
150
151
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
151
152
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
@@ -216,7 +217,7 @@ ruler revert [options]
|
|
|
216
217
|
| Option | Description |
|
|
217
218
|
| ------------------------------ | --------------------------------------------------------- |
|
|
218
219
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
219
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie) |
|
|
220
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode) |
|
|
220
221
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
221
222
|
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
222
223
|
| `--dry-run` | Preview changes without actually reverting files |
|
|
@@ -316,6 +317,10 @@ merge_strategy = "merge"
|
|
|
316
317
|
# Disable specific agents
|
|
317
318
|
[agents.windsurf]
|
|
318
319
|
enabled = false
|
|
320
|
+
|
|
321
|
+
[agents.kilocode]
|
|
322
|
+
enabled = true
|
|
323
|
+
output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
319
324
|
```
|
|
320
325
|
|
|
321
326
|
### Configuration Precedence
|
|
@@ -35,12 +35,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.AugmentCodeAgent = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
-
const fs_1 = require("fs");
|
|
39
38
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
40
|
-
const
|
|
39
|
+
const settings_1 = require("../vscode/settings");
|
|
41
40
|
/**
|
|
42
41
|
* AugmentCode agent adapter.
|
|
43
|
-
* Generates .
|
|
42
|
+
* Generates ruler_augment_instructions.md configuration file and updates VSCode settings.json with MCP server configuration.
|
|
44
43
|
*/
|
|
45
44
|
class AugmentCodeAgent {
|
|
46
45
|
getIdentifier() {
|
|
@@ -53,26 +52,17 @@ class AugmentCodeAgent {
|
|
|
53
52
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
54
53
|
await (0, FileSystemUtils_1.backupFile)(output);
|
|
55
54
|
await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
|
|
56
|
-
// Handle MCP configuration if provided
|
|
57
55
|
if (rulerMcpJson) {
|
|
58
|
-
const
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
catch (error) {
|
|
65
|
-
if (error.code !== 'ENOENT') {
|
|
66
|
-
throw error;
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
const merged = (0, merge_1.mergeMcp)(existingConfig, rulerMcpJson, agentConfig?.mcp?.strategy ?? 'merge', this.getMcpServerKey());
|
|
70
|
-
await fs_1.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
71
|
-
await fs_1.promises.writeFile(configPath, JSON.stringify(merged, null, 2));
|
|
56
|
+
const settingsPath = (0, settings_1.getVSCodeSettingsPath)(projectRoot);
|
|
57
|
+
await (0, FileSystemUtils_1.backupFile)(settingsPath);
|
|
58
|
+
const existingSettings = await (0, settings_1.readVSCodeSettings)(settingsPath);
|
|
59
|
+
const augmentServers = (0, settings_1.transformRulerToAugmentMcp)(rulerMcpJson);
|
|
60
|
+
const mergedSettings = (0, settings_1.mergeAugmentMcpServers)(existingSettings, augmentServers, agentConfig?.mcp?.strategy ?? 'merge');
|
|
61
|
+
await (0, settings_1.writeVSCodeSettings)(settingsPath, mergedSettings);
|
|
72
62
|
}
|
|
73
63
|
}
|
|
74
64
|
getDefaultOutputPath(projectRoot) {
|
|
75
|
-
return path.join(projectRoot, '.augment
|
|
65
|
+
return path.join(projectRoot, '.augment', 'rules', 'ruler_augment_instructions.md');
|
|
76
66
|
}
|
|
77
67
|
getMcpServerKey() {
|
|
78
68
|
return 'mcpServers';
|
|
@@ -0,0 +1,64 @@
|
|
|
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.KiloCodeAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
|
+
/**
|
|
40
|
+
* Kilo Code agent adapter.
|
|
41
|
+
* Generates ruler_kilocode_instructions.md configuration file in .kilocode/rules/ directory.
|
|
42
|
+
*/
|
|
43
|
+
class KiloCodeAgent {
|
|
44
|
+
getIdentifier() {
|
|
45
|
+
return 'kilocode';
|
|
46
|
+
}
|
|
47
|
+
getName() {
|
|
48
|
+
return 'Kilo Code';
|
|
49
|
+
}
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
51
|
+
agentConfig) {
|
|
52
|
+
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
53
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
|
|
54
|
+
await (0, FileSystemUtils_1.backupFile)(output);
|
|
55
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
|
|
56
|
+
}
|
|
57
|
+
getDefaultOutputPath(projectRoot) {
|
|
58
|
+
return path.join(projectRoot, '.kilocode', 'rules', 'ruler_kilocode_instructions.md');
|
|
59
|
+
}
|
|
60
|
+
getMcpServerKey() {
|
|
61
|
+
return 'mcpServers';
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
exports.KiloCodeAgent = KiloCodeAgent;
|
package/dist/cli/commands.js
CHANGED
|
@@ -60,7 +60,7 @@ function run() {
|
|
|
60
60
|
});
|
|
61
61
|
y.option('agents', {
|
|
62
62
|
type: 'string',
|
|
63
|
-
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie',
|
|
63
|
+
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode',
|
|
64
64
|
});
|
|
65
65
|
y.option('config', {
|
|
66
66
|
type: 'string',
|
|
@@ -174,7 +174,7 @@ and apply them to your configured AI coding agents.
|
|
|
174
174
|
|
|
175
175
|
# --- Agent Specific Configurations ---
|
|
176
176
|
# You can enable/disable agents and override their default output paths here.
|
|
177
|
-
# Use lowercase agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider
|
|
177
|
+
# Use lowercase agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
|
|
178
178
|
|
|
179
179
|
# [agents.copilot]
|
|
180
180
|
# enabled = true
|
|
@@ -211,6 +211,10 @@ and apply them to your configured AI coding agents.
|
|
|
211
211
|
|
|
212
212
|
# [agents.gemini-cli]
|
|
213
213
|
# enabled = true
|
|
214
|
+
|
|
215
|
+
# [agents.kilocode]
|
|
216
|
+
# enabled = true
|
|
217
|
+
# output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
214
218
|
`;
|
|
215
219
|
if (!(await exists(instructionsPath))) {
|
|
216
220
|
await fs_1.promises.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
|
|
@@ -250,7 +254,7 @@ and apply them to your configured AI coding agents.
|
|
|
250
254
|
});
|
|
251
255
|
y.option('agents', {
|
|
252
256
|
type: 'string',
|
|
253
|
-
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie',
|
|
257
|
+
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode',
|
|
254
258
|
});
|
|
255
259
|
y.option('config', {
|
|
256
260
|
type: 'string',
|
package/dist/lib.js
CHANGED
|
@@ -53,6 +53,7 @@ const GeminiCliAgent_1 = require("./agents/GeminiCliAgent");
|
|
|
53
53
|
const JulesAgent_1 = require("./agents/JulesAgent");
|
|
54
54
|
const JunieAgent_1 = require("./agents/JunieAgent");
|
|
55
55
|
const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
|
|
56
|
+
const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
|
|
56
57
|
const merge_1 = require("./mcp/merge");
|
|
57
58
|
const validate_1 = require("./mcp/validate");
|
|
58
59
|
const mcp_1 = require("./paths/mcp");
|
|
@@ -106,6 +107,7 @@ const agents = [
|
|
|
106
107
|
new JulesAgent_1.JulesAgent(),
|
|
107
108
|
new JunieAgent_1.JunieAgent(),
|
|
108
109
|
new AugmentCodeAgent_1.AugmentCodeAgent(),
|
|
110
|
+
new KiloCodeAgent_1.KiloCodeAgent(),
|
|
109
111
|
];
|
|
110
112
|
/**
|
|
111
113
|
* Applies ruler configurations for all supported AI agents.
|
|
@@ -217,7 +219,21 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
217
219
|
}
|
|
218
220
|
agentsMdWritten = true;
|
|
219
221
|
}
|
|
220
|
-
|
|
222
|
+
let finalAgentConfig = agentConfig;
|
|
223
|
+
if (agent.getIdentifier() === 'augmentcode' && rulerMcpJson) {
|
|
224
|
+
const resolvedStrategy = cliMcpStrategy ??
|
|
225
|
+
agentConfig?.mcp?.strategy ??
|
|
226
|
+
config.mcp?.strategy ??
|
|
227
|
+
'merge';
|
|
228
|
+
finalAgentConfig = {
|
|
229
|
+
...agentConfig,
|
|
230
|
+
mcp: {
|
|
231
|
+
...agentConfig?.mcp,
|
|
232
|
+
strategy: resolvedStrategy,
|
|
233
|
+
},
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
await agent.applyRulerConfig(concatenated, projectRoot, rulerMcpJson, finalAgentConfig);
|
|
221
237
|
}
|
|
222
238
|
const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
223
239
|
const mcpEnabledForAgent = cliMcpEnabled &&
|
|
@@ -241,6 +257,14 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
241
257
|
}
|
|
242
258
|
// Open Hands config file is already included above
|
|
243
259
|
}
|
|
260
|
+
else if (agent.getIdentifier() === 'augmentcode') {
|
|
261
|
+
// *** Special handling for AugmentCode ***
|
|
262
|
+
// AugmentCode handles MCP configuration internally in applyRulerConfig
|
|
263
|
+
// by updating VSCode settings.json with augment.advanced.mcpServers format
|
|
264
|
+
if (dryRun) {
|
|
265
|
+
(0, constants_1.logVerbose)(`DRY RUN: AugmentCode MCP config handled internally via VSCode settings`, verbose);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
244
268
|
else {
|
|
245
269
|
if (rulerMcpJson) {
|
|
246
270
|
const strategy = cliMcpStrategy ??
|
package/dist/paths/mcp.js
CHANGED
|
@@ -75,8 +75,10 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
75
75
|
candidates.push(path.join(projectRoot, '.gemini', 'settings.json'));
|
|
76
76
|
break;
|
|
77
77
|
case 'AugmentCode':
|
|
78
|
-
candidates.push(path.join(projectRoot, '.
|
|
79
|
-
|
|
78
|
+
candidates.push(path.join(projectRoot, '.vscode', 'settings.json'));
|
|
79
|
+
break;
|
|
80
|
+
case 'Kilo Code':
|
|
81
|
+
candidates.push(path.join(projectRoot, '.kilocode', 'mcp.json'));
|
|
80
82
|
break;
|
|
81
83
|
default:
|
|
82
84
|
return null;
|
package/dist/revert.js
CHANGED
|
@@ -50,8 +50,11 @@ const OpenHandsAgent_1 = require("./agents/OpenHandsAgent");
|
|
|
50
50
|
const GeminiCliAgent_1 = require("./agents/GeminiCliAgent");
|
|
51
51
|
const JulesAgent_1 = require("./agents/JulesAgent");
|
|
52
52
|
const JunieAgent_1 = require("./agents/JunieAgent");
|
|
53
|
+
const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
|
|
54
|
+
const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
|
|
53
55
|
const mcp_1 = require("./paths/mcp");
|
|
54
56
|
const constants_1 = require("./constants");
|
|
57
|
+
const settings_1 = require("./vscode/settings");
|
|
55
58
|
const agents = [
|
|
56
59
|
new CopilotAgent_1.CopilotAgent(),
|
|
57
60
|
new ClaudeAgent_1.ClaudeAgent(),
|
|
@@ -65,6 +68,8 @@ const agents = [
|
|
|
65
68
|
new GeminiCliAgent_1.GeminiCliAgent(),
|
|
66
69
|
new JulesAgent_1.JulesAgent(),
|
|
67
70
|
new JunieAgent_1.JunieAgent(),
|
|
71
|
+
new AugmentCodeAgent_1.AugmentCodeAgent(),
|
|
72
|
+
new KiloCodeAgent_1.KiloCodeAgent(),
|
|
68
73
|
];
|
|
69
74
|
/**
|
|
70
75
|
* Gets all output paths for an agent, taking into account any config overrides.
|
|
@@ -170,9 +175,101 @@ async function removeBackupFile(filePath, verbose, dryRun) {
|
|
|
170
175
|
}
|
|
171
176
|
return true;
|
|
172
177
|
}
|
|
178
|
+
/**
|
|
179
|
+
* Recursively checks if a directory contains only empty directories
|
|
180
|
+
*/
|
|
181
|
+
async function isDirectoryTreeEmpty(dirPath) {
|
|
182
|
+
try {
|
|
183
|
+
const entries = await fs_1.promises.readdir(dirPath);
|
|
184
|
+
if (entries.length === 0) {
|
|
185
|
+
return true;
|
|
186
|
+
}
|
|
187
|
+
for (const entry of entries) {
|
|
188
|
+
const entryPath = path.join(dirPath, entry);
|
|
189
|
+
const entryStat = await fs_1.promises.stat(entryPath);
|
|
190
|
+
if (entryStat.isFile()) {
|
|
191
|
+
return false;
|
|
192
|
+
}
|
|
193
|
+
else if (entryStat.isDirectory()) {
|
|
194
|
+
const isEmpty = await isDirectoryTreeEmpty(entryPath);
|
|
195
|
+
if (!isEmpty) {
|
|
196
|
+
return false;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
return true;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
return false;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
/**
|
|
207
|
+
* Helper function to execute directory removal with consistent dry-run handling and logging.
|
|
208
|
+
*/
|
|
209
|
+
async function executeDirectoryAction(dirPath, action, verbose, dryRun) {
|
|
210
|
+
const actionPrefix = dryRun ? '[ruler:dry-run]' : '[ruler]';
|
|
211
|
+
const actionText = action === 'remove-tree' ? 'directory tree' : 'directory';
|
|
212
|
+
if (dryRun) {
|
|
213
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Would remove empty ${actionText}: ${dirPath}`, verbose);
|
|
214
|
+
}
|
|
215
|
+
else {
|
|
216
|
+
await fs_1.promises.rm(dirPath, { recursive: true });
|
|
217
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Removed empty ${actionText}: ${dirPath}`, verbose);
|
|
218
|
+
}
|
|
219
|
+
return true;
|
|
220
|
+
}
|
|
221
|
+
/**
|
|
222
|
+
* Attempts to remove a single empty directory if it exists and is empty.
|
|
223
|
+
*/
|
|
224
|
+
async function removeEmptyDirectory(dirPath, verbose, dryRun, logMissing = false) {
|
|
225
|
+
try {
|
|
226
|
+
const stat = await fs_1.promises.stat(dirPath);
|
|
227
|
+
if (!stat.isDirectory()) {
|
|
228
|
+
return false;
|
|
229
|
+
}
|
|
230
|
+
const isEmpty = await isDirectoryTreeEmpty(dirPath);
|
|
231
|
+
if (isEmpty) {
|
|
232
|
+
return await executeDirectoryAction(dirPath, 'remove-tree', verbose, dryRun);
|
|
233
|
+
}
|
|
234
|
+
return false;
|
|
235
|
+
}
|
|
236
|
+
catch {
|
|
237
|
+
if (logMissing) {
|
|
238
|
+
(0, constants_1.logVerbose)(`Directory ${dirPath} doesn't exist or can't be accessed`, verbose);
|
|
239
|
+
}
|
|
240
|
+
return false;
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
/**
|
|
244
|
+
* Handles special cleanup logic for .augment directory and its rules subdirectory.
|
|
245
|
+
*/
|
|
246
|
+
async function removeAugmentDirectory(projectRoot, verbose, dryRun) {
|
|
247
|
+
const augmentDir = path.join(projectRoot, '.augment');
|
|
248
|
+
let directoriesRemoved = 0;
|
|
249
|
+
try {
|
|
250
|
+
const augmentStat = await fs_1.promises.stat(augmentDir);
|
|
251
|
+
if (!augmentStat.isDirectory()) {
|
|
252
|
+
return 0;
|
|
253
|
+
}
|
|
254
|
+
const rulesDir = path.join(augmentDir, 'rules');
|
|
255
|
+
const rulesRemoved = await removeEmptyDirectory(rulesDir, verbose, dryRun);
|
|
256
|
+
if (rulesRemoved) {
|
|
257
|
+
directoriesRemoved++;
|
|
258
|
+
}
|
|
259
|
+
const augmentRemoved = await removeEmptyDirectory(augmentDir, verbose, dryRun);
|
|
260
|
+
if (augmentRemoved) {
|
|
261
|
+
directoriesRemoved++;
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
catch {
|
|
265
|
+
// .augment directory doesn't exist, that's fine. leaving comment as catch block can't be kept empty.
|
|
266
|
+
}
|
|
267
|
+
return directoriesRemoved;
|
|
268
|
+
}
|
|
173
269
|
/**
|
|
174
270
|
* Removes empty directories that were created by ruler.
|
|
175
271
|
* Only removes directories if they are empty and were likely created by ruler.
|
|
272
|
+
* Special handling for .augment directory to clean up rules subdirectory.
|
|
176
273
|
*/
|
|
177
274
|
async function removeEmptyDirectories(projectRoot, verbose, dryRun) {
|
|
178
275
|
const rulerCreatedDirs = [
|
|
@@ -184,58 +281,18 @@ async function removeEmptyDirectories(projectRoot, verbose, dryRun) {
|
|
|
184
281
|
'.idx',
|
|
185
282
|
'.gemini',
|
|
186
283
|
'.vscode',
|
|
284
|
+
'.augmentcode',
|
|
285
|
+
'.kilocode',
|
|
187
286
|
];
|
|
188
287
|
let directoriesRemoved = 0;
|
|
189
|
-
|
|
288
|
+
// Handle .augment directory with special logic
|
|
289
|
+
directoriesRemoved += await removeAugmentDirectory(projectRoot, verbose, dryRun);
|
|
290
|
+
// Handle all other ruler-created directories
|
|
190
291
|
for (const dirName of rulerCreatedDirs) {
|
|
191
292
|
const dirPath = path.join(projectRoot, dirName);
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
continue;
|
|
196
|
-
}
|
|
197
|
-
const entries = await fs_1.promises.readdir(dirPath);
|
|
198
|
-
if (entries.length === 0) {
|
|
199
|
-
if (dryRun) {
|
|
200
|
-
(0, constants_1.logVerbose)(`${actionPrefix} Would remove empty directory: ${dirPath}`, verbose);
|
|
201
|
-
}
|
|
202
|
-
else {
|
|
203
|
-
await fs_1.promises.rmdir(dirPath);
|
|
204
|
-
(0, constants_1.logVerbose)(`${actionPrefix} Removed empty directory: ${dirPath}`, verbose);
|
|
205
|
-
}
|
|
206
|
-
directoriesRemoved++;
|
|
207
|
-
}
|
|
208
|
-
else {
|
|
209
|
-
let hasNonEmptyContent = false;
|
|
210
|
-
for (const entry of entries) {
|
|
211
|
-
const entryPath = path.join(dirPath, entry);
|
|
212
|
-
const entryStat = await fs_1.promises.stat(entryPath);
|
|
213
|
-
if (entryStat.isFile()) {
|
|
214
|
-
hasNonEmptyContent = true;
|
|
215
|
-
break;
|
|
216
|
-
}
|
|
217
|
-
else if (entryStat.isDirectory()) {
|
|
218
|
-
const subEntries = await fs_1.promises.readdir(entryPath);
|
|
219
|
-
if (subEntries.length > 0) {
|
|
220
|
-
hasNonEmptyContent = true;
|
|
221
|
-
break;
|
|
222
|
-
}
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
if (!hasNonEmptyContent) {
|
|
226
|
-
if (dryRun) {
|
|
227
|
-
(0, constants_1.logVerbose)(`${actionPrefix} Would remove directory tree: ${dirPath}`, verbose);
|
|
228
|
-
}
|
|
229
|
-
else {
|
|
230
|
-
await fs_1.promises.rm(dirPath, { recursive: true });
|
|
231
|
-
(0, constants_1.logVerbose)(`${actionPrefix} Removed directory tree: ${dirPath}`, verbose);
|
|
232
|
-
}
|
|
233
|
-
directoriesRemoved++;
|
|
234
|
-
}
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
catch {
|
|
238
|
-
(0, constants_1.logVerbose)(`Directory ${dirPath} doesn't exist or can't be accessed`, verbose);
|
|
293
|
+
const removed = await removeEmptyDirectory(dirPath, verbose, dryRun, true);
|
|
294
|
+
if (removed) {
|
|
295
|
+
directoriesRemoved++;
|
|
239
296
|
}
|
|
240
297
|
}
|
|
241
298
|
return directoriesRemoved;
|
|
@@ -250,6 +307,7 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
250
307
|
'.mcp.json',
|
|
251
308
|
'.vscode/mcp.json',
|
|
252
309
|
'.cursor/mcp.json',
|
|
310
|
+
'.kilocode/mcp.json',
|
|
253
311
|
'.openhands/config.toml',
|
|
254
312
|
];
|
|
255
313
|
let filesRemoved = 0;
|
|
@@ -283,6 +341,55 @@ async function removeAdditionalAgentFiles(projectRoot, verbose, dryRun) {
|
|
|
283
341
|
(0, constants_1.logVerbose)(`Additional file ${fullPath} doesn't exist or can't be accessed`, verbose);
|
|
284
342
|
}
|
|
285
343
|
}
|
|
344
|
+
const settingsPath = (0, settings_1.getVSCodeSettingsPath)(projectRoot);
|
|
345
|
+
const backupPath = `${settingsPath}.bak`;
|
|
346
|
+
if (await fileExists(backupPath)) {
|
|
347
|
+
const restored = await restoreFromBackup(settingsPath, verbose, dryRun);
|
|
348
|
+
if (restored) {
|
|
349
|
+
filesRemoved++;
|
|
350
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Restored VSCode settings from backup`, verbose);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
else if (await fileExists(settingsPath)) {
|
|
354
|
+
try {
|
|
355
|
+
if (dryRun) {
|
|
356
|
+
const settings = await (0, settings_1.readVSCodeSettings)(settingsPath);
|
|
357
|
+
if (settings['augment.advanced']) {
|
|
358
|
+
delete settings['augment.advanced'];
|
|
359
|
+
const remainingKeys = Object.keys(settings);
|
|
360
|
+
if (remainingKeys.length === 0) {
|
|
361
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Would remove empty VSCode settings file`, verbose);
|
|
362
|
+
}
|
|
363
|
+
else {
|
|
364
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Would remove augment.advanced section from ${settingsPath}`, verbose);
|
|
365
|
+
}
|
|
366
|
+
filesRemoved++;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
else {
|
|
370
|
+
const settings = await (0, settings_1.readVSCodeSettings)(settingsPath);
|
|
371
|
+
if (settings['augment.advanced']) {
|
|
372
|
+
delete settings['augment.advanced'];
|
|
373
|
+
const remainingKeys = Object.keys(settings);
|
|
374
|
+
if (remainingKeys.length === 0) {
|
|
375
|
+
await fs_1.promises.unlink(settingsPath);
|
|
376
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Removed empty VSCode settings file`, verbose);
|
|
377
|
+
}
|
|
378
|
+
else {
|
|
379
|
+
await (0, settings_1.writeVSCodeSettings)(settingsPath, settings);
|
|
380
|
+
(0, constants_1.logVerbose)(`${actionPrefix} Removed augment.advanced section from VSCode settings`, verbose);
|
|
381
|
+
}
|
|
382
|
+
filesRemoved++;
|
|
383
|
+
}
|
|
384
|
+
else {
|
|
385
|
+
(0, constants_1.logVerbose)(`No augment.advanced section found in ${settingsPath}`, verbose);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
}
|
|
389
|
+
catch (error) {
|
|
390
|
+
(0, constants_1.logVerbose)(`Failed to process VSCode settings.json: ${error}`, verbose);
|
|
391
|
+
}
|
|
392
|
+
}
|
|
286
393
|
return filesRemoved;
|
|
287
394
|
}
|
|
288
395
|
/**
|
|
@@ -407,20 +514,26 @@ async function revertAllAgentConfigs(projectRoot, includedAgents, configPath, ke
|
|
|
407
514
|
const mcpPath = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
408
515
|
if (mcpPath && mcpPath.startsWith(projectRoot)) {
|
|
409
516
|
totalFilesProcessed++;
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
if (!keepBackups) {
|
|
414
|
-
const mcpBackupRemoved = await removeBackupFile(mcpPath, verbose, dryRun);
|
|
415
|
-
if (mcpBackupRemoved) {
|
|
416
|
-
totalBackupsRemoved++;
|
|
417
|
-
}
|
|
418
|
-
}
|
|
517
|
+
if (agent.getName() === 'AugmentCode' &&
|
|
518
|
+
mcpPath.endsWith('.vscode/settings.json')) {
|
|
519
|
+
(0, constants_1.logVerbose)(`Skipping MCP handling for AugmentCode settings.json - handled separately`, verbose);
|
|
419
520
|
}
|
|
420
521
|
else {
|
|
421
|
-
const
|
|
422
|
-
if (
|
|
423
|
-
|
|
522
|
+
const mcpRestored = await restoreFromBackup(mcpPath, verbose, dryRun);
|
|
523
|
+
if (mcpRestored) {
|
|
524
|
+
totalFilesRestored++;
|
|
525
|
+
if (!keepBackups) {
|
|
526
|
+
const mcpBackupRemoved = await removeBackupFile(mcpPath, verbose, dryRun);
|
|
527
|
+
if (mcpBackupRemoved) {
|
|
528
|
+
totalBackupsRemoved++;
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
}
|
|
532
|
+
else {
|
|
533
|
+
const mcpRemoved = await removeGeneratedFile(mcpPath, verbose, dryRun);
|
|
534
|
+
if (mcpRemoved) {
|
|
535
|
+
totalFilesRemoved++;
|
|
536
|
+
}
|
|
424
537
|
}
|
|
425
538
|
}
|
|
426
539
|
}
|
|
@@ -0,0 +1,117 @@
|
|
|
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.readVSCodeSettings = readVSCodeSettings;
|
|
37
|
+
exports.writeVSCodeSettings = writeVSCodeSettings;
|
|
38
|
+
exports.transformRulerToAugmentMcp = transformRulerToAugmentMcp;
|
|
39
|
+
exports.mergeAugmentMcpServers = mergeAugmentMcpServers;
|
|
40
|
+
exports.getVSCodeSettingsPath = getVSCodeSettingsPath;
|
|
41
|
+
const fs_1 = require("fs");
|
|
42
|
+
const path = __importStar(require("path"));
|
|
43
|
+
/**
|
|
44
|
+
* Read VSCode settings.json file
|
|
45
|
+
*/
|
|
46
|
+
async function readVSCodeSettings(settingsPath) {
|
|
47
|
+
try {
|
|
48
|
+
const content = await fs_1.promises.readFile(settingsPath, 'utf8');
|
|
49
|
+
return JSON.parse(content);
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
if (error.code === 'ENOENT') {
|
|
53
|
+
return {};
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Write VSCode settings.json file
|
|
60
|
+
*/
|
|
61
|
+
async function writeVSCodeSettings(settingsPath, settings) {
|
|
62
|
+
await fs_1.promises.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
63
|
+
await fs_1.promises.writeFile(settingsPath, JSON.stringify(settings, null, 4));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Transform ruler MCP config to Augment MCP server array format
|
|
67
|
+
*/
|
|
68
|
+
function transformRulerToAugmentMcp(rulerMcpJson) {
|
|
69
|
+
const servers = [];
|
|
70
|
+
if (rulerMcpJson.mcpServers && typeof rulerMcpJson.mcpServers === 'object') {
|
|
71
|
+
const mcpServers = rulerMcpJson.mcpServers;
|
|
72
|
+
for (const [name, serverConfig] of Object.entries(mcpServers)) {
|
|
73
|
+
const augmentServer = {
|
|
74
|
+
name,
|
|
75
|
+
command: serverConfig.command,
|
|
76
|
+
};
|
|
77
|
+
if (serverConfig.args) {
|
|
78
|
+
augmentServer.args = serverConfig.args;
|
|
79
|
+
}
|
|
80
|
+
if (serverConfig.env) {
|
|
81
|
+
augmentServer.env = serverConfig.env;
|
|
82
|
+
}
|
|
83
|
+
servers.push(augmentServer);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return servers;
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* Merge MCP servers into VSCode settings using the specified strategy
|
|
90
|
+
*/
|
|
91
|
+
function mergeAugmentMcpServers(existingSettings, newServers, strategy) {
|
|
92
|
+
const result = structuredClone(existingSettings);
|
|
93
|
+
if (!result['augment.advanced']) {
|
|
94
|
+
result['augment.advanced'] = {};
|
|
95
|
+
}
|
|
96
|
+
if (strategy === 'overwrite') {
|
|
97
|
+
result['augment.advanced'].mcpServers = newServers;
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
const existingServers = result['augment.advanced'].mcpServers || [];
|
|
101
|
+
const existingServerMap = new Map();
|
|
102
|
+
for (const server of existingServers) {
|
|
103
|
+
existingServerMap.set(server.name, server);
|
|
104
|
+
}
|
|
105
|
+
for (const newServer of newServers) {
|
|
106
|
+
existingServerMap.set(newServer.name, newServer);
|
|
107
|
+
}
|
|
108
|
+
result['augment.advanced'].mcpServers = Array.from(existingServerMap.values());
|
|
109
|
+
}
|
|
110
|
+
return result;
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Get the VSCode settings.json path for a project (local)
|
|
114
|
+
*/
|
|
115
|
+
function getVSCodeSettingsPath(projectRoot) {
|
|
116
|
+
return path.join(projectRoot, '.vscode', 'settings.json');
|
|
117
|
+
}
|