This commit is contained in:
282
src/utils/diff-utils.ts
Normal file
282
src/utils/diff-utils.ts
Normal file
@@ -0,0 +1,282 @@
|
||||
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<void> {
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user