@intellectronica/ruler 0.3.41 → 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 +40 -28
- 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 +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/core/ConfigLoader.d.ts +57 -0
- package/dist/core/ConfigLoader.js +106 -39
- 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/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 +16 -3
- 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 +6 -4
|
@@ -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>;
|
|
@@ -52,6 +52,7 @@ const propagateOpenHandsMcp_1 = require("../mcp/propagateOpenHandsMcp");
|
|
|
52
52
|
const propagateOpenCodeMcp_1 = require("../mcp/propagateOpenCodeMcp");
|
|
53
53
|
const agent_utils_1 = require("../agents/agent-utils");
|
|
54
54
|
const capabilities_1 = require("../mcp/capabilities");
|
|
55
|
+
const path_utils_1 = require("./path-utils");
|
|
55
56
|
const constants_1 = require("../constants");
|
|
56
57
|
async function loadNestedConfigurations(projectRoot, configPath, localOnly, resolvedNested) {
|
|
57
58
|
const { dirs: rulerDirs } = await findRulerDirectories(projectRoot, localOnly, true);
|
|
@@ -59,11 +60,11 @@ async function loadNestedConfigurations(projectRoot, configPath, localOnly, reso
|
|
|
59
60
|
// Load config first so we know whether `.ruler/agents/` should be included
|
|
60
61
|
// in the rule concatenation for each directory.
|
|
61
62
|
for (const rulerDir of rulerDirs) {
|
|
62
|
-
const config = await loadConfigForRulerDir(rulerDir, configPath, resolvedNested);
|
|
63
|
+
const config = await loadConfigForRulerDir(rulerDir, configPath, resolvedNested, localOnly);
|
|
63
64
|
const files = await FileSystemUtils.readMarkdownFiles(rulerDir, {
|
|
64
65
|
includeAgents: shouldIncludeAgentsInRules(config),
|
|
65
66
|
});
|
|
66
|
-
results.push(await createHierarchicalConfiguration(rulerDir, files, config, configPath));
|
|
67
|
+
results.push(await createHierarchicalConfiguration(rulerDir, files, config, configPath, localOnly));
|
|
67
68
|
}
|
|
68
69
|
return results;
|
|
69
70
|
}
|
|
@@ -74,7 +75,7 @@ async function loadNestedConfigurations(projectRoot, configPath, localOnly, reso
|
|
|
74
75
|
function shouldIncludeAgentsInRules(config) {
|
|
75
76
|
return config.subagents?.include_in_rules === true;
|
|
76
77
|
}
|
|
77
|
-
async function createHierarchicalConfiguration(rulerDir, files, config, cliConfigPath) {
|
|
78
|
+
async function createHierarchicalConfiguration(rulerDir, files, config, cliConfigPath, localOnly) {
|
|
78
79
|
await warnAboutLegacyMcpJson(rulerDir);
|
|
79
80
|
const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(rulerDir));
|
|
80
81
|
const directoryRoot = path.dirname(rulerDir);
|
|
@@ -91,6 +92,7 @@ async function createHierarchicalConfiguration(rulerDir, files, config, cliConfi
|
|
|
91
92
|
const unifiedConfig = await loadUnifiedConfig({
|
|
92
93
|
projectRoot: directoryRoot,
|
|
93
94
|
configPath: configPathToUse,
|
|
95
|
+
checkGlobal: !localOnly,
|
|
94
96
|
});
|
|
95
97
|
let rulerMcpJson = null;
|
|
96
98
|
if (unifiedConfig.mcp && Object.keys(unifiedConfig.mcp.servers).length > 0) {
|
|
@@ -105,7 +107,7 @@ async function createHierarchicalConfiguration(rulerDir, files, config, cliConfi
|
|
|
105
107
|
rulerMcpJson,
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
|
-
async function loadConfigForRulerDir(rulerDir, cliConfigPath, resolvedNested) {
|
|
110
|
+
async function loadConfigForRulerDir(rulerDir, cliConfigPath, resolvedNested, localOnly) {
|
|
109
111
|
const directoryRoot = path.dirname(rulerDir);
|
|
110
112
|
const localConfigPath = path.join(rulerDir, 'ruler.toml');
|
|
111
113
|
let hasLocalConfig = false;
|
|
@@ -119,6 +121,7 @@ async function loadConfigForRulerDir(rulerDir, cliConfigPath, resolvedNested) {
|
|
|
119
121
|
const loaded = await (0, ConfigLoader_1.loadConfig)({
|
|
120
122
|
projectRoot: directoryRoot,
|
|
121
123
|
configPath: hasLocalConfig ? localConfigPath : cliConfigPath,
|
|
124
|
+
checkGlobal: !localOnly,
|
|
122
125
|
});
|
|
123
126
|
const cloned = cloneLoadedConfig(loaded);
|
|
124
127
|
if (resolvedNested) {
|
|
@@ -144,6 +147,7 @@ function cloneLoadedConfig(config) {
|
|
|
144
147
|
cliAgents: config.cliAgents ? [...config.cliAgents] : undefined,
|
|
145
148
|
mcp: config.mcp ? { ...config.mcp } : undefined,
|
|
146
149
|
gitignore: config.gitignore ? { ...config.gitignore } : undefined,
|
|
150
|
+
backup: config.backup ? { ...config.backup } : undefined,
|
|
147
151
|
skills: config.skills ? { ...config.skills } : undefined,
|
|
148
152
|
subagents: config.subagents ? { ...config.subagents } : undefined,
|
|
149
153
|
nested: config.nested,
|
|
@@ -156,18 +160,10 @@ function cloneLoadedConfig(config) {
|
|
|
156
160
|
async function findRulerDirectories(projectRoot, localOnly, hierarchical) {
|
|
157
161
|
if (hierarchical) {
|
|
158
162
|
const dirs = await FileSystemUtils.findAllRulerDirs(projectRoot);
|
|
159
|
-
|
|
160
|
-
// Add global config if not local-only
|
|
161
|
-
if (!localOnly) {
|
|
162
|
-
const globalDir = await FileSystemUtils.findGlobalRulerDir();
|
|
163
|
-
if (globalDir) {
|
|
164
|
-
allDirs.push(globalDir);
|
|
165
|
-
}
|
|
166
|
-
}
|
|
167
|
-
if (allDirs.length === 0) {
|
|
163
|
+
if (dirs.length === 0) {
|
|
168
164
|
throw (0, constants_1.createRulerError)(`.ruler directory not found`, `Searched from: ${projectRoot}`);
|
|
169
165
|
}
|
|
170
|
-
return { dirs
|
|
166
|
+
return { dirs, primaryDir: dirs[0] };
|
|
171
167
|
}
|
|
172
168
|
else {
|
|
173
169
|
const dir = await FileSystemUtils.findRulerDir(projectRoot, !localOnly);
|
|
@@ -202,6 +198,7 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
202
198
|
const config = await (0, ConfigLoader_1.loadConfig)({
|
|
203
199
|
projectRoot,
|
|
204
200
|
configPath,
|
|
201
|
+
checkGlobal: !localOnly,
|
|
205
202
|
});
|
|
206
203
|
// Read rule files. `.ruler/agents/` is only included when
|
|
207
204
|
// `[agents] include_in_rules = true`.
|
|
@@ -212,7 +209,11 @@ async function loadSingleConfiguration(projectRoot, configPath, localOnly) {
|
|
|
212
209
|
const concatenatedRules = (0, RuleProcessor_1.concatenateRules)(files, path.dirname(primaryDir));
|
|
213
210
|
// Load unified config to get merged MCP configuration
|
|
214
211
|
const { loadUnifiedConfig } = await Promise.resolve().then(() => __importStar(require('./UnifiedConfigLoader')));
|
|
215
|
-
const unifiedConfig = await loadUnifiedConfig({
|
|
212
|
+
const unifiedConfig = await loadUnifiedConfig({
|
|
213
|
+
projectRoot,
|
|
214
|
+
configPath,
|
|
215
|
+
checkGlobal: !localOnly,
|
|
216
|
+
});
|
|
216
217
|
// Synthesize rulerMcpJson from unified MCP bundle for backward compatibility
|
|
217
218
|
let rulerMcpJson = null;
|
|
218
219
|
if (unifiedConfig.mcp && Object.keys(unifiedConfig.mcp.servers).length > 0) {
|
|
@@ -306,22 +307,27 @@ async function applyConfigurationsToAgents(agents, concatenatedRules, rulerMcpJs
|
|
|
306
307
|
agentsMdWritten = true;
|
|
307
308
|
}
|
|
308
309
|
}
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
}
|
|
322
|
-
}
|
|
310
|
+
const effectiveMcpEnabled = cliMcpEnabled &&
|
|
311
|
+
(agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
|
|
312
|
+
const effectiveMcpStrategy = cliMcpStrategy ??
|
|
313
|
+
agentConfig?.mcp?.strategy ??
|
|
314
|
+
config.mcp?.strategy ??
|
|
315
|
+
'merge';
|
|
316
|
+
const finalAgentConfig = {
|
|
317
|
+
...agentConfig,
|
|
318
|
+
mcp: {
|
|
319
|
+
...agentConfig?.mcp,
|
|
320
|
+
enabled: effectiveMcpEnabled,
|
|
321
|
+
strategy: effectiveMcpStrategy,
|
|
322
|
+
},
|
|
323
|
+
};
|
|
324
|
+
const mcpForAgentApply = shouldUseEngineManagedMcp(agent)
|
|
325
|
+
? null
|
|
326
|
+
: effectiveMcpEnabled
|
|
327
|
+
? agentRulerMcpJson
|
|
328
|
+
: null;
|
|
323
329
|
if (!skipApplyForThisAgent) {
|
|
324
|
-
await agent.applyRulerConfig(concatenatedRules, projectRoot,
|
|
330
|
+
await agent.applyRulerConfig(concatenatedRules, projectRoot, mcpForAgentApply, finalAgentConfig, backup);
|
|
325
331
|
}
|
|
326
332
|
}
|
|
327
333
|
// Handle MCP configuration
|
|
@@ -334,7 +340,7 @@ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson,
|
|
|
334
340
|
(0, constants_1.logVerbose)(`Agent ${agent.getName()} does not support MCP - skipping MCP configuration`, verbose);
|
|
335
341
|
return;
|
|
336
342
|
}
|
|
337
|
-
const dest = await (
|
|
343
|
+
const dest = await resolveMcpDestination(agent, agentConfig, projectRoot);
|
|
338
344
|
const mcpEnabledForAgent = cliMcpEnabled && (agentConfig?.mcp?.enabled ?? config.mcp?.enabled ?? true);
|
|
339
345
|
if (!dest || !mcpEnabledForAgent) {
|
|
340
346
|
return;
|
|
@@ -349,8 +355,17 @@ async function handleMcpConfiguration(agent, agentConfig, config, rulerMcpJson,
|
|
|
349
355
|
await updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, backup);
|
|
350
356
|
await applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup);
|
|
351
357
|
}
|
|
358
|
+
function shouldUseEngineManagedMcp(agent) {
|
|
359
|
+
return (agent.getIdentifier() === 'codex' || agent.getIdentifier() === 'opencode');
|
|
360
|
+
}
|
|
361
|
+
async function resolveMcpDestination(agent, agentConfig, projectRoot) {
|
|
362
|
+
if (agentConfig?.outputPathConfig) {
|
|
363
|
+
return path.resolve(projectRoot, agentConfig.outputPathConfig);
|
|
364
|
+
}
|
|
365
|
+
return await (0, mcp_1.getNativeMcpPath)(agent.getName(), projectRoot);
|
|
366
|
+
}
|
|
352
367
|
async function updateGitignoreForMcpFile(dest, projectRoot, generatedPaths, backup = true) {
|
|
353
|
-
if (
|
|
368
|
+
if ((0, path_utils_1.isPathInsideOrEqual)(projectRoot, dest)) {
|
|
354
369
|
const relativeDest = path.relative(projectRoot, dest);
|
|
355
370
|
generatedPaths.push(relativeDest);
|
|
356
371
|
if (backup) {
|
|
@@ -391,16 +406,16 @@ function sanitizeMcpTimeoutsForAgent(agent, mcpJson, dryRun) {
|
|
|
391
406
|
}
|
|
392
407
|
async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig, config, projectRoot, cliMcpStrategy, dryRun, verbose, backup = true) {
|
|
393
408
|
// Prevent writing MCP configs outside the project root (e.g., legacy home-directory targets)
|
|
394
|
-
if (!
|
|
409
|
+
if (!(0, path_utils_1.isPathInsideOrEqual)(projectRoot, dest)) {
|
|
395
410
|
(0, constants_1.logVerbose)(`Skipping MCP config for ${agent.getName()} because target path is outside project: ${dest}`, verbose);
|
|
396
411
|
return;
|
|
397
412
|
}
|
|
398
413
|
const agentMcpJson = sanitizeMcpTimeoutsForAgent(agent, filteredMcpJson, dryRun);
|
|
399
414
|
if (agent.getIdentifier() === 'openhands') {
|
|
400
|
-
return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
|
|
415
|
+
return await applyOpenHandsMcpConfiguration(agentMcpJson, dest, cliMcpStrategy ?? agentConfig?.mcp?.strategy ?? config.mcp?.strategy, dryRun, verbose, backup);
|
|
401
416
|
}
|
|
402
417
|
if (agent.getIdentifier() === 'opencode') {
|
|
403
|
-
return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, dryRun, verbose, backup);
|
|
418
|
+
return await applyOpenCodeMcpConfiguration(agentMcpJson, dest, cliMcpStrategy ?? agentConfig?.mcp?.strategy ?? config.mcp?.strategy, dryRun, verbose, backup);
|
|
404
419
|
}
|
|
405
420
|
// Agents that handle MCP configuration internally should not have external MCP handling
|
|
406
421
|
if (agent.getIdentifier() === 'zed' ||
|
|
@@ -412,20 +427,20 @@ async function applyMcpConfiguration(agent, filteredMcpJson, dest, agentConfig,
|
|
|
412
427
|
}
|
|
413
428
|
return await applyStandardMcpConfiguration(agent, agentMcpJson, dest, agentConfig, config, cliMcpStrategy, dryRun, verbose, backup);
|
|
414
429
|
}
|
|
415
|
-
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
|
|
430
|
+
async function applyOpenHandsMcpConfiguration(filteredMcpJson, dest, strategy, dryRun, verbose, backup = true) {
|
|
416
431
|
if (dryRun) {
|
|
417
432
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating TOML file: ${dest}`, verbose);
|
|
418
433
|
}
|
|
419
434
|
else {
|
|
420
|
-
await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest, backup);
|
|
435
|
+
await (0, propagateOpenHandsMcp_1.propagateMcpToOpenHands)(filteredMcpJson, dest, backup, strategy);
|
|
421
436
|
}
|
|
422
437
|
}
|
|
423
|
-
async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, dryRun, verbose, backup = true) {
|
|
438
|
+
async function applyOpenCodeMcpConfiguration(filteredMcpJson, dest, strategy, dryRun, verbose, backup = true) {
|
|
424
439
|
if (dryRun) {
|
|
425
440
|
(0, constants_1.logVerbose)(`DRY RUN: Would apply MCP config by updating OpenCode config file: ${dest}`, verbose);
|
|
426
441
|
}
|
|
427
442
|
else {
|
|
428
|
-
await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup);
|
|
443
|
+
await (0, propagateOpenCodeMcp_1.propagateMcpToOpenCode)(filteredMcpJson, dest, backup, strategy);
|
|
429
444
|
}
|
|
430
445
|
}
|
|
431
446
|
/**
|
|
@@ -550,17 +565,9 @@ async function applyStandardMcpConfiguration(agent, filteredMcpJson, dest, agent
|
|
|
550
565
|
}
|
|
551
566
|
const CODEX_AGENT_ID = 'codex';
|
|
552
567
|
const isCodexToml = agent.getIdentifier() === CODEX_AGENT_ID && dest.endsWith('.toml');
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
const tomlContent = await fs_1.promises.readFile(dest, 'utf8');
|
|
557
|
-
existing = (0, toml_1.parse)(tomlContent);
|
|
558
|
-
}
|
|
559
|
-
catch (error) {
|
|
560
|
-
(0, constants_1.logVerbose)(`Failed to read Codex MCP TOML at ${dest}: ${error.message}`, verbose);
|
|
561
|
-
// ignore missing or invalid TOML, fall back to previously read value
|
|
562
|
-
}
|
|
563
|
-
}
|
|
568
|
+
const existing = isCodexToml
|
|
569
|
+
? await (0, mcp_1.readNativeMcpToml)(dest, (text) => (0, toml_1.parse)(text))
|
|
570
|
+
: await (0, mcp_1.readNativeMcp)(dest);
|
|
564
571
|
let merged = (0, merge_1.mergeMcp)(existing, mcpToMerge, strategy, serverKey);
|
|
565
572
|
if (isCodexToml) {
|
|
566
573
|
const { [serverKey]: servers, ...rest } = merged;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { IAgent, IAgentConfig } from '../agents/IAgent';
|
|
2
|
+
/**
|
|
3
|
+
* Maps raw agent configuration keys to their corresponding agent identifiers.
|
|
4
|
+
*
|
|
5
|
+
* This function normalizes configuration keys by matching them against agent identifiers
|
|
6
|
+
* and display names. It performs both exact matching (case-insensitive) with agent
|
|
7
|
+
* identifiers and substring matching (case-insensitive) with agent display names
|
|
8
|
+
* for backwards compatibility.
|
|
9
|
+
*
|
|
10
|
+
* @param raw Raw agent configurations with user-provided keys
|
|
11
|
+
* @param agents Array of all available agents
|
|
12
|
+
* @returns Record with agent identifiers as keys and their configurations as values
|
|
13
|
+
*/
|
|
14
|
+
export declare function mapRawAgentConfigs(raw: Record<string, IAgentConfig>, agents: IAgent[]): Record<string, IAgentConfig>;
|
|
@@ -17,11 +17,17 @@ function mapRawAgentConfigs(raw, agents) {
|
|
|
17
17
|
const mappedConfigs = {};
|
|
18
18
|
for (const [key, cfg] of Object.entries(raw)) {
|
|
19
19
|
const lowerKey = key.toLowerCase();
|
|
20
|
+
const exactMatches = agents.filter((agent) => agent.getIdentifier().toLowerCase() === lowerKey);
|
|
21
|
+
// Exact identifier matches take precedence over fuzzy display-name matching.
|
|
22
|
+
if (exactMatches.length > 0) {
|
|
23
|
+
for (const agent of exactMatches) {
|
|
24
|
+
mappedConfigs[agent.getIdentifier()] = cfg;
|
|
25
|
+
}
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
20
28
|
for (const agent of agents) {
|
|
21
29
|
const identifier = agent.getIdentifier();
|
|
22
|
-
|
|
23
|
-
if (identifier === lowerKey ||
|
|
24
|
-
agent.getName().toLowerCase().includes(lowerKey)) {
|
|
30
|
+
if (agent.getName().toLowerCase().includes(lowerKey)) {
|
|
25
31
|
mappedConfigs[identifier] = cfg;
|
|
26
32
|
}
|
|
27
33
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function isPathInsideOrEqual(parentPath: string, targetPath: string): boolean;
|