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:
Garry Tan
2026-04-23 23:53:53 -07:00
committed by GitHub
parent 78ba0b5b53
commit 246cd8be46
9 changed files with 404 additions and 4 deletions

View File

@@ -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.**

View File

@@ -1 +1 @@
0.19.0
0.19.1

View File

@@ -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` |

View File

@@ -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
View 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

View File

@@ -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` |

View File

@@ -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
View 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).

View File

@@ -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