diff --git a/.src/commands/diff.ts b/.src/commands/diff.ts new file mode 100644 index 0000000..1073730 --- /dev/null +++ b/.src/commands/diff.ts @@ -0,0 +1,141 @@ +import * as path from 'path'; +import { Command } from 'commander'; +import chalk from 'chalk'; +import { + getWorkspacePath, + loadWorkspaceConfig, + sanitizeAgentName +} from '../utils/file-utils'; +import { + generateDiff, + createGit, + getCurrentBranch +} from '../utils/git-utils'; +import { DiffLine } from '../types/index'; +import { + formatDiffAsMarkdown, + formatDiffAsJson, + formatDiffAsText, + exportDiffToFile, + generateShortDiffSummary +} from '../utils/diff-utils'; +import { GitAgentSyncError, WorkspaceNotFoundError } from '../utils/errors'; + +export function createDiffCommand(): Command { + const cmd = new Command('diff') + .description('Generate unified diffs for code review') + .argument('[agent-name]', 'Agent name (defaults to current workspace)') + .option('-o, --output ', 'Export diff to file') + .option('--short', 'Show short summary only') + .option('--json', 'Output as JSON') + .option('--text', 'Output as plain text') + .option('--compare ', 'Compare to a specific branch (default: main)') + .action(async (agentName, options) => { + try { + const currentPath = process.cwd(); + let workspacePath: string; + let targetAgentName: string; + + if (agentName) { + targetAgentName = sanitizeAgentName(agentName); + workspacePath = getWorkspacePath(currentPath, targetAgentName); + } else { + const workspaceResult = await detectCurrentWorkspace(currentPath); + workspacePath = workspaceResult.path; + targetAgentName = workspaceResult.agentName; + } + + const config = await loadWorkspaceConfig(workspacePath); + if (!config) { + throw new WorkspaceNotFoundError(targetAgentName); + } + + const compareBranch = options.compare || config.mainBranch; + const diffResult = await generateDiff(currentPath, workspacePath, targetAgentName, compareBranch); + + if (options.short) { + const summary = generateShortDiffSummary(diffResult); + console.log(chalk.white(`${targetAgentName}: ${summary}`)); + return; + } + + if (options.json) { + console.log(formatDiffAsJson(diffResult)); + return; + } + + if (options.text) { + console.log(formatDiffAsText(diffResult)); + return; + } + + if (options.output) { + await exportDiffToFile(diffResult, options.output, 'markdown'); + console.log(chalk.green(`\nāœ“ Diff exported to ${options.output}`)); + return; + } + + console.log(chalk.cyan(`\nšŸ“ Diff: ${targetAgentName}`)); + console.log(chalk.gray(`Branch: ${diffResult.branch} → ${diffResult.comparedBranch}`)); + console.log(chalk.gray('─'.repeat(60))); + + if (diffResult.files.length === 0) { + console.log(chalk.yellow('\n No differences found.')); + return; + } + + console.log(chalk.cyan('\nšŸ“Š Summary:')); + console.log(` Files: ${chalk.white(diffResult.summary.filesChanged)}`); + console.log(` Additions: ${chalk.green(`+${diffResult.summary.additions}`)}`); + console.log(` Deletions: ${chalk.red(`-${diffResult.summary.deletions}`)}`); + console.log(chalk.gray('─'.repeat(60))); + + for (const file of diffResult.files) { + console.log(chalk.cyan(`\nšŸ“„ ${file.file}`)); + console.log(` +${file.additions} / -${file.deletions}`); + + const relevantChanges = file.changes.filter((c: DiffLine) => c.type !== 'context').slice(0, 20); + + if (relevantChanges.length > 0) { + console.log(' Preview:'); + for (const line of relevantChanges) { + if (line.type === 'added') { + console.log(` ${chalk.green(line.content)}`); + } else if (line.type === 'deleted') { + console.log(` ${chalk.red(line.content)}`); + } + } + + if (file.changes.filter((c: DiffLine) => c.type !== 'context').length > 20) { + console.log(` ${chalk.gray('... and more changes (use --output to see full diff)')}`); + } + } + } + + console.log(chalk.cyan('\nšŸ’” Use --output to export full diff to a file')); + + } catch (error) { + const gitError = error instanceof GitAgentSyncError ? error : new GitAgentSyncError(String(error)); + console.error(chalk.red(`\nāŒ Error: ${gitError.message}`)); + process.exit(1); + } + }); + + return cmd; +} + +async function detectCurrentWorkspace(currentPath: string): Promise<{ path: string; agentName: string }> { + const git = createGit(currentPath); + const branch = await getCurrentBranch(git); + + if (branch.startsWith('agent-')) { + const agentName = branch.replace(/^agent-/, ''); + const { getWorkspacePath } = await import('../utils/file-utils'); + return { + agentName, + path: getWorkspacePath(currentPath, agentName) + }; + } + + throw new GitAgentSyncError('Not in an agent workspace. Please specify an agent name.'); +}