@intellectronica/ruler 0.1.1 → 0.1.3

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
@@ -1,3 +1,7 @@
1
+ > **Experimental Research Preview**
2
+ > - Please test this version with caution in your own setup
3
+ > - File issues at https://github.com/intellectronica/ruler/issues
4
+
1
5
  # Ruler
2
6
 
3
7
  A CLI tool to manage custom rules and configs across different AI coding agents.
@@ -35,7 +39,14 @@ Create a `.ruler/` directory at your project root and add Markdown files definin
35
39
  Run the apply command:
36
40
 
37
41
  ```bash
38
- ruler apply [--project-root <path>] [--agents <agent1,agent2,...>]
42
+ ruler apply [--project-root <path>] [--agents <agent1,agent2,...>] [--config <path>] [--gitignore] [--no-gitignore]
43
+ ```
44
+
45
+
46
+ Run the init command to scaffold a basic `.ruler/` setup:
47
+
48
+ ```bash
49
+ ruler init [--project-root <path>]
39
50
  ```
40
51
 
41
52
  Use `--agents` to specify a comma-separated list of agent names (case-insensitive substrings) to limit which agents the rules are applied to.
@@ -52,6 +63,141 @@ The command will read all `.md` files under `.ruler/`, concatenate their content
52
63
  | Cline | `.clinerules` |
53
64
  | Aider | `ruler_aider_instructions.md` <br>and updates `.aider.conf.yml` |
54
65
 
66
+ ## Configuration
67
+
68
+ Ruler uses a TOML configuration file located at `.ruler/ruler.toml` by default. You can override its location with the `--config <path>` option in the `apply` command.
69
+
70
+ ### Configuration structure
71
+
72
+ ```toml
73
+ # Run only these agents by default (omit to use all agents)
74
+ # default_agents = ["GitHub Copilot", "Claude Code", "Aider"]
75
+
76
+ [agents.Copilot]
77
+ enabled = true
78
+ output_path = ".github/copilot-instructions.md"
79
+
80
+ [agents.Claude]
81
+ enabled = true
82
+ # output_path = "CLAUDE.md"
83
+
84
+ [agents.Aider]
85
+ enabled = false
86
+ # output_path_instructions = "ruler_aider_instructions.md"
87
+ # output_path_config = ".aider.conf.yml"
88
+ ```
89
+
90
+ - `default_agents`: array of agent names (case-insensitive substrings) to run by default.
91
+ - `[agents.<AgentName>]`: per-agent settings:
92
+ - `enabled` (boolean): enable or disable this agent.
93
+ - `output_path` (string): custom path for agents that produce a single file.
94
+ - `output_path_instructions`/`output_path_config`: custom paths for Aider's instruction and config files.
95
+
96
+ ### Precedence
97
+
98
+ 1. CLI `--agents` option (substring filters)
99
+ 2. Config file `default_agents` and `[agents]` overrides
100
+ 3. Built-in defaults (all agents enabled, standard output paths)
101
+
102
+ ## MCP servers
103
+
104
+ Ruler can propagate a project-level `.ruler/mcp.json` file to native MCP configurations of supported agents, merging (or overwriting) each agent’s existing MCP server settings.
105
+
106
+ ### `.ruler/mcp.json`
107
+
108
+ Place your MCP servers config in a file at `.ruler/mcp.json`:
109
+
110
+ ```json
111
+ {
112
+ "mcpServers": {
113
+ "example": {
114
+ "url": "https://mcp.example.com"
115
+ }
116
+ }
117
+ }
118
+ ```
119
+
120
+ ### CLI flags
121
+
122
+ | Flag | Effect |
123
+ |-------------------|--------------------------------------------------------------|
124
+ | `--with-mcp` | Enable writing MCP configs for all agents (default) |
125
+ | `--no-mcp` | Disable writing MCP configs |
126
+ | `--mcp-overwrite` | Overwrite native MCP configs instead of merging |
127
+
128
+ ### Configuration (`ruler.toml`)
129
+
130
+ Configure default behavior in your `ruler.toml`:
131
+
132
+ ```toml
133
+ [mcp]
134
+ enabled = true
135
+ merge_strategy = "merge" # or "overwrite"
136
+
137
+ [agents.Cursor.mcp]
138
+ enabled = false
139
+ merge_strategy = "overwrite"
140
+ ```
141
+
142
+ ## .gitignore Integration
143
+
144
+ Ruler automatically adds generated agent configuration files to your project's `.gitignore` file to prevent them from being committed to version control. This ensures that the AI agent configuration files remain local to each developer's environment.
145
+
146
+ ### Behavior
147
+
148
+ When `ruler apply` runs, it will:
149
+ - Create or update a `.gitignore` file in your project root
150
+ - Add all generated file paths to a managed block marked with `# START Ruler Generated Files` and `# END Ruler Generated Files`
151
+ - Preserve any existing `.gitignore` content outside the managed block
152
+ - Sort paths alphabetically within the Ruler block
153
+ - Use relative POSIX-style paths (forward slashes)
154
+
155
+ ### CLI flags
156
+
157
+ | Flag | Effect |
158
+ |-------------------|--------------------------------------------------------------|
159
+ | `--gitignore` | Enable automatic .gitignore updates (default) |
160
+ | `--no-gitignore` | Disable automatic .gitignore updates |
161
+
162
+ ### Configuration (`ruler.toml`)
163
+
164
+ Configure the default behavior in your `ruler.toml`:
165
+
166
+ ```toml
167
+ [gitignore]
168
+ enabled = true # or false to disable by default
169
+ ```
170
+
171
+ ### Precedence
172
+
173
+ The configuration precedence for .gitignore updates is:
174
+
175
+ 1. CLI flags (`--gitignore` or `--no-gitignore`)
176
+ 2. Configuration file `[gitignore].enabled` setting
177
+ 3. Default behavior (enabled)
178
+
179
+ ### Example
180
+
181
+ After running `ruler apply`, your `.gitignore` might look like:
182
+
183
+ ```gitignore
184
+ node_modules/
185
+ *.log
186
+
187
+ # START Ruler Generated Files
188
+ .aider.conf.yml
189
+ .clinerules
190
+ .cursor/rules/ruler_cursor_instructions.md
191
+ .github/copilot-instructions.md
192
+ .windsurf/rules/ruler_windsurf_instructions.md
193
+ AGENTS.md
194
+ CLAUDE.md
195
+ ruler_aider_instructions.md
196
+ # END Ruler Generated Files
197
+
198
+ dist/
199
+ ```
200
+
55
201
  ## Development
