@intellectronica/ruler 0.2.4 → 0.2.6

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,14 @@ 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
+ | Jules | `AGENTS.md` |
43
44
  | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` |
44
45
  | Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` |
45
46
  | Cline | `.clinerules` |
46
47
  | Aider | `ruler_aider_instructions.md` and `.aider.conf.yml` |
47
48
  | Firebase Studio | `.idx/airules.md` |
48
49
  | Open Hands | `.openhands/microagents/repo.md` and `.openhands/config.toml` |
50
+ | Gemini CLI | `GEMINI.md` and `.gemini/settings.json` |
49
51
 
50
52
  ## Getting Started
51
53
 
@@ -77,13 +79,20 @@ npx @intellectronica/ruler apply
77
79
  - `.ruler/ruler.toml`: The main configuration file for Ruler
78
80
  - `.ruler/mcp.json`: An example MCP server configuration
79
81
 
82
+ Additionally, you can create a global configuration to use when no local `.ruler/` directory is found:
83
+ ```bash
84
+ ruler init --global
85
+ ```
86
+
87
+ The global configuration will be created to `$XDG_CONFIG_HOME/ruler` (default: `~/.config/ruler`).
88
+
80
89
  ## Core Concepts
81
90
 
82
91
  ### The `.ruler/` Directory
83
92
 
84
93
  This is your central hub for all AI agent instructions:
85
94
 
86
- - **Rule Files (`*.md`)**: Discovered recursively from `.ruler/` and alphabetically concatenated
95
+ - **Rule Files (`*.md`)**: Discovered recursively from `.ruler/` or `$XDG_CONFIG_HOME/ruler` and alphabetically concatenated
87
96
  - **Concatenation Marker**: Each file's content is prepended with `--- Source: <relative_path_to_md_file> ---` for traceability
88
97
  - **`ruler.toml`**: Master configuration for Ruler's behavior, agent selection, and output paths
89
98
  - **`mcp.json`**: Shared MCP server settings
@@ -127,6 +136,8 @@ This is your central hub for all AI agent instructions:
127
136
  ruler apply [options]
128
137
  ```
129
138
 
139
+ The `apply` command looks for `.ruler/` in the current directory tree, reading the first match. If no such directory is found, it will look for a global configuration in `$XDG_CONFIG_HOME/ruler`.
140
+
130
141
  ### Options
131
142
 
132
143
  | Option | Description |
@@ -139,6 +150,7 @@ ruler apply [options]
139
150
  | `--mcp-overwrite` | Overwrite native MCP config entirely instead of merging |
140
151
  | `--gitignore` | Enable automatic .gitignore updates (default: true) |
141
152
  | `--no-gitignore` | Disable automatic .gitignore updates |
153
+ | `--local-only` | Do not look for configuration in `$XDG_CONFIG_HOME` |
142
154
  | `--verbose` / `-v` | Display detailed output during execution |
143
155
 
144
156
  ### Common Examples
@@ -222,6 +234,12 @@ output_path_config = ".aider.conf.yml"
222
234
  enabled = true
223
235
  output_path = ".idx/airules.md"
224
236
 
237
+ [agents.gemini-cli]
238
+ enabled = true
239
+
240
+ [agents.jules]
241
+ enabled = true
242
+
225
243
  # Agent-specific MCP configuration
226
244
  [agents.cursor.mcp]
227
245
  enabled = true
@@ -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);
@@ -46,7 +46,8 @@ 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);
@@ -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;
@@ -0,0 +1,55 @@
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.JulesAgent = void 0;
37
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
38
+ const path = __importStar(require("path"));
39
+ class JulesAgent {
40
+ getIdentifier() {
41
+ return 'jules';
42
+ }
43
+ getName() {
44
+ return 'Jules';
45
+ }
46
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
47
+ const outputPath = agentConfig?.outputPath ?? this.getDefaultOutputPath(projectRoot);
48
+ const absolutePath = path.resolve(projectRoot, outputPath);
49
+ await (0, FileSystemUtils_1.writeGeneratedFile)(absolutePath, concatenatedRules);
50
+ }
51
+ getDefaultOutputPath(projectRoot) {
52
+ return path.join(projectRoot, 'AGENTS.md');
53
+ }
54
+ }
55
+ exports.JulesAgent = JulesAgent;
@@ -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);
@@ -41,6 +41,7 @@ const yargs_1 = __importDefault(require("yargs"));
41
41
  const helpers_1 = require("yargs/helpers");
42
42
  const lib_1 = require("../lib");
43
43
  const path = __importStar(require("path"));
44
+ const os = __importStar(require("os"));
44
45
  const fs_1 = require("fs");
45
46
  const constants_1 = require("../constants");
46
47
  /**
@@ -58,7 +59,7 @@ function run() {
58
59
  });
59
60
  y.option('agents', {
60
61
  type: 'string',
61
- description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase',
62
+ description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli',
62
63
  });
63
64
  y.option('config', {
64
65
  type: 'string',
@@ -90,6 +91,11 @@ function run() {
90
91
  description: 'Preview changes without writing files',
91
92
  default: false,
92
93
  });
94
+ y.option('local-only', {
95
+ type: 'boolean',
96
+ description: 'Only search for local .ruler directories, ignore global config',
97
+ default: false,
98
+ });
93
99
  }, async (argv) => {
94
100
  const projectRoot = argv['project-root'];
95
101
  const agents = argv.agents
@@ -102,6 +108,7 @@ function run() {
102
108
  : undefined;
103
109
  const verbose = argv.verbose;
104
110
  const dryRun = argv['dry-run'];
111
+ const localOnly = argv['local-only'];
105
112
  // Determine gitignore preference: CLI > TOML > Default (enabled)
106
113
  // yargs handles --no-gitignore by setting gitignore to false
107
114
  let gitignorePreference;
@@ -112,7 +119,7 @@ function run() {
112
119
  gitignorePreference = undefined; // Let TOML/default decide
113
120
  }
114
121
  try {
115
- await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun);
122
+ await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly);
116
123
  console.log('Ruler apply completed successfully.');
117
124
  }
118
125
  catch (err) {
@@ -126,10 +133,17 @@ function run() {
126
133
  type: 'string',
127
134
  description: 'Project root directory',
128
135
  default: process.cwd(),
136
+ }).option('global', {
137
+ type: 'boolean',
138
+ description: 'Initialize in global config directory (XDG_CONFIG_HOME/ruler)',
139
+ default: false,
129
140
  });
130
141
  }, async (argv) => {
131
142
  const projectRoot = argv['project-root'];
132
- const rulerDir = path.join(projectRoot, '.ruler');
143
+ const isGlobal = argv['global'];
144
+ const rulerDir = isGlobal
145
+ ? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'ruler')
146
+ : path.join(projectRoot, '.ruler');
133
147
  await fs_1.promises.mkdir(rulerDir, { recursive: true });
134
148
  const instructionsPath = path.join(rulerDir, 'instructions.md');
135
149
  const tomlPath = path.join(rulerDir, 'ruler.toml');
@@ -193,6 +207,9 @@ and apply them to your configured AI coding agents.
193
207
  # [agents.firebase]
194
208
  # enabled = true
195
209
  # output_path = ".idx/airules.md"
210
+
211
+ # [agents.gemini-cli]
212
+ # enabled = true
196
213
  `;
