@intellectronica/ruler 0.3.41 → 0.3.43

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 (105) hide show
  1. package/README.md +135 -36
  2. package/dist/agents/AbstractAgent.d.ts +53 -0
  3. package/dist/agents/AbstractAgent.js +3 -2
  4. package/dist/agents/AgentsMdAgent.d.ts +14 -0
  5. package/dist/agents/AgentsMdAgent.js +3 -2
  6. package/dist/agents/AiderAgent.d.ts +14 -0
  7. package/dist/agents/AiderAgent.js +7 -4
  8. package/dist/agents/AmazonQCliAgent.d.ts +13 -0
  9. package/dist/agents/AmazonQCliAgent.js +6 -4
  10. package/dist/agents/AmpAgent.d.ts +6 -0
  11. package/dist/agents/AntigravityAgent.d.ts +10 -0
  12. package/dist/agents/AugmentCodeAgent.d.ts +13 -0
  13. package/dist/agents/AugmentCodeAgent.js +3 -2
  14. package/dist/agents/ClaudeAgent.d.ts +13 -0
  15. package/dist/agents/ClineAgent.d.ts +9 -0
  16. package/dist/agents/CodexCliAgent.d.ts +31 -0
  17. package/dist/agents/CodexCliAgent.js +1 -1
  18. package/dist/agents/CopilotAgent.d.ts +20 -0
  19. package/dist/agents/CrushAgent.d.ts +14 -0
  20. package/dist/agents/CrushAgent.js +18 -6
  21. package/dist/agents/CursorAgent.d.ts +17 -0
  22. package/dist/agents/FactoryDroidAgent.d.ts +13 -0
  23. package/dist/agents/FirebaseAgent.d.ts +11 -0
  24. package/dist/agents/FirebenderAgent.d.ts +36 -0
  25. package/dist/agents/FirebenderAgent.js +5 -4
  26. package/dist/agents/GeminiCliAgent.d.ts +12 -0
  27. package/dist/agents/GeminiCliAgent.js +13 -7
  28. package/dist/agents/GooseAgent.d.ts +12 -0
  29. package/dist/agents/IAgent.d.ts +74 -0
  30. package/dist/agents/JetBrainsAiAssistantAgent.d.ts +10 -0
  31. package/dist/agents/JulesAgent.d.ts +5 -0
  32. package/dist/agents/JunieAgent.d.ts +12 -0
  33. package/dist/agents/KiloCodeAgent.d.ts +14 -0
  34. package/dist/agents/KiroAgent.d.ts +8 -0
  35. package/dist/agents/MistralVibeAgent.d.ts +31 -0
  36. package/dist/agents/MistralVibeAgent.js +14 -3
  37. package/dist/agents/OpenCodeAgent.d.ts +11 -0
  38. package/dist/agents/OpenCodeAgent.js +24 -12
  39. package/dist/agents/OpenHandsAgent.d.ts +8 -0
  40. package/dist/agents/PiAgent.d.ts +9 -0
  41. package/dist/agents/QwenCodeAgent.d.ts +11 -0
  42. package/dist/agents/QwenCodeAgent.js +11 -5
  43. package/dist/agents/RooCodeAgent.d.ts +16 -0
  44. package/dist/agents/RooCodeAgent.js +3 -2
  45. package/dist/agents/TraeAgent.d.ts +10 -0
  46. package/dist/agents/WarpAgent.d.ts +12 -0
  47. package/dist/agents/WindsurfAgent.d.ts +13 -0
  48. package/dist/agents/ZedAgent.d.ts +21 -0
  49. package/dist/agents/ZedAgent.js +8 -5
  50. package/dist/agents/agent-utils.d.ts +5 -0
  51. package/dist/agents/agent-utils.js +8 -5
  52. package/dist/agents/index.d.ts +9 -0
  53. package/dist/cli/commands.d.ts +4 -0
  54. package/dist/cli/commands.js +1 -2
  55. package/dist/cli/handlers.d.ts +41 -0
  56. package/dist/cli/handlers.js +75 -59
  57. package/dist/cli/index.d.ts +2 -0
  58. package/dist/constants.d.ts +35 -0
  59. package/dist/constants.js +1 -1
  60. package/dist/core/ConfigLoader.d.ts +59 -0
  61. package/dist/core/ConfigLoader.js +178 -44
  62. package/dist/core/FileSystemUtils.d.ts +53 -0
  63. package/dist/core/FileSystemUtils.js +157 -20
  64. package/dist/core/GitignoreUtils.d.ts +25 -0
  65. package/dist/core/GitignoreUtils.js +94 -32
  66. package/dist/core/RuleProcessor.d.ts +8 -0
  67. package/dist/core/SkillsProcessor.d.ts +127 -0
  68. package/dist/core/SkillsProcessor.js +118 -223
  69. package/dist/core/SkillsUtils.d.ts +26 -0
  70. package/dist/core/SubagentsProcessor.d.ts +38 -0
  71. package/dist/core/SubagentsProcessor.js +8 -5
  72. package/dist/core/SubagentsUtils.d.ts +34 -0
  73. package/dist/core/UnifiedConfigLoader.d.ts +10 -0
  74. package/dist/core/UnifiedConfigLoader.js +115 -33
  75. package/dist/core/UnifiedConfigTypes.d.ts +97 -0
  76. package/dist/core/agent-selection.d.ts +12 -0
  77. package/dist/core/agent-selection.js +17 -7
  78. package/dist/core/apply-engine.d.ts +70 -0
  79. package/dist/core/apply-engine.js +88 -58
  80. package/dist/core/config-utils.d.ts +14 -0
  81. package/dist/core/config-utils.js +9 -3
  82. package/dist/core/hash.d.ts +2 -0
  83. package/dist/core/path-utils.d.ts +1 -0
  84. package/dist/core/path-utils.js +42 -0
  85. package/dist/core/revert-engine.d.ts +37 -0
  86. package/dist/core/revert-engine.js +142 -34
  87. package/dist/lib.d.ts +13 -0
  88. package/dist/lib.js +24 -8
  89. package/dist/mcp/capabilities.d.ts +20 -0
  90. package/dist/mcp/merge.d.ts +10 -0
  91. package/dist/mcp/merge.js +36 -16
  92. package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
  93. package/dist/mcp/propagateOpenCodeMcp.js +30 -11
  94. package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
  95. package/dist/mcp/propagateOpenHandsMcp.js +48 -21
  96. package/dist/mcp/validate.d.ts +7 -0
  97. package/dist/mcp/validate.js +6 -1
  98. package/dist/paths/mcp.d.ts +8 -0
  99. package/dist/paths/mcp.js +44 -8
  100. package/dist/revert.d.ts +6 -0
  101. package/dist/revert.js +58 -46
  102. package/dist/types.d.ts +87 -0
  103. package/dist/vscode/settings.d.ts +40 -0
  104. package/dist/vscode/settings.js +3 -3
  105. package/package.json +8 -5
