@intellectronica/ruler 0.3.5 → 0.3.7
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 +16 -47
- package/dist/agents/CodexCliAgent.js +5 -55
- package/dist/agents/CopilotAgent.js +79 -1
- package/dist/agents/GeminiCliAgent.js +19 -3
- package/dist/agents/index.js +16 -0
- package/dist/cli/commands.js +3 -2
- package/dist/constants.js +27 -0
- package/dist/core/ConfigLoader.js +2 -2
- package/dist/core/UnifiedConfigLoader.js +13 -11
- package/dist/core/apply-engine.js +25 -59
- package/dist/lib.js +3 -2
- package/dist/mcp/propagateOpenHandsMcp.js +1 -2
- package/dist/paths/mcp.js +2 -6
- package/package.json +1 -2
package/README.md
CHANGED
|
@@ -57,18 +57,19 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
57
57
|
| Agent | Rules File(s) | MCP Configuration / Notes |
|
|
58
58
|
| ---------------- | ------------------------------------------------ | ------------------------------------------------ |
|
|
59
59
|
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
|
|
60
|
-
| GitHub Copilot | `.github/copilot-instructions.md`
|
|
60
|
+
| GitHub Copilot | `AGENTS.md`, `.github/copilot-instructions.md` | `.vscode/mcp.json` |
|
|
61
61
|
| Claude Code | `CLAUDE.md` | `.mcp.json` |
|
|
62
62
|
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
|
|
63
63
|
| Jules | `AGENTS.md` | - |
|
|
64
64
|
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
|
|
65
65
|
| Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | - |
|
|
66
66
|
| Cline | `.clinerules` | - |
|
|
67
|
+
| Crush | `CRUSH.md` | `.crush.json` |
|
|
67
68
|
| Amp | `AGENTS.md` | - |
|
|
68
69
|
| Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` |
|
|
69
70
|
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
|
|
70
71
|
| Firebase Studio | `.idx/airules.md` | - |
|
|
71
|
-
| Open Hands | `.openhands/microagents/repo.md` |
|
|
72
|
+
| Open Hands | `.openhands/microagents/repo.md` | `config.toml` |
|
|
72
73
|
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
|
|
73
74
|
| Junie | `.junie/guidelines.md` | - |
|
|
74
75
|
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
|
|
@@ -78,15 +79,12 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
78
79
|
| Qwen Code | `AGENTS.md` | `.qwen/settings.json` |
|
|
79
80
|
| RooCode | `AGENTS.md` | `.roo/mcp.json` |
|
|
80
81
|
| Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) |
|
|
82
|
+
| Trae AI | `.trae/rules/project_rules.md` | - |
|
|
81
83
|
| Warp | `WARP.md` | - |
|
|
82
84
|
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
|
|
83
85
|
|
|
84
86
|
## Getting Started
|
|
85
87
|
|
|
86
|
-
### Prerequisites
|
|
87
|
-
|
|
88
|
-
Node.js 18.x or higher is required.
|
|
89
|
-
|
|
90
88
|
### Installation
|
|
91
89
|
|
|
92
90
|
**Global Installation (Recommended for CLI use):**
|
|
@@ -216,13 +214,16 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
216
214
|
| Option | Description |
|
|
217
215
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
218
216
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
219
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode, warp, roo) |
|
|
217
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, amazonqcli, amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, openhands, gemini-cli, jules, junie, augmentcode, kilocode, opencode, goose, crush, zed, qwen, kiro, warp, roo, trae) |
|
|
220
218
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
221
219
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
222
220
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
223
221
|
| `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging |
|
|
224
222
|
| `--gitignore` | Enable automatic .gitignore updates (default: true) |
|
|
225
223
|
| `--no-gitignore` | Disable automatic .gitignore updates |
|
|
224
|
+
| `--nested` | Enable nested rule loading from nested .ruler directories (default: disabled) |
|
|
225
|
+
| `--backup` | Enable/disable creation of .bak backup files (default: enabled) |
|
|
226
|
+
| `--dry-run` | Preview changes without writing files |
|
|
226
227
|
| `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
|
|
227
228
|
| `--verbose` / `-v` | Display detailed output during execution |
|
|
228
229
|
|
|
@@ -252,6 +253,12 @@ ruler apply --agents firebase
|
|
|
252
253
|
ruler apply --agents warp
|
|
253
254
|
```
|
|
254
255
|
|
|
256
|
+
**Apply rules only to Trae AI:**
|
|
257
|
+
|
|
258
|
+
```bash
|
|
259
|
+
ruler apply --agents trae
|
|
260
|
+
```
|
|
261
|
+
|
|
255
262
|
**Apply rules only to RooCode:**
|
|
256
263
|
|
|
257
264
|
```bash
|
|
@@ -300,7 +307,7 @@ ruler revert [options]
|
|
|
300
307
|
| Option | Description |
|
|
301
308
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
302
309
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
303
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, warp) |
|
|
310
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, amazonqcli, amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, openhands, gemini-cli, jules, junie, augmentcode, kilocode, opencode, goose, crush, zed, qwen, kiro, warp, roo, trae) |
|
|
304
311
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
305
312
|
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
306
313
|
| `--dry-run` | Preview changes without actually reverting files |
|
|
@@ -616,7 +623,7 @@ This creates context-specific instructions for different parts of your project w
|
|
|
616
623
|
2. Commit the `.ruler` directory to your repository
|
|
617
624
|
3. Team members pull changes and run `ruler apply` to update their local AI agent configurations
|
|
618
625
|
|
|
619
|
-
### Scenario
|
|
626
|
+
### Scenario 4: Project-Specific Context for AI
|
|
620
627
|
|
|
621
628
|
1. Detail your project's architecture in `.ruler/project_overview.md`
|
|
622
629
|
2. Describe primary data structures in `.ruler/data_models.md`
|
|
@@ -792,43 +799,5 @@ MIT
|
|
|
792
799
|
|
|
793
800
|
---
|
|
794
801
|
|
|
795
|
-
## Development and Testing
|
|
796
|
-
|
|
797
|
-
### Running Tests
|
|
798
|
-
|
|
799
|
-
The project includes comprehensive test coverage with unit, integration, and end-to-end tests:
|
|
800
|
-
|
|
801
|
-
```bash
|
|
802
|
-
# Run all tests
|
|
803
|
-
npm test
|
|
804
|
-
|
|
805
|
-
# Run with coverage
|
|
806
|
-
npm run test:coverage
|
|
807
|
-
|
|
808
|
-
# Run integration tests specifically
|
|
809
|
-
npm run test:integration
|
|
810
|
-
|
|
811
|
-
# Run tests in watch mode
|
|
812
|
-
npm run test:watch
|
|
813
|
-
```
|
|
814
|
-
|
|
815
|
-
### Integration Testing
|
|
816
|
-
|
|
817
|
-
The project includes comprehensive integration tests that validate the complete CLI workflow:
|
|
818
|
-
|
|
819
|
-
- **`npm run test:integration`**: Runs a full end-to-end integration test that:
|
|
820
|
-
1. Creates a temporary test directory
|
|
821
|
-
2. Runs `ruler init` to set up the initial structure
|
|
822
|
-
3. Creates custom `ruler.toml` with MCP servers (both stdio and remote)
|
|
823
|
-
4. Creates sample `AGENTS.md` and additional markdown files for concatenation
|
|
824
|
-
5. Runs `ruler apply` to generate all agent configuration files
|
|
825
|
-
6. Inspects and validates all generated files contain expected content
|
|
826
|
-
7. Outputs the content of all generated files for manual verification
|
|
827
|
-
8. Validates MCP server configurations were properly applied
|
|
828
|
-
|
|
829
|
-
This integration test ensures the entire CLI workflow functions correctly and can be used for manual testing or CI validation.
|
|
830
|
-
|
|
831
|
-
---
|
|
832
|
-
|
|
833
802
|
© Eleanor Berger
|
|
834
803
|
[ai.intellectronica.net](https://ai.intellectronica.net/)
|
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.CodexCliAgent = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const fs_1 = require("fs");
|
|
39
|
-
const toml = __importStar(require("toml"));
|
|
40
39
|
const toml_1 = require("@iarna/toml");
|
|
41
40
|
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
42
41
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
@@ -85,7 +84,7 @@ class CodexCliAgent {
|
|
|
85
84
|
let existingConfig = {};
|
|
86
85
|
try {
|
|
87
86
|
const existingContent = await fs_1.promises.readFile(configPath, 'utf8');
|
|
88
|
-
existingConfig =
|
|
87
|
+
existingConfig = (0, toml_1.parse)(existingContent);
|
|
89
88
|
}
|
|
90
89
|
catch {
|
|
91
90
|
// File doesn't exist or can't be parsed, use empty config
|
|
@@ -121,59 +120,10 @@ class CodexCliAgent {
|
|
|
121
120
|
updatedConfig.mcp_servers[serverName] = mcpServer;
|
|
122
121
|
}
|
|
123
122
|
}
|
|
124
|
-
// Convert to TOML
|
|
125
|
-
|
|
126
|
-
//
|
|
127
|
-
const
|
|
128
|
-
delete configWithoutMcpServers.mcp_servers;
|
|
129
|
-
if (Object.keys(configWithoutMcpServers).length > 0) {
|
|
130
|
-
tomlContent += (0, toml_1.stringify)(configWithoutMcpServers);
|
|
131
|
-
}
|
|
132
|
-
// Now handle mcp_servers with special formatting for env
|
|
133
|
-
if (updatedConfig.mcp_servers &&
|
|
134
|
-
Object.keys(updatedConfig.mcp_servers).length > 0) {
|
|
135
|
-
for (const [serverName, serverConfig] of Object.entries(updatedConfig.mcp_servers)) {
|
|
136
|
-
tomlContent += `\n[mcp_servers.${serverName}]\n`;
|
|
137
|
-
// Add command
|
|
138
|
-
if (serverConfig.command) {
|
|
139
|
-
tomlContent += `command = "${serverConfig.command}"\n`;
|
|
140
|
-
}
|
|
141
|
-
// Add args if present
|
|
142
|
-
if (serverConfig.args && Array.isArray(serverConfig.args)) {
|
|
143
|
-
const argsStr = JSON.stringify(serverConfig.args)
|
|
144
|
-
.replace(/"/g, '"')
|
|
145
|
-
.replace(/,/g, ', ');
|
|
146
|
-
tomlContent += `args = ${argsStr}\n`;
|
|
147
|
-
}
|
|
148
|
-
// Add env as inline table if present
|
|
149
|
-
if (serverConfig.env && Object.keys(serverConfig.env).length > 0) {
|
|
150
|
-
tomlContent += `env = { `;
|
|
151
|
-
const entries = Object.entries(serverConfig.env);
|
|
152
|
-
for (let i = 0; i < entries.length; i++) {
|
|
153
|
-
const [key, value] = entries[i];
|
|
154
|
-
tomlContent += `${key} = "${value}"`;
|
|
155
|
-
if (i < entries.length - 1) {
|
|
156
|
-
tomlContent += ', ';
|
|
157
|
-
}
|
|
158
|
-
}
|
|
159
|
-
tomlContent += ` }\n`;
|
|
160
|
-
}
|
|
161
|
-
// Add headers as inline table if present (from transformed remote servers)
|
|
162
|
-
if (serverConfig.headers &&
|
|
163
|
-
Object.keys(serverConfig.headers).length > 0) {
|
|
164
|
-
tomlContent += `headers = { `;
|
|
165
|
-
const entries = Object.entries(serverConfig.headers);
|
|
166
|
-
for (let i = 0; i < entries.length; i++) {
|
|
167
|
-
const [key, value] = entries[i];
|
|
168
|
-
tomlContent += `${JSON.stringify(key)} = "${value}"`;
|
|
169
|
-
if (i < entries.length - 1) {
|
|
170
|
-
tomlContent += ', ';
|
|
171
|
-
}
|
|
172
|
-
}
|
|
173
|
-
tomlContent += ` }\n`;
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
}
|
|
123
|
+
// Convert to TOML using structured objects
|
|
124
|
+
const finalConfig = { ...updatedConfig };
|
|
125
|
+
// @iarna/toml should handle the formatting properly
|
|
126
|
+
const tomlContent = (0, toml_1.stringify)(finalConfig);
|
|
177
127
|
await (0, FileSystemUtils_1.writeGeneratedFile)(configPath, tomlContent);
|
|
178
128
|
}
|
|
179
129
|
}
|
|
@@ -1,17 +1,95 @@
|
|
|
1
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.CopilotAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
4
38
|
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
39
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
40
|
+
const fs_1 = require("fs");
|
|
5
41
|
/**
|
|
6
42
|
* GitHub Copilot agent adapter.
|
|
43
|
+
* Writes to both AGENTS.md (for web-based GitHub Copilot) and
|
|
44
|
+
* .github/copilot-instructions.md (for VS Code extension compatibility).
|
|
7
45
|
*/
|
|
8
|
-
class CopilotAgent
|
|
46
|
+
class CopilotAgent {
|
|
47
|
+
constructor() {
|
|
48
|
+
this.agentsMdAgent = new AgentsMdAgent_1.AgentsMdAgent();
|
|
49
|
+
}
|
|
9
50
|
getIdentifier() {
|
|
10
51
|
return 'copilot';
|
|
11
52
|
}
|
|
12
53
|
getName() {
|
|
13
54
|
return 'GitHub Copilot';
|
|
14
55
|
}
|
|
56
|
+
/**
|
|
57
|
+
* Returns multiple output paths to ensure both files are added to .gitignore.
|
|
58
|
+
*/
|
|
59
|
+
getDefaultOutputPath(projectRoot) {
|
|
60
|
+
return {
|
|
61
|
+
instructions: path.join(projectRoot, 'AGENTS.md'),
|
|
62
|
+
legacy: path.join(projectRoot, '.github', 'copilot-instructions.md'),
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
|
|
66
|
+
// First, write to AGENTS.md using the existing AgentsMdAgent infrastructure
|
|
67
|
+
await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, // No MCP config needed for the instructions file
|
|
68
|
+
{
|
|
69
|
+
// Preserve explicit outputPath precedence semantics if provided
|
|
70
|
+
outputPath: agentConfig?.outputPath || agentConfig?.outputPathInstructions,
|
|
71
|
+
}, backup);
|
|
72
|
+
// Additionally write to .github/copilot-instructions.md for VS Code extension compatibility
|
|
73
|
+
const outputPaths = this.getDefaultOutputPath(projectRoot);
|
|
74
|
+
const legacyPath = path.resolve(projectRoot, outputPaths.legacy);
|
|
75
|
+
// Add marker comment to the content to identify it as generated
|
|
76
|
+
const contentWithMarker = `<!-- Generated by Ruler -->\n${concatenatedRules}`;
|
|
77
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(legacyPath));
|
|
78
|
+
// Check if content has changed (idempotency)
|
|
79
|
+
let existingLegacy = null;
|
|
80
|
+
try {
|
|
81
|
+
existingLegacy = await fs_1.promises.readFile(legacyPath, 'utf8');
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
existingLegacy = null;
|
|
85
|
+
}
|
|
86
|
+
if (existingLegacy === null || existingLegacy !== contentWithMarker) {
|
|
87
|
+
if (backup) {
|
|
88
|
+
await (0, FileSystemUtils_1.backupFile)(legacyPath);
|
|
89
|
+
}
|
|
90
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(legacyPath, contentWithMarker);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
15
93
|
getMcpServerKey() {
|
|
16
94
|
return 'servers';
|
|
17
95
|
}
|
|
@@ -44,13 +44,12 @@ class GeminiCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
44
44
|
getName() {
|
|
45
45
|
return 'Gemini CLI';
|
|
46
46
|
}
|
|
47
|
-
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson,
|
|
48
|
-
agentConfig) {
|
|
47
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
|
|
49
48
|
// First, perform idempotent write of AGENTS.md via base class
|
|
50
49
|
await super.applyRulerConfig(concatenatedRules, projectRoot, null, {
|
|
51
50
|
outputPath: agentConfig?.outputPath,
|
|
52
51
|
});
|
|
53
|
-
//
|
|
52
|
+
// Prepare .gemini/settings.json with contextFileName and MCP configuration
|
|
54
53
|
const settingsPath = path.join(projectRoot, '.gemini', 'settings.json');
|
|
55
54
|
let existingSettings = {};
|
|
56
55
|
try {
|
|
@@ -66,6 +65,23 @@ class GeminiCliAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
66
65
|
...existingSettings,
|
|
67
66
|
contextFileName: 'AGENTS.md',
|
|
68
67
|
};
|
|
68
|
+
// Handle MCP server configuration if provided
|
|
69
|
+
const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
|
|
70
|
+
if (mcpEnabled && rulerMcpJson) {
|
|
71
|
+
const strategy = agentConfig?.mcp?.strategy ?? 'merge';
|
|
72
|
+
if (strategy === 'overwrite') {
|
|
73
|
+
// For overwrite, preserve existing settings except MCP servers
|
|
74
|
+
const incomingServers = rulerMcpJson.mcpServers || {};
|
|
75
|
+
updated[this.getMcpServerKey()] = incomingServers;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
// For merge strategy, merge with existing MCP servers
|
|
79
|
+
const baseServers = existingSettings[this.getMcpServerKey()] || {};
|
|
80
|
+
const incomingServers = rulerMcpJson.mcpServers || {};
|
|
81
|
+
const mergedServers = { ...baseServers, ...incomingServers };
|
|
82
|
+
updated[this.getMcpServerKey()] = mergedServers;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
69
85
|
await fs_1.promises.mkdir(path.dirname(settingsPath), { recursive: true });
|
|
70
86
|
await fs_1.promises.writeFile(settingsPath, JSON.stringify(updated, null, 2));
|
|
71
87
|
}
|
package/dist/agents/index.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.allAgents = exports.AbstractAgent = void 0;
|
|
4
|
+
exports.getAgentIdentifiersForCliHelp = getAgentIdentifiersForCliHelp;
|
|
4
5
|
const AbstractAgent_1 = require("./AbstractAgent");
|
|
5
6
|
Object.defineProperty(exports, "AbstractAgent", { enumerable: true, get: function () { return AbstractAgent_1.AbstractAgent; } });
|
|
6
7
|
const CopilotAgent_1 = require("./CopilotAgent");
|
|
@@ -57,3 +58,18 @@ exports.allAgents = [
|
|
|
57
58
|
new TraeAgent_1.TraeAgent(),
|
|
58
59
|
new AmazonQCliAgent_1.AmazonQCliAgent(),
|
|
59
60
|
];
|
|
61
|
+
/**
|
|
62
|
+
* Generates a comma-separated list of agent identifiers for CLI help text.
|
|
63
|
+
* Returns identifiers in alphabetical order, with 'agentsmd' always first.
|
|
64
|
+
*/
|
|
65
|
+
function getAgentIdentifiersForCliHelp() {
|
|
66
|
+
const identifiers = exports.allAgents.map((agent) => agent.getIdentifier());
|
|
67
|
+
const sorted = identifiers.sort();
|
|
68
|
+
// Ensure agentsmd is first (it should already be first alphabetically, but let's be explicit)
|
|
69
|
+
const agentsMdIndex = sorted.indexOf('agentsmd');
|
|
70
|
+
if (agentsMdIndex > 0) {
|
|
71
|
+
const agentsmd = sorted.splice(agentsMdIndex, 1)[0];
|
|
72
|
+
sorted.unshift(agentsmd);
|
|
73
|
+
}
|
|
74
|
+
return sorted.join(', ');
|
|
75
|
+
}
|
package/dist/cli/commands.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.run = run;
|
|
|
7
7
|
const yargs_1 = __importDefault(require("yargs"));
|
|
8
8
|
const helpers_1 = require("yargs/helpers");
|
|
9
9
|
const handlers_1 = require("./handlers");
|
|
10
|
+
const index_1 = require("../agents/index");
|
|
10
11
|
/**
|
|
11
12
|
* Sets up and parses CLI commands.
|
|
12
13
|
*/
|
|
@@ -23,7 +24,7 @@ function run() {
|
|
|
23
24
|
})
|
|
24
25
|
.option('agents', {
|
|
25
26
|
type: 'string',
|
|
26
|
-
description:
|
|
27
|
+
description: `Comma-separated list of agent identifiers: ${(0, index_1.getAgentIdentifiersForCliHelp)()}`,
|
|
27
28
|
})
|
|
28
29
|
.option('config', {
|
|
29
30
|
type: 'string',
|
|
@@ -93,7 +94,7 @@ function run() {
|
|
|
93
94
|
})
|
|
94
95
|
.option('agents', {
|
|
95
96
|
type: 'string',
|
|
96
|
-
description:
|
|
97
|
+
description: `Comma-separated list of agent identifiers: ${(0, index_1.getAgentIdentifiersForCliHelp)()}`,
|
|
97
98
|
})
|
|
98
99
|
.option('config', {
|
|
99
100
|
type: 'string',
|
package/dist/constants.js
CHANGED
|
@@ -4,6 +4,10 @@ exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
|
4
4
|
exports.actionPrefix = actionPrefix;
|
|
5
5
|
exports.createRulerError = createRulerError;
|
|
6
6
|
exports.logVerbose = logVerbose;
|
|
7
|
+
exports.logInfo = logInfo;
|
|
8
|
+
exports.logWarn = logWarn;
|
|
9
|
+
exports.logError = logError;
|
|
10
|
+
exports.logVerboseInfo = logVerboseInfo;
|
|
7
11
|
exports.ERROR_PREFIX = '[ruler]';
|
|
8
12
|
// Centralized default rules filename. Now points to 'AGENTS.md'.
|
|
9
13
|
// Legacy '.ruler/instructions.md' is still supported as a fallback with a warning.
|
|
@@ -22,3 +26,26 @@ function logVerbose(message, isVerbose) {
|
|
|
22
26
|
console.error(`[ruler:verbose] ${message}`);
|
|
23
27
|
}
|
|
24
28
|
}
|
|
29
|
+
/**
|
|
30
|
+
* Centralized logging functions with consistent output streams and prefixing.
|
|
31
|
+
* - info/verbose go to stdout (user-visible progress)
|
|
32
|
+
* - warn/error go to stderr (problems)
|
|
33
|
+
*/
|
|
34
|
+
function logInfo(message, dryRun = false) {
|
|
35
|
+
const prefix = actionPrefix(dryRun);
|
|
36
|
+
console.log(`${prefix} ${message}`);
|
|
37
|
+
}
|
|
38
|
+
function logWarn(message, dryRun = false) {
|
|
39
|
+
const prefix = actionPrefix(dryRun);
|
|
40
|
+
console.warn(`${prefix} ${message}`);
|
|
41
|
+
}
|
|
42
|
+
function logError(message, dryRun = false) {
|
|
43
|
+
const prefix = actionPrefix(dryRun);
|
|
44
|
+
console.error(`${prefix} ${message}`);
|
|
45
|
+
}
|
|
46
|
+
function logVerboseInfo(message, isVerbose, dryRun = false) {
|
|
47
|
+
if (isVerbose) {
|
|
48
|
+
const prefix = actionPrefix(dryRun);
|
|
49
|
+
console.log(`${prefix} ${message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
@@ -37,7 +37,7 @@ exports.loadConfig = loadConfig;
|
|
|
37
37
|
const fs_1 = require("fs");
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
39
|
const os = __importStar(require("os"));
|
|
40
|
-
const
|
|
40
|
+
const toml_1 = require("@iarna/toml");
|
|
41
41
|
const zod_1 = require("zod");
|
|
42
42
|
const constants_1 = require("../constants");
|
|
43
43
|
const mcpConfigSchema = zod_1.z
|
|
@@ -97,7 +97,7 @@ async function loadConfig(options) {
|
|
|
97
97
|
let raw = {};
|
|
98
98
|
try {
|
|
99
99
|
const text = await fs_1.promises.readFile(configFile, 'utf8');
|
|
100
|
-
raw = text.trim() ?
|
|
100
|
+
raw = text.trim() ? (0, toml_1.parse)(text) : {};
|
|
101
101
|
// Validate the configuration with zod
|
|
102
102
|
const validationResult = rulerConfigSchema.safeParse(raw);
|
|
103
103
|
if (!validationResult.success) {
|
|
@@ -36,7 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.loadUnifiedConfig = loadUnifiedConfig;
|
|
37
37
|
const fs_1 = require("fs");
|
|
38
38
|
const path = __importStar(require("path"));
|
|
39
|
-
const
|
|
39
|
+
const toml_1 = require("@iarna/toml");
|
|
40
40
|
const hash_1 = require("./hash");
|
|
41
41
|
const RuleProcessor_1 = require("./RuleProcessor");
|
|
42
42
|
const FileSystemUtils = __importStar(require("./FileSystemUtils"));
|
|
@@ -58,7 +58,7 @@ async function loadUnifiedConfig(options) {
|
|
|
58
58
|
: path.join(meta.rulerDir, 'ruler.toml');
|
|
59
59
|
try {
|
|
60
60
|
const text = await fs_1.promises.readFile(tomlFile, 'utf8');
|
|
61
|
-
tomlRaw = text.trim() ?
|
|
61
|
+
tomlRaw = text.trim() ? (0, toml_1.parse)(text) : {};
|
|
62
62
|
meta.configFile = tomlFile;
|
|
63
63
|
}
|
|
64
64
|
catch (err) {
|
|
@@ -228,11 +228,21 @@ async function loadUnifiedConfig(options) {
|
|
|
228
228
|
try {
|
|
229
229
|
await fs_1.promises.access(mcpFile);
|
|
230
230
|
mcpJsonExists = true;
|
|
231
|
-
|
|
231
|
+
// Warning is handled by apply-engine to avoid duplication
|
|
232
232
|
}
|
|
233
233
|
catch {
|
|
234
234
|
// file not present
|
|
235
235
|
}
|
|
236
|
+
// Add deprecation warning if mcp.json exists (regardless of validity)
|
|
237
|
+
if (mcpJsonExists) {
|
|
238
|
+
meta.mcpFile = mcpFile;
|
|
239
|
+
diagnostics.push({
|
|
240
|
+
severity: 'warning',
|
|
241
|
+
code: 'MCP_JSON_DEPRECATED',
|
|
242
|
+
message: 'mcp.json detected: please migrate MCP servers to ruler.toml [mcp_servers.*] sections',
|
|
243
|
+
file: mcpFile,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
236
246
|
try {
|
|
237
247
|
if (mcpJsonExists) {
|
|
238
248
|
const raw = await fs_1.promises.readFile(mcpFile, 'utf8');
|
|
@@ -256,14 +266,6 @@ async function loadUnifiedConfig(options) {
|
|
|
256
266
|
throw e; // rethrow original error for diagnostics
|
|
257
267
|
}
|
|
258
268
|
}
|
|
259
|
-
meta.mcpFile = mcpFile;
|
|
260
|
-
// Add deprecation warning if mcp.json exists (structured diagnostic)
|
|
261
|
-
diagnostics.push({
|
|
262
|
-
severity: 'warning',
|
|
263
|
-
code: 'MCP_JSON_DEPRECATED',
|
|
264
|
-
message: 'mcp.json detected: please migrate MCP servers to ruler.toml [mcp_servers.*] sections',
|
|
265
|
-
file: mcpFile,
|
|
266
|
-
});
|
|
267
269
|
const parsedObj = parsed;
|
|
268
270
|
const serversRaw = parsedObj.mcpServers ||
|
|
269
271
|
parsedObj.servers ||
|
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.loadNestedConfigurations = loadNestedConfigurations;
|
|
37
37
|
exports.loadSingleConfiguration = loadSingleConfiguration;
|
|
38
|
-
exports.selectAgentsToRun = selectAgentsToRun;
|
|
39
38
|
exports.processHierarchicalConfigurations = processHierarchicalConfigurations;
|
|
40
39
|
exports.processSingleConfiguration = processSingleConfiguration;
|
|
41
40
|
exports.applyConfigurationsToAgents = applyConfigurationsToAgents;
|
|
@@ -122,7 +121,7 @@ async function warnAboutLegacyMcpJson(rulerDir) {
|
|
|
122
121
|
try {
|
|
123
122
|
const legacyMcpPath = path.join(rulerDir, 'mcp.json');
|
|
124
123
|
await (await Promise.resolve().then(() => __importStar(require('fs/promises')))).access(legacyMcpPath);
|
|
125
|
-
|
|
124
|
+
(0, constants_1.logWarn)('Warning: Using legacy .ruler/mcp.json. Please migrate to ruler.toml. This fallback will be removed in a future release.');
|
|
126
125
|
}
|
|
127
126
|
catch {
|
|
128
127
|
// ignore
|
|
@@ -161,52 +160,6 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
161
160
|
rulerMcpJson,
|
|
162
161
|
};
|
|
163
162
|
}
|
|
164
|
-
/**
|
|
165
|
-
* Selects the agents to process based on configuration.
|
|
166
|
-
* @param allAgents Array of all available agents
|
|
167
|
-
* @param config Loaded configuration
|
|
168
|
-
* @returns Array of agents to be processed
|
|
169
|
-
*/
|
|
170
|
-
function selectAgentsToRun(allAgents, config) {
|
|
171
|
-
// CLI --agents > config.default_agents > per-agent.enabled flags > default all
|
|
172
|
-
let selected = allAgents;
|
|
173
|
-
if (config.cliAgents && config.cliAgents.length > 0) {
|
|
174
|
-
const filters = config.cliAgents.map((n) => n.toLowerCase());
|
|
175
|
-
// Check if any of the specified agents don't exist
|
|
176
|
-
const validAgentIdentifiers = new Set(allAgents.map((agent) => agent.getIdentifier()));
|
|
177
|
-
const validAgentNames = new Set(allAgents.map((agent) => agent.getName().toLowerCase()));
|
|
178
|
-
const invalidAgents = filters.filter((filter) => !validAgentIdentifiers.has(filter) &&
|
|
179
|
-
![...validAgentNames].some((name) => name.includes(filter)));
|
|
180
|
-
if (invalidAgents.length > 0) {
|
|
181
|
-
throw (0, constants_1.createRulerError)(`Invalid agent specified: ${invalidAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
|
|
182
|
-
}
|
|
183
|
-
selected = allAgents.filter((agent) => filters.some((f) => agent.getIdentifier() === f ||
|
|
184
|
-
agent.getName().toLowerCase().includes(f)));
|
|
185
|
-
}
|
|
186
|
-
else if (config.defaultAgents && config.defaultAgents.length > 0) {
|
|
187
|
-
const defaults = config.defaultAgents.map((n) => n.toLowerCase());
|
|
188
|
-
// Check if any of the default agents don't exist
|
|
189
|
-
const validAgentIdentifiers = new Set(allAgents.map((agent) => agent.getIdentifier()));
|
|
190
|
-
const validAgentNames = new Set(allAgents.map((agent) => agent.getName().toLowerCase()));
|
|
191
|
-
const invalidAgents = defaults.filter((filter) => !validAgentIdentifiers.has(filter) &&
|
|
192
|
-
![...validAgentNames].some((name) => name.includes(filter)));
|
|
193
|
-
if (invalidAgents.length > 0) {
|
|
194
|
-
throw (0, constants_1.createRulerError)(`Invalid agent specified in default_agents: ${invalidAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
|
|
195
|
-
}
|
|
196
|
-
selected = allAgents.filter((agent) => {
|
|
197
|
-
const identifier = agent.getIdentifier();
|
|
198
|
-
const override = config.agentConfigs[identifier]?.enabled;
|
|
199
|
-
if (override !== undefined) {
|
|
200
|
-
return override;
|
|
201
|
-
}
|
|
202
|
-
return defaults.some((d) => identifier === d || agent.getName().toLowerCase().includes(d));
|
|
203
|
-
});
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
selected = allAgents.filter((agent) => config.agentConfigs[agent.getIdentifier()]?.enabled !== false);
|
|
207
|
-
}
|
|
208
|
-
return selected;
|
|
209
|
-
}
|
|
210
163
|
/**
|
|
211
164
|
* Processes hierarchical configurations by applying rules to each .ruler directory independently.
|
|
212
165
|
* Each directory gets its own set of rules and generates its own agent files.
|
|
@@ -221,7 +174,7 @@ function selectAgentsToRun(allAgents, config) {
|
|
|
221
174
|
async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
|
|
222
175
|
const allGeneratedPaths = [];
|
|
223
176
|
for (const config of configurations) {
|
|
224
|
-
|
|
177
|
+
(0, constants_1.logVerboseInfo)(`Processing .ruler directory: ${config.rulerDir}`, verbose, dryRun);
|
|
225
178
|
const rulerRoot = path.dirname(config.rulerDir);
|
|
226
179
|
const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
|
|
227
180
|
allGeneratedPaths.push(...paths);
|
|
@@ -258,8 +211,7 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
258
211
|
const generatedPaths = [];
|
|
259
212
|
let agentsMdWritten = false;
|
|
260
213
|
for (const agent of agents) {
|
|
261
|
-
|
|
262
|
-
console.log(`${prefix} Applying rules for ${agent.getName()}...`);
|
|
214
|
+
(0, constants_1.logInfo)(`Applying rules for ${agent.getName()}...`, dryRun);
|
|
263
215
|
(0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
|
|
264
216
|
const agentConfig = config.agentConfigs[agent.getIdentifier()];
|
|
265
217
|
// Collect output paths for .gitignore
|
|
@@ -356,6 +308,13 @@ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig,
|
|
|
356
308
|
if (agent.getIdentifier() === 'opencode') {
|
|
357
309
|
return await applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup);
|
|
358
310
|
}
|
|
311
|
+
// Agents that handle MCP configuration internally should not have external MCP handling
|
|
312
|
+
if (agent.getIdentifier() === 'zed' ||
|
|
313
|
+
agent.getIdentifier() === 'gemini-cli' ||
|
|
314
|
+
agent.getIdentifier() === 'amazon-q-cli') {
|
|
315
|
+
(0, constants_1.logVerbose)(`Skipping external MCP config for ${agent.getName()} - handled internally by agent`, verbose);
|
|
316
|
+
return;
|
|
317
|
+
}
|
|
359
318
|
return await applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
|
|
360
319
|
}
|
|
361
320
|
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
|
|
@@ -385,13 +344,21 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
385
344
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
|
|
386
345
|
}
|
|
387
346
|
else {
|
|
388
|
-
if (backup) {
|
|
389
|
-
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
390
|
-
await backupFile(dest);
|
|
391
|
-
}
|
|
392
347
|
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
393
348
|
const merged = (0, merge_1.mergeMcp)(existing, filteredMcpJson, strategy, serverKey);
|
|
394
|
-
|
|
349
|
+
// Only backup and write if content would actually change (idempotent)
|
|
350
|
+
const currentContent = JSON.stringify(existing, null, 2);
|
|
351
|
+
const newContent = JSON.stringify(merged, null, 2);
|
|
352
|
+
if (currentContent !== newContent) {
|
|
353
|
+
if (backup) {
|
|
354
|
+
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
355
|
+
await backupFile(dest);
|
|
356
|
+
}
|
|
357
|
+
await (0, mcp_1.writeNativeMcp)(dest, merged);
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
(0, constants_1.logVerbose)(`MCP config for ${agent.getName()} is already up to date - skipping backup and write`, verbose);
|
|
361
|
+
}
|
|
395
362
|
}
|
|
396
363
|
}
|
|
397
364
|
/**
|
|
@@ -419,13 +386,12 @@ async function updateGitignore(projectRoot, generatedPaths, config, cliGitignore
|
|
|
419
386
|
// Note: Individual backup patterns are added per-file in the collection phase
|
|
420
387
|
// No need to add a broad *.bak pattern here
|
|
421
388
|
if (uniquePaths.length > 0) {
|
|
422
|
-
const prefix = (0, constants_1.actionPrefix)(dryRun);
|
|
423
389
|
if (dryRun) {
|
|
424
|
-
|
|
390
|
+
(0, constants_1.logInfo)(`Would update .gitignore with ${uniquePaths.length} unique path(s): ${uniquePaths.join(', ')}`, dryRun);
|
|
425
391
|
}
|
|
426
392
|
else {
|
|
427
393
|
await (0, GitignoreUtils_1.updateGitignore)(projectRoot, uniquePaths);
|
|
428
|
-
|
|
394
|
+
(0, constants_1.logInfo)(`Updated .gitignore with ${uniquePaths.length} unique path(s) in the Ruler block.`, dryRun);
|
|
429
395
|
}
|
|
430
396
|
}
|
|
431
397
|
}
|
package/dist/lib.js
CHANGED
|
@@ -7,6 +7,7 @@ Object.defineProperty(exports, "allAgents", { enumerable: true, get: function ()
|
|
|
7
7
|
const constants_1 = require("./constants");
|
|
8
8
|
const apply_engine_1 = require("./core/apply-engine");
|
|
9
9
|
const config_utils_1 = require("./core/config-utils");
|
|
10
|
+
const agent_selection_1 = require("./core/agent-selection");
|
|
10
11
|
const agents = agents_1.allAgents;
|
|
11
12
|
/**
|
|
12
13
|
* Applies ruler configurations for all supported AI agents.
|
|
@@ -38,7 +39,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
38
39
|
(0, constants_1.logVerbose)(`Loaded ${hierarchicalConfigs.length} .ruler directory configurations`, verbose);
|
|
39
40
|
(0, constants_1.logVerbose)(`Root configuration has ${Object.keys(rootConfig.agentConfigs).length} agent configs`, verbose);
|
|
40
41
|
normalizeAgentConfigs(rootConfig, agents);
|
|
41
|
-
selectedAgents = (0,
|
|
42
|
+
selectedAgents = (0, agent_selection_1.resolveSelectedAgents)(rootConfig, agents);
|
|
42
43
|
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
|
|
43
44
|
generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
|
|
44
45
|
}
|
|
@@ -49,7 +50,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
49
50
|
(0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(singleConfig.config.agentConfigs).length} agent configs`, verbose);
|
|
50
51
|
(0, constants_1.logVerbose)(`Found .ruler directory with ${singleConfig.concatenatedRules.length} characters of rules`, verbose);
|
|
51
52
|
normalizeAgentConfigs(singleConfig.config, agents);
|
|
52
|
-
selectedAgents = (0,
|
|
53
|
+
selectedAgents = (0, agent_selection_1.resolveSelectedAgents)(singleConfig.config, agents);
|
|
53
54
|
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
|
|
54
55
|
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
|
|
55
56
|
}
|
|
@@ -35,7 +35,6 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.propagateMcpToOpenHands = propagateMcpToOpenHands;
|
|
37
37
|
const fs = __importStar(require("fs/promises"));
|
|
38
|
-
const TOML = __importStar(require("toml"));
|
|
39
38
|
const toml_1 = require("@iarna/toml");
|
|
40
39
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
41
40
|
const path = __importStar(require("path"));
|
|
@@ -103,7 +102,7 @@ async function propagateMcpToOpenHands(rulerMcpData, openHandsConfigPath, backup
|
|
|
103
102
|
let config = {};
|
|
104
103
|
try {
|
|
105
104
|
const tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
|
|
106
|
-
config =
|
|
105
|
+
config = (0, toml_1.parse)(tomlContent);
|
|
107
106
|
}
|
|
108
107
|
catch {
|
|
109
108
|
// File doesn't exist, we'll create it.
|
package/dist/paths/mcp.js
CHANGED
|
@@ -36,12 +36,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.getNativeMcpPath = getNativeMcpPath;
|
|
37
37
|
exports.readNativeMcp = readNativeMcp;
|
|
38
38
|
exports.writeNativeMcp = writeNativeMcp;
|
|
39
|
-
const os = __importStar(require("os"));
|
|
40
39
|
const path = __importStar(require("path"));
|
|
41
40
|
const fs_1 = require("fs");
|
|
42
41
|
/** Determine the native MCP config path for a given agent. */
|
|
43
42
|
async function getNativeMcpPath(adapterName, projectRoot) {
|
|
44
|
-
const home = os.homedir();
|
|
45
43
|
const candidates = [];
|
|
46
44
|
switch (adapterName) {
|
|
47
45
|
case 'GitHub Copilot':
|
|
@@ -53,16 +51,15 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
53
51
|
break;
|
|
54
52
|
case 'Cursor':
|
|
55
53
|
candidates.push(path.join(projectRoot, '.cursor', 'mcp.json'));
|
|
56
|
-
candidates.push(path.join(home, '.cursor', 'mcp.json'));
|
|
57
54
|
break;
|
|
58
55
|
case 'Windsurf':
|
|
59
|
-
candidates.push(path.join(
|
|
56
|
+
candidates.push(path.join(projectRoot, '.windsurf', 'mcp_config.json'));
|
|
60
57
|
break;
|
|
61
58
|
case 'Claude Code':
|
|
62
59
|
candidates.push(path.join(projectRoot, '.mcp.json'));
|
|
63
60
|
break;
|
|
64
61
|
case 'OpenAI Codex CLI':
|
|
65
|
-
candidates.push(path.join(
|
|
62
|
+
candidates.push(path.join(projectRoot, '.codex', 'config.json'));
|
|
66
63
|
break;
|
|
67
64
|
case 'Aider':
|
|
68
65
|
candidates.push(path.join(projectRoot, '.mcp.json'));
|
|
@@ -82,7 +79,6 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
82
79
|
break;
|
|
83
80
|
case 'OpenCode':
|
|
84
81
|
candidates.push(path.join(projectRoot, 'opencode.json'));
|
|
85
|
-
candidates.push(path.join(home, '.config', 'opencode', 'opencode.json'));
|
|
86
82
|
break;
|
|
87
83
|
case 'Zed':
|
|
88
84
|
// Only consider project-local Zed settings (avoid writing to user home directory)
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.7",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"scripts": {
|
|
@@ -62,7 +62,6 @@
|
|
|
62
62
|
"dependencies": {
|
|
63
63
|
"@iarna/toml": "^2.2.5",
|
|
64
64
|
"js-yaml": "^4.1.0",
|
|
65
|
-
"toml": "^3.0.0",
|
|
66
65
|
"yargs": "^17.7.2",
|
|
67
66
|
"zod": "^3.25.28"
|
|
68
67
|
}
|