fix(claude-do): merge preview_merge_set: don't report a symbol as removed when the

ClaudeDo-Task: 4882c459-374c-4afe-a60d-2b25b8cce407
This commit is contained in:
mika kuns
2026-10-01 13:32:48 +02:00
2 changed files with 91 additions and 8 deletions
+50 -8
View File
@@ -744,7 +744,7 @@ public sealed class MergeMcpTools
// One line of a unified diff, tagged with the file it belongs to ('-'/'+' only; context lines
// are dropped). File is set from the `diff --git a/<path> b/<path>` header, which (unlike
// `+++ b/...`) is present and names the real path even for a deleted file (`+++ /dev/null`).
private readonly record struct DiffLine(string? File, char Sign, string Text);
internal readonly record struct DiffLine(string? File, char Sign, string Text);
private static List<DiffLine> ParseDiffLines(string diff)
{
@@ -783,22 +783,64 @@ public sealed class MergeMcpTools
@"|[\w<>\[\],.?]+\??\s+(?<name>[A-Za-z_]\w*)\s*(?:\(|\{|=>|;|=))",
RegexOptions.Compiled);
private static List<RemovedSymbolDto> ExtractRemovedSymbols(List<DiffLine> diffLines)
private static bool IsScannedFile(string? file)
=> file is not null &&
!file.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase) &&
!file.EndsWith(".json", StringComparison.OrdinalIgnoreCase);
// Names this diff declares on '+' lines, per file. A [ObservableProperty] field `_camelName` /
// `camelName` generates `CamelName`, so it counts as declaring that; the attribute may sit on its
// own '+' line(s) directly above the field.
private static HashSet<(string Name, string File)> ExtractAddedDeclarations(List<DiffLine> diffLines)
{
var added = new HashSet<(string Name, string File)>();
var observablePending = false;
foreach (var line in diffLines)
{
if (line.Sign != '+' || !IsScannedFile(line.File)) { observablePending = false; continue; }
var trimmed = line.Text.Trim();
var isObservable = trimmed.Contains("[ObservableProperty", StringComparison.Ordinal);
if (trimmed.StartsWith('[') && trimmed.EndsWith(']'))
{
observablePending |= isObservable;
continue;
}
var match = RemovedDeclarationRegex.Match(line.Text);
if (match.Success)
{
var name = match.Groups["name"].Value;
added.Add((name, line.File!));
if (isObservable || observablePending)
{
var bare = name.TrimStart('_');
if (bare.Length > 0)
added.Add((char.ToUpperInvariant(bare[0]) + bare[1..], line.File!));
}
}
observablePending = false;
}
return added;
}
internal static List<RemovedSymbolDto> ExtractRemovedSymbols(List<DiffLine> diffLines)
{
var added = ExtractAddedDeclarations(diffLines);
var seen = new HashSet<(string Name, string File)>();
var result = new List<RemovedSymbolDto>();
foreach (var line in diffLines)
{
if (line.Sign != '-' || line.File is null) continue;
if (line.File.EndsWith(".axaml", StringComparison.OrdinalIgnoreCase) ||
line.File.EndsWith(".json", StringComparison.OrdinalIgnoreCase))
continue;
if (line.Sign != '-' || !IsScannedFile(line.File)) continue;
var match = RemovedDeclarationRegex.Match(line.Text);
if (!match.Success) continue;
if (seen.Add((match.Groups["name"].Value, line.File)))
result.Add(new RemovedSymbolDto(match.Groups["name"].Value, line.File));
var key = (match.Groups["name"].Value, line.File!);
if (added.Contains(key)) continue;
if (seen.Add(key))
result.Add(new RemovedSymbolDto(key.Item1, key.Item2));
}
return result;
}