@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 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`, `.github/copilot-instructions.md` | `.vscode/mcp.json` |
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` | `.vscode/settings.json` |
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, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, openhands, gemini-cli, jules, junie, augmentcode, kilocode, opencode, goose, crush, zed, qwen, kiro, warp, roo, trae) |
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, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, openhands, gemini-cli, jules, junie, augmentcode, kilocode, opencode, goose, crush, zed, qwen, kiro, warp, roo, trae) |
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 both AGENTS.md (for web-based GitHub Copilot) and
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 multiple output paths to ensure both files are added to .gitignore.
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
- // First, write to AGENTS.md using the existing AgentsMdAgent infrastructure
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, filteredMcpJson, strategy, serverKey);
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(merged, null, 2);
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, merged);
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'));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.3.7",
3
+ "version": "0.3.9",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {