@intellectronica/ruler 0.2.19 → 0.3.1

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.
Files changed (47) hide show
  1. package/README.md +281 -62
  2. package/dist/agents/AbstractAgent.js +82 -0
  3. package/dist/agents/AgentsMdAgent.js +84 -0
  4. package/dist/agents/AiderAgent.js +29 -9
  5. package/dist/agents/AmpAgent.js +2 -44
  6. package/dist/agents/AugmentCodeAgent.js +10 -12
  7. package/dist/agents/ClaudeAgent.js +9 -9
  8. package/dist/agents/ClineAgent.js +3 -9
  9. package/dist/agents/CodexCliAgent.js +27 -21
  10. package/dist/agents/CopilotAgent.js +9 -10
  11. package/dist/agents/CrushAgent.js +6 -0
  12. package/dist/agents/CursorAgent.js +13 -5
  13. package/dist/agents/FirebaseAgent.js +2 -8
  14. package/dist/agents/GeminiCliAgent.js +31 -22
  15. package/dist/agents/GooseAgent.js +2 -10
  16. package/dist/agents/JulesAgent.js +3 -44
  17. package/dist/agents/JunieAgent.js +2 -9
  18. package/dist/agents/KiloCodeAgent.js +8 -9
  19. package/dist/agents/KiroAgent.js +50 -0
  20. package/dist/agents/OpenCodeAgent.js +50 -11
  21. package/dist/agents/OpenHandsAgent.js +8 -9
  22. package/dist/agents/QwenCodeAgent.js +83 -0
  23. package/dist/agents/WarpAgent.js +61 -0
  24. package/dist/agents/WindsurfAgent.js +16 -5
  25. package/dist/agents/ZedAgent.js +132 -0
  26. package/dist/agents/agent-utils.js +37 -0
  27. package/dist/agents/index.js +53 -0
  28. package/dist/cli/commands.js +53 -242
  29. package/dist/cli/handlers.js +181 -0
  30. package/dist/constants.js +9 -2
  31. package/dist/core/ConfigLoader.js +4 -1
  32. package/dist/core/FileSystemUtils.js +123 -4
  33. package/dist/core/RuleProcessor.js +15 -3
  34. package/dist/core/UnifiedConfigLoader.js +364 -0
  35. package/dist/core/UnifiedConfigTypes.js +2 -0
  36. package/dist/core/agent-selection.js +52 -0
  37. package/dist/core/apply-engine.js +416 -0
  38. package/dist/core/config-utils.js +30 -0
  39. package/dist/core/hash.js +24 -0
  40. package/dist/core/revert-engine.js +413 -0
  41. package/dist/lib.js +46 -329
  42. package/dist/mcp/capabilities.js +51 -0
  43. package/dist/mcp/propagateOpenCodeMcp.js +30 -31
  44. package/dist/mcp/propagateOpenHandsMcp.js +101 -17
  45. package/dist/paths/mcp.js +7 -3
  46. package/dist/revert.js +96 -481
  47. package/package.json +2 -1
package/dist/lib.js CHANGED
@@ -1,123 +1,13 @@
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
- })();
35
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.allAgents = void 0;
36
4
  exports.applyAllAgentConfigs = applyAllAgentConfigs;
37
- const path = __importStar(require("path"));
38
- const fs_1 = require("fs");
39
- const FileSystemUtils = __importStar(require("./core/FileSystemUtils"));
40
- const RuleProcessor_1 = require("./core/RuleProcessor");
41
- const ConfigLoader_1 = require("./core/ConfigLoader");
42
- const GitignoreUtils_1 = require("./core/GitignoreUtils");
43
- const CopilotAgent_1 = require("./agents/CopilotAgent");
44
- const ClaudeAgent_1 = require("./agents/ClaudeAgent");
45
- const CodexCliAgent_1 = require("./agents/CodexCliAgent");
46
- const CursorAgent_1 = require("./agents/CursorAgent");
47
- const WindsurfAgent_1 = require("./agents/WindsurfAgent");
48
- const ClineAgent_1 = require("./agents/ClineAgent");
49
- const AiderAgent_1 = require("./agents/AiderAgent");
50
- const FirebaseAgent_1 = require("./agents/FirebaseAgent");
51
- const OpenHandsAgent_1 = require("./agents/OpenHandsAgent");
52
- const GeminiCliAgent_1 = require("./agents/GeminiCliAgent");
53
- const JulesAgent_1 = require("./agents/JulesAgent");
54
- const JunieAgent_1 = require("./agents/JunieAgent");
55
- const AugmentCodeAgent_1 = require("./agents/AugmentCodeAgent");
56
- const KiloCodeAgent_1 = require("./agents/KiloCodeAgent");
57
- const OpenCodeAgent_1 = require("./agents/OpenCodeAgent");
58
- const CrushAgent_1 = require("./agents/CrushAgent");
59
- const GooseAgent_1 = require("./agents/GooseAgent");
60
- const AmpAgent_1 = require("./agents/AmpAgent");
61
- const merge_1 = require("./mcp/merge");
62
- const validate_1 = require("./mcp/validate");
63
- const mcp_1 = require("./paths/mcp");
64
- const propagateOpenHandsMcp_1 = require("./mcp/propagateOpenHandsMcp");
65
- const propagateOpenCodeMcp_1 = require("./mcp/propagateOpenCodeMcp");
5
+ const agents_1 = require("./agents");
6
+ Object.defineProperty(exports, "allAgents", { enumerable: true, get: function () { return agents_1.allAgents; } });
66
7
  const constants_1 = require("./constants");
