@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 +6 -2
- package/dist/agents/AiderAgent.js +2 -1
- package/dist/agents/ClaudeAgent.js +2 -1
- package/dist/agents/ClineAgent.js +2 -1
- package/dist/agents/CodexCliAgent.js +2 -1
- package/dist/agents/CopilotAgent.js +5 -1
- package/dist/agents/CursorAgent.js +3 -2
- package/dist/agents/FirebaseAgent.js +2 -1
- package/dist/agents/GeminiCliAgent.js +74 -0
- package/dist/agents/OpenHandsAgent.js +2 -1
- package/dist/agents/WindsurfAgent.js +2 -1
- package/dist/cli/commands.js +5 -2
- package/dist/lib.js +9 -5
- package/dist/mcp/merge.js +15 -5
- package/package.json +1 -1
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.
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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.
|
|
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,
|
|
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,
|
|
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,
|
|
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);
|
package/dist/cli/commands.js
CHANGED
|
@@ -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.
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
...
|
|
19
|
-
|
|
28
|
+
...newBase,
|
|
29
|
+
[serverKey]: mergedServers,
|
|
20
30
|
};
|
|
21
31
|
}
|