@intellectronica/ruler 0.2.18 → 0.3.0
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 +190 -30
- package/dist/agents/AbstractAgent.js +82 -0
- package/dist/agents/AgentsMdAgent.js +82 -0
- package/dist/agents/AiderAgent.js +29 -9
- package/dist/agents/AmpAgent.js +13 -0
- package/dist/agents/AugmentCodeAgent.js +10 -12
- package/dist/agents/ClaudeAgent.js +9 -9
- package/dist/agents/ClineAgent.js +3 -9
- package/dist/agents/CodexCliAgent.js +27 -21
- package/dist/agents/CopilotAgent.js +9 -10
- package/dist/agents/CrushAgent.js +6 -0
- package/dist/agents/CursorAgent.js +13 -5
- package/dist/agents/FirebaseAgent.js +2 -8
- package/dist/agents/GeminiCliAgent.js +31 -22
- package/dist/agents/GooseAgent.js +2 -10
- package/dist/agents/JulesAgent.js +3 -44
- package/dist/agents/JunieAgent.js +2 -9
- package/dist/agents/KiloCodeAgent.js +8 -9
- package/dist/agents/KiroAgent.js +50 -0
- package/dist/agents/OpenCodeAgent.js +50 -11
- package/dist/agents/OpenHandsAgent.js +8 -9
- package/dist/agents/QwenCodeAgent.js +83 -0
- package/dist/agents/WarpAgent.js +61 -0
- package/dist/agents/WindsurfAgent.js +9 -10
- package/dist/agents/ZedAgent.js +132 -0
- package/dist/agents/agent-utils.js +37 -0
- package/dist/agents/index.js +53 -0
- package/dist/cli/commands.js +48 -242
- package/dist/cli/handlers.js +176 -0
- package/dist/constants.js +9 -2
- package/dist/core/ConfigLoader.js +1 -1
- package/dist/core/FileSystemUtils.js +51 -4
- package/dist/core/RuleProcessor.js +15 -3
- package/dist/core/UnifiedConfigLoader.js +357 -0
- package/dist/core/UnifiedConfigTypes.js +2 -0
- package/dist/core/agent-selection.js +52 -0
- package/dist/core/apply-engine.js +302 -0
- package/dist/core/config-utils.js +30 -0
- package/dist/core/hash.js +24 -0
- package/dist/core/revert-engine.js +413 -0
- package/dist/lib.js +20 -312
- package/dist/mcp/capabilities.js +51 -0
- package/dist/mcp/propagateOpenCodeMcp.js +30 -31
- package/dist/mcp/propagateOpenHandsMcp.js +101 -17
- package/dist/paths/mcp.js +7 -3
- package/dist/revert.js +96 -479
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -1,11 +1,27 @@
|
|
|
1
1
|
# Ruler: Centralise Your AI Coding Assistant Instructions
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
3
|
+
<table style="width:100%">
|
|
4
|
+
<tr>
|
|
5
|
+
<td style="vertical-align: top;">
|
|
6
|
+
<p>
|
|
7
|
+
<a href="https://github.com/intellectronica/ruler/actions/workflows/ci.yml"><img src="https://github.com/intellectronica/ruler/actions/workflows/ci.yml/badge.svg" alt="CI"></a>
|
|
8
|
+
<a href="https://www.npmjs.com/package/@intellectronica/ruler"><img src="https://badge.fury.io/js/%40intellectronica%2Fruler.svg" alt="npm version"></a>
|
|
9
|
+
<img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT">
|
|
10
|
+
</p>
|
|
11
|
+
<ul>
|
|
12
|
+
<li><strong>GitHub</strong>: <a href="https://github.com/intellectronica/ruler">intellectronica/ruler</a></li>
|
|
13
|
+
<li><strong>NPM</strong>: <a href="https://www.npmjs.com/package/@intellectronica/ruler">@intellectronica/ruler</a></li>
|
|
14
|
+
</ul>
|
|
15
|
+
<hr />
|
|
16
|
+
<p>
|
|
17
|
+
<em>Animation by <a href="https://isaacflath.com/">Isaac Flath</a> of <strong><a href="https://elite-ai-assisted-coding.dev/">Elite AI-Assisted Coding</a></strong></em> ➡︎
|
|
18
|
+
</p>
|
|
19
|
+
</td>
|
|
20
|
+
<td style="vertical-align: top; width:33%;">
|
|
21
|
+
<img src="img/ruler-short.gif" alt="Ruler demo" style="width:300px; height:auto; display:block;" />
|
|
22
|
+
</td>
|
|
23
|
+
</tr>
|
|
24
|
+
</table>
|
|
9
25
|
|
|
10
26
|
---
|
|
11
27
|
|
|
@@ -36,24 +52,30 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
36
52
|
|
|
37
53
|
## Supported AI Agents
|
|
38
54
|
|
|
39
|
-
| Agent | Rules File(s) | MCP Configuration
|
|
55
|
+
| Agent | Rules File(s) | MCP Configuration / Notes |
|
|
40
56
|
| ---------------- | ------------------------------------------------ | --------------------------------------------------- |
|
|
57
|
+
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
|
|
41
58
|
| GitHub Copilot | `.github/copilot-instructions.md` | `.vscode/mcp.json` |
|
|
42
59
|
| Claude Code | `CLAUDE.md` | `.mcp.json` |
|
|
43
|
-
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml
|
|
60
|
+
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
|
|
44
61
|
| Jules | `AGENTS.md` | - |
|
|
45
|
-
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json
|
|
46
|
-
| Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` |
|
|
62
|
+
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
|
|
63
|
+
| Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | - |
|
|
47
64
|
| Cline | `.clinerules` | - |
|
|
48
|
-
|
|
|
65
|
+
| Amp | `AGENTS.md` | - |
|
|
66
|
+
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
|
|
49
67
|
| Firebase Studio | `.idx/airules.md` | - |
|
|
50
|
-
| Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml`
|
|
51
|
-
| Gemini CLI | `
|
|
68
|
+
| Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml` |
|
|
69
|
+
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
|
|
52
70
|
| Junie | `.junie/guidelines.md` | - |
|
|
53
71
|
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
|
|
54
72
|
| Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
|
|
55
|
-
|
|
|
73
|
+
| opencode | `AGENTS.md` | `opencode.json` |
|
|
56
74
|
| Goose | `.goosehints` | - |
|
|
75
|
+
| Qwen Code | `AGENTS.md` | `.qwen/settings.json` |
|
|
76
|
+
| Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) |
|
|
77
|
+
| Warp | `WARP.md` | - |
|
|
78
|
+
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
|
|
57
79
|
|
|
58
80
|
## Getting Started
|
|
59
81
|
|
|
@@ -80,10 +102,10 @@ npx @intellectronica/ruler apply
|
|
|
80
102
|
1. Navigate to your project's root directory
|
|
81
103
|
2. Run `ruler init`
|
|
82
104
|
3. This creates:
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
105
|
+
- `.ruler/` directory
|
|
106
|
+
- `.ruler/AGENTS.md`: The primary starter Markdown file for your rules
|
|
107
|
+
- `.ruler/ruler.toml`: The main configuration file for Ruler (now contains sample MCP server sections; legacy `.ruler/mcp.json` no longer scaffolded)
|
|
108
|
+
- (Optional legacy fallback) If you previously used `.ruler/instructions.md`, it is still respected when `AGENTS.md` is absent. (The prior runtime warning was removed.)
|
|
87
109
|
|
|
88
110
|
Additionally, you can create a global configuration to use when no local `.ruler/` directory is found:
|
|
89
111
|
|
|
@@ -99,11 +121,18 @@ The global configuration will be created to `$XDG_CONFIG_HOME/ruler` (default: `
|
|
|
99
121
|
|
|
100
122
|
This is your central hub for all AI agent instructions:
|
|
101
123
|
|
|
102
|
-
- **
|
|
124
|
+
- **Primary File Order & Precedence**:
|
|
125
|
+
1. A repository root `AGENTS.md` (outside `.ruler/`) if present (highest precedence, prepended)
|
|
126
|
+
2. `.ruler/AGENTS.md` (new default starter file)
|
|
127
|
+
3. Legacy `.ruler/instructions.md` (only if `.ruler/AGENTS.md` absent; no longer emits a deprecation warning)
|
|
128
|
+
4. Remaining discovered `.md` files under `.ruler/` (and subdirectories) in sorted order
|
|
129
|
+
- **Rule Files (`*.md`)**: Discovered recursively from `.ruler/` or `$XDG_CONFIG_HOME/ruler` and concatenated in the order above
|
|
103
130
|
- **Concatenation Marker**: Each file's content is prepended with `--- Source: <relative_path_to_md_file> ---` for traceability
|
|
104
131
|
- **`ruler.toml`**: Master configuration for Ruler's behavior, agent selection, and output paths
|
|
105
132
|
- **`mcp.json`**: Shared MCP server settings
|
|
106
133
|
|
|
134
|
+
This ordering lets you keep a short, high-impact root `AGENTS.md` (e.g. executive project summary) while housing detailed guidance inside `.ruler/`.
|
|
135
|
+
|
|
107
136
|
### Best Practices for Rule Files
|
|
108
137
|
|
|
109
138
|
**Granularity**: Break down complex instructions into focused `.md` files:
|
|
@@ -150,7 +179,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
150
179
|
| Option | Description |
|
|
151
180
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
152
181
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
153
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode) |
|
|
182
|
+
| `--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) |
|
|
154
183
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
155
184
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
156
185
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
@@ -180,6 +209,12 @@ ruler apply --agents copilot,claude
|
|
|
180
209
|
ruler apply --agents firebase
|
|
181
210
|
```
|
|
182
211
|
|
|
212
|
+
**Apply rules only to Warp:**
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
ruler apply --agents warp
|
|
216
|
+
```
|
|
217
|
+
|
|
183
218
|
**Use a specific configuration file:**
|
|
184
219
|
|
|
185
220
|
```bash
|
|
@@ -222,7 +257,7 @@ ruler revert [options]
|
|
|
222
257
|
| Option | Description |
|
|
223
258
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
224
259
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
225
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode) |
|
|
260
|
+
| `--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) |
|
|
226
261
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
227
262
|
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
228
263
|
| `--dry-run` | Preview changes without actually reverting files |
|
|
@@ -281,6 +316,21 @@ enabled = true
|
|
|
281
316
|
# Global merge strategy: 'merge' or 'overwrite' (default: 'merge')
|
|
282
317
|
merge_strategy = "merge"
|
|
283
318
|
|
|
319
|
+
# --- MCP Server Definitions ---
|
|
320
|
+
[mcp_servers.filesystem]
|
|
321
|
+
command = "npx"
|
|
322
|
+
args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
|
|
323
|
+
|
|
324
|
+
[mcp_servers.git]
|
|
325
|
+
command = "npx"
|
|
326
|
+
args = ["-y", "@modelcontextprotocol/server-git", "--repository", "."]
|
|
327
|
+
|
|
328
|
+
[mcp_servers.remote_api]
|
|
329
|
+
url = "https://api.example.com"
|
|
330
|
+
|
|
331
|
+
[mcp_servers.remote_api.headers]
|
|
332
|
+
Authorization = "Bearer your-token"
|
|
333
|
+
|
|
284
334
|
# --- Global .gitignore Configuration ---
|
|
285
335
|
[gitignore]
|
|
286
336
|
# Enable/disable automatic .gitignore updates (default: true)
|
|
@@ -297,7 +347,7 @@ output_path = "CLAUDE.md"
|
|
|
297
347
|
|
|
298
348
|
[agents.aider]
|
|
299
349
|
enabled = true
|
|
300
|
-
output_path_instructions = "
|
|
350
|
+
output_path_instructions = "AGENTS.md"
|
|
301
351
|
output_path_config = ".aider.conf.yml"
|
|
302
352
|
|
|
303
353
|
# OpenAI Codex CLI agent and MCP config
|
|
@@ -337,6 +387,10 @@ enabled = false
|
|
|
337
387
|
[agents.kilocode]
|
|
338
388
|
enabled = true
|
|
339
389
|
output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
390
|
+
|
|
391
|
+
[agents.warp]
|
|
392
|
+
enabled = true
|
|
393
|
+
output_path = "WARP.md"
|
|
340
394
|
```
|
|
341
395
|
|
|
342
396
|
### Configuration Precedence
|
|
@@ -349,9 +403,36 @@ output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
|
349
403
|
|
|
350
404
|
MCP provides broader context to AI models through server configurations. Ruler can manage and distribute these settings across compatible agents.
|
|
351
405
|
|
|
352
|
-
###
|
|
406
|
+
### TOML Configuration (Recommended)
|
|
407
|
+
|
|
408
|
+
You can now define MCP servers directly in `ruler.toml` using the `[mcp_servers.<name>]` syntax:
|
|
409
|
+
|
|
410
|
+
```toml
|
|
411
|
+
# Global MCP behavior
|
|
412
|
+
[mcp]
|
|
413
|
+
enabled = true
|
|
414
|
+
merge_strategy = "merge" # or "overwrite"
|
|
415
|
+
|
|
416
|
+
# Local (stdio) server
|
|
417
|
+
[mcp_servers.filesystem]
|
|
418
|
+
command = "npx"
|
|
419
|
+
args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
|
|
420
|
+
|
|
421
|
+
[mcp_servers.filesystem.env]
|
|
422
|
+
API_KEY = "your-api-key"
|
|
423
|
+
|
|
424
|
+
# Remote server
|
|
425
|
+
[mcp_servers.search]
|
|
426
|
+
url = "https://mcp.example.com"
|
|
427
|
+
|
|
428
|
+
[mcp_servers.search.headers]
|
|
429
|
+
Authorization = "Bearer your-token"
|
|
430
|
+
"X-API-Version" = "v1"
|
|
431
|
+
```
|
|
432
|
+
|
|
433
|
+
### Legacy `.ruler/mcp.json` (Deprecated)
|
|
353
434
|
|
|
354
|
-
|
|
435
|
+
For backward compatibility, you can still use the JSON format; a warning is issued encouraging migration to TOML. The file is no longer created during `ruler init`.
|
|
355
436
|
|
|
356
437
|
```json
|
|
357
438
|
{
|
|
@@ -372,8 +453,37 @@ Define your project's MCP servers:
|
|
|
372
453
|
}
|
|
373
454
|
```
|
|
374
455
|
|
|
456
|
+
### Configuration Precedence
|
|
457
|
+
|
|
458
|
+
When both TOML and JSON configurations are present:
|
|
459
|
+
1. **TOML servers take precedence** over JSON servers with the same name
|
|
460
|
+
2. **Servers are merged** from both sources (unless using overwrite strategy)
|
|
461
|
+
3. **Deprecation warning** is shown encouraging migration to TOML (warning shown once per run)
|
|
462
|
+
|
|
463
|
+
### Server Types
|
|
464
|
+
|
|
465
|
+
**Local/stdio servers** require a `command` field:
|
|
466
|
+
```toml
|
|
467
|
+
[mcp_servers.local_server]
|
|
468
|
+
command = "node"
|
|
469
|
+
args = ["server.js"]
|
|
470
|
+
|
|
471
|
+
[mcp_servers.local_server.env]
|
|
472
|
+
DEBUG = "1"
|
|
473
|
+
```
|
|
474
|
+
|
|
475
|
+
**Remote servers** require a `url` field (headers optional; bearer Authorization token auto-extracted for OpenHands when possible):
|
|
476
|
+
```toml
|
|
477
|
+
[mcp_servers.remote_server]
|
|
478
|
+
url = "https://api.example.com"
|
|
479
|
+
|
|
480
|
+
[mcp_servers.remote_server.headers]
|
|
481
|
+
Authorization = "Bearer token"
|
|
482
|
+
```
|
|
483
|
+
|
|
484
|
+
Ruler uses this configuration with the `merge` (default) or `overwrite` strategy, controlled by `ruler.toml` or CLI flags.
|
|
375
485
|
|
|
376
|
-
Ruler
|
|
486
|
+
**Home Directory Safety:** Ruler never writes MCP configuration files outside your project root. Any historical references to user home directories (e.g. `~/.codeium/windsurf/mcp_config.json` or `~/.zed/settings.json`) have been removed; only project-local paths are targeted.
|
|
377
487
|
|
|
378
488
|
**Note for OpenAI Codex CLI:** To apply the local Codex CLI MCP configuration, set the `CODEX_HOME` environment variable to your project’s `.codex` directory:
|
|
379
489
|
```bash
|
|
@@ -391,7 +501,7 @@ Ruler automatically manages your `.gitignore` file to keep generated agent confi
|
|
|
391
501
|
- Preserves existing content outside this block
|
|
392
502
|
- Sorts paths alphabetically and uses relative POSIX-style paths
|
|
393
503
|
|
|
394
|
-
### Example `.gitignore` Section
|
|
504
|
+
### Example `.gitignore` Section (sample - actual list depends on enabled agents)
|
|
395
505
|
|
|
396
506
|
```gitignore
|
|
397
507
|
# Your existing rules
|
|
@@ -406,7 +516,7 @@ node_modules/
|
|
|
406
516
|
.windsurf/rules/ruler_windsurf_instructions.md
|
|
407
517
|
AGENTS.md
|
|
408
518
|
CLAUDE.md
|
|
409
|
-
|
|
519
|
+
AGENTS.md
|
|
410
520
|
# END Ruler Generated Files
|
|
411
521
|
|
|
412
522
|
dist/
|
|
@@ -428,7 +538,7 @@ cd your-project
|
|
|
428
538
|
ruler init
|
|
429
539
|
|
|
430
540
|
# Edit the generated files
|
|
431
|
-
# - Add your coding guidelines to .ruler/
|
|
541
|
+
# - Add your coding guidelines to .ruler/AGENTS.md (or keep adding additional .md files)
|
|
432
542
|
# - Customize .ruler/ruler.toml if needed
|
|
433
543
|
|
|
434
544
|
# Apply rules to all AI agents
|
|
@@ -546,8 +656,20 @@ A: Ruler creates backups with `.bak` extension before overwriting any existing f
|
|
|
546
656
|
**Q: Can I run Ruler in CI/CD pipelines?**
|
|
547
657
|
A: Yes! Use `ruler apply --no-gitignore` in CI to avoid modifying `.gitignore`. See the GitHub Actions example above.
|
|
548
658
|
|
|
549
|
-
**Q: How do I migrate from
|
|
550
|
-
A:
|
|
659
|
+
**Q: How do I migrate from older versions using `instructions.md`?**
|
|
660
|
+
A: Simply rename `.ruler/instructions.md` to `.ruler/AGENTS.md` (recommended). If you keep the legacy file and omit `AGENTS.md`, Ruler will still use it (without emitting the old deprecation warning). Having both causes `AGENTS.md` to take precedence; the legacy file is still concatenated afterward.
|
|
661
|
+
|
|
662
|
+
**Q: How does OpenHands MCP propagation classify servers?**
|
|
663
|
+
A: Local stdio servers become `stdio_servers`. Remote URLs containing `/sse` are classified as `sse_servers`; others become `shttp_servers`. Bearer tokens in an `Authorization` header are extracted into `api_key` where possible.
|
|
664
|
+
|
|
665
|
+
**Q: Where is Zed configuration written now?**
|
|
666
|
+
A: Ruler writes a `settings.json` in the project root (not the user home dir) and transforms MCP server definitions to Zed's `context_servers` format including `source: "custom"`.
|
|
667
|
+
|
|
668
|
+
**Q: What changed about MCP initialization?**
|
|
669
|
+
A: `ruler init` now only adds example MCP server sections to `ruler.toml` instead of creating `.ruler/mcp.json`. The JSON file is still consumed if present, but TOML servers win on name conflicts.
|
|
670
|
+
|
|
671
|
+
**Q: Is Kiro supported?**
|
|
672
|
+
A: Yes. Kiro receives concatenated rules at `.kiro/steering/ruler_kiro_instructions.md`.
|
|
551
673
|
|
|
552
674
|
## Development
|
|
553
675
|
|
|
@@ -602,5 +724,43 @@ MIT
|
|
|
602
724
|
|
|
603
725
|
---
|
|
604
726
|
|
|
727
|
+
## Development and Testing
|
|
728
|
+
|
|
729
|
+
### Running Tests
|
|
730
|
+
|
|
731
|
+
The project includes comprehensive test coverage with unit, integration, and end-to-end tests:
|
|
732
|
+
|
|
733
|
+
```bash
|
|
734
|
+
# Run all tests
|
|
735
|
+
npm test
|
|
736
|
+
|
|
737
|
+
# Run with coverage
|
|
738
|
+
npm run test:coverage
|
|
739
|
+
|
|
740
|
+
# Run integration tests specifically
|
|
741
|
+
npm run test:integration
|
|
742
|
+
|
|
743
|
+
# Run tests in watch mode
|
|
744
|
+
npm run test:watch
|
|
745
|
+
```
|
|
746
|
+
|
|
747
|
+
### Integration Testing
|
|
748
|
+
|
|
749
|
+
The project includes comprehensive integration tests that validate the complete CLI workflow:
|
|
750
|
+
|
|
751
|
+
- **`npm run test:integration`**: Runs a full end-to-end integration test that:
|
|
752
|
+
1. Creates a temporary test directory
|
|
753
|
+
2. Runs `ruler init` to set up the initial structure
|
|
754
|
+
3. Creates custom `ruler.toml` with MCP servers (both stdio and remote)
|
|
755
|
+
4. Creates sample `AGENTS.md` and additional markdown files for concatenation
|
|
756
|
+
5. Runs `ruler apply` to generate all agent configuration files
|
|
757
|
+
6. Inspects and validates all generated files contain expected content
|
|
758
|
+
7. Outputs the content of all generated files for manual verification
|
|
759
|
+
8. Validates MCP server configurations were properly applied
|
|
760
|
+
|
|
761
|
+
This integration test ensures the entire CLI workflow functions correctly and can be used for manual testing or CI validation.
|
|
762
|
+
|
|
763
|
+
---
|
|
764
|
+
|
|
605
765
|
© Eleanor Berger
|
|
606
766
|
[ai.intellectronica.net](https://ai.intellectronica.net/)
|
|
@@ -0,0 +1,82 @@
|
|
|
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.AbstractAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
|
+
/**
|
|
40
|
+
* Abstract base class for agents that write to a single configuration file.
|
|
41
|
+
* Implements common logic for applying ruler configuration.
|
|
42
|
+
*/
|
|
43
|
+
class AbstractAgent {
|
|
44
|
+
/**
|
|
45
|
+
* Applies the concatenated ruler rules to the agent's configuration.
|
|
46
|
+
* This implementation handles the common pattern of:
|
|
47
|
+
* 1. Determining the output path
|
|
48
|
+
* 2. Ensuring the parent directory exists
|
|
49
|
+
* 3. Backing up the existing file
|
|
50
|
+
* 4. Writing the new content
|
|
51
|
+
*/
|
|
52
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
53
|
+
agentConfig) {
|
|
54
|
+
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
55
|
+
const absolutePath = path.resolve(projectRoot, output);
|
|
56
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
57
|
+
await (0, FileSystemUtils_1.backupFile)(absolutePath);
|
|
58
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, concatenatedRules);
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Returns the specific key to be used for the server object in MCP JSON.
|
|
62
|
+
* Defaults to 'mcpServers' if not overridden.
|
|
63
|
+
*/
|
|
64
|
+
getMcpServerKey() {
|
|
65
|
+
return 'mcpServers';
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Returns whether this agent supports MCP STDIO servers.
|
|
69
|
+
* Defaults to false if not overridden.
|
|
70
|
+
*/
|
|
71
|
+
supportsMcpStdio() {
|
|
72
|
+
return false;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Returns whether this agent supports MCP remote servers.
|
|
76
|
+
* Defaults to false if not overridden.
|
|
77
|
+
*/
|
|
78
|
+
supportsMcpRemote() {
|
|
79
|
+
return false;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.AbstractAgent = AbstractAgent;
|
|
@@ -0,0 +1,82 @@
|
|
|
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.AgentsMdAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const fs_1 = require("fs");
|
|
39
|
+
const AbstractAgent_1 = require("./AbstractAgent");
|
|
40
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
41
|
+
/**
|
|
42
|
+
* Pseudo-agent that ensures the concatenated rules are written to root-level `AGENTS.md`.
|
|
43
|
+
* Does not participate in MCP propagation. Idempotent: only writes (and creates a backup)
|
|
44
|
+
* when content differs from existing file.
|
|
45
|
+
*/
|
|
46
|
+
class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
|
|
47
|
+
getIdentifier() {
|
|
48
|
+
return 'agentsmd';
|
|
49
|
+
}
|
|
50
|
+
getName() {
|
|
51
|
+
return 'AgentsMd';
|
|
52
|
+
}
|
|
53
|
+
getDefaultOutputPath(projectRoot) {
|
|
54
|
+
return path.join(projectRoot, 'AGENTS.md');
|
|
55
|
+
}
|
|
56
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
57
|
+
agentConfig) {
|
|
58
|
+
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
59
|
+
const absolutePath = path.resolve(projectRoot, output);
|
|
60
|
+
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
61
|
+
// Read existing content if present and skip write if identical
|
|
62
|
+
let existing = null;
|
|
63
|
+
try {
|
|
64
|
+
existing = await fs_1.promises.readFile(absolutePath, 'utf8');
|
|
65
|
+
}
|
|
66
|
+
catch {
|
|
67
|
+
existing = null;
|
|
68
|
+
}
|
|
69
|
+
if (existing !== null && existing === concatenatedRules) {
|
|
70
|
+
// No change; skip backup/write for idempotency
|
|
71
|
+
return;
|
|
72
|
+
}
|
|
73
|
+
// Backup (only if file existed) then write new content
|
|
74
|
+
await (0, FileSystemUtils_1.backupFile)(absolutePath);
|
|
75
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, concatenatedRules);
|
|
76
|
+
}
|
|
77
|
+
getMcpServerKey() {
|
|
78
|
+
// No MCP configuration for this pseudo-agent
|
|
79
|
+
return '';
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
exports.AgentsMdAgent = AgentsMdAgent;
|
|
@@ -35,25 +35,32 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.AiderAgent = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
38
39
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
40
|
const fs = __importStar(require("fs/promises"));
|
|
40
41
|
const yaml = __importStar(require("js-yaml"));
|
|
41
42
|
/**
|
|
42
|
-
* Aider agent adapter
|
|
43
|
+
* Aider agent adapter that uses AGENTS.md for instructions and .aider.conf.yml for configuration.
|
|
43
44
|
*/
|
|
44
45
|
class AiderAgent {
|
|
46
|
+
constructor() {
|
|
47
|
+
this.agentsMdAgent = new AgentsMdAgent_1.AgentsMdAgent();
|
|
48
|
+
}
|
|
45
49
|
getIdentifier() {
|
|
46
50
|
return 'aider';
|
|
47
51
|
}
|
|
48
52
|
getName() {
|
|
49
53
|
return 'Aider';
|
|
50
54
|
}
|
|
51
|
-
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson,
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
55
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
|
|
56
|
+
// First perform idempotent AGENTS.md write via composed AgentsMdAgent
|
|
57
|
+
await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, {
|
|
58
|
+
// Preserve explicit outputPath precedence semantics if provided.
|
|
59
|
+
outputPath: agentConfig?.outputPath ||
|
|
60
|
+
agentConfig?.outputPathInstructions ||
|
|
61
|
+
undefined,
|
|
62
|
+
});
|
|
63
|
+
// Now handle .aider.conf.yml configuration
|
|
57
64
|
const cfgPath = agentConfig?.outputPathConfig ??
|
|
58
65
|
this.getDefaultOutputPath(projectRoot).config;
|
|
59
66
|
let doc = {};
|
|
@@ -69,7 +76,11 @@ class AiderAgent {
|
|
|
69
76
|
if (!Array.isArray(doc.read)) {
|
|
70
77
|
doc.read = [];
|
|
71
78
|
}
|
|
72
|
-
|
|
79
|
+
// Determine the actual agents file path (AGENTS.md by default, or custom path)
|
|
80
|
+
const agentsPath = agentConfig?.outputPath ||
|
|
81
|
+
agentConfig?.outputPathInstructions ||
|
|
82
|
+
this.getDefaultOutputPath(projectRoot).instructions;
|
|
83
|
+
const name = path.basename(agentsPath);
|
|
73
84
|
if (!doc.read.includes(name)) {
|
|
74
85
|
doc.read.push(name);
|
|
75
86
|
}
|
|
@@ -78,9 +89,18 @@ class AiderAgent {
|
|
|
78
89
|
}
|
|
79
90
|
getDefaultOutputPath(projectRoot) {
|
|
80
91
|
return {
|
|
81
|
-
instructions: path.join(projectRoot, '
|
|
92
|
+
instructions: path.join(projectRoot, 'AGENTS.md'),
|
|
82
93
|
config: path.join(projectRoot, '.aider.conf.yml'),
|
|
83
94
|
};
|
|
84
95
|
}
|
|
96
|
+
getMcpServerKey() {
|
|
97
|
+
return this.agentsMdAgent.getMcpServerKey();
|
|
98
|
+
}
|
|
99
|
+
supportsMcpStdio() {
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
supportsMcpRemote() {
|
|
103
|
+
return true;
|
|
104
|
+
}
|
|
85
105
|
}
|
|
86
106
|
exports.AiderAgent = AiderAgent;
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.AmpAgent = void 0;
|
|
4
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
5
|
+
class AmpAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
6
|
+
getIdentifier() {
|
|
7
|
+
return 'amp';
|
|
8
|
+
}
|
|
9
|
+
getName() {
|
|
10
|
+
return 'Amp';
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
exports.AmpAgent = AmpAgent;
|
|
@@ -36,7 +36,6 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
36
36
|
exports.AugmentCodeAgent = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
38
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
|
-
const settings_1 = require("../vscode/settings");
|
|
40
39
|
/**
|
|
41
40
|
* AugmentCode agent adapter.
|
|
42
41
|
* Generates ruler_augment_instructions.md configuration file and updates VSCode settings.json with MCP server configuration.
|
|
@@ -48,24 +47,23 @@ class AugmentCodeAgent {
|
|
|
48
47
|
getName() {
|
|
49
48
|
return 'AugmentCode';
|
|
50
49
|
}
|
|
51
|
-
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson,
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
|
|
51
|
+
agentConfig) {
|
|
52
52
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
53
53
|
await (0, FileSystemUtils_1.backupFile)(output);
|
|
54
54
|
await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
|
|
55
|
-
|
|
56
|
-
|
|
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);
|
|
62
|
-
}
|
|
55
|
+
// AugmentCode does not support MCP servers
|
|
56
|
+
// MCP configuration is ignored for this agent
|
|
63
57
|
}
|
|
64
58
|
getDefaultOutputPath(projectRoot) {
|
|
65
59
|
return path.join(projectRoot, '.augment', 'rules', 'ruler_augment_instructions.md');
|
|
66
60
|
}
|
|
67
|
-
|
|
68
|
-
|
|
61
|
+
// AugmentCode does not support MCP servers
|
|
62
|
+
supportsMcpStdio() {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
supportsMcpRemote() {
|
|
66
|
+
return false;
|
|
69
67
|
}
|
|
70
68
|
}
|
|
71
69
|
exports.AugmentCodeAgent = AugmentCodeAgent;
|