#!/usr/bin/env bash # git-worktrees-branches-agent-sessions.sh — one worktree and one branch per agent task. # # What it does: # new creates .worktrees/ on a new branch agent/ from the current HEAD, # copies any gitignored files listed in .worktreeinclude (one path per line, # same idea as Claude Code's .worktreeinclude) into it, and prints the commands # to start an agent there. # list shows every worktree and whether it has uncommitted changes. # clean removes worktrees under .worktrees/ that are clean (no changes, no untracked # files) and lists the ones it kept because they still hold work. # # Run from the main checkout: bash git-worktrees-branches-agent-sessions.sh new fix-parser # Requires git 2.5+ (worktree support). Tested with bash on Windows (Git Bash) and Linux. set -euo pipefail ROOT="$(git rev-parse --show-toplevel 2>/dev/null)" || { echo "not inside a git repository" >&2; exit 2; } WT_DIR="$ROOT/.worktrees" usage() { sed -n '2,15p' "$0"; exit 2; } cmd_new() { local task="${1:-}"; [[ -n "$task" ]] || usage local path="$WT_DIR/$task" branch="agent/$task" mkdir -p "$WT_DIR" grep -qx '.worktrees/' "$ROOT/.gitignore" 2>/dev/null || echo '.worktrees/' >> "$ROOT/.gitignore" git -C "$ROOT" worktree add -b "$branch" "$path" HEAD if [[ -f "$ROOT/.worktreeinclude" ]]; then while IFS= read -r rel; do [[ -z "$rel" || "$rel" == \#* ]] && continue if [[ -e "$ROOT/$rel" ]] && git -C "$ROOT" check-ignore -q "$rel"; then mkdir -p "$path/$(dirname "$rel")" cp -r "$ROOT/$rel" "$path/$rel" echo "seeded $rel" fi done < "$ROOT/.worktreeinclude" fi echo echo "worktree ready: $path (branch $branch)" echo "start an agent there with one of:" echo " cd \"$path\" && claude" echo " cd \"$path\" && codex --sandbox workspace-write" echo "when it is green, review the diff from the main checkout:" echo " git diff HEAD...$branch" } cmd_list() { git -C "$ROOT" worktree list --porcelain | awk '/^worktree /{print $2}' | while read -r wt; do local status status="$(git -C "$wt" status --porcelain 2>/dev/null | wc -l | tr -d ' ')" local branch branch="$(git -C "$wt" rev-parse --abbrev-ref HEAD 2>/dev/null)" printf '%-8s %-24s %s\n' "$([[ "$status" == 0 ]] && echo clean || echo "dirty($status)")" "$branch" "$wt" done } cmd_clean() { local kept=0 removed=0 [[ -d "$WT_DIR" ]] || { echo "no .worktrees/ directory"; return 0; } for wt in "$WT_DIR"/*/; do [[ -d "$wt" ]] || continue wt="${wt%/}" if [[ -n "$(git -C "$wt" status --porcelain 2>/dev/null)" ]]; then echo "kept $wt (uncommitted changes or untracked files)" kept=$((kept + 1)) else git -C "$ROOT" worktree remove "$wt" echo "removed $wt" removed=$((removed + 1)) fi done git -C "$ROOT" worktree prune echo "removed $removed, kept $kept; branches were left in place (delete with: git branch -d agent/)" } case "${1:-}" in new) shift; cmd_new "$@" ;; list) cmd_list ;; clean) cmd_clean ;; *) usage ;; esac