@intellectronica/ruler 0.3.10 → 0.3.12
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 +196 -35
- package/dist/agents/AbstractAgent.js +8 -2
- package/dist/agents/AgentsMdAgent.js +1 -2
- package/dist/agents/AugmentCodeAgent.js +1 -2
- package/dist/agents/ClaudeAgent.js +3 -0
- package/dist/agents/CursorAgent.js +1 -2
- package/dist/agents/QwenCodeAgent.js +1 -2
- package/dist/agents/WindsurfAgent.js +6 -137
- package/dist/cli/commands.js +5 -2
- package/dist/cli/handlers.js +31 -2
- package/dist/constants.js +8 -1
- package/dist/core/ConfigLoader.js +40 -2
- package/dist/core/SkillsProcessor.js +301 -0
- package/dist/core/SkillsUtils.js +161 -0
- package/dist/core/UnifiedConfigLoader.js +12 -0
- package/dist/core/apply-engine.js +195 -32
- package/dist/lib.js +105 -7
- package/package.json +17 -13
|
@@ -40,6 +40,7 @@ exports.processSingleConfiguration = processSingleConfiguration;
|
|
|
40
40
|
exports.applyConfigurationsToAgents = applyConfigurationsToAgents;
|
|
41
41
|
exports.updateGitignore = updateGitignore;
|
|
42
42
|
const path = __importStar(require("path"));
|
|
43
|
+
const fs_1 = require("fs");
|
|
43
44
|
const FileSystemUtils = __importStar(require("./FileSystemUtils"));
|
|
44
45
|
const RuleProcessor_1 = require("./RuleProcessor");
|
|
45
46
|
const ConfigLoader_1 = require("./ConfigLoader");
|
|
@@ -51,16 +52,13 @@ const propagateOpenCodeMcp_1 = require("../mcp/propagateOpenCodeMcp");
|
|
|
51
52
|
const agent_utils_1 = require("../agents/agent-utils");
|
|
52
53
|
const capabilities_1 = require("../mcp/capabilities");
|
|
53
54
|
const constants_1 = require("../constants");
|
|
54
|
-
async function loadNestedConfigurations(projectRoot, configPath, localOnly) {
|
|
55
|
+
async function loadNestedConfigurations(projectRoot, configPath, localOnly, resolvedNested) {
|
|
55
56
|
const { dirs: rulerDirs } = await findRulerDirectories(projectRoot, localOnly, true);
|
|
56
|
-
const rootConfig = await (0, ConfigLoader_1.loadConfig)({
|
|
57
|
-
projectRoot,
|
|
58
|
-
configPath,
|
|
59
|
-
});
|
|
60
57
|
const results = [];
|
|
61
58
|
const rulerDirConfigs = await processIndependentRulerDirs(rulerDirs);
|
|
62
59
|
for (const { rulerDir, files } of rulerDirConfigs) {
|
|
63
|
-
|
|
60
|
+
const config = await loadConfigForRulerDir(rulerDir, configPath, resolvedNested);
|
|
61
|
+
results.push(await createHierarchicalConfiguration(rulerDir, files, config, configPath));
|
|
64
62
|
}
|
|
65
63
|
return results;
|
|
66
64
|
}
|
|
@@ -77,14 +75,78 @@ async function processIndependentRulerDirs(rulerDirs) {
|
|
|
77
75
|
}
|
|
78
76
|
return results;
|
|
79
77
|
}
|
|
80
|
-
async function createHierarchicalConfiguration(rulerDir, files,
|
|
78
|
+
async function createHierarchicalConfiguration(rulerDir, files, config, cliConfigPath) {
|
|
81
79
|
await warnAboutLegacyMcpJson(rulerDir);
|
|
82
80
|
const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(rulerDir));
|
|
81
|
+
const directoryRoot = path.dirname(rulerDir);
|
|
82
|
+
const localConfigPath = path.join(rulerDir, 'ruler.toml');
|
|
83
|
+
let configPathToUse = cliConfigPath;
|
|
84
|
+
try {
|
|
85
|
+
await fs_1.promises.access(localConfigPath);
|
|
86
|
+
configPathToUse = localConfigPath;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
// fall back to CLI config or default resolution
|
|
90
|
+
}
|
|
91
|
+
const { loadUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('./UnifiedConfigLoader')));
|
|
92
|
+
const unifiedConfig = await loadUnifiedConfig({
|
|
93
|
+
projectRoot: directoryRoot,
|
|
94
|
+
configPath: configPathToUse,
|
|
95
|
+
});
|
|
96
|
+
let rulerMcpJson = null;
|
|
97
|
+
if (unifiedConfig.mcp && Object.keys(unifiedConfig.mcp.servers).length > 0) {
|
|
98
|
+
rulerMcpJson = {
|
|
99
|
+
mcpServers: unifiedConfig.mcp.servers,
|
|
100
|
+
};
|
|
101
|
+
}
|
|
83
102
|
return {
|
|
84
103
|
rulerDir,
|
|
85
|
-
config
|
|
104
|
+
config,
|
|
86
105
|
concatenatedRules,
|
|
87
|
-
rulerMcpJson
|
|
106
|
+
rulerMcpJson,
|
|
107
|
+
};
|
|
108
|
+
}
|
|
109
|
+
async function loadConfigForRulerDir(rulerDir, cliConfigPath, resolvedNested) {
|
|
110
|
+
const directoryRoot = path.dirname(rulerDir);
|
|
111
|
+
const localConfigPath = path.join(rulerDir, 'ruler.toml');
|
|
112
|
+
let hasLocalConfig = false;
|
|
113
|
+
try {
|
|
114
|
+
await fs_1.promises.access(localConfigPath);
|
|
115
|
+
hasLocalConfig = true;
|
|
116
|
+
}
|
|
117
|
+
catch {
|
|
118
|
+
hasLocalConfig = false;
|
|
119
|
+
}
|
|
120
|
+
const loaded = await (0, ConfigLoader_1.loadConfig)({
|
|
121
|
+
projectRoot: directoryRoot,
|
|
122
|
+
configPath: hasLocalConfig ? localConfigPath : cliConfigPath,
|
|
123
|
+
});
|
|
124
|
+
const cloned = cloneLoadedConfig(loaded);
|
|
125
|
+
if (resolvedNested) {
|
|
126
|
+
if (hasLocalConfig && loaded.nestedDefined && loaded.nested === false) {
|
|
127
|
+
(0, constants_1.logWarn)(`Nested mode is enabled but ${localConfigPath} sets nested = false. Continuing with nested processing.`);
|
|
128
|
+
}
|
|
129
|
+
cloned.nested = true;
|
|
130
|
+
cloned.nestedDefined = true;
|
|
131
|
+
}
|
|
132
|
+
return cloned;
|
|
133
|
+
}
|
|
134
|
+
function cloneLoadedConfig(config) {
|
|
135
|
+
const clonedAgentConfigs = {};
|
|
136
|
+
for (const [agent, agentConfig] of Object.entries(config.agentConfigs)) {
|
|
137
|
+
clonedAgentConfigs[agent] = {
|
|
138
|
+
...agentConfig,
|
|
139
|
+
mcp: agentConfig.mcp ? { ...agentConfig.mcp } : undefined,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
return {
|
|
143
|
+
defaultAgents: config.defaultAgents ? [...config.defaultAgents] : undefined,
|
|
144
|
+
agentConfigs: clonedAgentConfigs,
|
|
145
|
+
cliAgents: config.cliAgents ? [...config.cliAgents] : undefined,
|
|
146
|
+
mcp: config.mcp ? { ...config.mcp } : undefined,
|
|
147
|
+
gitignore: config.gitignore ? { ...config.gitignore } : undefined,
|
|
148
|
+
nested: config.nested,
|
|
149
|
+
nestedDefined: config.nestedDefined,
|
|
88
150
|
};
|
|
89
151
|
}
|
|
90
152
|
/**
|
|
@@ -120,7 +182,7 @@ async function findRulerDirectories(projectRoot, localOnly, hierarchical) {
|
|
|
120
182
|
async function warnAboutLegacyMcpJson(rulerDir) {
|
|
121
183
|
try {
|
|
122
184
|
const legacyMcpPath = path.join(rulerDir, 'mcp.json');
|
|
123
|
-
await
|
|
185
|
+
await fs_1.promises.access(legacyMcpPath);
|
|
124
186
|
(0, constants_1.logWarn)('Warning: Using legacy .ruler/mcp.json. Please migrate to ruler.toml. This fallback will be removed in a future release.');
|
|
125
187
|
}
|
|
126
188
|
catch {
|
|
@@ -171,13 +233,14 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
171
233
|
* @param cliMcpStrategy MCP strategy from CLI
|
|
172
234
|
* @returns Promise resolving to array of generated file paths
|
|
173
235
|
*/
|
|
174
|
-
async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
|
|
236
|
+
async function processHierarchicalConfigurations(agents, configurations, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true, skillsEnabled = true) {
|
|
175
237
|
const allGeneratedPaths = [];
|
|
176
238
|
for (const config of configurations) {
|
|
177
239
|
(0, constants_1.logVerboseInfo)(`Processing .ruler directory: ${config.rulerDir}`, verbose, dryRun);
|
|
178
240
|
const rulerRoot = path.dirname(config.rulerDir);
|
|
179
|
-
const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
|
|
180
|
-
|
|
241
|
+
const paths = await applyConfigurationsToAgents(agents, config.concatenatedRules, config.rulerMcpJson, config.config, rulerRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
|
|
242
|
+
const normalizedPaths = paths.map((p) => path.isAbsolute(p) ? p : path.join(rulerRoot, p));
|
|
243
|
+
allGeneratedPaths.push(...normalizedPaths);
|
|
181
244
|
}
|
|
182
245
|
return allGeneratedPaths;
|
|
183
246
|
}
|
|
@@ -193,8 +256,43 @@ async function processHierarchicalConfigurations(agents, configurations, verbose
|
|
|
193
256
|
* @param cliMcpStrategy MCP strategy from CLI
|
|
194
257
|
* @returns Promise resolving to array of generated file paths
|
|
195
258
|
*/
|
|
196
|
-
async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true) {
|
|
197
|
-
return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
|
|
259
|
+
async function processSingleConfiguration(agents, configuration, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup = true, skillsEnabled = true) {
|
|
260
|
+
return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
|
|
261
|
+
}
|
|
262
|
+
/**
|
|
263
|
+
* Adds Skillz MCP server to rulerMcpJson if skills exist and any agent needs it.
|
|
264
|
+
* Returns augmented MCP config or original if no changes needed.
|
|
265
|
+
*/
|
|
266
|
+
async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agents, verbose) {
|
|
267
|
+
// Check if any agent supports MCP stdio but not native skills
|
|
268
|
+
const hasAgentNeedingSkillz = agents.some((agent) => agent.supportsMcpStdio?.() && !agent.supportsNativeSkills?.());
|
|
269
|
+
if (!hasAgentNeedingSkillz) {
|
|
270
|
+
return rulerMcpJson;
|
|
271
|
+
}
|
|
272
|
+
// Check if .skillz directory exists
|
|
273
|
+
try {
|
|
274
|
+
const { SKILLZ_DIR } = await Promise.resolve().then(() => __importStar(require('../constants')));
|
|
275
|
+
const skillzPath = path.join(projectRoot, SKILLZ_DIR);
|
|
276
|
+
await fs_1.promises.access(skillzPath);
|
|
277
|
+
// Skills exist, add Skillz MCP server
|
|
278
|
+
const { buildSkillzMcpConfig } = await Promise.resolve().then(() => __importStar(require('./SkillsProcessor')));
|
|
279
|
+
const skillzMcp = buildSkillzMcpConfig(projectRoot);
|
|
280
|
+
// Initialize empty config if null
|
|
281
|
+
const baseConfig = rulerMcpJson || { mcpServers: {} };
|
|
282
|
+
const mcpServers = baseConfig.mcpServers || {};
|
|
283
|
+
(0, constants_1.logVerbose)('Adding Skillz MCP server to configuration for agents that need it', verbose);
|
|
284
|
+
return {
|
|
285
|
+
...baseConfig,
|
|
286
|
+
mcpServers: {
|
|
287
|
+
...mcpServers,
|
|
288
|
+
...skillzMcp,
|
|
289
|
+
},
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
catch {
|
|
293
|
+
// No .skillz directory, return original config
|
|
294
|
+
return rulerMcpJson;
|
|
295
|
+
}
|
|
198
296
|
}
|
|
199
297
|
/**
|
|
200
298
|
* Applies configurations to the selected agents (internal function).
|
|
@@ -207,23 +305,22 @@ async function processSingleConfiguration(agents, configuration, projectRoot, ve
|
|
|
207
305
|
* @param dryRun Whether to perform a dry run
|
|
208
306
|
* @returns Promise resolving to array of generated file paths
|
|
209
307
|
*/
|
|
210
|
-
async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
|
|
308
|
+
async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true, skillsEnabled = true) {
|
|
211
309
|
const generatedPaths = [];
|
|
212
310
|
let agentsMdWritten = false;
|
|
311
|
+
// Add Skillz MCP server to rulerMcpJson if skills are enabled
|
|
312
|
+
// This must happen before calling agent.applyRulerConfig() so that agents
|
|
313
|
+
// that handle MCP internally (e.g. Codex, Gemini) receive the Skillz server
|
|
314
|
+
let augmentedRulerMcpJson = rulerMcpJson;
|
|
315
|
+
if (skillsEnabled && !dryRun) {
|
|
316
|
+
augmentedRulerMcpJson = await addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agents, verbose);
|
|
317
|
+
}
|
|
213
318
|
for (const agent of agents) {
|
|
214
319
|
(0, constants_1.logInfo)(`Applying rules for ${agent.getName()}...`, dryRun);
|
|
215
320
|
(0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
|
|
216
321
|
const agentConfig = config.agentConfigs[agent.getIdentifier()];
|
|
217
322
|
// Collect output paths for .gitignore
|
|
218
|
-
|
|
219
|
-
// Special handling for Windsurf agent to account for file splitting
|
|
220
|
-
if (agent.getIdentifier() === 'windsurf' &&
|
|
221
|
-
'getActualOutputPaths' in agent) {
|
|
222
|
-
outputPaths = agent.getActualOutputPaths(concatenatedRules, projectRoot, agentConfig);
|
|
223
|
-
}
|
|
224
|
-
else {
|
|
225
|
-
outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
226
|
-
}
|
|
323
|
+
const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
227
324
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
|
|
228
325
|
generatedPaths.push(...outputPaths);
|
|
229
326
|
// Only add the backup file paths to the gitignore list if backups are enabled
|
|
@@ -247,7 +344,7 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
247
344
|
}
|
|
248
345
|
}
|
|
249
346
|
let finalAgentConfig = agentConfig;
|
|
250
|
-
if (agent.getIdentifier() === 'augmentcode' &&
|
|
347
|
+
if (agent.getIdentifier() === 'augmentcode' && augmentedRulerMcpJson) {
|
|
251
348
|
const resolvedStrategy = cliMcpStrategy ??
|
|
252
349
|
agentConfig?.mcp?.strategy ??
|
|
253
350
|
config.mcp?.strategy ??
|
|
@@ -261,25 +358,59 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
261
358
|
};
|
|
262
359
|
}
|
|
263
360
|
if (!skipApplyForThisAgent) {
|
|
264
|
-
await agent.applyRulerConfig(concatenatedRules, projectRoot,
|
|
361
|
+
await agent.applyRulerConfig(concatenatedRules, projectRoot, augmentedRulerMcpJson, finalAgentConfig, backup);
|
|
265
362
|
}
|
|
266
363
|
}
|
|
267
364
|
// Handle MCP configuration
|
|
268
|
-
await handleMcpConfiguration(agent, agentConfig, config,
|
|
365
|
+
await handleMcpConfiguration(agent, agentConfig, config, augmentedRulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
|
|
269
366
|
}
|
|
270
367
|
return generatedPaths;
|
|
271
368
|
}
|
|
272
|
-
async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true) {
|
|
369
|
+
async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true, skillsEnabled = true) {
|
|
273
370
|
if (!(0, capabilities_1.agentSupportsMcp)(agent)) {
|
|
274
371
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} does not support MCP - skipping MCP configuration`, verbose);
|
|
275
372
|
return;
|
|
276
373
|
}
|
|
277
374
|
const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
278
375
|
const mcpEnabledForAgent = cliMcpEnabled && (agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
|
|
279
|
-
if (!dest || !mcpEnabledForAgent
|
|
376
|
+
if (!dest || !mcpEnabledForAgent) {
|
|
280
377
|
return;
|
|
281
378
|
}
|
|
282
|
-
|
|
379
|
+
let filteredMcpJson = rulerMcpJson
|
|
380
|
+
? (0, capabilities_1.filterMcpConfigForAgent)(rulerMcpJson, agent)
|
|
381
|
+
: null;
|
|
382
|
+
// Add Skillz MCP server for agents that support stdio but not native skills
|
|
383
|
+
// Only add if skills are enabled
|
|
384
|
+
if (skillsEnabled &&
|
|
385
|
+
agent.supportsMcpStdio?.() &&
|
|
386
|
+
!agent.supportsNativeSkills?.()) {
|
|
387
|
+
// Check if .skillz directory exists
|
|
388
|
+
try {
|
|
389
|
+
const { SKILLZ_DIR } = await Promise.resolve().then(() => __importStar(require('../constants')));
|
|
390
|
+
const skillzPath = path.join(projectRoot, SKILLZ_DIR);
|
|
391
|
+
await fs_1.promises.access(skillzPath);
|
|
392
|
+
// Skills exist, add Skillz MCP server
|
|
393
|
+
const { buildSkillzMcpConfig } = await Promise.resolve().then(() => __importStar(require('./SkillsProcessor')));
|
|
394
|
+
const skillzMcp = buildSkillzMcpConfig(projectRoot);
|
|
395
|
+
// Merge Skillz server into MCP config
|
|
396
|
+
// Initialize empty config if null
|
|
397
|
+
if (!filteredMcpJson) {
|
|
398
|
+
filteredMcpJson = { mcpServers: {} };
|
|
399
|
+
}
|
|
400
|
+
const mcpServers = filteredMcpJson.mcpServers || {};
|
|
401
|
+
filteredMcpJson = {
|
|
402
|
+
...filteredMcpJson,
|
|
403
|
+
mcpServers: {
|
|
404
|
+
...mcpServers,
|
|
405
|
+
...skillzMcp,
|
|
406
|
+
},
|
|
407
|
+
};
|
|
408
|
+
(0, constants_1.logVerboseInfo)(`Added Skillz MCP server for ${agent.getName()}`, verbose, dryRun);
|
|
409
|
+
}
|
|
410
|
+
catch {
|
|
411
|
+
// No .skillz directory, skip adding Skillz server
|
|
412
|
+
}
|
|
413
|
+
}
|
|
283
414
|
if (!filteredMcpJson) {
|
|
284
415
|
(0, constants_1.logVerbose)(`No compatible MCP servers found for ${agent.getName()} - skipping MCP configuration`, verbose);
|
|
285
416
|
return;
|
|
@@ -369,6 +500,35 @@ function transformMcpForClaude(mcpJson) {
|
|
|
369
500
|
transformedMcp.mcpServers = transformedServers;
|
|
370
501
|
return transformedMcp;
|
|
371
502
|
}
|
|
503
|
+
/**
|
|
504
|
+
* Transform MCP server types for Kilo Code compatibility.
|
|
505
|
+
* Kilo Code expects "streamable-http" for remote HTTP servers, not "remote".
|
|
506
|
+
*/
|
|
507
|
+
function transformMcpForKiloCode(mcpJson) {
|
|
508
|
+
if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
|
|
509
|
+
return mcpJson;
|
|
510
|
+
}
|
|
511
|
+
const transformedMcp = { ...mcpJson };
|
|
512
|
+
const transformedServers = {};
|
|
513
|
+
for (const [name, serverDef] of Object.entries(mcpJson.mcpServers)) {
|
|
514
|
+
if (serverDef && typeof serverDef === 'object') {
|
|
515
|
+
const server = serverDef;
|
|
516
|
+
const transformedServer = { ...server };
|
|
517
|
+
// Transform type: "remote" to "streamable-http" for HTTP-based servers
|
|
518
|
+
if (server.type === 'remote' &&
|
|
519
|
+
server.url &&
|
|
520
|
+
typeof server.url === 'string') {
|
|
521
|
+
transformedServer.type = 'streamable-http';
|
|
522
|
+
}
|
|
523
|
+
transformedServers[name] = transformedServer;
|
|
524
|
+
}
|
|
525
|
+
else {
|
|
526
|
+
transformedServers[name] = serverDef;
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
transformedMcp.mcpServers = transformedServers;
|
|
530
|
+
return transformedMcp;
|
|
531
|
+
}
|
|
372
532
|
async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
373
533
|
const strategy = cliMcpStrategy ??
|
|
374
534
|
agentConfig?.mcp?.strategy ??
|
|
@@ -385,11 +545,14 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
385
545
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, verbose);
|
|
386
546
|
}
|
|
387
547
|
else {
|
|
388
|
-
// Transform MCP config for
|
|
548
|
+
// Transform MCP config for agent-specific compatibility
|
|
389
549
|
let mcpToMerge = filteredMcpJson;
|
|
390
550
|
if (agent.getIdentifier() === 'claude') {
|
|
391
551
|
mcpToMerge = transformMcpForClaude(filteredMcpJson);
|
|
392
552
|
}
|
|
553
|
+
else if (agent.getIdentifier() === 'kilocode') {
|
|
554
|
+
mcpToMerge = transformMcpForKiloCode(filteredMcpJson);
|
|
555
|
+
}
|
|
393
556
|
const existing = await (0, mcp_1.readNativeMcp)(dest);
|
|
394
557
|
const merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
|
|
395
558
|
// Firebase Studio (IDX) expects no "type" fields in .idx/mcp.json server entries.
|
package/dist/lib.js
CHANGED
|
@@ -1,7 +1,41 @@
|
|
|
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
|
+
})();
|
|
2
35
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
36
|
exports.allAgents = void 0;
|
|
4
37
|
exports.applyAllAgentConfigs = applyAllAgentConfigs;
|
|
38
|
+
const path = __importStar(require("path"));
|
|
5
39
|
const agents_1 = require("./agents");
|
|
6
40
|
Object.defineProperty(exports, "allAgents", { enumerable: true, get: function () { return agents_1.allAgents; } });
|
|
7
41
|
const constants_1 = require("./constants");
|
|
@@ -9,6 +43,16 @@ const apply_engine_1 = require("./core/apply-engine");
|
|
|
9
43
|
const config_utils_1 = require("./core/config-utils");
|
|
10
44
|
const agent_selection_1 = require("./core/agent-selection");
|
|
11
45
|
const agents = agents_1.allAgents;
|
|
46
|
+
/**
|
|
47
|
+
* Resolves skills enabled state based on precedence: CLI flag > ruler.toml > default (enabled)
|
|
48
|
+
*/
|
|
49
|
+
function resolveSkillsEnabled(cliFlag, configSetting) {
|
|
50
|
+
return cliFlag !== undefined
|
|
51
|
+
? cliFlag
|
|
52
|
+
: configSetting !== undefined
|
|
53
|
+
? configSetting
|
|
54
|
+
: true; // default to enabled
|
|
55
|
+
}
|
|
12
56
|
/**
|
|
13
57
|
* Applies ruler configurations for all supported AI agents.
|
|
14
58
|
* @param projectRoot Root directory of the project
|
|
@@ -18,7 +62,7 @@ const agents = agents_1.allAgents;
|
|
|
18
62
|
* @param projectRoot Root directory of the project
|
|
19
63
|
* @param includedAgents Optional list of agent name filters (case-insensitive substrings)
|
|
20
64
|
*/
|
|
21
|
-
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true) {
|
|
65
|
+
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true, skillsEnabled) {
|
|
22
66
|
// Load configuration and rules
|
|
23
67
|
(0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
|
|
24
68
|
if (configPath) {
|
|
@@ -28,20 +72,35 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
28
72
|
let generatedPaths;
|
|
29
73
|
let loadedConfig;
|
|
30
74
|
if (nested) {
|
|
31
|
-
const hierarchicalConfigs = await (0, apply_engine_1.loadNestedConfigurations)(projectRoot, configPath, localOnly);
|
|
75
|
+
const hierarchicalConfigs = await (0, apply_engine_1.loadNestedConfigurations)(projectRoot, configPath, localOnly, nested);
|
|
32
76
|
if (hierarchicalConfigs.length === 0) {
|
|
33
77
|
throw new Error('No .ruler directories found');
|
|
34
78
|
}
|
|
79
|
+
(0, constants_1.logWarn)('Nested mode is experimental and may change in future releases.', dryRun);
|
|
35
80
|
// Use the root config for agent selection (all levels share the same agent settings)
|
|
36
|
-
const
|
|
81
|
+
const rootConfigEntry = selectRootConfiguration(hierarchicalConfigs, projectRoot);
|
|
82
|
+
const rootConfig = rootConfigEntry.config;
|
|
37
83
|
loadedConfig = rootConfig;
|
|
38
84
|
rootConfig.cliAgents = includedAgents;
|
|
39
85
|
(0, constants_1.logVerbose)(`Loaded ${hierarchicalConfigs.length} .ruler directory configurations`, verbose);
|
|
40
86
|
(0, constants_1.logVerbose)(`Root configuration has ${Object.keys(rootConfig.agentConfigs).length} agent configs`, verbose);
|
|
41
|
-
|
|
87
|
+
for (const configEntry of hierarchicalConfigs) {
|
|
88
|
+
normalizeAgentConfigs(configEntry.config, agents);
|
|
89
|
+
}
|
|
42
90
|
selectedAgents = (0, agent_selection_1.resolveSelectedAgents)(rootConfig, agents);
|
|
43
91
|
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
|
|
44
|
-
|
|
92
|
+
// Propagate skills if enabled - do this for each nested directory
|
|
93
|
+
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, rootConfig.skills?.enabled);
|
|
94
|
+
if (skillsEnabledResolved) {
|
|
95
|
+
const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
96
|
+
// Propagate skills for each nested .ruler directory
|
|
97
|
+
for (const configEntry of hierarchicalConfigs) {
|
|
98
|
+
const nestedRoot = path.dirname(configEntry.rulerDir);
|
|
99
|
+
(0, constants_1.logVerbose)(`Propagating skills for nested directory: ${nestedRoot}`, verbose);
|
|
100
|
+
await propagateSkills(nestedRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabledResolved);
|
|
45
104
|
}
|
|
46
105
|
else {
|
|
47
106
|
const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
|
|
@@ -52,9 +111,24 @@ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cli
|
|
|
52
111
|
normalizeAgentConfigs(singleConfig.config, agents);
|
|
53
112
|
selectedAgents = (0, agent_selection_1.resolveSelectedAgents)(singleConfig.config, agents);
|
|
54
113
|
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
|
|
55
|
-
|
|
114
|
+
// Propagate skills if enabled
|
|
115
|
+
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, singleConfig.config.skills?.enabled);
|
|
116
|
+
if (skillsEnabledResolved) {
|
|
117
|
+
const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
118
|
+
await propagateSkills(projectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
|
|
119
|
+
}
|
|
120
|
+
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabledResolved);
|
|
56
121
|
}
|
|
57
|
-
|
|
122
|
+
// Add skills-generated paths to gitignore if skills are enabled
|
|
123
|
+
let allGeneratedPaths = generatedPaths;
|
|
124
|
+
const skillsEnabledForGitignore = resolveSkillsEnabled(skillsEnabled, loadedConfig.skills?.enabled);
|
|
125
|
+
if (skillsEnabledForGitignore) {
|
|
126
|
+
// Skills enabled by default or explicitly
|
|
127
|
+
const { getSkillsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
|
|
128
|
+
const skillsPaths = await getSkillsGitignorePaths(projectRoot);
|
|
129
|
+
allGeneratedPaths = [...generatedPaths, ...skillsPaths];
|
|
130
|
+
}
|
|
131
|
+
await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
|
|
58
132
|
}
|
|
59
133
|
/**
|
|
60
134
|
* Normalizes per-agent config keys to agent identifiers for consistent lookup.
|
|
@@ -66,3 +140,27 @@ function normalizeAgentConfigs(config, agents) {
|
|
|
66
140
|
// Normalize per-agent config keys to agent identifiers (exact match or substring match)
|
|
67
141
|
config.agentConfigs = (0, config_utils_1.mapRawAgentConfigs)(config.agentConfigs, agents);
|
|
68
142
|
}
|
|
143
|
+
function selectRootConfiguration(configurations, projectRoot) {
|
|
144
|
+
if (configurations.length === 0) {
|
|
145
|
+
throw new Error('No hierarchical configurations available');
|
|
146
|
+
}
|
|
147
|
+
const normalizedProjectRoot = path.resolve(projectRoot);
|
|
148
|
+
let bestIndex = -1;
|
|
149
|
+
let bestDepth = Number.POSITIVE_INFINITY;
|
|
150
|
+
for (let i = 0; i < configurations.length; i++) {
|
|
151
|
+
const entry = configurations[i];
|
|
152
|
+
const normalizedDir = path.resolve(entry.rulerDir);
|
|
153
|
+
if (!normalizedDir.startsWith(normalizedProjectRoot)) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
const depth = normalizedDir.split(path.sep).length;
|
|
157
|
+
if (depth < bestDepth) {
|
|
158
|
+
bestDepth = depth;
|
|
159
|
+
bestIndex = i;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
if (bestIndex === -1) {
|
|
163
|
+
return configurations[0];
|
|
164
|
+
}
|
|
165
|
+
return configurations[bestIndex];
|
|
166
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@intellectronica/ruler",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.12",
|
|
4
4
|
"description": "Ruler — apply the same rules to all coding agents",
|
|
5
5
|
"main": "dist/lib.js",
|
|
6
6
|
"scripts": {
|
|
@@ -35,6 +35,9 @@
|
|
|
35
35
|
"url": "https://github.com/intellectronica/ruler/issues"
|
|
36
36
|
},
|
|
37
37
|
"homepage": "https://ai.intellectronica.net/ruler",
|
|
38
|
+
"engines": {
|
|
39
|
+
"node": ">=18"
|
|
40
|
+
},
|
|
38
41
|
"files": [
|
|
39
42
|
"dist",
|
|
40
43
|
"README.md",
|
|
@@ -47,22 +50,23 @@
|
|
|
47
50
|
"@types/iarna__toml": "^2.0.5",
|
|
48
51
|
"@types/jest": "^29.5.14",
|
|
49
52
|
"@types/js-yaml": "^4.0.9",
|
|
50
|
-
"@types/node": "^
|
|
51
|
-
"@types/yargs": "^17.0.
|
|
52
|
-
"@typescript-eslint/eslint-plugin": "^8.
|
|
53
|
-
"@typescript-eslint/parser": "^8.
|
|
54
|
-
"eslint": "^
|
|
55
|
-
"eslint-config-prettier": "^10.1.
|
|
56
|
-
"eslint-plugin-prettier": "^5.4
|
|
53
|
+
"@types/node": "^24.9.2",
|
|
54
|
+
"@types/yargs": "^17.0.34",
|
|
55
|
+
"@typescript-eslint/eslint-plugin": "^8.46.2",
|
|
56
|
+
"@typescript-eslint/parser": "^8.46.2",
|
|
57
|
+
"eslint": "^9.38.0",
|
|
58
|
+
"eslint-config-prettier": "^10.1.8",
|
|
59
|
+
"eslint-plugin-prettier": "^5.5.4",
|
|
57
60
|
"jest": "^29.7.0",
|
|
58
|
-
"prettier": "^3.
|
|
59
|
-
"ts-jest": "^29.
|
|
60
|
-
"typescript": "^5.
|
|
61
|
+
"prettier": "^3.6.2",
|
|
62
|
+
"ts-jest": "^29.4.5",
|
|
63
|
+
"typescript": "^5.9.3",
|
|
64
|
+
"typescript-eslint": "^8.46.2"
|
|
61
65
|
},
|
|
62
66
|
"dependencies": {
|
|
63
67
|
"@iarna/toml": "^2.2.5",
|
|
64
68
|
"js-yaml": "^4.1.0",
|
|
65
|
-
"yargs": "^
|
|
66
|
-
"zod": "^
|
|
69
|
+
"yargs": "^18.0.0",
|
|
70
|
+
"zod": "^4.1.12"
|
|
67
71
|
}
|
|
68
72
|
}
|