import * as fs from 'fs-extra'; import * as path from 'path'; import { DiffResult, DiffFile, DiffLine, FileChange } from '../types/index'; export { parseDiffOutput } from './git-utils'; export function formatDiffAsMarkdown(diffResult: DiffResult): string { const lines: string[] = []; lines.push(`# Agent Changes: ${diffResult.agentName}`); lines.push(''); lines.push(`**Branch:** \`${diffResult.branch}\``); lines.push(`**Compared to:** \`${diffResult.comparedBranch}\``); lines.push(`**Files Changed:** ${diffResult.summary.filesChanged}`); lines.push(`**Additions:** \`+${diffResult.summary.additions}\``); lines.push(`**Deletions:** \`-${diffResult.summary.deletions}\``); lines.push(''); lines.push('---'); lines.push(''); for (const file of diffResult.files) { lines.push(`## ${file.file}`); lines.push(''); lines.push('```diff'); for (const line of file.changes) { lines.push(line.content); } lines.push('```'); lines.push(''); } return lines.join('\n'); } export function formatDiffAsJson(diffResult: DiffResult): string { return JSON.stringify(diffResult, null, 2); } export function formatDiffAsText(diffResult: DiffResult): string { const lines: string[] = []; lines.push(`Agent: ${diffResult.agentName}`); lines.push(`Branch: ${diffResult.branch}`); lines.push(`Compared to: ${diffResult.comparedBranch}`); lines.push(`Files Changed: ${diffResult.summary.filesChanged}`); lines.push(`Additions: +${diffResult.summary.additions}`); lines.push(`Deletions: -${diffResult.summary.deletions}`); lines.push(''); lines.push('---'); lines.push(''); for (const file of diffResult.files) { lines.push(`File: ${file.file}`); for (const line of file.changes) { if (line.type !== 'context') { lines.push(` ${line.content}`); } } lines.push(''); } return lines.join('\n'); } export function formatChangesForReview( workspacePath: string, changes: FileChange[], agentName: string ): string { const lines: string[] = []; lines.push(`# Code Review: Agent ${agentName}`); lines.push(''); lines.push(`**Date:** ${new Date().toISOString()}`); lines.push(`**Workspace:** ${workspacePath}`); lines.push(''); lines.push('## Summary'); lines.push(''); const added = changes.filter(c => c.status === 'added').length; const modified = changes.filter(c => c.status === 'modified').length; const deleted = changes.filter(c => c.status === 'deleted').length; lines.push(`- Added: ${added}`); lines.push(`- Modified: ${modified}`); lines.push(`- Deleted: ${deleted}`); lines.push(''); lines.push('## Changes'); lines.push(''); for (const change of changes) { lines.push(`### ${change.file} (\`${change.status}\`)`); lines.push(''); if (change.diff) { lines.push('```diff'); lines.push(change.diff); lines.push('```'); } lines.push(''); } return lines.join('\n'); } export async function exportDiffToFile( diffResult: DiffResult, outputPath: string, format: 'markdown' | 'json' | 'text' = 'markdown' ): Promise { let content: string; switch (format) { case 'markdown': content = formatDiffAsMarkdown(diffResult); break; case 'json': content = formatDiffAsJson(diffResult); break; case 'text': content = formatDiffAsText(diffResult); break; default: content = formatDiffAsMarkdown(diffResult); } await fs.ensureDir(path.dirname(outputPath)); await fs.writeFile(outputPath, content, 'utf-8'); } export function generateShortDiffSummary(diffResult: DiffResult): string { const parts: string[] = []; if (diffResult.summary.filesChanged > 0) { parts.push(`${diffResult.summary.filesChanged} file(s)`); } if (diffResult.summary.additions > 0) { parts.push(`+${diffResult.summary.additions}`); } if (diffResult.summary.deletions > 0) { parts.push(`-${diffResult.summary.deletions}`); } return parts.length > 0 ? parts.join(' ') : 'No changes'; } export function highlightDiffLine(line: string): string { if (line.startsWith('+')) { return `\x1b[32m${line}\x1b[0m`; } if (line.startsWith('-')) { return `\x1b[31m${line}\x1b[0m`; } if (line.startsWith('@@')) { return `\x1b[36m${line}\x1b[0m`; } return line; } export function countDiffStats(diffContent: string): { additions: number; deletions: number; files: number } { let additions = 0; let deletions = 0; let files = 0; const lines = diffContent.split('\n'); for (const line of lines) { if (line.startsWith('+') && !line.startsWith('+++')) { additions++; } else if (line.startsWith('-') && !line.startsWith('---')) { deletions++; } else if (line.startsWith('diff --git')) { files++; } } return { additions, deletions, files: files || 1 }; } export function parseFileDiff( filePath: string, oldContent: string, newContent: string ): DiffFile { const changes: DiffLine[] = []; const oldLines = oldContent.split('\n'); const newLines = newContent.split('\n'); const lcs = computeLCS(oldLines, newLines); let oldIdx = 0; let newIdx = 0; for (const [oldI, newI] of lcs) { while (oldIdx < oldI) { changes.push({ type: 'deleted', lineNumber: { old: oldIdx + 1, new: null }, content: `-${oldLines[oldIdx]}` }); oldIdx++; } while (newIdx < newI) { changes.push({ type: 'added', lineNumber: { old: null, new: newIdx + 1 }, content: `+${newLines[newIdx]}` }); newIdx++; } changes.push({ type: 'context', lineNumber: { old: oldIdx + 1, new: newIdx + 1 }, content: ` ${oldLines[oldIdx]}` }); oldIdx++; newIdx++; } while (oldIdx < oldLines.length) { changes.push({ type: 'deleted', lineNumber: { old: oldIdx + 1, new: null }, content: `-${oldLines[oldIdx]}` }); oldIdx++; } while (newIdx < newLines.length) { changes.push({ type: 'added', lineNumber: { old: null, new: newIdx + 1 }, content: `+${newLines[newIdx]}` }); newIdx++; } return { file: filePath, changes, additions: changes.filter(c => c.type === 'added').length, deletions: changes.filter(c => c.type === 'deleted').length }; } function computeLCS( a: string[], b: string[] ): [number, number][] { const m = a.length; const n = b.length; const dp: number[][] = Array(m + 1).fill(null).map(() => Array(n + 1).fill(0)); for (let i = 1; i <= m; i++) { for (let j = 1; j <= n; j++) { if (a[i - 1] === b[j - 1]) { dp[i][j] = dp[i - 1][j - 1] + 1; } else { dp[i][j] = Math.max(dp[i - 1][j], dp[i][j - 1]); } } } const lcs: [number, number][] = []; let i = m; let j = n; while (i > 0 && j > 0) { if (a[i - 1] === b[j - 1]) { lcs.unshift([i - 1, j - 1]); i--; j--; } else if (dp[i - 1][j] > dp[i][j - 1]) { i--; } else { j--; } } return lcs; }