From 5a8386abb19b91224d60ce02cbe4174145fc304d Mon Sep 17 00:00:00 2001 From: 7000pctAUTO Date: Tue, 3 Feb 2026 09:03:39 +0000 Subject: [PATCH] feat: add diff utilities module --- src/utils/diff-utils.ts | 287 ++++++---------------------------------- 1 file changed, 40 insertions(+), 247 deletions(-) diff --git a/src/utils/diff-utils.ts b/src/utils/diff-utils.ts index 3f1ce88..664ab74 100644 --- a/src/utils/diff-utils.ts +++ b/src/utils/diff-utils.ts @@ -1,35 +1,21 @@ import * as fs from 'fs-extra'; import * as path from 'path'; -import { DiffResult, DiffFile, DiffLine, FileChange } from '../types/index'; -export { parseDiffOutput } from './git-utils'; +import { DiffResult, FileChange } from '../types/index'; 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(`# Diff: ${diffResult.agentName}`); + lines.push(`\n**Branch:** ${diffResult.branch} → ${diffResult.comparedBranch}`); + lines.push(`\n**Summary:** ${diffResult.summary.filesChanged} files changed, +${diffResult.summary.additions}, -${diffResult.summary.deletions}\n`); 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(`\n### ${file.file}`); + lines.push(`\n\`\`\`diff`); + for (const change of file.changes) { + lines.push(change.content); } - - lines.push('```'); - lines.push(''); + lines.push('\`\`\`'); } - return lines.join('\n'); } @@ -39,244 +25,51 @@ export function formatDiffAsJson(diffResult: DiffResult): string { 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(`Diff: ${diffResult.agentName}`); + lines.push(`${diffResult.branch} → ${diffResult.comparedBranch}`); + lines.push(`${diffResult.summary.filesChanged} files, +${diffResult.summary.additions}, -${diffResult.summary.deletions}`); 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(''); + lines.push(file.file); + lines.push(`+${file.additions} / -${file.deletions}`); } - 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 { +export async function exportDiffToFile(diffResult: DiffResult, outputPath: string, format: 'markdown' | 'json' | 'text'): 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)); + if (format === 'json') content = formatDiffAsJson(diffResult); + else if (format === 'text') content = formatDiffAsText(diffResult); + else content = formatDiffAsMarkdown(diffResult); 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'; + return `${diffResult.summary.filesChanged} files, +${diffResult.summary.additions}, -${diffResult.summary.deletions}`; } -export function highlightDiffLine(line: string): string { - if (line.startsWith('+')) { - return `\x1b[32m${line}\x1b[0m`; +export function formatChangesForReview(workspacePath: string, changes: FileChange[], agentName: string): string { + const lines: string[] = []; + lines.push(`# Changes: ${agentName}`); + lines.push(`\n**Generated:** ${new Date().toISOString()}`); + lines.push(`\n**Total changes:** ${changes.length}\n`); + lines.push('---'); + for (const change of changes) { + const statusIcon = getStatusIcon(change.status); + lines.push(`\n${statusIcon} ${change.file} ${change.staged ? '(staged)' : ''}`); + if (change.diff) lines.push('\n```diff'); + lines.push(change.diff); + if (change.diff) lines.push('```'); } - if (line.startsWith('-')) { - return `\x1b[31m${line}\x1b[0m`; - } - if (line.startsWith('@@')) { - return `\x1b[36m${line}\x1b[0m`; - } - return line; + return lines.join('\n'); } -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++; - } +function getStatusIcon(status: string): string { + switch (status) { + case 'added': return '✚'; + case 'modified': return '✎'; + case 'deleted': return '✖'; + case 'renamed': return '↔'; + default: return '?'; } - - 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; -} +} \ No newline at end of file