@intellectronica/ruler 0.3.40 → 0.3.42
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 +59 -46
- package/dist/agents/AbstractAgent.d.ts +53 -0
- package/dist/agents/AgentsMdAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.d.ts +14 -0
- package/dist/agents/AiderAgent.js +3 -1
- package/dist/agents/AmazonQCliAgent.d.ts +13 -0
- 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/ClaudeAgent.d.ts +13 -0
- package/dist/agents/ClineAgent.d.ts +9 -0
- package/dist/agents/CodexCliAgent.d.ts +31 -0
- package/dist/agents/CopilotAgent.d.ts +20 -0
- package/dist/agents/CrushAgent.d.ts +14 -0
- package/dist/agents/CrushAgent.js +5 -2
- 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/GeminiCliAgent.d.ts +11 -0
- package/dist/agents/GeminiCliAgent.js +2 -2
- package/dist/agents/GooseAgent.d.ts +12 -0
- package/dist/agents/IAgent.d.ts +72 -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/OpenCodeAgent.d.ts +11 -0
- package/dist/agents/OpenCodeAgent.js +14 -9
- package/dist/agents/OpenHandsAgent.d.ts +8 -0
- package/dist/agents/PiAgent.d.ts +9 -0
- package/dist/agents/QwenCodeAgent.d.ts +10 -0
- package/dist/agents/QwenCodeAgent.js +2 -2
- package/dist/agents/RooCodeAgent.d.ts +16 -0
- 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 +5 -2
- 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 +2 -3
- package/dist/cli/handlers.d.ts +41 -0
- package/dist/cli/handlers.js +76 -60
- package/dist/cli/index.d.ts +2 -0
- package/dist/constants.d.ts +35 -0
- package/dist/core/ConfigLoader.d.ts +57 -0
- package/dist/core/ConfigLoader.js +123 -41
- package/dist/core/FileSystemUtils.d.ts +51 -0
- package/dist/core/FileSystemUtils.js +37 -17
- package/dist/core/GitignoreUtils.d.ts +15 -0
- package/dist/core/GitignoreUtils.js +32 -1
- package/dist/core/RuleProcessor.d.ts +8 -0
- package/dist/core/SkillsProcessor.d.ts +127 -0
- package/dist/core/SkillsProcessor.js +104 -218
- package/dist/core/SkillsUtils.d.ts +26 -0
- package/dist/core/SubagentsProcessor.d.ts +38 -0
- package/dist/core/SubagentsProcessor.js +68 -22
- package/dist/core/SubagentsUtils.d.ts +34 -0
- package/dist/core/UnifiedConfigLoader.d.ts +10 -0
- package/dist/core/UnifiedConfigLoader.js +61 -31
- package/dist/core/UnifiedConfigTypes.d.ts +95 -0
- package/dist/core/agent-selection.d.ts +12 -0
- package/dist/core/agent-selection.js +11 -3
- package/dist/core/apply-engine.d.ts +69 -0
- package/dist/core/apply-engine.js +57 -50
- 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 +36 -0
- package/dist/core/revert-engine.js +70 -9
- package/dist/lib.d.ts +13 -0
- package/dist/lib.js +23 -5
- package/dist/mcp/capabilities.d.ts +20 -0
- package/dist/mcp/merge.d.ts +10 -0
- package/dist/mcp/merge.js +19 -1
- package/dist/mcp/propagateOpenCodeMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenCodeMcp.js +21 -9
- package/dist/mcp/propagateOpenHandsMcp.d.ts +2 -0
- package/dist/mcp/propagateOpenHandsMcp.js +31 -15
- 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 +33 -4
- package/dist/revert.d.ts +6 -0
- package/dist/revert.js +39 -27
- package/dist/types.d.ts +87 -0
- package/dist/vscode/settings.d.ts +40 -0
- package/package.json +7 -4
|
@@ -62,16 +62,13 @@ async function discoverSubagents(projectRoot) {
|
|
|
62
62
|
catch {
|
|
63
63
|
return { subagents: [], warnings: [] };
|
|
64
64
|
}
|
|
65
|
-
const
|
|
66
|
-
const mdFiles = entries
|
|
67
|
-
.filter((entry) => entry.isFile() && entry.name.endsWith('.md'))
|
|
68
|
-
.map((entry) => path.join(dir, entry.name))
|
|
69
|
-
.sort();
|
|
65
|
+
const mdFiles = await listMarkdownFilesRecursive(dir);
|
|
70
66
|
const subagents = [];
|
|
71
67
|
const warnings = [];
|
|
72
68
|
for (const filePath of mdFiles) {
|
|
73
69
|
const info = await (0, SubagentsUtils_1.loadSubagentFile)(filePath);
|
|
74
70
|
if (info.valid) {
|
|
71
|
+
info.sourceRelativePath = path.relative(dir, filePath);
|
|
75
72
|
subagents.push(info);
|
|
76
73
|
}
|
|
77
74
|
else if (info.error) {
|
|
@@ -80,6 +77,22 @@ async function discoverSubagents(projectRoot) {
|
|
|
80
77
|
}
|
|
81
78
|
return { subagents, warnings };
|
|
82
79
|
}
|
|
80
|
+
async function listMarkdownFilesRecursive(dir) {
|
|
81
|
+
const results = [];
|
|
82
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
83
|
+
for (const entry of entries) {
|
|
84
|
+
const fullPath = path.join(dir, entry.name);
|
|
85
|
+
if (entry.isDirectory()) {
|
|
86
|
+
const nested = await listMarkdownFilesRecursive(fullPath);
|
|
87
|
+
results.push(...nested);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (entry.isFile() && entry.name.endsWith('.md')) {
|
|
91
|
+
results.push(fullPath);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return results.sort();
|
|
95
|
+
}
|
|
83
96
|
const SUBAGENT_TARGET_TO_IDENTIFIERS = new Map([
|
|
84
97
|
['claude', ['claude']],
|
|
85
98
|
['cursor', ['cursor']],
|
|
@@ -167,7 +180,9 @@ async function writeAgentsDirectoryAtomic(targetDir, files) {
|
|
|
167
180
|
await fs.mkdir(tempDir, { recursive: true });
|
|
168
181
|
try {
|
|
169
182
|
for (const { name, content } of files) {
|
|
170
|
-
|
|
183
|
+
const outputPath = path.join(tempDir, name);
|
|
184
|
+
await fs.mkdir(path.dirname(outputPath), { recursive: true });
|
|
185
|
+
await fs.writeFile(outputPath, content, 'utf8');
|
|
171
186
|
}
|
|
172
187
|
try {
|
|
173
188
|
await fs.rm(targetDir, { recursive: true, force: true });
|
|
@@ -187,6 +202,19 @@ async function writeAgentsDirectoryAtomic(targetDir, files) {
|
|
|
187
202
|
throw error;
|
|
188
203
|
}
|
|
189
204
|
}
|
|
205
|
+
function getSourceRelativeMdPath(sub) {
|
|
206
|
+
const fromSource = sub.sourceRelativePath;
|
|
207
|
+
if (typeof fromSource === 'string' &&
|
|
208
|
+
fromSource.length > 0 &&
|
|
209
|
+
!path.isAbsolute(fromSource) &&
|
|
210
|
+
!fromSource.startsWith('..')) {
|
|
211
|
+
return fromSource;
|
|
212
|
+
}
|
|
213
|
+
return `${sub.name}.md`;
|
|
214
|
+
}
|
|
215
|
+
function withExtension(filePath, ext) {
|
|
216
|
+
return filePath.replace(/\.md$/i, ext);
|
|
217
|
+
}
|
|
190
218
|
function buildClaudeFile(sub) {
|
|
191
219
|
const fm = sub.frontmatter;
|
|
192
220
|
const meta = {
|
|
@@ -276,10 +304,10 @@ async function propagateSubagentsForClaude(projectRoot, subagents, options) {
|
|
|
276
304
|
return [];
|
|
277
305
|
const targetDir = path.join(projectRoot, constants_1.CLAUDE_SUBAGENTS_PATH);
|
|
278
306
|
if (options.dryRun) {
|
|
279
|
-
return subagents.map((s) => `Write ${path.join(constants_1.CLAUDE_SUBAGENTS_PATH,
|
|
307
|
+
return subagents.map((s) => `Write ${path.join(constants_1.CLAUDE_SUBAGENTS_PATH, getSourceRelativeMdPath(s))}`);
|
|
280
308
|
}
|
|
281
309
|
const files = subagents.map((s) => ({
|
|
282
|
-
name:
|
|
310
|
+
name: getSourceRelativeMdPath(s),
|
|
283
311
|
content: buildClaudeFile(s),
|
|
284
312
|
}));
|
|
285
313
|
await writeAgentsDirectoryAtomic(targetDir, files);
|
|
@@ -290,10 +318,10 @@ async function propagateSubagentsForCursor(projectRoot, subagents, options) {
|
|
|
290
318
|
return [];
|
|
291
319
|
const targetDir = path.join(projectRoot, constants_1.CURSOR_SUBAGENTS_PATH);
|
|
292
320
|
if (options.dryRun) {
|
|
293
|
-
return subagents.map((s) => `Write ${path.join(constants_1.CURSOR_SUBAGENTS_PATH,
|
|
321
|
+
return subagents.map((s) => `Write ${path.join(constants_1.CURSOR_SUBAGENTS_PATH, getSourceRelativeMdPath(s))}`);
|
|
294
322
|
}
|
|
295
323
|
const files = subagents.map((s) => ({
|
|
296
|
-
name:
|
|
324
|
+
name: getSourceRelativeMdPath(s),
|
|
297
325
|
content: buildCursorFile(s),
|
|
298
326
|
}));
|
|
299
327
|
await writeAgentsDirectoryAtomic(targetDir, files);
|
|
@@ -304,10 +332,10 @@ async function propagateSubagentsForCodex(projectRoot, subagents, options) {
|
|
|
304
332
|
return [];
|
|
305
333
|
const targetDir = path.join(projectRoot, constants_1.CODEX_SUBAGENTS_PATH);
|
|
306
334
|
if (options.dryRun) {
|
|
307
|
-
return subagents.map((s) => `Write ${path.join(constants_1.CODEX_SUBAGENTS_PATH,
|
|
335
|
+
return subagents.map((s) => `Write ${path.join(constants_1.CODEX_SUBAGENTS_PATH, withExtension(getSourceRelativeMdPath(s), '.toml'))}`);
|
|
308
336
|
}
|
|
309
337
|
const files = subagents.map((s) => ({
|
|
310
|
-
name:
|
|
338
|
+
name: withExtension(getSourceRelativeMdPath(s), '.toml'),
|
|
311
339
|
content: buildCodexFile(s),
|
|
312
340
|
}));
|
|
313
341
|
await writeAgentsDirectoryAtomic(targetDir, files);
|
|
@@ -325,12 +353,12 @@ async function propagateSubagentsForCopilot(projectRoot, subagents, options) {
|
|
|
325
353
|
// emits when dryRun is true so users previewing a change can see
|
|
326
354
|
// which tools would be dropped before it actually happens.
|
|
327
355
|
buildCopilotFile(s, true, verbose);
|
|
328
|
-
planLines.push(`Write ${path.join(constants_1.COPILOT_SUBAGENTS_PATH,
|
|
356
|
+
planLines.push(`Write ${path.join(constants_1.COPILOT_SUBAGENTS_PATH, getSourceRelativeMdPath(s))}`);
|
|
329
357
|
}
|
|
330
358
|
return planLines;
|
|
331
359
|
}
|
|
332
360
|
const files = subagents.map((s) => ({
|
|
333
|
-
name:
|
|
361
|
+
name: getSourceRelativeMdPath(s),
|
|
334
362
|
content: buildCopilotFile(s, false, verbose).content,
|
|
335
363
|
}));
|
|
336
364
|
await writeAgentsDirectoryAtomic(targetDir, files);
|
|
@@ -363,10 +391,24 @@ async function cleanupAllSubagentsDirectories(projectRoot, dryRun, verbose) {
|
|
|
363
391
|
/* ------------------------------------------------------------------ */
|
|
364
392
|
/* Orchestrator */
|
|
365
393
|
/* ------------------------------------------------------------------ */
|
|
366
|
-
async function propagateSubagents(projectRoot, agents, subagentsEnabled, verbose, dryRun) {
|
|
367
|
-
|
|
368
|
-
(
|
|
394
|
+
async function propagateSubagents(projectRoot, agents, subagentsEnabled, cleanupOrphaned, verbose, dryRun) {
|
|
395
|
+
const maybeCleanupAllSubagentsDirectories = async () => {
|
|
396
|
+
if (!cleanupOrphaned) {
|
|
397
|
+
(0, constants_1.logVerboseInfo)('Subagent cleanup skipped (set [agents] cleanup_orphaned = true to enable directory cleanup)', verbose, dryRun);
|
|
398
|
+
return;
|
|
399
|
+
}
|
|
369
400
|
await cleanupAllSubagentsDirectories(projectRoot, dryRun, verbose);
|
|
401
|
+
};
|
|
402
|
+
const maybeCleanupSubagentsDir = async (relPath) => {
|
|
403
|
+
if (!cleanupOrphaned)
|
|
404
|
+
return;
|
|
405
|
+
await cleanupSubagentsDir(projectRoot, relPath, dryRun, verbose);
|
|
406
|
+
};
|
|
407
|
+
if (!subagentsEnabled) {
|
|
408
|
+
(0, constants_1.logVerboseInfo)(cleanupOrphaned
|
|
409
|
+
? 'Subagents support disabled, cleaning up subagent directories'
|
|
410
|
+
: 'Subagents support disabled, leaving existing subagent directories unchanged', verbose, dryRun);
|
|
411
|
+
await maybeCleanupAllSubagentsDirectories();
|
|
370
412
|
return;
|
|
371
413
|
}
|
|
372
414
|
const sourceDir = path.join(projectRoot, constants_1.RULER_SUBAGENTS_PATH);
|
|
@@ -374,16 +416,20 @@ async function propagateSubagents(projectRoot, agents, subagentsEnabled, verbose
|
|
|
374
416
|
await fs.access(sourceDir);
|
|
375
417
|
}
|
|
376
418
|
catch {
|
|
377
|
-
(0, constants_1.logVerboseInfo)(
|
|
378
|
-
|
|
419
|
+
(0, constants_1.logVerboseInfo)(cleanupOrphaned
|
|
420
|
+
? 'No .ruler/agents directory found, cleaning up any stale managed subagent directories'
|
|
421
|
+
: 'No .ruler/agents directory found; leaving existing subagent directories unchanged', verbose, dryRun);
|
|
422
|
+
await maybeCleanupAllSubagentsDirectories();
|
|
379
423
|
return;
|
|
380
424
|
}
|
|
381
425
|
const { subagents, warnings } = await discoverSubagents(projectRoot);
|
|
382
426
|
for (const w of warnings)
|
|
383
427
|
(0, constants_1.logWarn)(w, dryRun);
|
|
384
428
|
if (subagents.length === 0) {
|
|
385
|
-
(0, constants_1.logVerboseInfo)(
|
|
386
|
-
|
|
429
|
+
(0, constants_1.logVerboseInfo)(cleanupOrphaned
|
|
430
|
+
? 'No valid subagents found in .ruler/agents; cleaning up any stale managed subagent directories'
|
|
431
|
+
: 'No valid subagents found in .ruler/agents; leaving existing subagent directories unchanged', verbose, dryRun);
|
|
432
|
+
await maybeCleanupAllSubagentsDirectories();
|
|
387
433
|
return;
|
|
388
434
|
}
|
|
389
435
|
(0, constants_1.logVerboseInfo)(`Discovered ${subagents.length} subagent(s)`, verbose, dryRun);
|
|
@@ -401,7 +447,7 @@ async function propagateSubagents(projectRoot, agents, subagentsEnabled, verbose
|
|
|
401
447
|
const allTargets = ['claude', 'cursor', 'codex', 'copilot'];
|
|
402
448
|
for (const target of allTargets) {
|
|
403
449
|
if (!targets.has(target)) {
|
|
404
|
-
await
|
|
450
|
+
await maybeCleanupSubagentsDir(SUBAGENT_TARGET_PATHS[target]);
|
|
405
451
|
}
|
|
406
452
|
}
|
|
407
453
|
if (supporting.length === 0) {
|
|
@@ -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>;
|
|
@@ -42,8 +42,7 @@ const RuleProcessor_1 = require("./RuleProcessor");
|
|
|
42
42
|
const FileSystemUtils = __importStar(require("./FileSystemUtils"));
|
|
43
43
|
async function loadUnifiedConfig(options) {
|
|
44
44
|
// 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');
|
|
45
|
+
const resolvedRulerDir = (await FileSystemUtils.findRulerDir(options.projectRoot, options.checkGlobal ?? true)) || path.join(options.projectRoot, '.ruler');
|
|
47
46
|
const meta = {
|
|
48
47
|
projectRoot: options.projectRoot,
|
|
49
48
|
rulerDir: resolvedRulerDir,
|
|
@@ -62,11 +61,14 @@ async function loadUnifiedConfig(options) {
|
|
|
62
61
|
meta.configFile = tomlFile;
|
|
63
62
|
}
|
|
64
63
|
catch (err) {
|
|
65
|
-
if (
|
|
64
|
+
if (options.configPath ||
|
|
65
|
+
err.code !== 'ENOENT') {
|
|
66
66
|
diagnostics.push({
|
|
67
|
-
severity: 'warning',
|
|
67
|
+
severity: options.configPath ? 'error' : 'warning',
|
|
68
68
|
code: 'TOML_READ_ERROR',
|
|
69
|
-
message:
|
|
69
|
+
message: options.configPath
|
|
70
|
+
? 'Failed to read explicit config file'
|
|
71
|
+
: 'Failed to read ruler.toml',
|
|
70
72
|
file: tomlFile,
|
|
71
73
|
detail: err.message,
|
|
72
74
|
});
|
|
@@ -104,37 +106,41 @@ async function loadUnifiedConfig(options) {
|
|
|
104
106
|
nested,
|
|
105
107
|
skills: skillsConfig,
|
|
106
108
|
};
|
|
109
|
+
const includeAgentsInRules = (() => {
|
|
110
|
+
if (!tomlRaw || typeof tomlRaw !== 'object')
|
|
111
|
+
return false;
|
|
112
|
+
const raw = tomlRaw;
|
|
113
|
+
const agents = raw.agents;
|
|
114
|
+
const subagents = raw.subagents;
|
|
115
|
+
return ((agents &&
|
|
116
|
+
typeof agents === 'object' &&
|
|
117
|
+
agents.include_in_rules === true) ||
|
|
118
|
+
(subagents &&
|
|
119
|
+
typeof subagents === 'object' &&
|
|
120
|
+
subagents.include_in_rules === true));
|
|
121
|
+
})();
|
|
107
122
|
// Collect rule markdown files
|
|
108
123
|
let ruleFiles = [];
|
|
109
124
|
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;
|
|
125
|
+
const mdFiles = await FileSystemUtils.readMarkdownFiles(meta.rulerDir, {
|
|
126
|
+
includeAgents: includeAgentsInRules,
|
|
124
127
|
});
|
|
125
128
|
let order = 0;
|
|
126
129
|
ruleFiles = await Promise.all(mdFiles.map(async (file) => {
|
|
127
|
-
const
|
|
128
|
-
const
|
|
130
|
+
const stat = await fs_1.promises.stat(file.path);
|
|
131
|
+
const relativeFromRuler = path.relative(meta.rulerDir, file.path);
|
|
132
|
+
const relativePath = relativeFromRuler.startsWith('..')
|
|
133
|
+
? path.relative(path.dirname(meta.rulerDir), file.path)
|
|
134
|
+
: relativeFromRuler;
|
|
129
135
|
return {
|
|
130
|
-
path: file,
|
|
131
|
-
relativePath:
|
|
132
|
-
content,
|
|
133
|
-
contentHash: (0, hash_1.sha256)(content),
|
|
136
|
+
path: file.path,
|
|
137
|
+
relativePath: relativePath.replace(/\\/g, '/'),
|
|
138
|
+
content: file.content,
|
|
139
|
+
contentHash: (0, hash_1.sha256)(file.content),
|
|
134
140
|
mtimeMs: stat.mtimeMs,
|
|
135
141
|
size: stat.size,
|
|
136
142
|
order: order++,
|
|
137
|
-
primary: /agents\.md$/i.test(
|
|
143
|
+
primary: /(^|[/\\])agents\.md$/i.test(relativePath),
|
|
138
144
|
};
|
|
139
145
|
}));
|
|
140
146
|
}
|
|
@@ -221,6 +227,11 @@ async function loadUnifiedConfig(options) {
|
|
|
221
227
|
file: tomlFile,
|
|
222
228
|
});
|
|
223
229
|
}
|
|
230
|
+
if (hasCommand && hasUrl) {
|
|
231
|
+
delete server.command;
|
|
232
|
+
delete server.args;
|
|
233
|
+
delete server.env;
|
|
234
|
+
}
|
|
224
235
|
// Derive type - remote takes precedence if both are present
|
|
225
236
|
if (server.url) {
|
|
226
237
|
server.type = 'remote';
|
|
@@ -282,13 +293,32 @@ async function loadUnifiedConfig(options) {
|
|
|
282
293
|
}
|
|
283
294
|
}
|
|
284
295
|
const parsedObj = parsed;
|
|
285
|
-
const serversRaw =
|
|
286
|
-
parsedObj.
|
|
287
|
-
|
|
288
|
-
|
|
296
|
+
const serversRaw = 'mcpServers' in parsedObj
|
|
297
|
+
? parsedObj.mcpServers
|
|
298
|
+
: 'servers' in parsedObj
|
|
299
|
+
? parsedObj.servers
|
|
300
|
+
: undefined;
|
|
301
|
+
if (!serversRaw ||
|
|
302
|
+
typeof serversRaw !== 'object' ||
|
|
303
|
+
Array.isArray(serversRaw)) {
|
|
304
|
+
diagnostics.push({
|
|
305
|
+
severity: 'warning',
|
|
306
|
+
code: 'MCP_INVALID_SHAPE',
|
|
307
|
+
message: 'mcp.json must contain a non-array object in "mcpServers" or "servers"',
|
|
308
|
+
file: mcpFile,
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
else {
|
|
289
312
|
for (const [name, def] of Object.entries(serversRaw)) {
|
|
290
|
-
if (!def || typeof def !== 'object')
|
|
313
|
+
if (!def || typeof def !== 'object' || Array.isArray(def)) {
|
|
314
|
+
diagnostics.push({
|
|
315
|
+
severity: 'warning',
|
|
316
|
+
code: 'MCP_INVALID_SERVER',
|
|
317
|
+
message: `MCP server '${name}' must be an object`,
|
|
318
|
+
file: mcpFile,
|
|
319
|
+
});
|
|
291
320
|
continue;
|
|
321
|
+
}
|
|
292
322
|
const server = {};
|
|
293
323
|
if (typeof def.command === 'string')
|
|
294
324
|
server.command = def.command;
|
|
@@ -0,0 +1,95 @@
|
|
|
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
|
+
source: AgentConfigSourceMeta;
|
|
39
|
+
}
|
|
40
|
+
export interface AgentConfigSourceMeta {
|
|
41
|
+
sectionPath: string;
|
|
42
|
+
}
|
|
43
|
+
export interface RulesBundle {
|
|
44
|
+
files: RuleFile[];
|
|
45
|
+
concatenated: string;
|
|
46
|
+
concatenatedHash: string;
|
|
47
|
+
}
|
|
48
|
+
export interface RuleFile {
|
|
49
|
+
path: string;
|
|
50
|
+
relativePath: string;
|
|
51
|
+
content: string;
|
|
52
|
+
contentHash: string;
|
|
53
|
+
mtimeMs: number;
|
|
54
|
+
size: number;
|
|
55
|
+
order: number;
|
|
56
|
+
primary: boolean;
|
|
57
|
+
}
|
|
58
|
+
export interface McpBundle {
|
|
59
|
+
servers: Record<string, McpServerDef>;
|
|
60
|
+
raw: Record<string, unknown>;
|
|
61
|
+
hash: string;
|
|
62
|
+
}
|
|
63
|
+
export interface McpServerDef {
|
|
64
|
+
type?: 'stdio' | 'local' | 'remote';
|
|
65
|
+
command?: string;
|
|
66
|
+
args?: string[];
|
|
67
|
+
env?: Record<string, string>;
|
|
68
|
+
url?: string;
|
|
69
|
+
headers?: Record<string, string>;
|
|
70
|
+
timeout?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface EffectiveAgentConfig {
|
|
73
|
+
identifier: string;
|
|
74
|
+
enabled: boolean;
|
|
75
|
+
output: AgentOutputPaths;
|
|
76
|
+
mcp: EffectiveMcpConfig;
|
|
77
|
+
toml?: AgentTomlConfig;
|
|
78
|
+
}
|
|
79
|
+
export interface AgentOutputPaths {
|
|
80
|
+
instructions?: string;
|
|
81
|
+
config?: string;
|
|
82
|
+
generic?: string;
|
|
83
|
+
}
|
|
84
|
+
export interface EffectiveMcpConfig {
|
|
85
|
+
enabled: boolean;
|
|
86
|
+
strategy: McpStrategy;
|
|
87
|
+
}
|
|
88
|
+
export type DiagnosticSeverity = 'info' | 'warning' | 'error';
|
|
89
|
+
export interface ConfigDiagnostic {
|
|
90
|
+
severity: DiagnosticSeverity;
|
|
91
|
+
code: string;
|
|
92
|
+
message: string;
|
|
93
|
+
file?: string;
|
|
94
|
+
detail?: string;
|
|
95
|
+
}
|
|
@@ -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
|
|
@@ -23,8 +32,7 @@ function resolveSelectedAgents(config, allAgents) {
|
|
|
23
32
|
if (invalidAgents.length > 0) {
|
|
24
33
|
throw (0, constants_1.createRulerError)(`Invalid agent specified: ${invalidAgents.join(', ')}`, `Valid agents are: ${[...validAgentIdentifiers].join(', ')}`);
|
|
25
34
|
}
|
|
26
|
-
selected = allAgents.filter((agent) => filters.some((f) => agent
|
|
27
|
-
agent.getName().toLowerCase().includes(f)));
|
|
35
|
+
selected = allAgents.filter((agent) => filters.some((f) => agentMatchesFilter(agent, f, validAgentIdentifiers)));
|
|
28
36
|
}
|
|
29
37
|
else if (config.defaultAgents && config.defaultAgents.length > 0) {
|
|
30
38
|
const defaults = config.defaultAgents.map((n) => n.toLowerCase());
|
|
@@ -42,7 +50,7 @@ function resolveSelectedAgents(config, allAgents) {
|
|
|
42
50
|
if (override !== undefined) {
|
|
43
51
|
return override;
|
|
44
52
|
}
|
|
45
|
-
return defaults.some((d) =>
|
|
53
|
+
return defaults.some((d) => agentMatchesFilter(agent, d, validAgentIdentifiers));
|
|
46
54
|
});
|
|
47
55
|
}
|
|
48
56
|
else {
|
|
@@ -0,0 +1,69 @@
|
|
|
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
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Configuration data for a specific .ruler directory in hierarchical mode
|
|
14
|
+
*/
|
|
15
|
+
export interface HierarchicalRulerConfiguration extends RulerConfiguration {
|
|
16
|
+
rulerDir: string;
|
|
17
|
+
}
|
|
18
|
+
export declare function loadNestedConfigurations(projectRoot: string, configPath: string | undefined, localOnly: boolean, resolvedNested: boolean): Promise<HierarchicalRulerConfiguration[]>;
|
|
19
|
+
/**
|
|
20
|
+
* Loads configuration for single-directory mode (existing behavior).
|
|
21
|
+
*/
|
|
22
|
+
export declare function loadSingleConfiguration(projectRoot: string, configPath: string | undefined, localOnly: boolean): Promise<RulerConfiguration>;
|
|
23
|
+
/**
|
|
24
|
+
* Processes hierarchical configurations by applying rules to each .ruler directory independently.
|
|
25
|
+
* Each directory gets its own set of rules and generates its own agent files.
|
|
26
|
+
* @param agents Array of agents to process
|
|
27
|
+
* @param configurations Array of hierarchical configurations for each .ruler directory
|
|
28
|
+
* @param verbose Whether to enable verbose logging
|
|
29
|
+
* @param dryRun Whether to perform a dry run
|
|
30
|
+
* @param cliMcpEnabled Whether MCP is enabled via CLI
|
|
31
|
+
* @param cliMcpStrategy MCP strategy from CLI
|
|
32
|
+
* @returns Promise resolving to array of generated file paths
|
|
33
|
+
*/
|
|
34
|
+
export declare function processHierarchicalConfigurations(agents: IAgent[], configurations: HierarchicalRulerConfiguration[], verbose: boolean, dryRun: boolean, cliMcpEnabled: boolean, cliMcpStrategy?: McpStrategy, backup?: boolean): Promise<string[]>;
|
|
35
|
+
/**
|
|
36
|
+
* Processes a single configuration by applying rules to all selected agents.
|
|
37
|
+
* All rules are concatenated and applied to generate agent files in the project root.
|
|
38
|
+
* @param agents Array of agents to process
|
|
39
|
+
* @param configuration Single ruler configuration with concatenated rules
|
|
40
|
+
* @param projectRoot Root directory of the project
|
|
41
|
+
* @param verbose Whether to enable verbose logging
|
|
42
|
+
* @param dryRun Whether to perform a dry run
|
|
43
|
+
* @param cliMcpEnabled Whether MCP is enabled via CLI
|
|
44
|
+
* @param cliMcpStrategy MCP strategy from CLI
|
|
45
|
+
* @returns Promise resolving to array of generated file paths
|
|
46
|
+
*/
|
|
47
|
+
export declare function processSingleConfiguration(agents: IAgent[], configuration: RulerConfiguration, projectRoot: string, verbose: boolean, dryRun: boolean, cliMcpEnabled: boolean, cliMcpStrategy?: McpStrategy, backup?: boolean): Promise<string[]>;
|
|
48
|
+
/**
|
|
49
|
+
* Applies configurations to the selected agents (internal function).
|
|
50
|
+
* @param agents Array of agents to process
|
|
51
|
+
* @param concatenatedRules Concatenated rule content
|
|
52
|
+
* @param rulerMcpJson MCP configuration JSON
|
|
53
|
+
* @param config Loaded configuration
|
|
54
|
+
* @param projectRoot Root directory of the project
|
|
55
|
+
* @param verbose Whether to enable verbose logging
|
|
56
|
+
* @param dryRun Whether to perform a dry run
|
|
57
|
+
* @returns Promise resolving to array of generated file paths
|
|
58
|
+
*/
|
|
59
|
+
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[]>;
|
|
60
|
+
/**
|
|
61
|
+
* Updates the .gitignore file with generated paths.
|
|
62
|
+
* @param projectRoot Root directory of the project
|
|
63
|
+
* @param generatedPaths Array of generated file paths
|
|
64
|
+
* @param config Loaded configuration
|
|
65
|
+
* @param cliGitignoreEnabled CLI gitignore setting
|
|
66
|
+
* @param dryRun Whether to perform a dry run
|
|
67
|
+
* @param cliGitignoreLocal CLI toggle for .git/info/exclude usage
|
|
68
|
+
*/
|
|
69
|
+
export declare function updateGitignore(projectRoot: string, generatedPaths: string[], config: LoadedConfig, cliGitignoreEnabled: boolean | undefined, dryRun: boolean, cliGitignoreLocal?: boolean): Promise<void>;
|