@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.
Files changed (47) hide show
  1. package/README.md +190 -30
  2. package/dist/agents/AbstractAgent.js +82 -0
  3. package/dist/agents/AgentsMdAgent.js +82 -0
  4. package/dist/agents/AiderAgent.js +29 -9
  5. package/dist/agents/AmpAgent.js +13 -0
  6. package/dist/agents/AugmentCodeAgent.js +10 -12
  7. package/dist/agents/ClaudeAgent.js +9 -9
  8. package/dist/agents/ClineAgent.js +3 -9
  9. package/dist/agents/CodexCliAgent.js +27 -21
  10. package/dist/agents/CopilotAgent.js +9 -10
  11. package/dist/agents/CrushAgent.js +6 -0
  12. package/dist/agents/CursorAgent.js +13 -5
  13. package/dist/agents/FirebaseAgent.js +2 -8
  14. package/dist/agents/GeminiCliAgent.js +31 -22
  15. package/dist/agents/GooseAgent.js +2 -10
  16. package/dist/agents/JulesAgent.js +3 -44
  17. package/dist/agents/JunieAgent.js +2 -9
  18. package/dist/agents/KiloCodeAgent.js +8 -9
  19. package/dist/agents/KiroAgent.js +50 -0
  20. package/dist/agents/OpenCodeAgent.js +50 -11
  21. package/dist/agents/OpenHandsAgent.js +8 -9
  22. package/dist/agents/QwenCodeAgent.js +83 -0
  23. package/dist/agents/WarpAgent.js +61 -0
  24. package/dist/agents/WindsurfAgent.js +9 -10
  25. package/dist/agents/ZedAgent.js +132 -0
  26. package/dist/agents/agent-utils.js +37 -0
  27. package/dist/agents/index.js +53 -0
  28. package/dist/cli/commands.js +48 -242
  29. package/dist/cli/handlers.js +176 -0
  30. package/dist/constants.js +9 -2
  31. package/dist/core/ConfigLoader.js +1 -1
  32. package/dist/core/FileSystemUtils.js +51 -4
  33. package/dist/core/RuleProcessor.js +15 -3
  34. package/dist/core/UnifiedConfigLoader.js +357 -0
  35. package/dist/core/UnifiedConfigTypes.js +2 -0
  36. package/dist/core/agent-selection.js +52 -0
  37. package/dist/core/apply-engine.js +302 -0
  38. package/dist/core/config-utils.js +30 -0
  39. package/dist/core/hash.js +24 -0
  40. package/dist/core/revert-engine.js +413 -0
  41. package/dist/lib.js +20 -312
  42. package/dist/mcp/capabilities.js +51 -0
  43. package/dist/mcp/propagateOpenCodeMcp.js +30 -31
  44. package/dist/mcp/propagateOpenHandsMcp.js +101 -17
  45. package/dist/paths/mcp.js +7 -3
  46. package/dist/revert.js +96 -479
  47. package/package.json +2 -1
@@ -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 lib_1 = require("../lib");
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.option('project-root', {
18
+ return y
19
+ .option('project-root', {
57
20
  type: 'string',
58
21
  description: 'Project root directory',
59
22
  default: process.cwd(),
60
- });
61
- y.option('agents', {
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
- y.option('config', {
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
- y.option('mcp', {
31
+ })
32
+ .option('mcp', {
70
33
  type: 'boolean',
71
34
  description: 'Enable or disable applying MCP server config',
72
35
  default: true,
73
- });
74
- y.alias('mcp', 'with-mcp');
75
- y.option('mcp-overwrite', {
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
- y.option('gitignore', {
42
+ })
43
+ .option('gitignore', {
81
44
  type: 'boolean',
82
45
  description: 'Enable/disable automatic .gitignore updates (default: enabled)',
83
- });
84
- y.option('verbose', {
46
+ })
47
+ .option('verbose', {
85
48
  type: 'boolean',
86
49
  description: 'Enable verbose logging',
87
50
  default: false,
88
- });
89
- y.alias('verbose', 'v');
90
- y.option('dry-run', {
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
- y.option('local-only', {
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
- }, async (argv) => {
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.option('project-root', {
65
+ return y
66
+ .option('project-root', {
134
67
  type: 'string',
135
68
  description: 'Project root directory',
136
69
  default: process.cwd(),
137
- }).option('global', {
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
- }, async (argv) => {
143
- const projectRoot = argv['project-root'];
144
- const isGlobal = argv['global'];
145
- const rulerDir = isGlobal
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
- y.option('agents', {
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
- y.option('config', {
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
- y.option('keep-backups', {
91
+ })
92
+ .option('keep-backups', {
269
93
  type: 'boolean',
270
- description: 'Keep backup files (.bak) after restoration',
94
+ description: 'Keep backup files after revert',
271
95
  default: false,
272
- });
273
- y.option('verbose', {
96
+ })
97
+ .option('verbose', {
274
98
  type: 'boolean',
275
99
  description: 'Enable verbose logging',
276
100
  default: false,
277
- });
278
- y.alias('verbose', 'v');
279
- y.option('dry-run', {
101
+ })
102
+ .alias('verbose', 'v')
103
+ .option('dry-run', {
280
104
  type: 'boolean',
281
- description: 'Preview changes without actually reverting files',
105
+ description: 'Preview changes without writing files',
282
106
  default: false,
283
- });
284
- y.option('local-only', {
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
- }, async (argv) => {
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 = '[RulerError]';
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('[RulerError]')) {
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 results = [];
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
- results.push({ path: fullPath, content });
106
+ mdFiles.push({ path: fullPath, content });
106
107
  }
107
108
  }
108
109
  }
109
110
  await walk(rulerDir);
110
- results.sort((a, b) => a.path.localeCompare(b.path));
111
- return results;
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(process.cwd(), filePath);
45
- return ['---', `Source: ${rel}`, '---', content.trim(), ''].join('\n');
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
  }