diff --git a/src/ClaudeDo.Worker/Planning/PlanningAggregator.cs b/src/ClaudeDo.Worker/Planning/PlanningAggregator.cs index 3938cdc..314b4e9 100644 --- a/src/ClaudeDo.Worker/Planning/PlanningAggregator.cs +++ b/src/ClaudeDo.Worker/Planning/PlanningAggregator.cs @@ -112,6 +112,23 @@ public sealed class PlanningAggregator return new CombinedDiffResult.Ok(new CombinedDiffSuccess(integrationBranch, unifiedDiff)); } + public async Task CleanupIntegrationBranchAsync(string planningTaskId, CancellationToken ct) + { + var (planning, repoDir, _) = await LoadPlanningContextAsync(planningTaskId, ct); + var branch = BuildIntegrationBranchName(planning); + + var current = await _git.GetCurrentBranchAsync(repoDir, ct); + if (string.Equals(current, branch, StringComparison.Ordinal)) + { + var branches = await _git.ListLocalBranchesAsync(repoDir, ct); + var target = branches.FirstOrDefault(b => b != branch && !b.StartsWith("claudedo/", StringComparison.Ordinal)) ?? "main"; + await _git.CheckoutBranchAsync(repoDir, target, ct); + } + + try { await _git.BranchDeleteAsync(repoDir, branch, force: true, ct); } + catch { /* already gone — idempotent */ } + } + private async Task<(TaskEntity planning, string repoDir, IReadOnlyList children)> LoadPlanningContextAsync(string planningTaskId, CancellationToken ct) { diff --git a/tests/ClaudeDo.Worker.Tests/Planning/PlanningAggregatorTests.cs b/tests/ClaudeDo.Worker.Tests/Planning/PlanningAggregatorTests.cs index d762f49..b125855 100644 --- a/tests/ClaudeDo.Worker.Tests/Planning/PlanningAggregatorTests.cs +++ b/tests/ClaudeDo.Worker.Tests/Planning/PlanningAggregatorTests.cs @@ -194,6 +194,26 @@ public class PlanningAggregatorTests : IDisposable return (parentId, subA, subB); } + [Fact] + public async Task CleanupIntegrationBranchAsync_RemovesBranchIfPresent() + { + var db = NewDb(); + var repo = NewRepo(); + GitRepoFixture.RunGit(repo.RepoDir, "branch", "-m", "main"); + var (parentId, _, _) = await SeedPlanningWithTwoChildrenAsync(db, repo); + + var git = new GitService(); + var svc = new PlanningAggregator(db.CreateFactory(), git, NullLogger.Instance); + + var built = await svc.BuildIntegrationBranchAsync(parentId, "main", CancellationToken.None); + var ok = Assert.IsType(built); + + await svc.CleanupIntegrationBranchAsync(parentId, CancellationToken.None); + + var branches = await git.ListLocalBranchesAsync(repo.RepoDir, CancellationToken.None); + Assert.DoesNotContain(branches, b => b == ok.Value.IntegrationBranch); + } + private void SeedWorktreeWithFile(ClaudeDoDbContext ctx, GitRepoFixture repo, string taskId, string filename, string content) { var wtPath = Path.Combine(Path.GetTempPath(), $"wt_{Guid.NewGuid():N}"); diff --git a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs index 51d2bee..281c495 100644 --- a/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs +++ b/tests/ClaudeDo.Worker.Tests/UiVm/TasksIslandViewModelPlanningTests.cs @@ -19,6 +19,10 @@ sealed class FakeWorkerClient : IWorkerClient public int FinalizePlanningCalls { get; private set; } public int WakeQueueCalls { get; private set; } + public bool IsConnected => false; + public event System.ComponentModel.PropertyChangedEventHandler? PropertyChanged; + public event Action? TaskStartedEvent; + public event Action? TaskFinishedEvent; public event Action? TaskUpdatedEvent; public event Action? WorktreeUpdatedEvent; public event Action? TaskMessageEvent; @@ -26,6 +30,13 @@ sealed class FakeWorkerClient : IWorkerClient public void RaiseWorktreeUpdated(string taskId) => WorktreeUpdatedEvent?.Invoke(taskId); public void RaiseTaskMessage(string taskId, string line) => TaskMessageEvent?.Invoke(taskId, line); + public Task RunNowAsync(string taskId) => Task.CompletedTask; + public Task ContinueTaskAsync(string taskId, string followUpPrompt) => Task.CompletedTask; + public Task ResetTaskAsync(string taskId) => Task.CompletedTask; + public Task CancelTaskAsync(string taskId) => Task.CompletedTask; + public Task> GetAgentsAsync() => Task.FromResult(new List()); + public Task GetListConfigAsync(string listId) => Task.FromResult(null); + public Task UpdateTaskAgentSettingsAsync(UpdateTaskAgentSettingsDto dto) => Task.CompletedTask; public Task WakeQueueAsync() { WakeQueueCalls++; return Task.CompletedTask; } public Task StartPlanningSessionAsync(string taskId, CancellationToken ct = default) { StartPlanningCalls++; return Task.CompletedTask; } public Task ResumePlanningSessionAsync(string taskId, CancellationToken ct = default) { ResumePlanningCalls++; return Task.CompletedTask; }