@intellectronica/ruler
Version:
Ruler — apply the same rules to all coding agents
167 lines (166 loc) • 8.97 kB
JavaScript
;
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
var desc = Object.getOwnPropertyDescriptor(m, k);
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
desc = { enumerable: true, get: function() { return m[k]; } };
}
Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
var ownKeys = function(o) {
ownKeys = Object.getOwnPropertyNames || function (o) {
var ar = [];
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
return ar;
};
return ownKeys(o);
};
return function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
__setModuleDefault(result, mod);
return result;
};
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.allAgents = void 0;
exports.applyAllAgentConfigs = applyAllAgentConfigs;
const path = __importStar(require("path"));
const agents_1 = require("./agents");
Object.defineProperty(exports, "allAgents", { enumerable: true, get: function () { return agents_1.allAgents; } });
const constants_1 = require("./constants");
const apply_engine_1 = require("./core/apply-engine");
const config_utils_1 = require("./core/config-utils");
const agent_selection_1 = require("./core/agent-selection");
const agents = agents_1.allAgents;
/**
* Resolves skills enabled state based on precedence: CLI flag > ruler.toml > default (enabled)
*/
function resolveSkillsEnabled(cliFlag, configSetting) {
return cliFlag !== undefined
? cliFlag
: configSetting !== undefined
? configSetting
: true; // default to enabled
}
/**
* Applies ruler configurations for all supported AI agents.
* @param projectRoot Root directory of the project
*/
/**
* Applies ruler configurations for selected AI agents.
* @param projectRoot Root directory of the project
* @param includedAgents Optional list of agent name filters (case-insensitive substrings)
*/
async function applyAllAgentConfigs(projectRoot, includedAgents, configPath, cliMcpEnabled = true, cliMcpStrategy, cliGitignoreEnabled, verbose = false, dryRun = false, localOnly = false, nested = false, backup = true, skillsEnabled, cliGitignoreLocal) {
// Load configuration and rules
(0, constants_1.logVerbose)(`Loading configuration from project root: ${projectRoot}`, verbose);
if (configPath) {
(0, constants_1.logVerbose)(`Using custom config path: ${configPath}`, verbose);
}
let selectedAgents;
let generatedPaths;
let loadedConfig;
if (nested) {
const hierarchicalConfigs = await (0, apply_engine_1.loadNestedConfigurations)(projectRoot, configPath, localOnly, nested);
if (hierarchicalConfigs.length === 0) {
throw new Error('No .ruler directories found');
}
(0, constants_1.logWarn)('Nested mode is experimental and may change in future releases.', dryRun);
// Use the root config for agent selection (all levels share the same agent settings)
const rootConfigEntry = selectRootConfiguration(hierarchicalConfigs, projectRoot);
const rootConfig = rootConfigEntry.config;
loadedConfig = rootConfig;
rootConfig.cliAgents = includedAgents;
(0, constants_1.logVerbose)(`Loaded ${hierarchicalConfigs.length} .ruler directory configurations`, verbose);
(0, constants_1.logVerbose)(`Root configuration has ${Object.keys(rootConfig.agentConfigs).length} agent configs`, verbose);
for (const configEntry of hierarchicalConfigs) {
normalizeAgentConfigs(configEntry.config, agents);
}
selectedAgents = (0, agent_selection_1.resolveSelectedAgents)(rootConfig, agents);
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
// Propagate skills if enabled - do this for each nested directory
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, rootConfig.skills?.enabled);
if (skillsEnabledResolved) {
const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
// Propagate skills for each nested .ruler directory
for (const configEntry of hierarchicalConfigs) {
const nestedRoot = path.dirname(configEntry.rulerDir);
(0, constants_1.logVerbose)(`Propagating skills for nested directory: ${nestedRoot}`, verbose);
await propagateSkills(nestedRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
}
}
generatedPaths = await (0, apply_engine_1.processHierarchicalConfigurations)(selectedAgents, hierarchicalConfigs, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
}
else {
const singleConfig = await (0, apply_engine_1.loadSingleConfiguration)(projectRoot, configPath, localOnly);
loadedConfig = singleConfig.config;
singleConfig.config.cliAgents = includedAgents;
(0, constants_1.logVerbose)(`Loaded configuration with ${Object.keys(singleConfig.config.agentConfigs).length} agent configs`, verbose);
(0, constants_1.logVerbose)(`Found .ruler directory with ${singleConfig.concatenatedRules.length} characters of rules`, verbose);
normalizeAgentConfigs(singleConfig.config, agents);
selectedAgents = (0, agent_selection_1.resolveSelectedAgents)(singleConfig.config, agents);
(0, constants_1.logVerbose)(`Selected ${selectedAgents.length} agents: ${selectedAgents.map((a) => a.getName()).join(', ')}`, verbose);
// Propagate skills if enabled
const skillsEnabledResolved = resolveSkillsEnabled(skillsEnabled, singleConfig.config.skills?.enabled);
if (skillsEnabledResolved) {
const { propagateSkills } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
await propagateSkills(projectRoot, selectedAgents, skillsEnabledResolved, verbose, dryRun);
}
generatedPaths = await (0, apply_engine_1.processSingleConfiguration)(selectedAgents, singleConfig, projectRoot, verbose, dryRun, cliMcpEnabled, cliMcpStrategy, backup);
}
// Add skills-generated paths to gitignore if skills are enabled
let allGeneratedPaths = generatedPaths;
const skillsEnabledForGitignore = resolveSkillsEnabled(skillsEnabled, loadedConfig.skills?.enabled);
if (skillsEnabledForGitignore) {
// Skills enabled by default or explicitly
const { getSkillsGitignorePaths } = await Promise.resolve().then(() => __importStar(require('./core/SkillsProcessor')));
const skillsPaths = await getSkillsGitignorePaths(projectRoot, selectedAgents);
allGeneratedPaths = [...generatedPaths, ...skillsPaths];
}
await (0, apply_engine_1.updateGitignore)(projectRoot, allGeneratedPaths, loadedConfig, cliGitignoreEnabled, dryRun, cliGitignoreLocal);
}
/**
* Normalizes per-agent config keys to agent identifiers for consistent lookup.
* Maps both exact identifier matches and substring matches with agent names.
* @param config The configuration object to normalize
* @param agents Array of available agents
*/
function normalizeAgentConfigs(config, agents) {
// Normalize per-agent config keys to agent identifiers (exact match or substring match)
config.agentConfigs = (0, config_utils_1.mapRawAgentConfigs)(config.agentConfigs, agents);
}
function selectRootConfiguration(configurations, projectRoot) {
if (configurations.length === 0) {
throw new Error('No hierarchical configurations available');
}
const normalizedProjectRoot = path.resolve(projectRoot);
let bestIndex = -1;
let bestDepth = Number.POSITIVE_INFINITY;
for (let i = 0; i < configurations.length; i++) {
const entry = configurations[i];
const normalizedDir = path.resolve(entry.rulerDir);
if (!normalizedDir.startsWith(normalizedProjectRoot)) {
continue;
}
const depth = normalizedDir.split(path.sep).length;
if (depth < bestDepth) {
bestDepth = depth;
bestIndex = i;
}
}
if (bestIndex === -1) {
return configurations[0];
}
return configurations[bestIndex];
}