67
- /**
68
- * Gets all output paths for an agent, taking into account any config overrides.
69
- */
70
- function getAgentOutputPaths(agent, projectRoot, agentConfig) {
71
- const paths = [];
72
- const defaults = agent.getDefaultOutputPath(projectRoot);
73
- if (typeof defaults === 'string') {
74
- // Single output path (most agents)
75
- const actualPath = agentConfig?.outputPath ?? defaults;
76
- paths.push(actualPath);
77
- }
78
- else {
79
- // Multiple output paths (e.g., AiderAgent)
80
- const defaultPaths = defaults;
81
- // Handle instructions path
82
- if ('instructions' in defaultPaths) {
83
- const instructionsPath = agentConfig?.outputPathInstructions ?? defaultPaths.instructions;
84
- paths.push(instructionsPath);
85
- }
86
- // Handle config path
87
- if ('config' in defaultPaths) {
88
- const configPath = agentConfig?.outputPathConfig ?? defaultPaths.config;
89
- paths.push(configPath);
90
- }
91
- // Handle any other paths in the default paths record
92
- for (const [key, defaultPath] of Object.entries(defaultPaths)) {
93
- if (key !== 'instructions' && key !== 'config') {
94
- // For unknown path types, use the default since we don't have specific config overrides
95
- paths.push(defaultPath);
96
- }
97
- }
98
- }
99
- return paths;
100
- }
101
- const agents = [
102
- new CopilotAgent_1.CopilotAgent(),
103
- new ClaudeAgent_1.ClaudeAgent(),
104
- new CodexCliAgent_1.CodexCliAgent(),
105
- new CursorAgent_1.CursorAgent(),
106
- new WindsurfAgent_1.WindsurfAgent(),
107
- new ClineAgent_1.ClineAgent(),
108
- new AiderAgent_1.AiderAgent(),
109
- new FirebaseAgent_1.FirebaseAgent(),
110
- new OpenHandsAgent_1.OpenHandsAgent(),
111
- new GeminiCliAgent_1.GeminiCliAgent(),
112
- new JulesAgent_1.JulesAgent(),
113
- new JunieAgent_1.JunieAgent(),
114
- new AugmentCodeAgent_1.AugmentCodeAgent(),
115
- new KiloCodeAgent_1.KiloCodeAgent(),
116
- new OpenCodeAgent_1.OpenCodeAgent(),
117
- new GooseAgent_1.GooseAgent(),
118
- new CrushAgent_1.CrushAgent(),
119
- new AmpAgent_1.AmpAgent(),
120
- ];
8
+ const apply_engine_1 = require("./core/apply-engine");
9
+ const config_utils_1 = require("./core/config-utils");
10
+ const agents = agents_1.allAgents;
121
11
  /**
122
12
  * Applies ruler configurations for all supported AI agents.
123
13
  * @param projectRoot Root directory of the project
@@ -127,224 +17,51 @@ const agents = [
127
17
  * @param projectRoot Root directory of the project
128
18
  * @param includedAgents Optional list of agent name filters (case-insensitive substrings)
129
19
  */