@@ -48,6 +48,7 @@ const yaml = __importStar(require("js-yaml"));
48
48
  const toml_1 = require("@iarna/toml");
49
49
  const constants_1 = require("../constants");
50
50
  const SubagentsUtils_1 = require("./SubagentsUtils");
51
+ const FileSystemUtils_1 = require("./FileSystemUtils");
51
52
  /**
52
53
  * Discovers subagent definitions in `.ruler/agents/`.
53
54
  * Each `.md` file is parsed for YAML frontmatter (name, description, …).
@@ -173,8 +174,9 @@ function ensureBodyFormatting(body) {
173
174
  * Stages files into a temp directory and atomically swaps it into place.
174
175
  * Mirrors the pattern used by SkillsProcessor for safe overwriting.
175
176
  */
176
- async function writeAgentsDirectoryAtomic(targetDir, files) {
177
+ async function writeAgentsDirectoryAtomic(targetDir, files, projectRoot) {
177
178
  const parent = path.dirname(targetDir);
179
+ await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(targetDir, projectRoot, 'Refusing to replace subagents directory through symlinked path');
178
180
  await fs.mkdir(parent, { recursive: true });
179
181
  const tempDir = path.join(parent, `agents.tmp-${Date.now()}`);
180
182
  await fs.mkdir(tempDir, { recursive: true });
@@ -310,7 +312,7 @@ async function propagateSubagentsForClaude(projectRoot, subagents, options) {
310
312
  name: getSourceRelativeMdPath(s),
311
313
  content: buildClaudeFile(s),
312
314
  }));
313
- await writeAgentsDirectoryAtomic(targetDir, files);
315
+ await writeAgentsDirectoryAtomic(targetDir, files, projectRoot);
314
316
  return [];
315
317
  }
