@intellectronica/ruler 0.3.0 → 0.3.1

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
@@ -38,12 +38,14 @@ Managing instructions across multiple AI coding tools becomes complex as your te
38
38
  - **Duplicated effort** maintaining multiple config files
39
39
  - **Context drift** as project requirements evolve
40
40
  - **Onboarding friction** for new AI tools
41
+ - **Complex project structures** requiring context-specific instructions for different components
41
42
 
42
- Ruler solves this by providing a **single source of truth** for all your AI agent instructions, automatically distributing them to the right configuration files.
43
+ Ruler solves this by providing a **single source of truth** for all your AI agent instructions, automatically distributing them to the right configuration files. With support for **nested rule loading**, Ruler can handle complex project structures with context-specific instructions for different components.
43
44
 
44
45
  ## Core Features
45
46
 
46
47
  - **Centralised Rule Management**: Store all AI instructions in a dedicated `.ruler/` directory using Markdown files
48
+ - **Nested Rule Loading**: Support complex project structures with multiple `.ruler/` directories for context-specific instructions
47
49
  - **Automatic Distribution**: Ruler applies these rules to configuration files of supported AI agents
48
50
  - **Targeted Agent Configuration**: Fine-tune which agents are affected and their specific output paths via `ruler.toml`
49
51
  - **MCP Server Propagation**: Manage and distribute Model Context Protocol (MCP) server settings
@@ -52,30 +54,30 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
52
54
 
53
55
  ## Supported AI Agents
54
56
 
55
- | Agent | Rules File(s) | MCP Configuration / Notes |
56
- | ---------------- | ------------------------------------------------ | --------------------------------------------------- |
57
- | AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
58
- | GitHub Copilot | `.github/copilot-instructions.md` | `.vscode/mcp.json` |
59
- | Claude Code | `CLAUDE.md` | `.mcp.json` |
60
- | OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
61
- | Jules | `AGENTS.md` | - |
62
- | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json` |
63
- | Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | - |
64
- | Cline | `.clinerules` | - |
65
- | Amp | `AGENTS.md` | - |
66
- | Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
67
- | Firebase Studio | `.idx/airules.md` | - |
68
- | Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml` |
69
- | Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
70
- | Junie | `.junie/guidelines.md` | - |
71
- | AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
72
- | Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
73
- | opencode | `AGENTS.md` | `opencode.json` |
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
+ | Agent | Rules File(s) | MCP Configuration / Notes |
58
+ | ---------------- | ------------------------------------------------ | ------------------------------------------------ |
59
+ | AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
60
+ | GitHub Copilot | `.github/copilot-instructions.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 | `.windsurf/rules/ruler_windsurf_instructions.md` | - |
66
+ | Cline | `.clinerules` | - |
67
+ | Amp | `AGENTS.md` | - |
68
+ | Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
69
+ | Firebase Studio | `.idx/airules.md` | - |
70
+ | Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml` |
71
+ | Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
72
+ | Junie | `.junie/guidelines.md` | - |
73
+ | AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
74
+ | Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
75
+ | opencode | `AGENTS.md` | `opencode.json` |
76
+ | Goose | `.goosehints` | - |
77
+ | Qwen Code | `AGENTS.md` | `.qwen/settings.json` |
78
+ | Zed | `AGENTS.md` | `.zed/settings.json` (project root, never $HOME) |
79
+ | Warp | `WARP.md` | - |
80
+ | Kiro | `.kiro/steering/ruler_kiro_instructions.md` | - |
79
81
 
80
82
  ## Getting Started
81
83
 
@@ -133,6 +135,39 @@ This is your central hub for all AI agent instructions:
133
135
 
134
136
  This ordering lets you keep a short, high-impact root `AGENTS.md` (e.g. executive project summary) while housing detailed guidance inside `.ruler/`.
135
137
 
138
+ ### Nested Rule Loading
139
+
140
+ Ruler now supports **nested rule loading** with the `--nested` flag, enabling context-specific instructions for different parts of your project:
141
+
142
+ ```
143
+ project/
144
+ ├── .ruler/ # Global project rules
145
+ │ ├── AGENTS.md
146
+ │ └── coding_style.md
147
+ ├── src/
148
+ │ └── .ruler/ # Component-specific rules
149
+ │ └── api_guidelines.md
150
+ ├── tests/
151
+ │ └── .ruler/ # Test-specific rules
152
+ │ └── testing_conventions.md
153
+ └── docs/
154
+ └── .ruler/ # Documentation rules
155
+ └── writing_style.md
156
+ ```
157
+
158
+ **How it works:**
159
+
160
+ - Discover all `.ruler/` directories in the project hierarchy
161
+ - Load and concatenate rules from each directory in order
162
+ - Enable with: `ruler apply --nested`
163
+
164
+ **Perfect for:**
165
+
166
+ - Monorepos with multiple services
167
+ - Projects with distinct components (frontend/backend)
168
+ - Teams needing different instructions for different areas
169
+ - Complex codebases with varying standards
170
+
136
171
  ### Best Practices for Rule Files
