From d2f7c19f1324ac9af379471c46a73a9760ad5178 Mon Sep 17 00:00:00 2001 From: Kevin Veen-Birkenbach Date: Sat, 31 Oct 2026 17:30:24 +0100 Subject: [PATCH] feat(branch-gc): remove merged branches and worktrees through pkgmgr branch gc after the operator's pick Co-Authored-By: Claude Opus 5.5 (1M context) --- README.md | 3 ++ skills/branch-gc/SKILL.md | 104 ++++++++++++++++++++++++++++++++++++++ skills/cleanup/SKILL.md | 3 +- 3 files changed, 109 insertions(+), 1 deletion(-) create mode 100644 skills/branch-gc/SKILL.md diff --git a/README.md b/README.md index f5c3447..1d64801 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,7 @@ flowchart LR coordinate[coordinate] funnel[funnel] cleanup[cleanup] + branch_gc[branch-gc] subagent_model[subagent-model] end subgraph ci [CI] @@ -97,6 +98,8 @@ flowchart LR coordinate --> democracy cleanup --> democracy + cleanup --> branch_gc + branch_gc --> democracy funnel --> democracy funnel --> dialectic funnel --> subagent_model diff --git a/skills/branch-gc/SKILL.md b/skills/branch-gc/SKILL.md new file mode 100644 index 0000000..9478eb1 --- /dev/null +++ b/skills/branch-gc/SKILL.md @@ -0,0 +1,104 @@ +--- +name: branch-gc +description: > + Remove merged branches and worktrees with `pkgmgr branch gc`: dry run, + operator picks from the plan, then apply; unmerged items only one by one. + Trigger on /branch-gc, "branches aufräumen", "worktrees aufräumen", + "garbage collect". +--- + +`pkgmgr branch gc` decides what is merged and does the removing; this skill +decides what may go and who has to agree first. The dry run changes nothing, +and nothing is removed before the operator's pick in this run. + +## Step 0: claim and chair + +1. Claim `work/branch-gc-`, or `work/branch-gc-all` for every + repository, through the `democracy` skill. +2. The apply takes stacks down and ends with a host-wide + `docker image prune -f` and `docker builder prune -f`, which reach every + session's images and build cache. When another session holds the chair, + send it what, where and how long, and wait for the grant; the prune never + runs during a peer's build. + +## Step 1: dry run + +`pkgmgr branch gc --json`, with `--all` only when the operator asked for every +repository, output to `/tmp/branch-gc-.json`. No `git fetch` of your own +before it: gc fetches without auto-maintenance, while a plain fetch on git +2.50 or later may prune the sandbox's bind-mount stubs. Every repository's +`warnings` (a failed fetch) go to the top of the plan. Sort the items: + +- **Candidates**: `state` `merged-clean`, non-empty `actions`, and `skip` + null or `lock: liveness unknown`. +- **Step 5**: `state` `unmerged` or `dirty`. +- **Reported only**: `protected`, every other `skip`, and `stale` as one count + per repository. + +Never override a state: a branch without own commits is `unmerged` even +though git sees its tip on the default branch, because it is fresh work. + +`host_pid_namespace: false` means the dry run ran inside a sandbox: it saw no +processes and could not tell whether a lock is live. Say so above the plan; +the apply re-checks both on the host. + +## Step 2: claims + +Read `council.py list`. For every candidate whose branch or worktree a peer's +claim names, send that peer one message (Peer protocol) and wait up to 5 +minutes. A kept or unanswered item leaves the plan as `keep ()`; +silence is not consent. + +## Step 3: plan + +One table: + +| Repo | Branch or worktree | Merged by | Actions | Note | + +Then ask with `AskUserQuestion`: + +- **Call 1** (`multiSelect: true`): the candidates, grouped by repository, + nothing preselected. Each description names `merged_by` and every action + (stack with volumes, the `images`, worktree, local and remote branches), and + `lock checked at apply` where liveness was unknown. +- **Call 2** (`multiSelect: false`): `Apply: items (Recommended)`, + `Revise the selection`, `Abort`. Revise repeats call 1. + +## Step 4: apply + +1. Re-read `council.py list` and drop every item claimed since the dry run. +2. `--apply` refuses outside the host PID namespace (exit 2) and deletes + remote branches, which is a push. Unless this session runs in the host PID + namespace and the project lets agents push, hand the operator one command + per repository, run from inside it, one `--branch` per picked item (the + worktree path for a detached worktree): + + ```bash + pkgmgr branch gc --apply --json --branch --branch | tee /tmp/branch-gc-apply-.json + ``` + +3. Read every item's `result` in that file: `done`, `done; image kept: ` + (still in use elsewhere), or `failed: `. + Report a failure as it stands; never remove by hand what gc skipped or + refused. + +## Step 5: unmerged and dirty + +Never part of the apply. One `AskUserQuestion` question per item +(`multiSelect: false`): `Keep (Recommended)` and `Delete`, the description +naming what is lost (the item's `reason`: unique commits, dirty paths). For +each `Delete`, hand the operator +`pkgmgr branch purge --branch `: it asks y/N per item and answers no +without a terminal, so it runs only in the operator's shell. + +## Step 6: report + +Removed, kept and skipped items, each with its reason, and +`docker system df` before and after. Report the release to the chair, tell +each peer whose line an item touched what went, and release the claim. + +## Rules + +- A pick from an earlier run does not carry over. +- Never widen the selection, and never route around a refusal through another + command or a peer. diff --git a/skills/cleanup/SKILL.md b/skills/cleanup/SKILL.md index 5207158..f3d0168 100644 --- a/skills/cleanup/SKILL.md +++ b/skills/cleanup/SKILL.md @@ -58,7 +58,8 @@ Read-only, in parallel, sizes included: label, anonymous ones included. - **Other agent dirs**: package and tool caches (pip, npm, pre-commit, act, gh, buildx), worktrees with `git status --short` and whether their branch is - merged or pushed, Ansible and build temp dirs. + merged or pushed, Ansible and build temp dirs. With `pkgmgr` installed, + merged branches and worktrees go to the `branch-gc` skill instead. - **Live use**: open handles (`fuser`, `lsof`) and running processes by name only (`ps -o pid,etime,comm`). Never print full command lines or environments: process arguments carry credentials. A sandboxed agent often