@intellectronica/ruler 0.2.19 → 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 -31
- 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 +2 -44
- 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 -330
- 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 -481
- 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,25 +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
|
-
| Amp | `
|
|
49
|
-
| Aider | `
|
|
65
|
+
| Amp | `AGENTS.md` | - |
|
|
66
|
+
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
|
|
50
67
|
| Firebase Studio | `.idx/airules.md` | - |
|
|
51
|
-
| Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml`
|
|
52
|
-
| Gemini CLI | `
|
|
68
|
+
| Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml` |
|
|
69
|
+
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
|
|
53
70
|
| Junie | `.junie/guidelines.md` | - |
|
|
54
71
|
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
|
|
55
72
|
| Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
|
|
56
|
-
|
|
|
73
|
+
| opencode | `AGENTS.md` | `opencode.json` |
|
|
57
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` | - |
|
|
58
79
|
|
|
59
80
|
## Getting Started
|
|
60
81
|
|
|
@@ -81,10 +102,10 @@ npx @intellectronica/ruler apply
|
|
|
81
102
|
1. Navigate to your project's root directory
|
|
82
103
|
2. Run `ruler init`
|
|
83
104
|
3. This creates:
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
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.)
|
|
88
109
|
|
|
89
110
|
Additionally, you can create a global configuration to use when no local `.ruler/` directory is found:
|
|
90
111
|
|
|
@@ -100,11 +121,18 @@ The global configuration will be created to `$XDG_CONFIG_HOME/ruler` (default: `
|
|
|
100
121
|
|
|
101
122
|
This is your central hub for all AI agent instructions:
|
|
102
123
|
|
|
103
|
-
- **
|
|
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
|
|
104
130
|
- **Concatenation Marker**: Each file's content is prepended with `--- Source: <relative_path_to_md_file> ---` for traceability
|
|
105
131
|
- **`ruler.toml`**: Master configuration for Ruler's behavior, agent selection, and output paths
|
|
106
132
|
- **`mcp.json`**: Shared MCP server settings
|
|
107
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
|
+
|
|
108
136
|
### Best Practices for Rule Files
|
|
109
137
|
|
|
110
138
|
**Granularity**: Break down complex instructions into focused `.md` files:
|
|
@@ -151,7 +179,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
151
179
|
| Option | Description |
|
|
152
180
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
153
181
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
154
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (amp, 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) |
|
|
155
183
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
156
184
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
157
185
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
@@ -181,6 +209,12 @@ ruler apply --agents copilot,claude
|
|
|
181
209
|
ruler apply --agents firebase
|
|
182
210
|
```
|
|
183
211
|
|
|
212
|
+
**Apply rules only to Warp:**
|
|
213
|
+
|
|
214
|
+
```bash
|
|
215
|
+
ruler apply --agents warp
|
|
216
|
+
```
|
|
217
|
+
|
|
184
218
|
**Use a specific configuration file:**
|
|
185
219
|
|
|
186
220
|
```bash
|
|
@@ -223,7 +257,7 @@ ruler revert [options]
|
|
|
223
257
|
| Option | Description |
|
|
224
258
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
225
259
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
226
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (amp, 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) |
|
|
227
261
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
228
262
|
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
229
263
|
| `--dry-run` | Preview changes without actually reverting files |
|
|
@@ -282,6 +316,21 @@ enabled = true
|
|
|
282
316
|
# Global merge strategy: 'merge' or 'overwrite' (default: 'merge')
|
|
283
317
|
merge_strategy = "merge"
|
|
284
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
|
+
|
|
285
334
|
# --- Global .gitignore Configuration ---
|
|
286
335
|
[gitignore]
|
|
287
336
|
# Enable/disable automatic .gitignore updates (default: true)
|
|
@@ -298,7 +347,7 @@ output_path = "CLAUDE.md"
|
|
|
298
347
|
|
|
299
348
|
[agents.aider]
|
|
300
349
|
enabled = true
|
|
301
|
-
output_path_instructions = "
|
|
350
|
+
output_path_instructions = "AGENTS.md"
|
|
302
351
|
output_path_config = ".aider.conf.yml"
|
|
303
352
|
|
|
304
353
|
# OpenAI Codex CLI agent and MCP config
|
|
@@ -338,6 +387,10 @@ enabled = false
|
|
|
338
387
|
[agents.kilocode]
|
|
339
388
|
enabled = true
|
|
340
389
|
output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
390
|
+
|
|
391
|
+
[agents.warp]
|
|
392
|
+
enabled = true
|
|
393
|
+
output_path = "WARP.md"
|
|
341
394
|
```
|
|
342
395
|
|
|
343
396
|
### Configuration Precedence
|
|
@@ -350,9 +403,36 @@ output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
|
350
403
|
|
|
351
404
|
MCP provides broader context to AI models through server configurations. Ruler can manage and distribute these settings across compatible agents.
|
|
352
405
|
|
|
353
|
-
###
|
|
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)
|
|
354
434
|
|
|
355
|
-
|
|
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`.
|
|
356
436
|
|
|
357
437
|
```json
|
|
358
438
|
{
|
|
@@ -373,8 +453,37 @@ Define your project's MCP servers:
|
|
|
373
453
|
}
|
|
374
454
|
```
|
|
375
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.
|
|
376
485
|
|
|
377
|
-
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.
|
|
378
487
|
|
|
379
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:
|
|
380
489
|
```bash
|
|
@@ -392,7 +501,7 @@ Ruler automatically manages your `.gitignore` file to keep generated agent confi
|
|
|
392
501
|
- Preserves existing content outside this block
|
|
393
502
|
- Sorts paths alphabetically and uses relative POSIX-style paths
|
|
394
503
|
|
|
395
|
-
### Example `.gitignore` Section
|
|
504
|
+
### Example `.gitignore` Section (sample - actual list depends on enabled agents)
|
|
396
505
|
|
|
397
506
|
```gitignore
|
|
398
507
|
# Your existing rules
|
|
@@ -407,7 +516,7 @@ node_modules/
|
|
|
407
516
|
.windsurf/rules/ruler_windsurf_instructions.md
|
|
408
517
|
AGENTS.md
|
|
409
518
|
CLAUDE.md
|
|
410
|
-
|
|
519
|
+
AGENTS.md
|
|
411
520
|
# END Ruler Generated Files
|
|
412
521
|
|
|
413
522
|
dist/
|
|
@@ -429,7 +538,7 @@ cd your-project
|
|
|
429
538
|
ruler init
|
|
430
539
|
|
|
431
540
|
# Edit the generated files
|
|
432
|
-
# - Add your coding guidelines to .ruler/
|
|
541
|
+
# - Add your coding guidelines to .ruler/AGENTS.md (or keep adding additional .md files)
|
|
433
542
|
# - Customize .ruler/ruler.toml if needed
|
|
434
543
|
|
|
435
544
|
# Apply rules to all AI agents
|
|
@@ -547,8 +656,20 @@ A: Ruler creates backups with `.bak` extension before overwriting any existing f
|
|
|
547
656
|
**Q: Can I run Ruler in CI/CD pipelines?**
|
|
548
657
|
A: Yes! Use `ruler apply --no-gitignore` in CI to avoid modifying `.gitignore`. See the GitHub Actions example above.
|
|
549
658
|
|
|
550
|
-
**Q: How do I migrate from
|
|
551
|
-
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`.
|
|
552
673
|
|
|
553
674
|
## Development
|
|
554
675
|
|
|
@@ -603,5 +724,43 @@ MIT
|
|
|
603
724
|
|
|
604
725
|
---
|
|
605
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
|
+
|
|
606
765
|
© Eleanor Berger
|
|
607
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;
|
package/dist/agents/AmpAgent.js
CHANGED
|
@@ -1,55 +1,13 @@
|
|
|
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
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.AmpAgent = void 0;
|
|
37
|
-
const
|
|
38
|
-
|
|
39
|
-
class AmpAgent {
|
|
4
|
+
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
5
|
+
class AmpAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
40
6
|
getIdentifier() {
|
|
41
7
|
return 'amp';
|
|
42
8
|
}
|
|
43
9
|
getName() {
|
|
44
10
|
return 'Amp';
|
|
45
11
|
}
|
|
46
|
-
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
|
|
47
|
-
const outputPath = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
48
|
-
const absolutePath = path.resolve(projectRoot, outputPath);
|
|
49
|
-
await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, concatenatedRules);
|
|
50
|
-
}
|
|
51
|
-
getDefaultOutputPath(projectRoot) {
|
|
52
|
-
return path.join(projectRoot, 'AGENT.md');
|
|
53
|
-
}
|
|
54
12
|
}
|
|
55
13
|
exports.AmpAgent = AmpAgent;
|