@intellectronica/ruler 0.2.3 → 0.2.5

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
@@ -40,12 +40,13 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
40
40
  | GitHub Copilot | `.github/copilot-instructions.md` |
41
41
  | Claude Code | `CLAUDE.md` |
42
42
  | OpenAI Codex CLI | `AGENTS.md` |
43
- | Cursor | `.cursor/rules/ruler_cursor_instructions.md` |
43
+ | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` |
44
44
  | Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` |
45
45
  | Cline | `.clinerules` |
46
46
  | Aider | `ruler_aider_instructions.md` and `.aider.conf.yml` |
47
47
  | Firebase Studio | `.idx/airules.md` |
48
48
  | Open Hands | `.openhands/microagents/repo.md` and `.openhands/config.toml` |
49
+ | Gemini CLI | `GEMINI.md` and `.gemini/settings.json` |
49
50
 
50
51
  ## Getting Started
51
52
 
@@ -222,6 +223,9 @@ output_path_config = ".aider.conf.yml"
222
223
  enabled = true
223
224
  output_path = ".idx/airules.md"
224
225
 
226
+ [agents.gemini-cli]
227
+ enabled = true
228
+
225
229
  # Agent-specific MCP configuration
226
230
  [agents.cursor.mcp]
227
231
  enabled = true
@@ -288,7 +292,7 @@ node_modules/
288
292
  # START Ruler Generated Files
289
293
  .aider.conf.yml
290
294
  .clinerules
291
- .cursor/rules/ruler_cursor_instructions.md
295
+ .cursor/rules/ruler_cursor_instructions.mdc
292
296
  .github/copilot-instructions.md
293
297
  .windsurf/rules/ruler_windsurf_instructions.md
294
298
  AGENTS.md
