feat(mcp): expose the conflict resolver's hunk API over MCP

get_merge_conflicts and resolve_conflict_hunk let an MCP caller resolve a
leaveConflictsInTree merge hunk-by-hunk (ours/theirs/base/literal text)
instead of grep-parsing diff3 markers itself. Reuses the existing
TaskMergeService/ConflictMarkerParser machinery (adds MergeSegment.StartLine
and a non-staging TaskMergeService.WriteConflictFileAsync) rather than a
second parser. A partially resolved file is written without git add so
continue_merge keeps refusing while any hunk still has markers.
This commit is contained in:
mika kuns
2026-08-10 13:49:07 +02:00
parent 6a2a19cc9e
commit 5653daca6e
6 changed files with 335 additions and 6 deletions
@@ -488,6 +488,24 @@ public sealed class TaskMergeService
await _git.AddPathAsync(list.WorkingDir, path, ct);
}
/// <summary>
/// Writes a conflicted file's content without staging it. Unlike <see cref="WriteResolutionAsync"/> this must
/// NOT run `git add` — that marks the path resolved in git's index regardless of its content, so a partially
/// resolved hunk (markers still present elsewhere in the file) would drop out of
/// <see cref="GetConflictDocumentsAsync"/>'s conflicted-file list and <see cref="ContinueMergeAsync"/>'s
/// pre-stage marker scan, letting a still-conflicted file slip into a commit. Staging happens once, for
/// everything, inside <see cref="ContinueMergeAsync"/>.
/// </summary>
public async Task WriteConflictFileAsync(string taskId, string path, string content, CancellationToken ct)
{
var (_, list, _, _) = await LoadMergeContextAsync(taskId, ct);
if (string.IsNullOrWhiteSpace(list.WorkingDir))
throw new InvalidOperationException("list has no working directory");
var full = Path.Combine(list.WorkingDir, path.Replace('/', Path.DirectorySeparatorChar));
await File.WriteAllTextAsync(full, content, ct);
}
public async Task<MergeTargets> GetTargetsAsync(string taskId, CancellationToken ct)
{
var (_, list, _, _) = await LoadMergeContextAsync(taskId, ct);