Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 20 additions & 20 deletions agent-code-review/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -144,7 +144,7 @@ function generateMarkdownReport(entries: ReviewEntry[]): string {
const lines: string[] = [
'# Code Review Report',
'',
`Generated: ${new Date().toLocaleDateString('es-AR')}`,
`Generated: ${new Date().toLocaleDateString('en-US')}`,
'',
'## Summary',
'',
Expand Down Expand Up @@ -179,7 +179,7 @@ async function main(): Promise<void> {
console.log('=== Agent Code Review v1.0.0 ===\n');

if (dryRun) {
console.log('[DRY-RUN] Modo preview activo - no se escribirán archivos\n');
console.log('[DRY-RUN] Preview mode active - no files will be written\n');
}

const scanSpinner = ora('🔍 Resolviendo provider...').start();
Expand All @@ -205,24 +205,24 @@ async function main(): Promise<void> {

if (verbose) {
console.log(`[INFO] Provider: ${resolved.provider}`);
console.log(`[INFO] Modelo: ${resolved.model}`);
console.log(`[INFO] Model: ${resolved.model}`);
console.log(`[INFO] Max chars: ${maxChars}`);
}

const extensions = options.extensions
? options.extensions.split(',').map((e: string) => e.trim())
: rcConfig?.extensions;

const filesSpinner = ora('🔍 Escaneando archivos...').start();
const filesSpinner = ora('🔍 Scanning files...').start();
const files = collectFiles(options.path, verbose, extensions);

if (files.length === 0) {
filesSpinner.warn('No se encontraron archivos soportados para procesar.');
filesSpinner.warn('No supported files found to process.');
process.exitCode = 0;
return;
}

filesSpinner.succeed(`Encontrados ${files.length} archivo(s) para revisar.`);
filesSpinner.succeed(`Found ${files.length} file(s) to review.`);

const projectRoot = process.cwd();
const cache = loadCache(projectRoot);
Expand All @@ -241,7 +241,7 @@ async function main(): Promise<void> {
try {
const fileData = readFile(filePath);
if (!fileData || shouldSkipContent(fileData.content)) {
fileSpinner.warn(`Saltado (archivo vacío o no procesable)`);
fileSpinner.warn(`Skipped (empty or unprocessable file)`);
skipped++;
continue;
}
Expand All @@ -260,7 +260,7 @@ async function main(): Promise<void> {
}

if (verbose) {
fileSpinner.text = `[${i + 1}/${files.length}] ${relativePath} - Generando review...`;
fileSpinner.text = `[${i + 1}/${files.length}] ${relativePath} - Generating review...`;
}

const reviewResults = await generateReview(ai, filePath, fileData.content, verbose, resolved.model, maxChars);
Expand All @@ -279,12 +279,12 @@ async function main(): Promise<void> {
succeeded++;
}
} else {
fileSpinner.fail(`[${i + 1}/${files.length}] ${relativePath} - No se pudo generar review`);
fileSpinner.fail(`[${i + 1}/${files.length}] ${relativePath} - Could not generate review`);
failed++;
}
} catch (err) {
const error = err as Error;
fileSpinner.fail(`[${i + 1}/${files.length}] ${relativePath} - Error inesperado`);
fileSpinner.fail(`[${i + 1}/${files.length}] ${relativePath} - Unexpected error`);
if (verbose) {
console.error(` ${error.message}`);
}
Expand All @@ -294,30 +294,30 @@ async function main(): Promise<void> {

if (!dryRun && reviewEntries.length > 0) {
if (options.output) {
const writeSpinner = ora('📄 Escribiendo reporte...').start();
const writeSpinner = ora('📄 Writing report...').start();
const outputPath = path.resolve(options.output);

const format = mergeConfig(options.format, rcConfig?.format as 'terminal' | 'markdown' | 'html' | 'pdf' | undefined, 'terminal');
if (format === 'markdown') {
const markdown = generateMarkdownReport(reviewEntries);
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, markdown, 'utf-8');
writeSpinner.succeed(`Reporte guardado: ${outputPath}`);
writeSpinner.succeed(`Report saved: ${outputPath}`);
} else if (format === 'html') {
const htmlOutput = outputPath.endsWith('.html') ? outputPath : outputPath + '.html';
const html = generateHtmlReport(reviewEntries, 'Agent Code Review');
fs.mkdirSync(path.dirname(htmlOutput), { recursive: true });
fs.writeFileSync(htmlOutput, html, 'utf-8');
writeSpinner.succeed(`Reporte HTML guardado: ${htmlOutput}`);
writeSpinner.succeed(`HTML report saved: ${htmlOutput}`);
} else if (format === 'pdf') {
const pdfOutput = outputPath.endsWith('.pdf') ? outputPath : outputPath + '.pdf';
fs.mkdirSync(path.dirname(pdfOutput), { recursive: true });
await generatePdfReport(reviewEntries, pdfOutput, 'Agent Code Review');
writeSpinner.succeed(`Reporte PDF guardado: ${pdfOutput}`);
writeSpinner.succeed(`PDF report saved: ${pdfOutput}`);
} else {
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
fs.writeFileSync(outputPath, JSON.stringify(reviewEntries, null, 2), 'utf-8');
writeSpinner.succeed(`Reporte guardado: ${outputPath}`);
writeSpinner.succeed(`Report saved: ${outputPath}`);
}
} else {
printTerminalReport(reviewEntries);
Expand All @@ -328,7 +328,7 @@ async function main(): Promise<void> {
saveCache(projectRoot, cache);
}

console.log('=== Resumen ===');
console.log('=== Summary ===');
console.log(` Total: ${files.length}`);
console.log(` ✅ OK: ${succeeded}`);
console.log(` ⚠️ Skip: ${skipped}`);
Expand All @@ -345,13 +345,13 @@ async function main(): Promise<void> {

main().catch((err) => {
const error = err as Error;
console.error(`[FATAL] Error inesperado: ${error.message}`);
console.error(`[FATAL] Unexpected error: ${error.message}`);
exitGracefully(1);
});

// En Windows, process.exit() con writes pendientes en stdout/stderr aborta con
// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)". Esperamos a que
// drenen los streams antes de forzar la salida.
// On Windows, process.exit() with pending stdout/stderr writes aborts with
// "Assertion failed: !(handle->flags & UV_HANDLE_CLOSING)". We wait for the
// streams to drain before forcing the exit.
function exitGracefully(code: number): void {
process.exitCode = code;
process.stdout.write('', () => {
Expand Down
14 changes: 7 additions & 7 deletions agent-code-review/src/htmlFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,18 +35,18 @@ export function generateHtmlReport(entries: ReviewEntry[], agentName: string): s
return `<div class="file-section">
<h2>${entry.relativePath}</h2>
<table>
<thead><tr><th>Severidad</th><th>Categoría</th><th>Sugerencia</th></tr></thead>
<thead><tr><th>Severity</th><th>Category</th><th>Suggestion</th></tr></thead>
<tbody>${resultsHtml}</tbody>
</table>
</div>`;
}).join('\n');

return `<!DOCTYPE html>
<html lang="es">
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>${agentName} - Reporte</title>
<title>${agentName} - Report</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; color: #1a1a2e; background: #f5f6fa; padding: 2rem; }
Expand Down Expand Up @@ -77,24 +77,24 @@ export function generateHtmlReport(entries: ReviewEntry[], agentName: string): s
<div class="container">
<header>
<h1>${agentName}</h1>
<p class="meta">Generado: ${new Date().toLocaleString('es-AR')} • Archivos revisados: ${entries.length}</p>
<p class="meta">Generated: ${new Date().toLocaleString('en-US')} • Files reviewed: ${entries.length}</p>
</header>
<div class="summary">
<div class="summary-card critical">
<div class="count">${critical.length}</div>
<div class="label">🔴 Críticos</div>
<div class="label">🔴 Critical</div>
</div>
<div class="summary-card warning">
<div class="count">${warnings.length}</div>
<div class="label">🟡 Advertencias</div>
<div class="label">🟡 Warnings</div>
</div>
<div class="summary-card info">
<div class="count">${info.length}</div>
<div class="label">🟢 Info</div>
</div>
</div>
${filesHtml}
<footer>Generado por ${agentName} • AI Agent Toolkit</footer>
<footer>Generated by ${agentName} • AI Agent Toolkit</footer>
</div>
</body>
</html>`;
Expand Down
12 changes: 6 additions & 6 deletions agent-code-review/src/pdfFormatter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,15 +16,15 @@ export async function generatePdfReport(entries: PdfEntry[], outputPath: string,
const info = allResults.filter(r => r.severity === 'info');

doc.fontSize(22).font('Helvetica-Bold').text(agentName, { align: 'center' });
doc.fontSize(12).font('Helvetica').text(`Generado: ${new Date().toLocaleString('es-AR')}`, { align: 'center' });
doc.fontSize(12).font('Helvetica').text(`Generated: ${new Date().toLocaleString('en-US')}`, { align: 'center' });
doc.moveDown(1.5);

doc.fontSize(14).font('Helvetica-Bold').text('Resumen');
doc.fontSize(14).font('Helvetica-Bold').text('Summary');
doc.moveDown(0.5);
doc.fontSize(11).font('Helvetica');
doc.text(`Archivos revisados: ${entries.length}`);
doc.text(`Críticos: ${critical.length}`);
doc.text(`Advertencias: ${warnings.length}`);
doc.text(`Files reviewed: ${entries.length}`);
doc.text(`Critical: ${critical.length}`);
doc.text(`Warnings: ${warnings.length}`);
doc.text(`Info: ${info.length}`);
doc.moveDown(1);

Expand All @@ -45,7 +45,7 @@ export async function generatePdfReport(entries: PdfEntry[], outputPath: string,
doc.moveDown(0.5);
}

doc.fontSize(9).font('Helvetica').fillColor('#999').text(`Generado por ${agentName} • AI Agent Toolkit`, { align: 'center' });
doc.fontSize(9).font('Helvetica').fillColor('#999').text(`Generated by ${agentName} • AI Agent Toolkit`, { align: 'center' });
doc.end();

return new Promise((resolve, reject) => {
Expand Down
4 changes: 2 additions & 2 deletions agent-code-review/tests/htmlFormatter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,8 @@ describe('htmlFormatter', () => {

it('includes summary cards with 0 counts for empty entries', () => {
const html = generateHtmlReport([], 'Agent Code Review');
expect(html).toContain('Críticos');
expect(html).toContain('Advertencias');
expect(html).toContain('Critical');
expect(html).toContain('Warnings');
expect(html).toContain('Info');
});

Expand Down
8 changes: 4 additions & 4 deletions agent-code-review/tests/providerIntegration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ const openaiKey = process.env.OPENAI_API_KEY;
const anthropicKey = process.env.ANTHROPIC_API_KEY;
const deepseekKey = process.env.DEEPSEEK_API_KEY;

const SIMPLE_PROMPT = 'Responde solo con la palabra "OK" sin explicaciones.';
const SIMPLE_PROMPT = 'Reply only with the word "OK" without explanations.';

async function testProvider(provider: string, apiKey: string | undefined, model: string): Promise<void> {
const client = createAIClient({ provider, apiKey });
Expand Down Expand Up @@ -48,14 +48,14 @@ describe('End-to-End Integration', () => {
const client = createAIClient({ provider: 'gemini', apiKey: geminiKey });
expect(client).not.toBeNull();

const prompt = `Eres un revisor de código. Analiza este código y responde SOLO con un JSON array.
const prompt = `You are a code reviewer. Analyze this code and respond ONLY with a JSON array.

Código:
Code:
function add(a, b) {
return a + b;
}

Devuelve: [{"severity":"info","category":"test","suggestion":"test"}]`;
Return: [{"severity":"info","category":"test","suggestion":"test"}]`;
const result = await client!.generate(prompt, 'gemini-2.5-flash');
expect(result).not.toBeNull();
expect(result!.length).toBeGreaterThan(10);
Expand Down
Loading
Loading