#!/usr/bin/env bash
# Refuse to commit content or a commit message containing a name from a LOCAL denylist.
#
# Why this exists: this repo's vendor-neutrality rule (CLAUDE.md, "Keep the public surface
# vendor-neutral") says the kit refers to downstream consumers generically and never by project
# name. That rule held for every FILE and every published release note, and failed twice in commit
# MESSAGES: two prose mentions, 128 and 176 commits deep, found only because someone thought to
# look. Removing them cost a history rewrite and a force-push of 25 tags. A rule enforced by
# remembering is the failure mode this project has paid for most; this makes it a machine check.
#
# **The denylist is NOT in this repo, and that is the whole design.** Writing the names into a
# public repo's own checker would publish exactly what the checker exists to keep private. It is
# read from $PRIVATE_NAMES_FILE, default ~/.claude/private-names.txt. No file, no check: a
# contributor who does not have one is not blocked, and is told once why nothing ran.
#
# Usage (the two hooks are thin wrappers):
#   check-private-names staged        what `git commit` is about to record
#   check-private-names msg <file>    the commit message being written
#   check-private-names all           the whole tree at HEAD + every commit message (audit)
#
# Enable in a fresh clone with:  git config core.hooksPath .githooks

set -uo pipefail

LIST="${PRIVATE_NAMES_FILE:-$HOME/.claude/private-names.txt}"
MODE="${1:-staged}"

if [ ! -r "$LIST" ]; then
    echo "check-private-names: no denylist at $LIST, skipping." >&2
    echo "  (set PRIVATE_NAMES_FILE, or create that file, to enable the check)" >&2
    exit 0
fi

# Plain substrings, case-insensitive. `grep -F -f -` treats every line as a literal, so a name
# containing `.` or `-` cannot behave as a regex metacharacter.
PATTERNS="$(grep -vE '^[[:space:]]*(#|$)' "$LIST" || true)"
if [ -z "$PATTERNS" ]; then
    echo "check-private-names: $LIST has no patterns, skipping." >&2
    exit 0
fi

fail() {
    echo "" >&2
    echo "BLOCKED: a private project name reached $1." >&2
    echo "" >&2
    printf '%s\n' "$2" | sed 's/^/    /' >&2
    echo "" >&2
    echo "  These names must not appear in a public repo. Rewrite the wording generically," >&2
    echo "  \"a consumer\", \"a downstream harness\": the way CLAUDE.md's vendor-neutrality" >&2
    echo "  rule requires for every other mention." >&2
    echo "" >&2
    echo "  Deliberate exception? Commit with --no-verify, and be sure it is deliberate:" >&2
    echo "  a name in a PUSHED commit costs a history rewrite and a force-push of every tag." >&2
    exit 1
}

case "$MODE" in
    staged)
        # Only ADDED lines, so pre-existing content in a touched file does not block an
        # unrelated change. Staged paths are checked too: a filename can carry a name.
        added="$(git diff --cached --unified=0 --no-color | grep '^+' | grep -F -i -f <(printf '%s\n' "$PATTERNS") || true)"
        paths="$(git diff --cached --name-only | grep -F -i -f <(printf '%s\n' "$PATTERNS") || true)"
        hits="$(printf '%s\n%s' "$added" "$paths" | grep -v '^$' || true)"
        [ -n "$hits" ] && fail "staged content" "$hits"
        ;;
    msg)
        file="${2:?usage: check-private-names msg <file>}"
        # Comment lines are stripped by git and never become part of the message.
        hits="$(grep -v '^#' "$file" | grep -n -F -i -f <(printf '%s\n' "$PATTERNS") || true)"
        [ -n "$hits" ] && fail "the commit message" "$hits"
        ;;
    all)
        tree="$(git grep -n -F -i -f <(printf '%s\n' "$PATTERNS") HEAD -- . || true)"
        msgs="$(git log --all --format='%h %B' | grep -n -F -i -f <(printf '%s\n' "$PATTERNS") || true)"
        tags="$(git for-each-ref --format='%(refname:short) %(contents)' refs/tags \
                | grep -n -F -i -f <(printf '%s\n' "$PATTERNS") || true)"
        hits="$(printf '%s\n%s\n%s' "$tree" "$msgs" "$tags" | grep -v '^$' || true)"
        if [ -n "$hits" ]; then
            fail "this repository" "$hits"
        fi
        echo "check-private-names: clean, tree at HEAD, every commit message, every tag message."
        ;;
    *)
        echo "usage: check-private-names [staged|msg <file>|all]" >&2
        exit 2
        ;;
esac
exit 0
