From 046da0fd811d79b8d290f3a01a073323f544a5e2 Mon Sep 17 00:00:00 2001 From: mika kuns Date: Tue, 19 May 2026 09:37:29 +0200 Subject: [PATCH] feat(hub): expose worktree overview, state mutation, force-remove Co-Authored-By: Claude Sonnet 4.6 --- src/ClaudeDo.Worker/Hub/WorkerHub.cs | 46 ++++++++++++++++++++++++++-- 1 file changed, 44 insertions(+), 2 deletions(-) diff --git a/src/ClaudeDo.Worker/Hub/WorkerHub.cs b/src/ClaudeDo.Worker/Hub/WorkerHub.cs index d7ed772..ef97412 100644 --- a/src/ClaudeDo.Worker/Hub/WorkerHub.cs +++ b/src/ClaudeDo.Worker/Hub/WorkerHub.cs @@ -29,6 +29,21 @@ public record AppSettingsDto( public record WorktreeCleanupDto(int Removed); public record WorktreeResetDto(int Removed, int TasksAffected, bool Blocked, int RunningTasks); + +public record WorktreeOverviewDto( + string TaskId, + string TaskTitle, + ClaudeDo.Data.Models.TaskStatus TaskStatus, + string ListId, + string ListName, + string Path, + string BranchName, + WorktreeState State, + string? DiffStat, + DateTime CreatedAt, + bool PathExistsOnDisk); + +public record ForceRemoveResultDto(bool Removed, string? Reason); public record MergeResultDto(string Status, IReadOnlyList ConflictFiles, string? ErrorMessage); public record MergeTargetsDto(string DefaultBranch, IReadOnlyList LocalBranches); public record UpdateListDto(string Id, string Name, string? WorkingDir, string DefaultCommitType); @@ -220,9 +235,9 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub }); } - public async Task CleanupFinishedWorktrees() + public async Task CleanupFinishedWorktrees(string? listId = null) { - var result = await _wtMaintenance.CleanupFinishedAsync(); + var result = await _wtMaintenance.CleanupFinishedAsync(listId, Context.ConnectionAborted); return new WorktreeCleanupDto(result.Removed); } @@ -232,6 +247,33 @@ public sealed class WorkerHub : Microsoft.AspNetCore.SignalR.Hub return new WorktreeResetDto(result.Removed, result.TasksAffected, result.Blocked, result.RunningTasks); } + public async Task> GetWorktreesOverview(string? listId) + { + var rows = await _wtMaintenance.GetOverviewAsync(listId, Context.ConnectionAborted); + return rows.Select(r => new WorktreeOverviewDto( + r.TaskId, r.TaskTitle, r.TaskStatus, r.ListId, r.ListName, + r.Path, r.BranchName, r.State, r.DiffStat, r.CreatedAt, r.PathExistsOnDisk)).ToList(); + } + + public async Task SetWorktreeState(string taskId, WorktreeState newState) + { + using var ctx = _dbFactory.CreateDbContext(); + var repo = new WorktreeRepository(ctx); + var existing = await repo.GetByTaskIdAsync(taskId, Context.ConnectionAborted); + if (existing is null) throw new HubException("worktree not found"); + await repo.SetStateAsync(taskId, newState, Context.ConnectionAborted); + await _broadcaster.WorktreeUpdated(taskId); + return true; + } + + public async Task ForceRemoveWorktree(string taskId) + { + var result = await _wtMaintenance.ForceRemoveAsync(taskId, Context.ConnectionAborted); + if (result.Removed) + await _broadcaster.WorktreeUpdated(taskId); + return new ForceRemoveResultDto(result.Removed, result.Reason); + } + public async Task MergeTask( string taskId, string targetBranch, bool removeWorktree, string commitMessage) {