mirror of
https://github.com/garrytan/gbrain.git
synced 2026-07-27 22:15:33 +00:00
* feat: smoke-test skillpack — post-restart health checks + auto-fix Adds `gbrain smoke-test` CLI command that runs 8 health checks after container restart, auto-fixes known issues, and reports results. Built-in tests: 1. Bun runtime (auto-install if missing) 2. GBrain CLI loads (auto-reinstall deps) 3. GBrain database connection (doctor health score) 4. GBrain worker process (auto-start) 5. OpenClaw Codex plugin Zod CJS (auto-reinstall broken zod@4) 6. OpenClaw gateway responding 7. Embedding API key present 8. Brain repo exists User-extensible: drop scripts in ~/.gbrain/smoke-tests.d/*.sh Includes SKILL.md with full documentation, pattern for adding tests, and known-issue database (e.g. Zod core.cjs publish bug). Designed to run from OpenClaw bootstrap hooks so every container restart automatically verifies and repairs the environment. * fix: register smoke-test in RESOLVER + add required SKILL sections Fixes the 7 failing unit tests + 1 failing Tier 1 E2E: - `skills/RESOLVER.md`: add smoke-test under Operational (mirrors skillpack-check placement). Fixes resolver_health check failure which cascaded into skillpack-check tests, doctor exit code, and the E2E 'gbrain doctor exits 0 on healthy DB' assertion. - `skills/smoke-test/SKILL.md`: add `## Anti-Patterns` and `## Output Format` sections required by skills-conformance.test.ts. Root cause: PR #369 added skills/smoke-test/ to the manifest but never wired it into RESOLVER.md and never added the sections the conformance test requires for every manifest entry. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * fix: regenerate llms-full.txt to pick up RESOLVER smoke-test row build-llms drift guard (test/build-llms.test.ts:58) failed because llms-full.txt inlines skills/RESOLVER.md and the last commit added a smoke-test trigger row there. Regenerated via `bun run build:llms`. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com> * chore: bump version and changelog (v0.19.1) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com> --------- Co-authored-by: root <root@localhost> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
205 lines
8.4 KiB
Bash
Executable File
205 lines
8.4 KiB
Bash
Executable File
#!/bin/bash
|
|
# smoke-test.sh — GBrain post-restart smoke tests + auto-fix
|
|
#
|
|
# Ships with gbrain. Tests gbrain core services + OpenClaw plugin health.
|
|
# Users extend via ~/.gbrain/smoke-tests.d/*.sh (user-defined tests).
|
|
#
|
|
# Usage:
|
|
# gbrain smoke-test # run all tests
|
|
# bash scripts/smoke-test.sh # direct invocation
|
|
#
|
|
# Each test: check → if broken, attempt fix → re-check → report.
|
|
# Exit code = number of remaining failures (0 = all pass).
|
|
|
|
set -a
|
|
[ -f /data/.env ] && . /data/.env 2>/dev/null || true
|
|
set +a
|
|
|
|
LOG="${GBRAIN_SMOKE_LOG:-/tmp/gbrain-smoke-test.log}"
|
|
FAILURES=0
|
|
FIXES=0
|
|
TOTAL=0
|
|
SKIPPED=0
|
|
|
|
timestamp() { date -u '+%Y-%m-%d %H:%M:%S'; }
|
|
pass() { TOTAL=$((TOTAL + 1)); echo "✅ $1"; echo "$(timestamp) PASS: $1" >> "$LOG"; }
|
|
fail() { TOTAL=$((TOTAL + 1)); FAILURES=$((FAILURES + 1)); echo "❌ $1"; echo "$(timestamp) FAIL: $1" >> "$LOG"; }
|
|
fixed() { FIXES=$((FIXES + 1)); echo "🔧 Fixed: $1"; echo "$(timestamp) FIXED: $1" >> "$LOG"; }
|
|
skip() { SKIPPED=$((SKIPPED + 1)); echo "⏭️ $1"; echo "$(timestamp) SKIP: $1" >> "$LOG"; }
|
|
|
|
echo "$(timestamp) === GBrain Smoke Tests ===" >> "$LOG"
|
|
echo "🧪 Running gbrain smoke tests..."
|
|
echo ""
|
|
|
|
# ── Resolve paths ───────────────────────────────────────────
|
|
# Find gbrain — could be global install, workspace dep, or /data/gbrain
|
|
GBRAIN_DIR=""
|
|
for candidate in \
|
|
"${GBRAIN_DIR_OVERRIDE:-}" \
|
|
"/data/gbrain" \
|
|
"$(dirname "$0")/.." \
|
|
"${OPENCLAW_WORKSPACE:-/data/.openclaw/workspace}/node_modules/gbrain" \
|
|
"./node_modules/gbrain"; do
|
|
[ -n "$candidate" ] && [ -f "$candidate/src/cli.ts" ] && GBRAIN_DIR="$candidate" && break
|
|
done
|
|
|
|
# Find bun
|
|
BUN_PATH=""
|
|
for bp in "/root/.bun/bin/bun" "/data/.bun/bin/bun" "$(which bun 2>/dev/null)"; do
|
|
[ -n "$bp" ] && [ -x "$bp" ] && BUN_PATH="$bp" && break
|
|
done
|
|
|
|
# Resolve database URL
|
|
DB_URL="${GBRAIN_DATABASE_URL:-${DATABASE_URL:-}}"
|
|
# Fallback: grep from .env (handles files with parse-breaking lines)
|
|
[ -z "$DB_URL" ] && DB_URL=$(grep '^GBRAIN_DATABASE_URL=' /data/.env 2>/dev/null | head -1 | cut -d= -f2-)
|
|
[ -z "$DB_URL" ] && DB_URL=$(grep '^DATABASE_URL=' /data/.env 2>/dev/null | head -1 | cut -d= -f2-)
|
|
|
|
# ── 1. Bun runtime ─────────────────────────────────────────
|
|
if [ -n "$BUN_PATH" ]; then
|
|
export PATH="$(dirname "$BUN_PATH"):$PATH"
|
|
pass "Bun runtime ($BUN_PATH)"
|
|
else
|
|
# Auto-fix: install bun
|
|
curl -fsSL https://bun.sh/install | bash 2>/dev/null
|
|
if [ -x "/root/.bun/bin/bun" ]; then
|
|
BUN_PATH="/root/.bun/bin/bun"
|
|
export PATH="/root/.bun/bin:$PATH"
|
|
fixed "Bun runtime installed"
|
|
pass "Bun runtime"
|
|
else
|
|
fail "Bun runtime — install failed"
|
|
fi
|
|
fi
|
|
|
|
# ── 2. GBrain CLI loads ────────────────────────────────────
|
|
if [ -n "$GBRAIN_DIR" ] && [ -n "$BUN_PATH" ]; then
|
|
if timeout 15 "$BUN_PATH" run "$GBRAIN_DIR/src/cli.ts" --help >/dev/null 2>&1; then
|
|
pass "GBrain CLI ($GBRAIN_DIR)"
|
|
else
|
|
# Auto-fix: reinstall deps
|
|
cd "$GBRAIN_DIR" && "$BUN_PATH" install --frozen-lockfile 2>/dev/null
|
|
if timeout 15 "$BUN_PATH" run "$GBRAIN_DIR/src/cli.ts" --help >/dev/null 2>&1; then
|
|
fixed "GBrain deps reinstalled"
|
|
pass "GBrain CLI (after dep fix)"
|
|
else
|
|
fail "GBrain CLI — won't start"
|
|
fi
|
|
fi
|
|
else
|
|
[ -z "$GBRAIN_DIR" ] && fail "GBrain CLI — not found"
|
|
[ -z "$BUN_PATH" ] && skip "GBrain CLI — bun not available"
|
|
fi
|
|
|
|
# ── 3. GBrain database ────────────────────────────────────
|
|
if [ -n "$DB_URL" ] && [ -n "$GBRAIN_DIR" ] && [ -n "$BUN_PATH" ]; then
|
|
DOCTOR_OUT=$(DATABASE_URL="$DB_URL" GBRAIN_DATABASE_URL="$DB_URL" timeout 20 "$BUN_PATH" run "$GBRAIN_DIR/src/cli.ts" doctor 2>&1)
|
|
if echo "$DOCTOR_OUT" | grep -q "Health score\|brain_score\|Health Check"; then
|
|
SCORE=$(echo "$DOCTOR_OUT" | grep -oP 'Health score: \K[0-9]+' || echo '?')
|
|
pass "GBrain database (health score: $SCORE/100)"
|
|
else
|
|
fail "GBrain database — doctor returned no health data"
|
|
fi
|
|
else
|
|
[ -z "$DB_URL" ] && fail "GBrain database — no DATABASE_URL or GBRAIN_DATABASE_URL"
|
|
[ -z "$GBRAIN_DIR" ] && skip "GBrain database — gbrain not found"
|
|
fi
|
|
|
|
# ── 4. GBrain worker process ──────────────────────────────
|
|
if [ -n "$GBRAIN_DIR" ] && [ -n "$BUN_PATH" ] && [ -n "$DB_URL" ]; then
|
|
if [ -f /tmp/gbrain-worker.pid ] && kill -0 "$(cat /tmp/gbrain-worker.pid)" 2>/dev/null; then
|
|
pass "GBrain worker (PID: $(cat /tmp/gbrain-worker.pid))"
|
|
else
|
|
# Auto-fix: start worker
|
|
DATABASE_URL="$DB_URL" GBRAIN_DATABASE_URL="$DB_URL" GBRAIN_ALLOW_SHELL_JOBS=1 \
|
|
nohup "$BUN_PATH" run "$GBRAIN_DIR/src/cli.ts" jobs work --concurrency 2 > /tmp/gbrain-worker.log 2>&1 &
|
|
echo $! > /tmp/gbrain-worker.pid
|
|
sleep 2
|
|
if kill -0 "$(cat /tmp/gbrain-worker.pid)" 2>/dev/null; then
|
|
fixed "GBrain worker started"
|
|
pass "GBrain worker (PID: $(cat /tmp/gbrain-worker.pid))"
|
|
else
|
|
fail "GBrain worker — failed to start (check /tmp/gbrain-worker.log)"
|
|
fi
|
|
fi
|
|
else
|
|
skip "GBrain worker — prerequisites missing"
|
|
fi
|
|
|
|
# ── 5. OpenClaw plugin health (if OpenClaw is installed) ──
|
|
OPENCLAW_CODEX_ZOD="/app/node_modules/openclaw/dist/extensions/codex/node_modules/zod"
|
|
if [ -d "$OPENCLAW_CODEX_ZOD" ]; then
|
|
CORE_CJS="$OPENCLAW_CODEX_ZOD/v4/core/core.cjs"
|
|
if [ -f "$CORE_CJS" ] && node -e "require('$OPENCLAW_CODEX_ZOD/v4/core/index.cjs')" 2>/dev/null; then
|
|
pass "OpenClaw Codex plugin (Zod CJS)"
|
|
else
|
|
# Auto-fix: reinstall zod
|
|
cd "$OPENCLAW_CODEX_ZOD" && npm install zod@4 --force --silent 2>/dev/null
|
|
if [ -f "$CORE_CJS" ] && node -e "require('$OPENCLAW_CODEX_ZOD/v4/core/index.cjs')" 2>/dev/null; then
|
|
fixed "Codex Zod core.cjs reinstalled"
|
|
pass "OpenClaw Codex plugin (Zod CJS after fix)"
|
|
else
|
|
fail "OpenClaw Codex plugin — Zod fix failed"
|
|
fi
|
|
fi
|
|
else
|
|
skip "OpenClaw Codex plugin — not installed"
|
|
fi
|
|
|
|
# ── 6. OpenClaw gateway (if running) ─────────────────────
|
|
OPENCLAW_PORT="${OPENCLAW_GATEWAY_PORT:-18789}"
|
|
if curl -sf "http://127.0.0.1:$OPENCLAW_PORT/" >/dev/null 2>&1; then
|
|
pass "OpenClaw gateway (port $OPENCLAW_PORT)"
|
|
else
|
|
skip "OpenClaw gateway — not responding (may not be running yet)"
|
|
fi
|
|
|
|
# ── 7. Embedding API key ─────────────────────────────────
|
|
EMBED_KEY="${OPENAI_API_KEY:-${VOYAGE_API_KEY:-}}"
|
|
if [ -n "$EMBED_KEY" ]; then
|
|
pass "Embedding API key set"
|
|
else
|
|
fail "Embedding API key — neither OPENAI_API_KEY nor VOYAGE_API_KEY is set"
|
|
fi
|
|
|
|
# ── 8. Brain repo (if configured) ────────────────────────
|
|
BRAIN_PATH="${GBRAIN_BRAIN_PATH:-/data/brain}"
|
|
if [ -d "$BRAIN_PATH/.git" ]; then
|
|
PAGE_COUNT=$(find "$BRAIN_PATH" -name "*.md" -not -path "*/.git/*" 2>/dev/null | wc -l)
|
|
pass "Brain repo ($PAGE_COUNT pages at $BRAIN_PATH)"
|
|
elif [ -d "$BRAIN_PATH" ]; then
|
|
pass "Brain directory exists ($BRAIN_PATH, not a git repo)"
|
|
else
|
|
skip "Brain repo — $BRAIN_PATH not found"
|
|
fi
|
|
|
|
# ── User-defined tests (~/.gbrain/smoke-tests.d/*.sh) ────
|
|
USER_TESTS_DIR="${HOME}/.gbrain/smoke-tests.d"
|
|
if [ -d "$USER_TESTS_DIR" ]; then
|
|
for test_script in "$USER_TESTS_DIR"/*.sh; do
|
|
[ -f "$test_script" ] || continue
|
|
TEST_NAME=$(basename "$test_script" .sh)
|
|
echo " Running user test: $TEST_NAME"
|
|
if bash "$test_script" 2>/dev/null; then
|
|
pass "User: $TEST_NAME"
|
|
else
|
|
fail "User: $TEST_NAME"
|
|
fi
|
|
done
|
|
fi
|
|
|
|
# ── Summary ─────────────────────────────────────────────────
|
|
echo ""
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
PASSED=$((TOTAL - FAILURES))
|
|
echo "Results: $PASSED/$TOTAL passed, $FIXES auto-fixed, $SKIPPED skipped"
|
|
if [ $FAILURES -gt 0 ]; then
|
|
echo "⚠️ $FAILURES failure(s) remain — manual intervention needed"
|
|
else
|
|
echo "✅ All smoke tests passed"
|
|
fi
|
|
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
|
|
echo "$(timestamp) Summary: $PASSED/$TOTAL passed, $FIXES fixed, $FAILURES failed, $SKIPPED skipped" >> "$LOG"
|
|
|
|
exit $FAILURES
|