From 30d76537d1ce2ff7c5addec10f13135e38d21d36 Mon Sep 17 00:00:00 2001 From: giles Date: Fri, 24 Apr 2026 16:50:27 +0000 Subject: [PATCH] sx-loops: each language runs in its own git worktree Previous version ran all 7 claude sessions in the main working tree on branch 'architecture'. That would race on git operations and cross- contaminate commits between languages even though their file scopes don't overlap. Now each session runs in /root/rose-ash-loops/ on branch loops/, created from the current architecture HEAD. sx-loops-down.sh gains --clean to remove the worktrees; loops/ branches stay unless explicitly deleted. Also: second Enter keystroke after the /loop command, since Claude's input box sometimes interprets the first newline as a soft break. --- scripts/sx-loops-down.sh | 50 +++++++++++++++++++--------- scripts/sx-loops-up.sh | 72 ++++++++++++++++++++++++++++------------ 2 files changed, 86 insertions(+), 36 deletions(-) diff --git a/scripts/sx-loops-down.sh b/scripts/sx-loops-down.sh index 10c2fb49..f9c9fdc0 100755 --- a/scripts/sx-loops-down.sh +++ b/scripts/sx-loops-down.sh @@ -1,23 +1,43 @@ #!/usr/bin/env bash -# Stop the sx-loops tmux session. Prompts the claude sessions to exit cleanly -# first (/exit in each window), then kills the tmux session after a grace -# period. Loops stop immediately; in-progress iterations commit what they have. +# Stop the sx-loops tmux session. Optionally (--clean) also remove the +# per-language git worktrees. Loops stop immediately; in-progress iterations +# commit whatever they have; nothing is pushed that wasn't already. set -euo pipefail SESSION="sx-loops" +WORKTREE_BASE="/root/rose-ash-loops" +CLEAN=0 -if ! tmux has-session -t "$SESSION" 2>/dev/null; then - echo "No sx-loops session running." - exit 0 -fi - -WINDOWS=$(tmux list-windows -t "$SESSION" -F '#W') -for w in $WINDOWS; do - tmux send-keys -t "$SESSION:$w" "/exit" C-m 2>/dev/null || true +for arg in "$@"; do + case "$arg" in + --clean) CLEAN=1 ;; + *) echo "Unknown arg: $arg"; exit 2 ;; + esac done -echo "Sent /exit to all windows. Waiting 5s for clean shutdown..." -sleep 5 +if tmux has-session -t "$SESSION" 2>/dev/null; then + WINDOWS=$(tmux list-windows -t "$SESSION" -F '#W') + for w in $WINDOWS; do + tmux send-keys -t "$SESSION:$w" "/exit" C-m 2>/dev/null || true + done + echo "Sent /exit to all windows. Waiting 5s for clean shutdown..." + sleep 5 + tmux kill-session -t "$SESSION" + echo "Killed tmux session '$SESSION'." +else + echo "No sx-loops tmux session running." +fi -tmux kill-session -t "$SESSION" -echo "Killed sx-loops session." +if [ "$CLEAN" = "1" ]; then + cd "$(dirname "$0")/.." + for lang in lua prolog forth erlang haskell js hs; do + wt="$WORKTREE_BASE/$lang" + if [ -d "$wt" ]; then + git worktree remove --force "$wt" 2>/dev/null || rm -rf "$wt" + echo "Removed worktree: $wt" + fi + done + git worktree prune + echo "Worktree branches (loops/) are preserved. Delete manually if desired:" + echo " git branch -D loops/lua loops/prolog loops/forth loops/erlang loops/haskell loops/js loops/hs" +fi diff --git a/scripts/sx-loops-up.sh b/scripts/sx-loops-up.sh index c45d53a7..6a517aca 100755 --- a/scripts/sx-loops-up.sh +++ b/scripts/sx-loops-up.sh @@ -1,25 +1,32 @@ #!/usr/bin/env bash # Spawn 7 claude sessions in tmux, one per language loop. -# Each runs /loop against its briefing, doing ONE iteration per fire. +# Each runs in its own git worktree rooted at /root/rose-ash-loops/, +# on branch loops/. No two loops share a working tree, so there's +# zero risk of file collisions between languages. # # Usage: ./scripts/sx-loops-up.sh [interval] # interval defaults to self-paced (omit to let model decide) # # After the script prints done: # tmux a -t sx-loops -# Ctrl-B + to switch -# Ctrl-B + d to detach (loops keep running) +# Ctrl-B + to switch (0=lua ... 6=hs) +# Ctrl-B + d to detach (loops keep running, SSH-safe) +# +# Stop: ./scripts/sx-loops-down.sh +# Wipe worktrees too: ./scripts/sx-loops-down.sh --clean set -euo pipefail -cd "$(dirname "$0")/.." +ROOT="$(cd "$(dirname "$0")/.." && pwd)" +cd "$ROOT" SESSION="sx-loops" +WORKTREE_BASE="/root/rose-ash-loops" INTERVAL="${1:-}" # e.g. "15m", or empty for self-paced -BOOT_WAIT=20 # seconds to wait for each claude to boot before sending /loop +BOOT_WAIT=20 if tmux has-session -t "$SESSION" 2>/dev/null; then echo "Session '$SESSION' already exists." echo " Attach: tmux a -t $SESSION" - echo " Kill: tmux kill-session -t $SESSION" + echo " Kill: ./scripts/sx-loops-down.sh" exit 1 fi @@ -34,39 +41,62 @@ declare -A BRIEFING=( ) ORDER=(lua prolog forth erlang haskell js hs) -# Create tmux session with 7 windows, one per language -tmux new-session -d -s "$SESSION" -n "${ORDER[0]}" -c "$PWD" -for lang in "${ORDER[@]:1}"; do - tmux new-window -t "$SESSION" -n "$lang" -c "$PWD" +mkdir -p "$WORKTREE_BASE" + +echo "Preparing per-language worktrees under $WORKTREE_BASE ..." +for lang in "${ORDER[@]}"; do + wt="$WORKTREE_BASE/$lang" + branch="loops/$lang" + if [ -d "$wt/.git" ] || [ -f "$wt/.git" ]; then + echo " $lang: worktree exists at $wt" + else + # Create or reset the branch at current architecture HEAD, then add worktree + if git show-ref --verify --quiet "refs/heads/$branch"; then + git worktree add "$wt" "$branch" >/dev/null + else + git worktree add -b "$branch" "$wt" architecture >/dev/null + fi + echo " $lang: worktree created at $wt on $branch" + fi +done + +# Create tmux session with 7 windows, each cwd in its worktree +tmux new-session -d -s "$SESSION" -n "${ORDER[0]}" -c "$WORKTREE_BASE/${ORDER[0]}" +for lang in "${ORDER[@]:1}"; do + tmux new-window -t "$SESSION" -n "$lang" -c "$WORKTREE_BASE/$lang" done -# Start claude in each window echo "Starting 7 claude sessions..." for lang in "${ORDER[@]}"; do tmux send-keys -t "$SESSION:$lang" "claude" C-m done -echo "Waiting ${BOOT_WAIT}s for claude to boot in each window..." +echo "Waiting ${BOOT_WAIT}s for claude to boot..." sleep "$BOOT_WAIT" -# Send the /loop command in each window for lang in "${ORDER[@]}"; do briefing="${BRIEFING[$lang]}" if [ -n "$INTERVAL" ]; then - cmd="/loop $INTERVAL Read plans/agent-briefings/$briefing and do ONE iteration per fire: pick the first unchecked [ ] from the plan, implement, test, commit with a short factual message, tick the box, append one dated line to the Progress log (newest first), then stop. Push to origin/loops/$lang (branch per language; never main). Stay strictly in the briefing's scope. Never push to main." + preamble="/loop $INTERVAL " else - cmd="/loop Read plans/agent-briefings/$briefing and do ONE iteration per fire: pick the first unchecked [ ] from the plan, implement, test, commit with a short factual message, tick the box, append one dated line to the Progress log (newest first), then stop. Push to origin/loops/$lang (branch per language; never main). Stay strictly in the briefing's scope. Never push to main." + preamble="/loop " fi - tmux send-keys -t "$SESSION:$lang" "$cmd" C-m + cmd="${preamble}Read plans/agent-briefings/$briefing and do ONE iteration per fire: pick the first unchecked [ ] from the plan, implement, test, commit with a short factual message, tick the box, append one dated line to the Progress log (newest first), then stop. You are on branch loops/$lang in worktree /root/rose-ash-loops/$lang; push commits to origin/loops/$lang (never main). Stay strictly in the briefing's scope." + tmux send-keys -t "$SESSION:$lang" "$cmd" + # tiny pause, then a second Enter — Claude's input box sometimes eats the first newline as a soft break + sleep 0.5 + tmux send-keys -t "$SESSION:$lang" Enter done echo "" -echo "Done. 7 loops started in tmux session '$SESSION'." +echo "Done. 7 loops started in tmux session '$SESSION', each in its own worktree." echo "" echo " Attach: tmux a -t $SESSION" -echo " Switch: Ctrl-B then window number 0-6 (lua=0, prolog=1, forth=2, erlang=3, haskell=4, js=5, hs=6)" -echo " Or index: Ctrl-B w (lists windows)" +echo " Switch: Ctrl-B <0..6> (0=lua 1=prolog 2=forth 3=erlang 4=haskell 5=js 6=hs)" +echo " List: Ctrl-B w" echo " Detach: Ctrl-B d" -echo " Kill all: tmux kill-session -t $SESSION" +echo " Stop: ./scripts/sx-loops-down.sh" +echo " Capture: tmux capture-pane -t $SESSION: -p | tail -40" echo "" -echo "If a window did NOT enter /loop mode (boot was slow), attach and paste the /loop command manually." +echo "Worktrees:" +git worktree list | grep -E "rose-ash-loops/" || true