Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,7 @@ open test-artifacts/unified/report.html
npm run dashboard # serve at http://localhost:3199 (read-only, localhost)
npm run dashboard:open # …and open the browser
make dashboard
bash scripts/install-dashboard-service.sh # ALWAYS-on: systemd --user service (auto-restart + boot)
```
Leave it running while you work. It watches every `graphdone.unified-report/1` run
in BOTH repos (Core `test-artifacts/unified*` + Cloud `live-full-report/<stamp>`),
Expand Down
39 changes: 34 additions & 5 deletions scripts/dashboard-loop.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,15 @@
#!/usr/bin/env bash
# Autonomous continuous-improvement loop for GraphDone, driven by cron so it
# survives session limits AND reboots. Each invocation (cron passes "once"):
# 1. ensures the live test dashboard server is up (the guiding light)
# 1. ensures the live test dashboard server is up (prefers the systemd --user
# service graphdone-dashboard if installed, else nohup) + restarts on stale code
# 2. self-checks the dashboard (its own unit tests)
# 2.5. regenerates the narrated report when a run is newer than the narration
# 3. runs ONE bounded headless `claude -p` improvement iteration — the agent
# decides whether to run a real test suite (which is what feeds the
# dashboard fresh run data); the driver never fabricates synthetic runs.
# Single-instance via flock. Set NO_AGENT=1 to skip step 3 (mechanical only).
# For an ALWAYS-up server prefer the systemd service: bash scripts/install-dashboard-service.sh
#
# bash scripts/dashboard-loop.sh once
set -uo pipefail
Expand Down Expand Up @@ -34,8 +37,11 @@ log "── iteration start (pid $$) ──"

health() { curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/api/state" 2>/dev/null; }

have_service() { systemctl --user cat graphdone-dashboard.service >/dev/null 2>&1; }

start_dashboard() {
nohup npm run dashboard >/tmp/gd-dash.log 2>&1 & disown
if have_service; then systemctl --user start graphdone-dashboard 2>/dev/null
else nohup npm run dashboard >/tmp/gd-dash.log 2>&1 & disown; fi
for _ in 1 2 3 4 5 6; do sleep 1; [ "$(health)" = "200" ] && break; done
}

Expand All @@ -54,9 +60,29 @@ restart_if_stale() {
[ -z "$newest" ] && return 0
if [ "$newest" -gt "$pstart" ]; then
log "dashboard code newer than running server (pid $pid) → restarting"
kill "$pid" 2>/dev/null
sleep 1
start_dashboard
if have_service; then
systemctl --user restart graphdone-dashboard 2>/dev/null
for _ in 1 2 3 4 5 6; do sleep 1; [ "$(health)" = "200" ] && break; done
else
kill "$pid" 2>/dev/null; sleep 1; start_dashboard
fi
fi
}

# Keep the narrated report current: regenerate only when a run report.json is
# newer than the existing narration (and a piper voice is present). The server
# serves narration.json fresh per request, so no restart is needed.
refresh_narration_if_stale() {
[ -d "$STATE/voices" ] && ls "$STATE/voices"/*.onnx >/dev/null 2>&1 || { log "narration: no voice model, skipping"; return 0; }
local narr newest_report narr_mtime
narr="$STATE/narration/narration.json"
newest_report=$(find "$CORE/test-artifacts/unified" "$CORE"/test-artifacts/unified-* "$CORE/../GraphDone-Cloud/live-full-report" -name report.json -printf '%T@\n' 2>/dev/null | sort -n | tail -1 | cut -d. -f1)
narr_mtime=$( [ -f "$narr" ] && stat -c %Y "$narr" 2>/dev/null || echo 0 )
if [ -n "$newest_report" ] && [ "$newest_report" -gt "${narr_mtime:-0}" ]; then
log "narration stale → regenerating"
if node scripts/narrate-report.mjs >>"$LOG" 2>&1; then log "narration regenerated"; else log "narration regen failed (rc=$?)"; fi
else
log "narration up to date"
fi
}

Expand All @@ -72,6 +98,9 @@ log "dashboard health=$(health)"
# 2) self-check the dashboard tool (cheap; does NOT fabricate runs)
if node --test tests/lib/dashboard/ >>"$LOG" 2>&1; then log "dashboard lib tests ok"; else log "dashboard lib tests FAILED"; fi

# 2.5) keep the narrated report current with the latest runs
refresh_narration_if_stale

# 3) one bounded headless agent iteration (skippable)
if [ "${NO_AGENT:-0}" = "1" ]; then
log "NO_AGENT=1 → skipping agent iteration"
Expand Down
60 changes: 60 additions & 0 deletions scripts/install-dashboard-service.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
#!/usr/bin/env bash
# Install the live test dashboard as a systemd --user service so it is ALWAYS up:
# auto-restarts on crash, starts on boot, and (with linger) runs even when logged
# out. Serves the latest unified report — videos, graphs, images, narration — at
# http://localhost:3199. Idempotent; re-run to update.
#
# bash scripts/install-dashboard-service.sh # install + start + enable
# systemctl --user status graphdone-dashboard # check
# systemctl --user restart graphdone-dashboard # restart
# systemctl --user disable --now graphdone-dashboard # stop + remove from boot
set -euo pipefail

CORE="/home/scubasonar/Code/graphdone-repos/GraphDone-Core"
PORT="${DASHBOARD_PORT:-3199}"
NODE="$(command -v node || echo /home/scubasonar/.nvm/versions/node/v20.20.2/bin/node)"
UNIT_DIR="$HOME/.config/systemd/user"
UNIT="$UNIT_DIR/graphdone-dashboard.service"

if ! command -v systemctl >/dev/null 2>&1; then
echo "❌ systemctl not available — fall back to the cron loop (scripts/dashboard-loop.sh) which keeps it alive every ~30 min + @reboot."
exit 1
fi

mkdir -p "$UNIT_DIR"

# free the port from any stray non-systemd instance so ExecStart can bind
pkill -f 'dashboard/server.mjs' 2>/dev/null || true
sleep 1

cat > "$UNIT" <<EOF
[Unit]
Description=GraphDone live test dashboard (unified report: videos, graphs, images, narrated)
After=network.target

[Service]
Type=simple
WorkingDirectory=$CORE
ExecStart=$NODE $CORE/tests/lib/dashboard/server.mjs --port $PORT
Restart=always
RestartSec=3
Environment=NODE_NO_WARNINGS=1
Environment=DASHBOARD_PORT=$PORT

[Install]
WantedBy=default.target
EOF

systemctl --user daemon-reload
systemctl --user enable --now graphdone-dashboard

# keep it running across logout / before login (best-effort; may need privileges)
loginctl enable-linger "$USER" 2>/dev/null && echo "✅ linger enabled (survives logout)" || echo "ℹ️ linger not enabled (service still runs while logged in; @reboot cron covers boot)"

sleep 2
echo
systemctl --user --no-pager status graphdone-dashboard | head -6 || true
echo
code="$(curl -s -o /dev/null -w '%{http_code}' "http://localhost:$PORT/api/state" 2>/dev/null || echo 000)"
echo "🌐 dashboard http://localhost:$PORT → HTTP $code"
echo " (Restart=always · enabled on boot · auto-restarts on crash)"
13 changes: 13 additions & 0 deletions tests/lib/dashboard/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ npm run dashboard:open # …and open the browser
make dashboard # same, via make
```

**Always-on (recommended):** install it as a systemd `--user` service so it is
*constantly* available — auto-restarts on crash, starts on boot, runs even when
logged out:

```bash
bash scripts/install-dashboard-service.sh # install + start + enable + linger
systemctl --user status graphdone-dashboard # check
systemctl --user disable --now graphdone-dashboard # uninstall
```

The cron loop (`scripts/dashboard-loop.sh`) also keeps it alive (every ~30 min +
`@reboot`) and prefers the service when installed, so the two don't conflict.

Then, in another terminal, run tests as usual — each completed run appears live:

```bash
Expand Down
Loading