feat(merge): fold parent branch into combined-diff for improvement parents

This commit is contained in:
mika kuns
2026-06-04 16:53:42 +02:00
parent 176b9855bf
commit 469e68bbc8

View File

@@ -43,17 +43,24 @@ public sealed class PlanningAggregator
string planningTaskId, CancellationToken ct)
{
using var ctx = _dbFactory.CreateDbContext();
var children = await ctx.Tasks
var parent = await ctx.Tasks
.Include(t => t.Worktree)
.Where(t => t.ParentTaskId == planningTaskId)
.OrderBy(t => t.SortOrder)
.ToListAsync(ct);
.Include(t => t.Children).ThenInclude(c => c.Worktree)
.SingleOrDefaultAsync(t => t.Id == planningTaskId, ct);
if (parent is null) return new List<SubtaskDiff>();
var nodes = new List<TaskEntity>();
// An improvement parent carries its own code branch — fold it in first so the
// combined diff matches what the tree-merge will produce.
if (parent.PlanningPhase == PlanningPhase.None && parent.Worktree is { State: WorktreeState.Active })
nodes.Add(parent);
nodes.AddRange(parent.Children.OrderBy(c => c.SortOrder));
var result = new List<SubtaskDiff>();
foreach (var child in children)
foreach (var node in nodes)
{
if (child.Worktree is null) continue;
var wt = child.Worktree;
if (node.Worktree is null) continue;
var wt = node.Worktree;
var head = wt.HeadCommit ?? await _git.RevParseHeadAsync(wt.Path, ct);
string unified;
try
@@ -62,11 +69,11 @@ public sealed class PlanningAggregator
}
catch (Exception ex)
{
_logger.LogWarning(ex, "diff failed for subtask {Id}", child.Id);
_logger.LogWarning(ex, "diff failed for node {Id}", node.Id);
unified = "";
}
result.Add(new SubtaskDiff(
child.Id, child.Title, wt.BranchName, wt.BaseCommit, head, wt.DiffStat, unified));
node.Id, node.Title, wt.BranchName, wt.BaseCommit, head, wt.DiffStat, unified));
}
return result;
}
@@ -87,12 +94,18 @@ public sealed class PlanningAggregator
await GitRawAsync(repoDir, ct, "checkout", "-b", integrationBranch);
foreach (var child in childSubtasks)
var nodes = new List<TaskEntity>();
// Fold the improvement parent's own branch in first (planning parents have none).
if (planning.PlanningPhase == PlanningPhase.None && planning.Worktree is { State: WorktreeState.Active })
nodes.Add(planning);
nodes.AddRange(childSubtasks);
foreach (var node in nodes)
{
if (child.Worktree is null) continue;
if (node.Worktree is null) continue;
var (code, _) = await _git.MergeNoFfAsync(
repoDir, child.Worktree.BranchName,
$"Integrate subtask: {child.Title}", ct);
repoDir, node.Worktree.BranchName,
$"Integrate: {node.Title}", ct);
if (code != 0)
{
List<string> files;
@@ -104,7 +117,7 @@ public sealed class PlanningAggregator
try { await _git.BranchDeleteAsync(repoDir, integrationBranch, force: true, ct); } catch { }
return new CombinedDiffResult.Failed(
new CombinedDiffFailure(child.Id, files));
new CombinedDiffFailure(node.Id, files));
}
}
@@ -135,6 +148,7 @@ public sealed class PlanningAggregator
using var ctx = _dbFactory.CreateDbContext();
var planning = await ctx.Tasks
.Include(t => t.List)
.Include(t => t.Worktree)
.Include(t => t.Children).ThenInclude(c => c.Worktree)
.SingleOrDefaultAsync(t => t.Id == planningTaskId, ct)
?? throw new KeyNotFoundException($"Planning task '{planningTaskId}' not found.");