@felixgeelhaar/cclint
Version:
Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.
126 lines • 4.07 kB
JavaScript
import { getBuiltinRuleIds } from '../rules/registry/ruleDescriptors.js';
/**
* Central registry for all rules (built-in and custom)
* Manages rule registration, lookup, and lifecycle
*/
export class RuleRegistry {
rules = new Map();
plugins = new Map(); // rule ID -> plugin name
/**
* Register a custom rule
* @param rule The rule to register
* @param pluginName Optional plugin name that provides this rule
*/
registerRule(rule, pluginName) {
if (this.rules.has(rule.id)) {
throw new Error(`Rule with ID "${rule.id}" is already registered`);
}
this.rules.set(rule.id, rule);
if (pluginName) {
this.plugins.set(rule.id, pluginName);
}
}
/**
* Get a rule by its ID
* @param ruleId The ID of the rule to retrieve
* @returns The rule or undefined if not found
*/
getRule(ruleId) {
return this.rules.get(ruleId);
}
/**
* Check if a rule is registered
* @param ruleId The ID of the rule to check
* @returns True if the rule is registered
*/
hasRule(ruleId) {
return this.rules.has(ruleId);
}
/**
* Get all registered rules
* @returns Array of all registered rules
*/
getAllRules() {
return Array.from(this.rules.values());
}
/**
* Get rules filtered by category
* @param category The category to filter by
* @returns Array of rules in the specified category
*/
getRulesByCategory(category) {
return this.getAllRules().filter(rule => rule.category === category);
}
/**
* Get rules provided by a specific plugin
* @param pluginName The name of the plugin
* @returns Array of rules from the specified plugin
*/
getRulesByPlugin(pluginName) {
const ruleIds = Array.from(this.plugins.entries())
.filter(([, plugin]) => plugin === pluginName)
.map(([ruleId]) => ruleId);
return ruleIds
.map(id => this.rules.get(id))
.filter((rule) => rule !== undefined);
}
/**
* Unregister a rule
* @param ruleId The ID of the rule to unregister
*/
unregisterRule(ruleId) {
this.rules.delete(ruleId);
this.plugins.delete(ruleId);
}
/**
* Unregister all rules from a plugin
* @param pluginName The name of the plugin
*/
unregisterPlugin(pluginName) {
const ruleIds = Array.from(this.plugins.entries())
.filter(([, plugin]) => plugin === pluginName)
.map(([ruleId]) => ruleId);
ruleIds.forEach(ruleId => {
this.rules.delete(ruleId);
this.plugins.delete(ruleId);
});
}
/**
* Get plugin name for a rule
* @param ruleId The ID of the rule
* @returns The plugin name or undefined if it's a built-in rule
*/
getPluginForRule(ruleId) {
return this.plugins.get(ruleId);
}
/**
* Get registry statistics
* @returns Object containing registry statistics
*/
getStats() {
// A rule is "built-in" when its id belongs to the canonical descriptor
// list (the single source of truth); anything else — plugin-provided or
// ad-hoc — is custom. Deriving this here means new built-in rules never
// need a second edit to stay classified correctly.
const builtInRuleIds = getBuiltinRuleIds();
const totalRules = this.rules.size;
const customRules = Array.from(this.rules.values()).filter(rule => !builtInRuleIds.has(rule.id)).length;
const builtInRules = totalRules - customRules;
const pluginCount = new Set(this.plugins.values()).size;
return {
totalRules,
customRules,
builtInRules,
pluginCount,
};
}
/**
* Clear all registered rules
* Used primarily for testing
*/
clear() {
this.rules.clear();
this.plugins.clear();
}
}
//# sourceMappingURL=RuleRegistry.js.map