316
318
  async function propagateSubagentsForCursor(projectRoot, subagents, options) {
@@ -324,7 +326,7 @@ async function propagateSubagentsForCursor(projectRoot, subagents, options) {
324
326
  name: getSourceRelativeMdPath(s),
325
327
  content: buildCursorFile(s),
326
328
  }));
327
- await writeAgentsDirectoryAtomic(targetDir, files);
329
+ await writeAgentsDirectoryAtomic(targetDir, files, projectRoot);
328
330
  return [];
329
331
  }
330
332
  async function propagateSubagentsForCodex(projectRoot, subagents, options) {
@@ -338,7 +340,7 @@ async function propagateSubagentsForCodex(projectRoot, subagents, options) {
338
340
  name: withExtension(getSourceRelativeMdPath(s), '.toml'),
339
341
  content: buildCodexFile(s),
340
342
  }));
341
- await writeAgentsDirectoryAtomic(targetDir, files);
343
+ await writeAgentsDirectoryAtomic(targetDir, files, projectRoot);
342
344
  return [];
343
345
  }
344
346
  async function propagateSubagentsForCopilot(projectRoot, subagents, options) {
@@ -361,7 +363,7 @@ async function propagateSubagentsForCopilot(projectRoot, subagents, options) {
361
363
  name: getSourceRelativeMdPath(s),
362
364
  content: buildCopilotFile(s, false, verbose).content,
363
365
  }));
364
- await writeAgentsDirectoryAtomic(targetDir, files);
366
+ await writeAgentsDirectoryAtomic(targetDir, files, projectRoot);
365
367
  return [];
366
368
  }
367
369
  /* ------------------------------------------------------------------ */
@@ -379,6 +381,7 @@ async function cleanupSubagentsDir(projectRoot, relPath, dryRun, verbose) {
379
381
  (0, constants_1.logVerboseInfo)(`DRY RUN: Would remove ${relPath}`, verbose, dryRun);
380
382
  return;
381
383
  }
384
+ await (0, FileSystemUtils_1.assertManagedPathInsideRoot)(target, projectRoot, 'Refusing to remove subagents directory through symlinked path');
382
385
  await fs.rm(target, { recursive: true, force: true });
383
386
  (0, constants_1.logVerboseInfo)(`Removed ${relPath} (subagents disabled)`, verbose, dryRun);
384
387
  }
@@ -0,0 +1,34 @@
1
+ import type { SubagentFrontmatter, SubagentInfo } from '../types';
2
+ export interface ParsedFrontmatter {
3
+ meta: Record<string, unknown>;
4
+ body: string;
5
+ }
6
+ /**
7
+ * Extracts YAML frontmatter and body from a Markdown file's contents.
8
+ * Returns null if no frontmatter delimiter pair is present at the head of the file.
9
+ */
10
+ export declare function parseFrontmatter(content: string): ParsedFrontmatter | null;
11
+ /**
12
+ * Validates a parsed frontmatter object and reads the required and optional
13
+ * fields into a typed SubagentFrontmatter. Returns the typed value on success
14
+ * or an error message on failure.
15
+ */
16
+ export declare function validateFrontmatter(meta: Record<string, unknown>, expectedName: string): {
17
+ value: SubagentFrontmatter;
18
+ } | {
19
+ error: string;
20
+ };
21
+ /**
22
+ * Loads a single subagent file and produces a SubagentInfo.
23
+ * Invalid files produce a SubagentInfo with valid=false and an error string.
24
+ */
25
+ export declare function loadSubagentFile(filePath: string): Promise<SubagentInfo>;
26
+ export interface CopilotToolMapping {
27
+ tools: string[];
28
+ unknown: string[];
29
+ }
30
+ /**
31
+ * Translates Claude tool names to Copilot aliases. Deduplicates results.
32
+ * Unknown source tools are reported separately so callers can surface a warning.
33
+ */
34
+ export declare function mapToolsForCopilot(sourceTools: string[]): CopilotToolMapping;
@@ -0,0 +1,10 @@
1
+ import { RulerUnifiedConfig } from './UnifiedConfigTypes';
2
+ export interface UnifiedLoadOptions {
3
+ projectRoot: string;
4
+ configPath?: string;
5
+ cliAgents?: string[];
6
+ cliMcpEnabled?: boolean;
7
+ cliMcpStrategy?: string;
8
+ checkGlobal?: boolean;
9
+ }
10
+ export declare function loadUnifiedConfig(options: UnifiedLoadOptions): Promise<RulerUnifiedConfig>;
@@ -40,10 +40,21 @@ const toml_1 = require("@iarna/toml");
40
40
  const hash_1 = require("./hash");
