feat: git hygiene (branch/preview reaping + cleanup sweep) and panel charts; work sessions under Git (#548)

* feat(panel): session-start, 7d overview spend, and 30d business spend charts

* feat(panel): surface work sessions as a Git page tab (route was orphaned)

* feat(git): reap spent task branches and render previews at lifecycle chokepoints; guarded stale-branch sweep

* feat(panel): stale-branch cleanup button on the Git page

* fix(git,panel): cursor-resumable sweep, force-delete spent refs, local filter state

* docs(map,rag): branch/preview reaping, cleanup sweep, git-tab work sessions, wave-2 charts

---------

Co-authored-by: Renn F <rennf93@users.noreply.github.com>
This commit is contained in:
Renzo F
2026-07-18 00:44:48 +02:00
committed by GitHub
co-authored by Renn F
parent 885d6bbe83
commit 496c24d186
39 changed files with 1841 additions and 222 deletions
@@ -485,3 +485,89 @@ async def test_self_heal_recovers_clone_root_left_on_task_branch(clone: Path) ->
assert (wt / ".git").is_file()
assert _git(wt, "rev-parse", "--abbrev-ref", "HEAD").strip() == branch
assert _git(clone, "rev-parse", "--abbrev-ref", "HEAD").strip() == "main"
# ---------------------------------------------------------------------------
# delete_local_branch — cleans up the branch ref remove_worktree leaves behind
# (worktree removal never deletes refs/heads/{branch}; every claimed task
# leaked a permanent local branch ref until this).
# ---------------------------------------------------------------------------
async def test_delete_local_branch_skips_default_branches(clone: Path) -> None:
svc = _service()
for branch in ("main", "master", "develop", ""):
await svc.delete_local_branch(clone, branch, force=True)
# "main" is the clone's actual current branch — still checked out, untouched.
assert _git(clone, "rev-parse", "--abbrev-ref", "HEAD").strip() == "main"
async def test_delete_local_branch_removes_merged_branch(clone: Path) -> None:
svc = _service()
_git(clone, "branch", "feature/merged") # no new commits -> already merged
assert _ref_exists(clone, "refs/heads/feature/merged")
await svc.delete_local_branch(clone, "feature/merged", force=False)
assert not _ref_exists(clone, "refs/heads/feature/merged")
async def test_delete_local_branch_not_merged_skips_without_force(clone: Path) -> None:
svc = _service()
_git(clone, "checkout", "-b", "feature/unmerged")
(clone / "work.txt").write_text("x")
_git(clone, "add", "work.txt")
_git(clone, "commit", "-m", "unmerged work")
_git(clone, "checkout", "main")
assert _ref_exists(clone, "refs/heads/feature/unmerged")
await svc.delete_local_branch(clone, "feature/unmerged", force=False)
# `-d` refuses an unmerged branch — a clean skip, not an error, ref stays.
assert _ref_exists(clone, "refs/heads/feature/unmerged")
async def test_delete_local_branch_force_deletes_unmerged(clone: Path) -> None:
svc = _service()
_git(clone, "checkout", "-b", "feature/unmerged")
(clone / "work.txt").write_text("x")
_git(clone, "add", "work.txt")
_git(clone, "commit", "-m", "unmerged work")
_git(clone, "checkout", "main")
assert _ref_exists(clone, "refs/heads/feature/unmerged")
await svc.delete_local_branch(clone, "feature/unmerged", force=True)
assert not _ref_exists(clone, "refs/heads/feature/unmerged")
async def test_delete_local_branch_squash_merged_needs_force(clone: Path) -> None:
# RoboCo's default merge method is SQUASH: the branch's commits are never
# ancestors of the base afterwards, so `-d` refuses even though the work
# fully landed. This is why every terminal-path caller passes force=True —
# with -d the completed-task ref would leak forever.
svc = _service()
_git(clone, "checkout", "-b", "feature/squashed")
(clone / "work.txt").write_text("x")
_git(clone, "add", "work.txt")
_git(clone, "commit", "-m", "feature work")
_git(clone, "checkout", "main")
_git(clone, "merge", "--squash", "feature/squashed")
_git(clone, "commit", "-m", "squash-merge feature")
assert (clone / "work.txt").exists() # the work landed on main
await svc.delete_local_branch(clone, "feature/squashed", force=False)
assert _ref_exists(clone, "refs/heads/feature/squashed") # -d refused
await svc.delete_local_branch(clone, "feature/squashed", force=True)
assert not _ref_exists(clone, "refs/heads/feature/squashed")
async def test_delete_local_branch_missing_branch_is_clean_skip(clone: Path) -> None:
svc = _service()
await svc.delete_local_branch(
clone, "feature/never-existed", force=False
) # no error
await svc.delete_local_branch(
clone, "feature/never-existed", force=True
) # no error