56
202
 
57
203
  Clone the repository and install dependencies:
@@ -82,13 +228,6 @@ End-to-end tests (run build before tests):
82
228
  npm run build && npm test
83
229
  ```
84
230
 
85
- ### Roadmap
86
- - [ ] Support for MCP servers config
87
- - [ ] Support for transforming and rewriting the rules using AI
88
- - [ ] Support "harmonisation" (reading existing rules of specific agents and combining them with the master config)
89
- - [ ] Support for additional agents
90
- - [ ] Support for agent-specific features (for example: apply rules in copilot)
91
-
92
231
  ## Contributing
93
232
 
94
233
  Contributions are welcome! Please open issues or pull requests on GitHub.
@@ -45,11 +45,13 @@ class AiderAgent {
45
45
  getName() {
46
46
  return 'Aider';
47
47
  }
48
- async applyRulerConfig(concatenatedRules, projectRoot) {
49
- const mdFile = path.join(projectRoot, 'ruler_aider_instructions.md');
50
- await (0, FileSystemUtils_1.backupFile)(mdFile);
51
- await (0, FileSystemUtils_1.writeGeneratedFile)(mdFile, concatenatedRules);
52
- const cfgPath = path.join(projectRoot, '.aider.conf.yml');
48
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ const mdPath = agentConfig?.outputPathInstructions ??
50
+ this.getDefaultOutputPath(projectRoot).instructions;
51
+ await (0, FileSystemUtils_1.backupFile)(mdPath);
52
+ await (0, FileSystemUtils_1.writeGeneratedFile)(mdPath, concatenatedRules);
53
+ const cfgPath = agentConfig?.outputPathConfig ??
54
+ this.getDefaultOutputPath(projectRoot).config;
53
55
  let doc = {};
54
56
  try {
55
57
  await fs.access(cfgPath);
@@ -63,11 +65,18 @@ class AiderAgent {
63
65
  if (!Array.isArray(doc.read)) {
64
66
  doc.read = [];
65
67
  }
66
- if (!doc.read.includes('ruler_aider_instructions.md')) {
67
- doc.read.push('ruler_aider_instructions.md');
68
+ const name = path.basename(mdPath);
69
+ if (!doc.read.includes(name)) {
70
+ doc.read.push(name);
68
71
  }
69
72
  const yamlStr = yaml.dump(doc);
70
73
  await (0, FileSystemUtils_1.writeGeneratedFile)(cfgPath, yamlStr);
71
74
  }
75
+ getDefaultOutputPath(projectRoot) {
76
+ return {
77
+ instructions: path.join(projectRoot, 'ruler_aider_instructions.md'),
78
+ config: path.join(projectRoot, '.aider.conf.yml'),
79
+ };
80
+ }
72
81
  }
73
82
  exports.AiderAgent = AiderAgent;
@@ -43,10 +43,13 @@ class ClaudeAgent {
43
43
  getName() {
44
44
  return 'Claude Code';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot) {
47
- const target = path.join(projectRoot, 'CLAUDE.md');
48
- await (0, FileSystemUtils_1.backupFile)(target);
49
- await (0, FileSystemUtils_1.writeGeneratedFile)(target, concatenatedRules);
46
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
47
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ await (0, FileSystemUtils_1.backupFile)(output);
49
+ await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
50
+ }
51
+ getDefaultOutputPath(projectRoot) {
52
+ return path.join(projectRoot, 'CLAUDE.md');
50
53
  }
51
54
  }
52
55
  exports.ClaudeAgent = ClaudeAgent;
@@ -43,10 +43,13 @@ class ClineAgent {
43
43
  getName() {
44
44
  return 'Cline';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot) {
47
- const target = path.join(projectRoot, '.clinerules');
48
- await (0, FileSystemUtils_1.backupFile)(target);
49
- await (0, FileSystemUtils_1.writeGeneratedFile)(target, concatenatedRules);
46
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
47
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ await (0, FileSystemUtils_1.backupFile)(output);
49
+ await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
50
+ }
51
+ getDefaultOutputPath(projectRoot) {
52
+ return path.join(projectRoot, '.clinerules');
50
53
  }
51
54
  }
52
55
  exports.ClineAgent = ClineAgent;
@@ -43,10 +43,13 @@ class CodexCliAgent {
43
43
  getName() {
44
44
  return 'OpenAI Codex CLI';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot) {
47
- const target = path.join(projectRoot, 'AGENTS.md');
48
- await (0, FileSystemUtils_1.backupFile)(target);
49
- await (0, FileSystemUtils_1.writeGeneratedFile)(target, concatenatedRules);
46
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
47
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ await (0, FileSystemUtils_1.backupFile)(output);
49
+ await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
50
+ }
51
+ getDefaultOutputPath(projectRoot) {
52
+ return path.join(projectRoot, 'AGENTS.md');
50
53
  }
51
54
  }
52
55
  exports.CodexCliAgent = CodexCliAgent;
@@ -43,12 +43,14 @@ class CopilotAgent {
43
43
  getName() {
44
44
  return 'GitHub Copilot';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot) {
47
- const targetDir = path.join(projectRoot, '.github');
48
- await (0, FileSystemUtils_1.ensureDirExists)(targetDir);
49
- const target = path.join(targetDir, 'copilot-instructions.md');
50
- await (0, FileSystemUtils_1.backupFile)(target);
51
- await (0, FileSystemUtils_1.writeGeneratedFile)(target, concatenatedRules);
46
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
47
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
49
+ await (0, FileSystemUtils_1.backupFile)(output);
50
+ await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
51
+ }
52
+ getDefaultOutputPath(projectRoot) {
53
+ return path.join(projectRoot, '.github', 'copilot-instructions.md');
52
54
  }
53
55
  }
54
56
  exports.CopilotAgent = CopilotAgent;
@@ -43,12 +43,14 @@ class CursorAgent {
43
43
  getName() {
44
44
  return 'Cursor';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot) {
47
- const targetDir = path.join(projectRoot, '.cursor', 'rules');
48
- await (0, FileSystemUtils_1.ensureDirExists)(targetDir);
49
- const target = path.join(targetDir, 'ruler_cursor_instructions.md');
50
- await (0, FileSystemUtils_1.backupFile)(target);
51
- await (0, FileSystemUtils_1.writeGeneratedFile)(target, concatenatedRules);
46
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
47
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
49
+ await (0, FileSystemUtils_1.backupFile)(output);
50
+ await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
51
+ }
52
+ getDefaultOutputPath(projectRoot) {
53
+ return path.join(projectRoot, '.cursor', 'rules', 'ruler_cursor_instructions.md');
52
54
  }
53
55
  }
54
56
  exports.CursorAgent = CursorAgent;
@@ -43,12 +43,14 @@ class WindsurfAgent {
43
43
  getName() {
44
44
  return 'Windsurf';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot) {
47
- const targetDir = path.join(projectRoot, '.windsurf', 'rules');
48
- await (0, FileSystemUtils_1.ensureDirExists)(targetDir);
49
- const target = path.join(targetDir, 'ruler_windsurf_instructions.md');
50
- await (0, FileSystemUtils_1.backupFile)(target);
51
- await (0, FileSystemUtils_1.writeGeneratedFile)(target, concatenatedRules);
46
+ async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
47
+ const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
49
+ await (0, FileSystemUtils_1.backupFile)(output);
50
+ await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
51
+ }
52
+ getDefaultOutputPath(projectRoot) {
53
+ return path.join(projectRoot, '.windsurf', 'rules', 'ruler_windsurf_instructions.md');
52
54
  }
53
55
  }
54
56
  exports.WindsurfAgent = WindsurfAgent;
@@ -1,4 +1,37 @@
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
+ })();
2
35
  var __importDefault = (this && this.__importDefault) || function (mod) {
3
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
37
  };
@@ -7,6 +40,8 @@ exports.run = run;
7
40
  const yargs_1 = __importDefault(require("yargs"));
8
41
  const helpers_1 = require("yargs/helpers");
9
42
  const lib_1 = require("../lib");
43
+ const path = __importStar(require("path"));
44
+ const fs_1 = require("fs");
10
45
  /**
11
46
  * Sets up and parses CLI commands.
12
47
  */
@@ -24,13 +59,46 @@ function run() {
24
59
  type: 'string',
25
60
  description: 'Comma-separated list of agent names to include (e.g. "copilot,claude")',
26
61
  });
62
+ y.option('config', {
63
+ type: 'string',
64
+ description: 'Path to TOML configuration file',
65
+ });
66
+ y.option('mcp', {
67
+ type: 'boolean',
68
+ description: 'Enable or disable applying MCP server config',
69
+ default: true,
70
+ });
71
+ y.alias('mcp', 'with-mcp');
72
+ y.option('mcp-overwrite', {
73
+ type: 'boolean',
74
+ description: 'Replace (not merge) the native MCP config(s)',
75
+ default: false,
76
+ });
77
+ y.option('gitignore', {
78
+ type: 'boolean',
79
+ description: 'Enable/disable automatic .gitignore updates (default: enabled)',
80
+ });
27
81
  }, async (argv) => {
28
82
  const projectRoot = argv['project-root'];
29
83
  const agents = argv.agents
30
84
  ? argv.agents.split(',').map((a) => a.trim())
31
85
  : undefined;
86
+ const configPath = argv.config;
87
+ const mcpEnabled = argv.mcp;
88
+ const mcpStrategy = argv['mcp-overwrite']
89
+ ? 'overwrite'
90
+ : undefined;
91
+ // Determine gitignore preference: CLI > TOML > Default (enabled)
92
+ // yargs handles --no-gitignore by setting gitignore to false
93
+ let gitignorePreference;
94
+ if (argv.gitignore !== undefined) {
95
+ gitignorePreference = argv.gitignore;
96
+ }
97
+ else {
98
+ gitignorePreference = undefined; // Let TOML/default decide
99
+ }
32
100
  try {
33
- await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents);
101
+ await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference);
34
102
  console.log('Ruler apply completed successfully.');
35
103
  }
36
104
  catch (err) {
@@ -38,6 +106,104 @@ function run() {
38
106
  console.error('Error applying ruler configurations:', message);
39
107
  process.exit(1);
40
108
  }
109
+ })
110
+ .command('init', 'Scaffold a .ruler directory with default files', (y) => {
111
+ y.option('project-root', {
112
+ type: 'string',
113
+ description: 'Project root directory',
114
+ default: process.cwd(),
115
+ });
116
+ }, async (argv) => {
117
+ const projectRoot = argv['project-root'];
118
+ const rulerDir = path.join(projectRoot, '.ruler');
119
+ await fs_1.promises.mkdir(rulerDir, { recursive: true });
120
+ const instructionsPath = path.join(rulerDir, 'instructions.md');
121
+ const tomlPath = path.join(rulerDir, 'ruler.toml');
122
+ const exists = async (p) => {
123
+ try {
124
+ await fs_1.promises.access(p);
125
+ return true;
126
+ }
127
+ catch {
128
+ return false;
129
+ }
130
+ };
131
+ const DEFAULT_INSTRUCTIONS = `# Ruler Instructions
132
+
133
+ These are your centralised AI agent instructions.
134
+ Add your coding guidelines, style guides, and other project-specific context here.
135
+
136
+ Ruler will concatenate all .md files in this directory (and its subdirectories)
137
+ and apply them to your configured AI coding agents.
138
+ `;
139
+ const DEFAULT_TOML = `# Ruler Configuration File
140
+ # See https://ai.intellectronica.net/ruler for documentation.
141
+
142
+ # To specify which agents are active by default when --agents is not used,
143
+ # uncomment and populate the following line. If omitted, all agents are active.
144
+ # default_agents = ["Copilot", "Claude"]
145
+
146
+ # --- Agent Specific Configurations ---
147
+ # You can enable/disable agents and override their default output paths here.
148
+
149
+ # [agents.GitHubCopilot]
150
+ # enabled = true
151
+ # output_path = ".github/copilot-instructions.md"
152
+
153
+ # [agents.ClaudeCode]
154
+ # enabled = true
155
+ # output_path = "CLAUDE.md"
156
+
157
+ # [agents.OpenAICodexCLI]
158
+ # enabled = true
159
+ # output_path = "AGENTS.md"
160
+
161
+ # [agents.Cursor]
162
+ # enabled = true
163
+ # output_path = ".cursor/rules/ruler_cursor_instructions.md"
164
+
165
+ # [agents.Windsurf]
166
+ # enabled = true
167
+ # output_path = ".windsurf/rules/ruler_windsurf_instructions.md"
168
+
169
+ # [agents.Cline]
170
+ # enabled = true
171
+ # output_path = ".clinerules"
172
+
173
+ # [agents.Aider]
174
+ # enabled = true
175
+ # output_path_instructions = "ruler_aider_instructions.md"
176
+ # output_path_config = ".aider.conf.yml"
177
+ `;
178
+ if (!(await exists(instructionsPath))) {
179
+ await fs_1.promises.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
180
+ console.log(`[ruler] Created ${instructionsPath}`);
181
+ }
182
+ else {
183
+ console.log(`[ruler] instructions.md already exists, skipping`);
184
+ }
185
+ if (!(await exists(tomlPath))) {
186
+ await fs_1.promises.writeFile(tomlPath, DEFAULT_TOML);
187
+ console.log(`[ruler] Created ${tomlPath}`);
188
+ }
189
+ else {
190
+ console.log(`[ruler] ruler.toml already exists, skipping`);
191
+ }
192
+ const mcpPath = path.join(rulerDir, 'mcp.json');
193
+ const DEFAULT_MCP_JSON = `{
194
+ "mcpServers": {
195
+ "example": {
196
+ "url": "https://mcp.example.com"
197
+ }
198
+ }
199
+ }`;
200
+ if (!(await exists(mcpPath))) {
201
+ await fs_1.promises.writeFile(mcpPath, DEFAULT_MCP_JSON);
202
+ console.log(`[ruler] Created ${mcpPath}`);
203
+ }
204
+ else {
205
+ console.log(`[ruler] mcp.json already exists, skipping`);
206
+ }
41
207
  })
42
208
  .demandCommand(1, 'You need to specify a command')
43
209
  .help()
@@ -0,0 +1,132 @@
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
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.loadConfig = loadConfig;
40
+ const fs_1 = require("fs");
41
+ const path = __importStar(require("path"));
42
+ const toml_1 = __importDefault(require("toml"));
43
+ /**
44
+ * Loads and parses the ruler TOML configuration file, applying defaults.
45
+ * If the file is missing or invalid, returns empty/default config.
46
+ */
47
+ async function loadConfig(options) {
48
+ const { projectRoot, configPath, cliAgents } = options;
49
+ const configFile = configPath
50
+ ? path.resolve(configPath)
51
+ : path.join(projectRoot, '.ruler', 'ruler.toml');
52
+ let raw = {};
53
+ try {
54
+ const text = await fs_1.promises.readFile(configFile, 'utf8');
55
+ raw = text.trim() ? toml_1.default.parse(text) : {};
56
+ }
57
+ catch (err) {
58
+ if (err instanceof Error && err.code !== 'ENOENT') {
59
+ console.warn(`[ruler] Warning: could not read config file at ${configFile}: ${err.message}`);
60
+ }
61
+ raw = {};
62
+ }
63
+ const defaultAgents = Array.isArray(raw.default_agents)
64
+ ? raw.default_agents.map((a) => String(a))
65
+ : undefined;
66
+ const agentsSection = raw.agents && typeof raw.agents === 'object' && !Array.isArray(raw.agents)
67
+ ? raw.agents
68
+ : {};
69
+ const agentConfigs = {};
70
+ for (const [name, section] of Object.entries(agentsSection)) {
71
+ if (section && typeof section === 'object') {
72
+ const sectionObj = section;
73
+ const cfg = {};
74
+ if (typeof sectionObj.enabled === 'boolean') {
75
+ cfg.enabled = sectionObj.enabled;
76
+ }
77
+ if (typeof sectionObj.output_path === 'string') {
78
+ cfg.outputPath = path.resolve(projectRoot, sectionObj.output_path);
79
+ }
80
+ if (typeof sectionObj.output_path_instructions === 'string') {
81
+ cfg.outputPathInstructions = path.resolve(projectRoot, sectionObj.output_path_instructions);
82
+ }
83
+ if (typeof sectionObj.output_path_config === 'string') {
84
+ cfg.outputPathConfig = path.resolve(projectRoot, sectionObj.output_path_config);
85
+ }
86
+ if (sectionObj.mcp && typeof sectionObj.mcp === 'object') {
87
+ const m = sectionObj.mcp;
88
+ const mcpCfg = {};
89
+ if (typeof m.enabled === 'boolean') {
90
+ mcpCfg.enabled = m.enabled;
91
+ }
92
+ if (typeof m.merge_strategy === 'string') {
93
+ const ms = m.merge_strategy;
94
+ if (ms === 'merge' || ms === 'overwrite') {
95
+ mcpCfg.strategy = ms;
96
+ }
97
+ }
98
+ cfg.mcp = mcpCfg;
99
+ }
100
+ agentConfigs[name] = cfg;
101
+ }
102
+ }
103
+ const rawMcpSection = raw.mcp && typeof raw.mcp === 'object' && !Array.isArray(raw.mcp)
104
+ ? raw.mcp
105
+ : {};
106
+ const globalMcpConfig = {};
107
+ if (typeof rawMcpSection.enabled === 'boolean') {
108
+ globalMcpConfig.enabled = rawMcpSection.enabled;
109
+ }
110
+ if (typeof rawMcpSection.merge_strategy === 'string') {
111
+ const strat = rawMcpSection.merge_strategy;
112
+ if (strat === 'merge' || strat === 'overwrite') {
113
+ globalMcpConfig.strategy = strat;
114
+ }
115
+ }
116
+ const rawGitignoreSection = raw.gitignore &&
117
+ typeof raw.gitignore === 'object' &&
118
+ !Array.isArray(raw.gitignore)
119
+ ? raw.gitignore
120
+ : {};
121
+ const gitignoreConfig = {};
122
+ if (typeof rawGitignoreSection.enabled === 'boolean') {
123
+ gitignoreConfig.enabled = rawGitignoreSection.enabled;
124
+ }
125
+ return {
126
+ defaultAgents,
127
+ agentConfigs,
128
+ cliAgents,
129
+ mcp: globalMcpConfig,
130
+ gitignore: gitignoreConfig,
131
+ };
132
+ }
@@ -0,0 +1,145 @@
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.updateGitignore = updateGitignore;
37
+ const fs_1 = require("fs");
38
+ const path = __importStar(require("path"));
39
+ const RULER_START_MARKER = '# START Ruler Generated Files';
40
+ const RULER_END_MARKER = '# END Ruler Generated Files';
41
+ /**
42
+ * Updates the .gitignore file in the project root with paths in a managed Ruler block.
43
+ * Creates the file if it doesn't exist, and creates or updates the Ruler-managed block.
44
+ *
45
+ * @param projectRoot The project root directory (where .gitignore should be located)
46
+ * @param paths Array of file paths to add to .gitignore (can be absolute or relative)
47
+ */
48
+ async function updateGitignore(projectRoot, paths) {
49
+ const gitignorePath = path.join(projectRoot, '.gitignore');
50
+ // Read existing .gitignore or start with empty content
51
+ let existingContent = '';
52
+ try {
53
+ existingContent = await fs_1.promises.readFile(gitignorePath, 'utf8');
54
+ }
55
+ catch (err) {
56
+ if (err.code !== 'ENOENT') {
57
+ throw err;
58
+ }
59
+ }
60
+ // Convert paths to relative POSIX format
61
+ const relativePaths = paths.map((p) => {
62
+ const relative = path.isAbsolute(p) ? path.relative(projectRoot, p) : p;
63
+ return relative.replace(/\\/g, '/'); // Convert to POSIX format
64
+ });
65
+ // Get all existing paths from .gitignore (excluding Ruler block)
66
+ const existingPaths = getExistingPathsExcludingRulerBlock(existingContent);
67
+ // Filter out paths that already exist outside the Ruler block
68
+ const newPaths = relativePaths.filter((p) => !existingPaths.includes(p));
69
+ // The Ruler block should contain only the new paths (replacement behavior)
70
+ const allRulerPaths = [...new Set(newPaths)].sort();
71
+ // Create new content
72
+ const newContent = updateGitignoreContent(existingContent, allRulerPaths);
73
+ // Write the updated content
74
+ await fs_1.promises.writeFile(gitignorePath, newContent);
75
+ }
76
+ /**
77
+ * Gets all paths from .gitignore content excluding those in the Ruler block.
78
+ */
79
+ function getExistingPathsExcludingRulerBlock(content) {
80
+ const lines = content.split('\n');
81
+ const paths = [];
82
+ let inRulerBlock = false;
83
+ for (const line of lines) {
84
+ const trimmed = line.trim();
85
+ if (trimmed === RULER_START_MARKER) {
86
+ inRulerBlock = true;
87
+ continue;
88
+ }
89
+ if (trimmed === RULER_END_MARKER) {
90
+ inRulerBlock = false;
91
+ continue;
92
+ }
93
+ if (!inRulerBlock && trimmed && !trimmed.startsWith('#')) {
94
+ paths.push(trimmed);
95
+ }
96
+ }
97
+ return paths;
98
+ }
99
+ /**
100
+ * Updates the .gitignore content by replacing or adding the Ruler block.
101
+ */
102
+ function updateGitignoreContent(existingContent, rulerPaths) {
103
+ const lines = existingContent.split('\n');
104
+ const newLines = [];
105
+ let inFirstRulerBlock = false;
106
+ let hasRulerBlock = false;
107
+ let processedFirstBlock = false;
108
+ for (const line of lines) {
109
+ const trimmed = line.trim();
110
+ if (trimmed === RULER_START_MARKER && !processedFirstBlock) {
111
+ inFirstRulerBlock = true;
112
+ hasRulerBlock = true;
113
+ newLines.push(line);
114
+ // Add the new Ruler paths
115
+ rulerPaths.forEach((p) => newLines.push(p));
116
+ continue;
117
+ }
118
+ if (trimmed === RULER_END_MARKER && inFirstRulerBlock) {
119
+ inFirstRulerBlock = false;
120
+ processedFirstBlock = true;
121
+ newLines.push(line);
122
+ continue;
123
+ }
124
+ if (!inFirstRulerBlock) {
125
+ newLines.push(line);
126
+ }
127
+ // Skip lines that are in the first Ruler block (they get replaced)
128
+ }
129
+ // If no Ruler block exists, add one at the end
130
+ if (!hasRulerBlock) {
131
+ // Add blank line if content exists and doesn't end with blank line
132
+ if (existingContent.trim() && !existingContent.endsWith('\n\n')) {
133
+ newLines.push('');
134
+ }
135
+ newLines.push(RULER_START_MARKER);
136
+ rulerPaths.forEach((p) => newLines.push(p));
137
+ newLines.push(RULER_END_MARKER);
138
+ }
139
+ // Ensure file ends with a newline
140
+ let result = newLines.join('\n');
141
+ if (!result.endsWith('\n')) {
142
+ result += '\n';
143
+ }
144
+ return result;
145
+ }
package/dist/lib.js CHANGED
@@ -35,8 +35,11 @@ var __importStar = (this && this.__importStar) || (function () {
35
35
  Object.defineProperty(exports, "__esModule", { value: true });
36
36
  exports.applyAllAgentConfigs = applyAllAgentConfigs;
37
37
  const path = __importStar(require("path"));
38
- const FileSystemUtils_1 = require("./core/FileSystemUtils");
38
+ const fs_1 = require("fs");
39
+ const FileSystemUtils = __importStar(require("./core/FileSystemUtils"));
39
40
  const RuleProcessor_1 = require("./core/RuleProcessor");
41
+ const ConfigLoader_1 = require("./core/ConfigLoader");
42
+ const GitignoreUtils_1 = require("./core/GitignoreUtils");
40
43
  const CopilotAgent_1 = require("./agents/CopilotAgent");
41
44
  const ClaudeAgent_1 = require("./agents/ClaudeAgent");
42
45
  const CodexCliAgent_1 = require("./agents/CodexCliAgent");
@@ -44,6 +47,43 @@ const CursorAgent_1 = require("./agents/CursorAgent");
44
47
  const WindsurfAgent_1 = require("./agents/WindsurfAgent");
45
48
  const ClineAgent_1 = require("./agents/ClineAgent");
46
49
  const AiderAgent_1 = require("./agents/AiderAgent");
50
+ const merge_1 = require("./mcp/merge");
51
+ const validate_1 = require("./mcp/validate");
52
+ const mcp_1 = require("./paths/mcp");
53
+ /**
54
+ * Gets all output paths for an agent, taking into account any config overrides.
55
+ */
56
+ function getAgentOutputPaths(agent, projectRoot, agentConfig) {
57
+ const paths = [];
58
+ const defaults = agent.getDefaultOutputPath(projectRoot);
59
+ if (typeof defaults === 'string') {
60
+ // Single output path (most agents)
61
+ const actualPath = agentConfig?.outputPath ?? defaults;
62
+ paths.push(actualPath);
63
+ }
64
+ else {
65
+ // Multiple output paths (e.g., AiderAgent)
66
+ const defaultPaths = defaults;
67
+ // Handle instructions path
68
+ if ('instructions' in defaultPaths) {
69
+ const instructionsPath = agentConfig?.outputPathInstructions ?? defaultPaths.instructions;
70
+ paths.push(instructionsPath);
71
+ }
72
+ // Handle config path
73
+ if ('config' in defaultPaths) {
74
+ const configPath = agentConfig?.outputPathConfig ?? defaultPaths.config;
75
+ paths.push(configPath);
76
+ }
77
+ // Handle any other paths in the default paths record
78
+ for (const [key, defaultPath] of Object.entries(defaultPaths)) {
79
+ if (key !== 'instructions' && key !== 'config') {
80
+ // For unknown path types, use the default since we don't have specific config overrides
81
+ paths.push(defaultPath);
82
+ }
83
+ }
84
+ }
85
+ return paths;
86
+ }
47
87
  const agents = [
48
88
  new CopilotAgent_1.CopilotAgent(),
49
89
  new ClaudeAgent_1.ClaudeAgent(),
@@ -62,21 +102,106 @@ const agents = [
62
102
  * @param projectRoot Root directory of the project
63
103
  * @param includedAgents Optional list of agent name filters (case-insensitive substrings)
64
104
  */
65
- async function applyAllAgentConfigs(projectRoot, includedAgents) {
66
- const rulerDir = await (0, FileSystemUtils_1.findRulerDir)(projectRoot);
105
+ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled) {
106
+ // Load configuration (default_agents, per-agent overrides, CLI filters)
107
+ const config = await (0, ConfigLoader_1.loadConfig)({
108
+ projectRoot,
109
+ cliAgents: includedAgents,
110
+ configPath,
111
+ });
112
+ // Normalize per-agent config keys to actual agent names (substring match)
113
+ const rawConfigs = config.agentConfigs;
114
+ const mappedConfigs = {};
115
+ for (const [key, cfg] of Object.entries(rawConfigs)) {
116
+ const lowerKey = key.toLowerCase();
117
+ for (const agent of agents) {
118
+ if (agent.getName().toLowerCase().includes(lowerKey)) {
119
+ mappedConfigs[agent.getName()] = cfg;
120
+ }
121
+ }
122
+ }
123
+ config.agentConfigs = mappedConfigs;
124
+ const rulerDir = await FileSystemUtils.findRulerDir(projectRoot);
67
125
  if (!rulerDir) {
68
126
  throw new Error(`.ruler directory not found from ${projectRoot}`);
69
127
  }
70
- await (0, FileSystemUtils_1.ensureDirExists)(path.join(rulerDir, 'generated'));
71
- const files = await (0, FileSystemUtils_1.readMarkdownFiles)(rulerDir);
128
+ await FileSystemUtils.ensureDirExists(path.join(rulerDir, 'generated'));
129
+ const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
72
130
  const concatenated = (0, RuleProcessor_1.concatenateRules)(files);
131
+ const mcpFile = path.join(rulerDir, 'mcp.json');
132
+ let rulerMcpJson = null;
133
+ try {
134
+ const raw = await fs_1.promises.readFile(mcpFile, 'utf8');
135
+ rulerMcpJson = JSON.parse(raw);
136
+ (0, validate_1.validateMcp)(rulerMcpJson);
137
+ }
138
+ catch (err) {
139
+ if (err.code !== 'ENOENT') {
140
+ throw err;
141
+ }
142
+ }
143
+ // Determine which agents to run:
144
+ // CLI --agents > config.default_agents > per-agent.enabled flags > default all
73
145
  let selected = agents;
74
- if (includedAgents && includedAgents.length > 0) {
75
- const filters = includedAgents.map((n) => n.toLowerCase());
146
+ if (config.cliAgents && config.cliAgents.length > 0) {
147
+ const filters = config.cliAgents.map((n) => n.toLowerCase());
76
148
  selected = agents.filter((agent) => filters.some((f) => agent.getName().toLowerCase().includes(f)));
77
149
  }
150
+ else if (config.defaultAgents && config.defaultAgents.length > 0) {
151
+ const defaults = config.defaultAgents.map((n) => n.toLowerCase());
152
+ selected = agents.filter((agent) => {
153
+ const key = agent.getName();
154
+ const override = config.agentConfigs[key]?.enabled;
155
+ if (override !== undefined) {
156
+ return override;
157
+ }
158
+ return defaults.includes(key.toLowerCase());
159
+ });
160
+ }
161
+ else {
162
+ selected = agents.filter((agent) => config.agentConfigs[agent.getName()]?.enabled !== false);
163
+ }
164
+ // Collect all generated file paths for .gitignore
165
+ const generatedPaths = [];
78
166
  for (const agent of selected) {
79
167
  console.log(`[ruler] Applying rules for ${agent.getName()}...`);
80
- await agent.applyRulerConfig(concatenated, projectRoot);
168
+ const agentConfig = config.agentConfigs[agent.getName()];
169
+ await agent.applyRulerConfig(concatenated, projectRoot, agentConfig);
170
+ // Collect output paths for .gitignore
171
+ const outputPaths = getAgentOutputPaths(agent, projectRoot, agentConfig);
172
+ generatedPaths.push(...outputPaths);
173
+ const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
174
+ const enabled = cliMcpEnabled &&
175
+ (agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
176
+ if (dest && rulerMcpJson != null && enabled) {
177
+ const strategy = cliMcpStrategy ??
178
+ agentConfig?.mcp?.strategy ??
179
+ config.mcp?.strategy ??
180
+ 'merge';
181
+ const existing = await (0, mcp_1.readNativeMcp)(dest);
182
+ const merged = (0, merge_1.mergeMcp)(existing, rulerMcpJson, strategy);
183
+ await (0, mcp_1.writeNativeMcp)(dest, merged);
184
+ }
185
+ }
186
+ // Handle .gitignore updates
187
+ // Configuration precedence: CLI > TOML > Default (enabled)
188
+ let gitignoreEnabled;
189
+ if (cliGitignoreEnabled !== undefined) {
190
+ gitignoreEnabled = cliGitignoreEnabled;
191
+ }
192
+ else if (config.gitignore?.enabled !== undefined) {
193
+ gitignoreEnabled = config.gitignore.enabled;
194
+ }
195
+ else {
196
+ gitignoreEnabled = true; // Default enabled
197
+ }
198
+ if (gitignoreEnabled && generatedPaths.length > 0) {
199
+ // Filter out .bak files as specified in requirements
200
+ const pathsToIgnore = generatedPaths.filter((p) => !p.endsWith('.bak'));
201
+ const uniquePaths = [...new Set(pathsToIgnore)];
202
+ if (uniquePaths.length > 0) {
203
+ await (0, GitignoreUtils_1.updateGitignore)(projectRoot, uniquePaths);
204
+ console.log(`[ruler] Updated .gitignore with ${uniquePaths.length} unique path(s) in the Ruler block.`);
205
+ }
81
206
  }
82
207
  }
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.mergeMcp = mergeMcp;
4
+ /**
5
+ * Merge native and incoming MCP server configurations according to strategy.
6
+ * @param base Existing native MCP config object.
7
+ * @param incoming Ruler MCP config object.
8
+ * @param strategy Merge strategy: 'merge' to union servers, 'overwrite' to replace.
9
+ * @returns Merged MCP config object.
10
+ */
11
+ function mergeMcp(base, incoming, strategy) {
12
+ if (strategy === 'overwrite') {
13
+ return incoming;
14
+ }
15
+ const baseServers = base.mcpServers || {};
16
+ const incomingServers = incoming.mcpServers || {};
17
+ return {
18
+ ...base,
19
+ mcpServers: { ...baseServers, ...incomingServers },
20
+ };
21
+ }
@@ -0,0 +1,17 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.validateMcp = validateMcp;
4
+ /**
5
+ * Validate the structure of the Ruler MCP JSON config.
6
+ * Minimal validation: ensure 'mcpServers' property exists and is an object.
7
+ * @param data Parsed JSON object from .ruler/mcp.json.
8
+ * @throws Error if validation fails.
9
+ */
10
+ function validateMcp(data) {
11
+ if (!data ||
12
+ typeof data !== 'object' ||
13
+ !('mcpServers' in data) ||
14
+ typeof data.mcpServers !== 'object') {
15
+ throw new Error('[ruler] Invalid .ruler/mcp.json: must contain an object property "mcpServers"');
16
+ }
17
+ }
@@ -0,0 +1,100 @@
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.getNativeMcpPath = getNativeMcpPath;
37
+ exports.readNativeMcp = readNativeMcp;
38
+ exports.writeNativeMcp = writeNativeMcp;
39
+ const os = __importStar(require("os"));
40
+ const path = __importStar(require("path"));
41
+ const fs_1 = require("fs");
42
+ /** Determine the native MCP config path for a given agent. */
43
+ async function getNativeMcpPath(adapterName, projectRoot) {
44
+ const home = os.homedir();
45
+ const candidates = [];
46
+ switch (adapterName) {
47
+ case 'GitHub Copilot':
48
+ candidates.push(path.join(projectRoot, '.vscode', 'mcp.json'));
49
+ break;
50
+ case 'Visual Studio':
51
+ candidates.push(path.join(projectRoot, '.mcp.json'));
52
+ candidates.push(path.join(projectRoot, '.vs', 'mcp.json'));
53
+ break;
54
+ case 'Cursor':
55
+ candidates.push(path.join(projectRoot, '.cursor', 'mcp.json'));
56
+ candidates.push(path.join(home, '.cursor', 'mcp.json'));
57
+ break;
58
+ case 'Windsurf':
59
+ candidates.push(path.join(home, '.codeium', 'windsurf', 'mcp_config.json'));
60
+ break;
61
+ case 'Claude Code':
62
+ candidates.push(path.join(projectRoot, 'claude_desktop_config.json'));
63
+ break;
64
+ case 'OpenAI Codex CLI':
65
+ candidates.push(path.join(home, '.codex', 'config.json'));
66
+ break;
67
+ case 'Aider':
68
+ candidates.push(path.join(projectRoot, '.mcp.json'));
69
+ break;
70
+ default:
71
+ return null;
72
+ }
73
+ for (const p of candidates) {
74
+ try {
75
+ await fs_1.promises.access(p);
76
+ return p;
77
+ }
78
+ catch {
79
+ // continue
80
+ }
81
+ }
82
+ // default to first candidate if none exist
83
+ return candidates.length > 0 ? candidates[0] : null;
84
+ }
85
+ /** Read native MCP config from disk, or return empty object if missing/invalid. */
86
+ async function readNativeMcp(filePath) {
87
+ try {
88
+ const text = await fs_1.promises.readFile(filePath, 'utf8');
89
+ return JSON.parse(text);
90
+ }
91
+ catch {
92
+ return {};
93
+ }
94
+ }
95
+ /** Write native MCP config to disk, creating parent directories as needed. */
96
+ async function writeNativeMcp(filePath, data) {
97
+ await fs_1.promises.mkdir(path.dirname(filePath), { recursive: true });
98
+ const text = JSON.stringify(data, null, 2) + '\n';
99
+ await fs_1.promises.writeFile(filePath, text, 'utf8');
100
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {
@@ -57,6 +57,7 @@
57
57
  },
58
58
  "dependencies": {
59
59
  "js-yaml": "^4.1.0",
60
+ "toml": "^3.0.0",
60
61
  "yargs": "^17.7.2"
61
62
  }
62
63
  }