feat(merge): fold parent branch into tree-merge for improvement parents
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -22,6 +22,7 @@ public sealed class PlanningMergeOrchestrator
|
||||
{
|
||||
public required string TargetBranch { get; init; }
|
||||
public required Queue<string> RemainingSubtaskIds { get; init; }
|
||||
public required bool IsPlanning { get; init; }
|
||||
public string? CurrentSubtaskId { get; set; }
|
||||
}
|
||||
|
||||
@@ -43,32 +44,40 @@ public sealed class PlanningMergeOrchestrator
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task StartAsync(string planningTaskId, string targetBranch, CancellationToken ct)
|
||||
public async Task StartAsync(string parentTaskId, string targetBranch, CancellationToken ct)
|
||||
{
|
||||
string workingDir;
|
||||
List<TaskEntity> children;
|
||||
bool isPlanning;
|
||||
bool parentHasWorktree;
|
||||
|
||||
using (var ctx = _dbFactory.CreateDbContext())
|
||||
{
|
||||
var planning = await ctx.Tasks
|
||||
var parent = 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.");
|
||||
workingDir = planning.List.WorkingDir
|
||||
.SingleOrDefaultAsync(t => t.Id == parentTaskId, ct)
|
||||
?? throw new KeyNotFoundException($"Planning task '{parentTaskId}' not found.");
|
||||
workingDir = parent.List.WorkingDir
|
||||
?? throw new InvalidOperationException("List has no working directory.");
|
||||
children = planning.Children.OrderBy(c => c.SortOrder).ToList();
|
||||
children = parent.Children.OrderBy(c => c.SortOrder).ToList();
|
||||
isPlanning = parent.PlanningPhase != PlanningPhase.None;
|
||||
parentHasWorktree = parent.Worktree is { State: WorktreeState.Active };
|
||||
}
|
||||
|
||||
foreach (var c in children)
|
||||
if (isPlanning)
|
||||
{
|
||||
if (c.Status != TaskStatus.Done)
|
||||
throw new InvalidOperationException($"subtask {c.Id} is not Done (status {c.Status})");
|
||||
if (c.Worktree is null)
|
||||
throw new InvalidOperationException($"subtask {c.Id} has no worktree");
|
||||
if (c.Worktree.State != WorktreeState.Active && c.Worktree.State != WorktreeState.Merged)
|
||||
throw new InvalidOperationException(
|
||||
$"subtask {c.Id} worktree state is {c.Worktree.State}");
|
||||
foreach (var c in children)
|
||||
{
|
||||
if (c.Status != TaskStatus.Done)
|
||||
throw new InvalidOperationException($"subtask {c.Id} is not Done (status {c.Status})");
|
||||
if (c.Worktree is null)
|
||||
throw new InvalidOperationException($"subtask {c.Id} has no worktree");
|
||||
if (c.Worktree.State != WorktreeState.Active && c.Worktree.State != WorktreeState.Merged)
|
||||
throw new InvalidOperationException(
|
||||
$"subtask {c.Id} worktree state is {c.Worktree.State}");
|
||||
}
|
||||
}
|
||||
|
||||
if (await _git.IsMidMergeAsync(workingDir, ct))
|
||||
@@ -76,17 +85,22 @@ public sealed class PlanningMergeOrchestrator
|
||||
if (await _git.HasChangesAsync(workingDir, ct))
|
||||
throw new InvalidOperationException("working tree has uncommitted changes");
|
||||
|
||||
var queue = new Queue<string>(
|
||||
var idsToMerge = new List<string>();
|
||||
if (!isPlanning && parentHasWorktree)
|
||||
idsToMerge.Add(parentTaskId);
|
||||
idsToMerge.AddRange(
|
||||
children
|
||||
.Where(c => c.Worktree!.State == WorktreeState.Active)
|
||||
.Where(c => c.Status == TaskStatus.Done && c.Worktree is { State: WorktreeState.Active })
|
||||
.Select(c => c.Id));
|
||||
|
||||
var state = new State { TargetBranch = targetBranch, RemainingSubtaskIds = queue };
|
||||
if (!_states.TryAdd(planningTaskId, state))
|
||||
throw new InvalidOperationException($"Merge already in progress for {planningTaskId}.");
|
||||
var queue = new Queue<string>(idsToMerge);
|
||||
|
||||
await _broadcaster.PlanningMergeStarted(planningTaskId, targetBranch);
|
||||
await DrainAsync(planningTaskId, ct);
|
||||
var state = new State { TargetBranch = targetBranch, RemainingSubtaskIds = queue, IsPlanning = isPlanning };
|
||||
if (!_states.TryAdd(parentTaskId, state))
|
||||
throw new InvalidOperationException($"Merge already in progress for {parentTaskId}.");
|
||||
|
||||
await _broadcaster.PlanningMergeStarted(parentTaskId, targetBranch);
|
||||
await DrainAsync(parentTaskId, ct);
|
||||
}
|
||||
|
||||
public async Task ContinueAsync(string planningTaskId, CancellationToken ct)
|
||||
@@ -167,7 +181,7 @@ public sealed class PlanningMergeOrchestrator
|
||||
}
|
||||
|
||||
state.CurrentSubtaskId = null;
|
||||
await FinalizePlanningDoneAsync(planningTaskId, ct);
|
||||
await FinalizeParentDoneAsync(planningTaskId, state.IsPlanning, ct);
|
||||
await _broadcaster.PlanningCompleted(planningTaskId);
|
||||
}
|
||||
finally
|
||||
@@ -176,16 +190,20 @@ public sealed class PlanningMergeOrchestrator
|
||||
}
|
||||
}
|
||||
|
||||
private async Task FinalizePlanningDoneAsync(string planningTaskId, CancellationToken ct)
|
||||
private async Task FinalizeParentDoneAsync(string parentTaskId, bool isPlanning, CancellationToken ct)
|
||||
{
|
||||
using var ctx = _dbFactory.CreateDbContext();
|
||||
var planning = await ctx.Tasks.SingleOrDefaultAsync(t => t.Id == planningTaskId, ct);
|
||||
if (planning is null) return;
|
||||
planning.Status = TaskStatus.Done;
|
||||
planning.FinishedAt = DateTime.UtcNow;
|
||||
var parent = await ctx.Tasks.SingleOrDefaultAsync(t => t.Id == parentTaskId, ct);
|
||||
if (parent is null) return;
|
||||
parent.Status = TaskStatus.Done;
|
||||
parent.FinishedAt = DateTime.UtcNow;
|
||||
await ctx.SaveChangesAsync(ct);
|
||||
|
||||
try { await _aggregator.CleanupIntegrationBranchAsync(planningTaskId, ct); }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "integration branch cleanup failed"); }
|
||||
// Only planning builds an integration branch via the aggregator; skip cleanup otherwise.
|
||||
if (isPlanning)
|
||||
{
|
||||
try { await _aggregator.CleanupIntegrationBranchAsync(parentTaskId, ct); }
|
||||
catch (Exception ex) { _logger.LogWarning(ex, "integration branch cleanup failed"); }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user