feat(worker): preview_merge_set flags removed symbols and locale-key overlaps

Overlap detection was file-name-only: 9/15 tasks touching en.json/de.json in
run #316 all got flagged even though only one pair actually collided on a
key, and a removed method with no textual file conflict (#297 vs #286) went
unflagged entirely.

- removedSymbols per task entry: a line-pattern scan of `-` diff lines for
  C# method/property/field/type/event declarations (skips .axaml/.json).
- symbolWarnings at the set level: cross-references those names against
  every other task's `+` lines, catching same-symbol collisions across
  files that overlaps/subsets (both purely file-based) can't see.
- overlaps narrows to key-path level for .json files (base commit vs. task
  branch head, both sides parsed and flattened) so two tasks adding
  different locale keys to the same file no longer collide; unparsable
  JSON on either side falls back to the old file-level check.
- GitService.ShowFileAsync: `git show ref:path`, used to read a task
  branch's json content at its base/head commit.

Out of scope per the task: no Roslyn/parser, no AXAML analysis, and the
merge-helper prompt in PromptFiles.cs doesn't mention the new fields yet.

Build: dotnet build src/ClaudeDo.App (pulls in Ui+Data) + src/ClaudeDo.Worker, both clean.
Tests: dotnet test tests/ClaudeDo.Worker.Tests --filter "FullyQualifiedName~MergeMcpTools|FullyQualifiedName~ExternalMcpToolSchema" -- 51 passed, 0 failed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Mika Kuns
2026-09-16 15:43:38 +02:00
committed by mika kuns
co-authored by Claude Sonnet 5
parent 1325ab9438
commit 684b0115e5
3 changed files with 442 additions and 35 deletions
+10
View File
@@ -558,6 +558,16 @@ public sealed class GitService
return exitCode == 0 ? stdout : null;
}
/// <summary>
/// Content of <paramref name="path"/> at <paramref name="revision"/> via `git show revision:path`.
/// Null when the path doesn't exist at that revision (new, deleted, or renamed file).
/// </summary>
public async Task<string?> ShowFileAsync(string repoDir, string revision, string path, CancellationToken ct = default)
{
var (exitCode, stdout, _) = await RunGitAsync(repoDir, ["show", $"{revision}:{path}"], ct, trimOutput: false);
return exitCode == 0 ? stdout : null;
}
/// <summary>
/// Non-destructive mergeability probe via `git merge-tree --write-tree`. Writes only
/// loose objects — the working tree, index, and refs are left untouched.