Files
hermes-config/bin/timmy-loopstat.sh
Alexander Whitestone 1eb10f72bb feat: add Claude Code parallel worker loop + upgrade ops dashboard
- claude-loop.sh: parallel dispatch loop (N workers, default 3) with
  file-based locking, rate-limit detection + backoff, priority-sorted
  issue picking across all Gitea repos, auto-merge on success
- ops-panel.sh: add Claude worker status, stats, and queue sections
- ops-helpers.sh: add ops-wake-claude, ops-kill-claude,
  ops-assign-claude, ops-claude-queue commands
- ops-dashboard-v2.sh: 4-pane layout with dual Kimi + Claude feeds
- .gitignore: whitelist ops scripts for source control
- timmy-loopstat.sh: add to source control

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-22 18:06:38 -04:00

211 lines
6.1 KiB
Bash
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env bash
# ── LOOPSTAT Panel ──────────────────────
# Strategic view: queue, perf, triage,
# recent cycles. 40-col × 50-row pane.
# ────────────────────────────────────────
REPO="$HOME/Timmy-Time-dashboard"
QUEUE="$REPO/.loop/queue.json"
RETRO="$REPO/.loop/retro/cycles.jsonl"
TRIAGE_R="$REPO/.loop/retro/triage.jsonl"
DEEP_R="$REPO/.loop/retro/deep-triage.jsonl"
SUMMARY="$REPO/.loop/retro/summary.json"
QUARANTINE="$REPO/.loop/quarantine.json"
STATE="$REPO/.loop/state.json"
B='\033[1m' ; D='\033[2m' ; R='\033[0m'
G='\033[32m' ; Y='\033[33m' ; RD='\033[31m'
C='\033[36m' ; M='\033[35m'
W=$(tput cols 2>/dev/null || echo 40)
hr() { printf "${D}"; printf '─%.0s' $(seq 1 "$W"); printf "${R}\n"; }
while true; do
clear
echo -e "${B}${M} ◈ LOOPSTAT${R} ${D}$(date '+%H:%M')${R}"
hr
# ── PERFORMANCE ──────────────────────
python3 -c "
import json, os
f = '$SUMMARY'
if not os.path.exists(f):
print(' \033[2m(no perf data yet)\033[0m')
raise SystemExit
s = json.load(open(f))
rate = s.get('success_rate', 0)
avg = s.get('avg_duration_seconds', 0)
total = s.get('total_cycles', 0)
merged = s.get('total_prs_merged', 0)
added = s.get('total_lines_added', 0)
removed = s.get('total_lines_removed', 0)
rc = '\033[32m' if rate >= .8 else '\033[33m' if rate >= .5 else '\033[31m'
am, asec = divmod(avg, 60)
print(f' {rc}{rate*100:.0f}%\033[0m ok \033[1m{am:.0f}m{asec:02.0f}s\033[0m avg {total} cyc')
print(f' \033[32m{merged}\033[0m PRs \033[32m+{added}\033[0m/\033[31m-{removed}\033[0m lines')
bt = s.get('by_type', {})
parts = []
for t in ['bug','feature','refactor']:
i = bt.get(t, {})
if i.get('count', 0):
sr = i.get('success_rate', 0)
parts.append(f'{t[:3]}:{sr*100:.0f}%')
if parts:
print(f' \033[2m{\" \".join(parts)}\033[0m')
" 2>/dev/null
hr
# ── QUEUE ────────────────────────────
echo -e "${B}${Y} QUEUE${R}"
python3 -c "
import json, os
f = '$QUEUE'
if not os.path.exists(f):
print(' \033[2m(no queue yet)\033[0m')
raise SystemExit
q = json.load(open(f))
if not q:
print(' \033[2m(empty — needs triage)\033[0m')
raise SystemExit
types = {}
for item in q:
t = item.get('type','?')
types[t] = types.get(t, 0) + 1
ts = ' '.join(f'{t[0].upper()}:{n}' for t,n in sorted(types.items()) if t != 'philosophy')
print(f' \033[1m{len(q)}\033[0m ready \033[2m{ts}\033[0m')
print()
for i, item in enumerate(q[:8]):
n = item['issue']
s = item.get('score', 0)
title = item.get('title', '?')
t = item.get('type', '?')
ic = {'bug':'\033[31m●','feature':'\033[32m◆','refactor':'\033[36m○'}.get(t, '\033[2m·')
bar = '█' * s + '░' * (9 - s)
ptr = '\033[1m→' if i == 0 else f'\033[2m{i+1}'
# Truncate title to fit: 40 - 2(pad) - 2(ptr) - 2(ic) - 5(#num) - 1 = 28
tit = title[:24]
print(f' {ptr}\033[0m {ic}\033[0m \033[33m#{n}\033[0m {tit}')
if len(q) > 8:
print(f' \033[2m +{len(q)-8} more\033[0m')
" 2>/dev/null
hr
# ── TRIAGE ───────────────────────────
echo -e "${B}${G} TRIAGE${R}"
python3 -c "
import json, os
from datetime import datetime, timezone
cycle = '?'
if os.path.exists('$STATE'):
try: cycle = json.load(open('$STATE')).get('cycle','?')
except: pass
def ago(ts):
if not ts: return 'never'
try:
dt = datetime.fromisoformat(ts)
if dt.tzinfo is None:
dt = dt.replace(tzinfo=timezone.utc)
m = int((datetime.now(timezone.utc) - dt).total_seconds() / 60)
if m < 60: return f'{m}m ago'
if m < 1440: return f'{m//60}h{m%60}m ago'
return f'{m//1440}d ago'
except: return '?'
# Fast
fast_ago = 'never'
if os.path.exists('$TRIAGE_R'):
lines = open('$TRIAGE_R').read().strip().splitlines()
if lines:
try:
last = json.loads(lines[-1])
fast_ago = ago(last.get('timestamp',''))
except: pass
# Deep
deep_ago = 'never'
timmy = ''
if os.path.exists('$DEEP_R'):
lines = open('$DEEP_R').read().strip().splitlines()
if lines:
try:
last = json.loads(lines[-1])
deep_ago = ago(last.get('timestamp',''))
timmy = last.get('timmy_feedback','')[:60]
except: pass
# Next
try:
c = int(cycle)
nf = 5 - (c % 5)
nd = 20 - (c % 20)
except:
nf = nd = '?'
print(f' Fast {fast_ago:<12s} \033[2mnext:{nf}c\033[0m')
print(f' Deep {deep_ago:<12s} \033[2mnext:{nd}c\033[0m')
if timmy:
# wrap at ~36 chars
print(f' \033[35mTimmy:\033[0m')
t = timmy
while t:
print(f' \033[2m{t[:36]}\033[0m')
t = t[36:]
# Quarantine
if os.path.exists('$QUARANTINE'):
try:
qd = json.load(open('$QUARANTINE'))
if qd:
qs = ','.join(f'#{k}' for k in list(qd.keys())[:4])
print(f' \033[31mQuarantined:{len(qd)}\033[0m {qs}')
except: pass
" 2>/dev/null
hr
# ── RECENT CYCLES ────────────────────
echo -e "${B}${D} CYCLES${R}"
python3 -c "
import json, os
f = '$RETRO'
if not os.path.exists(f):
print(' \033[2m(none yet)\033[0m')
raise SystemExit
lines = open(f).read().strip().splitlines()
recent = []
for l in lines[-12:]:
try: recent.append(json.loads(l))
except: continue
if not recent:
print(' \033[2m(none yet)\033[0m')
raise SystemExit
for e in reversed(recent):
cy = e.get('cycle','?')
ok = e.get('success', False)
iss = e.get('issue','')
dur = e.get('duration', 0)
pr = e.get('pr','')
reason = e.get('reason','')[:18]
ic = '\033[32m✓\033[0m' if ok else '\033[31m✗\033[0m'
ds = f'{dur//60}m' if dur else '-'
ix = f'#{iss}' if iss else ' — '
if ok:
det = f'PR#{pr}' if pr else ''
else:
det = reason
print(f' {ic} {cy:<3} {ix:<5s} {ds:>4s} \033[2m{det}\033[0m')
" 2>/dev/null
hr
echo -e "${D} ↻ 10s${R}"
sleep 10
done