#!/bin/sh
# ATDD commit-msg hook — mass-delete contamination guard (#629 Layer 2).
#
# Git invokes this with $1 = path to the file holding the prepared commit
# message. Unlike pre-commit (which runs before .git/COMMIT_EDITMSG is
# written for the current commit), commit-msg reliably has the message.
#
# Blocks when:
#   - staged diff deletes > 50 files OR > 10,000 lines, AND
#   - the commit message does not have one of the recognised
#     decommission prefixes / explicit allow tokens.
#
# All ATDD_SKIP_* bypass env vars have been retired (E030, 2026-05-26).
# For genuine emergencies: atdd emergency --reason "<reason>"

set -e

# --- Emergency bypass check (E031) ---
_REPO_ROOT="${ATDD_REPO_ROOT:-$(git rev-parse --show-toplevel 2>/dev/null || echo "")}"
if [ -n "$_REPO_ROOT" ]; then
    _BYPASS_FILE="${_REPO_ROOT}/.atdd/EMERGENCY_BYPASS"
    if [ -f "$_BYPASS_FILE" ]; then
        if find "$_BYPASS_FILE" -mmin -5 2>/dev/null | grep -q .; then
            printf "ATDD: Emergency bypass active (commit-msg). Reason: %s\n" \
                "$(head -1 "$_BYPASS_FILE" 2>/dev/null | sed 's/^reason=//' || echo 'see .atdd/EMERGENCY_BYPASS')" >&2
            exit 0
        else
            printf "ATDD: Emergency bypass file found but expired (> 5 min). Ignored.\n" >&2
        fi
    fi
fi

# --- Count staged deletions ---
DEL_FILES=$(git diff --cached --name-only --diff-filter=D 2>/dev/null | wc -l | tr -d ' ')

# --- Merge commits: skip the ordinary threshold, keep a hard backstop ---
# The threat this guard defends is bare-mode contamination: a working tree that
# silently mass-deletes its OWN files (Wave 12). Contamination enters at the
# ORIGINATING commit — an ordinary (non-merge) commit, where this guard still
# fires unchanged. A merge commit's deletions were authored and reviewed on the
# branch being merged; re-challenging them here only re-interrogates history that
# was already accepted (every PR catching up to a main that deleted files trips
# the >50-file threshold), which trained operators to reach for
# [mass-delete-approved] / `atdd emergency` — i.e. it taught people to bypass a
# real guard. So merges skip the ordinary threshold but keep a catastrophic
# backstop: a merge that would delete more than MERGE_DEL_BACKSTOP files still
# blocks, so a contaminated tree cannot sweep its whole checkout in behind a merge.
MERGE_DEL_BACKSTOP=500
IS_MERGE=0
if git rev-parse -q --verify MERGE_HEAD >/dev/null 2>&1; then
    IS_MERGE=1
    # At or below the backstop: an ordinary upstream catch-up — allow outright,
    # no token needed. ABOVE it: do NOT exit here. Fall through to the same escape
    # check every blocked commit reaches, so the [mass-delete-approved] escape the
    # block message advertises actually works. (Exiting here bypassed the escape,
    # which made the advice a lie and would drive a legitimate large merge straight
    # to `atdd emergency` — the exact bypass-training this fix exists to end.)
    if [ "${DEL_FILES:-0}" -le "$MERGE_DEL_BACKSTOP" ]; then
        exit 0
    fi
fi
DEL_LINES=$(git diff --cached --numstat 2>/dev/null | awk '{sum+=$2} END {print sum+0}')

# Fast path: not a mass delete, nothing to do.
if [ "${DEL_FILES:-0}" -le 50 ] && [ "${DEL_LINES:-0}" -le 10000 ]; then
    exit 0
fi

# --- Inspect the commit message ---
MSG_FILE="${1:-}"
if [ -z "$MSG_FILE" ] || [ ! -f "$MSG_FILE" ]; then
    # Fallback for direct invocation outside git's hook context.
    MSG_FILE="$(git rev-parse --git-dir 2>/dev/null)/COMMIT_EDITMSG"
fi

MSG_CONTENT=""
if [ -f "$MSG_FILE" ]; then
    MSG_CONTENT=$(cat "$MSG_FILE" 2>/dev/null || true)
fi

# Strip comment lines (git-style # lines) before checking the prefix.
MSG_BODY=$(printf '%s\n' "$MSG_CONTENT" | grep -v '^#' || true)
FIRST_LINE=$(printf '%s\n' "$MSG_BODY" | head -1)

# Allow if first line begins with a recognised decommission prefix,
# or if the body contains the explicit allow token.
if printf '%s' "$FIRST_LINE" | grep -qE '^(chore\(decom|refactor\(remove|chore\(archive)' \
        || printf '%s' "$MSG_BODY" | grep -qF '[mass-delete-approved]'; then
    exit 0
fi

if [ "$IS_MERGE" -eq 1 ]; then
    cat >&2 <<MERGE_BACKSTOP_MSG

ATDD: Merge blocked — deletes ${DEL_FILES} files (backstop: ${MERGE_DEL_BACKSTOP}).

A merge this destructive looks like tree contamination swept in behind a merge,
not an ordinary upstream catch-up. Verify the merge base first:
  1. git config core.bare         # check if = true (the Wave 12 cause)
  2. git diff --cached --stat     # confirm what is staged

If the merge is genuinely correct, include the literal token
[mass-delete-approved] anywhere in the merge commit message and re-run.

For genuine emergencies: atdd emergency --reason "<reason>"

MERGE_BACKSTOP_MSG
    exit 1
fi

cat >&2 <<MASSDELETE_MSG

ATDD: Commit blocked — mass-delete signature detected.

Staged diff would delete ${DEL_FILES} files / ${DEL_LINES} lines.
This matches the Wave 12 bare-mode contamination signature
(PRs #625 / #627: 220,000-line deletions across 1,277 files each).

Recovery:
  1. git config core.bare         # check if = true (the upstream cause)
  2. git diff --cached --stat     # confirm what is staged
  3. If contaminated: git config core.bare false; git reset to a safe commit

If this deletion is intentional, prefix the commit message with one of:
  chore(decom):
  refactor(remove):
  chore(archive):
... or include the literal token [mass-delete-approved] anywhere in the body.

For genuine emergencies: atdd emergency --reason "<reason>"

MASSDELETE_MSG
exit 1
