Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions pstack/skills/poteto-mode/playbooks/worktree-cleanup.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
1. Snapshot and audit. Record `df -h /`, then run `scripts/worktree-audit.sh` (principle-build-the-lever). It reads paths from `git worktree list`, never hand-typed, since a hand-typed `myrepo-worktrees/x` misses one that lives at `.cursor/worktrees/myrepo/x` (principle-encode-lessons-in-structure). It classifies each worktree by size, age, merge state, uncommitted work, PR state, and the newest chat that touched it, then suggests a bucket. The transcript scan is slow, so background it.
2. The bucket is advice, not permission. The pinned and active chats are the real artifact (principle-prove-it-works). Get that set from the user or sidebar and cross-check every candidate. The lever has marked `safe` a worktree the user had pinned, so the pinned set wins.
3. Verify usage before deleting. For every `verify-recent-chat` row, or anything you doubt, fan subagents out to read the transcripts and report whether the chat is pinned or ongoing and which worktrees it touches (principle-guard-the-context-window, transcripts are bulk). A pinned chat spawns arena and repro trees into sibling worktrees via background subagents, and those are in use even when their names never hit the sidebar.
4. Pause on irreversible loss. `wip:N` is N tracked uncommitted edits. Show the diff and get a decision first, since removing a clean worktree is recoverable from its branch but uncommitted work is gone. `scratch:N` is untracked throwaway, safe to drop, but name the files. Per Autonomy, clean and merged and not-in-use proceeds. `wip` and in-use pause.
5. Prune the confirmed set. Per path, `git worktree remove --force <path>`. If the dir survives on ignored build artifacts, `rm -rf` it, then `git worktree prune`. Branch refs survive, so no commits are lost. Confirm with `df -h /` and re-list.
4. Pause on irreversible loss. `wip:N` counts tracked, untracked, and ignored paths. For each candidate, run `git -C <worktree> status --porcelain=v1 --untracked-files=all --ignored=matching`; show diffs for tracked edits and list untracked and ignored paths without opening their contents. Default to preserving every worktree with `wip`. Remove one only after the user explicitly approves every path that its removal will delete. Never treat untracked or ignored files as disposable scratch.
5. Prune only the fully approved set. `git worktree remove <path>` can delete ignored-only files without `--force`, so the command is not a preservation check. Re-run the step 4 status preflight immediately before removal; if any path lacks explicit approval, retain the entire worktree. If Git refuses, stop and retain it; do not use `rm -rf` or `git worktree prune` to bypass the refusal. Branch refs survive, so no commits are lost. Confirm with `df -h /` and re-list.
6. Simulators and other reclaimers. Simulators are usually the next-biggest win. `xcrun simctl --set testing delete all` (XCTestDevices clones), `xcrun simctl delete unavailable`, and `xcrun simctl runtime list` then `runtime delete <id>` for old runtimes. More when needed: Xcode `DerivedData` and `iOS DeviceSupport`, `~/Library/Application Support/Cursor` (`state.vscdb.backup`, and `snapshots/roots/<root>` where a `<root>` named for a folder you opened as a workspace balloons), package caches (pnpm, uv, brew, yarn). Clear only caches the user has not said to keep.

This is the one playbook that deletes user state with no code review to catch a slip, so the gates above are the review.
Expand Down
8 changes: 3 additions & 5 deletions pstack/skills/poteto-mode/scripts/worktree-audit.sh
Original file line number Diff line number Diff line change
Expand Up @@ -41,12 +41,10 @@ git worktree list --porcelain | awk '/^worktree /{print $2}' | while read -r wt;
# real signal; merge-base only catches fast-forward/rebase merges.
git merge-base --is-ancestor "$head" origin/main 2>/dev/null && merged=YES || merged=no

# Distinguish real WIP (tracked edits) from disposable untracked scratch.
porcelain=$(git -C "$wt" status --porcelain 2>/dev/null)
# Treat tracked, untracked, and ignored paths as work that must be held.
porcelain=$(git -C "$wt" status --porcelain --untracked-files=all --ignored=matching 2>/dev/null)
if [ -z "$porcelain" ]; then dirty=clean
elif printf '%s\n' "$porcelain" | grep -qv '^??'; then
dirty="wip:$(printf '%s\n' "$porcelain" | grep -cv '^??')"
else dirty="scratch:$(printf '%s\n' "$porcelain" | grep -c '^??')"; fi
else dirty="wip:$(printf '%s\n' "$porcelain" | wc -l | tr -d '[:space:]')"; fi

branch=$(git -C "$wt" symbolic-ref --quiet --short HEAD 2>/dev/null || echo "")
if [ -z "$branch" ]; then remote=detached
Expand Down