#!/usr/bin/env sh
# Pre-push: verify the governance/knowledge integrity + run affected tests/build.
# NEVER bypass with --no-verify. Local scope stays cheap; CI does the full pass.

# Everything below must judge what is BEING PUSHED, not what happens to be lying
# around in the working tree. The generators and the affected-workspace walker
# both read the filesystem, so uncommitted work makes them report on code that
# is not part of this push — knowledge:verify then fails on a manifest that is
# perfectly correct for the committed tree, and the push is blocked for no real
# reason.
#
# So: hide anything not committed, run the checks against the committed tree,
# then restore. The trap restores even on failure or interrupt — blocking a push
# is a nuisance, losing a developer's uncommitted work to a hook is not.
# Drain the ref list git writes to this hook's stdin. Nothing below needs it,
# but a hook that never reads it leaves that write outstanding, and when the
# hook exits git can take EPIPE on it and die with 141 — aborting the push
# *after* all four checks have already passed and printed OK. Consuming it costs
# nothing and keeps a green hook run from silently failing to transfer.
cat >/dev/null 2>&1 || true

STASHED=0
STASH_REF=""
STASH_MSG="pre-push-verify-$$"
if ! git diff --quiet HEAD || [ -n "$(git ls-files --others --exclude-standard)" ]; then
  STASH_BEFORE=$(git rev-parse -q --verify refs/stash 2>/dev/null || true)
  if git stash push --quiet --include-untracked --message "$STASH_MSG"; then
    STASH_AFTER=$(git rev-parse -q --verify refs/stash 2>/dev/null || true)
    if [ -n "$STASH_AFTER" ] && [ "$STASH_AFTER" != "$STASH_BEFORE" ]; then
      STASHED=1
      STASH_REF=$STASH_AFTER
      echo "pre-push: parked uncommitted work so the checks see exactly what is being pushed"
    fi
  fi
fi

restore_stash() {
  if [ "$STASHED" = "1" ]; then
    STASHED=0
    sh scripts/hooks/restore-stash.sh "$STASH_REF" "$STASH_MSG"
  fi
}
trap 'restore_stash' EXIT INT TERM

fail() {
  echo "$1"
  restore_stash
  exit 1
}

echo "pre-push 1/4: knowledge integrity (freshness, links, no hook-bypass, contradictions)"
npm run knowledge:verify || fail "→ knowledge:verify failed"
npm run audit:check || fail "→ audit:check failed — run 'npm run audit' and commit the snapshot"

echo "pre-push 2/4: knowledge + architecture tooling tests"
npm run knowledge:test || fail "→ knowledge:test failed"
npm run architecture:check || fail "→ architecture:check failed"

# Stages 3 and 4 are the expensive ones. If the scoped gates already ran green
# over exactly this tree and recorded a receipt, repeating them here proves
# nothing and only burns the developer's machine. The receipt names the tree it
# proved, so a single changed byte invalidates it and both stages run in full.
# This is the sanctioned way to avoid duplicate work — never --no-verify, which
# would also skip the cheap integrity checks above. See rules/34.
if node tools/gates/receipt.mjs check >/dev/null 2>&1; then
  echo "pre-push 3-4/4: skipped — gate receipt matches this exact tree (rules/34)"
else
  echo "pre-push 3/4: test affected workspaces"
  node tools/affected/index.mjs test --base=origin/HEAD ||
    node tools/affected/index.mjs test --base=main ||
    fail "→ affected tests failed"

  echo "pre-push 4/4: build affected workspaces"
  node tools/affected/index.mjs build --base=origin/HEAD ||
    node tools/affected/index.mjs build --base=main ||
    fail "→ affected build failed"
fi

restore_stash
trap - EXIT INT TERM

echo "pre-push OK"
