@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.
- package/README.md +135 -36
- package/dist/agents/AbstractAgent.d.ts +53 -0
- package/dist/agents/AbstractAgent.js +3 -2
- package/dist/agents/AgentsMdAgent.d.ts +14 -0
- package/dist/agents/AgentsMdAgent.js +3 -2
- package/dist/agents/AiderAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.js +7 -4
- package/dist/agents/AmazonQCliAgent.d.ts +13 -0
- package/dist/agents/AmazonQCliAgent.js +6 -4
- package/dist/agents/AmpAgent.d.ts +6 -0
- package/dist/agents/AntigravityAgent.d.ts +10 -0
- package/dist/agents/AugmentCodeAgent.d.ts +13 -0
- package/dist/agents/AugmentCodeAgent.js +3 -2
- package/dist/agents/ClaudeAgent.d.ts +13 -0
- package/dist/agents/ClineAgent.d.ts +9 -0
- package/dist/agents/CodexCliAgent.d.ts +31 -0
- package/dist/agents/CodexCliAgent.js +1 -1
- package/dist/agents/CopilotAgent.d.ts +20 -0
- package/dist/agents/CrushAgent.d.ts +14 -0
- package/dist/agents/CrushAgent.js +18 -6
- package/dist/agents/CursorAgent.d.ts +17 -0
- package/dist/agents/FactoryDroidAgent.d.ts +13 -0
- package/dist/agents/FirebaseAgent.d.ts +11 -0
- package/dist/agents/FirebenderAgent.d.ts +36 -0
- package/dist/agents/FirebenderAgent.js +5 -4
- package/dist/agents/GeminiCliAgent.d.ts +12 -0
- package/dist/agents/GeminiCliAgent.js +13 -7
- package/dist/agents/GooseAgent.d.ts +12 -0
- package/dist/agents/IAgent.d.ts +74 -0
- package/dist/agents/JetBrainsAiAssistantAgent.d.ts +10 -0
- package/dist/agents/JulesAgent.d.ts +5 -0
- package/dist/agents/JunieAgent.d.ts +12 -0
- package/dist/agents/KiloCodeAgent.d.ts +14 -0
- package/dist/agents/KiroAgent.d.ts +8 -0
- package/dist/agents/MistralVibeAgent.d.ts +31 -0
- package/dist/agents/MistralVibeAgent.js +14 -3
- package/dist/agents/OpenCodeAgent.d.ts +11 -0
- package/dist/agents/OpenCodeAgent.js +24 -12
- package/dist/agents/OpenHandsAgent.d.ts +8 -0
- package/dist/agents/PiAgent.d.ts +9 -0
- package/dist/agents/QwenCodeAgent.d.ts +11 -0
- package/dist/agents/QwenCodeAgent.js +11 -5
- package/dist/agents/RooCodeAgent.d.ts +16 -0
- package/dist/agents/RooCodeAgent.js +3 -2
- package/dist/agents/TraeAgent.d.ts +10 -0
- package/dist/agents/WarpAgent.d.ts +12 -0
- package/dist/agents/WindsurfAgent.d.ts +13 -0
- package/dist/agents/ZedAgent.d.ts +21 -0
- package/dist/agents/ZedAgent.js +8 -5
- package/dist/agents/agent-utils.d.ts +5 -0
- package/dist/agents/agent-utils.js +8 -5
- package/dist/agents/index.d.ts +9 -0
- package/dist/cli/commands.d.ts +4 -0
- package/dist/cli/commands.js +1 -2
- package/dist/cli/handlers.d.ts +41 -0
- package/dist/cli/handlers.js +75 -59
- package/dist/cli/index.d.ts +2 -0
- package/dist/constants.d.ts +35 -0
- package/dist/constants.js +1 -1
- package/dist/core/ConfigLoader.d.ts +59 -0
- package/dist/core/ConfigLoader.js +178 -44
- package/dist/core/FileSystemUtils.d.ts +53 -0
- package/dist/core/FileSystemUtils.js +157 -20
- package/dist/core/GitignoreUtils.d.ts +25 -0
- package/dist/core/GitignoreUtils.js +94 -32
- package/dist/core/RuleProcessor.d.ts +8 -0
- package/dist/core/SkillsProcessor.d.ts +127 -0
- package/dist/core/SkillsProcessor.js +118 -223
- package/dist/core/SkillsUtils.d.ts +26 -0
- package/dist/core/SubagentsProcessor.d.ts +38 -0
- package/dist/core/SubagentsProcessor.js +8 -5
- package/dist/core/SubagentsUtils.d.ts +34 -0
- package/dist/core/UnifiedConfigLoader.d.ts +10 -0
- package/dist/core/UnifiedConfigLoader.js +115 -33
- package/dist/core/UnifiedConfigTypes.d.ts +97 -0
- package/dist/core/agent-selection.d.ts +12 -0
- package/dist/core/agent-selection.js +17 -7
- package/dist/core/apply-engine.d.ts +70 -0
- package/dist/core/apply-engine.js +88 -58
- package/dist/core/config-utils.d.ts +14 -0
- package/dist/core/config-utils.js +9 -3
- package/dist/core/hash.d.ts +2 -0
- package/dist/core/path-utils.d.ts +1 -0
- package/dist/core/path-utils.js +42 -0
- package/dist/core/revert-engine.d.ts +37 -0
- package/dist/core/revert-engine.js +142 -34
- package/dist/lib.d.ts +13 -0
- package/dist/lib.js +24 -8
- package/dist/mcp/capabilities.d.ts +20 -0
- package/dist/mcp/merge.d.ts +10 -0
- package/dist/mcp/merge.js +36 -16
- package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenCodeMcp.js +30 -11
- package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenHandsMcp.js +48 -21
- package/dist/mcp/validate.d.ts +7 -0
- package/dist/mcp/validate.js +6 -1
- package/dist/paths/mcp.d.ts +8 -0
- package/dist/paths/mcp.js +44 -8
- package/dist/revert.d.ts +6 -0
- package/dist/revert.js +58 -46
- package/dist/types.d.ts +87 -0
- package/dist/vscode/settings.d.ts +40 -0
- package/dist/vscode/settings.js +3 -3
- 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 (
|
|
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:
|
|
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
|
|
111
|
-
|
|
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
|
|
128
|
-
const
|
|
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:
|
|
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(
|
|
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 =
|
|
286
|
-
parsedObj.
|
|
287
|
-
|
|
288
|
-
|
|
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
|
-
|
|
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
|
|
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) =>
|
|
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>;
|