This commit is contained in:
@@ -1,35 +1,21 @@
|
|||||||
import * as fs from 'fs-extra';
|
import * as fs from 'fs-extra';
|
||||||
import * as path from 'path';
|
import * as path from 'path';
|
||||||
import { DiffResult, DiffFile, DiffLine, FileChange } from '../types/index';
|
import { DiffResult, FileChange } from '../types/index';
|
||||||
export { parseDiffOutput } from './git-utils';
|
|
||||||
|
|
||||||
export function formatDiffAsMarkdown(diffResult: DiffResult): string {
|
export function formatDiffAsMarkdown(diffResult: DiffResult): string {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
lines.push(`# Diff: ${diffResult.agentName}`);
|
||||||
lines.push(`# Agent Changes: ${diffResult.agentName}`);
|
lines.push(`\n**Branch:** ${diffResult.branch} → ${diffResult.comparedBranch}`);
|
||||||
lines.push('');
|
lines.push(`\n**Summary:** ${diffResult.summary.filesChanged} files changed, +${diffResult.summary.additions}, -${diffResult.summary.deletions}\n`);
|
||||||
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('---');
|
||||||
lines.push('');
|
|
||||||
|
|
||||||
for (const file of diffResult.files) {
|
for (const file of diffResult.files) {
|
||||||
lines.push(`## ${file.file}`);
|
lines.push(`\n### ${file.file}`);
|
||||||
lines.push('');
|
lines.push(`\n\`\`\`diff`);
|
||||||
lines.push('```diff');
|
for (const change of file.changes) {
|
||||||
|
lines.push(change.content);
|
||||||
for (const line of file.changes) {
|
|
||||||
lines.push(line.content);
|
|
||||||
}
|
}
|
||||||
|
lines.push('\`\`\`');
|
||||||
lines.push('```');
|
|
||||||
lines.push('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -39,244 +25,51 @@ export function formatDiffAsJson(diffResult: DiffResult): string {
|
|||||||
|
|
||||||
export function formatDiffAsText(diffResult: DiffResult): string {
|
export function formatDiffAsText(diffResult: DiffResult): string {
|
||||||
const lines: string[] = [];
|
const lines: string[] = [];
|
||||||
|
lines.push(`Diff: ${diffResult.agentName}`);
|
||||||
lines.push(`Agent: ${diffResult.agentName}`);
|
lines.push(`${diffResult.branch} → ${diffResult.comparedBranch}`);
|
||||||
lines.push(`Branch: ${diffResult.branch}`);
|
lines.push(`${diffResult.summary.filesChanged} files, +${diffResult.summary.additions}, -${diffResult.summary.deletions}`);
|
||||||
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('---');
|
||||||
lines.push('');
|
|
||||||
|
|
||||||
for (const file of diffResult.files) {
|
for (const file of diffResult.files) {
|
||||||
lines.push(`File: ${file.file}`);
|
lines.push(file.file);
|
||||||
for (const line of file.changes) {
|
lines.push(`+${file.additions} / -${file.deletions}`);
|
||||||
if (line.type !== 'context') {
|
|
||||||
lines.push(` ${line.content}`);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
lines.push('');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return lines.join('\n');
|
return lines.join('\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function formatChangesForReview(
|
export async function exportDiffToFile(diffResult: DiffResult, outputPath: string, format: 'markdown' | 'json' | 'text'): Promise<void> {
|
||||||
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;
|
let content: string;
|
||||||
|
if (format === 'json') content = formatDiffAsJson(diffResult);
|
||||||
switch (format) {
|
else if (format === 'text') content = formatDiffAsText(diffResult);
|
||||||
case 'markdown':
|
else content = formatDiffAsMarkdown(diffResult);
|
||||||
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');
|
await fs.writeFile(outputPath, content, 'utf-8');
|
||||||
}
|
}
|
||||||
|
|
||||||
export function generateShortDiffSummary(diffResult: DiffResult): string {
|
export function generateShortDiffSummary(diffResult: DiffResult): string {
|
||||||
const parts: string[] = [];
|
return `${diffResult.summary.filesChanged} files, +${diffResult.summary.additions}, -${diffResult.summary.deletions}`;
|
||||||
|
|
||||||
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 {
|
export function formatChangesForReview(workspacePath: string, changes: FileChange[], agentName: string): string {
|
||||||
if (line.startsWith('+')) {
|
const lines: string[] = [];
|
||||||
return `\x1b[32m${line}\x1b[0m`;
|
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 lines.join('\n');
|
||||||
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 } {
|
function getStatusIcon(status: string): string {
|
||||||
let additions = 0;
|
switch (status) {
|
||||||
let deletions = 0;
|
case 'added': return '✚';
|
||||||
let files = 0;
|
case 'modified': return '✎';
|
||||||
|
case 'deleted': return '✖';
|
||||||
const lines = diffContent.split('\n');
|
case 'renamed': return '↔';
|
||||||
for (const line of lines) {
|
default: return '?';
|
||||||
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