mirror of
https://github.com/AutoMaker-Org/automaker.git
synced 2026-03-18 10:23:07 +00:00
feat: Address review comments, add stage/unstage functionality, conflict resolution improvements, support for Sonnet 4.6
This commit is contained in:
@@ -36,6 +36,7 @@ export function formatModelName(model: string): string {
|
||||
// Claude models
|
||||
if (model.includes('opus-4-6') || model === 'claude-opus') return 'Opus 4.6';
|
||||
if (model.includes('opus')) return 'Opus 4.5';
|
||||
if (model.includes('sonnet-4-6') || model === 'claude-sonnet') return 'Sonnet 4.6';
|
||||
if (model.includes('sonnet')) return 'Sonnet 4.5';
|
||||
if (model.includes('haiku')) return 'Haiku 4.5';
|
||||
|
||||
|
||||
133
apps/ui/src/lib/diff-utils.ts
Normal file
133
apps/ui/src/lib/diff-utils.ts
Normal file
@@ -0,0 +1,133 @@
|
||||
/**
|
||||
* Shared diff parsing utilities.
|
||||
*
|
||||
* Extracted from commit-worktree-dialog, discard-worktree-changes-dialog,
|
||||
* stash-changes-dialog and git-diff-panel to eliminate duplication.
|
||||
*/
|
||||
|
||||
export interface ParsedDiffHunk {
|
||||
header: string;
|
||||
lines: {
|
||||
type: 'context' | 'addition' | 'deletion' | 'header';
|
||||
content: string;
|
||||
lineNumber?: { old?: number; new?: number };
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface ParsedFileDiff {
|
||||
filePath: string;
|
||||
hunks: ParsedDiffHunk[];
|
||||
isNew?: boolean;
|
||||
isDeleted?: boolean;
|
||||
isRenamed?: boolean;
|
||||
/** Pre-computed count of added lines across all hunks */
|
||||
additions: number;
|
||||
/** Pre-computed count of deleted lines across all hunks */
|
||||
deletions: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse unified diff format into structured data.
|
||||
*
|
||||
* Note: The regex `diff --git a\/(.*?) b\/(.*)` uses a non-greedy match for
|
||||
* the `a/` path and a greedy match for `b/`. This can mis-handle paths that
|
||||
* literally contain " b/" or are quoted by git. In practice this covers the
|
||||
* vast majority of real-world paths; exotic cases will fall back to "unknown".
|
||||
*/
|
||||
export function parseDiff(diffText: string): ParsedFileDiff[] {
|
||||
if (!diffText) return [];
|
||||
|
||||
const files: ParsedFileDiff[] = [];
|
||||
const lines = diffText.split('\n');
|
||||
let currentFile: ParsedFileDiff | null = null;
|
||||
let currentHunk: ParsedDiffHunk | null = null;
|
||||
let oldLineNum = 0;
|
||||
let newLineNum = 0;
|
||||
|
||||
for (let i = 0; i < lines.length; i++) {
|
||||
const line = lines[i];
|
||||
|
||||
if (line.startsWith('diff --git')) {
|
||||
if (currentFile) {
|
||||
if (currentHunk) currentFile.hunks.push(currentHunk);
|
||||
files.push(currentFile);
|
||||
}
|
||||
const match = line.match(/diff --git a\/(.*?) b\/(.*)/);
|
||||
currentFile = {
|
||||
filePath: match ? match[2] : 'unknown',
|
||||
hunks: [],
|
||||
additions: 0,
|
||||
deletions: 0,
|
||||
};
|
||||
currentHunk = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('new file mode')) {
|
||||
if (currentFile) currentFile.isNew = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('deleted file mode')) {
|
||||
if (currentFile) currentFile.isDeleted = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('rename from') || line.startsWith('rename to')) {
|
||||
if (currentFile) currentFile.isRenamed = true;
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('index ') || line.startsWith('--- ') || line.startsWith('+++ ')) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (line.startsWith('@@')) {
|
||||
if (currentHunk && currentFile) currentFile.hunks.push(currentHunk);
|
||||
const hunkMatch = line.match(/@@ -(\d+)(?:,\d+)? \+(\d+)(?:,\d+)? @@/);
|
||||
oldLineNum = hunkMatch ? parseInt(hunkMatch[1], 10) : 1;
|
||||
newLineNum = hunkMatch ? parseInt(hunkMatch[2], 10) : 1;
|
||||
currentHunk = {
|
||||
header: line,
|
||||
lines: [{ type: 'header', content: line }],
|
||||
};
|
||||
continue;
|
||||
}
|
||||
|
||||
if (currentHunk) {
|
||||
// Skip trailing empty line produced by split('\n') to avoid phantom context line
|
||||
if (line === '' && i === lines.length - 1) {
|
||||
continue;
|
||||
}
|
||||
if (line.startsWith('+')) {
|
||||
currentHunk.lines.push({
|
||||
type: 'addition',
|
||||
content: line.substring(1),
|
||||
lineNumber: { new: newLineNum },
|
||||
});
|
||||
newLineNum++;
|
||||
if (currentFile) currentFile.additions++;
|
||||
} else if (line.startsWith('-')) {
|
||||
currentHunk.lines.push({
|
||||
type: 'deletion',
|
||||
content: line.substring(1),
|
||||
lineNumber: { old: oldLineNum },
|
||||
});
|
||||
oldLineNum++;
|
||||
if (currentFile) currentFile.deletions++;
|
||||
} else if (line.startsWith(' ') || line === '') {
|
||||
currentHunk.lines.push({
|
||||
type: 'context',
|
||||
content: line.substring(1) || '',
|
||||
lineNumber: { old: oldLineNum, new: newLineNum },
|
||||
});
|
||||
oldLineNum++;
|
||||
newLineNum++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (currentFile) {
|
||||
if (currentHunk) currentFile.hunks.push(currentHunk);
|
||||
files.push(currentFile);
|
||||
}
|
||||
|
||||
return files;
|
||||
}
|
||||
@@ -2259,6 +2259,17 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
};
|
||||
},
|
||||
|
||||
stageFiles: async (worktreePath: string, files: string[], operation: 'stage' | 'unstage') => {
|
||||
console.log('[Mock] Stage files:', { worktreePath, files, operation });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
operation,
|
||||
filesCount: files.length,
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
pull: async (worktreePath: string, remote?: string, stashIfNeeded?: boolean) => {
|
||||
const targetRemote = remote || 'origin';
|
||||
console.log('[Mock] Pulling latest changes for:', {
|
||||
@@ -2760,6 +2771,28 @@ function createMockWorktreeAPI(): WorktreeAPI {
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
abortOperation: async (worktreePath: string) => {
|
||||
console.log('[Mock] Abort operation:', { worktreePath });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
operation: 'merge',
|
||||
message: 'Merge aborted successfully',
|
||||
},
|
||||
};
|
||||
},
|
||||
|
||||
continueOperation: async (worktreePath: string) => {
|
||||
console.log('[Mock] Continue operation:', { worktreePath });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
operation: 'merge',
|
||||
message: 'Merge continued successfully',
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
@@ -2787,6 +2820,17 @@ function createMockGitAPI(): GitAPI {
|
||||
filePath,
|
||||
};
|
||||
},
|
||||
|
||||
stageFiles: async (projectPath: string, files: string[], operation: 'stage' | 'unstage') => {
|
||||
console.log('[Mock] Git stage files:', { projectPath, files, operation });
|
||||
return {
|
||||
success: true,
|
||||
result: {
|
||||
operation,
|
||||
filesCount: files.length,
|
||||
},
|
||||
};
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -2135,6 +2135,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
featureId,
|
||||
filePath,
|
||||
}),
|
||||
stageFiles: (worktreePath: string, files: string[], operation: 'stage' | 'unstage') =>
|
||||
this.post('/api/worktree/stage-files', { worktreePath, files, operation }),
|
||||
pull: (worktreePath: string, remote?: string, stashIfNeeded?: boolean) =>
|
||||
this.post('/api/worktree/pull', { worktreePath, remote, stashIfNeeded }),
|
||||
checkoutBranch: (worktreePath: string, branchName: string, baseBranch?: string) =>
|
||||
@@ -2232,6 +2234,10 @@ export class HttpApiClient implements ElectronAPI {
|
||||
this.post('/api/worktree/cherry-pick', { worktreePath, commitHashes, options }),
|
||||
rebase: (worktreePath: string, ontoBranch: string) =>
|
||||
this.post('/api/worktree/rebase', { worktreePath, ontoBranch }),
|
||||
abortOperation: (worktreePath: string) =>
|
||||
this.post('/api/worktree/abort-operation', { worktreePath }),
|
||||
continueOperation: (worktreePath: string) =>
|
||||
this.post('/api/worktree/continue-operation', { worktreePath }),
|
||||
getBranchCommitLog: (worktreePath: string, branchName?: string, limit?: number) =>
|
||||
this.post('/api/worktree/branch-commit-log', { worktreePath, branchName, limit }),
|
||||
getTestLogs: (worktreePath?: string, sessionId?: string): Promise<TestLogsResponse> => {
|
||||
@@ -2263,6 +2269,8 @@ export class HttpApiClient implements ElectronAPI {
|
||||
getDiffs: (projectPath: string) => this.post('/api/git/diffs', { projectPath }),
|
||||
getFileDiff: (projectPath: string, filePath: string) =>
|
||||
this.post('/api/git/file-diff', { projectPath, filePath }),
|
||||
stageFiles: (projectPath: string, files: string[], operation: 'stage' | 'unstage') =>
|
||||
this.post('/api/git/stage-files', { projectPath, files, operation }),
|
||||
};
|
||||
|
||||
// Spec Regeneration API
|
||||
|
||||
Reference in New Issue
Block a user