#!/usr/bin/env bash
# Steplog pre-commit hook — enforces the build-log freshness contract.
#
# Blocks a commit when .steplog/state.json has not changed since the last
# commit. On the allow path, regenerates BUILD_LOG.html and stages it so the
# rendered log stays in sync with the canonical state.
#
# Bypass with `git commit --no-verify` if you really mean it. The PRD treats
# that as a residual risk, not a bug.

set -euo pipefail

STATE_FILE=".steplog/state.json"
SCHEMA_FILE="state.schema.json"
GEN_SCRIPT="src/steplog/gen.py"
LOG_FILE="BUILD_LOG.html"
MIGRATIONS_DIR="src/steplog/migrations"
MIGRATION_TESTS="src/steplog/migrations/test/run_tests.py"

# Not a Steplog repo (or pre-init). No-op.
if [ ! -f "$STATE_FILE" ]; then
  exit 0
fi

if git rev-parse --verify HEAD >/dev/null 2>&1; then
  # There is a previous commit. State must differ from it in this commit.
  if git diff --cached --quiet HEAD -- "$STATE_FILE"; then
    echo "" >&2
    echo "  steplog: blocking commit." >&2
    echo "  $STATE_FILE has not changed since the last commit." >&2
    echo "" >&2
    echo "  Update activities[], next_actions[], or lifecycle, stage the" >&2
    echo "  change, and try again. Bypass intentionally with --no-verify." >&2
    echo "" >&2
    exit 1
  fi
else
  # First commit. Require state.json to be in the staged set.
  if ! git diff --cached --name-only | grep -qx "$STATE_FILE"; then
    echo "" >&2
    echo "  steplog: blocking first commit." >&2
    echo "  $STATE_FILE must be staged in the bootstrap commit." >&2
    echo "" >&2
    exit 1
  fi
fi

# If any staged file lives under src/steplog/migrations/, run the
# migration test fixtures. Catches "you changed a migration but didn't
# update its fixtures, or you broke an existing one." Block if tests
# fail. Feature-016 Phase 1 / s_066 C2 (a_160): path shifted from
# scripts/migrations/ when migrations moved into the package.
if [ -f "$MIGRATION_TESTS" ]; then
  if git diff --cached --name-only | grep -q "^$MIGRATIONS_DIR/"; then
    if ! python3 "$MIGRATION_TESTS"; then
      echo "" >&2
      echo "  steplog: migration test fixtures failed; commit aborted." >&2
      echo "  Re-run with: python3 $MIGRATION_TESTS" >&2
      echo "" >&2
      exit 1
    fi
  fi
fi

# Plugin sync-check (feature-005 §3.2 + §3.5). Runs when a canonical
# file with a build-step copy in plugins/claude-code/ changes, or when
# AGENT_PROTOCOL.md / SKILL.md changes (skill must mirror protocol's
# substantive content). Block on drift.
SYNC_CHECK_SCRIPT="plugins/claude-code/scripts/check-skill-sync.py"
if [ -f "$SYNC_CHECK_SCRIPT" ]; then
  # Feature-016 Phase 1 / s_066 C2 (a_160): scripts/steplog-gen.py →
  # src/steplog/gen.py; scripts/migrations/(000_introduce_versioning.py
  # |README.md) → src/steplog/migrations/(000_introduce_versioning.py
  # |README.md). CLI alternation already shifted in C1.
  # Feature-016 Phase 2 / s_067 C2 (a_164): pyproject\.toml added to the
  # alternation so the parity check between pyproject's force-include and
  # cli.py's _CANONICAL_SOURCE_PATHS fires whenever EITHER file changes.
  if git diff --cached --name-only | grep -qE "^(AGENT_PROTOCOL\.md|AGENTS\.md|CLAUDE\.md|state\.schema\.json|plugins/claude-code/|pyproject\.toml|src/steplog/gen\.py|src/steplog/cli\.py|scripts/hooks/(pre-commit|claude-stop\.sh)|src/steplog/migrations/(000_introduce_versioning\.py|README\.md)|docs/patches/example-prd-import\.json)"; then
    if ! python3 "$SYNC_CHECK_SCRIPT"; then
      echo "" >&2
      echo "  steplog: plugin sync-check failed; commit aborted." >&2
      echo "  A canonical file changed without its plugin template copy" >&2
      echo "  being updated, or SKILL.md drifted from AGENT_PROTOCOL.md." >&2
      echo "  Re-run: python3 $SYNC_CHECK_SCRIPT" >&2
      echo "" >&2
      exit 1
    fi
  fi
fi

# Regenerate BUILD_LOG.html and stage it. Side effect: the generator
# validates state against the schema, so an invalid state.json fails here.
#
# Feature-012 s_061: set STEPLOG_RENDER_CONTEXT=precommit so the
# generator's _load_committed_state() reads the working state directly
# rather than HEAD's state.json. At this moment HEAD still points to
# the previous commit (the new commit object isn't created yet), so
# without this env var the committed branch-view in BUILD_LOG.html
# would reflect pre-change state — always one commit behind. With the
# env var, both branch-views render from the same about-to-be-committed
# state and stay in sync. Cleared after the renderer returns so it
# doesn't leak into subsequent hook stages or shell sessions.
# Feature-016 Phase 1 / s_066 C2 (a_160): invocation switched to module
# form `python3 -m steplog.gen` (per operator Q3 override — single
# user-facing console_scripts entry; internal callers run the generator
# as a module). Defensive fallback to `python3 src/steplog/gen.py` for
# source-clone contexts where the package isn't on the active python's
# sys.path (e.g. operator's editable install lives in .venv/ but the
# hook fires from the system python3). Either form lands the same render.
if [ -f "$GEN_SCRIPT" ] && [ -f "$SCHEMA_FILE" ]; then
  err_log="$(mktemp -t steplog-gen.err.XXXXXX)"
  export STEPLOG_RENDER_CONTEXT=precommit
  if python3 -c "import steplog.gen" >/dev/null 2>&1; then
    invoke=(python3 -m steplog.gen)
  else
    invoke=(python3 "$GEN_SCRIPT")
  fi
  if ! "${invoke[@]}" "$STATE_FILE" "$SCHEMA_FILE" > "$LOG_FILE" 2>"$err_log"; then
    unset STEPLOG_RENDER_CONTEXT
    echo "" >&2
    echo "  steplog: generator failed; commit aborted." >&2
    echo "  state.json may be invalid against state.schema.json." >&2
    echo "" >&2
    cat "$err_log" >&2
    rm -f "$err_log"
    exit 1
  fi
  unset STEPLOG_RENDER_CONTEXT
  rm -f "$err_log"
  git add "$LOG_FILE"
fi

exit 0