41
41
  const RuleProcessor_1 = require("./RuleProcessor");
42
42
  const FileSystemUtils = __importStar(require("./FileSystemUtils"));
43
+ const KNOWN_MCP_SERVER_FIELDS = new Set([
44
+ 'type',
45
+ 'command',
46
+ 'args',
47
+ 'env',
48
+ 'url',
49
+ 'headers',
50
+ 'timeout',
51
+ ]);
52
+ function copyAdditionalMcpServerFields(def) {
53
+ return Object.fromEntries(Object.entries(def).filter(([key]) => !KNOWN_MCP_SERVER_FIELDS.has(key)));
54
+ }
43
55
  async function loadUnifiedConfig(options) {
44
56
  // Resolve the effective .ruler directory (local or global), mirroring the main loader behavior
45
- const resolvedRulerDir = (await FileSystemUtils.findRulerDir(options.projectRoot, true)) ||
46
- path.join(options.projectRoot, '.ruler');
57
+ const resolvedRulerDir = (await FileSystemUtils.findRulerDir(options.projectRoot, options.checkGlobal ?? true)) || path.join(options.projectRoot, '.ruler');
47
58
  const meta = {
48
59
  projectRoot: options.projectRoot,
49
60
  rulerDir: resolvedRulerDir,
@@ -62,11 +73,14 @@ async function loadUnifiedConfig(options) {
62
73
  meta.configFile = tomlFile;
63
74
  }
64
75
  catch (err) {
65
- if (err.code !== 'ENOENT') {
76
+ if (options.configPath ||
77
+ err.code !== 'ENOENT') {
66
78
  diagnostics.push({
67
- severity: 'warning',
79
+ severity: options.configPath ? 'error' : 'warning',
68
80
  code: 'TOML_READ_ERROR',
69
- message: 'Failed to read ruler.toml',
81
+ message: options.configPath
82
+ ? 'Failed to read explicit config file'
83
+ : 'Failed to read ruler.toml',
70
84
  file: tomlFile,
71
85
  detail: err.message,
72
86
  });
@@ -104,37 +118,41 @@ async function loadUnifiedConfig(options) {
104
118
  nested,
105
119
  skills: skillsConfig,
106
120
  };
121
+ const includeAgentsInRules = (() => {
122
+ if (!tomlRaw || typeof tomlRaw !== 'object')
123
+ return false;
124
+ const raw = tomlRaw;
125
+ const agents = raw.agents;
126
+ const subagents = raw.subagents;
127
+ return ((agents &&
128
+ typeof agents === 'object' &&
129
+ agents.include_in_rules === true) ||
130
+ (subagents &&
131
+ typeof subagents === 'object' &&
132
+ subagents.include_in_rules === true));
133
+ })();
107
134
  // Collect rule markdown files
108
135
  let ruleFiles = [];
109
136
  try {
110
- const dirEntries = await fs_1.promises.readdir(meta.rulerDir, { withFileTypes: true });
111
- const mdFiles = dirEntries
112
- .filter((e) => e.isFile() && e.name.toLowerCase().endsWith('.md'))
113
- .map((e) => path.join(meta.rulerDir, e.name));
114
- // Sort lexicographically then ensure AGENTS.md first
115
- mdFiles.sort((a, b) => a.localeCompare(b));
116
- mdFiles.sort((a, b) => {
117
- const aIs = /agents\.md$/i.test(a);
118
- const bIs = /agents\.md$/i.test(b);
119
- if (aIs && !bIs)
120
- return -1;
121
- if (bIs && !aIs)
122
- return 1;
123
- return 0;
137
+ const mdFiles = await FileSystemUtils.readMarkdownFiles(meta.rulerDir, {
138
+ includeAgents: includeAgentsInRules,
124
139
  });
125
140
  let order = 0;
126
141
  ruleFiles = await Promise.all(mdFiles.map(async (file) => {
127
- const content = await fs_1.promises.readFile(file, 'utf8');
128
- const stat = await fs_1.promises.stat(file);
142
+ const stat = await fs_1.promises.stat(file.path);
143
+ const relativeFromRuler = path.relative(meta.rulerDir, file.path);
144
+ const relativePath = relativeFromRuler.startsWith('..')
145
+ ? path.relative(path.dirname(meta.rulerDir), file.path)
146
+ : relativeFromRuler;
129
147
  return {
130
- path: file,
131
- relativePath: path.basename(file),
132
- content,
133
- contentHash: (0, hash_1.sha256)(content),
148
+ path: file.path,
149
+ relativePath: relativePath.replace(/\\/g, '/'),
150
+ content: file.content,
151
+ contentHash: (0, hash_1.sha256)(file.content),
134
152
  mtimeMs: stat.mtimeMs,
135
153
  size: stat.size,
136
154
  order: order++,
137
- primary: /agents\.md$/i.test(file),
155
+ primary: /(^|[/\\])agents\.md$/i.test(relativePath),
138
156
  };
139
157
  }));
140
158
  }
@@ -163,7 +181,7 @@ async function loadUnifiedConfig(options) {
163
181
  if (!def || typeof def !== 'object')
164
182
  continue;
165
183
  const serverDef = def;
166
- const server = {};
184
+ const server = copyAdditionalMcpServerFields(serverDef);
167
185
  // Parse command and args
168
186
  if (typeof serverDef.command === 'string') {
169
187
  server.command = serverDef.command;
@@ -221,6 +239,11 @@ async function loadUnifiedConfig(options) {
221
239
  file: tomlFile,
222
240
  });
223
241
  }
242
+ if (hasCommand && hasUrl) {
243
+ delete server.command;
244
+ delete server.args;
245
+ delete server.env;
246
+ }
224
247
  // Derive type - remote takes precedence if both are present
225
248
  if (server.url) {
226
249
  server.type = 'remote';
@@ -282,14 +305,33 @@ async function loadUnifiedConfig(options) {
282
305
  }
283
306
  }
284
307
  const parsedObj = parsed;
285
- const serversRaw = parsedObj.mcpServers ||
286
- parsedObj.servers ||
287
- {};
288
- if (serversRaw && typeof serversRaw === 'object') {
308
+ const serversRaw = 'mcpServers' in parsedObj
309
+ ? parsedObj.mcpServers
310
+ : 'servers' in parsedObj
311
+ ? parsedObj.servers
312
+ : undefined;
313
+ if (!serversRaw ||
314
+ typeof serversRaw !== 'object' ||
315
+ Array.isArray(serversRaw)) {
316
+ diagnostics.push({
317
+ severity: 'warning',
318
+ code: 'MCP_INVALID_SHAPE',
319
+ message: 'mcp.json must contain a non-array object in "mcpServers" or "servers"',
320
+ file: mcpFile,
321
+ });
322
+ }
323
+ else {
289
324
  for (const [name, def] of Object.entries(serversRaw)) {
290
- if (!def || typeof def !== 'object')
325
+ if (!def || typeof def !== 'object' || Array.isArray(def)) {
326
+ diagnostics.push({
327
+ severity: 'warning',
328
+ code: 'MCP_INVALID_SERVER',
329
+ message: `MCP server '${name}' must be an object`,
330
+ file: mcpFile,
331
+ });
291
332
  continue;
292
- const server = {};
333
+ }
334
+ const server = copyAdditionalMcpServerFields(def);
293
335
  if (typeof def.command === 'string')
294
336
  server.command = def.command;
295
337
  if (Array.isArray(def.command))
@@ -307,6 +349,46 @@ async function loadUnifiedConfig(options) {
307
349
  if (typeof def.timeout === 'number') {
308
350
  server.timeout = def.timeout;
309
351
  }
352
+ const hasCommand = !!server.command;
353
+ const hasUrl = !!server.url;
354
+ if (!hasCommand && !hasUrl) {
355
+ diagnostics.push({
356
+ severity: 'warning',
357
+ code: 'MCP_JSON_INVALID_SERVER',
358
+ message: `MCP server '${name}' must have at least one of command or url`,
359
+ file: mcpFile,
360
+ });
361
+ continue;
362
+ }
363
+ if (hasCommand && hasUrl) {
364
+ diagnostics.push({
365
+ severity: 'warning',
366
+ code: 'MCP_JSON_FIELD_CONFLICT',
367
+ message: `MCP server '${name}' has both command and url - using url (remote)`,
368
+ file: mcpFile,
369
+ });
370
+ }
371
+ if (hasCommand && server.headers) {
372
+ diagnostics.push({
373
+ severity: 'warning',
374
+ code: 'MCP_JSON_FIELD_CONFLICT',
375
+ message: `MCP server '${name}' has headers with command (should be used with url only)`,
376
+ file: mcpFile,
377
+ });
378
+ }
379
+ if (hasUrl && server.env) {
380
+ diagnostics.push({
381
+ severity: 'warning',
382
+ code: 'MCP_JSON_FIELD_CONFLICT',
383
+ message: `MCP server '${name}' has env with url (should be used with command only)`,
384
+ file: mcpFile,
385
+ });
386
+ }
387
+ if (hasCommand && hasUrl) {
388
+ delete server.command;
389
+ delete server.args;
390
+ delete server.env;
391
+ }
310
392
  // Derive type
311
393
  if (server.url)
312
394
  server.type = 'remote';
@@ -0,0 +1,97 @@
1
+ import { McpConfig, GitignoreConfig, SkillsConfig, SubagentsConfig, McpStrategy } from '../types';
2
+ export interface RulerUnifiedConfig {
3
+ meta: ConfigMeta;
4
+ toml: TomlConfig;
5
+ rules: RulesBundle;
6
+ mcp: McpBundle | null;
7
+ agents: Record<string, EffectiveAgentConfig>;
8
+ diagnostics: ConfigDiagnostic[];
9
+ hash: string;
10
+ }
11
+ export interface ConfigMeta {
12
+ projectRoot: string;
13
+ rulerDir: string;
14
+ configFile?: string;
15
+ mcpFile?: string;
16
+ loadedAt: Date;
17
+ version: string;
18
+ }
19
+ export interface TomlConfig {
20
+ raw: unknown;
21
+ schemaVersion: number;
22
+ defaultAgents?: string[];
23
+ agents: Record<string, AgentTomlConfig>;
24
+ mcp?: McpToggleConfig;
25
+ mcpServers?: Record<string, McpServerDef>;
26
+ gitignore?: GitignoreConfig;
27
+ skills?: SkillsConfig;
28
+ subagents?: SubagentsConfig;
29
+ nested?: boolean;
30
+ }
31
+ export type McpToggleConfig = McpConfig;
32
+ export interface AgentTomlConfig {
33
+ enabled?: boolean;
34
+ outputPath?: string;
35
+ outputPathInstructions?: string;
36
+ outputPathConfig?: string;
37
+ mcp?: McpConfig;
38
+ mcpServers?: Record<string, McpServerDef>;
39
+ source: AgentConfigSourceMeta;
40
+ }
41
+ export interface AgentConfigSourceMeta {
42
+ sectionPath: string;
43
+ }
44
+ export interface RulesBundle {
45
+ files: RuleFile[];
46
+ concatenated: string;
47
+ concatenatedHash: string;
48
+ }
49
+ export interface RuleFile {
50
+ path: string;
51
+ relativePath: string;
52
+ content: string;
53
+ contentHash: string;
54
+ mtimeMs: number;
55
+ size: number;
56
+ order: number;
57
+ primary: boolean;
58
+ }
59
+ export interface McpBundle {
60
+ servers: Record<string, McpServerDef>;
61
+ raw: Record<string, unknown>;
62
+ hash: string;
63
+ }
64
+ export interface McpServerDef {
65
+ [key: string]: unknown;
66
+ type?: string;
67
+ command?: string;
68
+ args?: string[];
69
+ env?: Record<string, string>;
70
+ url?: string;
71
+ headers?: Record<string, string>;
72
+ timeout?: number;
73
+ }
74
+ export interface EffectiveAgentConfig {
75
+ identifier: string;
76
+ enabled: boolean;
77
+ output: AgentOutputPaths;
78
+ mcp: EffectiveMcpConfig;
79
+ toml?: AgentTomlConfig;
80
+ }
81
+ export interface AgentOutputPaths {
82
+ instructions?: string;
83
+ config?: string;
84
+ generic?: string;
85
+ }
86
+ export interface EffectiveMcpConfig {
87
+ enabled: boolean;
88
+ strategy: McpStrategy;
89
+ }
90
+ export type DiagnosticSeverity = 'info' | 'warning' | 'error';
91
+ export interface ConfigDiagnostic {
92
+ severity: DiagnosticSeverity;
93
+ code: string;
94
+ message: string;
95
+ file?: string;
96
+ detail?: string;
97
+ }
@@ -0,0 +1,12 @@
1
+ import { IAgent } from '../agents/IAgent';
2
+ import { LoadedConfig } from './ConfigLoader';
3
+ export declare function agentMatchesFilter(agent: IAgent, filter: string, validAgentIdentifiers: Set<string>): boolean;
4
+ /**
5
+ * Resolves which agents should be selected based on configuration.
6
+ * Handles precedence: CLI agents > default_agents > per-agent enabled flags > all agents
7
+ *
8
+ * @param config Loaded configuration containing CLI agents, default agents, and per-agent configs
9
+ * @param allAgents Array of all available agents
10
+ * @returns Array of agents that should be processed
11
+ */
12
+ export declare function resolveSelectedAgents(config: LoadedConfig, allAgents: IAgent[]): IAgent[];
@@ -1,7 +1,16 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.agentMatchesFilter = agentMatchesFilter;
3
4
  exports.resolveSelectedAgents = resolveSelectedAgents;
4
5
  const constants_1 = require("../constants");
6
+ function agentMatchesFilter(agent, filter, validAgentIdentifiers) {
7
+ const identifier = agent.getIdentifier().toLowerCase();
8
+ // Exact identifier matches take precedence over fuzzy display-name matching.
9
+ if (validAgentIdentifiers.has(filter)) {
10
+ return identifier === filter;
11
+ }
12
+ return agent.getName().toLowerCase().includes(filter);
13
+ }
5
14
  /**
6
15
  * Resolves which agents should be selected based on configuration.
7
16
  * Handles precedence: CLI agents > default_agents > per-agent enabled flags > all agents
@@ -13,24 +22,25 @@ const constants_1 = require("../constants");
13
22
  function resolveSelectedAgents(config, allAgents) {
14
23
  // CLI --agents > config.default_agents > per-agent.enabled flags > default all
15
24
  let selected = allAgents;
25
+ const validAgentIdentifiers = new Set(allAgents.map((agent) => agent.getIdentifier()));
26
+ const validAgentNames = new Set(allAgents.map((agent) => agent.getName().toLowerCase()));
27
+ const invalidConfiguredAgents = Object.keys(config.agentConfigs).filter((identifier) => !validAgentIdentifiers.has(identifier));
28
+ if (invalidConfiguredAgents.length > 0) {
29
+ throw (0, constants_1.createRulerError)(`Invalid agent configured: ${invalidConfiguredAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
30
+ }
16
31
  if (config.cliAgents && config.cliAgents.length > 0) {
17
32
  const filters = config.cliAgents.map((n) => n.toLowerCase());
18
33
  // Check if any of the specified agents don't exist
19
- const validAgentIdentifiers = new Set(allAgents.map((agent) => agent.getIdentifier()));
20
- const validAgentNames = new Set(allAgents.map((agent) => agent.getName().toLowerCase()));
21
34
  const invalidAgents = filters.filter((filter) => !validAgentIdentifiers.has(filter) &&
22
35
  ![...validAgentNames].some((name) => name.includes(filter)));
23
36
  if (invalidAgents.length > 0) {
24
37
  throw (0, constants_1.createRulerError)(`Invalid agent specified: ${invalidAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
25
38
  }
26
- selected = allAgents.filter((agent) => filters.some((f) => agent.getIdentifier() === f ||
27
- agent.getName().toLowerCase().includes(f)));
39
+ selected = allAgents.filter((agent) => filters.some((f) => agentMatchesFilter(agent, f, validAgentIdentifiers)));
28
40
  }
29
41
  else if (config.defaultAgents && config.defaultAgents.length > 0) {
30
42
  const defaults = config.defaultAgents.map((n) => n.toLowerCase());
31
43
  // Check if any of the default agents don't exist
32
- const validAgentIdentifiers = new Set(allAgents.map((agent) => agent.getIdentifier()));
33
- const validAgentNames = new Set(allAgents.map((agent) => agent.getName().toLowerCase()));
34
44
  const invalidAgents = defaults.filter((filter) => !validAgentIdentifiers.has(filter) &&
35
45
  ![...validAgentNames].some((name) => name.includes(filter)));
36
46
  if (invalidAgents.length > 0) {
@@ -42,7 +52,7 @@ function resolveSelectedAgents(config, allAgents) {
42
52
  if (override !== undefined) {
43
53
  return override;
44
54
  }
45
- return defaults.some((d) => identifier === d || agent.getName().toLowerCase().includes(d));
55
+ return defaults.some((d) => agentMatchesFilter(agent, d, validAgentIdentifiers));
46
56
  });
47
57
  }
48
58
  else {
@@ -0,0 +1,70 @@
1
+ import { LoadedConfig } from './ConfigLoader';
2
+ import { IAgent } from '../agents/IAgent';
3
+ import { McpStrategy } from '../types';
4
+ /**
5
+ * Configuration data loaded from the ruler setup
6
+ */
7
+ export interface RulerConfiguration {
8
+ config: LoadedConfig;
9
+ concatenatedRules: string;
10
+ rulerMcpJson: Record<string, unknown> | null;
11
+ projectRoot: string;
12
+ }
13
+ /**
14
+ * Configuration data for a specific .ruler directory in hierarchical mode
15
+ */
16
+ export interface HierarchicalRulerConfiguration extends RulerConfiguration {
17
+ rulerDir: string;
18
+ }
19
+ export declare function loadNestedConfigurations(projectRoot: string, configPath: string | undefined, localOnly: boolean, resolvedNested: boolean): Promise<HierarchicalRulerConfiguration[]>;
20
+ /**
21
+ * Loads configuration for single-directory mode (existing behavior).
22
+ */
23
+ export declare function loadSingleConfiguration(projectRoot: string, configPath: string | undefined, localOnly: boolean): Promise<RulerConfiguration>;
24
+ /**
25
+ * Processes hierarchical configurations by applying rules to each .ruler directory independently.
26
+ * Each directory gets its own set of rules and generates its own agent files.
27
+ * @param agents Array of agents to process
28
+ * @param configurations Array of hierarchical configurations for each .ruler directory
29
+ * @param verbose Whether to enable verbose logging
30
+ * @param dryRun Whether to perform a dry run
31
+ * @param cliMcpEnabled Whether MCP is enabled via CLI
32
+ * @param cliMcpStrategy MCP strategy from CLI
33
+ * @returns Promise resolving to array of generated file paths
34
+ */
35
+ export declare function processHierarchicalConfigurations(agents: IAgent[], configurations: HierarchicalRulerConfiguration[], verbose: boolean, dryRun: boolean, cliMcpEnabled: boolean, cliMcpStrategy?: McpStrategy, backup?: boolean): Promise<string[]>;
36
+ /**
37
+ * Processes a single configuration by applying rules to all selected agents.
38
+ * All rules are concatenated and applied to generate agent files in the project root.
39
+ * @param agents Array of agents to process
40
+ * @param configuration Single ruler configuration with concatenated rules
41
+ * @param projectRoot Root directory of the project
42
+ * @param verbose Whether to enable verbose logging
43
+ * @param dryRun Whether to perform a dry run
44
+ * @param cliMcpEnabled Whether MCP is enabled via CLI
45
+ * @param cliMcpStrategy MCP strategy from CLI
46
+ * @returns Promise resolving to array of generated file paths
47
+ */
48
+ export declare function processSingleConfiguration(agents: IAgent[], configuration: RulerConfiguration, projectRoot: string, verbose: boolean, dryRun: boolean, cliMcpEnabled: boolean, cliMcpStrategy?: McpStrategy, backup?: boolean): Promise<string[]>;
49
+ /**
50
+ * Applies configurations to the selected agents (internal function).
51
+ * @param agents Array of agents to process
52
+ * @param concatenatedRules Concatenated rule content
53
+ * @param rulerMcpJson MCP configuration JSON
54
+ * @param config Loaded configuration
55
+ * @param projectRoot Root directory of the project
56
+ * @param verbose Whether to enable verbose logging
57
+ * @param dryRun Whether to perform a dry run
58
+ * @returns Promise resolving to array of generated file paths
59
+ */
60
+ export declare function applyConfigurationsToAgents(agents: IAgent[], concatenatedRules: string, rulerMcpJson: Record<string, unknown> | null, config: LoadedConfig, projectRoot: string, verbose: boolean, dryRun: boolean, cliMcpEnabled?: boolean, cliMcpStrategy?: McpStrategy, backup?: boolean): Promise<string[]>;
61
+ /**
62
+ * Updates the .gitignore file with generated paths.
63
+ * @param projectRoot Root directory of the project
64
+ * @param generatedPaths Array of generated file paths
65
+ * @param config Loaded configuration
66
+ * @param cliGitignoreEnabled CLI gitignore setting
67
+ * @param dryRun Whether to perform a dry run
68
+ * @param cliGitignoreLocal CLI toggle for .git/info/exclude usage
69
+ */
70
+ export declare function updateGitignore(projectRoot: string, generatedPaths: string[], config: LoadedConfig, cliGitignoreEnabled: boolean | undefined, dryRun: boolean, cliGitignoreLocal?: boolean): Promise<void>;