@intellectronica/ruler 0.3.11 → 0.3.12
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 +19 -11
- package/dist/agents/AbstractAgent.js +1 -2
- package/dist/agents/AgentsMdAgent.js +1 -2
- package/dist/agents/AugmentCodeAgent.js +1 -2
- package/dist/agents/CursorAgent.js +1 -2
- package/dist/agents/QwenCodeAgent.js +1 -2
- package/dist/core/ConfigLoader.js +24 -1
- package/dist/core/apply-engine.js +33 -1
- package/package.json +17 -13
package/README.md
CHANGED
|
@@ -54,16 +54,16 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
54
54
|
|
|
55
55
|
## Supported AI Agents
|
|
56
56
|
|
|
57
|
-
| Agent | Rules File(s)
|
|
58
|
-
| ---------------- |
|
|
59
|
-
| AGENTS.md | `AGENTS.md`
|
|
60
|
-
| GitHub Copilot | `AGENTS.md`
|
|
61
|
-
| Claude Code | `CLAUDE.md`
|
|
62
|
-
| OpenAI Codex CLI | `AGENTS.md`
|
|
63
|
-
| Jules | `AGENTS.md`
|
|
64
|
-
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc`
|
|
65
|
-
| Windsurf | `AGENTS.md`
|
|
66
|
-
| Cline | `.clinerules`
|
|
57
|
+
| Agent | Rules File(s) | MCP Configuration / Notes |
|
|
58
|
+
| ---------------- | ------------------------------------------------ | ------------------------------------------------ |
|
|
59
|
+
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
|
|
60
|
+
| GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
|
|
61
|
+
| Claude Code | `CLAUDE.md` | `.mcp.json` |
|
|
62
|
+
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
|
|
63
|
+
| Jules | `AGENTS.md` | - |
|
|
64
|
+
| Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
|
|
65
|
+
| Windsurf | `AGENTS.md` | `.windsurf/mcp_config.json` |
|
|
66
|
+
| Cline | `.clinerules` | - |
|
|
67
67
|
| Crush | `CRUSH.md` | `.crush.json` |
|
|
68
68
|
| Amp | `AGENTS.md` | - |
|
|
69
69
|
| Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` |
|
|
@@ -82,7 +82,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
82
82
|
| Trae AI | `.trae/rules/project_rules.md` | - |
|
|
83
83
|
| Warp | `WARP.md` | - |
|
|
84
84
|
| Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
|
|
85
|
-
| Firebender | `firebender.json` |
|
|
85
|
+
| Firebender | `firebender.json` | `firebender.json` (rules and MCP in same file) |
|
|
86
86
|
|
|
87
87
|
## Getting Started
|
|
88
88
|
|
|
@@ -582,9 +582,11 @@ Skills can be organized flat or nested:
|
|
|
582
582
|
```
|
|
583
583
|
|
|
584
584
|
Each skill must contain:
|
|
585
|
+
|
|
585
586
|
- `SKILL.md` - Primary skill file with instructions or knowledge base
|
|
586
587
|
|
|
587
588
|
Skills can optionally include additional resources like:
|
|
589
|
+
|
|
588
590
|
- Markdown files with supplementary documentation
|
|
589
591
|
- Python, JavaScript, or other scripts
|
|
590
592
|
- Configuration files or data
|
|
@@ -594,6 +596,7 @@ Skills can optionally include additional resources like:
|
|
|
594
596
|
Skills support is **enabled by default** but can be controlled via:
|
|
595
597
|
|
|
596
598
|
**CLI flags:**
|
|
599
|
+
|
|
597
600
|
```bash
|
|
598
601
|
# Enable skills (default)
|
|
599
602
|
ruler apply --skills
|
|
@@ -603,6 +606,7 @@ ruler apply --no-skills
|
|
|
603
606
|
```
|
|
604
607
|
|
|
605
608
|
**Configuration in `ruler.toml`:**
|
|
609
|
+
|
|
606
610
|
```toml
|
|
607
611
|
[skills]
|
|
608
612
|
enabled = true # or false to disable
|
|
@@ -617,6 +621,7 @@ For agents that support MCP but don't have native skills support (all agents exc
|
|
|
617
621
|
3. Uses `uvx` to launch the server with the absolute path to `.skillz`
|
|
618
622
|
|
|
619
623
|
Example auto-generated MCP server configuration:
|
|
624
|
+
|
|
620
625
|
```toml
|
|
621
626
|
[mcp_servers.skillz]
|
|
622
627
|
command = "uvx"
|
|
@@ -626,6 +631,7 @@ args = ["skillz@latest", "/absolute/path/to/project/.skillz"]
|
|
|
626
631
|
### `.gitignore` Integration
|
|
627
632
|
|
|
628
633
|
When skills support is enabled and gitignore integration is active, Ruler automatically adds:
|
|
634
|
+
|
|
629
635
|
- `.claude/skills/` (for Claude Code agents)
|
|
630
636
|
- `.skillz/` (for MCP-based agents)
|
|
631
637
|
|
|
@@ -643,6 +649,7 @@ to your `.gitignore` file within the managed Ruler block.
|
|
|
643
649
|
### Validation
|
|
644
650
|
|
|
645
651
|
Ruler validates discovered skills and issues warnings for:
|
|
652
|
+
|
|
646
653
|
- Missing required file (`SKILL.md`)
|
|
647
654
|
- Invalid directory structures (directories without `SKILL.md` and no sub-skills)
|
|
648
655
|
|
|
@@ -651,6 +658,7 @@ Warnings don't prevent propagation but help identify potential issues.
|
|
|
651
658
|
### Dry-Run Mode
|
|
652
659
|
|
|
653
660
|
Test skills propagation without making changes:
|
|
661
|
+
|
|
654
662
|
```bash
|
|
655
663
|
ruler apply --dry-run
|
|
656
664
|
```
|
|
@@ -49,8 +49,7 @@ class AbstractAgent {
|
|
|
49
49
|
* 3. Backing up the existing file
|
|
50
50
|
* 4. Writing the new content
|
|
51
51
|
*/
|
|
52
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
53
|
-
agentConfig, backup = true) {
|
|
52
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
54
53
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
55
54
|
const absolutePath = path.resolve(projectRoot, output);
|
|
56
55
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
@@ -53,8 +53,7 @@ class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
53
53
|
getDefaultOutputPath(projectRoot) {
|
|
54
54
|
return path.join(projectRoot, 'AGENTS.md');
|
|
55
55
|
}
|
|
56
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
57
|
-
agentConfig, backup = true) {
|
|
56
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
58
57
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
59
58
|
const absolutePath = path.resolve(projectRoot, output);
|
|
60
59
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
|
|
@@ -47,8 +47,7 @@ class AugmentCodeAgent {
|
|
|
47
47
|
getName() {
|
|
48
48
|
return 'AugmentCode';
|
|
49
49
|
}
|
|
50
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
51
|
-
agentConfig, backup = true) {
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
52
51
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
53
52
|
if (backup) {
|
|
54
53
|
await (0, FileSystemUtils_1.backupFile)(output);
|
|
@@ -47,8 +47,7 @@ class CursorAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
47
47
|
getName() {
|
|
48
48
|
return 'Cursor';
|
|
49
49
|
}
|
|
50
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
51
|
-
agentConfig, backup = true) {
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
|
|
52
51
|
const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
|
|
53
52
|
const absolutePath = path.resolve(projectRoot, output);
|
|
54
53
|
// Cursor expects a YAML front-matter block with an `alwaysApply` flag.
|
|
@@ -44,8 +44,7 @@ class QwenCodeAgent extends AgentsMdAgent_1.AgentsMdAgent {
|
|
|
44
44
|
getName() {
|
|
45
45
|
return 'Qwen Code';
|
|
46
46
|
}
|
|
47
|
-
async applyRulerConfig(concatenatedRules, projectRoot,
|
|
48
|
-
agentConfig) {
|
|
47
|
+
async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig) {
|
|
49
48
|
// First, perform idempotent write of AGENTS.md via base class
|
|
50
49
|
await super.applyRulerConfig(concatenatedRules, projectRoot, null, {
|
|
51
50
|
outputPath: agentConfig?.outputPath,
|
|
@@ -76,6 +76,27 @@ const rulerConfigSchema = zod_1.z.object({
|
|
|
76
76
|
.optional(),
|
|
77
77
|
nested: zod_1.z.boolean().optional(),
|
|
78
78
|
});
|
|
79
|
+
/**
|
|
80
|
+
* Recursively creates a new object with only enumerable string keys,
|
|
81
|
+
* effectively excluding Symbol properties.
|
|
82
|
+
* The @iarna/toml parser adds Symbol properties (Symbol(type), Symbol(declared))
|
|
83
|
+
* for metadata, which Zod v4+ validates and rejects as invalid record keys.
|
|
84
|
+
* By rebuilding the object structure using Object.keys(), we create clean objects
|
|
85
|
+
* that only contain the actual data without Symbol metadata.
|
|
86
|
+
*/
|
|
87
|
+
function stripSymbols(obj) {
|
|
88
|
+
if (obj === null || typeof obj !== 'object') {
|
|
89
|
+
return obj;
|
|
90
|
+
}
|
|
91
|
+
if (Array.isArray(obj)) {
|
|
92
|
+
return obj.map(stripSymbols);
|
|
93
|
+
}
|
|
94
|
+
const result = {};
|
|
95
|
+
for (const key of Object.keys(obj)) {
|
|
96
|
+
result[key] = stripSymbols(obj[key]);
|
|
97
|
+
}
|
|
98
|
+
return result;
|
|
99
|
+
}
|
|
79
100
|
/**
|
|
80
101
|
* Loads and parses the ruler TOML configuration file, applying defaults.
|
|
81
102
|
* If the file is missing or invalid, returns empty/default config.
|
|
@@ -102,7 +123,9 @@ async function loadConfig(options) {
|
|
|
102
123
|
let raw = {};
|
|
103
124
|
try {
|
|
104
125
|
const text = await fs_1.promises.readFile(configFile, 'utf8');
|
|
105
|
-
|
|
126
|
+
const parsed = text.trim() ? (0, toml_1.parse)(text) : {};
|
|
127
|
+
// Strip Symbol properties added by @iarna/toml (required for Zod v4+)
|
|
128
|
+
raw = stripSymbols(parsed);
|
|
106
129
|
// Validate the configuration with zod
|
|
107
130
|
const validationResult = rulerConfigSchema.safeParse(raw);
|
|
108
131
|
if (!validationResult.success) {
|
|
@@ -500,6 +500,35 @@ function transformMcpForClaude(mcpJson) {
|
|
|
500
500
|
transformedMcp.mcpServers = transformedServers;
|
|
501
501
|
return transformedMcp;
|
|
502
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Transform MCP server types for Kilo Code compatibility.
|
|
505
|
+
* Kilo Code expects "streamable-http" for remote HTTP servers, not "remote".
|
|
506
|
+
*/
|
|
507
|
+
function transformMcpForKiloCode(mcpJson) {
|
|
508
|
+
if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
|
|
509
|
+
return mcpJson;
|
|
510
|
+
}
|
|
511
|
+
const transformedMcp = { ...mcpJson };
|
|
512
|
+
const transformedServers = {};
|
|
513
|
+
for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
|
|
514
|
+
if (serverDef && typeof serverDef === 'object') {
|
|
515
|
+
const server = serverDef;
|
|
516
|
+
const transformedServer = { ...server };
|
|
517
|
+
// Transform type: "remote" to "streamable-http" for HTTP-based servers
|
|
518
|
+
if (server.type === 'remote' &&
|
|
519
|
+
server.url &&
|
|
520
|
+
typeof server.url === 'string') {
|
|
521
|
+
transformedServer.type = 'streamable-http';
|
|
522
|
+
}
|
|
523
|
+
transformedServers[name] = transformedServer;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
transformedServers[name] = serverDef;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
transformedMcp.mcpServers = transformedServers;
|
|
530
|
+
return transformedMcp;
|
|
531
|
+
}
|
|
503
532
|
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
504
533
|
const strategy = cliMcpStrategy ??
|
|
505
534
|
agentConfig?.mcp?.strategy ??
|
|
@@ -516,11 +545,14 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
516
545
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
|
|
517
546
|
}
|
|
518
547
|
else {
|
|
519
|
-
// Transform MCP config for
|
|
548
|
+
// Transform MCP config for agent-specific compatibility
|
|
520
549
|
let mcpToMerge = filteredMcpJson;
|
|
521
550
|
if (agent.getIdentifier() === 'claude') {
|
|
522
551
|
mcpToMerge = transformMcpForClaude(filteredMcpJson);
|
|
523
552
|
}
|
|
553
|
+
else if (agent.getIdentifier() === 'kilocode') {
|
|
554
|
+
mcpToMerge = transformMcpForKiloCode(filteredMcpJson);
|
|
555
|
+
}
|
|
524
556
|
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
525
557
|
const merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
|
|
526
558
|
// Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"scripts": {
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"url": "https://github.com/intellectronica/ruler/issues"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://ai.intellectronica.net/ruler",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
38
41
|
"files": [
|
|
39
42
|
"dist",
|
|
40
43
|
"README.md",
|
|
@@ -47,22 +50,23 @@
|
|
|
47
50
|
"@types/iarna__toml": "^2.0.5",
|
|
48
51
|
"@types/jest": "^29.5.14",
|
|
49
52
|
"@types/js-yaml": "^4.0.9",
|
|
50
|
-
"@types/node": "^
|
|
51
|
-
"@types/yargs": "^17.0.
|
|
52
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
53
|
-
"@typescript-eslint/parser": "^8.
|
|
54
|
-
"eslint": "^
|
|
55
|
-
"eslint-config-prettier": "^10.1.
|
|
56
|
-
"eslint-plugin-prettier": "^5.4
|
|
53
|
+
"@types/node": "^24.9.2",
|
|
54
|
+
"@types/yargs": "^17.0.34",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
|
56
|
+
"@typescript-eslint/parser": "^8.46.2",
|
|
57
|
+
"eslint": "^9.38.0",
|
|
58
|
+
"eslint-config-prettier": "^10.1.8",
|
|
59
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
57
60
|
"jest": "^29.7.0",
|
|
58
|
-
"prettier": "^3.
|
|
59
|
-
"ts-jest": "^29.
|
|
60
|
-
"typescript": "^5.
|
|
61
|
+
"prettier": "^3.6.2",
|
|
62
|
+
"ts-jest": "^29.4.5",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"typescript-eslint": "^8.46.2"
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
63
67
|
"@iarna/toml": "^2.2.5",
|
|
64
68
|
"js-yaml": "^4.1.0",
|
|
65
|
-
"yargs": "^
|
|
66
|
-
"zod": "^
|
|
69
|
+
"yargs": "^18.0.0",
|
|
70
|
+
"zod": "^4.1.12"
|
|
67
71
|
}
|
|
68
72
|
}
|