130
- async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false) {
131
- // Load configuration (default_agents, per-agent overrides, CLI filters)
20
+ async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false) {
21
+ // Load configuration and rules
132
22
  (0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
133
23
  if (configPath) {
134
24
  (0, constants_1.logVerbose)(`Using custom config path: ${configPath}`, verbose);
135
25
  }
136
- const config = await (0, ConfigLoader_1.loadConfig)({
137
- projectRoot,
138
- cliAgents: includedAgents,
139
- configPath,
140
- });
141
- (0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(config.agentConfigs).length} agent configs`, verbose);
142
- // Normalize per-agent config keys to agent identifiers (exact match or substring match)
143
- const rawConfigs = config.agentConfigs;
144
- const mappedConfigs = {};
145
- for (const [key, cfg] of Object.entries(rawConfigs)) {
146
- const lowerKey = key.toLowerCase();
147
- for (const agent of agents) {
148
- const identifier = agent.getIdentifier();
149
- // Exact match with identifier or substring match with display name for backwards compatibility
150
- if (identifier === lowerKey ||
151
- agent.getName().toLowerCase().includes(lowerKey)) {
152
- mappedConfigs[identifier] = cfg;
153
- }
154
- }
155
- }
156
- config.agentConfigs = mappedConfigs;
157
- const rulerDir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
158
- if (!rulerDir) {
159
- throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
160
- }
161
- (0, constants_1.logVerbose)(`Found .ruler directory at: ${rulerDir}`, verbose);
162
- const files = await FileSystemUtils.readMarkdownFiles(rulerDir);
163
- (0, constants_1.logVerbose)(`Found ${files.length} markdown files in ruler configuration directory`, verbose);
164
- const concatenated = (0, RuleProcessor_1.concatenateRules)(files);
165
- (0, constants_1.logVerbose)(`Concatenated rules length: ${concatenated.length} characters`, verbose);
166
- const mcpFile = path.join(rulerDir, 'mcp.json');
167
- let rulerMcpJson = null;
168
- try {
169
- const raw = await fs_1.promises.readFile(mcpFile, 'utf8');
170
- rulerMcpJson = JSON.parse(raw);
171
- (0, validate_1.validateMcp)(rulerMcpJson);
172
- (0, constants_1.logVerbose)(`Loaded MCP configuration from: ${mcpFile}`, verbose);
173
- }
174
- catch (err) {
175
- if (err.code !== 'ENOENT') {
176
- throw (0, constants_1.createRulerError)(`Failed to load MCP configuration`, `File: ${mcpFile}, Error: ${err.message}`);
177
- }
178
- (0, constants_1.logVerbose)(`No MCP configuration found at: ${mcpFile}`, verbose);
179
- }
180
- // Determine which agents to run:
181
- // CLI --agents > config.default_agents > per-agent.enabled flags > default all
182
- let selected = agents;
183
- if (config.cliAgents && config.cliAgents.length > 0) {
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(', ')}`);
26
+ let selectedAgents;
27
+ let generatedPaths;
28
+ let loadedConfig;
29
+ if (nested) {
30
+ const hierarchicalConfigs = await (0, apply_engine_1.loadNestedConfigurations)(projectRoot, configPath, localOnly);
31
+ if (hierarchicalConfigs.length === 0) {
32
+ throw new Error('No .ruler directories found');
192
33
  }
193
- selected = agents.filter((agent) => filters.some((f) => agent.getIdentifier() === f ||
194
- agent.getName().toLowerCase().includes(f)));
195
- (0, constants_1.logVerbose)(`Selected agents via CLI filter: ${selected.map((a) => a.getName()).join(', ')}`, verbose);
196
- }
197
- else if (config.defaultAgents && config.defaultAgents.length > 0) {
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
- }
207
- selected = agents.filter((agent) => {
208
- const identifier = agent.getIdentifier();
209
- const override = config.agentConfigs[identifier]?.enabled;
210
- if (override !== undefined) {
211
- return override;
212
- }
213
- return defaults.some((d) => identifier === d || agent.getName().toLowerCase().includes(d));
214
- });
215
- (0, constants_1.logVerbose)(`Selected agents via config default_agents: ${selected.map((a) => a.getName()).join(', ')}`, verbose);
34
+ // Use the root config for agent selection (all levels share the same agent settings)
35
+ const rootConfig = hierarchicalConfigs[0].config;
36
+ loadedConfig = rootConfig;
37
+ rootConfig.cliAgents = includedAgents;
38
+ (0, constants_1.logVerbose)(`Loaded ${hierarchicalConfigs.length} .ruler directory configurations`, verbose);
39
+ (0, constants_1.logVerbose)(`Root configuration has ${Object.keys(rootConfig.agentConfigs).length} agent configs`, verbose);
40
+ normalizeAgentConfigs(rootConfig, agents);
41
+ selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, rootConfig);
42
+ (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
43
+ generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
216
44
  }
