feat(merge): real conflict-hunk parsing pipeline (chunk 2 backend)

Replace the whole-file conflict model with line-level hunks, the
foundation for the full in-app merge editor.

- ConflictMarkerParser: parses git conflict markers (incl. diff3 base)
  into ordered stable/conflict MergeSegments; exact round-trip + Compose
- GitService.MergeNoFfAsync passes -c merge.conflictStyle=diff3 so the
  working tree carries the merge base in conflict markers
- TaskMergeService.GetConflictDocumentsAsync: reads each conflicted file,
  parses into segments, flags binary files
- hub GetMergeConflictDocuments + DTOs (MergeConflictDocumentsDto/
  ConflictDocumentDto/MergeSegmentDto), IWorkerClient + both fakes
- tests: 8 parser unit tests + a real-git integration test asserting
  line-level hunks with a diff3 base
This commit is contained in:
Mika Kuns
2026-06-18 16:22:56 +02:00
parent 4847c5c0a4
commit e779e13654
10 changed files with 378 additions and 1 deletions

View File

@@ -275,6 +275,9 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable, IWorkerC
public Task<MergeConflictsDto> GetMergeConflictsAsync(string taskId)
=> _hub.InvokeAsync<MergeConflictsDto>("GetMergeConflicts", taskId);
public Task<MergeConflictDocumentsDto> GetMergeConflictDocumentsAsync(string taskId)
=> _hub.InvokeAsync<MergeConflictDocumentsDto>("GetMergeConflictDocuments", taskId);
public Task WriteConflictResolutionAsync(string taskId, string path, string resolvedContent)
=> _hub.InvokeAsync("WriteConflictResolution", taskId, path, resolvedContent);
@@ -559,6 +562,9 @@ public record MergeTargetsDto(string DefaultBranch, IReadOnlyList<string> LocalB
public record MergeConflictsDto(string TaskId, IReadOnlyList<ConflictFileDto> Files);
public record ConflictFileDto(string Path, IReadOnlyList<ConflictHunkDto> Hunks);
public record ConflictHunkDto(string Ours, string Theirs, string? Base);
public record MergeConflictDocumentsDto(string TaskId, IReadOnlyList<ConflictDocumentDto> Files);
public record ConflictDocumentDto(string Path, bool IsBinary, IReadOnlyList<MergeSegmentDto> Segments);
public record MergeSegmentDto(bool IsConflict, string Text, string Ours, string? Base, string Theirs);
public sealed record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType);
public sealed record UpdateListConfigDto(string ListId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);
public sealed record UpdateTaskAgentSettingsDto(string TaskId, string? Model, string? SystemPrompt, string? AgentPath, int? MaxTurns = null);