#!/bin/sh
# vault-sync.sh — one pass of the git "keep the vault in sync" loop.
#
# The always-on host's working tree is shared by three writers: this sync, the hourly
# sweep, and (when installed) the inbox processors / IDLE listener. This script reconciles
# that tree with the remote on a timer (launchd com.alexandria.vault-sync, StartInterval
# ~600s re-runs it): pull --rebase to take remote edits (e.g. from the laptop or another
# device), commit any local changes the agents made, then push.
#
# It is intentionally a SINGLE PASS, not its own loop — launchd owns the cadence, so a
# crash just means the next tick retries. Safe to run by hand any time.
#
# When the vault-mcp module runs on this box, its server process owns git (it commits
# every seat's write and runs this same reconcile pass in-process) — this script detects
# its fresh heartbeat and stands down, resuming automatically if the server stops.
#
# Invariants (mirror the sweep's): never hard-delete (agents move-not-delete, so every
# change is git-revertible); commits are plain; conflicts abort the rebase and leave the
# tree clean for the next tick rather than wedging.
#
# Env (optional): VAULT_DIR (else self-locate by walking up to the dir holding
# Agent/config.json — the same sentinel as vault_core.locate_vault; kept as shell so the
# sync lifeline never depends on a python venv, with a parity test holding the two
# locators in agreement), SYNC_BRANCH (else the current branch), SYNC_REMOTE (default
# origin).
set -e

# --- locate the vault working tree ---------------------------------------------------
if [ -n "$VAULT_DIR" ]; then
  REPO="$VAULT_DIR"
else
  DIR=$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)
  REPO="$DIR"
  while [ "$REPO" != "/" ]; do
    if [ -f "$REPO/Agent/config.json" ] || [ -f "$REPO/Alexandria/config.json" ]; then break; fi
    REPO=$(dirname "$REPO")
  done
fi
if [ ! -d "$REPO/.git" ]; then
  echo "vault-sync: $REPO is not a git working tree — nothing to sync." >&2
  exit 0
fi

# --- stand down while the vault-mcp server owns git (docs/V3-PLAN.md §4 P3) ----------
# The MCP server serializes every seat's writes and runs this same sync pass in-process
# on an interval, so a second committer would just race it. It claims ownership with a
# heartbeat (pid + refreshed mtime); while that is fresh and the pid is alive, this tick
# is a no-op. If the server dies, the heartbeat goes stale and this job resumes as the
# sync backstop — never double-committing, never leaving the vault unsynced.
HB_FILE="$(dirname "${SWEEP_STATE:-$HOME/Obsidian/secrets/sweep-state.json}")/vault-mcp-status.json"
if [ -f "$HB_FILE" ]; then
  HB_PID=$(sed -n 's/.*"pid":[[:space:]]*\([0-9][0-9]*\).*/\1/p' "$HB_FILE")
  HB_AGE=$(( $(date +%s) - $(stat -f %m "$HB_FILE" 2>/dev/null || echo 0) ))
  if [ -n "$HB_PID" ] && [ "$HB_AGE" -lt 1800 ] && kill -0 "$HB_PID" 2>/dev/null; then
    echo "vault-sync: vault-mcp (pid $HB_PID) owns git for this vault — standing down."
    exit 0
  fi
fi

cd "$REPO"

REMOTE="${SYNC_REMOTE:-origin}"
BRANCH="${SYNC_BRANCH:-$(git rev-parse --abbrev-ref HEAD)}"
STAMP=$(date -u +%FT%TZ)

# --- take remote edits first (rebase local agent commits on top) ---------------------
# A failed rebase (real conflict) must not wedge the tree: abort it, leave a clean state,
# and let the next tick try again. Most ticks are a clean fast-forward.
CONFLICT=0
git pull --rebase "$REMOTE" "$BRANCH" || { git rebase --abort 2>/dev/null || true; CONFLICT=1; }

# --- commit anything the agents wrote since last tick --------------------------------
git add -A
if ! git diff --cached --quiet; then
  git commit -m "vault-sync: $STAMP" >/dev/null
fi

# --- push (fail-soft: a rejected push just retries next tick) ------------------------
PUSHFAIL=0
git push "$REMOTE" "$BRANCH" || { echo "vault-sync: push failed (will retry next tick)" >&2; PUSHFAIL=1; }

# --- heartbeat: record this sync so a stuck/conflicted sync is visible to `doctor` ---
STATUSDIR=$(dirname "${SWEEP_STATE:-$HOME/Obsidian/secrets/sweep-state.json}")
if [ "$CONFLICT" = 1 ]; then OK=false; NOTE="rebase conflict — aborted, will retry"
elif [ "$PUSHFAIL" = 1 ]; then OK=false; NOTE="push failed — will retry"
else OK=true; NOTE="clean"; fi
mkdir -p "$STATUSDIR" 2>/dev/null || true
printf '{"at":"%s","ok":%s,"note":"%s"}\n' "$STAMP" "$OK" "$NOTE" \
  > "$STATUSDIR/sync-status.json" 2>/dev/null || true