@@ -48,7 +48,8 @@ class AiderAgent {
48
48
  getName() {
49
49
  return 'Aider';
50
50
  }
51
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
51
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
52
+ agentConfig) {
52
53
  const mdPath = agentConfig?.outputPathInstructions ??
53
54
  this.getDefaultOutputPath(projectRoot).instructions;
54
55
  await (0, FileSystemUtils_1.backupFile)(mdPath);
@@ -46,7 +46,8 @@ class ClaudeAgent {
46
46
  getName() {
47
47
  return 'Claude Code';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.backupFile)(output);
52
53
  await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
@@ -46,7 +46,8 @@ class ClineAgent {
46
46
  getName() {
47
47
  return 'Cline';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.backupFile)(output);
52
53
  await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
@@ -46,7 +46,8 @@ class CodexCliAgent {
46
46
  getName() {
47
47
  return 'OpenAI Codex CLI';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.backupFile)(output);
52
53
  await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
@@ -46,7 +46,8 @@ class CopilotAgent {
46
46
  getName() {
47
47
  return 'GitHub Copilot';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
52
53
  await (0, FileSystemUtils_1.backupFile)(output);
@@ -55,5 +56,8 @@ class CopilotAgent {
55
56
  getDefaultOutputPath(projectRoot) {
56
57
  return path.join(projectRoot, '.github', 'copilot-instructions.md');
57
58
  }
59
+ getMcpServerKey() {
60
+ return 'servers';
61
+ }
58
62
  }
59
63
  exports.CopilotAgent = CopilotAgent;
@@ -46,14 +46,15 @@ class CursorAgent {
46
46
  getName() {
47
47
  return 'Cursor';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
52
53
  await (0, FileSystemUtils_1.backupFile)(output);
53
54
  await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
54
55
  }
55
56
  getDefaultOutputPath(projectRoot) {
56
- return path.join(projectRoot, '.cursor', 'rules', 'ruler_cursor_instructions.md');
57
+ return path.join(projectRoot, '.cursor', 'rules', 'ruler_cursor_instructions.mdc');
57
58
  }
58
59
  }
59
60
  exports.CursorAgent = CursorAgent;
@@ -46,7 +46,8 @@ class FirebaseAgent {
46
46
  getName() {
47
47
  return 'Firebase Studio';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.backupFile)(output);
52
53
  await (0, FileSystemUtils_1.writeGeneratedFile)(output, concatenatedRules);
@@ -0,0 +1,74 @@
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.GeminiCliAgent = void 0;
37
+ const path = __importStar(require("path"));
38
+ const fs_1 = require("fs");
39
+ const merge_1 = require("../mcp/merge");
40
+ class GeminiCliAgent {
41
+ getIdentifier() {
42
+ return 'gemini-cli';
43
+ }
44
+ getName() {
45
+ return 'Gemini CLI';
46
+ }
47
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
48
+ const outputPath = this.getDefaultOutputPath(projectRoot);
49
+ await fs_1.promises.writeFile(outputPath, concatenatedRules);
50
+ if (rulerMcpJson) {
51
+ const settingsPath = path.join(projectRoot, '.gemini', 'settings.json');
52
+ let existingSettings = {};
53
+ try {
54
+ const existingSettingsRaw = await fs_1.promises.readFile(settingsPath, 'utf8');
55
+ existingSettings = JSON.parse(existingSettingsRaw);
56
+ }
57
+ catch (error) {
58
+ if (error.code !== 'ENOENT') {
59
+ throw error;
60
+ }
61
+ }
62
+ const merged = (0, merge_1.mergeMcp)(existingSettings, rulerMcpJson, agentConfig?.mcp?.strategy ?? 'merge', this.getMcpServerKey());
63
+ await fs_1.promises.mkdir(path.dirname(settingsPath), { recursive: true });
64
+ await fs_1.promises.writeFile(settingsPath, JSON.stringify(merged, null, 2));
65
+ }
66
+ }
67
+ getDefaultOutputPath(projectRoot) {
68
+ return path.join(projectRoot, 'GEMINI.md');
69
+ }
70
+ getMcpServerKey() {
71
+ return 'mcpServers';
72
+ }
73
+ }
74
+ exports.GeminiCliAgent = GeminiCliAgent;
@@ -43,7 +43,8 @@ class OpenHandsAgent {
43
43
  getName() {
44
44
  return 'Open Hands';
45
45
  }
46
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
46
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
47
+ agentConfig) {
47
48
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
49
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
49
50
  await (0, FileSystemUtils_1.backupFile)(output);
@@ -46,7 +46,8 @@ class WindsurfAgent {
46
46
  getName() {
47
47
  return 'Windsurf';
48
48
  }
49
- async applyRulerConfig(concatenatedRules, projectRoot, agentConfig) {
49
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, // eslint-disable-line @typescript-eslint/no-unused-vars
50
+ agentConfig) {
50
51
  const output = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
51
52
  await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(output));
52
53
  await (0, FileSystemUtils_1.backupFile)(output);
@@ -58,7 +58,7 @@ function run() {
58
58
  });
59
59
  y.option('agents', {
60
60
  type: 'string',
61
- description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase',
61
+ description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli',
62
62
  });
63
63
  y.option('config', {
64
64
  type: 'string',
@@ -175,7 +175,7 @@ and apply them to your configured AI coding agents.
175
175
 
176
176
  # [agents.cursor]
177
177
  # enabled = true
178
- # output_path = ".cursor/rules/ruler_cursor_instructions.md"
178
+ # output_path = ".cursor/rules/ruler_cursor_instructions.mdc"
179
179
 
180
180
  # [agents.windsurf]
181
181
  # enabled = true
@@ -193,6 +193,9 @@ and apply them to your configured AI coding agents.
193
193
  # [agents.firebase]
194
194
  # enabled = true
195
195
  # output_path = ".idx/airules.md"
196
+
197
+ # [agents.gemini-cli]
198
+ # enabled = true
196
199
  `;
197
200
  if (!(await exists(instructionsPath))) {
198
201
  await fs_1.promises.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
package/dist/lib.js CHANGED
@@ -45,10 +45,11 @@ const ClaudeAgent_1 = require("./agents/ClaudeAgent");
45
45
  const CodexCliAgent_1 = require("./agents/CodexCliAgent");
46
46
  const CursorAgent_1 = require("./agents/CursorAgent");
47
47
  const WindsurfAgent_1 = require("./agents/WindsurfAgent");
48
- const ClineAgent_1 = require("./agents/ClineAgent");
48
+ const ClineAgent = __importStar(require("./agents/ClineAgent"));
49
49
  const AiderAgent_1 = require("./agents/AiderAgent");
50
50
  const FirebaseAgent_1 = require("./agents/FirebaseAgent");
51
51
  const OpenHandsAgent_1 = require("./agents/OpenHandsAgent");
52
+ const GeminiCliAgent_1 = require("./agents/GeminiCliAgent");
52
53
  const merge_1 = require("./mcp/merge");
53
54
  const validate_1 = require("./mcp/validate");
54
55
  const mcp_1 = require("./paths/mcp");
@@ -94,10 +95,11 @@ const agents = [
94
95
  new CodexCliAgent_1.CodexCliAgent(),
95
96
  new CursorAgent_1.CursorAgent(),
96
97
  new WindsurfAgent_1.WindsurfAgent(),
97
- new ClineAgent_1.ClineAgent(),
98
+ new ClineAgent.ClineAgent(),
98
99
  new AiderAgent_1.AiderAgent(),
99
100
  new FirebaseAgent_1.FirebaseAgent(),
100
101
  new OpenHandsAgent_1.OpenHandsAgent(),
102
+ new GeminiCliAgent_1.GeminiCliAgent(),
101
103
  ];
102
104
  /**
103
105
  * Applies ruler configurations for all supported AI agents.
@@ -198,7 +200,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
198
200
  (0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`, verbose);
199
201
  }
200
202
  else {
201
- await agent.applyRulerConfig(concatenated, projectRoot, agentConfig);
203
+ await agent.applyRulerConfig(concatenated, projectRoot, rulerMcpJson, agentConfig);
202
204
  }
203
205
  const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
204
206
  const mcpEnabledForAgent = cliMcpEnabled &&
@@ -222,13 +224,15 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
222
224
  agentConfig?.mcp?.strategy ??
223
225
  config.mcp?.strategy ??
224
226
  'merge';
225
- (0, constants_1.logVerbose)(`Applying MCP config for ${agent.getName()} with strategy: ${strategy}`, verbose);
227
+ // Determine the correct server key for the agent
228
+ const serverKey = agent.getMcpServerKey?.() ?? 'mcpServers';
229
+ (0, constants_1.logVerbose)(`Applying MCP config for ${agent.getName()} with strategy: ${strategy} and key: ${serverKey}`, verbose);
226
230
  if (dryRun) {
227
231
  (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, true);
228
232
  }
229
233
  else {
230
234
  const existing = await (0, mcp_1.readNativeMcp)(dest);
231
- const merged = (0, merge_1.mergeMcp)(existing, rulerMcpJson, strategy);
235
+ const merged = (0, merge_1.mergeMcp)(existing, rulerMcpJson, strategy, serverKey);
232
236
  await (0, mcp_1.writeNativeMcp)(dest, merged);
233
237
  }
234
238
  }
package/dist/mcp/merge.js CHANGED
@@ -6,16 +6,26 @@ exports.mergeMcp = mergeMcp;
6
6
  * @param base Existing native MCP config object.
7
7
  * @param incoming Ruler MCP config object.
8
8
  * @param strategy Merge strategy: 'merge' to union servers, 'overwrite' to replace.
9
+ * @param serverKey The key to use for servers in the output (e.g., 'servers' for Copilot, 'mcpServers' for others).
9
10
  * @returns Merged MCP config object.
10
11
  */
11
- function mergeMcp(base, incoming, strategy) {
12
+ function mergeMcp(base, incoming, strategy, serverKey) {
12
13
  if (strategy === 'overwrite') {
13
- return incoming;
14
+ // Ensure the incoming object uses the correct server key.
15
+ const incomingServers = incoming.mcpServers || {};
16
+ return {
17
+ [serverKey]: incomingServers,
18
+ };
14
19
  }
15
- const baseServers = base.mcpServers || {};
20
+ const baseServers = base[serverKey] ||
21
+ base.mcpServers ||
22
+ {}; // Handle legacy key in existing files
16
23
  const incomingServers = incoming.mcpServers || {};
24
+ const mergedServers = { ...baseServers, ...incomingServers };
25
+ const newBase = { ...base };
26
+ delete newBase.mcpServers; // Remove old key if present
17
27
  return {
18
- ...base,
19
- mcpServers: { ...baseServers, ...incomingServers },
28
+ ...newBase,
29
+ [serverKey]: mergedServers,
20
30
  };
21
31
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.2.3",
3
+ "version": "0.2.5",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {