#!/usr/bin/env bash
# =============================================================================
# reverie-audit — local install auditor for reveried (the reverie memory daemon)
# =============================================================================
#
# WHAT THIS DOES
#   Runs a series of read-only diagnostic checks against your local reverie /
#   reveried ("engram") install to answer the question: "why is reverie having
#   no effect?" — i.e. is memory actually being captured and, crucially, surfaced
#   back into your coding sessions. It collects the results into a single JSON
#   document and (optionally) uploads it to the Cerebral audit portal so the
#   maintainers can see exactly what your box reports.
#
# WHAT IT DOES *NOT* DO
#   - No writes to your system. Every check is read-only (ps / curl /health /
#     stat / jq over config). It never touches your DB, config, or secrets.
#   - No secret exfiltration. It reads config *keys*, never values. It does not
#     dump environment variables. Home-directory paths are collapsed to "~".
#   - No `sudo`, no package installs, no network calls except (a) your LOCAL
#     reveried /health endpoint and (b) the single results upload you can skip.
#
# SAFETY / "curl | bash"
#   You can run this two ways:
#     1) REVIEW FIRST (recommended):
#          curl -fsSL https://audit.cerebral.work/audit.sh -o audit.sh
#          # read it — it's this file, heavily commented
#          sha256sum audit.sh                 # compare to:
#          curl -fsSL https://audit.cerebral.work/audit.sh.sha256
#          bash audit.sh
#     2) ONE-LINER (convenient, pipes remote code to your shell):
#          curl -fsSL https://audit.cerebral.work/audit.sh | bash
#   The one-liner is offered for convenience. Piping remote code to a shell is
#   inherently trust-on-first-use — the published SHA-256 above lets you verify
#   the bytes match what you can read here. If you don't trust it, use path (1).
#
# FLAGS
#   --no-upload     run all checks, print JSON locally, do NOT upload
#   --print         also pretty-print a human summary to the terminal (default on
#                   when stdout is a TTY)
#   --port <N>      reveried port to probe (default: auto-detect, then 7437)
#
# EXIT CODE
#   0 if the audit ran (regardless of findings). Non-zero only on its own error.
# =============================================================================

set -u

# --- values injected by the portal when served over HTTP -------------------
# When you download this from https://audit.cerebral.work/audit.sh these two
# placeholders are replaced with the live upload endpoint + a write-only token.
# The token can ONLY append an audit object to the "incoming/" prefix of the
# bucket — it cannot read, list, or delete anything. It is intentionally public.
AUDIT_UPLOAD_URL="https://audit.cerebral.work/api/audit"
AUDIT_TOKEN="rvat_b4b9ab8f8e3f65b089e05a50dd1601f6fb9ebd451d1728b4"

# --- arg parsing -----------------------------------------------------------
DO_UPLOAD=1
DO_PRINT=0
FORCE_PORT=""
[ -t 1 ] && DO_PRINT=1
while [ $# -gt 0 ]; do
  case "$1" in
    --no-upload) DO_UPLOAD=0 ;;
    --print)     DO_PRINT=1 ;;
    --port)      shift; FORCE_PORT="${1:-}" ;;
    -h|--help)   sed -n '2,60p' "$0" 2>/dev/null; exit 0 ;;
    *) echo "unknown flag: $1" >&2 ;;
  esac
  shift
done

# --- tiny helpers ----------------------------------------------------------
# collapse absolute home paths to ~ so uploaded evidence carries no usernames
redact() { printf '%s' "${1:-}" | sed "s#${HOME}#~#g"; }
# JSON-escape an arbitrary string (uses jq if present, else a safe fallback)
jstr() {
  if command -v jq >/dev/null 2>&1; then jq -Rn --arg s "${1:-}" '$s'
  else printf '%s' "${1:-}" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | awk 'BEGIN{printf "\""} {printf "%s", $0} END{printf "\""}'; fi
}

# checks[] accumulator (one JSON object per line, assembled at the end)
CHECKS_FILE="$(mktemp)"
trap 'rm -f "$CHECKS_FILE"' EXIT
PASS=0; WARN=0; FAIL=0; INFO=0
# add_check <id> <label> <status pass|warn|fail|info> <detail> <evidence>
add_check() {
  local id="$1" label="$2" status="$3" detail="$4" evidence="$5"
  case "$status" in
    pass) PASS=$((PASS+1)) ;; warn) WARN=$((WARN+1)) ;;
    fail) FAIL=$((FAIL+1)) ;; *) INFO=$((INFO+1)); status="info" ;;
  esac
  printf '{"id":%s,"label":%s,"status":%s,"detail":%s,"evidence":%s}\n' \
    "$(jstr "$id")" "$(jstr "$label")" "$(jstr "$status")" \
    "$(jstr "$detail")" "$(jstr "$(redact "$evidence")")" >> "$CHECKS_FILE"
}

