@felixgeelhaar/cclint
Version:
Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.
96 lines • 3.15 kB
JavaScript
import { ContextFile } from './ContextFile.js';
import { LintingResult } from './LintingResult.js';
import { Violation } from './Violation.js';
/**
* Core linting engine that aggregates rules and runs validation.
*
* @remarks
* The RulesEngine follows the hexagonal architecture pattern, residing in the
* domain layer. It coordinates rule execution and aggregates violations into
* a {@link LintingResult}.
*
* @example
* ```typescript
* const rules = [
* new FileSizeRule(10000),
* new StructureRule(),
* new FormatRule()
* ];
*
* const engine = new RulesEngine(rules);
* const file = new ContextFile('CLAUDE.md', readFileSync('CLAUDE.md', 'utf-8'));
* const result = engine.lint(file);
*
* console.log(`Errors: ${result.errorCount}`);
* ```
*
* @category Domain
*/
export class RulesEngine {
_rules = new Map();
_severityOverrides;
/**
* @param rules - the rules to run, in order
* @param severityOverrides - optional per-rule severity (by rule id). When a
* rule has an override, ALL of its violations are re-emitted at that
* severity — the standard linter model where a rule is configured to one
* level (error/warning/info). This is how `config.rules.<id>.severity`
* takes effect.
*/
constructor(rules, severityOverrides) {
for (const rule of rules) {
if (this._rules.has(rule.id)) {
throw new Error(`Duplicate rule ID: ${rule.id}`);
}
this._rules.set(rule.id, rule);
}
this._severityOverrides = severityOverrides ?? new Map();
}
get rules() {
return Array.from(this._rules.values());
}
/**
* Lint a context file using all registered rules.
*
* @param file - The {@link ContextFile} to validate
* @returns A {@link LintingResult} containing all violations found
*
* @remarks
* Rules are executed in the order they were registered. Each rule's violations
* are aggregated into a single result object.
*/
lint(file) {
const result = new LintingResult(file);
for (const rule of this._rules.values()) {
if (rule.appliesTo && !rule.appliesTo(file)) {
continue;
}
const override = this._severityOverrides.get(rule.id);
for (const violation of rule.lint(file)) {
result.addViolation(override
? new Violation(violation.ruleId, violation.message, override, violation.location, violation.fix)
: violation);
}
}
return result;
}
/**
* Retrieve a rule by its ID.
*
* @param ruleId - The unique identifier of the rule
* @returns The rule if found, undefined otherwise
*/
getRuleById(ruleId) {
return this._rules.get(ruleId);
}
/**
* Check if a rule is registered.
*
* @param ruleId - The unique identifier of the rule
* @returns True if the rule is registered
*/
hasRule(ruleId) {
return this._rules.has(ruleId);
}
}
//# sourceMappingURL=RulesEngine.js.map