UNPKG

@diullei/codeguardian

Version:

Open-source developer tool to validate and enforce architectural rules, especially for AI-generated code

250 lines 10.3 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.ConsoleReporter = void 0; const cliCommandBuilder_1 = require("../utils/cliCommandBuilder"); class ConsoleReporter { claudeCodeHook; log; constructor(options = {}) { this.claudeCodeHook = options.claudeCodeHook || false; this.log = this.claudeCodeHook ? console.error : console.log; } colors = { reset: '\x1b[0m', bright: '\x1b[1m', dim: '\x1b[2m', red: '\x1b[31m', green: '\x1b[32m', yellow: '\x1b[33m', blue: '\x1b[34m', cyan: '\x1b[36m', white: '\x1b[37m', bgRed: '\x1b[41m', bgGreen: '\x1b[42m', }; report(report) { if (this.claudeCodeHook && report.passed) { return; } if (this.claudeCodeHook && !report.passed) { this.reportCompact(report); return; } this.reportDetailed(report); } reportCompact(report) { const allViolations = []; report.results .filter(r => !r.passed) .forEach(result => { result.violations.forEach(violation => { allViolations.push({ ...violation, ruleId: result.ruleId }); }); }); const prioritizedViolations = this.prioritizeViolations(allViolations); const maxToShow = 5; const totalViolations = prioritizedViolations.length; const violationsToShow = prioritizedViolations.slice(0, maxToShow); const remainingCount = Math.max(0, totalViolations - maxToShow); this.log(`VIOLATIONS (${totalViolations} total${remainingCount > 0 ? `, showing first ${violationsToShow.length}` : ''}):`); this.log(''); violationsToShow.forEach(violation => { this.printCompactViolation(violation); }); if (remainingCount > 0 && report.originalCliArgs) { const fullCommand = (0, cliCommandBuilder_1.generateFullViewCommand)(report.originalCliArgs); this.log(`+${remainingCount} more violation${remainingCount === 1 ? '' : 's'}. Run: ${fullCommand}`); } } reportDetailed(report) { this.log(this.color('='.repeat(80), 'cyan')); this.log(this.color('validation session starts', 'cyan', 'bright')); this.log(`platform ${process.platform} -- Node ${process.version}, codeguardian-1.0.0`); this.log(`rootdir: ${process.cwd()}`); const individualRules = report.summary.totalIndividualRules || 0; const configFiles = report.results.length; this.log(`collected ${report.summary.totalFiles} files, ${individualRules} ${individualRules === 1 ? 'rule' : 'rules'} (${configFiles} config ${configFiles === 1 ? 'file' : 'files'})`); this.log(''); if (!report.passed && report.results.some(r => !r.passed)) { this.log(this.color('FAILURES', 'red', 'bright')); this.log(this.color('='.repeat(80), 'red')); report.results .filter(r => !r.passed) .forEach((result, index) => { if (index > 0) this.log(''); this.printRuleViolations(result); }); } this.log(''); this.log(this.color('='.repeat(80), report.passed ? 'green' : 'red')); this.printPytestStyleSummary(report); } printPytestStyleSummary(report) { const { summary } = report; const duration = report.duration / 1000; if (report.passed) { const passedText = `${summary.passedRules} ${summary.passedRules === 1 ? 'rule' : 'rules'} passed`; this.log(this.color(`${'='.repeat(4)} ${passedText} in ${duration.toFixed(2)}s ${'='.repeat(80 - passedText.length - 13)}`, 'green', 'bright')); } else { const parts = []; if (summary.failedRules > 0) { parts.push(this.color(`${summary.failedRules} ${summary.failedRules === 1 ? 'rule' : 'rules'} failed`, 'red', 'bright')); } if (summary.passedRules > 0) { parts.push(this.color(`${summary.passedRules} ${summary.passedRules === 1 ? 'rule' : 'rules'} passed`, 'green')); } const statusText = parts.join(', '); const fullText = `${statusText} in ${duration.toFixed(2)}s`; const textLength = fullText.replace(/\x1b\[[0-9;]*m/g, '').length; const padding = Math.max(0, 80 - textLength - 8); this.log(`${'='.repeat(4)} ${fullText} ${'='.repeat(padding)}`); } this.log(''); const fileInfo = `Validated ${summary.totalFiles} ${summary.totalFiles === 1 ? 'file' : 'files'}`; const violationInfo = summary.violations > 0 ? `, found ${summary.violations} ${summary.violations === 1 ? 'violation' : 'violations'}` : ''; this.log(this.color(`${fileInfo}${violationInfo}`, 'dim')); if (!report.passed) { this.log(this.color('\nHint: ', 'yellow') + 'use --format=json for machine-readable output'); } } printRuleViolations(result) { this.log(this.color(result.ruleId, 'red', 'bright')); if (result.configFile) { this.log(this.color(`From: ${result.configFile}`, 'cyan')); } if (result.ruleDescription) { this.log(this.color(`${result.ruleDescription}`, 'dim')); } this.log(''); result.violations.forEach((violation) => { this.printViolation(violation); }); } printViolation(violation) { const location = this.formatLocation(violation); this.log(this.color(`> ${location}`, 'bright')); this.log(this.color('[CHECK FAIL] ' + violation.message, 'red', 'bright')); if (violation.context) { if (violation.context.code) { this.log(''); const codeLines = violation.context.code.split('\n').slice(0, 3); codeLines.forEach(line => { this.log(this.color(` ${line}`, 'dim')); }); } if (violation.context.suggestion) { this.log(''); this.log(this.color(' Suggestion: ', 'yellow') + violation.context.suggestion); } if (violation.context.documentation) { this.log(this.color(' See: ', 'cyan') + violation.context.documentation); } } this.log(''); } formatLocation(violation) { if (!violation.file) return 'General'; let location = violation.file; if (violation.line !== undefined) { location += `:${violation.line}`; if (violation.column !== undefined) { location += `:${violation.column}`; } } return location; } prioritizeViolations(violations) { return violations.sort((a, b) => { if (a.severity === 'error' && b.severity !== 'error') return -1; if (a.severity !== 'error' && b.severity === 'error') return 1; if (a.line !== undefined && b.line === undefined) return -1; if (a.line === undefined && b.line !== undefined) return 1; if (a.file && b.file) { return a.file.localeCompare(b.file); } return 0; }); } printCompactViolation(violation) { const location = this.formatLocation(violation); const ruleDisplay = `[${violation.ruleId}]`; this.log(`${location} ${ruleDisplay}`); const foundText = this.extractFoundText(violation); if (foundText) { this.log(` Found: ${foundText}`); } const fixText = this.extractFixText(violation); if (fixText) { this.log(` Fix: ${fixText}`); } else if (violation.context?.suggestion) { this.log(` Fix: ${violation.context.suggestion}`); } this.log(''); } extractFoundText(violation) { if (violation.context?.code) { const firstLine = violation.context.code.split('\n').find(line => line.trim()); if (firstLine) { return firstLine.trim().substring(0, 60) + (firstLine.length > 60 ? '...' : ''); } } const foundMatch = violation.message.match(/(?:found|contains|has|detected)[\s:]+(.+?)(?:\s+(?:but|in|at|$))/i); if (foundMatch && foundMatch[1]) { return foundMatch[1].substring(0, 60); } return null; } extractFixText(violation) { const message = violation.message.toLowerCase(); if (message.includes('console.log') || message.includes('console')) { return 'Remove console.log or use proper logger'; } if (message.includes('unused')) { return 'Remove unused code'; } if (message.includes('import') && message.includes('infrastructure')) { return 'Remove infrastructure import from domain layer'; } if (message.includes('try-catch') || message.includes('error handling')) { return 'Add error handling'; } if (message.includes('validation') || message.includes('sanitiz')) { return 'Add input validation'; } if (message.includes('secret') || message.includes('password')) { return 'Use environment variables'; } if (message.includes('not allowed') || message.includes('should not')) { return 'Remove disallowed code'; } return null; } color(text, ...colors) { if (this.claudeCodeHook) { return text; } let result = text; for (const color of colors) { const colorCode = this.colors[color]; if (colorCode) { result = colorCode + result + this.colors.reset; } } return result; } } exports.ConsoleReporter = ConsoleReporter; //# sourceMappingURL=ConsoleReporter.js.map