# =============================================================================
# CHECK 1 — binary present on PATH / in ~/.local/bin
# =============================================================================
BIN=""
for cand in engram reveried; do
  if command -v "$cand" >/dev/null 2>&1; then BIN="$(command -v "$cand")"; break; fi
done
[ -z "$BIN" ] && [ -x "$HOME/.local/bin/engram" ] && BIN="$HOME/.local/bin/engram"
[ -z "$BIN" ] && [ -x "$HOME/.local/bin/reveried" ] && BIN="$HOME/.local/bin/reveried"
if [ -n "$BIN" ]; then
  add_check binary-present "reveried binary installed" pass "found on system" "$BIN"
else
  add_check binary-present "reveried binary installed" fail \
    "no 'engram' or 'reveried' binary on PATH or in ~/.local/bin — nothing is installed" ""
fi

# =============================================================================
# CHECK 2 — binary version
# =============================================================================
BIN_VER=""
if [ -n "$BIN" ]; then
  BIN_VER="$("$BIN" --version 2>/dev/null | head -1)"
  if [ -n "$BIN_VER" ]; then
    add_check binary-version "binary reports a version" pass "$BIN_VER" "$BIN_VER"
  else
    add_check binary-version "binary reports a version" warn "binary present but --version returned nothing" ""
  fi
fi

# =============================================================================
# CHECK 3 — daemon process running (reveried serve)
# =============================================================================
SERVE_PROC="$(ps -eo pid,args 2>/dev/null | grep -E '(reveried|engram) serve' | grep -v grep | head -1)"
if [ -n "$SERVE_PROC" ]; then
  add_check daemon-running "reveried serve process" pass "daemon process is running" "$SERVE_PROC"
else
  add_check daemon-running "reveried serve process" fail \
    "no 'reveried serve' process found — the daemon is not running, so nothing is captured or served" ""
fi

# =============================================================================
# CHECK 4 — port + /health endpoint (the load-bearing signal)
# =============================================================================
# resolve port: --port flag > --port on the running serve process > port file > 7437
PORT="${FORCE_PORT:-}"
if [ -z "$PORT" ]; then
  # most reliable: the actual --port arg of the live 'reveried serve' process
  PORT="$(ps -eo args 2>/dev/null | grep -E '(reveried|engram) serve' | grep -v grep \
          | grep -oE -- '--port[= ]+[0-9]+' | grep -oE '[0-9]+' | head -1)"
fi
if [ -z "$PORT" ] && [ -f "$HOME/.engram/reveried.port" ]; then
  # file may hold "http://127.0.0.1:7437", "127.0.0.1:7437", or "7437" — take
  # the segment after the LAST colon and keep only its digits
  PF="$(cat "$HOME/.engram/reveried.port" 2>/dev/null)"
  PORT="$(printf '%s' "${PF##*:}" | tr -dc '0-9')"
fi
[ -z "$PORT" ] && PORT=7437
HEALTH="$(curl -s --max-time 4 "http://127.0.0.1:${PORT}/health" 2>/dev/null)"
if [ -n "$HEALTH" ]; then
  add_check port-health "/health endpoint reachable" pass "responded on 127.0.0.1:${PORT}" "$HEALTH"
  # parse individual signals out of the health JSON if jq is available
  if command -v jq >/dev/null 2>&1; then
    H_STATUS="$(printf '%s' "$HEALTH" | jq -r '.status // "?"')"
    H_DB="$(printf '%s' "$HEALTH" | jq -r '.db_healthy // false')"
    H_VER="$(printf '%s' "$HEALTH" | jq -r '.version // "?"')"
    H_SEARCH="$(printf '%s' "$HEALTH" | jq -r '.search.observed // false')"
    [ "$H_STATUS" = "ok" ] \
      && add_check health-status "service status" pass "status=ok" "$H_STATUS" \
      || add_check health-status "service status" fail "status=$H_STATUS (not ok)" "$H_STATUS"
    [ "$H_DB" = "true" ] \
      && add_check db-healthy "database health" pass "db_healthy=true" "$H_DB" \
      || add_check db-healthy "database health" fail "db_healthy=$H_DB — the store is not healthy" "$H_DB"
    # search.observed=false is THE classic 'no effect' signal: memory may be
    # stored, but retrieval/search has never actually been exercised this run,
    # which is exactly what "reverie isn't doing anything" feels like.
    if [ "$H_SEARCH" = "true" ]; then
      add_check search-observed "retrieval exercised" pass "search.observed=true — recall is being used" "$H_SEARCH"
    else
      add_check search-observed "retrieval exercised" warn \
        "search.observed=false — the daemon is up but no search/recall has run. If reverie 'has no effect', the harness is likely storing but never retrieving (MCP search tools not wired / not called)." "$H_SEARCH"
    fi
    # running daemon version vs installed binary version (skew = stale daemon)
    if [ -n "$BIN_VER" ] && [ "$H_VER" != "?" ]; then
      case "$BIN_VER" in
        *"$H_VER"*) add_check version-skew "daemon vs binary version" pass "running $H_VER matches installed binary" "daemon=$H_VER binary=$BIN_VER" ;;
        *) add_check version-skew "daemon vs binary version" warn \
             "running daemon is v$H_VER but installed binary is '$BIN_VER' — restart the daemon to pick up the new binary" "daemon=$H_VER binary=$BIN_VER" ;;
      esac
    fi
  fi
