#!/usr/bin/env bash
set -euo pipefail
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
BIN="$REPO_ROOT/packaging/binary/dist/wigolo"
PASS=0
FAILN=0
log() { printf '\n### %s\n' "$*"; }
ok() { printf 'PASS: %s\n' "$*"; PASS=$((PASS + 1)); }
bad() { printf 'FAIL: %s\n' "$*" >&2; FAILN=$((FAILN + 1)); }
freshdir() { mktemp -d "${TMPDIR:-/tmp}/wigolo-verify-bin.XXXXXX"; }
PKG_VERSION="$(node -e "process.stdout.write(require('$REPO_ROOT/package.json').version)")"
log "package.json version: $PKG_VERSION"
log "npm run build:binary (fresh dist -> bundle -> pkg)"
( cd "$REPO_ROOT" && npm run build:binary ) || { bad "build:binary failed"; exit 1; }
[ -x "$BIN" ] || { bad "binary not found / not executable: $BIN"; exit 1; }
SIZE_BYTES="$(wc -c < "$BIN" | tr -d ' ')"
SIZE_MB="$(( SIZE_BYTES / 1024 / 1024 ))"
log "binary size: ${SIZE_MB} MB ($SIZE_BYTES bytes)"
log "(a) --help exit 0"
D="$(freshdir)"
if WIGOLO_DATA_DIR="$D" "$BIN" --help >/dev/null 2>&1; then
ok "(a) --help exit 0"
else
bad "(a) --help non-zero"
fi
rm -rf "$D"
log "(b) --version == package.json"
D="$(freshdir)"
GOT="$(WIGOLO_DATA_DIR="$D" "$BIN" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^ ]*' | head -n 1)"
echo "binary --version: $GOT"
if [ "$GOT" = "$PKG_VERSION" ]; then
ok "(b) version matches ($GOT)"
else
bad "(b) version mismatch: binary=$GOT pkg=$PKG_VERSION"
fi
rm -rf "$D"
log "(c) doctor --json parses"
D="$(freshdir)"
DOC_OUT="$(WIGOLO_DATA_DIR="$D" "$BIN" doctor --json 2>/dev/null || true)"
if node -e "JSON.parse(process.argv[1])" "$DOC_OUT" 2>/dev/null; then
DSTATUS="$(node -e "process.stdout.write(String(JSON.parse(process.argv[1]).status))" "$DOC_OUT")"
ok "(c) doctor --json parses (status=$DSTATUS)"
else
bad "(c) doctor --json did not parse"
fi
rm -rf "$D"
log "(d) cache write + sqlite-vec vec_version / vec tables"
D="$(freshdir)"
FETCH_ERR="$D/fetch.err"
WIGOLO_DATA_DIR="$D" "$BIN" fetch https://example.com >/dev/null 2>"$FETCH_ERR" || bad "(d) fetch failed"
if grep -qiE 'sqlite-vec extension failed|vector search disabled|dylib\.dylib|no such table: vec_id_map' "$FETCH_ERR"; then
bad "(d) sqlite-vec soft-fail signal present"
else
if command -v sqlite3 >/dev/null 2>&1; then
VECTABLES="$(sqlite3 "$D/wigolo.db" "SELECT count(*) FROM sqlite_master WHERE name LIKE 'vec_%';" 2>/dev/null || echo 0)"
if [ "${VECTABLES:-0}" -gt 0 ]; then
ok "(d) sqlite-vec loaded (vec tables present: $VECTABLES, no soft-fail)"
else
bad "(d) no vec_* tables — extension likely did not load"
fi
else
ok "(d) sqlite-vec loaded (no soft-fail signal; sqlite3 CLI unavailable for vec_version)"
fi
fi
if [ -f "$D/native/vec0.dylib" ] || [ -f "$D/native/vec0.so" ] || [ -f "$D/native/vec0.dll" ]; then
ok "(d) extension copied out of snapshot to <dataDir>/native/"
else
bad "(d) no extracted extension under <dataDir>/native/"
fi
rm -rf "$D"
log "(e) warmup --embeddings (real embed, ORT dylib extraction)"
D="$(freshdir)"
EMB_ERR="$D/embed.err"
if WIGOLO_DATA_DIR="$D" timeout 300 "$BIN" warmup --embeddings >/dev/null 2>"$EMB_ERR"; then
if grep -qiE 'Embedding model ready|Embeddings:\s+ok' "$EMB_ERR"; then
ok "(e) embeddings ready (ORT + sibling dylib load OK)"
else
bad "(e) warmup --embeddings exit 0 but no 'ready' signal"
fi
else
bad "(e) warmup --embeddings failed"
fi
rm -rf "$D"
log "(f) mcp stdio handshake + ListTools"
D="$(freshdir)"
MCP_OUT="$(printf '%s\n' \
'{"jsonrpc":"2.0","id":1,"method":"initialize","params":{"protocolVersion":"2024-11-05","capabilities":{},"clientInfo":{"name":"verify","version":"1"}}}' \
'{"jsonrpc":"2.0","method":"notifications/initialized"}' \
'{"jsonrpc":"2.0","id":2,"method":"tools/list"}' \
| WIGOLO_DATA_DIR="$D" timeout 60 "$BIN" mcp 2>/dev/null)"
TOOLCOUNT="$(printf '%s' "$MCP_OUT" | node -e '
let d="";process.stdin.on("data",c=>d+=c).on("end",()=>{
for(const l of d.trim().split(/\n/)){try{const j=JSON.parse(l);
if(j.id===2 && j.result && Array.isArray(j.result.tools)){console.log(j.result.tools.length);return;}}catch{}}
console.log(0);
});')"
if [ "${TOOLCOUNT:-0}" -eq 10 ]; then
ok "(f) mcp handshake OK, 10 tools listed"
else
bad "(f) mcp handshake listed ${TOOLCOUNT} tools (expected 10)"
fi
rm -rf "$D"
log "(g) warmup --browser (playwright cli spawn seam)"
D="$(freshdir)"
BROW_ERR="$D/brow.err"
if WIGOLO_DATA_DIR="$D" timeout 400 "$BIN" warmup --browser >/dev/null 2>"$BROW_ERR"; then
if grep -qiE 'Browser:\s+ok|playwright installed' "$BROW_ERR"; then
ok "(g) browser warmup OK (cli spawn seam works)"
else
bad "(g) warmup --browser exit 0 but no 'ok' signal"
fi
else
bad "(g) warmup --browser failed"
fi
rm -rf "$D"
log "(h) Gatekeeper / quarantine (documented, not solved)"
if [ "$(uname -s)" = "Darwin" ]; then
Q="$(mktemp "${TMPDIR:-/tmp}/wigolo-quarantined.XXXXXX")"
cp "$BIN" "$Q"
xattr -w com.apple.quarantine "0081;;;Safari" "$Q" 2>/dev/null || true
if "$Q" --version >/dev/null 2>&1; then
echo "(h) quarantined binary executed (host may have relaxed GK / already assessed)"
else
echo "(h) quarantined unsigned binary BLOCKED by Gatekeeper — needs release-time notarization"
fi
rm -f "$Q"
ok "(h) documented"
else
ok "(h) not macOS — Gatekeeper N/A"
fi
log "dist-freshness: binary --version == package.json"
D="$(freshdir)"
GOT2="$(WIGOLO_DATA_DIR="$D" "$BIN" --version 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+[^ ]*' | head -n 1)"
if [ "$GOT2" = "$PKG_VERSION" ]; then ok "dist-freshness: $GOT2"; else bad "dist-freshness mismatch: $GOT2 != $PKG_VERSION"; fi
rm -rf "$D"
log "SUMMARY: $PASS passed, $FAILN failed (binary ${SIZE_MB} MB, $(uname -s)/$(uname -m))"
[ "$FAILN" -eq 0 ] || exit 1