fix(work-task): refuse to delete a worktree that still holds uncommitted work
Removing a task's worktree ran `git worktree remove --force` with nothing between the click and the removal to look at what was inside it. An agent that edited files without committing them — or a user who edited them by hand — lost that work outright, and nothing on the way in ever named it: the cancel dialog's checkbox offers "its worktree and work branch", the card's button says "Delete worktree", and neither mentions files left unstaged. A stop pressed mid-edit is exactly when a checkout is dirty, so the likeliest case was the losing one. The other two surfaces that remove a worktree already draw this line — `complete_task` refuses with the same sentence, and the branch dropdown re-asks under an explicit "those changes cannot be recovered". `cleanup_task` now checks under the task lock, before the removal, and leaves the worktree standing while it still holds uncommitted files. Committed work stays deletable: `branch -D` is what the checkbox spells out and the reflog still holds those commits. The probe behind the check fails CLOSED — a git error means the directory could not be PROVEN safe to destroy, and the one reading of "cannot ask" that is safe is the checkout already being off disk. Three call sites carried their own fail-open copy of that question and now share the probe; the reason clause they print is composed in one place too, so the card, the toast and the timeline cannot drift into describing the same condition differently. The refusal is both recorded on the row and returned, because the callers hear different channels. The direct button surfaces the error as a toast. `work_task_cancel_core` swallows it and the card's `cleanup_state` carries the reason instead — turning it into an error would report a stop that already happened as failed. `work_task_delete_core` stops on it: the tombstone it would otherwise write takes the card, its `cleanup_state` and its retry entry in one write, stranding a directory the user was told would be deleted with nothing left anywhere to say why. `CleanupBlocked` is what lets that path tell a refusal from a cleanup that merely failed, which it may still proceed over.
X
xintaofei committed
dde7e213bbe205011200a9a99cf5847781820f17
Parent: a5bebe1