skiller 0.4.3
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/LICENSE +21 -0
- package/README.md +989 -0
- package/dist/agents/AbstractAgent.js +92 -0
- package/dist/agents/AgentsMdAgent.js +85 -0
- package/dist/agents/AiderAgent.js +108 -0
- package/dist/agents/AmazonQCliAgent.js +103 -0
- package/dist/agents/AmpAgent.js +13 -0
- package/dist/agents/AugmentCodeAgent.js +70 -0
- package/dist/agents/ClaudeAgent.js +95 -0
- package/dist/agents/ClineAgent.js +53 -0
- package/dist/agents/CodexCliAgent.js +143 -0
- package/dist/agents/CopilotAgent.js +43 -0
- package/dist/agents/CrushAgent.js +128 -0
- package/dist/agents/CursorAgent.js +93 -0
- package/dist/agents/FirebaseAgent.js +61 -0
- package/dist/agents/FirebenderAgent.js +205 -0
- package/dist/agents/GeminiCliAgent.js +99 -0
- package/dist/agents/GooseAgent.js +58 -0
- package/dist/agents/IAgent.js +2 -0
- package/dist/agents/JulesAgent.js +14 -0
- package/dist/agents/JunieAgent.js +53 -0
- package/dist/agents/KiloCodeAgent.js +63 -0
- package/dist/agents/KiroAgent.js +50 -0
- package/dist/agents/OpenCodeAgent.js +99 -0
- package/dist/agents/OpenHandsAgent.js +56 -0
- package/dist/agents/QwenCodeAgent.js +82 -0
- package/dist/agents/RooCodeAgent.js +139 -0
- package/dist/agents/TraeAgent.js +54 -0
- package/dist/agents/WarpAgent.js +61 -0
- package/dist/agents/WindsurfAgent.js +27 -0
- package/dist/agents/ZedAgent.js +132 -0
- package/dist/agents/agent-utils.js +37 -0
- package/dist/agents/index.js +77 -0
- package/dist/cli/commands.js +136 -0
- package/dist/cli/handlers.js +221 -0
- package/dist/cli/index.js +5 -0
- package/dist/constants.js +58 -0
- package/dist/core/ConfigLoader.js +274 -0
- package/dist/core/FileSystemUtils.js +421 -0
- package/dist/core/FrontmatterParser.js +142 -0
- package/dist/core/GitignoreUtils.js +171 -0
- package/dist/core/RuleProcessor.js +60 -0
- package/dist/core/SkillsProcessor.js +528 -0
- package/dist/core/SkillsUtils.js +230 -0
- package/dist/core/UnifiedConfigLoader.js +432 -0
- package/dist/core/UnifiedConfigTypes.js +2 -0
- package/dist/core/agent-selection.js +52 -0
- package/dist/core/apply-engine.js +668 -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 +196 -0
- package/dist/mcp/capabilities.js +65 -0
- package/dist/mcp/merge.js +39 -0
- package/dist/mcp/propagateOpenCodeMcp.js +116 -0
- package/dist/mcp/propagateOpenHandsMcp.js +169 -0
- package/dist/mcp/validate.js +17 -0
- package/dist/paths/mcp.js +120 -0
- package/dist/revert.js +186 -0
- package/dist/types.js +2 -0
- package/dist/vscode/settings.js +117 -0
- package/package.json +77 -0
|
@@ -0,0 +1,221 @@
|
|
|
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
|
+
const ConfigLoader_1 = require("../core/ConfigLoader");
|
|
46
|
+
/**
|
|
47
|
+
* Handler for the 'apply' command.
|
|
48
|
+
*/
|
|
49
|
+
async function applyHandler(argv) {
|
|
50
|
+
const projectRoot = argv['project-root'];
|
|
51
|
+
const agents = argv.agents
|
|
52
|
+
? argv.agents.split(',').map((a) => a.trim())
|
|
53
|
+
: undefined;
|
|
54
|
+
const configPath = argv.config;
|
|
55
|
+
const mcpEnabled = argv.mcp;
|
|
56
|
+
const mcpStrategy = argv['mcp-overwrite']
|
|
57
|
+
? 'overwrite'
|
|
58
|
+
: undefined;
|
|
59
|
+
const verbose = argv.verbose;
|
|
60
|
+
const dryRun = argv['dry-run'];
|
|
61
|
+
const localOnly = argv['local-only'];
|
|
62
|
+
// Determine backup preference: CLI > TOML > Default (enabled)
|
|
63
|
+
// yargs handles --no-backup by setting backup to false
|
|
64
|
+
let backupPreference;
|
|
65
|
+
if (argv.backup !== undefined) {
|
|
66
|
+
backupPreference = argv.backup;
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
backupPreference = undefined; // Let TOML/default decide
|
|
70
|
+
}
|
|
71
|
+
// Determine gitignore preference: CLI > TOML > Default (enabled)
|
|
72
|
+
// yargs handles --no-gitignore by setting gitignore to false
|
|
73
|
+
let gitignorePreference;
|
|
74
|
+
if (argv.gitignore !== undefined) {
|
|
75
|
+
gitignorePreference = argv.gitignore;
|
|
76
|
+
}
|
|
77
|
+
else {
|
|
78
|
+
gitignorePreference = undefined; // Let TOML/default decide
|
|
79
|
+
}
|
|
80
|
+
// Determine nested preference: CLI > TOML > Default (false)
|
|
81
|
+
let nested;
|
|
82
|
+
if (argv.nested !== undefined) {
|
|
83
|
+
// CLI explicitly set nested (either --nested or --no-nested)
|
|
84
|
+
nested = argv.nested;
|
|
85
|
+
}
|
|
86
|
+
else {
|
|
87
|
+
// CLI didn't set nested, check TOML configuration
|
|
88
|
+
try {
|
|
89
|
+
const config = await (0, ConfigLoader_1.loadConfig)({
|
|
90
|
+
projectRoot,
|
|
91
|
+
configPath,
|
|
92
|
+
});
|
|
93
|
+
// Use TOML setting if available, otherwise default to false
|
|
94
|
+
nested = config.nested ?? false;
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// If config loading fails, use default (false)
|
|
98
|
+
nested = false;
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
// Determine skills preference: CLI > TOML > Default (enabled)
|
|
102
|
+
let skillsEnabled;
|
|
103
|
+
if (argv.skills !== undefined) {
|
|
104
|
+
skillsEnabled = argv.skills;
|
|
105
|
+
}
|
|
106
|
+
else {
|
|
107
|
+
skillsEnabled = undefined; // Let config/default decide
|
|
108
|
+
}
|
|
109
|
+
try {
|
|
110
|
+
await (0, lib_1.applyAllAgentConfigs)(projectRoot, agents, configPath, mcpEnabled, mcpStrategy, gitignorePreference, verbose, dryRun, localOnly, nested, backupPreference, skillsEnabled);
|
|
111
|
+
console.log('Ruler apply completed successfully.');
|
|
112
|
+
}
|
|
113
|
+
catch (err) {
|
|
114
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
115
|
+
console.error(`${constants_1.ERROR_PREFIX} ${message}`);
|
|
116
|
+
process.exit(1);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* Handler for the 'init' command.
|
|
121
|
+
*/
|
|
122
|
+
async function initHandler(argv) {
|
|
123
|
+
const projectRoot = argv['project-root'];
|
|
124
|
+
const isGlobal = argv['global'];
|
|
125
|
+
const useClaude = argv['claude'] ?? false;
|
|
126
|
+
const folderName = useClaude ? '.claude' : '.ruler';
|
|
127
|
+
const rulerDir = isGlobal
|
|
128
|
+
? path.join(process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config'), 'ruler')
|
|
129
|
+
: path.join(projectRoot, folderName);
|
|
130
|
+
await fs.mkdir(rulerDir, { recursive: true });
|
|
131
|
+
const instructionsPath = path.join(rulerDir, constants_1.DEFAULT_RULES_FILENAME); // .ruler/AGENTS.md or .claude/AGENTS.md
|
|
132
|
+
const tomlPath = path.join(rulerDir, 'ruler.toml');
|
|
133
|
+
const exists = async (p) => {
|
|
134
|
+
try {
|
|
135
|
+
await fs.access(p);
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
catch {
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
};
|
|
142
|
+
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`;
|
|
143
|
+
const DEFAULT_TOML = `# Ruler Configuration File
|
|
144
|
+
# See https://ai.intellectronica.net/ruler for documentation.
|
|
145
|
+
|
|
146
|
+
# To specify which agents are active by default when --agents is not used,
|
|
147
|
+
# uncomment and populate the following line. If omitted, all agents are active.
|
|
148
|
+
# default_agents = ["copilot", "claude"]
|
|
149
|
+
${useClaude ? '\n# Root folder for ruler files\nroot_folder = ".claude"\n' : ''}
|
|
150
|
+
# Enable nested rule loading from nested ${folderName} directories
|
|
151
|
+
# When enabled, ruler will search for and process ${folderName} directories throughout the project hierarchy
|
|
152
|
+
# nested = false
|
|
153
|
+
|
|
154
|
+
# --- Agent Specific Configurations ---
|
|
155
|
+
# You can enable/disable agents and override their default output paths here.
|
|
156
|
+
# Use lowercase agent identifiers: amp, copilot, claude, codex, cursor, windsurf, cline, aider, kilocode
|
|
157
|
+
|
|
158
|
+
# [agents.copilot]
|
|
159
|
+
# enabled = true
|
|
160
|
+
# output_path = ".github/copilot-instructions.md"
|
|
161
|
+
|
|
162
|
+
# [agents.aider]
|
|
163
|
+
# enabled = true
|
|
164
|
+
# output_path_instructions = "AGENTS.md"
|
|
165
|
+
# output_path_config = ".aider.conf.yml"
|
|
166
|
+
|
|
167
|
+
# [agents.gemini-cli]
|
|
168
|
+
# enabled = true
|
|
169
|
+
|
|
170
|
+
# --- MCP Servers ---
|
|
171
|
+
# Define Model Context Protocol servers here. Two examples:
|
|
172
|
+
# 1. A stdio server (local executable)
|
|
173
|
+
# 2. A remote server (HTTP-based)
|
|
174
|
+
|
|
175
|
+
# [mcp_servers.example_stdio]
|
|
176
|
+
# command = "node"
|
|
177
|
+
# args = ["scripts/your-mcp-server.js"]
|
|
178
|
+
# env = { API_KEY = "replace_me" }
|
|
179
|
+
|
|
180
|
+
# [mcp_servers.example_remote]
|
|
181
|
+
# url = "https://api.example.com/mcp"
|
|
182
|
+
# headers = { Authorization = "Bearer REPLACE_ME" }
|
|
183
|
+
`;
|
|
184
|
+
if (!(await exists(instructionsPath))) {
|
|
185
|
+
// Create new AGENTS.md regardless of legacy presence.
|
|
186
|
+
await fs.writeFile(instructionsPath, DEFAULT_INSTRUCTIONS);
|
|
187
|
+
console.log(`[ruler] Created ${instructionsPath}`);
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
console.log(`[ruler] ${constants_1.DEFAULT_RULES_FILENAME} already exists, skipping`);
|
|
191
|
+
}
|
|
192
|
+
if (!(await exists(tomlPath))) {
|
|
193
|
+
await fs.writeFile(tomlPath, DEFAULT_TOML);
|
|
194
|
+
console.log(`[ruler] Created ${tomlPath}`);
|
|
195
|
+
}
|
|
196
|
+
else {
|
|
197
|
+
console.log(`[ruler] ruler.toml already exists, skipping`);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
/**
|
|
201
|
+
* Handler for the 'revert' command.
|
|
202
|
+
*/
|
|
203
|
+
async function revertHandler(argv) {
|
|
204
|
+
const projectRoot = argv['project-root'];
|
|
205
|
+
const agents = argv.agents
|
|
206
|
+
? argv.agents.split(',').map((a) => a.trim())
|
|
207
|
+
: undefined;
|
|
208
|
+
const configPath = argv.config;
|
|
209
|
+
const keepBackups = argv['keep-backups'];
|
|
210
|
+
const verbose = argv.verbose;
|
|
211
|
+
const dryRun = argv['dry-run'];
|
|
212
|
+
const localOnly = argv['local-only'];
|
|
213
|
+
try {
|
|
214
|
+
await (0, revert_1.revertAllAgentConfigs)(projectRoot, agents, configPath, keepBackups, verbose, dryRun, localOnly);
|
|
215
|
+
}
|
|
216
|
+
catch (err) {
|
|
217
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
218
|
+
console.error(`${constants_1.ERROR_PREFIX} ${message}`);
|
|
219
|
+
process.exit(1);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.SKILLZ_MCP_SERVER_NAME = exports.SKILL_MD_FILENAME = exports.SKILLZ_DIR = exports.CLAUDE_SKILLS_PATH = exports.RULER_SKILLS_PATH = exports.SKILLS_DIR = exports.DEFAULT_RULES_FILENAME = exports.ERROR_PREFIX = void 0;
|
|
4
|
+
exports.actionPrefix = actionPrefix;
|
|
5
|
+
exports.createRulerError = createRulerError;
|
|
6
|
+
exports.logVerbose = logVerbose;
|
|
7
|
+
exports.logInfo = logInfo;
|
|
8
|
+
exports.logWarn = logWarn;
|
|
9
|
+
exports.logError = logError;
|
|
10
|
+
exports.logVerboseInfo = logVerboseInfo;
|
|
11
|
+
exports.ERROR_PREFIX = '[ruler]';
|
|
12
|
+
// Centralized default rules filename. Now points to 'AGENTS.md'.
|
|
13
|
+
// Legacy '.ruler/instructions.md' is still supported as a fallback with a warning.
|
|
14
|
+
exports.DEFAULT_RULES_FILENAME = 'AGENTS.md';
|
|
15
|
+
function actionPrefix(dry) {
|
|
16
|
+
return dry ? '[ruler:dry-run]' : '[ruler]';
|
|
17
|
+
}
|
|
18
|
+
function createRulerError(message, context) {
|
|
19
|
+
const fullMessage = context
|
|
20
|
+
? `${exports.ERROR_PREFIX} ${message} (Context: ${context})`
|
|
21
|
+
: `${exports.ERROR_PREFIX} ${message}`;
|
|
22
|
+
return new Error(fullMessage);
|
|
23
|
+
}
|
|
24
|
+
function logVerbose(message, isVerbose) {
|
|
25
|
+
if (isVerbose) {
|
|
26
|
+
console.error(`[ruler:verbose] ${message}`);
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Centralized logging functions with consistent output streams and prefixing.
|
|
31
|
+
* - info/verbose go to stdout (user-visible progress)
|
|
32
|
+
* - warn/error go to stderr (problems)
|
|
33
|
+
*/
|
|
34
|
+
function logInfo(message, dryRun = false) {
|
|
35
|
+
const prefix = actionPrefix(dryRun);
|
|
36
|
+
console.log(`${prefix} ${message}`);
|
|
37
|
+
}
|
|
38
|
+
function logWarn(message, dryRun = false) {
|
|
39
|
+
const prefix = actionPrefix(dryRun);
|
|
40
|
+
console.warn(`${prefix} ${message}`);
|
|
41
|
+
}
|
|
42
|
+
function logError(message, dryRun = false) {
|
|
43
|
+
const prefix = actionPrefix(dryRun);
|
|
44
|
+
console.error(`${prefix} ${message}`);
|
|
45
|
+
}
|
|
46
|
+
function logVerboseInfo(message, isVerbose, dryRun = false) {
|
|
47
|
+
if (isVerbose) {
|
|
48
|
+
const prefix = actionPrefix(dryRun);
|
|
49
|
+
console.log(`${prefix} ${message}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// Skills-related constants
|
|
53
|
+
exports.SKILLS_DIR = 'skills';
|
|
54
|
+
exports.RULER_SKILLS_PATH = '.ruler/skills';
|
|
55
|
+
exports.CLAUDE_SKILLS_PATH = '.claude/skills';
|
|
56
|
+
exports.SKILLZ_DIR = '.skillz';
|
|
57
|
+
exports.SKILL_MD_FILENAME = 'SKILL.md';
|
|
58
|
+
exports.SKILLZ_MCP_SERVER_NAME = 'skillz';
|
|
@@ -0,0 +1,274 @@
|
|
|
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.loadConfig = loadConfig;
|
|
37
|
+
const fs_1 = require("fs");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const os = __importStar(require("os"));
|
|
40
|
+
const toml_1 = require("@iarna/toml");
|
|
41
|
+
const zod_1 = require("zod");
|
|
42
|
+
const constants_1 = require("../constants");
|
|
43
|
+
const mcpConfigSchema = zod_1.z
|
|
44
|
+
.object({
|
|
45
|
+
enabled: zod_1.z.boolean().optional(),
|
|
46
|
+
merge_strategy: zod_1.z.enum(['merge', 'overwrite']).optional(),
|
|
47
|
+
})
|
|
48
|
+
.optional();
|
|
49
|
+
const agentConfigSchema = zod_1.z
|
|
50
|
+
.object({
|
|
51
|
+
enabled: zod_1.z.boolean().optional(),
|
|
52
|
+
output_path: zod_1.z.string().optional(),
|
|
53
|
+
output_path_instructions: zod_1.z.string().optional(),
|
|
54
|
+
output_path_config: zod_1.z.string().optional(),
|
|
55
|
+
mcp: mcpConfigSchema,
|
|
56
|
+
})
|
|
57
|
+
.optional();
|
|
58
|
+
const rulerConfigSchema = zod_1.z.object({
|
|
59
|
+
default_agents: zod_1.z.array(zod_1.z.string()).optional(),
|
|
60
|
+
root_folder: zod_1.z.string().optional(),
|
|
61
|
+
agents: zod_1.z.record(zod_1.z.string(), agentConfigSchema).optional(),
|
|
62
|
+
mcp: zod_1.z
|
|
63
|
+
.object({
|
|
64
|
+
enabled: zod_1.z.boolean().optional(),
|
|
65
|
+
merge_strategy: zod_1.z.enum(['merge', 'overwrite']).optional(),
|
|
66
|
+
})
|
|
67
|
+
.optional(),
|
|
68
|
+
gitignore: zod_1.z
|
|
69
|
+
.object({
|
|
70
|
+
enabled: zod_1.z.boolean().optional(),
|
|
71
|
+
})
|
|
72
|
+
.optional(),
|
|
73
|
+
backup: zod_1.z
|
|
74
|
+
.object({
|
|
75
|
+
enabled: zod_1.z.boolean().optional(),
|
|
76
|
+
})
|
|
77
|
+
.optional(),
|
|
78
|
+
skills: zod_1.z
|
|
79
|
+
.object({
|
|
80
|
+
enabled: zod_1.z.boolean().optional(),
|
|
81
|
+
})
|
|
82
|
+
.optional(),
|
|
83
|
+
rules: zod_1.z
|
|
84
|
+
.object({
|
|
85
|
+
include: zod_1.z.array(zod_1.z.string()).optional(),
|
|
86
|
+
exclude: zod_1.z.array(zod_1.z.string()).optional(),
|
|
87
|
+
merge_strategy: zod_1.z.enum(['all', 'cursor']).optional(),
|
|
88
|
+
})
|
|
89
|
+
.optional(),
|
|
90
|
+
nested: zod_1.z.boolean().optional(),
|
|
91
|
+
});
|
|
92
|
+
/**
|
|
93
|
+
* Recursively creates a new object with only enumerable string keys,
|
|
94
|
+
* effectively excluding Symbol properties.
|
|
95
|
+
* The @iarna/toml parser adds Symbol properties (Symbol(type), Symbol(declared))
|
|
96
|
+
* for metadata, which Zod v4+ validates and rejects as invalid record keys.
|
|
97
|
+
* By rebuilding the object structure using Object.keys(), we create clean objects
|
|
98
|
+
* that only contain the actual data without Symbol metadata.
|
|
99
|
+
*/
|
|
100
|
+
function stripSymbols(obj) {
|
|
101
|
+
if (obj === null || typeof obj !== 'object') {
|
|
102
|
+
return obj;
|
|
103
|
+
}
|
|
104
|
+
if (Array.isArray(obj)) {
|
|
105
|
+
return obj.map(stripSymbols);
|
|
106
|
+
}
|
|
107
|
+
const result = {};
|
|
108
|
+
for (const key of Object.keys(obj)) {
|
|
109
|
+
result[key] = stripSymbols(obj[key]);
|
|
110
|
+
}
|
|
111
|
+
return result;
|
|
112
|
+
}
|
|
113
|
+
/**
|
|
114
|
+
* Loads and parses the ruler TOML configuration file, applying defaults.
|
|
115
|
+
* If the file is missing or invalid, returns empty/default config.
|
|
116
|
+
*/
|
|
117
|
+
async function loadConfig(options) {
|
|
118
|
+
const { projectRoot, configPath, cliAgents } = options;
|
|
119
|
+
let configFile;
|
|
120
|
+
if (configPath) {
|
|
121
|
+
configFile = path.resolve(configPath);
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Try local .ruler/ruler.toml first
|
|
125
|
+
const localConfigFile = path.join(projectRoot, '.ruler', 'ruler.toml');
|
|
126
|
+
try {
|
|
127
|
+
await fs_1.promises.access(localConfigFile);
|
|
128
|
+
configFile = localConfigFile;
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
// If .ruler config doesn't exist, try .claude/ruler.toml
|
|
132
|
+
const claudeConfigFile = path.join(projectRoot, '.claude', 'ruler.toml');
|
|
133
|
+
try {
|
|
134
|
+
await fs_1.promises.access(claudeConfigFile);
|
|
135
|
+
configFile = claudeConfigFile;
|
|
136
|
+
}
|
|
137
|
+
catch {
|
|
138
|
+
// If neither local config exists, try global config
|
|
139
|
+
const xdgConfigDir = process.env.XDG_CONFIG_HOME || path.join(os.homedir(), '.config');
|
|
140
|
+
configFile = path.join(xdgConfigDir, 'ruler', 'ruler.toml');
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
let raw = {};
|
|
145
|
+
try {
|
|
146
|
+
const text = await fs_1.promises.readFile(configFile, 'utf8');
|
|
147
|
+
const parsed = text.trim() ? (0, toml_1.parse)(text) : {};
|
|
148
|
+
// Strip Symbol properties added by @iarna/toml (required for Zod v4+)
|
|
149
|
+
raw = stripSymbols(parsed);
|
|
150
|
+
// Validate the configuration with zod
|
|
151
|
+
const validationResult = rulerConfigSchema.safeParse(raw);
|
|
152
|
+
if (!validationResult.success) {
|
|
153
|
+
throw (0, constants_1.createRulerError)('Invalid configuration file format', `File: ${configFile}, Errors: ${validationResult.error.issues.map((i) => `${i.path.join('.')}: ${i.message}`).join(', ')}`);
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
catch (err) {
|
|
157
|
+
if (err instanceof Error && err.code !== 'ENOENT') {
|
|
158
|
+
if (err.message.includes('[ruler]')) {
|
|
159
|
+
throw err; // Re-throw validation errors
|
|
160
|
+
}
|
|
161
|
+
console.warn(`[ruler] Warning: could not read config file at ${configFile}: ${err.message}`);
|
|
162
|
+
}
|
|
163
|
+
raw = {};
|
|
164
|
+
}
|
|
165
|
+
const defaultAgents = Array.isArray(raw.default_agents)
|
|
166
|
+
? raw.default_agents.map((a) => String(a))
|
|
167
|
+
: undefined;
|
|
168
|
+
const rootFolder = typeof raw.root_folder === 'string' ? raw.root_folder : undefined;
|
|
169
|
+
const agentsSection = raw.agents && typeof raw.agents === 'object' && !Array.isArray(raw.agents)
|
|
170
|
+
? raw.agents
|
|
171
|
+
: {};
|
|
172
|
+
const agentConfigs = {};
|
|
173
|
+
for (const [name, section] of Object.entries(agentsSection)) {
|
|
174
|
+
if (section && typeof section === 'object') {
|
|
175
|
+
const sectionObj = section;
|
|
176
|
+
const cfg = {};
|
|
177
|
+
if (typeof sectionObj.enabled === 'boolean') {
|
|
178
|
+
cfg.enabled = sectionObj.enabled;
|
|
179
|
+
}
|
|
180
|
+
if (typeof sectionObj.output_path === 'string') {
|
|
181
|
+
cfg.outputPath = path.resolve(projectRoot, sectionObj.output_path);
|
|
182
|
+
}
|
|
183
|
+
if (typeof sectionObj.output_path_instructions === 'string') {
|
|
184
|
+
cfg.outputPathInstructions = path.resolve(projectRoot, sectionObj.output_path_instructions);
|
|
185
|
+
}
|
|
186
|
+
if (typeof sectionObj.output_path_config === 'string') {
|
|
187
|
+
cfg.outputPathConfig = path.resolve(projectRoot, sectionObj.output_path_config);
|
|
188
|
+
}
|
|
189
|
+
if (sectionObj.mcp && typeof sectionObj.mcp === 'object') {
|
|
190
|
+
const m = sectionObj.mcp;
|
|
191
|
+
const mcpCfg = {};
|
|
192
|
+
if (typeof m.enabled === 'boolean') {
|
|
193
|
+
mcpCfg.enabled = m.enabled;
|
|
194
|
+
}
|
|
195
|
+
if (typeof m.merge_strategy === 'string') {
|
|
196
|
+
const ms = m.merge_strategy;
|
|
197
|
+
if (ms === 'merge' || ms === 'overwrite') {
|
|
198
|
+
mcpCfg.strategy = ms;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
cfg.mcp = mcpCfg;
|
|
202
|
+
}
|
|
203
|
+
agentConfigs[name] = cfg;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
const rawMcpSection = raw.mcp && typeof raw.mcp === 'object' && !Array.isArray(raw.mcp)
|
|
207
|
+
? raw.mcp
|
|
208
|
+
: {};
|
|
209
|
+
const globalMcpConfig = {};
|
|
210
|
+
if (typeof rawMcpSection.enabled === 'boolean') {
|
|
211
|
+
globalMcpConfig.enabled = rawMcpSection.enabled;
|
|
212
|
+
}
|
|
213
|
+
if (typeof rawMcpSection.merge_strategy === 'string') {
|
|
214
|
+
const strat = rawMcpSection.merge_strategy;
|
|
215
|
+
if (strat === 'merge' || strat === 'overwrite') {
|
|
216
|
+
globalMcpConfig.strategy = strat;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
const rawGitignoreSection = raw.gitignore &&
|
|
220
|
+
typeof raw.gitignore === 'object' &&
|
|
221
|
+
!Array.isArray(raw.gitignore)
|
|
222
|
+
? raw.gitignore
|
|
223
|
+
: {};
|
|
224
|
+
const gitignoreConfig = {};
|
|
225
|
+
if (typeof rawGitignoreSection.enabled === 'boolean') {
|
|
226
|
+
gitignoreConfig.enabled = rawGitignoreSection.enabled;
|
|
227
|
+
}
|
|
228
|
+
const rawBackupSection = raw.backup && typeof raw.backup === 'object' && !Array.isArray(raw.backup)
|
|
229
|
+
? raw.backup
|
|
230
|
+
: {};
|
|
231
|
+
const backupConfig = {};
|
|
232
|
+
if (typeof rawBackupSection.enabled === 'boolean') {
|
|
233
|
+
backupConfig.enabled = rawBackupSection.enabled;
|
|
234
|
+
}
|
|
235
|
+
const rawSkillsSection = raw.skills && typeof raw.skills === 'object' && !Array.isArray(raw.skills)
|
|
236
|
+
? raw.skills
|
|
237
|
+
: {};
|
|
238
|
+
const skillsConfig = {};
|
|
239
|
+
if (typeof rawSkillsSection.enabled === 'boolean') {
|
|
240
|
+
skillsConfig.enabled = rawSkillsSection.enabled;
|
|
241
|
+
}
|
|
242
|
+
if (typeof rawSkillsSection.generate_from_rules === 'boolean') {
|
|
243
|
+
skillsConfig.generate_from_rules = rawSkillsSection.generate_from_rules;
|
|
244
|
+
}
|
|
245
|
+
const rawRulesSection = raw.rules && typeof raw.rules === 'object' && !Array.isArray(raw.rules)
|
|
246
|
+
? raw.rules
|
|
247
|
+
: {};
|
|
248
|
+
const rulesConfig = {};
|
|
249
|
+
if (Array.isArray(rawRulesSection.include)) {
|
|
250
|
+
rulesConfig.include = rawRulesSection.include.map((p) => String(p));
|
|
251
|
+
}
|
|
252
|
+
if (Array.isArray(rawRulesSection.exclude)) {
|
|
253
|
+
rulesConfig.exclude = rawRulesSection.exclude.map((p) => String(p));
|
|
254
|
+
}
|
|
255
|
+
if (rawRulesSection.merge_strategy === 'all' ||
|
|
256
|
+
rawRulesSection.merge_strategy === 'cursor') {
|
|
257
|
+
rulesConfig.merge_strategy = rawRulesSection.merge_strategy;
|
|
258
|
+
}
|
|
259
|
+
const nestedDefined = typeof raw.nested === 'boolean';
|
|
260
|
+
const nested = nestedDefined ? raw.nested : false;
|
|
261
|
+
return {
|
|
262
|
+
defaultAgents,
|
|
263
|
+
rootFolder,
|
|
264
|
+
agentConfigs,
|
|
265
|
+
cliAgents,
|
|
266
|
+
mcp: globalMcpConfig,
|
|
267
|
+
gitignore: gitignoreConfig,
|
|
268
|
+
backup: backupConfig,
|
|
269
|
+
skills: skillsConfig,
|
|
270
|
+
rules: rulesConfig,
|
|
271
|
+
nested,
|
|
272
|
+
nestedDefined,
|
|
273
|
+
};
|
|
274
|
+
}
|