197
214
  if (!(await exists(instructionsPath))) {
198
215
  await fs_1.promises.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
@@ -39,6 +39,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
39
39
  exports.loadConfig = loadConfig;
40
40
  const fs_1 = require("fs");
41
41
  const path = __importStar(require("path"));
42
+ const os = __importStar(require("os"));
42
43
  const toml_1 = __importDefault(require("@iarna/toml"));
43
44
  const zod_1 = require("zod");
44
45
  const constants_1 = require("../constants");
@@ -78,9 +79,23 @@ const rulerConfigSchema = zod_1.z.object({
78
79
  */
79
80
  async function loadConfig(options) {
80
81
  const { projectRoot, configPath, cliAgents } = options;
81
- const configFile = configPath
82
- ? path.resolve(configPath)
83
- : path.join(projectRoot, '.ruler', 'ruler.toml');
82
+ let configFile;
83
+ if (configPath) {
84
+ configFile = path.resolve(configPath);
85
+ }
86
+ else {
87
+ // Try local .ruler/ruler.toml first
88
+ const localConfigFile = path.join(projectRoot, '.ruler', 'ruler.toml');
89
+ try {
90
+ await fs_1.promises.access(localConfigFile);
91
+ configFile = localConfigFile;
92
+ }
93
+ catch {
94
+ // If local config doesn't exist, try global config
95
+ const xdgConfigDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
96
+ configFile = path.join(xdgConfigDir, 'ruler', 'ruler.toml');
97
+ }
98
+ }
84
99
  let raw = {};
85
100
  try {
86
101
  const text = await fs_1.promises.readFile(configFile, 'utf8');
@@ -40,11 +40,20 @@ exports.backupFile = backupFile;
40
40
  exports.ensureDirExists = ensureDirExists;
41
41
  const fs_1 = require("fs");
42
42
  const path = __importStar(require("path"));
43
+ const os = __importStar(require("os"));
44
+ /**
45
+ * Gets the XDG config directory path, falling back to ~/.config if XDG_CONFIG_HOME is not set.
46
+ */
47
+ function getXdgConfigDir() {
48
+ return process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
49
+ }
43
50
  /**
44
51
  * Searches upwards from startPath to find a directory named .ruler.
52
+ * If not found locally and checkGlobal is true, checks for global config at XDG_CONFIG_HOME/ruler.
45
53
  * Returns the path to the .ruler directory, or null if not found.
46
54
  */
47
- async function findRulerDir(startPath) {
55
+ async function findRulerDir(startPath, checkGlobal = true) {
56
+ // First, search upwards from startPath for local .ruler directory
48
57
  let current = startPath;
49
58
  while (current) {
50
59
  const candidate = path.join(current, '.ruler');
@@ -63,6 +72,19 @@ async function findRulerDir(startPath) {
63
72
  }
64
73
  current = parent;
65
74
  }
75
+ // If no local .ruler found and checkGlobal is true, check global config directory
76
+ if (checkGlobal) {
77
+ const globalConfigDir = path.join(getXdgConfigDir(), 'ruler');
78
+ try {
79
+ const stat = await fs_1.promises.stat(globalConfigDir);
80
+ if (stat.isDirectory()) {
81
+ return globalConfigDir;
82
+ }
83
+ }
84
+ catch (err) {
85
+ console.error(`[ruler] Error checking global config directory ${globalConfigDir}:`, err);
86
+ }
87
+ }
66
88
  return null;
67
89
  }
68
90
  /**
package/dist/lib.js CHANGED
@@ -45,10 +45,12 @@ 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");
53
+ const JulesAgent_1 = require("./agents/JulesAgent");
52
54
  const merge_1 = require("./mcp/merge");
53
55
  const validate_1 = require("./mcp/validate");
54
56
  const mcp_1 = require("./paths/mcp");
@@ -94,10 +96,12 @@ const agents = [
94
96
  new CodexCliAgent_1.CodexCliAgent(),
95
97
  new CursorAgent_1.CursorAgent(),
96
98
  new WindsurfAgent_1.WindsurfAgent(),
97
- new ClineAgent_1.ClineAgent(),
99
+ new ClineAgent.ClineAgent(),
98
100
  new AiderAgent_1.AiderAgent(),
99
101
  new FirebaseAgent_1.FirebaseAgent(),
100
102
  new OpenHandsAgent_1.OpenHandsAgent(),
103
+ new GeminiCliAgent_1.GeminiCliAgent(),
104
+ new JulesAgent_1.JulesAgent(),
101
105
  ];
102
106
  /**
103
107
  * Applies ruler configurations for all supported AI agents.
@@ -108,7 +112,7 @@ const agents = [
108
112
  * @param projectRoot Root directory of the project
109
113
  * @param includedAgents Optional list of agent name filters (case-insensitive substrings)
110
114
  */
111
- async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false) {
115
+ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false) {
112
116
  // Load configuration (default_agents, per-agent overrides, CLI filters)
113
117
  (0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
114
118
  if (configPath) {
@@ -135,13 +139,13 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
135
139
  }
136
140
  }
137
141
  config.agentConfigs = mappedConfigs;
138
- const rulerDir = await FileSystemUtils.findRulerDir(projectRoot);
142
+ const rulerDir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
139
143
  if (!rulerDir) {
140
144
  throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
141
145
  }
142
146
  (0, constants_1.logVerbose)(`Found .ruler directory at: ${rulerDir}`, verbose);
143
147
  const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
144
- (0, constants_1.logVerbose)(`Found ${files.length} markdown files in .ruler directory`, verbose);
148
+ (0, constants_1.logVerbose)(`Found ${files.length} markdown files in ruler configuration directory`, verbose);
145
149
  const concatenated = (0, RuleProcessor_1.concatenateRules)(files);
146
150
  (0, constants_1.logVerbose)(`Concatenated rules length: ${concatenated.length} characters`, verbose);
147
151
  const mcpFile = path.join(rulerDir, 'mcp.json');
@@ -185,6 +189,7 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
185
189
  }
186
190
  // Collect all generated file paths for .gitignore
187
191
  const generatedPaths = [];
192
+ let agentsMdWritten = false;
188
193
  for (const agent of selected) {
189
194
  const actionPrefix = dryRun ? '[ruler:dry-run]' : '[ruler]';
190
195
  console.log(`${actionPrefix} Applying rules for ${agent.getName()}...`);
@@ -198,7 +203,14 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
198
203
  (0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`, verbose);
199
204
  }
200
205
  else {
201
- await agent.applyRulerConfig(concatenated, projectRoot, agentConfig);
206
+ if (agent.getIdentifier() === 'jules' ||
207
+ agent.getIdentifier() === 'codex') {
208
+ if (agentsMdWritten) {
209
+ continue;
210
+ }
211
+ agentsMdWritten = true;
212
+ }
213
+ await agent.applyRulerConfig(concatenated, projectRoot, rulerMcpJson, agentConfig);
202
214
  }
203
215
  const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
204
216
  const mcpEnabledForAgent = cliMcpEnabled &&
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.2.4",
3
+ "version": "0.2.6",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {