Spaces:
Runtime error
Runtime error
File size: 7,526 Bytes
f7e1070 80649a9 f7e1070 80649a9 f7e1070 80649a9 f7e1070 e36381e f7e1070 ea561c8 80649a9 ea561c8 80649a9 ea561c8 80649a9 f7e1070 80649a9 633a37a f7e1070 80649a9 f7e1070 80649a9 f7e1070 80649a9 f7e1070 80649a9 f7e1070 8645e42 f7e1070 633a37a f7e1070 80649a9 f7e1070 80649a9 f7e1070 633a37a f7e1070 80649a9 f7e1070 80649a9 f7e1070 80649a9 f7e1070 e36381e f7e1070 80649a9 e36381e f7e1070 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 | #!/usr/bin/env bash
# Auto-orchestrate loop β fires SA β architect β qa-tdd β dev β qa-verify β reviewer chain.
#
# Strategy: pick a real TODO/FIXME from any axentx project, run the full pipeline,
# auto-commit on APPROVE. Runs every 20 min via cron.
# Pairs with surrogate-dev-loop (light/fast); this one does heavy multi-stage work.
#
# Linux + macOS compatible (auto-detects coreutils variants).
set -uo pipefail
set -a; source "$HOME/.hermes/.env" 2>/dev/null; set +a
LOG="$HOME/.surrogate/logs/auto-orchestrate-loop.log"
mkdir -p "$(dirname "$LOG")"
# ββ Resource guard ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
# HF Space CPU has spiky load avg from ollama pulls + concurrent scrape workers.
# load >50 = real saturation; free_mb <100 = OOM risk.
# Previous threshold (load>8) was paused 90% of time during model pulls β too aggressive.
LOAD=$(uptime | sed -E 's/.*load average[s]?:[[:space:]]*//' | awk -F',' '{print int($1)}')
if [[ -r /proc/meminfo ]]; then
FREE_MB=$(awk '/MemAvailable/{print int($2/1024)}' /proc/meminfo)
elif command -v vm_stat >/dev/null 2>&1; then
FREE_MB=$(vm_stat | awk '/Pages free/{gsub("[.]","",$3); printf "%d", ($3*16384)/1048576}')
else
FREE_MB=999
fi
if [[ ${LOAD:-0} -gt 50 ]] || [[ ${FREE_MB:-999} -lt 100 ]]; then
echo "[$(date +%H:%M:%S)] resource-pause: load=$LOAD free_mb=$FREE_MB β skip" >> "$LOG"
exit 0
fi
# ββ Pick a real task: one TODO/FIXME from a randomly-chosen axentx project ββ
TASK_INFO=$(python3 <<'PYEOF'
import os, random, re, subprocess, json
from pathlib import Path
# Real paths (verified via api.github.com 2026-04-28)
PROJECTS = [
Path.home() / 'axentx/Costinel',
Path.home() / 'axentx/vanguard',
Path.home() / 'axentx/arkship',
Path.home() / 'axentx/surrogate',
Path.home() / 'axentx/workio',
Path.home() / 'axentx/hermes-toolbelt',
]
PROJECTS = [p for p in PROJECTS if (p/'.git').exists()]
if not PROJECTS:
print("{}"); exit()
# ROUND-ROBIN across projects (instead of random.shuffle which kept hitting same repo).
# Persistent counter at ~/.surrogate/state/orchestrate-project-cursor β increments each run.
# Result: every 6 runs covers all 6 axentx repos evenly.
cursor_file = Path.home() / '.surrogate/state/orchestrate-project-cursor'
cursor_file.parent.mkdir(parents=True, exist_ok=True)
try:
cursor = int(cursor_file.read_text().strip())
except Exception:
cursor = 0
PROJECTS = sorted(PROJECTS, key=lambda p: p.name) # stable order
cursor = cursor % len(PROJECTS)
# Rotate so current cursor's project is first
PROJECTS = PROJECTS[cursor:] + PROJECTS[:cursor]
cursor_file.write_text(str((cursor + 1) % len(PROJECTS)))
for proj in PROJECTS:
cmd = ['rg', '--no-heading', '-n', '-m', '5',
'--type', 'py', '--type', 'ts', '--type', 'go', '--type', 'sh',
'-g', '!node_modules', '-g', '!.venv', '-g', '!__pycache__',
'-g', '!.git', '-g', '!dist', '-g', '!build',
r'(TODO|FIXME)[:\s]', str(proj)]
try:
r = subprocess.run(cmd, capture_output=True, text=True, timeout=8)
lines = [l for l in r.stdout.splitlines() if l.strip()]
if not lines: continue
line = random.choice(lines)
m = re.match(r'^([^:]+):(\d+):(.+)$', line)
if not m: continue
path, lineno, content = m.groups()
rel = os.path.relpath(path, proj)
c = content.strip().lower()
if any(skip in c for skip in ['#todo:', 'todo: fix', 'todo:', '// todo', 'todo()']) and len(content) < 30:
continue
print(json.dumps({
'project': str(proj),
'project_name': proj.name,
'file': rel,
'line': int(lineno),
'content': content.strip()[:300],
}))
exit()
except Exception:
continue
print("{}")
PYEOF
)
if [[ -z "$TASK_INFO" ]] || [[ "$TASK_INFO" == "{}" ]]; then
echo "[$(date +%H:%M:%S)] no task found β skip" >> "$LOG"
exit 0
fi
PROJECT=$(echo "$TASK_INFO" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['project'])")
PROJ_NAME=$(echo "$TASK_INFO" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['project_name'])")
FILE=$(echo "$TASK_INFO" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['file'])")
LINE=$(echo "$TASK_INFO" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['line'])")
CONTENT=$(echo "$TASK_INFO" | python3 -c "import json,sys; print(json.loads(sys.stdin.read())['content'])")
# ββ Per-task throttle: don't redo same TODO within 4 hours βββββββββββββββββ
# md5 (macOS) vs md5sum (Linux); stat -f%m (macOS) vs stat -c%Y (Linux)
if command -v md5sum >/dev/null 2>&1; then
TASK_HASH=$(echo "${PROJ_NAME}:${FILE}:${LINE}" | md5sum | cut -c1-12)
else
TASK_HASH=$(echo "${PROJ_NAME}:${FILE}:${LINE}" | md5 | cut -c1-12)
fi
LOCK_DIR="$HOME/.hermes/workspace/auto-orchestrate-locks"
mkdir -p "$LOCK_DIR"
LOCK="$LOCK_DIR/${TASK_HASH}"
if [[ -f "$LOCK" ]]; then
if stat -c %Y "$LOCK" >/dev/null 2>&1; then
LOCK_TS=$(stat -c %Y "$LOCK")
else
LOCK_TS=$(stat -f %m "$LOCK" 2>/dev/null || echo 0)
fi
AGE=$(( $(date +%s) - LOCK_TS ))
if [[ $AGE -lt 14400 ]]; then
echo "[$(date +%H:%M:%S)] task ${TASK_HASH} done ${AGE}s ago β skip" >> "$LOG"
exit 0
fi
fi
touch "$LOCK"
# ββ Run orchestrate (auto-commits on APPROVE) ββββββββββββββββββββββββββββββ
START=$(date +%s)
echo "[$(date +%H:%M:%S)] orchestrate start: $PROJ_NAME/$FILE:$LINE" >> "$LOG"
echo " task: $CONTENT" >> "$LOG"
TASK_DESC="Resolve this TODO/FIXME in $PROJ_NAME at $FILE:$LINE: \"$CONTENT\". Implement a real fix (not stub), keep changes scoped to the file/function. Match existing code style."
cd "$PROJECT" || { echo "[$(date +%H:%M:%S)] cd failed" >> "$LOG"; exit 1; }
bash "$HOME/.surrogate/bin/surrogate-orchestrate.sh" "$TASK_DESC" >> "$LOG" 2>&1
RC=$?
DUR=$(( $(date +%s) - START ))
echo "[$(date +%H:%M:%S)] orchestrate done in ${DUR}s rc=$RC" >> "$LOG"
# ββ Push to GitHub if commit was created βββββββββββββββββββββββββββββββββββ
if [[ $RC -eq 0 ]]; then
LATEST_COMMIT=$(git -C "$PROJECT" log -1 --format=%H 2>/dev/null)
LATEST_AGE=$(( $(date +%s) - $(git -C "$PROJECT" log -1 --format=%ct 2>/dev/null || echo 0) ))
if [[ $LATEST_AGE -lt 600 ]]; then # commit within last 10 min = was just made
if git -C "$PROJECT" push origin HEAD:main >> "$LOG" 2>&1; then
echo "[$(date +%H:%M:%S)] β
pushed $LATEST_COMMIT to $PROJ_NAME" >> "$LOG"
else
echo "[$(date +%H:%M:%S)] β push failed for $PROJ_NAME" >> "$LOG"
fi
fi
fi
# ββ Discord notification βββββββββββββββββββββββββββββββββββββββββββββββββββ
NOTIFY="$HOME/.surrogate/bin/notify-discord.sh"
if [[ -x "$NOTIFY" ]]; then
if [[ $RC -eq 0 ]]; then
"$NOTIFY" task "Auto-orchestrate: $PROJ_NAME" "$FILE:$LINE β \`$(echo "$CONTENT" | head -c 80)\` Β· ${DUR}s" 2>/dev/null &
else
"$NOTIFY" warn "Auto-orchestrate failed" "$PROJ_NAME Β· $FILE:$LINE Β· rc=$RC Β· ${DUR}s" 2>/dev/null &
fi
fi
|