@intellectronica/ruler 0.2.14 → 0.2.16
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/CodexCliAgent.js +104 -7
- package/dist/agents/GooseAgent.js +66 -0
- package/dist/core/ConfigLoader.js +2 -5
- package/dist/lib.js +2 -0
- package/dist/mcp/propagateOpenHandsMcp.js +4 -6
- package/dist/revert.js +2 -0
- package/package.json +2 -1
package/README.md
CHANGED
|
@@ -40,7 +40,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
40
40
|
| ---------------- | ------------------------------------------------ | --------------------------------------------------- |
|
|
41
41
|
| GitHub Copilot | `.github/copilot-instructions.md` | `.vscode/mcp.json` |
|
|
42
42
|
| Claude Code | `CLAUDE.md` | `claude_desktop_config.json` |
|
|
43
|
-
| OpenAI Codex CLI | `AGENTS.md` | `~/.codex/config.json`
|
|
43
|
+
| OpenAI Codex CLI | `AGENTS.md` | `.codex/config.toml`, `~/.codex/config.json` |
|
|
44
44
|
| Jules | `AGENTS.md` | - |
|
|
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` |
|
|
@@ -53,6 +53,7 @@ Ruler solves this by providing a **single source of truth** for all your AI agen
|
|
|
53
53
|
| AugmentCode | `.augment/rules/ruler_augment_instructions.md` | `.vscode/settings.json` |
|
|
54
54
|
| Kilo Code | `.kilocode/rules/ruler_kilocode_instructions.md` | `.kilocode/mcp.json` |
|
|
55
55
|
| OpenCode | `AGENTS.md` | `opencode.json`, `~/.config/opencode/opencode.json` |
|
|
56
|
+
| Goose | `.goosehints` | - |
|
|
56
57
|
|
|
57
58
|
## Getting Started
|
|
58
59
|
|
|
@@ -299,6 +300,17 @@ enabled = true
|
|
|
299
300
|
output_path_instructions = "ruler_aider_instructions.md"
|
|
300
301
|
output_path_config = ".aider.conf.yml"
|
|
301
302
|
|
|
303
|
+
# OpenAI Codex CLI agent and MCP config
|
|
304
|
+
[agents.codex]
|
|
305
|
+
enabled = true
|
|
306
|
+
output_path = "AGENTS.md"
|
|
307
|
+
output_path_config = ".codex/config.toml"
|
|
308
|
+
|
|
309
|
+
# Agent-specific MCP configuration for Codex CLI
|
|
310
|
+
[agents.codex.mcp]
|
|
311
|
+
enabled = true
|
|
312
|
+
merge_strategy = "merge"
|
|
313
|
+
|
|
302
314
|
[agents.firebase]
|
|
303
315
|
enabled = true
|
|
304
316
|
output_path = ".idx/airules.md"
|
|
@@ -360,8 +372,14 @@ Define your project's MCP servers:
|
|
|
360
372
|
}
|
|
361
373
|
```
|
|
362
374
|
|
|
375
|
+
|
|
363
376
|
Ruler uses this file with the `merge` (default) or `overwrite` strategy, controlled by `ruler.toml` or CLI flags.
|
|
364
377
|
|
|
378
|
+
**Note for OpenAI Codex CLI:** To apply the local Codex CLI MCP configuration, set the `CODEX_HOME` environment variable to your project’s `.codex` directory:
|
|
379
|
+
```bash
|
|
380
|
+
export CODEX_HOME="$(pwd)/.codex"
|
|
381
|
+
```
|
|
382
|
+
|
|
365
383
|
## `.gitignore` Integration
|
|
366
384
|
|
|
367
385
|
Ruler automatically manages your `.gitignore` file to keep generated agent configuration files out of version control.
|
|
@@ -35,9 +35,13 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
35
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
36
|
exports.CodexCliAgent = void 0;
|
|
37
37
|
const path = __importStar(require("path"));
|
|
38
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
39
|
+
const fs_1 = require("fs");
|
|
40
|
+
const toml = __importStar(require("toml"));
|
|
41
|
+
const toml_1 = require("@iarna/toml");
|
|
38
42
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
43
|
/**
|
|
40
|
-
* OpenAI Codex CLI agent adapter
|
|
44
|
+
* OpenAI Codex CLI agent adapter.
|
|
41
45
|
*/
|
|
42
46
|
class CodexCliAgent {
|
|
43
47
|
getIdentifier() {
|
|
@@ -46,14 +50,107 @@ class CodexCliAgent {
|
|
|
46
50
|
getName() {
|
|
47
51
|
return 'OpenAI Codex CLI';
|
|
48
52
|
}
|
|
49
|
-
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson,
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
53
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
|
|
54
|
+
// Get default paths
|
|
55
|
+
const defaults = this.getDefaultOutputPath(projectRoot);
|
|
56
|
+
// Determine the instructions file path
|
|
57
|
+
const instructionsPath = agentConfig?.outputPath ??
|
|
58
|
+
agentConfig?.outputPathInstructions ??
|
|
59
|
+
defaults.instructions;
|
|
60
|
+
// Write the instructions file
|
|
61
|
+
await (0, FileSystemUtils_1.backupFile)(instructionsPath);
|
|
62
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(instructionsPath, concatenatedRules);
|
|
63
|
+
// Handle MCP configuration if enabled
|
|
64
|
+
const mcpEnabled = agentConfig?.mcp?.enabled ?? true;
|
|
65
|
+
if (mcpEnabled && rulerMcpJson) {
|
|
66
|
+
// Determine the config file path
|
|
67
|
+
const configPath = agentConfig?.outputPathConfig ?? defaults.config;
|
|
68
|
+
// Ensure the parent directory exists
|
|
69
|
+
await fs_1.promises.mkdir(path.dirname(configPath), { recursive: true });
|
|
70
|
+
// Get the merge strategy
|
|
71
|
+
const strategy = agentConfig?.mcp?.strategy ?? 'merge';
|
|
72
|
+
// Extract MCP servers from ruler config
|
|
73
|
+
const rulerServers = rulerMcpJson.mcpServers || {};
|
|
74
|
+
// Read existing TOML config if it exists
|
|
75
|
+
let existingConfig = {};
|
|
76
|
+
try {
|
|
77
|
+
const existingContent = await fs_1.promises.readFile(configPath, 'utf8');
|
|
78
|
+
existingConfig = toml.parse(existingContent);
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
// File doesn't exist or can't be parsed, use empty config
|
|
82
|
+
}
|
|
83
|
+
// Create the updated config
|
|
84
|
+
const updatedConfig = { ...existingConfig };
|
|
85
|
+
// Initialize mcp_servers if it doesn't exist
|
|
86
|
+
if (!updatedConfig.mcp_servers) {
|
|
87
|
+
updatedConfig.mcp_servers = {};
|
|
88
|
+
}
|
|
89
|
+
if (strategy === 'overwrite') {
|
|
90
|
+
// For overwrite strategy, replace the entire mcp_servers section
|
|
91
|
+
updatedConfig.mcp_servers = {};
|
|
92
|
+
}
|
|
93
|
+
// Add the ruler servers
|
|
94
|
+
for (const [serverName, serverConfig] of Object.entries(rulerServers)) {
|
|
95
|
+
// Create a properly formatted MCP server entry
|
|
96
|
+
const mcpServer = {
|
|
97
|
+
command: serverConfig.command,
|
|
98
|
+
args: serverConfig.args,
|
|
99
|
+
};
|
|
100
|
+
// Format env as an inline table
|
|
101
|
+
if (serverConfig.env) {
|
|
102
|
+
mcpServer.env = serverConfig.env;
|
|
103
|
+
}
|
|
104
|
+
updatedConfig.mcp_servers[serverName] = mcpServer;
|
|
105
|
+
}
|
|
106
|
+
// Convert to TOML with special handling for env to ensure it's an inline table
|
|
107
|
+
let tomlContent = '';
|
|
108
|
+
// Handle non-mcp_servers sections first
|
|
109
|
+
const configWithoutMcpServers = { ...updatedConfig };
|
|
110
|
+
delete configWithoutMcpServers.mcp_servers;
|
|
111
|
+
if (Object.keys(configWithoutMcpServers).length > 0) {
|
|
112
|
+
tomlContent += (0, toml_1.stringify)(configWithoutMcpServers);
|
|
113
|
+
}
|
|
114
|
+
// Now handle mcp_servers with special formatting for env
|
|
115
|
+
if (updatedConfig.mcp_servers &&
|
|
116
|
+
Object.keys(updatedConfig.mcp_servers).length > 0) {
|
|
117
|
+
for (const [serverName, serverConfigRaw] of Object.entries(updatedConfig.mcp_servers)) {
|
|
118
|
+
const serverConfig = serverConfigRaw;
|
|
119
|
+
tomlContent += `\n[mcp_servers.${serverName}]\n`;
|
|
120
|
+
// Add command
|
|
121
|
+
if (serverConfig.command) {
|
|
122
|
+
tomlContent += `command = "${serverConfig.command}"\n`;
|
|
123
|
+
}
|
|
124
|
+
// Add args if present
|
|
125
|
+
if (serverConfig.args && Array.isArray(serverConfig.args)) {
|
|
126
|
+
const argsStr = JSON.stringify(serverConfig.args)
|
|
127
|
+
.replace(/"/g, '"')
|
|
128
|
+
.replace(/,/g, ', ');
|
|
129
|
+
tomlContent += `args = ${argsStr}\n`;
|
|
130
|
+
}
|
|
131
|
+
// Add env as inline table if present
|
|
132
|
+
if (serverConfig.env && Object.keys(serverConfig.env).length > 0) {
|
|
133
|
+
tomlContent += `env = { `;
|
|
134
|
+
const entries = Object.entries(serverConfig.env);
|
|
135
|
+
for (let i = 0; i < entries.length; i++) {
|
|
136
|
+
const [key, value] = entries[i];
|
|
137
|
+
tomlContent += `${key} = "${value}"`;
|
|
138
|
+
if (i < entries.length - 1) {
|
|
139
|
+
tomlContent += ', ';
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
tomlContent += ` }\n`;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(configPath, tomlContent);
|
|
147
|
+
}
|
|
54
148
|
}
|
|
55
149
|
getDefaultOutputPath(projectRoot) {
|
|
56
|
-
return
|
|
150
|
+
return {
|
|
151
|
+
instructions: path.join(projectRoot, 'AGENTS.md'),
|
|
152
|
+
config: path.join(projectRoot, '.codex', 'config.toml'),
|
|
153
|
+
};
|
|
57
154
|
}
|
|
58
155
|
}
|
|
59
156
|
exports.CodexCliAgent = CodexCliAgent;
|
|
@@ -0,0 +1,66 @@
|
|
|
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.GooseAgent = void 0;
|
|
37
|
+
const path = __importStar(require("path"));
|
|
38
|
+
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
39
|
+
/**
|
|
40
|
+
* Goose agent adapter for Block's Goose AI assistant.
|
|
41
|
+
* Propagates rules to .goosehints file.
|
|
42
|
+
*/
|
|
43
|
+
class GooseAgent {
|
|
44
|
+
getIdentifier() {
|
|
45
|
+
return 'goose';
|
|
46
|
+
}
|
|
47
|
+
getName() {
|
|
48
|
+
return 'Goose';
|
|
49
|
+
}
|
|
50
|
+
async applyRulerConfig(concatenatedRules, projectRoot, rulerMcpJson, agentConfig) {
|
|
51
|
+
// Get the output path for .goosehints
|
|
52
|
+
const hintsPath = agentConfig?.outputPathInstructions ??
|
|
53
|
+
this.getDefaultOutputPath(projectRoot);
|
|
54
|
+
// Write rules to .goosehints
|
|
55
|
+
await (0, FileSystemUtils_1.backupFile)(hintsPath);
|
|
56
|
+
await (0, FileSystemUtils_1.writeGeneratedFile)(hintsPath, concatenatedRules);
|
|
57
|
+
}
|
|
58
|
+
getDefaultOutputPath(projectRoot) {
|
|
59
|
+
return path.join(projectRoot, '.goosehints');
|
|
60
|
+
}
|
|
61
|
+
getMcpServerKey() {
|
|
62
|
+
// Goose doesn't support MCP configuration via local config files
|
|
63
|
+
return '';
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
exports.GooseAgent = GooseAgent;
|
|
@@ -32,15 +32,12 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.loadConfig = loadConfig;
|
|
40
37
|
const fs_1 = require("fs");
|
|
41
38
|
const path = __importStar(require("path"));
|
|
42
39
|
const os = __importStar(require("os"));
|
|
43
|
-
const
|
|
40
|
+
const TOML = __importStar(require("toml"));
|
|
44
41
|
const zod_1 = require("zod");
|
|
45
42
|
const constants_1 = require("../constants");
|
|
46
43
|
const mcpConfigSchema = zod_1.z
|
|
@@ -99,7 +96,7 @@ async function loadConfig(options) {
|
|
|
99
96
|
let raw = {};
|
|
100
97
|
try {
|
|
101
98
|
const text = await fs_1.promises.readFile(configFile, 'utf8');
|
|
102
|
-
raw = text.trim() ?
|
|
99
|
+
raw = text.trim() ? TOML.parse(text) : {};
|
|
103
100
|
// Validate the configuration with zod
|
|
104
101
|
const validationResult = rulerConfigSchema.safeParse(raw);
|
|
105
102
|
if (!validationResult.success) {
|
package/dist/lib.js
CHANGED
|
@@ -55,6 +55,7 @@ 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 GooseAgent_1 = require("./agents/GooseAgent");
|
|
58
59
|
const merge_1 = require("./mcp/merge");
|
|
59
60
|
const validate_1 = require("./mcp/validate");
|
|
60
61
|
const mcp_1 = require("./paths/mcp");
|
|
@@ -111,6 +112,7 @@ const agents = [
|
|
|
111
112
|
new AugmentCodeAgent_1.AugmentCodeAgent(),
|
|
112
113
|
new KiloCodeAgent_1.KiloCodeAgent(),
|
|
113
114
|
new OpenCodeAgent_1.OpenCodeAgent(),
|
|
115
|
+
new GooseAgent_1.GooseAgent(),
|
|
114
116
|
];
|
|
115
117
|
/**
|
|
116
118
|
* Applies ruler configurations for all supported AI agents.
|
|
@@ -32,13 +32,11 @@ var __importStar = (this && this.__importStar) || (function () {
|
|
|
32
32
|
return result;
|
|
33
33
|
};
|
|
34
34
|
})();
|
|
35
|
-
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
-
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
-
};
|
|
38
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
36
|
exports.propagateMcpToOpenHands = propagateMcpToOpenHands;
|
|
40
37
|
const fs = __importStar(require("fs/promises"));
|
|
41
|
-
const
|
|
38
|
+
const TOML = __importStar(require("toml"));
|
|
39
|
+
const toml_1 = require("@iarna/toml");
|
|
42
40
|
const FileSystemUtils_1 = require("../core/FileSystemUtils");
|
|
43
41
|
const path = __importStar(require("path"));
|
|
44
42
|
async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
|
|
@@ -54,7 +52,7 @@ async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
|
|
|
54
52
|
let config = {};
|
|
55
53
|
try {
|
|
56
54
|
const tomlContent = await fs.readFile(openHandsConfigPath, 'utf8');
|
|
57
|
-
config =
|
|
55
|
+
config = TOML.parse(tomlContent);
|
|
58
56
|
}
|
|
59
57
|
catch {
|
|
60
58
|
// File doesn't exist, we'll create it.
|
|
@@ -79,5 +77,5 @@ async function propagateMcpToOpenHands(rulerMcpPath, openHandsConfigPath) {
|
|
|
79
77
|
}
|
|
80
78
|
config.mcp.stdio_servers = Array.from(existingServers.values());
|
|
81
79
|
await (0, FileSystemUtils_1.ensureDirExists)(path.dirname(openHandsConfigPath));
|
|
82
|
-
await fs.writeFile(openHandsConfigPath, toml_1.
|
|
80
|
+
await fs.writeFile(openHandsConfigPath, (0, toml_1.stringify)(config));
|
|
83
81
|
}
|
package/dist/revert.js
CHANGED
|
@@ -53,6 +53,7 @@ const JunieAgent_1 = require("./agents/JunieAgent");
|
|
|
53
53
|
const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
|
|
54
54
|
const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
|
|
55
55
|
const OpenCodeAgent_1 = require("./agents/OpenCodeAgent");
|
|
56
|
+
const GooseAgent_1 = require("./agents/GooseAgent");
|
|
56
57
|
const mcp_1 = require("./paths/mcp");
|
|
57
58
|
const constants_1 = require("./constants");
|
|
58
59
|
const settings_1 = require("./vscode/settings");
|
|
@@ -72,6 +73,7 @@ const agents = [
|
|
|
72
73
|
new AugmentCodeAgent_1.AugmentCodeAgent(),
|
|
73
74
|
new KiloCodeAgent_1.KiloCodeAgent(),
|
|
74
75
|
new OpenCodeAgent_1.OpenCodeAgent(),
|
|
76
|
+
new GooseAgent_1.GooseAgent(),
|
|
75
77
|
];
|
|
76
78
|
/**
|
|
77
79
|
* 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.
|
|
3
|
+
"version": "0.2.16",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"scripts": {
|
|
@@ -61,6 +61,7 @@
|
|
|
61
61
|
"dependencies": {
|
|
62
62
|
"@iarna/toml": "^2.2.5",
|
|
63
63
|
"js-yaml": "^4.1.0",
|
|
64
|
+
"toml": "^3.0.0",
|
|
64
65
|
"yargs": "^17.7.2",
|
|
65
66
|
"zod": "^3.25.28"
|
|
66
67
|
}
|