@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 +19 -1
- 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 +2 -1
- package/dist/agents/CursorAgent.js +2 -1
- package/dist/agents/FirebaseAgent.js +2 -1
- package/dist/agents/GeminiCliAgent.js +74 -0
- package/dist/agents/JulesAgent.js +55 -0
- package/dist/agents/OpenHandsAgent.js +2 -1
- package/dist/agents/WindsurfAgent.js +2 -1
- package/dist/cli/commands.js +20 -3
- package/dist/core/ConfigLoader.js +18 -3
- package/dist/core/FileSystemUtils.js +23 -1
- package/dist/lib.js +18 -6
- package/package.json +1 -1
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,
|
|
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);
|
|
@@ -46,7 +46,8 @@ 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);
|
|
@@ -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;
|
|
@@ -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,
|
|
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
|
@@ -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
|
|
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
|
-
|
|
82
|
-
|
|
83
|
-
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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 &&
|