@intellectronica/ruler 0.2.17 → 0.2.19

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
@@ -45,6 +45,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
45
45
  | Cursor | `.cursor/rules/ruler_cursor_instructions.mdc` | `.cursor/mcp.json`, `~/.cursor/mcp.json` |
46
46
  | Windsurf | `.windsurf/rules/ruler_windsurf_instructions.md` | `~/.codeium/windsurf/mcp_config.json` |
47
47
  | Cline | `.clinerules` | - |
48
+ | Amp | `AGENT.md` | - |
48
49
  | Aider | `ruler_aider_instructions.md`, `.aider.conf.yml` | `.mcp.json` |
49
50
  | Firebase Studio | `.idx/airules.md` | - |
50
51
  | Open Hands | `.openhands/microagents/repo.md` | `.openhands/config.toml` |
@@ -150,7 +151,7 @@ The `apply` command looks for `.ruler/` in the current directory tree, reading t
150
151
  | Option | Description |
151
152
  | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------- |
152
153
  | `--project-root <path>` | Path to your project's root (default: current directory) |
153
- | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode) |
154
+ | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to target (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, augmentcode, kilocode) |
154
155
  | `--config <path>` | Path to a custom `ruler.toml` configuration file |
155
156
  | `--mcp` / `--with-mcp` | Enable applying MCP server configurations (default: true) |
156
157
  | `--no-mcp` | Disable applying MCP server configurations |
@@ -222,7 +223,7 @@ ruler revert [options]
222
223
  | Option | Description |
223
224
  | ------------------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------- |
224
225
  | `--project-root <path>` | Path to your project's root (default: current directory) |
225
- | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode) |
226
+ | `--agents <agent1,agent2,...>` | Comma-separated list of agent names to revert (amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode) |
226
227
  | `--config <path>` | Path to a custom `ruler.toml` configuration file |
227
228
  | `--keep-backups` | Keep backup files (.bak) after restoration (default: false) |
228
229
  | `--dry-run` | Preview changes without actually reverting files |
@@ -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.AmpAgent = void 0;
37
+ const FileSystemUtils_1 = require("../core/FileSystemUtils");
38
+ const path = __importStar(require("path"));
39
+ class AmpAgent {
40
+ getIdentifier() {
41
+ return 'amp';
42
+ }
43
+ getName() {
44
+ return 'Amp';
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, 'AGENT.md');
53
+ }
54
+ }
55
+ exports.AmpAgent = AmpAgent;
@@ -0,0 +1,88 @@
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.CrushAgent = void 0;
37
+ const fs = __importStar(require("fs/promises"));
38
+ const path = __importStar(require("path"));
39
+ class CrushAgent {
40
+ getIdentifier() {
41
+ return 'crush';
42
+ }
43
+ getName() {
44
+ return 'Crush';
45
+ }
46
+ getDefaultOutputPath(projectRoot) {
47
+ return {
48
+ instructions: path.join(projectRoot, 'CRUSH.md'),
49
+ mcp: path.join(projectRoot, '.crush.json'),
50
+ };
51
+ }
52
+ async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
53
+ const outputPaths = this.getDefaultOutputPath(projectRoot);
54
+ const instructionsPath = agentConfig?.outputPathInstructions ?? outputPaths['instructions'];
55
+ const mcpPath = agentConfig?.outputPathConfig ?? outputPaths['mcp'];
56
+ await fs.writeFile(instructionsPath, concatenatedRules);
57
+ // Always transform from mcpServers ({ mcpServers: ... }) to { mcp: ... } for Crush
58
+ let finalMcpConfig = { mcp: {} };
59
+ try {
60
+ const existingMcpConfig = JSON.parse(await fs.readFile(mcpPath, 'utf-8'));
61
+ if (existingMcpConfig && typeof existingMcpConfig === 'object') {
62
+ finalMcpConfig = {
63
+ ...existingMcpConfig,
64
+ mcp: {
65
+ ...(existingMcpConfig.mcp || {}),
66
+ ...(rulerMcpJson?.mcpServers ?? {}),
67
+ },
68
+ };
69
+ }
70
+ else if (rulerMcpJson) {
71
+ finalMcpConfig = {
72
+ mcp: (rulerMcpJson?.mcpServers ?? {}),
73
+ };
74
+ }
75
+ }
76
+ catch {
77
+ if (rulerMcpJson) {
78
+ finalMcpConfig = {
79
+ mcp: (rulerMcpJson?.mcpServers ?? {}),
80
+ };
81
+ }
82
+ }
83
+ if (Object.keys(finalMcpConfig.mcp).length > 0) {
84
+ await fs.writeFile(mcpPath, JSON.stringify(finalMcpConfig, null, 2));
85
+ }
86
+ }
87
+ }
88
+ exports.CrushAgent = CrushAgent;
@@ -60,7 +60,7 @@ function run() {
60
60
  });