137
172
 
138
173
  **Granularity**: Break down complex instructions into focused `.md` files:
@@ -176,18 +211,18 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
176
211
 
177
212
  ### Options
178
213
 
179
- | Option | Description |
180
- | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
181
- | `--project-root <path>` | Path to your project's root (default: current directory) |
214
+ | Option | Description |
215
+ | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
216
+ | `--project-root <path>` | Path to your project's root (default: current directory) |
182
217
  | `--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) |
183
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
184
- | `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
185
- | `--no-mcp` | Disable applying MCP server configurations |
186
- | `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging |
187
- | `--gitignore` | Enable automatic .gitignore updates (default: true) |
188
- | `--no-gitignore` | Disable automatic .gitignore updates |
189
- | `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
190
- | `--verbose` / `-v` | Display detailed output during execution |
218
+ | `--config <path>` | Path to a custom `ruler.toml` configuration file |
219
+ | `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
220
+ | `--no-mcp` | Disable applying MCP server configurations |
221
+ | `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging |
222
+ | `--gitignore` | Enable automatic .gitignore updates (default: true) |
223
+ | `--no-gitignore` | Disable automatic .gitignore updates |
224
+ | `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
225
+ | `--verbose` / `-v` | Display detailed output during execution |
191
226
 
192
227
  ### Common Examples
193
228
 
@@ -254,15 +289,15 @@ ruler revert [options]
254
289
 
255
290
  ### Options
256
291
 
257
- | Option | Description |
258
- | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
259
- | `--project-root <path>` | Path to your project's root (default: current directory) |
292
+ | Option | Description |
293
+ | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
294
+ | `--project-root <path>` | Path to your project's root (default: current directory) |
260
295
  | `--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) |
261
- | `--config <path>` | Path to a custom `ruler.toml` configuration file |
262
- | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
263
- | `--dry-run` | Preview changes without actually reverting files |
264
- | `--verbose` / `-v` | Display detailed output during execution |
265
- | `--local-only` | Only search for local .ruler directories, ignore global config |
296
+ | `--config <path>` | Path to a custom `ruler.toml` configuration file |
297
+ | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
298
+ | `--dry-run` | Preview changes without actually reverting files |
299
+ | `--verbose` / `-v` | Display detailed output during execution |
300
+ | `--local-only` | Only search for local .ruler directories, ignore global config |
266
301
 
267
302
  ### Common Examples
268
303
 
@@ -322,7 +357,7 @@ command = "npx"
322
357
  args = ["-y", "@modelcontextprotocol/server-filesystem", "/path/to/project"]
323
358
 
324
359
  [mcp_servers.git]
325
- command = "npx"
360
+ command = "npx"
326
361
  args = ["-y", "@modelcontextprotocol/server-git", "--repository", "."]
327
362
 
328
363
  [mcp_servers.remote_api]
@@ -456,6 +491,7 @@ For backward compatibility, you can still use the JSON format; a warning is issu
456
491
  ### Configuration Precedence
457
492
 
458
493
  When both TOML and JSON configurations are present:
494
+
459
495
  1. **TOML servers take precedence** over JSON servers with the same name
460
496
  2. **Servers are merged** from both sources (unless using overwrite strategy)
461
497
  3. **Deprecation warning** is shown encouraging migration to TOML (warning shown once per run)
@@ -463,6 +499,7 @@ When both TOML and JSON configurations are present:
463
499
  ### Server Types
464
500
 
465
501
  **Local/stdio servers** require a `command` field:
502
+
466
503
  ```toml
467
504
  [mcp_servers.local_server]
468
505
  command = "node"
@@ -474,7 +511,7 @@ DEBUG = "1"
474
511
 
475
512
  **Remote servers** require a `url` field (headers optional; bearer Authorization token auto-extracted for OpenHands when possible):
476
513
  ```toml
477
- [mcp_servers.remote_server]
514
+ [mcp_servers.remote_server]
478
515
  url = "https://api.example.com"
479
516
 
480
517
  [mcp_servers.remote_server.headers]
@@ -486,6 +523,7 @@ Ruler uses this configuration with the `merge` (default) or `overwrite` strategy
486
523
  **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.
487
524
 
488
525
  **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:
526
+
489
527
  ```bash
490
528
  export CODEX_HOME="$(pwd)/.codex"
491
529
  ```
@@ -545,7 +583,26 @@ ruler init
545
583
  ruler apply