217
45
  else {
218
- selected = agents.filter((agent) => config.agentConfigs[agent.getIdentifier()]?.enabled !== false);
219
- (0, constants_1.logVerbose)(`Selected all enabled agents: ${selected.map((a) => a.getName()).join(', ')}`, verbose);
220
- }
221
- // Collect all generated file paths for .gitignore
222
- const generatedPaths = [];
223
- let agentsMdWritten = false;
224
- for (const agent of selected) {
225
- const actionPrefix = dryRun ? '[ruler:dry-run]' : '[ruler]';
226
- console.log(`${actionPrefix} Applying rules for ${agent.getName()}...`);
227
- (0, constants_1.logVerbose)(`Processing agent: ${agent.getName()}`, verbose);
228
- const agentConfig = config.agentConfigs[agent.getIdentifier()];
229
- // Collect output paths for .gitignore
230
- const outputPaths = getAgentOutputPaths(agent, projectRoot, agentConfig);
231
- (0, constants_1.logVerbose)(`Agent ${agent.getName()} output paths: ${outputPaths.join(', ')}`, verbose);
232
- generatedPaths.push(...outputPaths);
233
- // Also add the backup file paths to the gitignore list
234
- const backupPaths = outputPaths.map((p) => `${p}.bak`);
235
- generatedPaths.push(...backupPaths);
236
- if (dryRun) {
237
- (0, constants_1.logVerbose)(`DRY RUN: Would write rules to: ${outputPaths.join(', ')}`, verbose);
238
- }
239
- else {
240
- if (agent.getIdentifier() === 'jules' ||
241
- agent.getIdentifier() === 'codex') {
242
- if (agentsMdWritten) {
243
- continue;
244
- }
245
- agentsMdWritten = true;
246
- }
247
- let finalAgentConfig = agentConfig;
248
- if (agent.getIdentifier() === 'augmentcode' && rulerMcpJson) {
249
- const resolvedStrategy = cliMcpStrategy ??
250
- agentConfig?.mcp?.strategy ??
251
- config.mcp?.strategy ??
252
- 'merge';
253
- finalAgentConfig = {
254
- ...agentConfig,
255
- mcp: {
256
- ...agentConfig?.mcp,
257
- strategy: resolvedStrategy,
258
- },
259
- };
260
- }
261
- await agent.applyRulerConfig(concatenated, projectRoot, rulerMcpJson, finalAgentConfig);
262
- }
263
- const dest = await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
264
- const mcpEnabledForAgent = cliMcpEnabled &&
265
- (agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
266
- const rulerMcpFile = path.join(rulerDir, 'mcp.json');
267
- if (dest && mcpEnabledForAgent) {
268
- // Include MCP config file in .gitignore only if it's within the project directory
269
- if (dest.startsWith(projectRoot)) {
270
- const relativeDest = path.relative(projectRoot, dest);
271
- generatedPaths.push(relativeDest);
272
- // Also add the backup for the MCP file
273
- generatedPaths.push(`${relativeDest}.bak`);
274
- }
275
- if (agent.getIdentifier() === 'openhands') {
276
- // *** Special handling for Open Hands ***
277
- if (dryRun) {
278
- (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
279
- }
280
- else {
281
- await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(rulerMcpFile, dest);
282
- }
283
- // Open Hands config file is already included above
284
- }
285
- else if (agent.getIdentifier() === 'augmentcode') {
286
- // *** Special handling for AugmentCode ***
287
- // AugmentCode handles MCP configuration internally in applyRulerConfig
288
- // by updating VSCode settings.json with augment.advanced.mcpServers format
289
- if (dryRun) {
290
- (0, constants_1.logVerbose)(`DRY RUN: AugmentCode MCP config handled internally via VSCode settings`, verbose);
291
- }
292
- }
293
- else if (agent.getIdentifier() === 'opencode') {
294
- // *** Special handling for OpenCode ***
295
- if (dryRun) {
296
- (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
297
- }
298
- else {
299
- await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(rulerMcpFile, dest);
300
- }
301
- }
302
- else {
303
- if (rulerMcpJson) {
304
- const strategy = cliMcpStrategy ??
305
- agentConfig?.mcp?.strategy ??
306
- config.mcp?.strategy ??
307
- 'merge';
308
- // Determine the correct server key for the agent
309
- const serverKey = agent.getMcpServerKey?.() ?? 'mcpServers';
310
- (0, constants_1.logVerbose)(`Applying MCP config for ${agent.getName()} with strategy: ${strategy} and key: ${serverKey}`, verbose);
311
- if (dryRun) {
312
- (0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config to: ${dest}`, true);
313
- }
314
- else {
315
- const existing = await (0, mcp_1.readNativeMcp)(dest);
316
- const merged = (0, merge_1.mergeMcp)(existing, rulerMcpJson, strategy, serverKey);
317
- await (0, mcp_1.writeNativeMcp)(dest, merged);
318
- }
319
- }
320
- }
321
- }
322
- }
323
- // Handle .gitignore updates
324
- // Configuration precedence: CLI > TOML > Default (enabled)
325
- let gitignoreEnabled;
326
- if (cliGitignoreEnabled !== undefined) {
327
- gitignoreEnabled = cliGitignoreEnabled;
328
- }
329
- else if (config.gitignore?.enabled !== undefined) {
330
- gitignoreEnabled = config.gitignore.enabled;
331
- }
332
- else {
333
- gitignoreEnabled = true; // Default enabled
334
- }
335
- if (gitignoreEnabled && generatedPaths.length > 0) {
336
- const uniquePaths = [...new Set(generatedPaths)];
337
- // Add wildcard pattern for backup files
338
- uniquePaths.push('*.bak');
339
- if (uniquePaths.length > 0) {
340
- const actionPrefix = dryRun ? '[ruler:dry-run]' : '[ruler]';
341
- if (dryRun) {
342
- console.log(`${actionPrefix} Would update .gitignore with ${uniquePaths.length} unique path(s): ${uniquePaths.join(', ')}`);
343
- }
344
- else {
345
- await (0, GitignoreUtils_1.updateGitignore)(projectRoot, uniquePaths);
346
- console.log(`${actionPrefix} Updated .gitignore with ${uniquePaths.length} unique path(s) in the Ruler block.`);
347
- }
348
- }
349
- }
46
+ const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
47
+ loadedConfig = singleConfig.config;
48
+ singleConfig.config.cliAgents = includedAgents;
49
+ (0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(singleConfig.config.agentConfigs).length} agent configs`, verbose);
50
+ (0, constants_1.logVerbose)(`Found .ruler directory with ${singleConfig.concatenatedRules.length} characters of rules`, verbose);
51
+ normalizeAgentConfigs(singleConfig.config, agents);
52
+ selectedAgents = (0, apply_engine_1.selectAgentsToRun)(agents, singleConfig.config);
53
+ (0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
54
+ generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy);
55
+ }
56
+ await (0, apply_engine_1.updateGitignore)(projectRoot, generatedPaths, loadedConfig, cliGitignoreEnabled, dryRun);
57
+ }
58
+ /**
59
+ * Normalizes per-agent config keys to agent identifiers for consistent lookup.
60
+ * Maps both exact identifier matches and substring matches with agent names.
61
+ * @param config The configuration object to normalize
62
+ * @param agents Array of available agents
63
+ */
64
+ function normalizeAgentConfigs(config, agents) {
65
+ // Normalize per-agent config keys to agent identifiers (exact match or substring match)
66
+ config.agentConfigs = (0, config_utils_1.mapRawAgentConfigs)(config.agentConfigs, agents);
350
67
  }
@@ -0,0 +1,51 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAgentMcpCapabilities = getAgentMcpCapabilities;
4
+ exports.agentSupportsMcp = agentSupportsMcp;
5
+ exports.filterMcpConfigForAgent = filterMcpConfigForAgent;
6
+ /**
7
+ * Derives MCP capabilities for an agent
8
+ */
9
+ function getAgentMcpCapabilities(agent) {
10
+ return {
11
+ supportsStdio: agent.supportsMcpStdio?.() ?? false,
12
+ supportsRemote: agent.supportsMcpRemote?.() ?? false,
13
+ };
14
+ }
15
+ /**
16
+ * Checks if an agent supports any MCP functionality
17
+ */
18
+ function agentSupportsMcp(agent) {
19
+ const capabilities = getAgentMcpCapabilities(agent);
20
+ return capabilities.supportsStdio || capabilities.supportsRemote;
21
+ }
22
+ /**
23
+ * Filters MCP configuration based on agent capabilities
24
+ */
25
+ function filterMcpConfigForAgent(mcpConfig, agent) {
26
+ const capabilities = getAgentMcpCapabilities(agent);
27
+ if (!agentSupportsMcp(agent)) {
28
+ return null;
29
+ }
30
+ const servers = mcpConfig.mcpServers;
31
+ if (!servers) {
32
+ return null;
33
+ }
34
+ const filteredServers = {};
35
+ for (const [serverName, serverConfig] of Object.entries(servers)) {
36
+ const config = serverConfig;
37
+ // Determine server type
38
+ const hasCommand = 'command' in config;
39
+ const hasUrl = 'url' in config;
40
+ const isStdio = hasCommand && !hasUrl;
41
+ const isRemote = hasUrl && !hasCommand;
42
+ // Include server if agent supports its type
43
+ if ((isStdio && capabilities.supportsStdio) ||
44
+ (isRemote && capabilities.supportsRemote)) {
45
+ filteredServers[serverName] = serverConfig;
46
+ }
47
+ }
48
+ return Object.keys(filteredServers).length > 0
49
+ ? { mcpServers: filteredServers }
50
+ : null;
51
+ }
@@ -37,6 +37,15 @@ exports.propagateMcpToOpenCode = propagateMcpToOpenCode;
37
37
  const fs = __importStar(require("fs/promises"));
38
38
  const FileSystemUtils_1 = require("../core/FileSystemUtils");
39
39
  const path = __importStar(require("path"));
40
+ function isLocalServer(value) {
41
+ const server = value;
42
+ return (server &&
43
+ (typeof server.command === 'string' || Array.isArray(server.command)));
44
+ }
45
+ function isRemoteServer(value) {
46
+ const server = value;
47
+ return server && typeof server.url === 'string';
48
+ }
40
49
  /**
41
50
  * Transform ruler MCP configuration to OpenCode's specific format
42
51
  */
@@ -44,34 +53,31 @@ function transformToOpenCodeFormat(rulerMcp) {
44
53
  const rulerServers = rulerMcp.mcpServers || {};
45
54
  const openCodeServers = {};
46
55
  for (const [name, serverDef] of Object.entries(rulerServers)) {
47
- const server = serverDef;
48
- // Determine if this is a local or remote server
49
- const isRemote = !!server.url;
50
56
  const openCodeServer = {
51
- type: isRemote ? 'remote' : 'local',
52
- enabled: true, // Always true as per the issue requirements
57
+ type: 'local',
58
+ enabled: true,
53
59
  };
54
- if (isRemote) {
55
- // Remote server configuration
56
- openCodeServer.url = server.url;
57
- if (server.headers) {
58
- openCodeServer.headers = server.headers;
60
+ if (isRemoteServer(serverDef)) {
61
+ openCodeServer.type = 'remote';
62
+ openCodeServer.url = serverDef.url;
63
+ if (serverDef.headers) {
64
+ openCodeServer.headers = serverDef.headers;
59
65
  }
60
66
  }
61
- else {
62
- // Local server configuration
63
- if (server.command) {
64
- // Combine command and args into a single array
65
- const command = Array.isArray(server.command)
66
- ? server.command
67
- : [server.command];
68
- const args = server.args || [];
69
- openCodeServer.command = [...command, ...args];
70
- }
71
- if (server.env) {
72
- openCodeServer.environment = server.env;
67
+ else if (isLocalServer(serverDef)) {
68
+ openCodeServer.type = 'local';
69
+ const command = Array.isArray(serverDef.command)
70
+ ? serverDef.command
71
+ : [serverDef.command];
72
+ const args = serverDef.args || [];
73
+ openCodeServer.command = [...command, ...args];
74
+ if (serverDef.env) {
75
+ openCodeServer.environment = serverDef.env;
73
76
  }
74
77
  }
78
+ else {
79
+ continue;
80
+ }
75
81
  openCodeServers[name] = openCodeServer;
76
82
  }
77
83
  return {
@@ -79,15 +85,8 @@ function transformToOpenCodeFormat(rulerMcp) {
79
85
  mcp: openCodeServers,
80
86
  };
81
87
  }
82
- async function propagateMcpToOpenCode(rulerMcpPath, openCodeConfigPath) {
83
- let rulerMcp;
84
- try {
85
- const rulerJsonContent = await fs.readFile(rulerMcpPath, 'utf8');
86
- rulerMcp = JSON.parse(rulerJsonContent);
87
- }
88
- catch {
89
- return;
90
- }
88
+ async function propagateMcpToOpenCode(rulerMcpData, openCodeConfigPath) {
89
+ const rulerMcp = rulerMcpData || {};
91
90
  // Read existing OpenCode config if it exists
92
91
  let existingConfig = {};
93
92
  try {