@diullei/codeguardian
Version:
Open-source developer tool to validate and enforce architectural rules, especially for AI-generated code
250 lines • 10.3 kB
JavaScript
"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