546
584
  ```
547
585
 
548
- ### Scenario 2: Team Standardization
586
+ ### Scenario 2: Complex Projects with Nested Rules
587
+
588
+ For large projects with multiple components or services, use nested rule loading:
589
+
590
+ ```bash
591
+ # Set up nested .ruler directories
592
+ mkdir -p src/.ruler tests/.ruler docs/.ruler
593
+
594
+ # Add component-specific instructions
595
+ echo "# API Design Guidelines" > src/.ruler/api_rules.md
596
+ echo "# Testing Best Practices" > tests/.ruler/test_rules.md
597
+ echo "# Documentation Standards" > docs/.ruler/docs_rules.md
598
+
599
+ # Apply with nested loading
600
+ ruler apply --nested --verbose
601
+ ```
602
+
603
+ This creates context-specific instructions for different parts of your project while maintaining global rules in the root `.ruler/` directory.
604
+
605
+ ### Scenario 3: Team Standardization
549
606
 
550
607
  1. Create `.ruler/coding_standards.md`, `.ruler/api_usage.md`
551
608
  2. Commit the `.ruler` directory to your repository
@@ -647,6 +704,9 @@ This shows:
647
704
  **Q: Can I use different rules for different agents?**
648
705
  A: Currently, all agents receive the same concatenated rules. For agent-specific instructions, include sections in your rule files like "## GitHub Copilot Specific" or "## Aider Configuration".
649
706
 
707
+ **Q: How do I set up different instructions for different parts of my project?**
708
+ A: Use the `--nested` flag with `ruler apply --nested`. This enables Ruler to discover and load rules from multiple `.ruler/` directories throughout your project hierarchy. Place component-specific instructions in `src/.ruler/`, test-specific rules in `tests/.ruler/`, etc., while keeping global rules in the root `.ruler/` directory.
709
+
650
710
  **Q: How do I temporarily disable Ruler for an agent?**
651
711
  A: Set `enabled = false` in `ruler.toml` under `[agents.agentname]`, or use `--agents` flag to specify only the agents you want.
652
712
 
@@ -58,6 +58,8 @@ class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
58
58
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
59
59
  const absolutePath = path.resolve(projectRoot, output);
60
60
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
61
+ // Add marker comment to the content to identify it as generated
62
+ const contentWithMarker = `<!-- Generated by Ruler -->\n${concatenatedRules}`;
61
63
  // Read existing content if present and skip write if identical
62
64
  let existing = null;
63
65
  try {
@@ -66,13 +68,13 @@ class AgentsMdAgent extends AbstractAgent_1.AbstractAgent {
66
68
  catch {
67
69
  existing = null;
68
70
  }
69
- if (existing !== null && existing === concatenatedRules) {
71
+ if (existing !== null && existing === contentWithMarker) {
70
72
  // No change; skip backup/write for idempotency
71
73
  return;
72
74
  }
73
75
  // Backup (only if file existed) then write new content
74
76
  await (0, FileSystemUtils_1.backupFile)(absolutePath);
75
- await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, concatenatedRules);
77
+ await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, contentWithMarker);
76
78
  }
77
79
  getMcpServerKey() {
78
80
  // No MCP configuration for this pseudo-agent
@@ -36,6 +36,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.WindsurfAgent = void 0;
37
37
  const path = __importStar(require("path"));
38
38
  const AbstractAgent_1 = require("./AbstractAgent");
39
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
39
40
  /**
40
41
  * Windsurf agent adapter.
41
42
  */
@@ -46,6 +47,17 @@ class WindsurfAgent extends AbstractAgent_1.AbstractAgent {
46
47
  getName() {
47
48
  return 'Windsurf';
48
49
  }
50
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
51
+ agentConfig) {
52
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
53
+ const absolutePath = path.resolve(projectRoot, output);
54
+ // Windsurf expects a YAML front-matter block with a `trigger` flag.
55
+ const frontMatter = ['---', 'trigger: always_on', '---', ''].join('\n');
56
+ const content = `${frontMatter}${concatenatedRules.trimStart()}`;
57
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(absolutePath));
58
+ await (0, FileSystemUtils_1.backupFile)(absolutePath);
59
+ await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, content);
60
+ }
49
61
  getDefaultOutputPath(projectRoot) {
50
62
  return path.join(projectRoot, '.windsurf', 'rules', 'ruler_windsurf_instructions.md');
51
63
  }
@@ -59,6 +59,11 @@ function run() {
59
59
  type: 'boolean',
60
60
  description: 'Only search for local .ruler directories, ignore global config',
61
61
  default: false,
62
+ })
63
+ .option('nested', {
64
+ type: 'boolean',
65
+ description: 'Enable nested rule loading from nested .ruler directories (default: disabled)',
66
+ default: false,
62
67
  });
63
68
  }, handlers_1.applyHandler)
64
69
  .command('init', 'Scaffold a .ruler directory with default files', (y) => {
@@ -58,6 +58,7 @@ async function applyHandler(argv) {
58
58
  const verbose = argv.verbose;
59
59
  const dryRun = argv['dry-run'];
60
60
  const localOnly = argv['local-only'];
61
+ const nested = argv.nested;
61
62
  // Determine gitignore preference: CLI > TOML > Default (enabled)
62
63
  // yargs handles --no-gitignore by setting gitignore to false
63
64
  let gitignorePreference;
@@ -68,7 +69,7 @@ async function applyHandler(argv) {
68
69
  gitignorePreference = undefined; // Let TOML/default decide
69
70
  }
70
71
  try {
71
- await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly);
72
+ await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested);
72
73
  console.log('Ruler apply completed successfully.');
73
74
  }
74
75
  catch (err) {
@@ -106,6 +107,10 @@ async function initHandler(argv) {
106
107
  # uncomment and populate the following line. If omitted, all agents are active.
107
108
  # default_agents = ["copilot", "claude"]
108
109
 
110
+ # Enable nested rule loading from nested .ruler directories
111
+ # When enabled, ruler will search for and process .ruler directories throughout the project hierarchy
112
+ # nested = false
113
+
109
114
  # --- Agent Specific Configurations ---
110
115
  # You can enable/disable agents and override their default output paths here.
111
116
  # Use lowercase agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
@@ -69,6 +69,7 @@ const rulerConfigSchema = zod_1.z.object({
69
69
  enabled: zod_1.z.boolean().optional(),
70
70
  })
71
71
  .optional(),
72
+ nested: zod_1.z.boolean().optional(),
72
73
  });
73
74
  /**
74
75
  * Loads and parses the ruler TOML configuration file, applying defaults.
@@ -174,11 +175,13 @@ async function loadConfig(options) {
174
175
  if (typeof rawGitignoreSection.enabled === 'boolean') {
175
176
  gitignoreConfig.enabled = rawGitignoreSection.enabled;
176
177
  }
178
+ const nested = typeof raw.nested === 'boolean' ? raw.nested : false;
177
179
  return {
178
180
  defaultAgents,
179
181
  agentConfigs,
180
182
  cliAgents,
181
183
  mcp: globalMcpConfig,
182
184
  gitignore: gitignoreConfig,
185
+ nested,
183
186
  };
184
187
  }
@@ -38,6 +38,8 @@ exports.readMarkdownFiles = readMarkdownFiles;
38
38
  exports.writeGeneratedFile = writeGeneratedFile;
39
39
  exports.backupFile = backupFile;
40
40
  exports.ensureDirExists = ensureDirExists;
41
+ exports.findGlobalRulerDir = findGlobalRulerDir;
42
+ exports.findAllRulerDirs = findAllRulerDirs;
41
43
  const fs_1 = require("fs");
42
44
  const path = __importStar(require("path"));
43
45
  const os = __importStar(require("os"));
@@ -147,8 +149,19 @@ async function readMarkdownFiles(rulerDir) {
147
149
  const stat = await fs_1.promises.stat(rootAgentsPath);
148
150
  if (stat.isFile()) {
149
151
  const content = await fs_1.promises.readFile(rootAgentsPath, 'utf8');
150
- // Prepend so it has highest precedence
151
- ordered = [{ path: rootAgentsPath, content }, ...ordered];
152
+ // Check if this is a generated file and we have other .ruler files
153
+ const isGenerated = content.startsWith('<!-- Generated by Ruler -->');
154
+ const hasRulerFiles = others.length > 0 || primaryFile !== null;
155
+ // Additional check: if AGENTS.md contains ruler source comments and we have ruler files,
156
+ // it's likely a corrupted generated file that should be skipped
157
+ const containsRulerSources = content.includes('<!-- Source: .ruler/') ||
158
+ content.includes('<!-- Source: ruler/');
159
+ const isProbablyGenerated = isGenerated || (containsRulerSources && hasRulerFiles);
160
+ // Skip generated AGENTS.md if we have other files in .ruler
161
+ if (!isProbablyGenerated || !hasRulerFiles) {
162
+ // Prepend so it has highest precedence
163
+ ordered = [{ path: rootAgentsPath, content }, ...ordered];
164
+ }
152
165
  }
153
166
  }
154
167
  }
@@ -182,3 +195,62 @@ async function backupFile(filePath) {
182
195
  async function ensureDirExists(dirPath) {
183
196
  await fs_1.promises.mkdir(dirPath, { recursive: true });
184
197
  }
198
+ /**
199
+ * Finds the global ruler configuration directory at XDG_CONFIG_HOME/ruler.
200
+ * Returns the path if it exists, null otherwise.
201
+ */
202
+ async function findGlobalRulerDir() {
203
+ const globalConfigDir = path.join(getXdgConfigDir(), 'ruler');
204
+ try {
205
+ const stat = await fs_1.promises.stat(globalConfigDir);
206
+ if (stat.isDirectory()) {
207
+ return globalConfigDir;
208
+ }
209
+ }
210
+ catch {
211
+ // ignore if global config doesn't exist
212
+ }
213
+ return null;
214
+ }
215
+ /**
216
+ * Searches the entire directory tree from startPath to find all .ruler directories.
217
+ * Returns an array of .ruler directory paths from most specific to least specific.
218
+ */
219
+ async function findAllRulerDirs(startPath) {
220
+ const rulerDirs = [];
221
+ // Search the entire directory tree downwards from startPath
222
+ async function findRulerDirs(dir) {
223
+ try {
224
+ const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
225
+ for (const entry of entries) {
226
+ const fullPath = path.join(dir, entry.name);
227
+ if (entry.isDirectory()) {
228
+ if (entry.name === '.ruler') {
229
+ rulerDirs.push(fullPath);
230
+ }
231
+ else {
232
+ // Recursively search subdirectories (but skip hidden directories like .git)
233
+ if (!entry.name.startsWith('.')) {
234
+ await findRulerDirs(fullPath);
235
+ }
236
+ }
237
+ }
238
+ }
239
+ }
240
+ catch {
241
+ // ignore errors when reading directories
242
+ }
243
+ }
244
+ // Start searching from the startPath
245
+ await findRulerDirs(startPath);
246
+ // Sort by depth (most specific first) - deeper paths come first
247
+ rulerDirs.sort((a, b) => {
248
+ const depthA = a.split(path.sep).length;
249
+ const depthB = b.split(path.sep).length;
250
+ if (depthA !== depthB) {
251
+ return depthB - depthA; // Deeper paths first
252
+ }
253
+ return a.localeCompare(b); // Alphabetical for same depth
254
+ });
255
+ return rulerDirs;
256
+ }
@@ -79,11 +79,18 @@ async function loadUnifiedConfig(options) {
79
79
  Array.isArray(tomlRaw.default_agents)) {
80
80
  defaultAgents = tomlRaw.default_agents.map((a) => String(a));
81
81
  }
82
+ let nested = false;
83
+ if (tomlRaw &&
84
+ typeof tomlRaw === 'object' &&
85
+ typeof tomlRaw.nested === 'boolean') {
86
+ nested = tomlRaw.nested;
87
+ }
82
88
  const toml = {
83
89
  raw: tomlRaw,
84
90
  schemaVersion: 1,
85
91
  agents: {},
86
92
  defaultAgents,
93
+ nested,
87
94
  };
88
95
  // Collect rule markdown files
89
96
  let ruleFiles = [];
@@ -33,8 +33,11 @@ var __importStar = (this && this.__importStar) || (function () {
33
33
  };
34
34
  })();
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
- exports.loadRulerConfiguration = loadRulerConfiguration;
36
+ exports.loadNestedConfigurations = loadNestedConfigurations;
37
+ exports.loadSingleConfiguration = loadSingleConfiguration;
37
38
  exports.selectAgentsToRun = selectAgentsToRun;
39
+ exports.processHierarchicalConfigurations = processHierarchicalConfigurations;
40
+ exports.processSingleConfiguration = processSingleConfiguration;
38
41
  exports.applyConfigurationsToAgents = applyConfigurationsToAgents;
39
42
  exports.updateGitignore = updateGitignore;
40
43
  const path = __importStar(require("path"));
@@ -49,27 +52,99 @@ const propagateOpenCodeMcp_1 = require("../mcp/propagateOpenCodeMcp");
49
52
  const agent_utils_1 = require("../agents/agent-utils");
50
53
  const capabilities_1 = require("../mcp/capabilities");
51
54
  const constants_1 = require("../constants");
55
+ async function loadNestedConfigurations(projectRoot, configPath, localOnly) {
56
+ const { dirs: rulerDirs } = await findRulerDirectories(projectRoot, localOnly, true);
57
+ const rootConfig = await (0, ConfigLoader_1.loadConfig)({
58
+ projectRoot,
59
+ configPath,
60
+ });
61
+ const results = [];
62
+ const rulerDirConfigs = await processIndependentRulerDirs(rulerDirs);
63
+ for (const { rulerDir, files } of rulerDirConfigs) {
64
+ results.push(await createHierarchicalConfiguration(rulerDir, files, rootConfig));
65
+ }
66
+ return results;
67
+ }
52
68
  /**
53
- * Loads all necessary configurations for ruler operation.
54
- * @param projectRoot Root directory of the project
55
- * @param configPath Optional custom config path
56
- * @param localOnly Whether to search only locally for .ruler directory
57
- * @returns Promise resolving to the loaded configuration
69
+ * Processes each .ruler directory independently, returning configuration for each.
70
+ * Each .ruler directory gets its own rules (not merged with others).
58
71
  */
59
- async function loadRulerConfiguration(projectRoot, configPath, localOnly) {
60
- // Find the .ruler directory
61
- const rulerDir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
62
- if (!rulerDir) {
63
- throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
72
+ async function processIndependentRulerDirs(rulerDirs) {
73
+ const results = [];
74
+ // Process each .ruler directory independently
75
+ for (const rulerDir of rulerDirs) {
76
+ const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
77
+ results.push({ rulerDir, files });
64
78
  }
79
+ return results;
80
+ }
81
+ async function createHierarchicalConfiguration(rulerDir, files, rootConfig) {
82
+ await warnAboutLegacyMcpJson(rulerDir);
83
+ const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(rulerDir));
84
+ return {
85
+ rulerDir,
86
+ config: rootConfig,
87
+ concatenatedRules,
88
+ rulerMcpJson: null, // No nested MCP support - each level uses root config only
89
+ };
90
+ }
91
+ /**
92
+ * Finds ruler directories based on the specified mode.
93
+ */
94
+ async function findRulerDirectories(projectRoot, localOnly, hierarchical) {
95
+ if (hierarchical) {
96
+ const dirs = await FileSystemUtils.findAllRulerDirs(projectRoot);
97
+ const allDirs = [...dirs];
98
+ // Add global config if not local-only
99
+ if (!localOnly) {
100
+ const globalDir = await FileSystemUtils.findGlobalRulerDir();
101
+ if (globalDir) {
102
+ allDirs.push(globalDir);
103
+ }
104
+ }
105
+ if (allDirs.length === 0) {
106
+ throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
107
+ }
108
+ return { dirs: allDirs, primaryDir: allDirs[0] };
109
+ }
110
+ else {
111
+ const dir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
112
+ if (!dir) {
113
+ throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
114
+ }
115
+ return { dirs: [dir], primaryDir: dir };
116
+ }
117
+ }
118
+ /**
119
+ * Warns about legacy mcp.json files if they exist.
120
+ */
121
+ async function warnAboutLegacyMcpJson(rulerDir) {
122
+ try {
123
+ const legacyMcpPath = path.join(rulerDir, 'mcp.json');
124
+ await (await Promise.resolve().then(() => __importStar(require('fs/promises')))).access(legacyMcpPath);
125
+ console.warn('[ruler] Warning: Using legacy .ruler/mcp.json. Please migrate to ruler.toml. This fallback will be removed in a future release.');
126
+ }
127
+ catch {
128
+ // ignore
129
+ }
130
+ }
131
+ /**
132
+ * Loads configuration for single-directory mode (existing behavior).
133
+ */
134
+ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
135
+ // Find the single ruler directory
136
+ const { dirs: rulerDirs, primaryDir } = await findRulerDirectories(projectRoot, localOnly, false);
137
+ // Warn about legacy mcp.json
138
+ await warnAboutLegacyMcpJson(primaryDir);
65
139
  // Load the ruler.toml configuration
66
140
  const config = await (0, ConfigLoader_1.loadConfig)({
67
141
  projectRoot,
68
142
  configPath,
69
143
  });
70
- // Read and concatenate the markdown rule files
71
- const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
72
- const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(rulerDir));
144
+ // Read rule files
145
+ const files = await FileSystemUtils.readMarkdownFiles(rulerDirs[0]);
146
+ // Concatenate rules
147
+ const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(primaryDir));
73
148
  // Load unified config to get merged MCP configuration
74
149
  const { loadUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('./UnifiedConfigLoader')));
75
150
  const unifiedConfig = await loadUnifiedConfig({ projectRoot, configPath });
@@ -133,7 +208,43 @@ function selectAgentsToRun(allAgents, config) {
133
208
  return selected;
134
209
  }
135
210
  /**
136
- * Applies configurations to the selected agents.
211
+ * Processes hierarchical configurations by applying rules to each .ruler directory independently.
212
+ * Each directory gets its own set of rules and generates its own agent files.
213
+ * @param agents Array of agents to process
214
+ * @param configurations Array of hierarchical configurations for each .ruler directory
215
+ * @param verbose Whether to enable verbose logging
216
+ * @param dryRun Whether to perform a dry run
217
+ * @param cliMcpEnabled Whether MCP is enabled via CLI
218
+ * @param cliMcpStrategy MCP strategy from CLI
219
+ * @returns Promise resolving to array of generated file paths
220
+ */
221
+ async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy) {
222
+ const allGeneratedPaths = [];
223
+ for (const config of configurations) {
224
+ console.log(`[ruler] Processing .ruler directory: ${config.rulerDir}`);
225
+ const rulerRoot = path.dirname(config.rulerDir);
226
+ const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
227
+ allGeneratedPaths.push(...paths);
228
+ }
229
+ return allGeneratedPaths;
230
+ }
231
+ /**
232
+ * Processes a single configuration by applying rules to all selected agents.
233
+ * All rules are concatenated and applied to generate agent files in the project root.
234
+ * @param agents Array of agents to process
235
+ * @param configuration Single ruler configuration with concatenated rules
236
+ * @param projectRoot Root directory of the project
237
+ * @param verbose Whether to enable verbose logging
238
+ * @param dryRun Whether to perform a dry run
239
+ * @param cliMcpEnabled Whether MCP is enabled via CLI
240
+ * @param cliMcpStrategy MCP strategy from CLI
241
+ * @returns Promise resolving to array of generated file paths
242
+ */
243
+ async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy) {
244
+ return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
245
+ }
246
+ /**
247
+ * Applies configurations to the selected agents (internal function).
137
248
  * @param agents Array of agents to process
138
249
  * @param concatenatedRules Concatenated rule content
139
250
  * @param rulerMcpJson MCP configuration JSON
@@ -196,72 +307,75 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
196
307
  }
197
308
  return generatedPaths;
198
309
  }
199
- /**
200
- * Handles MCP configuration for a specific agent.
201
- */
202
310
  async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy) {
203
- // Check if agent supports MCP at all
204
311
  if (!(0, capabilities_1.agentSupportsMcp)(agent)) {
205
312
  (0, constants_1.logVerbose)(`Agent ${agent.getName()} does not support MCP - skipping MCP configuration`, verbose);
206
313
  return;
207
314
  }
208
315
  const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
209
316
  const mcpEnabledForAgent = cliMcpEnabled && (agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
210
- if (dest && mcpEnabledForAgent && rulerMcpJson) {
211
- // Filter MCP configuration based on agent capabilities
212
- const filteredMcpJson = (0, capabilities_1.filterMcpConfigForAgent)(rulerMcpJson, agent);
213
- if (!filteredMcpJson) {
214
- (0, constants_1.logVerbose)(`No compatible MCP servers found for ${agent.getName()} - skipping MCP configuration`, verbose);
215
- return;
216
- }
217
- // Include MCP config file in .gitignore only if it's within the project directory
218
- if (dest.startsWith(projectRoot)) {
219
- const relativeDest = path.relative(projectRoot, dest);
220
- generatedPaths.push(relativeDest);
221
- // Also add the backup for the MCP file
222
- generatedPaths.push(`${relativeDest}.bak`);
223
- }
224
- // Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
225
- if (!dest.startsWith(projectRoot)) {
226
- (0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
227
- return;
228
- }
229
- if (agent.getIdentifier() === 'openhands') {
230
- // *** Special handling for Open Hands ***
231
- if (dryRun) {
232
- (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
233
- }
234
- else {
235
- await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest);
236
- }
237
- }
238
- else if (agent.getIdentifier() === 'opencode') {
239
- // *** Special handling for OpenCode ***
240
- if (dryRun) {
241
- (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
242
- }
243
- else {
244
- await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest);
245
- }
246
- }
247
- else {
248
- // Standard MCP handling using capabilities
249
- const strategy = cliMcpStrategy ??
250
- agentConfig?.mcp?.strategy ??
251
- config.mcp?.strategy ??
252
- 'merge';
253
- // Determine the correct server key for the agent
254
- const serverKey = agent.getMcpServerKey?.() || 'mcpServers';
255
- (0, constants_1.logVerbose)(`Applying filtered MCP config for ${agent.getName()} with strategy: ${strategy} and key: ${serverKey}`, verbose);
256
- if (dryRun) {
257
- (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
258
- }
259
- else {
260
- const existing = await (0, mcp_1.readNativeMcp)(dest);
261
- const merged = (0, merge_1.mergeMcp)(existing, filteredMcpJson, strategy, serverKey);
262
- await (0, mcp_1.writeNativeMcp)(dest, merged);
263
- }
264
- }
317
+ if (!dest || !mcpEnabledForAgent || !rulerMcpJson) {
318
+ return;
319
+ }
320
+ const filteredMcpJson = (0, capabilities_1.filterMcpConfigForAgent)(rulerMcpJson, agent);
321
+ if (!filteredMcpJson) {
322
+ (0, constants_1.logVerbose)(`No compatible MCP servers found for ${agent.getName()} - skipping MCP configuration`, verbose);
323
+ return;
324
+ }
325
+ await updateGitignoreForMcpFile(dest, projectRoot, generatedPaths);
326
+ await applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose);
327
+ }
328
+ async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths) {
329
+ if (dest.startsWith(projectRoot)) {
330
+ const relativeDest = path.relative(projectRoot, dest);
331
+ generatedPaths.push(relativeDest);
332
+ generatedPaths.push(`${relativeDest}.bak`);
333
+ }
334
+ }
335
+ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose) {
336
+ // Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
337
+ if (!dest.startsWith(projectRoot)) {
338
+ (0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
339
+ return;
340
+ }
341
+ if (agent.getIdentifier() === 'openhands') {
342
+ return await applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose);
343
+ }
344
+ if (agent.getIdentifier() === 'opencode') {
345
+ return await applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose);
346
+ }
347
+ return await applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose);
348
+ }
349
+ async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose) {
350
+ if (dryRun) {
351
+ (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
352
+ }
353
+ else {
354
+ await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest);
355
+ }
356
+ }
357
+ async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose) {
358
+ if (dryRun) {
359
+ (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
360
+ }
361
+ else {
362
+ await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest);
363
+ }
364
+ }
365
+ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose) {
366
+ const strategy = cliMcpStrategy ??
367
+ agentConfig?.mcp?.strategy ??
368
+ config.mcp?.strategy ??
369
+ 'merge';
370
+ const serverKey = agent.getMcpServerKey?.() ?? 'mcpServers';
371
+ (0, constants_1.logVerbose)(`Applying filtered MCP config for ${agent.getName()} with strategy: ${strategy} and key: ${serverKey}`, verbose);
372
+ if (dryRun) {
373
+ (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
374
+ }
375
+ else {
376
+ const existing = await (0, mcp_1.readNativeMcp)(dest);
377
+ const merged = (0, merge_1.mergeMcp)(existing, filteredMcpJson, strategy, serverKey);
378
+ await (0, mcp_1.writeNativeMcp)(dest, merged);
265
379
  }
266
380
  }
267
381
  /**
package/dist/lib.js CHANGED
@@ -17,24 +17,51 @@ const agents = agents_1.allAgents;
17
17
  * @param projectRoot Root directory of the project
18
18
  * @param includedAgents Optional list of agent name filters (case-insensitive substrings)
19
19
  */
20
- async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false) {
20
+ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false) {
21
21
  // Load configuration and rules
22
22
  (0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
23
23
  if (configPath) {
24
24
  (0, constants_1.logVerbose)(`Using custom config path: ${configPath}`, verbose);
25
25
  }
26
- const rulerConfiguration = await (0, apply_engine_1.loadRulerConfiguration)(projectRoot, configPath, localOnly);
27
- // Add CLI agents to the configuration
28
- rulerConfiguration.config.cliAgents = includedAgents;
29
- (0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(rulerConfiguration.config.agentConfigs).length} agent configs`, verbose);
30
- (0, constants_1.logVerbose)(`Found .ruler directory with ${rulerConfiguration.concatenatedRules.length} characters of rules`, verbose);
26
+ let selectedAgents;
27
+ let generatedPaths;
28
+ let loadedConfig;
29
+ if (nested) {
30
+ const hierarchicalConfigs = await (0, apply_engine_1.loadNestedConfigurations)(projectRoot, configPath, localOnly);
31
+ if (hierarchicalConfigs.length === 0) {
32
+ throw new Error('No .ruler directories found');
33
+ }
34
+ // Use the root config for agent selection (all levels share the same agent settings)
35
+ const rootConfig = hierarchicalConfigs[0].config;
36
+ loadedConfig = rootConfig;
37
+ rootConfig.cliAgents = includedAgents;
38
+ (0, constants_1.logVerbose)(`Loaded ${hierarchicalConfigs.length} .ruler directory configurations`, verbose);
39
+ (0, constants_1.logVerbose)(`Root configuration has ${Object.keys(rootConfig.agentConfigs).length} agent configs`, verbose);
40
+ normalizeAgentConfigs(rootConfig, agents);
41
+ selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, rootConfig);
42
+ (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
43
+ generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
44
+ }
45
+ else {
46
+ const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
47
+ loadedConfig = singleConfig.config;
48
+ singleConfig.config.cliAgents = includedAgents;
49
+ (0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(singleConfig.config.agentConfigs).length} agent configs`, verbose);
50
+ (0, constants_1.logVerbose)(`Found .ruler directory with ${singleConfig.concatenatedRules.length} characters of rules`, verbose);
51
+ normalizeAgentConfigs(singleConfig.config, agents);
52
+ selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, singleConfig.config);
53
+ (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
54
+ generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
55
+ }
56
+ await (0, apply_engine_1.updateGitignore)(projectRoot, generatedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
57
+ }
58
+ /**
59
+ * Normalizes per-agent config keys to agent identifiers for consistent lookup.
60
+ * Maps both exact identifier matches and substring matches with agent names.
61
+ * @param config The configuration object to normalize
62
+ * @param agents Array of available agents
63
+ */
64
+ function normalizeAgentConfigs(config, agents) {
31
65
  // Normalize per-agent config keys to agent identifiers (exact match or substring match)
32
- rulerConfiguration.config.agentConfigs = (0, config_utils_1.mapRawAgentConfigs)(rulerConfiguration.config.agentConfigs, agents);
33
- // Select agents to run
34
- const selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, rulerConfiguration.config);
35
- (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
36
- // Apply configurations to agents
37
- const generatedPaths = await (0, apply_engine_1.applyConfigurationsToAgents)(selectedAgents, rulerConfiguration.concatenatedRules, rulerConfiguration.rulerMcpJson, rulerConfiguration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
38
- // Update .gitignore
39
- await (0, apply_engine_1.updateGitignore)(projectRoot, generatedPaths, rulerConfiguration.config, cliGitignoreEnabled, dryRun);
66
+ config.agentConfigs = (0, config_utils_1.mapRawAgentConfigs)(config.agentConfigs, agents);
40
67
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.0",
3
+ "version": "0.3.1",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {