UNPKG

@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
/** * 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