All criteria verified and the project is fully green.
**Verified (final pass — all 3 phase-06 tasks were already in `complete/`)**
- `classify("gpu.busy_pct", 99)` / `gpu.mem_used_pct` → `None`; `THRESHOLDS` has no `gpu.*` row; `alerts.py`/`api.py` carry no GPU verdict; live browser: deskwork GPU at **91 %** and **98 %** renders `class="hero"`, `color=rgb(22,25,31)` (plain text), no bar, no `.sev-*`.
- GPU temp on the hero `.sub` from live agents: `Radeon 8060S Graphics · VRAM 165.1 MiB / 512.0 MiB · pool 78% · 48 °C`, R9700 `· 40 °C`, gateway prints none (no `n/a`, no `0 °C`).
- Charts collapsed by default: fresh load = 3 panels / 3 toggles / **0 open / 0 `<svg>`**; expand → 3 SVGs (510 px wide), repaints across polls (legend 3.3 %→5.6 %), collapsed siblings keep their own height (803 vs 358/339).
- `gpu.busy_pct@0` still answers from `/api/system/history` (345 points) — verdict gone, data intact.
**Tests / lint / coverage**
- `bash .agents/validate.sh` → **EXIT=0**; `uv run pytest --cov --cov-fail-under=94` → **550 passed, 8 skipped, 97.91 %**; `uv run ruff check src tests agent` → clean; `uv run pyright` → 0 errors; `node --check` all scripts clean; `uv pip list --outdated` empty.
- `uv run pytest -m live --live` → **8 passed** (real agents/engines/upsd); dev server restarted, `/api/health` all sources `ok`.
- New screenshots: `data/logs/phase06-final-collapsed-default.png`, `data/logs/phase06-final-expanded-gpu-busy-uncoloured.png`.
**Completion criteria:** classify→None ✅ · pytest+94 % gate ✅ · ruff+pyright ✅ · validate.sh ✅ · screenshots of the three behaviours on live data ✅ · AGENTS.md severity section claims no GPU rows ✅
**Docs:** fixed 3 items — (1) `AGENTS.md` pytest count stale (546 → 550); (2) `app.js` `severityFor` docstring still claimed "`page-ups.js` still calls it (phase 05 converts it)", contradicting the code/tests/AGENTS.md (comment-only); rest of README/AGENTS reviewed in full (env table vs `config.py`+`.env.example`, agent routes incl. `/schema`, alerts rules, page descriptions, contracts) — consistent.
**Notable:** fixed a real pre-existing defect in `scripts/dev-server.sh` — `stop` slept 1 s and deleted the pidfile without waiting for exit, so `restart` with a browser open overlapped two servers on one SQLite file, producing a `database is locked` store refusal that `/system` rendered as a red event. Now waits (bounded 20 s) then SIGKILLs; verified a restart with live polling logs no such error/event. No live infra config was changed.
**Next pending phase:** `.agents/phases/todo/07_llm_monitor_style_pages`
84 lines
2.7 KiB
Bash
Executable File
84 lines
2.7 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# Local development server: start|stop|status|restart|logs|tunnel-check
|
|
#
|
|
# ./scripts/dev-server.sh restart # reads .env.dev if present, runs on DASH_PORT
|
|
#
|
|
# The server keeps running after this shell exits (nohup + pidfile) so you can browse it
|
|
# while an agent works. Logs: data/logs/dev.log
|
|
set -uo pipefail
|
|
|
|
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
|
cd "$ROOT"
|
|
PIDFILE="$ROOT/data/dev-server.pid"
|
|
LOG="$ROOT/data/logs/dev.log"
|
|
mkdir -p "$ROOT/data/logs"
|
|
|
|
running() { [ -f "$PIDFILE" ] && kill -0 "$(cat "$PIDFILE")" 2>/dev/null; }
|
|
|
|
start() {
|
|
if running; then
|
|
echo "already running (pid $(cat "$PIDFILE")) — http://127.0.0.1:${DASH_PORT:-8090}"
|
|
return 0
|
|
fi
|
|
if [ -f "$ROOT/.env.dev" ]; then
|
|
set -a; . "$ROOT/.env.dev"; set +a
|
|
echo "loaded .env.dev"
|
|
fi
|
|
PORT="${DASH_PORT:-8090}"
|
|
nohup .venv/bin/dashboards >"$LOG" 2>&1 &
|
|
echo $! >"$PIDFILE"
|
|
sleep 3
|
|
if running && curl -sf -o /dev/null "http://127.0.0.1:${PORT}/api/health"; then
|
|
echo "started pid $(cat "$PIDFILE") — http://127.0.0.1:${PORT}"
|
|
else
|
|
echo "FAILED to start; last lines of $LOG:"
|
|
tail -20 "$LOG"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
stop() {
|
|
if running; then
|
|
local pid
|
|
pid="$(cat "$PIDFILE")"
|
|
kill "$pid" 2>/dev/null
|
|
# Wait for the process to actually go away before handing the pidfile back. A bare
|
|
# `sleep 1` was not enough: uvicorn's graceful shutdown waits for in-flight requests,
|
|
# so `restart` with a browser open started the new server while the old one still
|
|
# held the SQLite file — the dying process then logged `database is locked` for a poll
|
|
# it had already fetched, which the app correctly turned into a red store-error event.
|
|
# Two servers on one DB is the one way this script can fabricate an incident, so the
|
|
# wait is bounded rather than optimistic, and SIGKILL is the last resort.
|
|
local i=0
|
|
while kill -0 "$pid" 2>/dev/null; do
|
|
i=$((i + 1))
|
|
if [ "$i" -gt 60 ]; then
|
|
echo "pid $pid ignored SIGTERM after 20s; sending SIGKILL"
|
|
kill -9 "$pid" 2>/dev/null
|
|
break
|
|
fi
|
|
sleep 0.25
|
|
done
|
|
echo "stopped"
|
|
else
|
|
echo "not running"
|
|
fi
|
|
rm -f "$PIDFILE"
|
|
}
|
|
|
|
case "${1:-start}" in
|
|
start) start ;;
|
|
stop) stop ;;
|
|
restart) stop; start ;;
|
|
status)
|
|
if running; then
|
|
echo "running pid $(cat "$PIDFILE")"
|
|
curl -s "http://127.0.0.1:${DASH_PORT:-8090}/api/health" \
|
|
| .venv/bin/python -c "import json,sys; d=json.load(sys.stdin); print('status:', d['status']); [print(' ', s['key'], 'ok' if s['ok'] else 'DOWN', (s['error'] or '')[:90]) for s in d['sources']]"
|
|
else
|
|
echo "not running"
|
|
fi ;;
|
|
logs) tail -n 40 "$LOG" ;;
|
|
*) echo "usage: $0 start|stop|restart|status|logs"; exit 2 ;;
|
|
esac
|