@felixgeelhaar/cclint
Version:
Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.
167 lines • 6.41 kB
JavaScript
import { RULE_METADATA } from './ruleMetadata.js';
import { FileSizeRule } from '../FileSizeRule.js';
import { StructureRule } from '../StructureRule.js';
import { ContentOrganizationRule } from '../ContentOrganizationRule.js';
import { FormatRule } from '../FormatRule.js';
import { CodeBlockRule } from '../CodeBlockRule.js';
import { ImportSyntaxRule } from '../ImportSyntaxRule.js';
import { FileLocationRule } from '../FileLocationRule.js';
import { ImportResolutionRule } from '../ImportResolutionRule.js';
import { ContentAppropriatenessRule } from '../ContentAppropriatenessRule.js';
import { MonorepoHierarchyRule } from '../MonorepoHierarchyRule.js';
import { CommandSafetyRule } from '../CommandSafetyRule.js';
import { SkillStructureRule } from '../SkillStructureRule.js';
import { SubagentStructureRule } from '../SubagentStructureRule.js';
import { HookConfigurationRule } from '../HookConfigurationRule.js';
import { KarpathyRule } from '../KarpathyRule.js';
import { SecretDetectionRule } from '../SecretDetectionRule.js';
import { PluginManifestRule } from '../PluginManifestRule.js';
import { McpConfigRule } from '../McpConfigRule.js';
import { OutputStyleRule } from '../OutputStyleRule.js';
/** Read a numeric option in a type-safe way, falling back when absent. */
function numberOption(options, key) {
const value = options?.[key];
return typeof value === 'number' ? value : undefined;
}
/**
* All built-in rule descriptors, in execution order.
*
* @remarks
* The order here is the order violations are produced by every entry point, so
* keep it stable. This array is the one place to touch when adding, removing,
* or reconfiguring a built-in rule.
*/
export const RULE_DESCRIPTORS = [
{
id: 'file-size',
defaultEnabled: false,
metadata: RULE_METADATA['file-size'],
create: config => new FileSizeRule(config.rules['file-size']?.options?.maxSize ?? 10000),
},
{
id: 'structure',
defaultEnabled: false,
metadata: RULE_METADATA['structure'],
create: config => new StructureRule(config.rules['structure']?.options?.requiredSections),
},
{
id: 'content-organization',
defaultEnabled: false,
metadata: RULE_METADATA['content-organization'],
// Backward compat: the legacy `content` key and the current
// `content-organization` key both drive the same rule.
isEnabled: config => (config.rules['content']?.enabled ?? false) ||
(config.rules['content-organization']?.enabled ?? false),
create: () => new ContentOrganizationRule(),
},
{
id: 'format',
defaultEnabled: false,
metadata: RULE_METADATA['format'],
create: () => new FormatRule(),
},
{
id: 'code-blocks',
defaultEnabled: true,
metadata: RULE_METADATA['code-blocks'],
create: config => new CodeBlockRule(config.rules['code-blocks']?.options ?? {}),
},
{
id: 'import-syntax',
defaultEnabled: true,
metadata: RULE_METADATA['import-syntax'],
create: config => new ImportSyntaxRule(numberOption(config.rules['import-syntax']?.options, 'maxDepth')),
},
{
id: 'file-location',
defaultEnabled: true,
metadata: RULE_METADATA['file-location'],
create: () => new FileLocationRule(),
},
{
id: 'import-resolution',
defaultEnabled: true,
metadata: RULE_METADATA['import-resolution'],
create: config => new ImportResolutionRule(numberOption(config.rules['import-resolution']?.options, 'maxDepth')),
},
{
id: 'content-appropriateness',
defaultEnabled: true,
metadata: RULE_METADATA['content-appropriateness'],
create: config => new ContentAppropriatenessRule(config.rules['content-appropriateness']?.options ?? {}),
},
{
id: 'monorepo-hierarchy',
defaultEnabled: true,
metadata: RULE_METADATA['monorepo-hierarchy'],
create: () => new MonorepoHierarchyRule(),
},
{
id: 'command-safety',
defaultEnabled: true,
metadata: RULE_METADATA['command-safety'],
create: () => new CommandSafetyRule(),
},
{
id: 'skill-structure',
defaultEnabled: true,
metadata: RULE_METADATA['skill-structure'],
create: config => new SkillStructureRule(config.rules['skill-structure']?.options ?? {}),
},
{
id: 'subagent-structure',
defaultEnabled: true,
metadata: RULE_METADATA['subagent-structure'],
create: config => new SubagentStructureRule(config.rules['subagent-structure']?.options ?? {}),
},
{
id: 'hook-configuration',
defaultEnabled: true,
metadata: RULE_METADATA['hook-configuration'],
create: config => new HookConfigurationRule(config.rules['hook-configuration']?.options ?? {}),
},
{
id: 'karpathy',
defaultEnabled: true,
metadata: RULE_METADATA['karpathy'],
create: () => new KarpathyRule(),
},
{
id: 'secret-detection',
// Committing a live credential is severe enough that this runs unless a
// config explicitly opts out.
defaultEnabled: true,
metadata: RULE_METADATA['secret-detection'],
create: () => new SecretDetectionRule(),
},
{
id: 'plugin-manifest',
defaultEnabled: true,
metadata: RULE_METADATA['plugin-manifest'],
create: () => new PluginManifestRule(),
},
{
id: 'mcp-config',
defaultEnabled: true,
metadata: RULE_METADATA['mcp-config'],
create: () => new McpConfigRule(),
},
{
id: 'output-style',
defaultEnabled: true,
metadata: RULE_METADATA['output-style'],
create: () => new OutputStyleRule(),
},
];
/** Resolve whether a descriptor's rule is enabled under the given config. */
export function isRuleEnabled(descriptor, config) {
if (descriptor.isEnabled) {
return descriptor.isEnabled(config);
}
return config.rules[descriptor.id]?.enabled ?? descriptor.defaultEnabled;
}
/** The set of built-in rule ids, derived from the descriptor list. */
export function getBuiltinRuleIds() {
return new Set(RULE_DESCRIPTORS.map(descriptor => descriptor.id));
}
//# sourceMappingURL=ruleDescriptors.js.map