@intellectronica/ruler 0.3.7 → 0.3.9
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 +5 -7
- package/dist/agents/CopilotAgent.js +4 -64
- package/dist/agents/FirebaseAgent.js +8 -0
- package/dist/core/apply-engine.js +71 -3
- package/dist/paths/mcp.js +3 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -57,7 +57,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
57
57
|
| Agent | Rules File(s) | MCP Configuration / Notes |
|
|
58
58
|
| ---------------- | ------------------------------------------------ | ------------------------------------------------ |
|
|
59
59
|
| AGENTS.md | `AGENTS.md` | (pseudo-agent ensuring root `AGENTS.md` exists) |
|
|
60
|
-
| GitHub Copilot | `AGENTS.md
|
|
60
|
+
| GitHub Copilot | `AGENTS.md` | `.vscode/mcp.json` |
|
|
61
61
|
| Claude Code | `CLAUDE.md` | `.mcp.json` |
|
|
62
62
|
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml` |
|
|
63
63
|
| Jules | `AGENTS.md` | - |
|
|
@@ -68,11 +68,11 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
68
68
|
| Amp | `AGENTS.md` | - |
|
|
69
69
|
| Amazon Q CLI | `.amazonq/rules/ruler_q_rules.md` | `.amazonq/mcp.json` |
|
|
70
70
|
| Aider | `AGENTS.md`, `.aider.conf.yml` | `.mcp.json` |
|
|
71
|
-
| Firebase Studio | `.idx/airules.md` |
|
|
71
|
+
| Firebase Studio | `.idx/airules.md` | `.idx/mcp.json` |
|
|
72
72
|
| Open Hands | `.openhands/microagents/repo.md` | `config.toml` |
|
|
73
73
|
| Gemini CLI | `AGENTS.md` | `.gemini/settings.json` |
|
|
74
74
|
| Junie | `.junie/guidelines.md` | - |
|
|
75
|
-
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` |
|
|
75
|
+
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | - |
|
|
76
76
|
| Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
|
|
77
77
|
| opencode | `AGENTS.md` | `opencode.json` |
|
|
78
78
|
| Goose | `.goosehints` | - |
|
|
@@ -214,7 +214,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
|
|
|
214
214
|
| Option | Description |
|
|
215
215
|
| ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
216
216
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
217
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, amazonqcli, amp,
|
|
217
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
|
|
218
218
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
219
219
|
| `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
|
|
220
220
|
| `--no-mcp` | Disable applying MCP server configurations |
|
|
@@ -307,7 +307,7 @@ ruler revert [options]
|
|
|
307
307
|
| Option | Description |
|
|
308
308
|
| ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
|
309
309
|
| `--project-root <path>` | Path to your project's root (default: current directory) |
|
|
310
|
-
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, amazonqcli, amp,
|
|
310
|
+
| `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (agentsmd, aider, amazonqcli, amp, augmentcode, claude, cline, codex, copilot, crush, cursor, firebase, gemini-cli, goose, jules, junie, kilocode, kiro, opencode, openhands, qwen, roo, trae, warp, windsurf, zed) |
|
|
311
311
|
| `--config <path>` | Path to a custom `ruler.toml` configuration file |
|
|
312
312
|
| `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
|
|
313
313
|
| `--dry-run` | Preview changes without actually reverting files |
|
|
@@ -389,7 +389,6 @@ enabled = true
|
|
|
389
389
|
# --- Agent-Specific Configurations ---
|
|
390
390
|
[agents.copilot]
|
|
391
391
|
enabled = true
|
|
392
|
-
output_path = ".github/copilot-instructions.md"
|
|
393
392
|
|
|
394
393
|
[agents.claude]
|
|
395
394
|
enabled = true
|
|
@@ -565,7 +564,6 @@ node_modules/
|
|
|
565
564
|
.aider.conf.yml
|
|
566
565
|
.clinerules
|
|
567
566
|
.cursor/rules/ruler_cursor_instructions.mdc
|
|
568
|
-
.github/copilot-instructions.md
|
|
569
567
|
.windsurf/rules/ruler_windsurf_instructions.md
|
|
570
568
|
AGENTS.md
|
|
571
569
|
CLAUDE.md
|
|
@@ -1,47 +1,10 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
-
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
-
if (k2 === undefined) k2 = k;
|
|
4
|
-
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
-
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
-
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
-
}
|
|
8
|
-
Object.defineProperty(o, k2, desc);
|
|
9
|
-
}) : (function(o, m, k, k2) {
|
|
10
|
-
if (k2 === undefined) k2 = k;
|
|
11
|
-
o[k2] = m[k];
|
|
12
|
-
}));
|
|
13
|
-
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
-
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
-
}) : function(o, v) {
|
|
16
|
-
o["default"] = v;
|
|
17
|
-
});
|
|
18
|
-
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
-
var ownKeys = function(o) {
|
|
20
|
-
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
-
var ar = [];
|
|
22
|
-
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
-
return ar;
|
|
24
|
-
};
|
|
25
|
-
return ownKeys(o);
|
|
26
|
-
};
|
|
27
|
-
return function (mod) {
|
|
28
|
-
if (mod && mod.__esModule) return mod;
|
|
29
|
-
var result = {};
|
|
30
|
-
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
-
__setModuleDefault(result, mod);
|
|
32
|
-
return result;
|
|
33
|
-
};
|
|
34
|
-
})();
|
|
35
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
3
|
exports.CopilotAgent = void 0;
|
|
37
|
-
const path = __importStar(require("path"));
|
|
38
4
|
const AgentsMdAgent_1 = require("./AgentsMdAgent");
|
|
39
|
-
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
40
|
-
const fs_1 = require("fs");
|
|
41
5
|
/**
|
|
42
6
|
* GitHub Copilot agent adapter.
|
|
43
|
-
* Writes to
|
|
44
|
-
* .github/copilot-instructions.md (for VS Code extension compatibility).
|
|
7
|
+
* Writes to AGENTS.md for both web-based GitHub Copilot and VS Code extension.
|
|
45
8
|
*/
|
|
46
9
|
class CopilotAgent {
|
|
47
10
|
constructor() {
|
|
@@ -54,41 +17,18 @@ class CopilotAgent {
|
|
|
54
17
|
return 'GitHub Copilot';
|
|
55
18
|
}
|
|
56
19
|
/**
|
|
57
|
-
* Returns
|
|
20
|
+
* Returns the default output path for AGENTS.md.
|
|
58
21
|
*/
|
|
59
22
|
getDefaultOutputPath(projectRoot) {
|
|
60
|
-
return
|
|
61
|
-
instructions: path.join(projectRoot, 'AGENTS.md'),
|
|
62
|
-
legacy: path.join(projectRoot, '.github', 'copilot-instructions.md'),
|
|
63
|
-
};
|
|
23
|
+
return this.agentsMdAgent.getDefaultOutputPath(projectRoot);
|
|
64
24
|
}
|
|
65
25
|
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig, backup = true) {
|
|
66
|
-
//
|
|
26
|
+
// Write to AGENTS.md using the existing AgentsMdAgent infrastructure
|
|
67
27
|
await this.agentsMdAgent.applyRulerConfig(concatenatedRules, projectRoot, null, // No MCP config needed for the instructions file
|
|
68
28
|
{
|
|
69
29
|
// Preserve explicit outputPath precedence semantics if provided
|
|
70
30
|
outputPath: agentConfig?.outputPath || agentConfig?.outputPathInstructions,
|
|
71
31
|
}, backup);
|
|
72
|
-
// Additionally write to .github/copilot-instructions.md for VS Code extension compatibility
|
|
73
|
-
const outputPaths = this.getDefaultOutputPath(projectRoot);
|
|
74
|
-
const legacyPath = path.resolve(projectRoot, outputPaths.legacy);
|
|
75
|
-
// Add marker comment to the content to identify it as generated
|
|
76
|
-
const contentWithMarker = `<!-- Generated by Ruler -->\n${concatenatedRules}`;
|
|
77
|
-
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(legacyPath));
|
|
78
|
-
// Check if content has changed (idempotency)
|
|
79
|
-
let existingLegacy = null;
|
|
80
|
-
try {
|
|
81
|
-
existingLegacy = await fs_1.promises.readFile(legacyPath, 'utf8');
|
|
82
|
-
}
|
|
83
|
-
catch {
|
|
84
|
-
existingLegacy = null;
|
|
85
|
-
}
|
|
86
|
-
if (existingLegacy === null || existingLegacy !== contentWithMarker) {
|
|
87
|
-
if (backup) {
|
|
88
|
-
await (0, FileSystemUtils_1.backupFile)(legacyPath);
|
|
89
|
-
}
|
|
90
|
-
await (0, FileSystemUtils_1.writeGeneratedFile)(legacyPath, contentWithMarker);
|
|
91
|
-
}
|
|
92
32
|
}
|
|
93
33
|
getMcpServerKey() {
|
|
94
34
|
return 'servers';
|
|
@@ -49,5 +49,13 @@ class FirebaseAgent extends AbstractAgent_1.AbstractAgent {
|
|
|
49
49
|
getDefaultOutputPath(projectRoot) {
|
|
50
50
|
return path.join(projectRoot, '.idx', 'airules.md');
|
|
51
51
|
}
|
|
52
|
+
// Firebase Studio (IDX) supports stdio MCP servers via .idx/mcp.json
|
|
53
|
+
supportsMcpStdio() {
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
// Remote MCP over HTTP/SSE is not documented for Firebase Studio yet
|
|
57
|
+
supportsMcpRemote() {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
52
60
|
}
|
|
53
61
|
exports.FirebaseAgent = FirebaseAgent;
|
|
@@ -333,28 +333,96 @@ async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verb
|
|
|
333
333
|
await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup);
|
|
334
334
|
}
|
|
335
335
|
}
|
|
336
|
+
/**
|
|
337
|
+
* Transform MCP server types for Claude Code compatibility.
|
|
338
|
+
* Claude expects "http" for HTTP servers and "sse" for SSE servers, not "remote".
|
|
339
|
+
*/
|
|
340
|
+
function transformMcpForClaude(mcpJson) {
|
|
341
|
+
if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
|
|
342
|
+
return mcpJson;
|
|
343
|
+
}
|
|
344
|
+
const transformedMcp = { ...mcpJson };
|
|
345
|
+
const transformedServers = {};
|
|
346
|
+
for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
|
|
347
|
+
if (serverDef && typeof serverDef === 'object') {
|
|
348
|
+
const server = serverDef;
|
|
349
|
+
const transformedServer = { ...server };
|
|
350
|
+
// Transform type: "remote" to appropriate Claude types
|
|
351
|
+
if (server.type === 'remote' &&
|
|
352
|
+
server.url &&
|
|
353
|
+
typeof server.url === 'string') {
|
|
354
|
+
const url = server.url;
|
|
355
|
+
// Check if URL suggests SSE (contains /sse path segment)
|
|
356
|
+
if (/\/sse(\/|$)/i.test(url)) {
|
|
357
|
+
transformedServer.type = 'sse';
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
transformedServer.type = 'http';
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
transformedServers[name] = transformedServer;
|
|
364
|
+
}
|
|
365
|
+
else {
|
|
366
|
+
transformedServers[name] = serverDef;
|
|
367
|
+
}
|
|
368
|
+
}
|
|
369
|
+
transformedMcp.mcpServers = transformedServers;
|
|
370
|
+
return transformedMcp;
|
|
371
|
+
}
|
|
336
372
|
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
337
373
|
const strategy = cliMcpStrategy ??
|
|
338
374
|
agentConfig?.mcp?.strategy ??
|
|
339
375
|
config.mcp?.strategy ??
|
|
340
376
|
'merge';
|
|
341
377
|
const serverKey = agent.getMcpServerKey?.() ?? 'mcpServers';
|
|
378
|
+
// Skip agents with empty server keys (e.g., AgentsMdAgent, GooseAgent)
|
|
379
|
+
if (serverKey === '') {
|
|
380
|
+
(0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} - agent has empty server key`, verbose);
|
|
381
|
+
return;
|
|
382
|
+
}
|
|
342
383
|
(0, constants_1.logVerbose)(`Applying filtered MCP config for ${agent.getName()} with strategy: ${strategy} and key: ${serverKey}`, verbose);
|
|
343
384
|
if (dryRun) {
|
|
344
385
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
|
|
345
386
|
}
|
|
346
387
|
else {
|
|
388
|
+
// Transform MCP config for Claude Code compatibility
|
|
389
|
+
let mcpToMerge = filteredMcpJson;
|
|
390
|
+
if (agent.getIdentifier() === 'claude') {
|
|
391
|
+
mcpToMerge = transformMcpForClaude(filteredMcpJson);
|
|
392
|
+
}
|
|
347
393
|
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
348
|
-
const merged = (0, merge_1.mergeMcp)(existing,
|
|
394
|
+
const merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
|
|
395
|
+
// Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
|
|
396
|
+
// Sanitize merged config by stripping 'type' from each server when targeting Firebase.
|
|
397
|
+
const sanitizeForFirebase = (obj) => {
|
|
398
|
+
if (agent.getIdentifier() !== 'firebase')
|
|
399
|
+
return obj;
|
|
400
|
+
const out = { ...obj };
|
|
401
|
+
const servers = out[serverKey] || {};
|
|
402
|
+
const cleanedServers = {};
|
|
403
|
+
for (const [name, def] of Object.entries(servers)) {
|
|
404
|
+
if (def && typeof def === 'object') {
|
|
405
|
+
const copy = { ...def };
|
|
406
|
+
delete copy.type;
|
|
407
|
+
cleanedServers[name] = copy;
|
|
408
|
+
}
|
|
409
|
+
else {
|
|
410
|
+
cleanedServers[name] = def;
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
out[serverKey] = cleanedServers;
|
|
414
|
+
return out;
|
|
415
|
+
};
|
|
416
|
+
const toWrite = sanitizeForFirebase(merged);
|
|
349
417
|
// Only backup and write if content would actually change (idempotent)
|
|
350
418
|
const currentContent = JSON.stringify(existing, null, 2);
|
|
351
|
-
const newContent = JSON.stringify(
|
|
419
|
+
const newContent = JSON.stringify(toWrite, null, 2);
|
|
352
420
|
if (currentContent !== newContent) {
|
|
353
421
|
if (backup) {
|
|
354
422
|
const { backupFile } = await Promise.resolve().then(() => __importStar(require('../core/FileSystemUtils')));
|
|
355
423
|
await backupFile(dest);
|
|
356
424
|
}
|
|
357
|
-
await (0, mcp_1.writeNativeMcp)(dest,
|
|
425
|
+
await (0, mcp_1.writeNativeMcp)(dest, toWrite);
|
|
358
426
|
}
|
|
359
427
|
else {
|
|
360
428
|
(0, constants_1.logVerbose)(`MCP config for ${agent.getName()} is already up to date - skipping backup and write`, verbose);
|
package/dist/paths/mcp.js
CHANGED
|
@@ -80,6 +80,9 @@ async function getNativeMcpPath(adapterName, projectRoot) {
|
|
|
80
80
|
case 'OpenCode':
|
|
81
81
|
candidates.push(path.join(projectRoot, 'opencode.json'));
|
|
82
82
|
break;
|
|
83
|
+
case 'Firebase Studio':
|
|
84
|
+
candidates.push(path.join(projectRoot, '.idx', 'mcp.json'));
|
|
85
|
+
break;
|
|
83
86
|
case 'Zed':
|
|
84
87
|
// Only consider project-local Zed settings (avoid writing to user home directory)
|
|
85
88
|
candidates.push(path.join(projectRoot, '.zed', 'settings.json'));
|