diff --git a/bin/timmy-orchestrator.sh b/bin/timmy-orchestrator.sh index 806a721f..990e83da 100755 --- a/bin/timmy-orchestrator.sh +++ b/bin/timmy-orchestrator.sh @@ -3,7 +3,7 @@ # Uses Hermes CLI plus workforce-manager to triage and review. # Timmy is the brain. Other agents are the hands. -set -uo pipefail +set -uo pipefail\n\nSCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" LOG_DIR="$HOME/.hermes/logs" LOG="$LOG_DIR/timmy-orchestrator.log" @@ -40,6 +40,7 @@ gather_state() { > "$state_dir/unassigned.txt" > "$state_dir/open_prs.txt" > "$state_dir/agent_status.txt" + > "$state_dir/uncommitted_work.txt" for repo in $REPOS; do local short=$(echo "$repo" | cut -d/ -f2) @@ -71,6 +72,24 @@ for p in json.load(sys.stdin): tail -50 "/tmp/kimi-heartbeat.log" 2>/dev/null | grep -c "FAILED:" | xargs -I{} echo "Kimi recent failures: {}" >> "$state_dir/agent_status.txt" tail -1 "/tmp/kimi-heartbeat.log" 2>/dev/null | xargs -I{} echo "Kimi last event: {}" >> "$state_dir/agent_status.txt" + # Scan worktrees for uncommitted work + for wt_dir in "$HOME/worktrees"/*/; do + [ -d "$wt_dir" ] || continue + [ -d "$wt_dir/.git" ] || continue + local dirty + dirty=$(cd "$wt_dir" && git status --porcelain 2>/dev/null | wc -l | tr -d " ") + if [ "${dirty:-0}" -gt 0 ]; then + local branch + branch=$(cd "$wt_dir" && git branch --show-current 2>/dev/null || echo "?") + local age="" + local last_commit + last_commit=$(cd "$wt_dir" && git log -1 --format=%ct 2>/dev/null || echo 0) + local now=$(date +%s) + local stale_mins=$(( (now - last_commit) / 60 )) + echo "DIR=$wt_dir BRANCH=$branch DIRTY=$dirty STALE=${stale_mins}m" >> "$state_dir/uncommitted_work.txt" + fi + done + echo "$state_dir" } @@ -81,6 +100,25 @@ run_triage() { log "Cycle: $unassigned_count unassigned, $pr_count open PRs" + # Check for uncommitted work — nag if stale + local uncommitted_count + uncommitted_count=$(wc -l < "$state_dir/uncommitted_work.txt" 2>/dev/null | tr -d " " || echo 0) + if [ "${uncommitted_count:-0}" -gt 0 ]; then + log "WARNING: $uncommitted_count worktree(s) with uncommitted work" + while IFS= read -r line; do + log " UNCOMMITTED: $line" + # Auto-commit stale work (>60 min without commit) + local stale=$(echo "$line" | sed 's/.*STALE=\([0-9]*\)m.*/\1/') + local wt_dir=$(echo "$line" | sed 's/.*DIR=\([^ ]*\) .*/\1/') + if [ "${stale:-0}" -gt 60 ]; then + log " AUTO-COMMITTING stale work in $wt_dir (${stale}m stale)" + (cd "$wt_dir" && git add -A && git commit -m "WIP: orchestrator auto-commit — ${stale}m stale work + +Preserved by timmy-orchestrator to prevent loss." 2>/dev/null && git push 2>/dev/null) && log " COMMITTED: $wt_dir" || log " COMMIT FAILED: $wt_dir" + fi + done < "$state_dir/uncommitted_work.txt" + fi + # If nothing to do, skip the LLM call if [ "$unassigned_count" -eq 0 ] && [ "$pr_count" -eq 0 ]; then log "Nothing to triage" @@ -198,6 +236,12 @@ FOOTER log "=== Timmy Orchestrator Started (PID $$) ===" log "Cycle: ${CYCLE_INTERVAL}s | Auto-assign: ${AUTO_ASSIGN_UNASSIGNED} | Inference surface: Hermes CLI" +# Start auto-commit-guard daemon for work preservation +if ! pgrep -f "auto-commit-guard.sh" >/dev/null 2>&1; then + nohup bash "$SCRIPT_DIR/auto-commit-guard.sh" 120 >> "$LOG_DIR/auto-commit-guard.log" 2>&1 & + log "Started auto-commit-guard daemon (PID $!)" +fi + WORKFORCE_CYCLE=0 while true; do