#!/usr/bin/env bash # auto-commit-guard.sh — Background daemon that auto-commits uncommitted work # # Usage: auto-commit-guard.sh [interval_seconds] [worktree_base] # auto-commit-guard.sh # defaults: 120s, ~/worktrees # auto-commit-guard.sh 60 # check every 60s # auto-commit-guard.sh 180 ~/my-worktrees # # Scans all git repos under the worktree base for uncommitted changes. # If dirty for >= 1 check cycle, auto-commits with a WIP message. # Pushes unpushed commits so work is always recoverable from the remote. # # Also scans /tmp for orphaned agent workdirs on startup. set -uo pipefail INTERVAL="${1:-120}" WORKTREE_BASE="${2:-$HOME/worktrees}" LOG_DIR="$HOME/.hermes/logs" LOG="$LOG_DIR/auto-commit-guard.log" PIDFILE="$LOG_DIR/auto-commit-guard.pid" ORPHAN_SCAN_DONE="$LOG_DIR/.orphan-scan-done" mkdir -p "$LOG_DIR" # Single instance guard if [ -f "$PIDFILE" ]; then old_pid=$(cat "$PIDFILE") if kill -0 "$old_pid" 2>/dev/null; then echo "auto-commit-guard already running (PID $old_pid)" >&2 exit 0 fi fi echo $$ > "$PIDFILE" trap 'rm -f "$PIDFILE"' EXIT log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] AUTO-COMMIT: $*" >> "$LOG" } # --- Orphaned workdir scan (runs once on startup) --- scan_orphans() { if [ -f "$ORPHAN_SCAN_DONE" ]; then return 0 fi log "Scanning /tmp for orphaned agent workdirs..." local found=0 local rescued=0 for dir in /tmp/*-work-* /tmp/timmy-burn-* /tmp/tc-burn; do [ -d "$dir" ] || continue [ -d "$dir/.git" ] || continue found=$((found + 1)) cd "$dir" 2>/dev/null || continue local dirty dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ") if [ "${dirty:-0}" -gt 0 ]; then local branch branch=$(git branch --show-current 2>/dev/null || echo "orphan") git add -A 2>/dev/null if git commit -m "WIP: orphan rescue — $dirty file(s) auto-committed on $(date -u +%Y-%m-%dT%H:%M:%SZ) Orphaned workdir detected at $dir. Branch: $branch Rescued by auto-commit-guard on startup." 2>/dev/null; then rescued=$((rescued + 1)) log "RESCUED: $dir ($dirty files on branch $branch)" # Try to push if remote exists if git remote get-url origin >/dev/null 2>&1; then git push -u origin "$branch" 2>/dev/null && log "PUSHED orphan rescue: $dir → $branch" || log "PUSH FAILED orphan rescue: $dir (no remote access)" fi fi fi done log "Orphan scan complete: $found workdirs checked, $rescued rescued" touch "$ORPHAN_SCAN_DONE" } # --- Main guard loop --- guard_cycle() { local committed=0 local scanned=0 # Scan worktree base if [ -d "$WORKTREE_BASE" ]; then for dir in "$WORKTREE_BASE"/*/; do [ -d "$dir" ] || continue [ -d "$dir/.git" ] || continue scanned=$((scanned + 1)) cd "$dir" 2>/dev/null || continue local dirty dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ") [ "${dirty:-0}" -eq 0 ] && continue local branch branch=$(git branch --show-current 2>/dev/null || echo "detached") git add -A 2>/dev/null if git commit -m "WIP: auto-commit — $dirty file(s) on $branch Automated commit by auto-commit-guard at $(date -u +%Y-%m-%dT%H:%M:%SZ). Work preserved to prevent loss on crash." 2>/dev/null; then committed=$((committed + 1)) log "COMMITTED: $dir ($dirty files, branch $branch)" # Push to preserve remotely if git remote get-url origin >/dev/null 2>&1; then git push -u origin "$branch" 2>/dev/null && log "PUSHED: $dir → $branch" || log "PUSH FAILED: $dir (will retry next cycle)" fi fi done fi # Also scan /tmp for agent workdirs for dir in /tmp/*-work-*; do [ -d "$dir" ] || continue [ -d "$dir/.git" ] || continue scanned=$((scanned + 1)) cd "$dir" 2>/dev/null || continue local dirty dirty=$(git status --porcelain 2>/dev/null | wc -l | tr -d " ") [ "${dirty:-0}" -eq 0 ] && continue local branch branch=$(git branch --show-current 2>/dev/null || echo "detached") git add -A 2>/dev/null if git commit -m "WIP: auto-commit — $dirty file(s) on $branch Automated commit by auto-commit-guard at $(date -u +%Y-%m-%dT%H:%M:%SZ). Agent workdir preserved to prevent loss." 2>/dev/null; then committed=$((committed + 1)) log "COMMITTED: $dir ($dirty files, branch $branch)" if git remote get-url origin >/dev/null 2>&1; then git push -u origin "$branch" 2>/dev/null && log "PUSHED: $dir → $branch" || log "PUSH FAILED: $dir (will retry next cycle)" fi fi done [ "$committed" -gt 0 ] && log "Cycle done: $scanned scanned, $committed committed" } # --- Entry point --- log "Starting auto-commit-guard (interval=${INTERVAL}s, worktree=${WORKTREE_BASE})" scan_orphans while true; do guard_cycle sleep "$INTERVAL" done