@felixgeelhaar/cclint
Version:
Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.
572 lines • 24.3 kB
JavaScript
/**
* Registry of all rule metadata
*/
export const RULE_METADATA = {
'file-size': {
id: 'file-size',
name: 'File Size',
description: 'Enforces maximum file size for CLAUDE.md files',
rationale: 'Large CLAUDE.md files can exceed context window limits and become difficult to maintain. ' +
'Keeping files concise ensures they remain effective as AI context.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '# Project (10,000+ characters of content...)',
explanation: 'Files exceeding the maximum size limit may be truncated or cause performance issues.',
},
],
goodExamples: [
{
code: '# Project\\n\\nConcise, focused documentation...',
explanation: 'Keep CLAUDE.md files focused on essential context that fits within limits.',
},
],
options: [
{
name: 'maxSize',
type: 'number',
default: 10000,
description: 'Maximum file size in characters',
},
],
related: ['structure', 'content-organization'],
},
structure: {
id: 'structure',
name: 'Document Structure',
description: 'Validates required sections and document organization',
rationale: 'A well-structured CLAUDE.md file helps AI assistants quickly understand project context. ' +
'Required sections ensure essential information is always present.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: 'Just some random text without headers...',
explanation: 'Missing required sections like title and project overview.',
},
{
code: '## Subsection\\n\\nContent without main title',
explanation: 'Document should start with a main title (# header).',
},
],
goodExamples: [
{
code: '# Project Name\\n\\n## Overview\\n\\nProject description...',
explanation: 'Clear structure with title and organized sections.',
},
],
related: ['content-organization', 'format'],
},
format: {
id: 'format',
name: 'Markdown Format',
description: 'Validates markdown syntax and formatting conventions',
rationale: 'Consistent markdown formatting improves readability and ensures proper parsing. ' +
'Well-formatted documents are easier for both humans and AI to process.',
fixable: true,
defaultSeverity: 'warning',
badExamples: [
{
code: '##Missing space',
explanation: 'Headers require a space after the # symbols.',
},
{
code: '# Title ',
explanation: 'Lines should not have trailing whitespace.',
},
{
code: '```unknownlang\\ncode\\n```',
explanation: 'Use recognized language identifiers for code blocks.',
},
],
goodExamples: [
{
code: '## Properly Spaced Header',
explanation: 'Headers have proper spacing after #.',
},
{
code: '```javascript\\nconst x = 1;\\n```',
explanation: 'Code blocks use recognized language identifiers.',
},
],
related: ['code-blocks', 'structure'],
},
'code-blocks': {
id: 'code-blocks',
name: 'Code Blocks',
description: 'Validates code block syntax and language specifications',
rationale: 'Properly formatted code blocks with language specifications enable syntax highlighting ' +
'and help AI understand the context of code examples.',
fixable: true,
defaultSeverity: 'warning',
badExamples: [
{
code: '```\\ncode without language\\n```',
explanation: 'Code blocks should specify a language for syntax highlighting.',
},
{
code: '```javascript\\nconst x = 1;\\n// missing closing',
explanation: 'Code blocks must be properly closed.',
},
],
goodExamples: [
{
code: '```typescript\\nconst greeting: string = "hello";\\n```',
explanation: 'Code block with proper language identifier and closing.',
},
],
options: [
{
name: 'requireLanguage',
type: 'boolean',
default: true,
description: 'Require language specification for code blocks',
},
{
name: 'allowedLanguages',
type: 'string[]',
default: '[]',
description: 'Restrict to specific languages (empty = all allowed)',
},
],
related: ['format'],
},
'import-syntax': {
id: 'import-syntax',
name: 'Import Syntax',
description: 'Validates @import directive syntax for including other files',
rationale: 'The @import directive allows modular CLAUDE.md organization. Proper syntax ensures ' +
'imports are correctly resolved and processed.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '@import missing/quotes',
explanation: 'Import paths must be quoted.',
},
{
code: '@import "../../../deeply/nested/file.md"',
explanation: 'Excessive directory traversal may indicate import issues.',
},
],
goodExamples: [
{
code: '@import "./common/rules.md"',
explanation: 'Properly quoted relative import path.',
},
{
code: '@import "../shared/conventions.md"',
explanation: 'Parent directory imports within reasonable depth.',
},
],
options: [
{
name: 'maxDepth',
type: 'number',
default: 3,
description: 'Maximum allowed directory traversal depth',
},
],
related: ['import-resolution', 'file-location'],
},
'import-resolution': {
id: 'import-resolution',
name: 'Import Resolution',
description: 'Validates that @import paths resolve to existing files',
rationale: 'Import paths must point to actual files for the modular system to work. ' +
'This rule catches broken imports before they cause runtime errors.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '@import "./nonexistent.md"',
explanation: 'Import points to a file that does not exist.',
},
],
goodExamples: [
{
code: '@import "./README.md" # existing file',
explanation: 'Import points to an existing file in the project.',
},
],
related: ['import-syntax', 'file-location'],
},
'file-location': {
id: 'file-location',
name: 'File Location',
description: 'Validates CLAUDE.md is in appropriate locations',
rationale: 'CLAUDE.md files should be placed in root directories or recognized subdirectories ' +
'to ensure they are discovered and processed correctly.',
fixable: false,
defaultSeverity: 'warning',
badExamples: [
{
code: 'src/utils/helpers/CLAUDE.md',
explanation: 'CLAUDE.md in a deeply nested utility folder may not be discovered.',
},
],
goodExamples: [
{
code: 'CLAUDE.md # in project root',
explanation: 'Root-level CLAUDE.md is always discovered.',
},
{
code: 'packages/api/CLAUDE.md # in monorepo package',
explanation: 'Package-level CLAUDE.md in monorepo structure.',
},
],
related: ['monorepo-hierarchy'],
},
'content-appropriateness': {
id: 'content-appropriateness',
name: 'Content Appropriateness',
description: 'Validates content is appropriate for AI context files',
rationale: 'CLAUDE.md files should contain project context, not secrets, credentials, or ' +
'inappropriate content that could be exposed or misused.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: 'API_KEY=sk-1234567890abcdef',
explanation: 'Secrets and credentials should never be in CLAUDE.md.',
},
{
code: 'password: admin123',
explanation: 'Passwords and sensitive data should be excluded.',
},
],
goodExamples: [
{
code: 'API_KEY: Use environment variable $API_KEY',
explanation: 'Reference environment variables instead of actual values.',
},
{
code: 'See .env.example for required configuration',
explanation: 'Point to template files for configuration examples.',
},
],
related: ['command-safety'],
},
'monorepo-hierarchy': {
id: 'monorepo-hierarchy',
name: 'Monorepo Hierarchy',
description: 'Validates CLAUDE.md hierarchy in monorepo structures',
rationale: 'In monorepos, CLAUDE.md files should form a proper hierarchy with root context ' +
'and package-specific context that complements rather than duplicates.',
fixable: false,
defaultSeverity: 'warning',
badExamples: [
{
code: '# packages/api/CLAUDE.md duplicates root info',
explanation: 'Package CLAUDE.md should add specific context, not duplicate root.',
},
],
goodExamples: [
{
code: '# API Package\\n\\n@import "../../CLAUDE.md"\\n\\n## API-Specific Context',
explanation: 'Package imports root context and adds specific information.',
},
],
related: ['file-location', 'import-resolution'],
},
'command-safety': {
id: 'command-safety',
name: 'Command Safety',
description: 'Validates that documented commands are safe to execute',
rationale: 'Commands in CLAUDE.md may be executed by AI assistants. This rule flags potentially ' +
'dangerous commands that could harm the system or data.',
fixable: false,
defaultSeverity: 'warning',
badExamples: [
{
code: '```bash\\nrm -rf /\\n```',
explanation: 'Destructive commands that could delete system files.',
},
{
code: '```bash\\nsudo chmod 777 /etc\\n```',
explanation: 'Overly permissive permission changes.',
},
],
goodExamples: [
{
code: '```bash\\nnpm test\\n```',
explanation: 'Safe development commands with clear purpose.',
},
{
code: '```bash\\n# Clean build artifacts\\nrm -rf dist/\\n```',
explanation: 'Scoped deletion with clear intent documented.',
},
],
related: ['content-appropriateness'],
},
'content-organization': {
id: 'content-organization',
name: 'Content Organization',
description: 'Validates logical organization and flow of content',
rationale: 'Well-organized content with clear sections and logical flow helps AI understand ' +
'the project context more effectively.',
fixable: false,
defaultSeverity: 'warning',
badExamples: [
{
code: 'Random info\\n# Title\\nMore random info',
explanation: 'Content before the main title disrupts document flow.',
},
],
goodExamples: [
{
code: '# Title\\n\\n## Section 1\\n\\nContent...\\n\\n## Section 2\\n\\nMore content...',
explanation: 'Logical progression from title through organized sections.',
},
],
related: ['structure', 'format'],
},
'skill-structure': {
id: 'skill-structure',
name: 'Skill Structure',
description: 'Validates Claude Code skill files in .claude/skills/*.md',
rationale: 'Skills are dynamically loaded by Claude Code and must declare valid frontmatter ' +
'(name, description) to be discoverable. A skill with malformed metadata is dead code.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '# My Skill\\n\\nSome instructions...',
explanation: 'Missing frontmatter. Skill cannot be loaded by Claude Code.',
},
],
goodExamples: [
{
code: '---\\nname: my-skill\\ndescription: Helps with X. Use when user asks about Y.\\n---\\n\\n# My Skill',
explanation: 'Frontmatter with kebab-case name and trigger-word description.',
},
],
related: ['subagent-structure', 'hook-configuration'],
references: ['https://docs.anthropic.com/en/docs/claude-code/skills'],
},
'subagent-structure': {
id: 'subagent-structure',
name: 'Subagent Structure',
description: 'Validates Claude Code subagent files in .claude/agents/*.md',
rationale: 'Subagents declare frontmatter with name, description, optional tools, and model. ' +
'Invalid model IDs or unknown tool names produce silent runtime failures when the ' +
'agent is invoked. The rule also flags deprecated Claude 3 models.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '---\\nname: agent\\nmodel: claude-3-5-sonnet\\n---\\n\\nDo things.',
explanation: 'claude-3-5-sonnet is from a deprecated family; prompt is too short.',
},
],
goodExamples: [
{
code: '---\\nname: security-reviewer\\ndescription: Reviews code for vulnerabilities\\nmodel: claude-sonnet-4-6\\ntools:\\n - Read\\n - Grep\\n---\\n\\nYou are a senior security engineer...',
explanation: 'Current Claude 4 model, declared tools, descriptive prompt.',
},
],
related: ['skill-structure', 'hook-configuration'],
references: ['https://docs.anthropic.com/en/docs/claude-code/sub-agents'],
},
'hook-configuration': {
id: 'hook-configuration',
name: 'Hook Configuration',
description: 'Validates Claude Code hook configuration in .claude/settings.json',
rationale: 'Hooks execute shell commands automatically on Claude Code events. Malformed JSON ' +
'breaks Claude Code; dangerous commands (rm -rf, curl|sh) become a confused-deputy ' +
"attack surface because they run with the user's permissions, not the tool's.",
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '{ "hooks": { "PreToolUse": [{ "command": "rm -rf $TARGET" }] } }',
explanation: 'Unquoted variable in destructive command + no error handling.',
},
],
goodExamples: [
{
code: '{ "hooks": { "PreToolUse": [{ "matcher": "Bash", "command": "set -e; ./scripts/audit.sh" }] } }',
explanation: 'Explicit matcher, set -e for error propagation, no shell interpolation of untrusted input.',
},
],
related: ['command-safety', 'subagent-structure'],
references: ['https://docs.anthropic.com/en/docs/claude-code/hooks'],
},
karpathy: {
id: 'karpathy',
name: 'Karpathy Recommendations',
description: 'Opinionated CLAUDE.md style advisories: minimal, high-signal, ' +
'literal, example-driven context',
rationale: 'Inspired by Andrej Karpathy’s commentary on writing for LLMs and ' +
'"context engineering": you program the model in English, so the ' +
'context window should be minimal and high signal-to-noise, ' +
'instructions should be literal (hedging invites drift), and rules ' +
'are best shown with concrete examples (few-shot beats zero-shot). ' +
'Opinionated heuristics, not an official standard — all findings ' +
'are INFO.',
fixable: false,
defaultSeverity: 'info',
badExamples: [
{
code: '- Please try to keep functions small where appropriate. Thank you!',
explanation: 'Hedging ("try to", "where appropriate") and politeness ' +
'("Please", "Thank you") dilute a literal instruction and burn ' +
'context tokens.',
},
],
goodExamples: [
{
code: '- Keep functions under 40 lines.\n\n```go\nfunc Add(a, b int) int { return a + b }\n```',
explanation: 'Direct imperative rule plus a concrete example — literal and ' +
'few-shot.',
},
],
related: ['content-appropriateness', 'content-organization'],
references: [
'https://karpathy.ai/',
'https://x.com/karpathy/status/1937902205765607626',
],
},
'secret-detection': {
id: 'secret-detection',
name: 'Secret Detection',
description: 'Detects likely API keys, tokens, and private keys committed to ' +
'CLAUDE.md',
rationale: 'CLAUDE.md files are versioned, shared, and fed to models, so a ' +
'credential pasted into one leaks widely and is trivially exfiltrated. ' +
'This rule flags common provider key shapes (OpenAI, Anthropic, ' +
'GitHub, AWS, Google, Slack), PEM private-key blocks, and high-entropy ' +
'KEY=/TOKEN=/SECRET=/PASSWORD= assignments. Findings are errors and ' +
'mask the value so the linter never re-echoes the secret.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
// Low-entropy placeholder body: illustrates the sk- shape without a
// realistic key (which would trip secret scanners on this repo's docs).
code: 'Set your key: sk-REDACTED-example-not-a-real-token',
explanation: 'An OpenAI key shape committed to the context file. Remove it and ' +
'rotate the credential; reference secrets via environment ' +
'variables instead.',
},
],
goodExamples: [
{
code: 'Set `OPENAI_API_KEY` in your environment (never commit the value).',
explanation: 'Documents the variable name without embedding the secret.',
},
],
related: ['command-safety', 'content-appropriateness'],
references: [
'https://owasp.org/www-community/vulnerabilities/Use_of_hard-coded_password',
],
},
'plugin-manifest': {
id: 'plugin-manifest',
name: 'Plugin Manifest',
description: 'Validates Claude Code plugin.json and marketplace.json manifests',
rationale: 'A plugin manifest drives discovery and installation. Malformed JSON, a ' +
'missing "name", a non-SemVer "version", or a broken resource path ' +
'(commands/agents/skills/hooks) silently prevents the plugin — or an ' +
"entire marketplace's plugins — from loading.",
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '{ "version": "v1" }',
explanation: 'Missing "name" and "version" is not valid SemVer (expected 1.0.0).',
},
{
code: '{ "name": "x", "commands": 42 }',
explanation: '"commands" must be a path string or an array of path strings.',
},
],
goodExamples: [
{
code: '{ "name": "my-plugin", "version": "1.2.3", "commands": ["./commands/run.md"] }',
explanation: 'Valid name, SemVer version, and a well-formed relative resource path.',
},
],
related: ['hook-configuration', 'mcp-config'],
references: ['https://docs.anthropic.com/en/docs/claude-code/plugins'],
},
'mcp-config': {
id: 'mcp-config',
name: 'MCP Config',
description: 'Validates Claude Code MCP server configuration (.mcp.json)',
rationale: 'Claude Code loads MCP servers from .mcp.json. A server that mixes stdio ' +
'and remote transports, omits both, uses a bad "type", or carries a ' +
'malformed "${VAR}" placeholder is silently dropped — and a duplicate ' +
'server name overwrites an earlier one because JSON keeps only the last.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '{ "mcpServers": { "a": { "command": "x", "url": "https://y" } } }',
explanation: 'A server must be either stdio ("command") or remote ("url"), not both.',
},
{
code: '{ "mcpServers": { "a": { "url": "https://y", "type": "ws" } } }',
explanation: 'Remote "type" must be "sse" or "http".',
},
],
goodExamples: [
{
code: '{ "mcpServers": { "fs": { "command": "npx", "args": ["-y", "server"], "env": { "TOKEN": "${GH_TOKEN}" } } } }',
explanation: 'A stdio server with string args and a well-formed env placeholder.',
},
],
related: ['hook-configuration', 'plugin-manifest'],
references: ['https://docs.anthropic.com/en/docs/claude-code/mcp'],
},
'output-style': {
id: 'output-style',
name: 'Output Style',
description: 'Validates Claude Code output-style frontmatter (.claude/output-styles/*.md)',
rationale: 'An output style is discovered by the "name" and "description" in its ' +
'frontmatter; a style missing either is invisible to Claude Code, and an ' +
'unrecognized key is almost always a typo for one of them.',
fixable: false,
defaultSeverity: 'error',
badExamples: [
{
code: '# Concise\n\nRespond tersely.',
explanation: 'No frontmatter, so the style cannot be loaded.',
},
{
code: '---\nname: Concise\ncolour: blue\n---',
explanation: 'Missing "description" and an unknown key ("colour") that is likely a typo.',
},
],
goodExamples: [
{
code: '---\nname: Concise\ndescription: Short, direct answers with no preamble.\n---\n\nRespond tersely.',
explanation: 'Frontmatter declares both required fields.',
},
],
related: ['skill-structure', 'subagent-structure'],
references: [
'https://docs.anthropic.com/en/docs/claude-code/output-styles',
],
},
};
/**
* Get metadata for a specific rule
*/
export function getRuleMetadata(ruleId) {
return RULE_METADATA[ruleId];
}
/**
* Get all available rule IDs
*/
export function getAllRuleIds() {
return Object.keys(RULE_METADATA);
}
/**
* Check if a rule exists
*/
export function isValidRule(ruleId) {
return ruleId in RULE_METADATA;
}
//# sourceMappingURL=ruleMetadata.js.map