UNPKG

@felixgeelhaar/cclint

Version:

Catch CLAUDE.md drift before Claude misbehaves. Lints CLAUDE.md, skills, subagents, and hooks for Claude Code projects.

139 lines • 6.07 kB
import { Command } from 'commander'; import { resolve } from 'path'; import { FileWatcher, } from '../../infrastructure/FileWatcher.js'; import { FileReader } from '../../infrastructure/FileReader.js'; import { RulesEngine } from '../../domain/RulesEngine.js'; import { createRules } from '../../rules/registry/createRules.js'; import { formatResult } from '../formatters/textFormatter.js'; import { ConfigLoader } from '../../infrastructure/ConfigLoader.js'; import { AutoFixer } from '../../infrastructure/AutoFixer.js'; import { writeFileSync } from 'fs'; /** * Format timestamp for display */ function formatTime(date) { return date.toLocaleTimeString('en-US', { hour12: false, hour: '2-digit', minute: '2-digit', second: '2-digit', }); } /** * Clear the terminal screen */ function clearScreen() { process.stdout.write('\x1B[2J\x1B[0f'); } export const watchCommand = new Command('watch') .description('Watch CLAUDE.md files for changes and lint on save') .argument('[patterns...]', 'File patterns to watch (default: CLAUDE.md, **/CLAUDE.md)') .option('-r, --recursive', 'Watch directories recursively', true) .option('--no-recursive', 'Do not watch directories recursively') .option('--fix', 'Automatically fix problems on save', false) .option('--debounce <ms>', 'Debounce delay in milliseconds', '300') .option('--clear', 'Clear console between runs', false) .option('--no-clear', 'Do not clear console between runs') .option('-c, --config <path>', 'Path to configuration file') .action(async (patterns, options) => { try { // Default patterns if none provided const watchPatterns = patterns.length > 0 ? patterns : ['CLAUDE.md', '**/CLAUDE.md']; // Parse debounce const debounceMs = parseInt(options.debounce, 10); if (isNaN(debounceMs) || debounceMs < 0) { console.error('Error: --debounce must be a non-negative number'); process.exit(1); } // Load configuration const config = ConfigLoader.load(options.config); const rules = createRules(config); const engine = new RulesEngine(rules); const fileReader = new FileReader(); // Create file watcher const watcher = new FileWatcher({ patterns: watchPatterns, recursive: options.recursive, debounceMs, }); // Print startup message console.log('\nšŸ‘ļø Watching for changes...'); console.log(` Patterns: ${watchPatterns.join(', ')}`); console.log(` Debounce: ${debounceMs}ms`); console.log(` Auto-fix: ${options.fix ? 'enabled' : 'disabled'}`); console.log(` Recursive: ${options.recursive ? 'yes' : 'no'}`); console.log('\nPress Ctrl+C to stop\n'); // Handle file changes watcher.on('change', (event) => { void (async () => { if (options.clear) { clearScreen(); console.log('šŸ‘ļø Watching for changes...\n'); } const timestamp = formatTime(event.timestamp); const relativePath = event.path; if (event.type === 'unlink') { console.log(`[${timestamp}] Deleted: ${relativePath}\n`); return; } console.log(`[${timestamp}] ${event.type === 'add' ? 'Added' : 'Changed'}: ${relativePath}`); try { const absolutePath = resolve(process.cwd(), relativePath); const contextFile = await fileReader.readContextFile(absolutePath); let result = engine.lint(contextFile); // Auto-fix if requested if (options.fix && result.violations.length > 0) { const fixes = AutoFixer.generateFixesForViolations([...result.violations], contextFile.content, []); if (fixes.length > 0) { const fixResult = AutoFixer.applyFixes(contextFile.content, fixes); if (fixResult.fixed) { writeFileSync(absolutePath, fixResult.content, 'utf8'); console.log(`šŸ”§ Applied ${fixResult.appliedFixes.length} fixes`); // Re-lint after fixes const newContextFile = await fileReader.readContextFile(absolutePath); result = engine.lint(newContextFile); } } } // Display results const output = formatResult(result, 'text'); console.log(output); } catch (error) { if (error instanceof Error) { console.error(`Error linting ${relativePath}: ${error.message}`); } else { console.error(`Error linting ${relativePath}: Unknown error`); } } console.log(''); // Empty line for readability })(); }); // Handle watcher errors watcher.on('error', (error) => { console.error(`Watcher error: ${error.message}`); }); // Start watching await watcher.start(); // Handle graceful shutdown const shutdown = () => { console.log('\n\nStopping watcher...'); void watcher.stop().then(() => { process.exit(0); }); }; process.on('SIGINT', shutdown); process.on('SIGTERM', shutdown); } catch (error) { if (error instanceof Error) { console.error(`Error: ${error.message}`); } else { console.error('Error: Unknown error occurred'); } process.exit(1); } }); //# sourceMappingURL=watch.js.map