v0.19.1 — smoke-test skillpack (post-restart health + auto-fix) (#369)
* 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>
This commit is contained in:
14
CHANGELOG.md
14
CHANGELOG.md
@@ -2,6 +2,20 @@
|
||||
|
||||
All notable changes to GBrain will be documented in this file.
|
||||
|
||||
## [0.19.1] - 2026-04-24
|
||||
|
||||
### Added
|
||||
|
||||
- New `gbrain smoke-test` CLI command. Runs 8 post-restart health checks with auto-fix: Bun runtime, gbrain CLI loads, database reachable via doctor, worker process liveness, OpenClaw Codex plugin Zod CJS (auto-reinstall when the zod@4 package ships without `core.cjs`), OpenClaw gateway responding, embedding API key present, brain repo exists. User-extensible via drop-in scripts at `~/.gbrain/smoke-tests.d/*.sh`. Designed to run from OpenClaw bootstrap hooks so every container restart automatically verifies and repairs the environment.
|
||||
- `skills/smoke-test/` skill with full documentation, pattern for adding new tests, and a growing known-issue database (starting with the Zod `core.cjs` publish bug discovered 2026-04-23).
|
||||
- `smoke-test` routing entry in `skills/RESOLVER.md` under Operational, so agents reach the skill on "post-restart health", "did the container restart break anything", and "smoke test" triggers.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Doctor's `resolver_health` check now passes on fresh installs: `skills/smoke-test/` is wired into `RESOLVER.md` so the cascade that was flagging it unreachable (and dragging `gbrain doctor` to exit 1 on healthy brains) no longer fires.
|
||||
- `skills/smoke-test/SKILL.md` gains the required `## Anti-Patterns` and `## Output Format` sections, so `test/skills-conformance.test.ts` no longer flags it.
|
||||
- `llms-full.txt` regenerated to reflect the new RESOLVER row. The drift guard in `test/build-llms.test.ts` now passes.
|
||||
|
||||
## [0.19.0] - 2026-04-22
|
||||
|
||||
## **Your OpenClaw finally learns. Say "skillify it!" and every new failure becomes a durable skill.**
|
||||
|
||||
@@ -1106,6 +1106,7 @@ This is the dispatcher. Skills are the implementation. **Read the skill file bef
|
||||
| "Create a skill", "improve this skill" | `skills/skill-creator/SKILL.md` |
|
||||
| "Skillify this", "is this a skill?", "make this proper" | `skills/skillify/SKILL.md` |
|
||||
| "Is gbrain healthy?", morning health check, skillpack-check | `skills/skillpack-check/SKILL.md` |
|
||||
| Post-restart health + auto-fix, "did the container restart break anything", smoke test | `skills/smoke-test/SKILL.md` |
|
||||
| Cross-modal review, second opinion | `skills/cross-modal-review/SKILL.md` |
|
||||
| "Validate skills", skill health check | `skills/testing/SKILL.md` |
|
||||
| Webhook setup, external event processing | `skills/webhook-transforms/SKILL.md` |
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "gbrain",
|
||||
"version": "0.19.0",
|
||||
"version": "0.19.1",
|
||||
"description": "Postgres-native personal knowledge brain with hybrid RAG search",
|
||||
"type": "module",
|
||||
"main": "src/core/index.ts",
|
||||
|
||||
204
scripts/smoke-test.sh
Executable file
204
scripts/smoke-test.sh
Executable file
@@ -0,0 +1,204 @@
|
||||
#!/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
|
||||
@@ -54,6 +54,7 @@ This is the dispatcher. Skills are the implementation. **Read the skill file bef
|
||||
| "Create a skill", "improve this skill" | `skills/skill-creator/SKILL.md` |
|
||||
| "Skillify this", "is this a skill?", "make this proper" | `skills/skillify/SKILL.md` |
|
||||
| "Is gbrain healthy?", morning health check, skillpack-check | `skills/skillpack-check/SKILL.md` |
|
||||
| Post-restart health + auto-fix, "did the container restart break anything", smoke test | `skills/smoke-test/SKILL.md` |
|
||||
| Cross-modal review, second opinion | `skills/cross-modal-review/SKILL.md` |
|
||||
| "Validate skills", skill health check | `skills/testing/SKILL.md` |
|
||||
| Webhook setup, external event processing | `skills/webhook-transforms/SKILL.md` |
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
"name": "gbrain",
|
||||
"version": "0.10.0",
|
||||
"conformance_version": "1.0.0",
|
||||
"description": "Personal knowledge brain with hybrid RAG search — GStack mod for agent platforms",
|
||||
"description": "Personal knowledge brain with hybrid RAG search \u2014 GStack mod for agent platforms",
|
||||
"skills": [
|
||||
{
|
||||
"name": "ingest",
|
||||
@@ -143,6 +143,11 @@
|
||||
"name": "skillpack-check",
|
||||
"path": "skillpack-check/SKILL.md",
|
||||
"description": "Agent-readable gbrain health report. Wraps doctor + apply-migrations --list into one JSON blob with exit codes. Cron-friendly for morning-briefing pipelines."
|
||||
},
|
||||
{
|
||||
"name": "smoke-test",
|
||||
"path": "smoke-test/SKILL.md",
|
||||
"description": "Post-restart smoke tests + auto-fix for gbrain and OpenClaw environments"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
|
||||
159
skills/smoke-test/SKILL.md
Normal file
159
skills/smoke-test/SKILL.md
Normal file
@@ -0,0 +1,159 @@
|
||||
---
|
||||
name: smoke-test
|
||||
description: |
|
||||
Post-restart smoke tests + auto-fix for gbrain and OpenClaw environments.
|
||||
Tests critical services, auto-fixes known issues, extensible via user-defined
|
||||
test scripts in ~/.gbrain/smoke-tests.d/*.sh.
|
||||
triggers:
|
||||
- "smoke test"
|
||||
- "run smoke tests"
|
||||
- "container restart check"
|
||||
- "health check"
|
||||
- "did the restart break anything"
|
||||
tools:
|
||||
- exec
|
||||
- read
|
||||
mutating: true
|
||||
---
|
||||
|
||||
# Smoke Test Skillpack
|
||||
|
||||
> Run `gbrain smoke-test` or `bash scripts/smoke-test.sh` after any container restart.
|
||||
|
||||
## Contract
|
||||
|
||||
This skill guarantees:
|
||||
- 8 core tests verify gbrain + OpenClaw health after restart
|
||||
- Known failures are auto-fixed before reporting
|
||||
- User-extensible via `~/.gbrain/smoke-tests.d/*.sh` drop-in scripts
|
||||
- Results logged to `/tmp/gbrain-smoke-test.log`
|
||||
- Exit code = number of unfixed failures (0 = all pass)
|
||||
|
||||
## Built-in Tests
|
||||
|
||||
| # | Test | Auto-Fix |
|
||||
|---|------|----------|
|
||||
| 1 | Bun runtime | Install from bun.sh |
|
||||
| 2 | GBrain CLI loads | Reinstall deps |
|
||||
| 3 | GBrain database (doctor) | — |
|
||||
| 4 | GBrain worker process | Start worker |
|
||||
| 5 | OpenClaw Codex plugin (Zod CJS) | `npm install zod@4 --force` |
|
||||
| 6 | OpenClaw gateway | — (may not be started yet) |
|
||||
| 7 | Embedding API key | — (check .env) |
|
||||
| 8 | Brain repo exists | — |
|
||||
|
||||
## Usage
|
||||
|
||||
### CLI
|
||||
```bash
|
||||
gbrain smoke-test
|
||||
```
|
||||
|
||||
### Direct
|
||||
```bash
|
||||
bash scripts/smoke-test.sh
|
||||
```
|
||||
|
||||
### From OpenClaw bootstrap
|
||||
Add to your `ensure-services.sh` or equivalent:
|
||||
```bash
|
||||
bash /path/to/gbrain/scripts/smoke-test.sh >> /tmp/bootstrap.log 2>&1
|
||||
```
|
||||
|
||||
### From an agent
|
||||
```
|
||||
exec: bash /data/gbrain/scripts/smoke-test.sh
|
||||
```
|
||||
|
||||
## Adding Custom Tests
|
||||
|
||||
Create executable scripts in `~/.gbrain/smoke-tests.d/`:
|
||||
|
||||
```bash
|
||||
# ~/.gbrain/smoke-tests.d/check-redis.sh
|
||||
#!/bin/bash
|
||||
redis-cli ping | grep -q PONG
|
||||
```
|
||||
|
||||
Rules:
|
||||
- Exit 0 = pass, non-zero = fail
|
||||
- Filename becomes the test name (e.g. `check-redis` from `check-redis.sh`)
|
||||
- Keep tests fast (< 10s each)
|
||||
- Tests run in alphabetical order
|
||||
|
||||
## Adding Built-in Tests
|
||||
|
||||
Edit `scripts/smoke-test.sh`. Follow this pattern:
|
||||
|
||||
```bash
|
||||
# ── N. [Service Name] ──────────────────────────────────────
|
||||
if [test condition]; then
|
||||
pass "[Service Name]"
|
||||
else
|
||||
# Auto-fix attempt
|
||||
[fix command]
|
||||
if [re-test condition]; then
|
||||
fixed "[What was fixed]"
|
||||
pass "[Service Name] (after fix)"
|
||||
else
|
||||
fail "[Service Name] — [error detail]"
|
||||
fi
|
||||
fi
|
||||
```
|
||||
|
||||
### Design rules:
|
||||
1. **Test first** — never fix without confirming broken
|
||||
2. **Re-test after fix** — verify the fix worked
|
||||
3. **Timeout everything** — `timeout N` on any command that could hang
|
||||
4. **Use helpers** — `pass()`, `fail()`, `fixed()`, `skip()`
|
||||
5. **Idempotent fixes** — safe to run repeatedly
|
||||
6. **Skip gracefully** — `skip()` when a prerequisite is missing, don't fail
|
||||
|
||||
## Environment Variables
|
||||
|
||||
| Var | Default | Description |
|
||||
|-----|---------|-------------|
|
||||
| `GBRAIN_SMOKE_LOG` | `/tmp/gbrain-smoke-test.log` | Log file path |
|
||||
| `GBRAIN_DIR_OVERRIDE` | (auto-detect) | Force gbrain install path |
|
||||
| `GBRAIN_DATABASE_URL` | (from .env) | Database connection URL |
|
||||
| `OPENCLAW_GATEWAY_PORT` | `18789` | Gateway port to test |
|
||||
| `GBRAIN_BRAIN_PATH` | `/data/brain` | Brain repo path |
|
||||
|
||||
## Known Issues & Their Auto-Fixes
|
||||
|
||||
### Codex Zod core.cjs Missing (discovered 2026-04-23)
|
||||
- **Symptom:** `Cannot find module './core.cjs'` → all Codex ACP sessions fail
|
||||
- **Cause:** Zod v4 npm package ships without `core.cjs` in some installs
|
||||
- **Auto-fix:** `npm install zod@4 --force` in the codex extension's zod dir
|
||||
- **Persistence:** Does NOT survive container restart (gateway reinstalls deps)
|
||||
- This is why smoke tests must run on every restart
|
||||
|
||||
### GBrain Worker Auth Failure
|
||||
- **Symptom:** Worker can't connect to DB
|
||||
- **Cause:** `GBRAIN_DATABASE_URL` not propagated to worker subprocess
|
||||
- **Auto-fix:** Script explicitly passes both `DATABASE_URL` and `GBRAIN_DATABASE_URL`
|
||||
|
||||
## Anti-Patterns
|
||||
|
||||
- ❌ Running smoke tests on every chat turn. Once per container restart (or
|
||||
on user request) is plenty. The script is cheap but it's not free.
|
||||
- ❌ Writing a user drop-in without `timeout N` around any command that
|
||||
could hang. A single hung drop-in stalls every subsequent run.
|
||||
- ❌ Auto-fixing without confirming the check is actually broken first.
|
||||
The `pass → fail-detected → fix → re-test` loop is the contract; fixes
|
||||
that skip the re-test can report success on a still-broken state.
|
||||
- ❌ Treating `skip` as `fail`. Missing prerequisites (no OpenClaw installed,
|
||||
no brain repo configured) are skips, not failures. Exit code = count of
|
||||
real failures, not skipped checks.
|
||||
- ❌ Hardcoding paths in a user drop-in. Read env vars
|
||||
(`GBRAIN_DATABASE_URL`, `HOME`, etc.) so the script travels across
|
||||
container rebuilds.
|
||||
|
||||
## Output Format
|
||||
|
||||
The script writes a one-line status per check to stdout (✅/❌/🔧/⏭️) plus a
|
||||
final summary line: `Results: N/M passed, F auto-fixed, S skipped`. A
|
||||
structured timestamped log appends to `$GBRAIN_SMOKE_LOG`
|
||||
(default `/tmp/gbrain-smoke-test.log`) for post-run forensics. Exit code
|
||||
equals the count of unfixed failures (0 = all pass, positive integer =
|
||||
count of remaining failures).
|
||||
18
src/cli.ts
18
src/cli.ts
@@ -19,7 +19,7 @@ for (const op of operations) {
|
||||
}
|
||||
|
||||
// CLI-only commands that bypass the operation layer
|
||||
const CLI_ONLY = new Set(['init', 'upgrade', 'post-upgrade', 'check-update', 'integrations', 'publish', 'check-backlinks', 'lint', 'report', 'import', 'export', 'files', 'embed', 'serve', 'call', 'config', 'doctor', 'migrate', 'eval', 'sync', 'extract', 'features', 'autopilot', 'graph-query', 'jobs', 'agent', 'apply-migrations', 'skillpack-check', 'skillpack', 'resolvers', 'integrity', 'repair-jsonb', 'orphans', 'sources', 'dream', 'check-resolvable', 'routing-eval', 'skillify']);
|
||||
const CLI_ONLY = new Set(['init', 'upgrade', 'post-upgrade', 'check-update', 'integrations', 'publish', 'check-backlinks', 'lint', 'report', 'import', 'export', 'files', 'embed', 'serve', 'call', 'config', 'doctor', 'migrate', 'eval', 'sync', 'extract', 'features', 'autopilot', 'graph-query', 'jobs', 'agent', 'apply-migrations', 'skillpack-check', 'skillpack', 'resolvers', 'integrity', 'repair-jsonb', 'orphans', 'sources', 'dream', 'check-resolvable', 'routing-eval', 'skillify', 'smoke-test']);
|
||||
|
||||
async function main() {
|
||||
// Parse global flags (--quiet / --progress-json / --progress-interval)
|
||||
@@ -381,6 +381,22 @@ async function handleCliOnly(command: string, args: string[]) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'smoke-test') {
|
||||
// Run smoke tests — no DB connection needed, the script handles its own checks
|
||||
const { execSync } = await import('child_process');
|
||||
const { resolve, dirname } = await import('path');
|
||||
const { fileURLToPath } = await import('url');
|
||||
const scriptDir = dirname(fileURLToPath(import.meta.url));
|
||||
const scriptPath = resolve(scriptDir, '..', 'scripts', 'smoke-test.sh');
|
||||
try {
|
||||
execSync(`bash "${scriptPath}"`, { stdio: 'inherit', env: { ...process.env } });
|
||||
} catch (e: any) {
|
||||
// Non-zero exit = some tests failed (exit code = failure count)
|
||||
process.exit(e.status ?? 1);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (command === 'dream') {
|
||||
// Dream mirrors doctor's pattern: filesystem phases run without a DB,
|
||||
// so an engine connection failure is non-fatal. runCycle honestly
|
||||
|
||||
Reference in New Issue
Block a user