feat: auto-commit-guard — 4-layer work preservation (#511) (#525)
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has started running
Validate Config / JSON Validate (push) Has started running
Some checks failed
Architecture Lint / Lint Repository (push) Has been cancelled
Architecture Lint / Linter Tests (push) Has been cancelled
Smoke Test / smoke (push) Has been cancelled
Validate Config / Python Syntax & Import Check (push) Has been cancelled
Validate Config / Python Test Suite (push) Has been cancelled
Validate Config / Shell Script Lint (push) Has been cancelled
Validate Config / Cron Syntax Check (push) Has been cancelled
Validate Config / Deploy Script Dry Run (push) Has been cancelled
Validate Config / Playbook Schema Validation (push) Has been cancelled
Validate Config / YAML Lint (push) Has started running
Validate Config / JSON Validate (push) Has started running
Merge PR #525
This commit was merged in pull request #525.
This commit is contained in:
159
bin/auto-commit-guard.sh
Normal file
159
bin/auto-commit-guard.sh
Normal file
@@ -0,0 +1,159 @@
|
||||
#!/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
|
||||
Reference in New Issue
Block a user