else
  add_check port-health "/health endpoint reachable" fail \
    "no response on 127.0.0.1:${PORT}/health — the daemon isn't serving HTTP (wrong port, crashed, or not started)" "port=$PORT"
fi

# =============================================================================
# CHECK 5 — database present + recent write activity
# =============================================================================
DB="$HOME/.engram/engram.db"
if [ -f "$DB" ]; then
  DB_SIZE="$(du -h "$DB" 2>/dev/null | cut -f1)"
  add_check db-present "engram.db exists" pass "database file present (${DB_SIZE})" "$DB ($DB_SIZE)"
  # WAL mtime tells us whether writes are actually landing recently. A stale
  # WAL on a "running" daemon means capture has silently stopped.
  WAL="$DB-wal"
  if [ -f "$WAL" ]; then
    NOW="$(date +%s)"; WAL_MT="$(stat -c %Y "$WAL" 2>/dev/null || stat -f %m "$WAL" 2>/dev/null || echo 0)"
    AGE=$(( NOW - WAL_MT ))
    if [ "$AGE" -lt 3600 ]; then
      add_check db-write-activity "recent write activity" pass "WAL touched ${AGE}s ago — captures are landing" "wal_age_s=$AGE"
    elif [ "$AGE" -lt 86400 ]; then
      add_check db-write-activity "recent write activity" warn "WAL last touched ${AGE}s ago (<24h) — captures are slow or paused" "wal_age_s=$AGE"
    else
      add_check db-write-activity "recent write activity" fail "WAL last touched ${AGE}s ago (>24h) — nothing is being written; capture is dead" "wal_age_s=$AGE"
    fi
  fi
else
  add_check db-present "engram.db exists" fail "no ~/.engram/engram.db — the daemon has no store (never initialised or wrong HOME)" ""
fi

# =============================================================================
# CHECK 6 — MCP wiring in ~/.claude.json  (the #1 "no effect" root cause)
# =============================================================================
# If reverie's MCP server ("engram") isn't wired into the harness config, the
# mem_* tools never load, so no session ever reads or writes memory — reverie is
# installed and running but completely inert from the harness's point of view.
CLAUDE_JSON="$HOME/.claude.json"
if [ -f "$CLAUDE_JSON" ] && command -v jq >/dev/null 2>&1; then
  GLOBAL_MCP="$(jq -r '.mcpServers // {} | keys[]?' "$CLAUDE_JSON" 2>/dev/null | grep -iE 'engram|reverie' | head -1)"
  PROJ_MCP="$(jq -r '.projects // {} | to_entries[] | .value.mcpServers // {} | keys[]?' "$CLAUDE_JSON" 2>/dev/null | grep -iE 'engram|reverie' | head -1)"
  if [ -n "$GLOBAL_MCP" ] || [ -n "$PROJ_MCP" ]; then
    add_check mcp-wired "reverie MCP server wired into harness" pass \
      "found MCP server '${GLOBAL_MCP:-$PROJ_MCP}' in ~/.claude.json" "global=${GLOBAL_MCP:-none} project=${PROJ_MCP:-none}"
  else
    add_check mcp-wired "reverie MCP server wired into harness" fail \
      "no 'engram'/'reverie' MCP server in ~/.claude.json — the mem_* tools never load, so sessions can't read or write memory. THIS is the most common cause of 'reverie has no effect'." ""
  fi
  # is at least one mcp subprocess actually alive? (tools loaded this session)
  MCP_PROC="$(ps -eo args 2>/dev/null | grep -E '(reveried|engram) mcp' | grep -v grep | head -1)"
  [ -n "$MCP_PROC" ] \
    && add_check mcp-process "reverie MCP subprocess alive" pass "an MCP subprocess is running (tools loaded)" "$MCP_PROC" \
    || add_check mcp-process "reverie MCP subprocess alive" warn "no 'reveried mcp' subprocess — no harness has loaded the tools in this session" ""
