@intellectronica/ruler 0.3.11 → 0.3.13

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 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) | 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` | - |
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 | `AGENTS.md` | `.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
  ```
@@ -704,11 +712,8 @@ node_modules/
704
712
  # START Ruler Generated Files
705
713
  .aider.conf.yml
706
714
  .clinerules
707
- .cursor/rules/ruler_cursor_instructions.mdc
708
- .windsurf/rules/ruler_windsurf_instructions.md
709
715
  AGENTS.md
710
716
  CLAUDE.md
711
- AGENTS.md
712
717
  # END Ruler Generated Files
713
718
 
714
719
  dist/
@@ -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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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);
@@ -1,68 +1,28 @@
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.CursorAgent = void 0;
37
- const path = __importStar(require("path"));
38
- const AbstractAgent_1 = require("./AbstractAgent");
39
- const FileSystemUtils_1 = require("../core/FileSystemUtils");
4
+ const AgentsMdAgent_1 = require("./AgentsMdAgent");
40
5
  /**
41
6
  * Cursor agent adapter.
7
+ * Leverages the standardized AGENTS.md approach supported natively by Cursor.
8
+ * See: https://docs.cursor.com/en/cli/using
42
9
  */
43
- class CursorAgent extends AbstractAgent_1.AbstractAgent {
10
+ class CursorAgent extends AgentsMdAgent_1.AgentsMdAgent {
44
11
  getIdentifier() {
45
12
  return 'cursor';
46
13
  }
47
14
  getName() {
48
15
  return 'Cursor';
49
16
  }
50
- async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
51
- agentConfig, backup = true) {
52
- const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
53
- const absolutePath = path.resolve(projectRoot, output);
54
- // Cursor expects a YAML front-matter block with an `alwaysApply` flag.
55
- // See: https://docs.cursor.com/context/rules#rule-anatomy
56
- const frontMatter = ['---', 'alwaysApply: true', '---', ''].join('\n');
57
- const content = `${frontMatter}${concatenatedRules.trimStart()}`;
58
- await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
59
- if (backup) {
60
- await (0, FileSystemUtils_1.backupFile)(absolutePath);
61
- }
62
- await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, content);
17
+ async applyRulerConfig(concatenatedRules, projectRoot, _rulerMcpJson, agentConfig, backup = true) {
18
+ // Write AGENTS.md via base class
19
+ // Cursor natively reads AGENTS.md from the project root
20
+ await super.applyRulerConfig(concatenatedRules, projectRoot, null, {
21
+ outputPath: agentConfig?.outputPath,
22
+ }, backup);
63
23
  }
64
- getDefaultOutputPath(projectRoot) {
65
- return path.join(projectRoot, '.cursor', 'rules', 'ruler_cursor_instructions.mdc');
24
+ getMcpServerKey() {
25
+ return 'mcpServers';
66
26
  }
67
27
  supportsMcpStdio() {
68
28
  return true;
@@ -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, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
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
- raw = text.trim() ? (0, toml_1.parse)(text) : {};
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 Claude Code compatibility
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.11",
3
+ "version": "0.3.13",
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": "^22.15.24",
51
- "@types/yargs": "^17.0.33",
52
- "@typescript-eslint/eslint-plugin": "^8.32.1",
53
- "@typescript-eslint/parser": "^8.32.1",
54
- "eslint": "^8.57.1",
55
- "eslint-config-prettier": "^10.1.5",
56
- "eslint-plugin-prettier": "^5.4.0",
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.5.3",
59
- "ts-jest": "^29.3.4",
60
- "typescript": "^5.8.3"
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": "^17.7.2",
66
- "zod": "^3.25.28"
69
+ "yargs": "^18.0.0",
70
+ "zod": "^4.1.12"
67
71
  }
68
72
  }