61
61
  y.option('agents', {
62
62
  type: 'string',
63
- description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode',
63
+ description: 'Comma-separated list of agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, crush',
64
64
  });
65
65
  y.option('config', {
66
66
  type: 'string',
@@ -174,7 +174,7 @@ and apply them to your configured AI coding agents.
174
174
 
175
175
  # --- Agent Specific Configurations ---
176
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
177
+ # Use lowercase agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
178
178
 
179
179
  # [agents.copilot]
180
180
  # enabled = true
@@ -234,10 +234,15 @@ and apply them to your configured AI coding agents.
234
234
  const DEFAULT_MCP_JSON = `{
235
235
  "mcpServers": {
236
236
  "example": {
237
- "url": "https://mcp.example.com"
237
+ "type": "stdio",
238
+ "command": "node",
239
+ "args": ["/path/to/mcp-server.js"],
240
+ "env": {
241
+ "NODE_ENV": "production"
242
+ }
238
243
  }
239
244
  }
240
- }`;
245
+ }\n`;
241
246
  if (!(await exists(mcpPath))) {
242
247
  await fs_1.promises.writeFile(mcpPath, DEFAULT_MCP_JSON);
243
248
  console.log(`[ruler] Created ${mcpPath}`);
@@ -254,7 +259,7 @@ and apply them to your configured AI coding agents.
254
259
  });
255
260
  y.option('agents', {
256
261
  type: 'string',
257
- description: 'Comma-separated list of agent identifiers: copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode',
262
+ description: 'Comma-separated list of agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, firebase, gemini-cli, junie, kilocode, opencode, crush',
258
263
  });
259
264
  y.option('config', {
260
265
  type: 'string',
package/dist/lib.js CHANGED
@@ -45,7 +45,7 @@ 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 = __importStar(require("./agents/ClineAgent"));
48
+ const ClineAgent_1 = 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");
@@ -55,7 +55,9 @@ const JunieAgent_1 = require("./agents/JunieAgent");
55
55
  const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
56
56
  const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
57
57
  const OpenCodeAgent_1 = require("./agents/OpenCodeAgent");
58
+ const CrushAgent_1 = require("./agents/CrushAgent");
58
59
  const GooseAgent_1 = require("./agents/GooseAgent");
60
+ const AmpAgent_1 = require("./agents/AmpAgent");
59
61
  const merge_1 = require("./mcp/merge");
60
62
  const validate_1 = require("./mcp/validate");
61
63
  const mcp_1 = require("./paths/mcp");
@@ -102,7 +104,7 @@ const agents = [
102
104
  new CodexCliAgent_1.CodexCliAgent(),
103
105
  new CursorAgent_1.CursorAgent(),
104
106
  new WindsurfAgent_1.WindsurfAgent(),
105
- new ClineAgent.ClineAgent(),
107
+ new ClineAgent_1.ClineAgent(),
106
108
  new AiderAgent_1.AiderAgent(),
107
109
  new FirebaseAgent_1.FirebaseAgent(),
108
110
  new OpenHandsAgent_1.OpenHandsAgent(),
@@ -113,6 +115,8 @@ const agents = [
113
115
  new KiloCodeAgent_1.KiloCodeAgent(),
114
116
  new OpenCodeAgent_1.OpenCodeAgent(),
115
117
  new GooseAgent_1.GooseAgent(),
118
+ new CrushAgent_1.CrushAgent(),
119
+ new AmpAgent_1.AmpAgent(),
116
120
  ];
117
121
  /**
118
122
  * Applies ruler configurations for all supported AI agents.
@@ -178,12 +182,28 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
178
182
  let selected = agents;
179
183
  if (config.cliAgents && config.cliAgents.length > 0) {
180
184
  const filters = config.cliAgents.map((n) => n.toLowerCase());
185
+ // Check if any of the specified agents don't exist
186
+ const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
187
+ const validAgentNames = new Set(agents.map((agent) => agent.getName().toLowerCase()));
188
+ const invalidAgents = filters.filter((filter) => !validAgentIdentifiers.has(filter) &&
189
+ ![...validAgentNames].some((name) => name.includes(filter)));
190
+ if (invalidAgents.length > 0) {
191
+ throw (0, constants_1.createRulerError)(`Invalid agent specified: ${invalidAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
192
+ }
181
193
  selected = agents.filter((agent) => filters.some((f) => agent.getIdentifier() === f ||
182
194
  agent.getName().toLowerCase().includes(f)));
183
195
  (0, constants_1.logVerbose)(`Selected agents via CLI filter: ${selected.map((a) => a.getName()).join(', ')}`, verbose);
184
196
  }