else
  add_check mcp-wired "reverie MCP server wired into harness" warn "~/.claude.json missing or jq unavailable — cannot verify MCP wiring" "$(redact "$CLAUDE_JSON")"
fi

# =============================================================================
# CHECK 7 — capture hooks present (passive capture path)
# =============================================================================
HOOK_HIT="$(grep -rlEi 'engram|reveried|mem_capture|session-bootstrap' "$HOME/.claude/settings.json" "$HOME/.claude/hooks" 2>/dev/null | head -1)"
if [ -n "$HOOK_HIT" ]; then
  add_check capture-hooks "capture / bootstrap hooks present" pass "found reverie-aware hook config" "$(redact "$HOOK_HIT")"
else
  add_check capture-hooks "capture / bootstrap hooks present" info \
    "no reverie hook found in ~/.claude — passive capture / session bootstrap may not be configured (MCP tools can still work without it)" ""
fi

# =============================================================================
# assemble final JSON document
# =============================================================================
HOSTNAME_R="$(hostname 2>/dev/null | cut -d. -f1)"
OS_R="$(uname -sr 2>/dev/null)"
GEN_AT="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
CHECKS_JSON="$(paste -sd, "$CHECKS_FILE" 2>/dev/null)"; [ -z "$CHECKS_JSON" ] && CHECKS_JSON=""

DOC="$(cat <<EOF
{
  "schema": "reverie-audit/v1",
  "generated_at": "$GEN_AT",
  "host": { "hostname": $(jstr "$HOSTNAME_R"), "os": $(jstr "$OS_R"), "user": $(jstr "$(id -un 2>/dev/null)") },
  "reverie": { "binary": $(jstr "$(redact "$BIN")"), "version": $(jstr "$BIN_VER") },
  "summary": { "pass": $PASS, "warn": $WARN, "fail": $FAIL, "info": $INFO },
  "checks": [ $CHECKS_JSON ]
}
EOF
)"

# --- optional human summary ------------------------------------------------
if [ "$DO_PRINT" = "1" ]; then
  echo "───────────────────────────────────────────────"
  echo " reverie install audit  —  $GEN_AT"
  echo " pass=$PASS  warn=$WARN  fail=$FAIL  info=$INFO"
  echo "───────────────────────────────────────────────"
  if command -v jq >/dev/null 2>&1; then
    printf '%s' "$DOC" | jq -r '.checks[] | "  [\(.status|ascii_upcase)] \(.label) — \(.detail)"'
  fi
  echo "───────────────────────────────────────────────"
fi

# --- upload ----------------------------------------------------------------
# NOTE: we test for a real "rvat_" token prefix rather than comparing against
# the literal placeholder — the portal replaces every "rvat_b4b9ab8f8e3f65b089e05a50dd1601f6fb9ebd451d1728b4" in this
# file when serving it, which would rewrite the guard itself and defeat the check.
case "$AUDIT_TOKEN" in rvat_*) TOKEN_OK=1 ;; *) TOKEN_OK=0 ;; esac
if [ "$DO_UPLOAD" = "1" ] && [ "$TOKEN_OK" = "1" ]; then
  RESP="$(printf '%s' "$DOC" | curl -s --max-time 15 -X POST "$AUDIT_UPLOAD_URL" \
      -H "authorization: Bearer $AUDIT_TOKEN" \
      -H "content-type: application/json" \
      --data-binary @- 2>/dev/null)"
  if printf '%s' "$RESP" | grep -q '"ok"'; then
    ID="$(printf '%s' "$RESP" | (command -v jq >/dev/null 2>&1 && jq -r '.id // empty' || cat))"
    echo "✓ uploaded — view at https://audit.cerebral.work/#${ID}"
  else
    echo "✗ upload failed (results above are still valid). response: ${RESP:-<none>}" >&2
  fi
elif [ "$DO_UPLOAD" = "0" ]; then
  # local-only mode: emit the raw JSON so the user can inspect/save it
  printf '%s\n' "$DOC"
else
  echo "note: run over HTTP from https://audit.cerebral.work to enable upload; printing locally:" >&2
  printf '%s\n' "$DOC"
fi
