#!/bin/bash
# Watchtower SessionStart hook — injects ambient state context.
#
# Runs at session start. Calls watchtower-build-context.mjs to assemble
# a state summary, then outputs it as hookSpecificOutput for Claude's
# additionalContext.
#
# Also runs the frontier-model watchdog: the SessionStart payload (stdin
# JSON, per the CC hook contract) carries the session's `model` id. If
# ~/.claude/cc-registry.json designates a frontierModel and this session
# runs a different model, a loud warning is prepended to the injected
# context. Visibility only — never blocks anything.
#
# If watchtower is not installed (no config.json), exits silently.
# If the context builder produces no output, exits silently.
#
# ROLLBACK: Comment out the SessionStart entry in .claude/settings.json
# to disable this hook immediately.

command -v jq >/dev/null 2>&1 || exit 0

# Hook payload arrives on stdin (.tool_input-style JSON; never an env var).
# Guard against interactive invocation where stdin is a tty.
PAYLOAD=""
if [ ! -t 0 ]; then
  PAYLOAD=$(cat)
fi

WATCHTOWER_DIR="${HOME}/.claude-cabinet/watchtower"
PROJECT_PATH="$(pwd)"

# --- Frontier-model watchdog -------------------------------------------------
# Canonical match rule lives in the orient skill (templates/skills/orient/
# SKILL.md, "Frontier-Model Watchdog") — this is a reference implementation
# of that rule, not a second definition:
#   - key starting with "claude-" AND containing a digit => exact model ID,
#     exact case-insensitive match required
#   - anything else => family alias, case-insensitive containment
#   - absent/empty/whitespace key => silent no-op ('' would match everything
#     and silence the watchdog while appearing configured)
FRONTIER_WARNING=""
SESSION_MODEL=""
SESSION_ID=""
if [ -n "${PAYLOAD}" ]; then
  SESSION_MODEL=$(printf '%s' "${PAYLOAD}" | jq -r '.model // empty' 2>/dev/null)
  # Session id from the stdin payload — the documented CC hook contract used by
  # every other watchtower hook (session-end, telemetry). NOT $CLAUDE_SESSION_ID
  # (which CC does not set here). Keys the mid-session baseline snapshot below.
  SESSION_ID=$(printf '%s' "${PAYLOAD}" | jq -r '.session_id // empty' 2>/dev/null)
  # Defense-in-depth: the id is used as a filename component below. CC emits a
  # UUID, but reject anything carrying a path separator or dot-dir so a
  # malformed id can never escape state/session-snapshots/ (then it's treated
  # as "no session id" and capture is skipped, never written under a bad path).
  case "${SESSION_ID}" in */*|..|.) SESSION_ID="" ;; esac
fi
REGISTRY="${HOME}/.claude/cc-registry.json"
FRONTIER_KEY=""
if [ -f "${REGISTRY}" ]; then
  FRONTIER_KEY=$(jq -r '.frontierModel // empty' "${REGISTRY}" 2>/dev/null | tr -d '[:space:]')
fi
if [ -n "${FRONTIER_KEY}" ] && [ -z "${SESSION_MODEL}" ]; then
  # A key is configured but the payload exposed no model id (field absent,
  # renamed, or reshaped by a future CC release). Say so instead of going
  # silent — silence here is indistinguishable from "model matches".
  FRONTIER_WARNING="ℹ FRONTIER WATCHDOG: a frontier model is designated (${FRONTIER_KEY}) but the SessionStart payload exposed no session model id — the early-boundary check was SKIPPED, not passed. The /orient watchdog phase remains the boundary."
fi
if [ -n "${SESSION_MODEL}" ] && [ -n "${FRONTIER_KEY}" ]; then
  key_lc=$(printf '%s' "${FRONTIER_KEY}" | tr '[:upper:]' '[:lower:]')
  model_lc=$(printf '%s' "${SESSION_MODEL}" | tr '[:upper:]' '[:lower:]')
  # Session model ids may carry a bracketed runtime suffix (e.g.
  # claude-fable-5[1m]); strip it before exact comparison — the suffix is
  # session configuration, not model identity.
  model_base_lc="${model_lc%%\[*}"
  matched=0
  case "${key_lc}" in
    claude-*[0-9]*)
      # Exact model ID — require identity against the suffix-stripped id.
      [ "${model_base_lc}" = "${key_lc}" ] && matched=1
      ;;
    *)
      # Family alias — containment.
      case "${model_lc}" in
        *"${key_lc}"*) matched=1 ;;
      esac
      ;;
  esac
  if [ "${matched}" -eq 0 ]; then
    FRONTIER_WARNING="⚠ FRONTIER WATCHDOG: this session runs ${SESSION_MODEL}; your designated frontier model is ${FRONTIER_KEY} — switch with /model or relaunch. Surface this warning to the user as the FIRST line of any briefing. (Visibility only; nothing is blocked. Update the key with: npx create-claude-cabinet --frontier-model <model>)"
  fi
fi
# -----------------------------------------------------------------------------

# No config → watchtower not installed → still emit a frontier warning if
# one fired (the hook only registers on watchtower installs, but a torn-down
# config should not eat the watchdog), otherwise exit silently.
CONTEXT=""
if [ -f "${WATCHTOWER_DIR}/config.json" ]; then
  # Build context. Suppress stderr to avoid noise on missing files.
  CONTEXT=$(node "${WATCHTOWER_DIR}/scripts/watchtower-build-context.mjs" --project-path "${PROJECT_PATH}" 2>/dev/null)

  # Capture this session's mid-session-polling baseline (for /catch-up). A
  # SEPARATE, guarded best-effort invocation — it can never alter or block the
  # injection above (the additive contract: the injected context is unchanged).
  # Skipped entirely when no session id is available, so we never write a
  # shared/keyless snapshot that a later session would mistake for its own
  # baseline. The module prunes snapshots >7d on each write, so the dir stays
  # bounded.
  if [ -n "${SESSION_ID}" ] && [ -f "${WATCHTOWER_DIR}/scripts/watchtower-snapshot.mjs" ]; then
    node "${WATCHTOWER_DIR}/scripts/watchtower-snapshot.mjs" \
      --emit-snapshot "${WATCHTOWER_DIR}/state/session-snapshots/${SESSION_ID}.json" \
      --project-path "${PROJECT_PATH}" \
      --session-id "${SESSION_ID}" >/dev/null 2>&1 || true
  fi
fi

if [ -n "${FRONTIER_WARNING}" ]; then
  if [ -n "${CONTEXT}" ]; then
    CONTEXT="${FRONTIER_WARNING}

${CONTEXT}"
  else
    CONTEXT="${FRONTIER_WARNING}"
  fi
fi

# Empty context → nothing to inject
if [ -z "${CONTEXT}" ]; then
  exit 0
fi

cat <<HOOKEOF
{
  "hookSpecificOutput": {
    "hookEventName": "SessionStart",
    "additionalContext": $(echo "${CONTEXT}" | jq -Rs .)
  }
}
HOOKEOF