185
197
  else if (config.defaultAgents && config.defaultAgents.length > 0) {
186
198
  const defaults = config.defaultAgents.map((n) => n.toLowerCase());
199
+ // Check if any of the default agents don't exist
200
+ const validAgentIdentifiers = new Set(agents.map((agent) => agent.getIdentifier()));
201
+ const validAgentNames = new Set(agents.map((agent) => agent.getName().toLowerCase()));
202
+ const invalidAgents = defaults.filter((filter) => !validAgentIdentifiers.has(filter) &&
203
+ ![...validAgentNames].some((name) => name.includes(filter)));
204
+ if (invalidAgents.length > 0) {
205
+ throw (0, constants_1.createRulerError)(`Invalid agent specified in default_agents: ${invalidAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
206
+ }
187
207
  selected = agents.filter((agent) => {
188
208
  const identifier = agent.getIdentifier();
189
209
  const override = config.agentConfigs[identifier]?.enabled;
package/dist/mcp/merge.js CHANGED
@@ -12,15 +12,23 @@ exports.mergeMcp = mergeMcp;
12
12
  function mergeMcp(base, incoming, strategy, serverKey) {
13
13
  if (strategy === 'overwrite') {
14
14
  // Ensure the incoming object uses the correct server key.
15
- const incomingServers = incoming.mcpServers || {};
15
+ // Transform from the standard (Crush) MCP config format
16
+ const incomingServers = incoming[serverKey] ||
17
+ incoming.mcpServers ||
18
+ incoming.mcp ||
19
+ {};
16
20
  return {
17
21
  [serverKey]: incomingServers,
18
22
  };
19
23
  }
20
24
  const baseServers = base[serverKey] ||
21
25
  base.mcpServers ||
22
- {}; // Handle legacy key in existing files
23
- const incomingServers = incoming.mcpServers || {};
26
+ base.mcp ||
27
+ {};
28
+ const incomingServers = incoming[serverKey] ||
29
+ incoming.mcpServers ||
30
+ incoming.mcp ||
31
+ {};
24
32
  const mergedServers = { ...baseServers, ...incomingServers };
25
33
  const newBase = { ...base };
26
34
  delete newBase.mcpServers; // Remove old key if present
@@ -48,6 +48,7 @@ async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
48
48
  catch {
49
49
  return;
50
50
  }
51
+ // Always use the legacy Ruler MCP config format as input (top-level "mcpServers" key)
51
52
  const rulerServers = rulerMcp.mcpServers || {};
52
53
  let config = {};
53
54
  try {
@@ -12,6 +12,6 @@ function validateMcp(data) {
12
12
  typeof data !== 'object' ||
13
13
  !('mcpServers' in data) ||
14
14
  typeof data.mcpServers !== 'object') {
15
- throw new Error('[ruler] Invalid .ruler/mcp.json: must contain an object property "mcpServers"');
15
+ throw new Error('[ruler] Invalid MCP config: must contain an object property "mcpServers" (Ruler style)');
16
16
  }
17
17
  }
package/dist/revert.js CHANGED
@@ -54,6 +54,7 @@ const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
54
54
  const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
55
55
  const OpenCodeAgent_1 = require("./agents/OpenCodeAgent");
56
56
  const GooseAgent_1 = require("./agents/GooseAgent");
57
+ const AmpAgent_1 = require("./agents/AmpAgent");
57
58
  const mcp_1 = require("./paths/mcp");
58
59
  const constants_1 = require("./constants");
59
60
  const settings_1 = require("./vscode/settings");
@@ -74,6 +75,7 @@ const agents = [
74
75
  new KiloCodeAgent_1.KiloCodeAgent(),
75
76
  new OpenCodeAgent_1.OpenCodeAgent(),
76
77
  new GooseAgent_1.GooseAgent(),
78
+ new AmpAgent_1.AmpAgent(),
77
79
  ];
78
80
  /**
79
81
  * Gets all output paths for an agent, taking into account any config overrides.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@intellectronica/ruler",
3
- "version": "0.2.17",
3
+ "version": "0.2.19",
4
4
  "description": "Ruler — apply the same rules to all coding agents",
5
5
  "main": "dist/lib.js",
6
6
  "scripts": {