@intellectronica/ruler 0.3.23 → 0.3.25
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 +2 -2
- package/dist/agents/AbstractAgent.js +7 -0
- package/dist/agents/OpenCodeAgent.js +3 -0
- package/dist/core/SkillsProcessor.js +3 -3
- package/dist/core/UnifiedConfigLoader.js +6 -0
- package/dist/core/apply-engine.js +46 -19
- package/dist/mcp/propagateOpenCodeMcp.js +6 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -633,7 +633,7 @@ For agents that support MCP but don't have native skills support, Ruler automati
|
|
|
633
633
|
|
|
634
634
|
1. Copies skills to `.skillz/` directory
|
|
635
635
|
2. Configures a Skillz MCP server in the agent's configuration
|
|
636
|
-
3. Uses `uvx` to launch the server with the
|
|
636
|
+
3. Uses `uvx` to launch the server with the project-relative path to `.skillz`
|
|
637
637
|
|
|
638
638
|
Agents using native skills support (Claude Code, GitHub Copilot, Kilo Code, OpenAI Codex CLI, OpenCode, Pi Coding Agent, Goose, Amp, Mistral Vibe, Roo Code, Gemini CLI, and Cursor) **do not** use the Skillz MCP server and instead use their own native skills directories.
|
|
639
639
|
|
|
@@ -642,7 +642,7 @@ Example auto-generated MCP server configuration:
|
|
|
642
642
|
```toml
|
|
643
643
|
[mcp_servers.skillz]
|
|
644
644
|
command = "uvx"
|
|
645
|
-
args = ["skillz@latest", "
|
|
645
|
+
args = ["skillz@latest", ".skillz"]
|
|
646
646
|
```
|
|
647
647
|
|
|
648
648
|
### `.gitignore` Integration
|
|
@@ -79,6 +79,13 @@ class AbstractAgent {
|
|
|
79
79
|
supportsMcpRemote() {
|
|
80
80
|
return false;
|
|
81
81
|
}
|
|
82
|
+
/**
|
|
83
|
+
* Returns whether this agent supports MCP server timeout configuration.
|
|
84
|
+
* Defaults to false if not overridden.
|
|
85
|
+
*/
|
|
86
|
+
supportsMcpTimeout() {
|
|
87
|
+
return false;
|
|
88
|
+
}
|
|
82
89
|
/**
|
|
83
90
|
* Returns whether this agent has native skills support.
|
|
84
91
|
* Defaults to false if not overridden.
|
|
@@ -808,7 +808,7 @@ async function propagateSkillsForSkillz(projectRoot, options) {
|
|
|
808
808
|
if (options.dryRun) {
|
|
809
809
|
return [
|
|
810
810
|
`Copy skills from ${constants_1.RULER_SKILLS_PATH} to ${constants_1.SKILLZ_DIR}`,
|
|
811
|
-
`Configure Skillz MCP server with
|
|
811
|
+
`Configure Skillz MCP server with path to ${constants_1.SKILLZ_DIR}`,
|
|
812
812
|
];
|
|
813
813
|
}
|
|
814
814
|
// Use atomic replace: copy to temp, then rename
|
|
@@ -843,11 +843,11 @@ async function propagateSkillsForSkillz(projectRoot, options) {
|
|
|
843
843
|
* Builds MCP config for Skillz server.
|
|
844
844
|
*/
|
|
845
845
|
function buildSkillzMcpConfig(projectRoot) {
|
|
846
|
-
|
|
846
|
+
void projectRoot;
|
|
847
847
|
return {
|
|
848
848
|
[constants_1.SKILLZ_MCP_SERVER_NAME]: {
|
|
849
849
|
command: 'uvx',
|
|
850
|
-
args: ['skillz@latest',
|
|
850
|
+
args: ['skillz@latest', constants_1.SKILLZ_DIR],
|
|
851
851
|
},
|
|
852
852
|
};
|
|
853
853
|
}
|
|
@@ -182,6 +182,9 @@ async function loadUnifiedConfig(options) {
|
|
|
182
182
|
if (serverDef.headers && typeof serverDef.headers === 'object') {
|
|
183
183
|
server.headers = Object.fromEntries(Object.entries(serverDef.headers).filter(([, v]) => typeof v === 'string'));
|
|
184
184
|
}
|
|
185
|
+
if (typeof serverDef.timeout === 'number') {
|
|
186
|
+
server.timeout = serverDef.timeout;
|
|
187
|
+
}
|
|
185
188
|
// Validate server configuration
|
|
186
189
|
const hasCommand = !!server.command;
|
|
187
190
|
const hasUrl = !!server.url;
|
|
@@ -301,6 +304,9 @@ async function loadUnifiedConfig(options) {
|
|
|
301
304
|
if (def.headers && typeof def.headers === 'object') {
|
|
302
305
|
server.headers = Object.fromEntries(Object.entries(def.headers).filter(([, v]) => typeof v === 'string'));
|
|
303
306
|
}
|
|
307
|
+
if (typeof def.timeout === 'number') {
|
|
308
|
+
server.timeout = def.timeout;
|
|
309
|
+
}
|
|
304
310
|
// Derive type
|
|
305
311
|
if (server.url)
|
|
306
312
|
server.type = 'remote';
|
|
@@ -260,13 +260,12 @@ async function processSingleConfiguration(agents, configuration, projectRoot, ve
|
|
|
260
260
|
return await applyConfigurationsToAgents(agents, configuration.concatenatedRules, configuration.rulerMcpJson, configuration.config, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
|
|
261
261
|
}
|
|
262
262
|
/**
|
|
263
|
-
* Adds Skillz MCP server to rulerMcpJson if skills exist and
|
|
263
|
+
* Adds Skillz MCP server to rulerMcpJson if skills exist and this agent needs it.
|
|
264
264
|
* Returns augmented MCP config or original if no changes needed.
|
|
265
265
|
*/
|
|
266
|
-
async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot,
|
|
267
|
-
// Check if
|
|
268
|
-
|
|
269
|
-
if (!hasAgentNeedingSkillz) {
|
|
266
|
+
async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agent, verbose) {
|
|
267
|
+
// Check if this agent supports MCP stdio but not native skills
|
|
268
|
+
if (!agent.supportsMcpStdio?.() || agent.supportsNativeSkills?.()) {
|
|
270
269
|
return rulerMcpJson;
|
|
271
270
|
}
|
|
272
271
|
// Check if .skillz directory exists
|
|
@@ -280,7 +279,7 @@ async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agents, ver
|
|
|
280
279
|
// Initialize empty config if null
|
|
281
280
|
const baseConfig = rulerMcpJson || { mcpServers: {} };
|
|
282
281
|
const mcpServers = baseConfig.mcpServers || {};
|
|
283
|
-
(0, constants_1.logVerbose)(
|
|
282
|
+
(0, constants_1.logVerbose)(`Adding Skillz MCP server to configuration for ${agent.getName()}`, verbose);
|
|
284
283
|
return {
|
|
285
284
|
...baseConfig,
|
|
286
285
|
mcpServers: {
|
|
@@ -308,17 +307,13 @@ async function addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agents, ver
|
|
|
308
307
|
async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJson, config, projectRoot, verbose, dryRun, cliMcpEnabled = true, cliMcpStrategy, backup = true, skillsEnabled = true) {
|
|
309
308
|
const generatedPaths = [];
|
|
310
309
|
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
|
-
}
|
|
318
310
|
for (const agent of agents) {
|
|
319
311
|
(0, constants_1.logInfo)(`Applying rules for ${agent.getName()}...`, dryRun);
|
|
320
312
|
(0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
|
|
321
313
|
const agentConfig = config.agentConfigs[agent.getIdentifier()];
|
|
314
|
+
const agentRulerMcpJson = skillsEnabled && !dryRun
|
|
315
|
+
? await addSkillzMcpServerIfNeeded(rulerMcpJson, projectRoot, agent, verbose)
|
|
316
|
+
: rulerMcpJson;
|
|
322
317
|
// Collect output paths for .gitignore
|
|
323
318
|
const outputPaths = (0, agent_utils_1.getAgentOutputPaths)(agent, projectRoot, agentConfig);
|
|
324
319
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
|
|
@@ -344,7 +339,7 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
344
339
|
}
|
|
345
340
|
}
|
|
346
341
|
let finalAgentConfig = agentConfig;
|
|
347
|
-
if (agent.getIdentifier() === 'augmentcode' &&
|
|
342
|
+
if (agent.getIdentifier() === 'augmentcode' && agentRulerMcpJson) {
|
|
348
343
|
const resolvedStrategy = cliMcpStrategy ??
|
|
349
344
|
agentConfig?.mcp?.strategy ??
|
|
350
345
|
config.mcp?.strategy ??
|
|
@@ -358,11 +353,11 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
358
353
|
};
|
|
359
354
|
}
|
|
360
355
|
if (!skipApplyForThisAgent) {
|
|
361
|
-
await agent.applyRulerConfig(concatenatedRules, projectRoot,
|
|
356
|
+
await agent.applyRulerConfig(concatenatedRules, projectRoot, agentRulerMcpJson, finalAgentConfig, backup);
|
|
362
357
|
}
|
|
363
358
|
}
|
|
364
359
|
// Handle MCP configuration
|
|
365
|
-
await handleMcpConfiguration(agent, agentConfig, config,
|
|
360
|
+
await handleMcpConfiguration(agent, agentConfig, config, agentRulerMcpJson, projectRoot, generatedPaths, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup, skillsEnabled);
|
|
366
361
|
}
|
|
367
362
|
return generatedPaths;
|
|
368
363
|
}
|
|
@@ -427,17 +422,49 @@ async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, back
|
|
|
427
422
|
}
|
|
428
423
|
}
|
|
429
424
|
}
|
|
425
|
+
function sanitizeMcpTimeoutsForAgent(agent, mcpJson, dryRun) {
|
|
426
|
+
if (agent.supportsMcpTimeout?.()) {
|
|
427
|
+
return mcpJson;
|
|
428
|
+
}
|
|
429
|
+
if (!mcpJson.mcpServers || typeof mcpJson.mcpServers !== 'object') {
|
|
430
|
+
return mcpJson;
|
|
431
|
+
}
|
|
432
|
+
const servers = mcpJson.mcpServers;
|
|
433
|
+
const sanitizedServers = {};
|
|
434
|
+
const strippedTimeouts = [];
|
|
435
|
+
for (const [name, serverDef] of Object.entries(servers)) {
|
|
436
|
+
if (serverDef && typeof serverDef === 'object') {
|
|
437
|
+
const copy = { ...serverDef };
|
|
438
|
+
if ('timeout' in copy) {
|
|
439
|
+
delete copy.timeout;
|
|
440
|
+
strippedTimeouts.push(name);
|
|
441
|
+
}
|
|
442
|
+
sanitizedServers[name] = copy;
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
sanitizedServers[name] = serverDef;
|
|
446
|
+
}
|
|
447
|
+
}
|
|
448
|
+
if (strippedTimeouts.length > 0) {
|
|
449
|
+
(0, constants_1.logWarn)(`${agent.getName()} does not support MCP server timeout configuration; ignoring timeout for: ${strippedTimeouts.join(', ')}`, dryRun);
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
...mcpJson,
|
|
453
|
+
mcpServers: sanitizedServers,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
430
456
|
async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
431
457
|
// Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
|
|
432
458
|
if (!dest.startsWith(projectRoot)) {
|
|
433
459
|
(0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
|
|
434
460
|
return;
|
|
435
461
|
}
|
|
462
|
+
const agentMcpJson = sanitizeMcpTimeoutsForAgent(agent, filteredMcpJson, dryRun);
|
|
436
463
|
if (agent.getIdentifier() === 'openhands') {
|
|
437
|
-
return await applyOpenHandsMcpConfiguration(
|
|
464
|
+
return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
|
|
438
465
|
}
|
|
439
466
|
if (agent.getIdentifier() === 'opencode') {
|
|
440
|
-
return await applyOpenCodeMcpConfiguration(
|
|
467
|
+
return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
|
|
441
468
|
}
|
|
442
469
|
// Agents that handle MCP configuration internally should not have external MCP handling
|
|
443
470
|
if (agent.getIdentifier() === 'zed' ||
|
|
@@ -447,7 +474,7 @@ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig,
|
|
|
447
474
|
(0, constants_1.logVerbose)(`Skipping external MCP config for ${agent.getName()} - handled internally by agent`, verbose);
|
|
448
475
|
return;
|
|
449
476
|
}
|
|
450
|
-
return await applyStandardMcpConfiguration(agent,
|
|
477
|
+
return await applyStandardMcpConfiguration(agent, agentMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
|
|
451
478
|
}
|
|
452
479
|
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
|
|
453
480
|
if (dryRun) {
|
|
@@ -63,6 +63,9 @@ function transformToOpenCodeFormat(rulerMcp) {
|
|
|
63
63
|
if (serverDef.headers) {
|
|
64
64
|
openCodeServer.headers = serverDef.headers;
|
|
65
65
|
}
|
|
66
|
+
if (typeof serverDef.timeout === 'number') {
|
|
67
|
+
openCodeServer.timeout = serverDef.timeout;
|
|
68
|
+
}
|
|
66
69
|
}
|
|
67
70
|
else if (isLocalServer(serverDef)) {
|
|
68
71
|
openCodeServer.type = 'local';
|
|
@@ -74,6 +77,9 @@ function transformToOpenCodeFormat(rulerMcp) {
|
|
|
74
77
|
if (serverDef.env) {
|
|
75
78
|
openCodeServer.environment = serverDef.env;
|
|
76
79
|
}
|
|
80
|
+
if (typeof serverDef.timeout === 'number') {
|
|
81
|
+
openCodeServer.timeout = serverDef.timeout;
|
|
82
|
+
}
|
|
77
83
|
}
|
|
78
84
|
else {
|
|
79
85
|
continue;
|