#!/usr/bin/env bash
# adom auto-update hook -- registry-native and cross-agent.
#
# Keeps the adom/core apps and skills current from wiki.adom.inc via the
# adom-wiki CLI. No git, no private repo. Runs under BOTH Claude Code and Codex
# `UserPromptSubmit`; its stdout becomes the model's additionalContext. Throttled
# and backgrounded so it never blocks a prompt or interrupts the agent.
#
# The update runs DETACHED so it never blocks a prompt, which means its result is
# only knowable on a LATER run. This script therefore does two jobs each time it
# fires: (1) report the OUTCOME of the previous background update, and (2) start a
# fresh one if we're behind. Job 1 exists because the old version piped the update
# to /dev/null and then unconditionally printed "updating now" -- so a hard
# failure (adom/core depending on a deleted package) was indistinguishable from
# success, and masked a fleet-wide auto-update outage for four days. A failed
# update is now surfaced, loudly, until it succeeds.
#
# Tunables (env):
#   ADOM_HOOK_INTERVAL   seconds between checks (default 1800 = 30 min)
#   ADOM_HOOK_FORCE      any non-empty value forces a check this run
#   ADOM_HOOK_DISABLE    any non-empty value disables the hook entirely

[ -n "${ADOM_HOOK_DISABLE:-}" ] && exit 0

# A UserPromptSubmit hook can fire from a non-login shell whose PATH lacks
# ~/.local/bin (where the adom-wiki CLI is installed). Without this the check
# silently no-ops ("command -v adom-wiki" fails). Put the CLI dir on PATH first.
export PATH="$HOME/.local/bin:$PATH"

ADOM_DIR="${HOME}/.adom"
STAMP="${ADOM_DIR}/last-core-check"
LOG="${ADOM_DIR}/last-update.log"        # combined output of the last update
STATUS="${ADOM_DIR}/last-update.status"  # 'running' | integer exit code
INTERVAL="${ADOM_HOOK_INTERVAL:-1800}"
RUN_STALE_AFTER=600                        # a 'running' marker older than this = crashed mid-update
mkdir -p "$ADOM_DIR" 2>/dev/null

now=$(date +%s 2>/dev/null || echo 0)

# --- 1. Report the outcome of the PREVIOUS background update. -----------------
# Runs before the throttle: a genuinely-broken auto-update is a loud, every-prompt
# condition (that is the whole point -- it used to be silent). Nothing prints when
# the last update succeeded or none has run.
if [ -f "$STATUS" ]; then
  st="$(cat "$STATUS" 2>/dev/null)"
  if [ "$st" != "running" ] && [ -n "$st" ] && [ "$st" != "0" ]; then
    reason="$(tail -n 3 "$LOG" 2>/dev/null | tr '\n' ' ' | cut -c1-300)"
    echo "adom: the last background auto-update FAILED (exit $st) -- the fleet may be behind wiki.adom.inc. See why with 'adom-wiki pkg install adom/core'. Recent output: ${reason:-<none>}"
    # Fall through and retry below; a transient failure self-heals, a real one
    # (a deleted dependency) keeps surfacing here until it is fixed at the source.
  fi
fi

# --- 2. Throttle. -------------------------------------------------------------
last=0
[ -f "$STAMP" ] && last=$(stat -c %Y "$STAMP" 2>/dev/null || echo 0)
if [ -z "${ADOM_HOOK_FORCE:-}" ] && [ "$last" -gt 0 ] && [ $(( now - last )) -lt "$INTERVAL" ]; then
  exit 0
fi
touch "$STAMP" 2>/dev/null

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

# --- 3. Don't stack a second updater on top of one still running. -------------
# But treat a 'running' marker older than RUN_STALE_AFTER as crashed (container
# restarted mid-update), so a wedged marker can never block updates forever.
if [ -f "$STATUS" ] && [ "$(cat "$STATUS" 2>/dev/null)" = "running" ]; then
  run_age=$(( now - $(stat -c %Y "$STATUS" 2>/dev/null || echo "$now") ))
  [ "$run_age" -lt "$RUN_STALE_AFTER" ] && exit 0
fi

# --- 4. Cheap, bounded staleness probe. ---------------------------------------
# `adom-wiki pkg outdated --quiet` exits non-zero when something is behind.
if timeout 12 adom-wiki pkg outdated --quiet >/dev/null 2>&1; then
  rm -f "$STATUS" 2>/dev/null   # current -> clear any stale failure marker
  exit 0
fi

# --- 5. Behind: apply in the background, CAPTURING the result. ----------------
# Output goes to a log and the exit code to a status file so the next run (job 1
# above) can tell success from failure. `pkg update` bumps in-range versions;
# `pkg install adom/core` re-resolves and pulls any newly added core deps. Both
# run regardless (as before), and the status reflects a failure of EITHER, with
# the install failure preferred since installing core is the actual goal.
#
# CONCURRENCY: cron (*/30 + @reboot) and UserPromptSubmit can fire within the same
# second, and the STATUS='running' check has a check-then-write gap. Two updaters
# racing into the same package extraction (which is NOT atomic -- it wipes and
# repopulates the module dir in place) makes one see a half-written tree, e.g.
# `cp: cannot stat 'skills/.../SKILL.md'`. The flock below serializes the hook's
# own updaters: the second to arrive skips instead of stacking. It is held for the
# WHOLE background update (inside the subshell), not just the parent launch.
# (It does NOT cover a MANUAL `adom-wiki pkg install` racing the hook -- that needs
# a CLI-level install lock. See adom/hook #1 follow-up.)
echo running > "$STATUS" 2>/dev/null
(
  if command -v flock >/dev/null 2>&1; then
    exec 9>"${ADOM_DIR}/update.lock" 2>/dev/null || true
    flock -n 9 2>/dev/null || { rm -f "$STATUS" 2>/dev/null; exit 0; }
  fi
  adom-wiki pkg update > "$LOG" 2>&1
  u=$?
  adom-wiki pkg install adom/core >> "$LOG" 2>&1
  i=$?
  rc=0
  [ "$u" -ne 0 ] && rc="$u"
  [ "$i" -ne 0 ] && rc="$i"
  echo "$rc" > "$STATUS" 2>/dev/null
) </dev/null >/dev/null 2>&1 &
disown 2>/dev/null

echo "adom: core skills/apps are behind wiki.adom.inc; an update is running in the background. If it fails, the next check will report it here."
exit 0
