@intellectronica/ruler 0.2.18 → 0.3.0
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 +190 -30
- package/dist/agents/AbstractAgent.js +82 -0
- package/dist/agents/AgentsMdAgent.js +82 -0
- package/dist/agents/AiderAgent.js +29 -9
- package/dist/agents/AmpAgent.js +13 -0
- package/dist/agents/AugmentCodeAgent.js +10 -12
- package/dist/agents/ClaudeAgent.js +9 -9
- package/dist/agents/ClineAgent.js +3 -9
- package/dist/agents/CodexCliAgent.js +27 -21
- package/dist/agents/CopilotAgent.js +9 -10
- package/dist/agents/CrushAgent.js +6 -0
- package/dist/agents/CursorAgent.js +13 -5
- package/dist/agents/FirebaseAgent.js +2 -8
- package/dist/agents/GeminiCliAgent.js +31 -22
- package/dist/agents/GooseAgent.js +2 -10
- package/dist/agents/JulesAgent.js +3 -44
- package/dist/agents/JunieAgent.js +2 -9
- package/dist/agents/KiloCodeAgent.js +8 -9
- package/dist/agents/KiroAgent.js +50 -0
- package/dist/agents/OpenCodeAgent.js +50 -11
- package/dist/agents/OpenHandsAgent.js +8 -9
- package/dist/agents/QwenCodeAgent.js +83 -0
- package/dist/agents/WarpAgent.js +61 -0
- package/dist/agents/WindsurfAgent.js +9 -10
- package/dist/agents/ZedAgent.js +132 -0
- package/dist/agents/agent-utils.js +37 -0
- package/dist/agents/index.js +53 -0
- package/dist/cli/commands.js +48 -242
- package/dist/cli/handlers.js +176 -0
- package/dist/constants.js +9 -2
- package/dist/core/ConfigLoader.js +1 -1
- package/dist/core/FileSystemUtils.js +51 -4
- package/dist/core/RuleProcessor.js +15 -3
- package/dist/core/UnifiedConfigLoader.js +357 -0
- package/dist/core/UnifiedConfigTypes.js +2 -0
- package/dist/core/agent-selection.js +52 -0
- package/dist/core/apply-engine.js +302 -0
- package/dist/core/config-utils.js +30 -0
- package/dist/core/hash.js +24 -0
- package/dist/core/revert-engine.js +413 -0
- package/dist/lib.js +20 -312
- package/dist/mcp/capabilities.js +51 -0
- package/dist/mcp/propagateOpenCodeMcp.js +30 -31
- package/dist/mcp/propagateOpenHandsMcp.js +101 -17
- package/dist/paths/mcp.js +7 -3
- package/dist/revert.js +96 -479
- package/package.json +2 -1
package/dist/cli/commands.js
CHANGED
|
@@ -1,37 +1,4 @@
|
|
|
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
|
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
4
|
};
|
|
@@ -39,12 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
39
6
|
exports.run = run;
|
|
40
7
|
const yargs_1 = __importDefault(require("yargs"));
|
|
41
8
|
const helpers_1 = require("yargs/helpers");
|
|
42
|
-
const
|
|
43
|
-
const revert_1 = require("../revert");
|
|
44
|
-
const path = __importStar(require("path"));
|
|
45
|
-
const os = __importStar(require("os"));
|
|
46
|
-
const fs_1 = require("fs");
|
|
47
|
-
const constants_1 = require("../constants");
|
|
9
|
+
const handlers_1 = require("./handlers");
|
|
48
10
|
/**
|
|
49
11
|
* Sets up and parses CLI commands.
|
|
50
12
|
*/
|
|
@@ -53,258 +15,102 @@ function run() {
|
|
|
53
15
|
.scriptName('ruler')
|
|
54
16
|
.usage('$0 <command> [options]')
|
|
55
17
|
.command('apply', 'Apply ruler configurations to supported AI agents', (y) => {
|
|
56
|
-
y
|
|
18
|
+
return y
|
|
19
|
+
.option('project-root', {
|
|
57
20
|
type: 'string',
|
|
58
21
|
description: 'Project root directory',
|
|
59
22
|
default: process.cwd(),
|
|
60
|
-
})
|
|
61
|
-
|
|
23
|
+
})
|
|
24
|
+
.option('agents', {
|
|
62
25
|
type: 'string',
|
|
63
|
-
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, crush',
|
|
64
|
-
})
|
|
65
|
-
|
|
26
|
+
description: 'Comma-separated list of agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, crush, zed, qwen',
|
|
27
|
+
})
|
|
28
|
+
.option('config', {
|
|
66
29
|
type: 'string',
|
|
67
30
|
description: 'Path to TOML configuration file',
|
|
68
|
-
})
|
|
69
|
-
|
|
31
|
+
})
|
|
32
|
+
.option('mcp', {
|
|
70
33
|
type: 'boolean',
|
|
71
34
|
description: 'Enable or disable applying MCP server config',
|
|
72
35
|
default: true,
|
|
73
|
-
})
|
|
74
|
-
|
|
75
|
-
|
|
36
|
+
})
|
|
37
|
+
.alias('mcp', 'with-mcp')
|
|
38
|
+
.option('mcp-overwrite', {
|
|
76
39
|
type: 'boolean',
|
|
77
40
|
description: 'Replace (not merge) the native MCP config(s)',
|
|
78
41
|
default: false,
|
|
79
|
-
})
|
|
80
|
-
|
|
42
|
+
})
|
|
43
|
+
.option('gitignore', {
|
|
81
44
|
type: 'boolean',
|
|
82
45
|
description: 'Enable/disable automatic .gitignore updates (default: enabled)',
|
|
83
|
-
})
|
|
84
|
-
|
|
46
|
+
})
|
|
47
|
+
.option('verbose', {
|
|
85
48
|
type: 'boolean',
|
|
86
49
|
description: 'Enable verbose logging',
|
|
87
50
|
default: false,
|
|
88
|
-
})
|
|
89
|
-
|
|
90
|
-
|
|
51
|
+
})
|
|
52
|
+
.alias('verbose', 'v')
|
|
53
|
+
.option('dry-run', {
|
|
91
54
|
type: 'boolean',
|
|
92
55
|
description: 'Preview changes without writing files',
|
|
93
56
|
default: false,
|
|
94
|
-
})
|
|
95
|
-
|
|
57
|
+
})
|
|
58
|
+
.option('local-only', {
|
|
96
59
|
type: 'boolean',
|
|
97
60
|
description: 'Only search for local .ruler directories, ignore global config',
|
|
98
61
|
default: false,
|
|
99
62
|
});
|
|
100
|
-
},
|
|
101
|
-
const projectRoot = argv['project-root'];
|
|
102
|
-
const agents = argv.agents
|
|
103
|
-
? argv.agents.split(',').map((a) => a.trim())
|
|
104
|
-
: undefined;
|
|
105
|
-
const configPath = argv.config;
|
|
106
|
-
const mcpEnabled = argv.mcp;
|
|
107
|
-
const mcpStrategy = argv['mcp-overwrite']
|
|
108
|
-
? 'overwrite'
|
|
109
|
-
: undefined;
|
|
110
|
-
const verbose = argv.verbose;
|
|
111
|
-
const dryRun = argv['dry-run'];
|
|
112
|
-
const localOnly = argv['local-only'];
|
|
113
|
-
// Determine gitignore preference: CLI > TOML > Default (enabled)
|
|
114
|
-
// yargs handles --no-gitignore by setting gitignore to false
|
|
115
|
-
let gitignorePreference;
|
|
116
|
-
if (argv.gitignore !== undefined) {
|
|
117
|
-
gitignorePreference = argv.gitignore;
|
|
118
|
-
}
|
|
119
|
-
else {
|
|
120
|
-
gitignorePreference = undefined; // Let TOML/default decide
|
|
121
|
-
}
|
|
122
|
-
try {
|
|
123
|
-
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly);
|
|
124
|
-
console.log('Ruler apply completed successfully.');
|
|
125
|
-
}
|
|
126
|
-
catch (err) {
|
|
127
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
128
|
-
console.error(`${constants_1.ERROR_PREFIX} ${message}`);
|
|
129
|
-
process.exit(1);
|
|
130
|
-
}
|
|
131
|
-
})
|
|
63
|
+
}, handlers_1.applyHandler)
|
|
132
64
|
.command('init', 'Scaffold a .ruler directory with default files', (y) => {
|
|
133
|
-
y
|
|
65
|
+
return y
|
|
66
|
+
.option('project-root', {
|
|
134
67
|
type: 'string',
|
|
135
68
|
description: 'Project root directory',
|
|
136
69
|
default: process.cwd(),
|
|
137
|
-
})
|
|
70
|
+
})
|
|
71
|
+
.option('global', {
|
|
138
72
|
type: 'boolean',
|
|
139
73
|
description: 'Initialize in global config directory (XDG_CONFIG_HOME/ruler)',
|
|
140
74
|
default: false,
|
|
141
75
|
});
|
|
142
|
-
},
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'ruler')
|
|
147
|
-
: path.join(projectRoot, '.ruler');
|
|
148
|
-
await fs_1.promises.mkdir(rulerDir, { recursive: true });
|
|
149
|
-
const instructionsPath = path.join(rulerDir, 'instructions.md');
|
|
150
|
-
const tomlPath = path.join(rulerDir, 'ruler.toml');
|
|
151
|
-
const exists = async (p) => {
|
|
152
|
-
try {
|
|
153
|
-
await fs_1.promises.access(p);
|
|
154
|
-
return true;
|
|
155
|
-
}
|
|
156
|
-
catch {
|
|
157
|
-
return false;
|
|
158
|
-
}
|
|
159
|
-
};
|
|
160
|
-
const DEFAULT_INSTRUCTIONS = `# Ruler Instructions
|
|
161
|
-
|
|
162
|
-
These are your centralised AI agent instructions.
|
|
163
|
-
Add your coding guidelines, style guides, and other project-specific context here.
|
|
164
|
-
|
|
165
|
-
Ruler will concatenate all .md files in this directory (and its subdirectories)
|
|
166
|
-
and apply them to your configured AI coding agents.
|
|
167
|
-
`;
|
|
168
|
-
const DEFAULT_TOML = `# Ruler Configuration File
|
|
169
|
-
# See https://ai.intellectronica.net/ruler for documentation.
|
|
170
|
-
|
|
171
|
-
# To specify which agents are active by default when --agents is not used,
|
|
172
|
-
# uncomment and populate the following line. If omitted, all agents are active.
|
|
173
|
-
# default_agents = ["copilot", "claude"]
|
|
174
|
-
|
|
175
|
-
# --- Agent Specific Configurations ---
|
|
176
|
-
# You can enable/disable agents and override their default output paths here.
|
|
177
|
-
# Use lowercase agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
|
|
178
|
-
|
|
179
|
-
# [agents.copilot]
|
|
180
|
-
# enabled = true
|
|
181
|
-
# output_path = ".github/copilot-instructions.md"
|
|
182
|
-
|
|
183
|
-
# [agents.claude]
|
|
184
|
-
# enabled = true
|
|
185
|
-
# output_path = "CLAUDE.md"
|
|
186
|
-
|
|
187
|
-
# [agents.codex]
|
|
188
|
-
# enabled = true
|
|
189
|
-
# output_path = "AGENTS.md"
|
|
190
|
-
|
|
191
|
-
# [agents.cursor]
|
|
192
|
-
# enabled = true
|
|
193
|
-
# output_path = ".cursor/rules/ruler_cursor_instructions.mdc"
|
|
194
|
-
|
|
195
|
-
# [agents.windsurf]
|
|
196
|
-
# enabled = true
|
|
197
|
-
# output_path = ".windsurf/rules/ruler_windsurf_instructions.md"
|
|
198
|
-
|
|
199
|
-
# [agents.cline]
|
|
200
|
-
# enabled = true
|
|
201
|
-
# output_path = ".clinerules"
|
|
202
|
-
|
|
203
|
-
# [agents.aider]
|
|
204
|
-
# enabled = true
|
|
205
|
-
# output_path_instructions = "ruler_aider_instructions.md"
|
|
206
|
-
# output_path_config = ".aider.conf.yml"
|
|
207
|
-
|
|
208
|
-
# [agents.firebase]
|
|
209
|
-
# enabled = true
|
|
210
|
-
# output_path = ".idx/airules.md"
|
|
211
|
-
|
|
212
|
-
# [agents.gemini-cli]
|
|
213
|
-
# enabled = true
|
|
214
|
-
|
|
215
|
-
# [agents.kilocode]
|
|
216
|
-
# enabled = true
|
|
217
|
-
# output_path = ".kilocode/rules/ruler_kilocode_instructions.md"
|
|
218
|
-
`;
|
|
219
|
-
if (!(await exists(instructionsPath))) {
|
|
220
|
-
await fs_1.promises.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
|
|
221
|
-
console.log(`[ruler] Created ${instructionsPath}`);
|
|
222
|
-
}
|
|
223
|
-
else {
|
|
224
|
-
console.log(`[ruler] instructions.md already exists, skipping`);
|
|
225
|
-
}
|
|
226
|
-
if (!(await exists(tomlPath))) {
|
|
227
|
-
await fs_1.promises.writeFile(tomlPath, DEFAULT_TOML);
|
|
228
|
-
console.log(`[ruler] Created ${tomlPath}`);
|
|
229
|
-
}
|
|
230
|
-
else {
|
|
231
|
-
console.log(`[ruler] ruler.toml already exists, skipping`);
|
|
232
|
-
}
|
|
233
|
-
const mcpPath = path.join(rulerDir, 'mcp.json');
|
|
234
|
-
const DEFAULT_MCP_JSON = `{
|
|
235
|
-
"mcpServers": {
|
|
236
|
-
"example": {
|
|
237
|
-
"type": "stdio",
|
|
238
|
-
"command": "node",
|
|
239
|
-
"args": ["/path/to/mcp-server.js"],
|
|
240
|
-
"env": {
|
|
241
|
-
"NODE_ENV": "production"
|
|
242
|
-
}
|
|
243
|
-
}
|
|
244
|
-
}
|
|
245
|
-
}\n`;
|
|
246
|
-
if (!(await exists(mcpPath))) {
|
|
247
|
-
await fs_1.promises.writeFile(mcpPath, DEFAULT_MCP_JSON);
|
|
248
|
-
console.log(`[ruler] Created ${mcpPath}`);
|
|
249
|
-
}
|
|
250
|
-
else {
|
|
251
|
-
console.log(`[ruler] mcp.json already exists, skipping`);
|
|
252
|
-
}
|
|
253
|
-
})
|
|
254
|
-
.command('revert', 'Revert ruler configurations by restoring backups and removing generated files', (y) => {
|
|
255
|
-
y.option('project-root', {
|
|
76
|
+
}, handlers_1.initHandler)
|
|
77
|
+
.command('revert', 'Revert ruler configurations from supported AI agents', (y) => {
|
|
78
|
+
return y
|
|
79
|
+
.option('project-root', {
|
|
256
80
|
type: 'string',
|
|
257
81
|
description: 'Project root directory',
|
|
258
82
|
default: process.cwd(),
|
|
259
|
-
})
|
|
260
|
-
|
|
83
|
+
})
|
|
84
|
+
.option('agents', {
|
|
261
85
|
type: 'string',
|
|
262
|
-
description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, crush',
|
|
263
|
-
})
|
|
264
|
-
|
|
86
|
+
description: 'Comma-separated list of agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, crush, zed, qwen',
|
|
87
|
+
})
|
|
88
|
+
.option('config', {
|
|
265
89
|
type: 'string',
|
|
266
90
|
description: 'Path to TOML configuration file',
|
|
267
|
-
})
|
|
268
|
-
|
|
91
|
+
})
|
|
92
|
+
.option('keep-backups', {
|
|
269
93
|
type: 'boolean',
|
|
270
|
-
description: 'Keep backup files
|
|
94
|
+
description: 'Keep backup files after revert',
|
|
271
95
|
default: false,
|
|
272
|
-
})
|
|
273
|
-
|
|
96
|
+
})
|
|
97
|
+
.option('verbose', {
|
|
274
98
|
type: 'boolean',
|
|
275
99
|
description: 'Enable verbose logging',
|
|
276
100
|
default: false,
|
|
277
|
-
})
|
|
278
|
-
|
|
279
|
-
|
|
101
|
+
})
|
|
102
|
+
.alias('verbose', 'v')
|
|
103
|
+
.option('dry-run', {
|
|
280
104
|
type: 'boolean',
|
|
281
|
-
description: 'Preview changes without
|
|
105
|
+
description: 'Preview changes without writing files',
|
|
282
106
|
default: false,
|
|
283
|
-
})
|
|
284
|
-
|
|
107
|
+
})
|
|
108
|
+
.option('local-only', {
|
|
285
109
|
type: 'boolean',
|
|
286
110
|
description: 'Only search for local .ruler directories, ignore global config',
|
|
287
111
|
default: false,
|
|
288
112
|
});
|
|
289
|
-
},
|
|
290
|
-
const projectRoot = argv['project-root'];
|
|
291
|
-
const agents = argv.agents
|
|
292
|
-
? argv.agents.split(',').map((a) => a.trim())
|
|
293
|
-
: undefined;
|
|
294
|
-
const configPath = argv.config;
|
|
295
|
-
const keepBackups = argv['keep-backups'];
|
|
296
|
-
const verbose = argv.verbose;
|
|
297
|
-
const dryRun = argv['dry-run'];
|
|
298
|
-
const localOnly = argv['local-only'];
|
|
299
|
-
try {
|
|
300
|
-
await (0, revert_1.revertAllAgentConfigs)(projectRoot, agents, configPath, keepBackups, verbose, dryRun, localOnly);
|
|
301
|
-
}
|
|
302
|
-
catch (err) {
|
|
303
|
-
const message = err instanceof Error ? err.message : String(err);
|
|
304
|
-
console.error(`${constants_1.ERROR_PREFIX} ${message}`);
|
|
305
|
-
process.exit(1);
|
|
306
|
-
}
|
|
307
|
-
})
|
|
113
|
+
}, handlers_1.revertHandler)
|
|
308
114
|
.demandCommand(1, 'You need to specify a command')
|
|
309
115
|
.help()
|
|
310
116
|
.strict()
|
|
@@ -0,0 +1,176 @@
|
|
|
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.applyHandler = applyHandler;
|
|
37
|
+
exports.initHandler = initHandler;
|
|
38
|
+
exports.revertHandler = revertHandler;
|
|
39
|
+
const lib_1 = require("../lib");
|
|
40
|
+
const revert_1 = require("../revert");
|
|
41
|
+
const path = __importStar(require("path"));
|
|
42
|
+
const os = __importStar(require("os"));
|
|
43
|
+
const fs = __importStar(require("fs/promises"));
|
|
44
|
+
const constants_1 = require("../constants");
|
|
45
|
+
/**
|
|
46
|
+
* Handler for the 'apply' command.
|
|
47
|
+
*/
|
|
48
|
+
async function applyHandler(argv) {
|
|
49
|
+
const projectRoot = argv['project-root'];
|
|
50
|
+
const agents = argv.agents
|
|
51
|
+
? argv.agents.split(',').map((a) => a.trim())
|
|
52
|
+
: undefined;
|
|
53
|
+
const configPath = argv.config;
|
|
54
|
+
const mcpEnabled = argv.mcp;
|
|
55
|
+
const mcpStrategy = argv['mcp-overwrite']
|
|
56
|
+
? 'overwrite'
|
|
57
|
+
: undefined;
|
|
58
|
+
const verbose = argv.verbose;
|
|
59
|
+
const dryRun = argv['dry-run'];
|
|
60
|
+
const localOnly = argv['local-only'];
|
|
61
|
+
// Determine gitignore preference: CLI > TOML > Default (enabled)
|
|
62
|
+
// yargs handles --no-gitignore by setting gitignore to false
|
|
63
|
+
let gitignorePreference;
|
|
64
|
+
if (argv.gitignore !== undefined) {
|
|
65
|
+
gitignorePreference = argv.gitignore;
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
gitignorePreference = undefined; // Let TOML/default decide
|
|
69
|
+
}
|
|
70
|
+
try {
|
|
71
|
+
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly);
|
|
72
|
+
console.log('Ruler apply completed successfully.');
|
|
73
|
+
}
|
|
74
|
+
catch (err) {
|
|
75
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
76
|
+
console.error(`${constants_1.ERROR_PREFIX} ${message}`);
|
|
77
|
+
process.exit(1);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Handler for the 'init' command.
|
|
82
|
+
*/
|
|
83
|
+
async function initHandler(argv) {
|
|
84
|
+
const projectRoot = argv['project-root'];
|
|
85
|
+
const isGlobal = argv['global'];
|
|
86
|
+
const rulerDir = isGlobal
|
|
87
|
+
? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'ruler')
|
|
88
|
+
: path.join(projectRoot, '.ruler');
|
|
89
|
+
await fs.mkdir(rulerDir, { recursive: true });
|
|
90
|
+
const instructionsPath = path.join(rulerDir, constants_1.DEFAULT_RULES_FILENAME); // .ruler/AGENTS.md
|
|
91
|
+
const tomlPath = path.join(rulerDir, 'ruler.toml');
|
|
92
|
+
const exists = async (p) => {
|
|
93
|
+
try {
|
|
94
|
+
await fs.access(p);
|
|
95
|
+
return true;
|
|
96
|
+
}
|
|
97
|
+
catch {
|
|
98
|
+
return false;
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
const DEFAULT_INSTRUCTIONS = `# AGENTS.md\n\nCentralised AI agent instructions. Add coding guidelines, style guides, and project context here.\n\nRuler concatenates all .md files in this directory (and subdirectories), starting with AGENTS.md (if present), then remaining files in sorted order.\n`;
|
|
102
|
+
const DEFAULT_TOML = `# Ruler Configuration File
|
|
103
|
+
# See https://ai.intellectronica.net/ruler for documentation.
|
|
104
|
+
|
|
105
|
+
# To specify which agents are active by default when --agents is not used,
|
|
106
|
+
# uncomment and populate the following line. If omitted, all agents are active.
|
|
107
|
+
# default_agents = ["copilot", "claude"]
|
|
108
|
+
|
|
109
|
+
# --- Agent Specific Configurations ---
|
|
110
|
+
# You can enable/disable agents and override their default output paths here.
|
|
111
|
+
# Use lowercase agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
|
|
112
|
+
|
|
113
|
+
# [agents.copilot]
|
|
114
|
+
# enabled = true
|
|
115
|
+
# output_path = ".github/copilot-instructions.md"
|
|
116
|
+
|
|
117
|
+
# [agents.aider]
|
|
118
|
+
# enabled = true
|
|
119
|
+
# output_path_instructions = "AGENTS.md"
|
|
120
|
+
# output_path_config = ".aider.conf.yml"
|
|
121
|
+
|
|
122
|
+
# [agents.gemini-cli]
|
|
123
|
+
# enabled = true
|
|
124
|
+
|
|
125
|
+
# --- MCP Servers ---
|
|
126
|
+
# Define Model Context Protocol servers here. Two examples:
|
|
127
|
+
# 1. A stdio server (local executable)
|
|
128
|
+
# 2. A remote server (HTTP-based)
|
|
129
|
+
|
|
130
|
+
# [mcp_servers.example_stdio]
|
|
131
|
+
# command = "node"
|
|
132
|
+
# args = ["scripts/your-mcp-server.js"]
|
|
133
|
+
# env = { API_KEY = "replace_me" }
|
|
134
|
+
|
|
135
|
+
# [mcp_servers.example_remote]
|
|
136
|
+
# url = "https://api.example.com/mcp"
|
|
137
|
+
# headers = { Authorization = "Bearer REPLACE_ME" }
|
|
138
|
+
`;
|
|
139
|
+
if (!(await exists(instructionsPath))) {
|
|
140
|
+
// Create new AGENTS.md regardless of legacy presence.
|
|
141
|
+
await fs.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
|
|
142
|
+
console.log(`[ruler] Created ${instructionsPath}`);
|
|
143
|
+
}
|
|
144
|
+
else {
|
|
145
|
+
console.log(`[ruler] ${constants_1.DEFAULT_RULES_FILENAME} already exists, skipping`);
|
|
146
|
+
}
|
|
147
|
+
if (!(await exists(tomlPath))) {
|
|
148
|
+
await fs.writeFile(tomlPath, DEFAULT_TOML);
|
|
149
|
+
console.log(`[ruler] Created ${tomlPath}`);
|
|
150
|
+
}
|
|
151
|
+
else {
|
|
152
|
+
console.log(`[ruler] ruler.toml already exists, skipping`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
/**
|
|
156
|
+
* Handler for the 'revert' command.
|
|
157
|
+
*/
|
|
158
|
+
async function revertHandler(argv) {
|
|
159
|
+
const projectRoot = argv['project-root'];
|
|
160
|
+
const agents = argv.agents
|
|
161
|
+
? argv.agents.split(',').map((a) => a.trim())
|
|
162
|
+
: undefined;
|
|
163
|
+
const configPath = argv.config;
|
|
164
|
+
const keepBackups = argv['keep-backups'];
|
|
165
|
+
const verbose = argv.verbose;
|
|
166
|
+
const dryRun = argv['dry-run'];
|
|
167
|
+
const localOnly = argv['local-only'];
|
|
168
|
+
try {
|
|
169
|
+
await (0, revert_1.revertAllAgentConfigs)(projectRoot, agents, configPath, keepBackups, verbose, dryRun, localOnly);
|
|
170
|
+
}
|
|
171
|
+
catch (err) {
|
|
172
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
173
|
+
console.error(`${constants_1.ERROR_PREFIX} ${message}`);
|
|
174
|
+
process.exit(1);
|
|
175
|
+
}
|
|
176
|
+
}
|
package/dist/constants.js
CHANGED
|
@@ -1,9 +1,16 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
-
exports.ERROR_PREFIX = void 0;
|
|
3
|
+
exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
4
|
+
exports.actionPrefix = actionPrefix;
|
|
4
5
|
exports.createRulerError = createRulerError;
|
|
5
6
|
exports.logVerbose = logVerbose;
|
|
6
|
-
exports.ERROR_PREFIX = '[
|
|
7
|
+
exports.ERROR_PREFIX = '[ruler]';
|
|
8
|
+
// Centralized default rules filename. Now points to 'AGENTS.md'.
|
|
9
|
+
// Legacy '.ruler/instructions.md' is still supported as a fallback with a warning.
|
|
10
|
+
exports.DEFAULT_RULES_FILENAME = 'AGENTS.md';
|
|
11
|
+
function actionPrefix(dry) {
|
|
12
|
+
return dry ? '[ruler:dry-run]' : '[ruler]';
|
|
13
|
+
}
|
|
7
14
|
function createRulerError(message, context) {
|
|
8
15
|
const fullMessage = context
|
|
9
16
|
? `${exports.ERROR_PREFIX} ${message} (Context: ${context})`
|
|
@@ -105,7 +105,7 @@ async function loadConfig(options) {
|
|
|
105
105
|
}
|
|
106
106
|
catch (err) {
|
|
107
107
|
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
108
|
-
if (err.message.includes('[
|
|
108
|
+
if (err.message.includes('[ruler]')) {
|
|
109
109
|
throw err; // Re-throw validation errors
|
|
110
110
|
}
|
|
111
111
|
console.warn(`[ruler] Warning: could not read config file at ${configFile}: ${err.message}`);
|
|
@@ -92,7 +92,8 @@ async function findRulerDir(startPath, checkGlobal = true) {
|
|
|
92
92
|
* Files are sorted alphabetically by path.
|
|
93
93
|
*/
|
|
94
94
|
async function readMarkdownFiles(rulerDir) {
|
|
95
|
-
const
|
|
95
|
+
const mdFiles = [];
|
|
96
|
+
// Gather all markdown files (recursive) first
|
|
96
97
|
async function walk(dir) {
|
|
97
98
|
const entries = await fs_1.promises.readdir(dir, { withFileTypes: true });
|
|
98
99
|
for (const entry of entries) {
|
|
@@ -102,13 +103,59 @@ async function readMarkdownFiles(rulerDir) {
|
|
|
102
103
|
}
|
|
103
104
|
else if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
104
105
|
const content = await fs_1.promises.readFile(fullPath, 'utf8');
|
|
105
|
-
|
|
106
|
+
mdFiles.push({ path: fullPath, content });
|
|
106
107
|
}
|
|
107
108
|
}
|
|
108
109
|
}
|
|
109
110
|
await walk(rulerDir);
|
|
110
|
-
|
|
111
|
-
|
|
111
|
+
// Prioritisation logic:
|
|
112
|
+
// 1. Prefer top-level AGENTS.md if present.
|
|
113
|
+
// 2. If AGENTS.md absent but legacy instructions.md present, use it (no longer emits a warning; legacy accepted silently).
|
|
114
|
+
// 3. Include any remaining .md files (excluding whichever of the above was used if present) in
|
|
115
|
+
// sorted order AFTER the preferred primary file so that new concatenation priority starts with AGENTS.md.
|
|
116
|
+
const topLevelAgents = path.join(rulerDir, 'AGENTS.md');
|
|
117
|
+
const topLevelLegacy = path.join(rulerDir, 'instructions.md');
|
|
118
|
+
// Separate primary candidates from others
|
|
119
|
+
let primaryFile = null;
|
|
120
|
+
const others = [];
|
|
121
|
+
for (const f of mdFiles) {
|
|
122
|
+
if (f.path === topLevelAgents) {
|
|
123
|
+
primaryFile = f; // Highest priority
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (!primaryFile) {
|
|
127
|
+
for (const f of mdFiles) {
|
|
128
|
+
if (f.path === topLevelLegacy) {
|
|
129
|
+
primaryFile = f;
|
|
130
|
+
break;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
for (const f of mdFiles) {
|
|
135
|
+
if (primaryFile && f.path === primaryFile.path)
|
|
136
|
+
continue;
|
|
137
|
+
others.push(f);
|
|
138
|
+
}
|
|
139
|
+
// Sort the remaining others for stable deterministic concatenation order.
|
|
140
|
+
others.sort((a, b) => a.path.localeCompare(b.path));
|
|
141
|
+
let ordered = primaryFile ? [primaryFile, ...others] : others;
|
|
142
|
+
// NEW: Prepend repository root AGENTS.md (outside .ruler) if it exists and is not identical path.
|
|
143
|
+
try {
|
|
144
|
+
const repoRoot = path.dirname(rulerDir); // .ruler parent
|
|
145
|
+
const rootAgentsPath = path.join(repoRoot, 'AGENTS.md');
|
|
146
|
+
if (path.resolve(rootAgentsPath) !== path.resolve(topLevelAgents)) {
|
|
147
|
+
const stat = await fs_1.promises.stat(rootAgentsPath);
|
|
148
|
+
if (stat.isFile()) {
|
|
149
|
+
const content = await fs_1.promises.readFile(rootAgentsPath, 'utf8');
|
|
150
|
+
// Prepend so it has highest precedence
|
|
151
|
+
ordered = [{ path: rootAgentsPath, content }, ...ordered];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
catch {
|
|
156
|
+
// ignore if root AGENTS.md not present
|
|
157
|
+
}
|
|
158
|
+
return ordered;
|
|
112
159
|
}
|
|
113
160
|
/**
|
|
114
161
|
* Writes content to filePath, creating parent directories if necessary.
|
|
@@ -39,10 +39,22 @@ const path = __importStar(require("path"));
|
|
|
39
39
|
* Concatenates markdown rule files into a single string,
|
|
40
40
|
* marking each section with its source filename.
|
|
41
41
|
*/
|
|
42
|
-
function concatenateRules(files) {
|
|
42
|
+
function concatenateRules(files, baseDir) {
|
|
43
|
+
const base = baseDir || process.cwd();
|
|
43
44
|
const sections = files.map(({ path: filePath, content }) => {
|
|
44
|
-
const rel = path.relative(
|
|
45
|
-
|
|
45
|
+
const rel = path.relative(base, filePath);
|
|
46
|
+
// Normalize path separators to forward slashes for consistent output across platforms
|
|
47
|
+
const normalizedRel = rel.replace(/\\/g, '/');
|
|
48
|
+
// New format: two leading blank lines, HTML comment with source, one blank line, then content, then trailing newline
|
|
49
|
+
// We intentionally trim content to avoid cascading blank lines, then ensure a final newline via join logic
|
|
50
|
+
return [
|
|
51
|
+
'', // first leading blank line
|
|
52
|
+
'', // second leading blank line
|
|
53
|
+
`<!-- Source: ${normalizedRel} -->`,
|
|
54
|
+
'', // single blank line after the comment
|
|
55
|
+
content.trim(),
|
|
56
|
+
'', // ensure file section ends with newline
|
|
57
|
+
].join('\n');
|
|
46
58
|
});
|
|
47
59
|
return sections.join('\n');
|
|
48
60
|
}
|