@intellectronica/ruler 0.2.1 → 0.2.3
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 +80 -29
- package/dist/agents/FirebaseAgent.js +58 -0
- package/dist/agents/OpenHandsAgent.js +56 -0
- package/dist/cli/commands.js +5 -1
- package/dist/core/ConfigLoader.js +1 -1
- package/dist/lib.js +34 -13
- package/dist/mcp/propagateOpenHandsMcp.js +83 -0
- package/dist/paths/mcp.js +4 -0
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
Managing instructions across multiple AI coding tools becomes complex as your team grows. Different agents (GitHub Copilot, Claude, Cursor, Aider, etc.) require their own configuration files, leading to:
|
|
19
19
|
|
|
20
20
|
- **Inconsistent guidance** across AI tools
|
|
21
|
-
- **Duplicated effort** maintaining multiple config files
|
|
21
|
+
- **Duplicated effort** maintaining multiple config files
|
|
22
22
|
- **Context drift** as project requirements evolve
|
|
23
23
|
- **Onboarding friction** for new AI tools
|
|
24
24
|
|
|
@@ -35,15 +35,17 @@ 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
|
|
39
|
-
|
|
|
40
|
-
| GitHub Copilot
|
|
41
|
-
| Claude Code
|
|
42
|
-
| OpenAI Codex CLI
|
|
43
|
-
| Cursor
|
|
44
|
-
| Windsurf
|
|
45
|
-
| Cline
|
|
46
|
-
| Aider
|
|
38
|
+
| Agent | File(s) Created/Updated |
|
|
39
|
+
| ---------------- | ------------------------------------------------------------- |
|
|
40
|
+
| GitHub Copilot | `.github/copilot-instructions.md` |
|
|
41
|
+
| Claude Code | `CLAUDE.md` |
|
|
42
|
+
| OpenAI Codex CLI | `AGENTS.md` |
|
|
43
|
+
| Cursor | `.cursor/rules/ruler_cursor_instructions.md` |
|
|
44
|
+
| Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` |
|
|
45
|
+
| Cline | `.clinerules` |
|
|
46
|
+
| Aider | `ruler_aider_instructions.md` and `.aider.conf.yml` |
|
|
47
|
+
| Firebase Studio | `.idx/airules.md` |
|
|
48
|
+
| Open Hands | `.openhands/microagents/repo.md` and `.openhands/config.toml` |
|
|
47
49
|
|
|
48
50
|
## Getting Started
|
|
49
51
|
|
|
@@ -54,11 +56,13 @@ Node.js 18.x or higher is required.
|
|
|
54
56
|
### Installation
|
|
55
57
|
|
|
56
58
|
**Global Installation (Recommended for CLI use):**
|
|
59
|
+
|
|
57
60
|
```bash
|
|
58
61
|
npm install -g @intellectronica/ruler
|
|
59
62
|
```
|
|
60
63
|
|
|
61
64
|
**Using `npx` (for one-off commands):**
|
|
65
|
+
|
|
62
66
|
```bash
|
|
63
67
|
npx @intellectronica/ruler apply
|
|
64
68
|
```
|
|
@@ -87,25 +91,30 @@ This is your central hub for all AI agent instructions:
|
|
|
87
91
|
### Best Practices for Rule Files
|
|
88
92
|
|
|
89
93
|
**Granularity**: Break down complex instructions into focused `.md` files:
|
|
94
|
+
|
|
90
95
|
- `coding_style.md`
|
|
91
|
-
- `api_conventions.md`
|
|
96
|
+
- `api_conventions.md`
|
|
92
97
|
- `project_architecture.md`
|
|
93
98
|
- `security_guidelines.md`
|
|
94
99
|
|
|
95
100
|
**Example rule file (`.ruler/python_guidelines.md`):**
|
|
101
|
+
|
|
96
102
|
```markdown
|
|
97
103
|
# Python Project Guidelines
|
|
98
104
|
|
|
99
105
|
## General Style
|
|
106
|
+
|
|
100
107
|
- Follow PEP 8 for all Python code
|
|
101
108
|
- Use type hints for all function signatures and complex variables
|
|
102
109
|
- Keep functions short and focused on a single task
|
|
103
110
|
|
|
104
111
|
## Error Handling
|
|
112
|
+
|
|
105
113
|
- Use specific exception types rather than generic `Exception`
|
|
106
114
|
- Log errors effectively with context
|
|
107
115
|
|
|
108
116
|
## Security
|
|
117
|
+
|
|
109
118
|
- Always validate and sanitize user input
|
|
110
119
|
- Be mindful of potential injection vulnerabilities
|
|
111
120
|
```
|
|
@@ -113,47 +122,59 @@ This is your central hub for all AI agent instructions:
|
|
|
113
122
|
## Usage: The `apply` Command
|
|
114
123
|
|
|
115
124
|
### Primary Command
|
|
125
|
+
|
|
116
126
|
```bash
|
|
117
127
|
ruler apply [options]
|
|
118
128
|
```
|
|
119
129
|
|
|
120
130
|
### Options
|
|
121
131
|
|
|
122
|
-
| Option
|
|
123
|
-
|
|
124
|
-
| `--project-root <path>`
|
|
125
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target
|
|
126
|
-
| `--config <path>`
|
|
127
|
-
| `--mcp` / `--with-mcp`
|
|
128
|
-
| `--no-mcp`
|
|
129
|
-
| `--mcp-overwrite`
|
|
130
|
-
| `--gitignore`
|
|
131
|
-
| `--no-gitignore`
|
|
132
|
-
| `--verbose` / `-v`
|
|
132
|
+
| Option | Description |
|
|
133
|
+
| ------------------------------ | --------------------------------------------------------- |
|
|
134
|
+
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
135
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target |
|
|
136
|
+
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
137
|
+
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
138
|
+
| `--no-mcp` | Disable applying MCP server configurations |
|
|
139
|
+
| `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging |
|
|
140
|
+
| `--gitignore` | Enable automatic .gitignore updates (default: true) |
|
|
141
|
+
| `--no-gitignore` | Disable automatic .gitignore updates |
|
|
142
|
+
| `--verbose` / `-v` | Display detailed output during execution |
|
|
133
143
|
|
|
134
144
|
### Common Examples
|
|
135
145
|
|
|
136
146
|
**Apply rules to all configured agents:**
|
|
147
|
+
|
|
137
148
|
```bash
|
|
138
149
|
ruler apply
|
|
139
150
|
```
|
|
140
151
|
|
|
141
152
|
**Apply rules only to GitHub Copilot and Claude:**
|
|
153
|
+
|
|
142
154
|
```bash
|
|
143
155
|
ruler apply --agents copilot,claude
|
|
144
156
|
```
|
|
145
157
|
|
|
158
|
+
**Apply rules only to Firebase Studio:**
|
|
159
|
+
|
|
160
|
+
```bash
|
|
161
|
+
ruler apply --agents firebase
|
|
162
|
+
```
|
|
163
|
+
|
|
146
164
|
**Use a specific configuration file:**
|
|
165
|
+
|
|
147
166
|
```bash
|
|
148
167
|
ruler apply --config ./team-configs/ruler.frontend.toml
|
|
149
168
|
```
|
|
150
169
|
|
|
151
170
|
**Apply rules with verbose output:**
|
|
171
|
+
|
|
152
172
|
```bash
|
|
153
173
|
ruler apply --verbose
|
|
154
174
|
```
|
|
155
175
|
|
|
156
176
|
**Apply rules but skip MCP and .gitignore updates:**
|
|
177
|
+
|
|
157
178
|
```bash
|
|
158
179
|
ruler apply --no-mcp --no-gitignore
|
|
159
180
|
```
|
|
@@ -161,9 +182,11 @@ ruler apply --no-mcp --no-gitignore
|
|
|
161
182
|
## Configuration (`ruler.toml`) in Detail
|
|
162
183
|
|
|
163
184
|
### Location
|
|
185
|
+
|
|
164
186
|
Defaults to `.ruler/ruler.toml` in the project root. Override with `--config` CLI option.
|
|
165
187
|
|
|
166
188
|
### Complete Example
|
|
189
|
+
|
|
167
190
|
```toml
|
|
168
191
|
# Default agents to run when --agents is not specified
|
|
169
192
|
# Uses case-insensitive substring matching
|
|
@@ -195,6 +218,10 @@ enabled = true
|
|
|
195
218
|
output_path_instructions = "ruler_aider_instructions.md"
|
|
196
219
|
output_path_config = ".aider.conf.yml"
|
|
197
220
|
|
|
221
|
+
[agents.firebase]
|
|
222
|
+
enabled = true
|
|
223
|
+
output_path = ".idx/airules.md"
|
|
224
|
+
|
|
198
225
|
# Agent-specific MCP configuration
|
|
199
226
|
[agents.cursor.mcp]
|
|
200
227
|
enabled = true
|
|
@@ -216,16 +243,22 @@ enabled = false
|
|
|
216
243
|
MCP provides broader context to AI models through server configurations. Ruler can manage and distribute these settings across compatible agents.
|
|
217
244
|
|
|
218
245
|
### `.ruler/mcp.json`
|
|
246
|
+
|
|
219
247
|
Define your project's MCP servers:
|
|
248
|
+
|
|
220
249
|
```json
|
|
221
250
|
{
|
|
222
251
|
"mcpServers": {
|
|
223
252
|
"filesystem": {
|
|
224
253
|
"command": "npx",
|
|
225
|
-
"args": [
|
|
254
|
+
"args": [
|
|
255
|
+
"-y",
|
|
256
|
+
"@modelcontextprotocol/server-filesystem",
|
|
257
|
+
"/path/to/project"
|
|
258
|
+
]
|
|
226
259
|
},
|
|
227
260
|
"git": {
|
|
228
|
-
"command": "npx",
|
|
261
|
+
"command": "npx",
|
|
229
262
|
"args": ["-y", "@modelcontextprotocol/server-git", "--repository", "."]
|
|
230
263
|
}
|
|
231
264
|
}
|
|
@@ -239,12 +272,14 @@ Ruler uses this file with the `merge` (default) or `overwrite` strategy, control
|
|
|
239
272
|
Ruler automatically manages your `.gitignore` file to keep generated agent configuration files out of version control.
|
|
240
273
|
|
|
241
274
|
### How it Works
|
|
275
|
+
|
|
242
276
|
- Creates or updates `.gitignore` in your project root
|
|
243
277
|
- Adds paths to a managed block marked with `# START Ruler Generated Files` and `# END Ruler Generated Files`
|
|
244
278
|
- Preserves existing content outside this block
|
|
245
279
|
- Sorts paths alphabetically and uses relative POSIX-style paths
|
|
246
280
|
|
|
247
281
|
### Example `.gitignore` Section
|
|
282
|
+
|
|
248
283
|
```gitignore
|
|
249
284
|
# Your existing rules
|
|
250
285
|
node_modules/
|
|
@@ -265,6 +300,7 @@ dist/
|
|
|
265
300
|
```
|
|
266
301
|
|
|
267
302
|
### Control Options
|
|
303
|
+
|
|
268
304
|
- **CLI flags**: `--gitignore` or `--no-gitignore`
|
|
269
305
|
- **Configuration**: `[gitignore].enabled` in `ruler.toml`
|
|
270
306
|
- **Default**: enabled
|
|
@@ -272,6 +308,7 @@ dist/
|
|
|
272
308
|
## Practical Usage Scenarios
|
|
273
309
|
|
|
274
310
|
### Scenario 1: Getting Started Quickly
|
|
311
|
+
|
|
275
312
|
```bash
|
|
276
313
|
# Initialize Ruler in your project
|
|
277
314
|
cd your-project
|
|
@@ -286,16 +323,19 @@ ruler apply
|
|
|
286
323
|
```
|
|
287
324
|
|
|
288
325
|
### Scenario 2: Team Standardization
|
|
326
|
+
|
|
289
327
|
1. Create `.ruler/coding_standards.md`, `.ruler/api_usage.md`
|
|
290
328
|
2. Commit the `.ruler` directory to your repository
|
|
291
329
|
3. Team members pull changes and run `ruler apply` to update their local AI agent configurations
|
|
292
330
|
|
|
293
331
|
### Scenario 3: Project-Specific Context for AI
|
|
332
|
+
|
|
294
333
|
1. Detail your project's architecture in `.ruler/project_overview.md`
|
|
295
|
-
2. Describe primary data structures in `.ruler/data_models.md`
|
|
334
|
+
2. Describe primary data structures in `.ruler/data_models.md`
|
|
296
335
|
3. Run `ruler apply` to help AI tools provide more relevant suggestions
|
|
297
336
|
|
|
298
337
|
### Integration with NPM Scripts
|
|
338
|
+
|
|
299
339
|
```json
|
|
300
340
|
{
|
|
301
341
|
"scripts": {
|
|
@@ -307,6 +347,7 @@ ruler apply
|
|
|
307
347
|
```
|
|
308
348
|
|
|
309
349
|
### Integration with GitHub Actions
|
|
350
|
+
|
|
310
351
|
```yaml
|
|
311
352
|
# .github/workflows/ruler-check.yml
|
|
312
353
|
name: Check Ruler Configuration
|
|
@@ -323,13 +364,13 @@ jobs:
|
|
|
323
364
|
with:
|
|
324
365
|
node-version: '18'
|
|
325
366
|
cache: 'npm'
|
|
326
|
-
|
|
367
|
+
|
|
327
368
|
- name: Install Ruler
|
|
328
369
|
run: npm install -g @intellectronica/ruler
|
|
329
|
-
|
|
370
|
+
|
|
330
371
|
- name: Apply Ruler configuration
|
|
331
372
|
run: ruler apply --no-gitignore
|
|
332
|
-
|
|
373
|
+
|
|
333
374
|
- name: Check for uncommitted changes
|
|
334
375
|
run: |
|
|
335
376
|
if [[ -n $(git status --porcelain) ]]; then
|
|
@@ -344,28 +385,35 @@ jobs:
|
|
|
344
385
|
### Common Issues
|
|
345
386
|
|
|
346
387
|
**"Cannot find module" errors:**
|
|
388
|
+
|
|
347
389
|
- Ensure Ruler is installed globally: `npm install -g @intellectronica/ruler`
|
|
348
390
|
- Or use `npx @intellectronica/ruler`
|
|
349
391
|
|
|
350
392
|
**Permission denied errors:**
|
|
393
|
+
|
|
351
394
|
- On Unix systems, you may need `sudo` for global installation
|
|
352
395
|
|
|
353
396
|
**Agent files not updating:**
|
|
397
|
+
|
|
354
398
|
- Check if the agent is enabled in `ruler.toml`
|
|
355
399
|
- Verify agent isn't excluded by `--agents` flag
|
|
356
400
|
- Use `--verbose` to see detailed execution logs
|
|
357
401
|
|
|
358
402
|
**Configuration validation errors:**
|
|
403
|
+
|
|
359
404
|
- Ruler now validates `ruler.toml` format and will show specific error details
|
|
360
405
|
- Check that all configuration values match the expected types and formats
|
|
361
406
|
|
|
362
407
|
### Debug Mode
|
|
408
|
+
|
|
363
409
|
Use `--verbose` flag to see detailed execution logs:
|
|
410
|
+
|
|
364
411
|
```bash
|
|
365
412
|
ruler apply --verbose
|
|
366
413
|
```
|
|
367
414
|
|
|
368
415
|
This shows:
|
|
416
|
+
|
|
369
417
|
- Configuration loading details
|
|
370
418
|
- Agent selection logic
|
|
371
419
|
- File processing information
|
|
@@ -391,6 +439,7 @@ A: Version 0.2.0 is backward compatible. Your existing `.ruler/` directory and `
|
|
|
391
439
|
## Development
|
|
392
440
|
|
|
393
441
|
### Setup
|
|
442
|
+
|
|
394
443
|
```bash
|
|
395
444
|
git clone https://github.com/intellectronica/ruler.git
|
|
396
445
|
cd ruler
|
|
@@ -399,6 +448,7 @@ npm run build
|
|
|
399
448
|
```
|
|
400
449
|
|
|
401
450
|
### Testing
|
|
451
|
+
|
|
402
452
|
```bash
|
|
403
453
|
# Run all tests
|
|
404
454
|
npm test
|
|
@@ -411,6 +461,7 @@ npm run test:watch
|
|
|
411
461
|
```
|
|
412
462
|
|
|
413
463
|
### Code Quality
|
|
464
|
+
|
|
414
465
|
```bash
|
|
415
466
|
# Run linting
|
|
416
467
|
npm run lint
|
|
@@ -439,4 +490,4 @@ MIT
|
|
|
439
490
|
---
|
|
440
491
|
|
|
441
492
|
© Eleanor Berger
|
|
442
|
-
[ai.intellectronica.net](https://ai.intellectronica.net/)
|
|
493
|
+
[ai.intellectronica.net](https://ai.intellectronica.net/)
|
|
@@ -0,0 +1,58 @@
|
|
|
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.FirebaseAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
|
+
/**
|
|
40
|
+
* Firebase Studio agent adapter.
|
|
41
|
+
*/
|
|
42
|
+
class FirebaseAgent {
|
|
43
|
+
getIdentifier() {
|
|
44
|
+
return 'firebase';
|
|
45
|
+
}
|
|
46
|
+
getName() {
|
|
47
|
+
return 'Firebase Studio';
|
|
48
|
+
}
|
|
49
|
+
async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
|
|
50
|
+
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
51
|
+
await (0, FileSystemUtils_1.backupFile)(output);
|
|
52
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
|
|
53
|
+
}
|
|
54
|
+
getDefaultOutputPath(projectRoot) {
|
|
55
|
+
return path.join(projectRoot, '.idx', 'airules.md');
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
exports.FirebaseAgent = FirebaseAgent;
|
|
@@ -0,0 +1,56 @@
|
|
|
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.OpenHandsAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
|
+
class OpenHandsAgent {
|
|
40
|
+
getIdentifier() {
|
|
41
|
+
return 'openhands';
|
|
42
|
+
}
|
|
43
|
+
getName() {
|
|
44
|
+
return 'Open Hands';
|
|
45
|
+
}
|
|
46
|
+
async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
|
|
47
|
+
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
48
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
|
|
49
|
+
await (0, FileSystemUtils_1.backupFile)(output);
|
|
50
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
|
|
51
|
+
}
|
|
52
|
+
getDefaultOutputPath(projectRoot) {
|
|
53
|
+
return path.join(projectRoot, '.openhands', 'microagents', 'repo.md');
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
exports.OpenHandsAgent = OpenHandsAgent;
|
package/dist/cli/commands.js
CHANGED
|
@@ -58,7 +58,7 @@ function run() {
|
|
|
58
58
|
});
|
|
59
59
|
y.option('agents', {
|
|
60
60
|
type: 'string',
|
|
61
|
-
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider',
|
|
61
|
+
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase',
|
|
62
62
|
});
|
|
63
63
|
y.option('config', {
|
|
64
64
|
type: 'string',
|
|
@@ -189,6 +189,10 @@ and apply them to your configured AI coding agents.
|
|
|
189
189
|
# enabled = true
|
|
190
190
|
# output_path_instructions = "ruler_aider_instructions.md"
|
|
191
191
|
# output_path_config = ".aider.conf.yml"
|
|
192
|
+
|
|
193
|
+
# [agents.firebase]
|
|
194
|
+
# enabled = true
|
|
195
|
+
# output_path = ".idx/airules.md"
|
|
192
196
|
`;
|
|
193
197
|
if (!(await exists(instructionsPath))) {
|
|
194
198
|
await fs_1.promises.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
|
|
@@ -39,7 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
39
|
exports.loadConfig = loadConfig;
|
|
40
40
|
const fs_1 = require("fs");
|
|
41
41
|
const path = __importStar(require("path"));
|
|
42
|
-
const toml_1 = __importDefault(require("toml"));
|
|
42
|
+
const toml_1 = __importDefault(require("@iarna/toml"));
|
|
43
43
|
const zod_1 = require("zod");
|
|
44
44
|
const constants_1 = require("../constants");
|
|
45
45
|
const mcpConfigSchema = zod_1.z
|
package/dist/lib.js
CHANGED
|
@@ -47,9 +47,12 @@ const CursorAgent_1 = require("./agents/CursorAgent");
|
|
|
47
47
|
const WindsurfAgent_1 = require("./agents/WindsurfAgent");
|
|
48
48
|
const ClineAgent_1 = require("./agents/ClineAgent");
|
|
49
49
|
const AiderAgent_1 = require("./agents/AiderAgent");
|
|
50
|
+
const FirebaseAgent_1 = require("./agents/FirebaseAgent");
|
|
51
|
+
const OpenHandsAgent_1 = require("./agents/OpenHandsAgent");
|
|
50
52
|
const merge_1 = require("./mcp/merge");
|
|
51
53
|
const validate_1 = require("./mcp/validate");
|
|
52
54
|
const mcp_1 = require("./paths/mcp");
|
|
55
|
+
const propagateOpenHandsMcp_1 = require("./mcp/propagateOpenHandsMcp");
|
|
53
56
|
const constants_1 = require("./constants");
|
|
54
57
|
/**
|
|
55
58
|
* Gets all output paths for an agent, taking into account any config overrides.
|
|
@@ -93,6 +96,8 @@ const agents = [
|
|
|
93
96
|
new WindsurfAgent_1.WindsurfAgent(),
|
|
94
97
|
new ClineAgent_1.ClineAgent(),
|
|
95
98
|
new AiderAgent_1.AiderAgent(),
|
|
99
|
+
new FirebaseAgent_1.FirebaseAgent(),
|
|
100
|
+
new OpenHandsAgent_1.OpenHandsAgent(),
|
|
96
101
|
];
|
|
97
102
|
/**
|
|
98
103
|
* Applies ruler configurations for all supported AI agents.
|
|
@@ -190,27 +195,43 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
190
195
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
|
|
191
196
|
generatedPaths.push(...outputPaths);
|
|
192
197
|
if (dryRun) {
|
|
193
|
-
(0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`,
|
|
198
|
+
(0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`, verbose);
|
|
194
199
|
}
|
|
195
200
|
else {
|
|
196
201
|
await agent.applyRulerConfig(concatenated, projectRoot, agentConfig);
|
|
197
202
|
}
|
|
198
203
|
const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
199
|
-
const
|
|
204
|
+
const mcpEnabledForAgent = cliMcpEnabled &&
|
|
200
205
|
(agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
206
|
+
const rulerMcpFile = path.join(rulerDir, 'mcp.json');
|
|
207
|
+
if (dest && mcpEnabledForAgent) {
|
|
208
|
+
if (agent.getIdentifier() === 'openhands') {
|
|
209
|
+
// *** Special handling for Open Hands ***
|
|
210
|
+
if (dryRun) {
|
|
211
|
+
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
|
|
212
|
+
}
|
|
213
|
+
else {
|
|
214
|
+
await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(rulerMcpFile, dest);
|
|
215
|
+
}
|
|
216
|
+
// Include Open Hands config file in .gitignore
|
|
217
|
+
generatedPaths.push(dest);
|
|
209
218
|
}
|
|
210
219
|
else {
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
220
|
+
if (rulerMcpJson) {
|
|
221
|
+
const strategy = cliMcpStrategy ??
|
|
222
|
+
agentConfig?.mcp?.strategy ??
|
|
223
|
+
config.mcp?.strategy ??
|
|
224
|
+
'merge';
|
|
225
|
+
(0, constants_1.logVerbose)(`Applying MCP config for ${agent.getName()} with strategy: ${strategy}`, verbose);
|
|
226
|
+
if (dryRun) {
|
|
227
|
+
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, true);
|
|
228
|
+
}
|
|
229
|
+
else {
|
|
230
|
+
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
231
|
+
const merged = (0, merge_1.mergeMcp)(existing, rulerMcpJson, strategy);
|
|
232
|
+
await (0, mcp_1.writeNativeMcp)(dest, merged);
|
|
233
|
+
}
|
|
234
|
+
}
|
|
214
235
|
}
|
|
215
236
|
}
|
|
216
237
|
}
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.propagateMcpToOpenHands = propagateMcpToOpenHands;
|
|
40
|
+
const fs = __importStar(require("fs/promises"));
|
|
41
|
+
const toml_1 = __importDefault(require("@iarna/toml"));
|
|
42
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
43
|
+
const path = __importStar(require("path"));
|
|
44
|
+
async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
|
|
45
|
+
let rulerMcp;
|
|
46
|
+
try {
|
|
47
|
+
const rulerJsonContent = await fs.readFile(rulerMcpPath, 'utf8');
|
|
48
|
+
rulerMcp = JSON.parse(rulerJsonContent);
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
const rulerServers = rulerMcp.mcpServers || {};
|
|
54
|
+
let config = {};
|
|
55
|
+
try {
|
|
56
|
+
const tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
|
|
57
|
+
config = toml_1.default.parse(tomlContent);
|
|
58
|
+
}
|
|
59
|
+
catch {
|
|
60
|
+
// File doesn't exist, we'll create it.
|
|
61
|
+
}
|
|
62
|
+
if (!config.mcp) {
|
|
63
|
+
config.mcp = {};
|
|
64
|
+
}
|
|
65
|
+
if (!config.mcp.stdio_servers) {
|
|
66
|
+
config.mcp.stdio_servers = [];
|
|
67
|
+
}
|
|
68
|
+
const existingServers = new Map(config.mcp.stdio_servers.map((s) => [s.name, s]));
|
|
69
|
+
for (const [name, serverDef] of Object.entries(rulerServers)) {
|
|
70
|
+
const { command, args, env } = serverDef;
|
|
71
|
+
if (command) {
|
|
72
|
+
const newServer = { name, command };
|
|
73
|
+
if (args)
|
|
74
|
+
newServer.args = args;
|
|
75
|
+
if (env)
|
|
76
|
+
newServer.env = env;
|
|
77
|
+
existingServers.set(name, newServer);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
config.mcp.stdio_servers = Array.from(existingServers.values());
|
|
81
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
|
|
82
|
+
await fs.writeFile(openHandsConfigPath, toml_1.default.stringify(config));
|
|
83
|
+
}
|
package/dist/paths/mcp.js
CHANGED
|
@@ -67,6 +67,10 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
67
67
|
case 'Aider':
|
|
68
68
|
candidates.push(path.join(projectRoot, '.mcp.json'));
|
|
69
69
|
break;
|
|
70
|
+
case 'Open Hands':
|
|
71
|
+
// For Open Hands, we target the main config file, not a separate mcp.json
|
|
72
|
+
candidates.push(path.join(projectRoot, '.openhands', 'config.toml'));
|
|
73
|
+
break;
|
|
70
74
|
default:
|
|
71
75
|
return null;
|
|
72
76
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.3",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"scripts": {
|
|
@@ -43,6 +43,7 @@
|
|
|
43
43
|
"ruler": "dist/cli/index.js"
|
|
44
44
|
},
|
|
45
45
|
"devDependencies": {
|
|
46
|
+
"@types/iarna__toml": "^2.0.5",
|
|
46
47
|
"@types/jest": "^29.5.14",
|
|
47
48
|
"@types/js-yaml": "^4.0.9",
|
|
48
49
|
"@types/node": "^22.15.24",
|
|
@@ -58,8 +59,8 @@
|
|
|
58
59
|
"typescript": "^5.8.3"
|
|
59
60
|
},
|
|
60
61
|
"dependencies": {
|
|
62
|
+
"@iarna/toml": "^2.2.5",
|
|
61
63
|
"js-yaml": "^4.1.0",
|
|
62
|
-
"toml": "^3.0.0",
|
|
63
64
|
"yargs": "^17.7.2",
|
|
64
65
|
"zod": "^3.25.28"
|
|
65
66
|
}
|