#!/usr/bin/env bash
# PreToolUse hook for Claude Code.
# Blocks `npm install <pkg>` when the package isn't already in package.json
# AND isn't on a curated allowlist. Forces the user to add it manually
# (where the diff is visible in code review).
#
# Wire up in .claude/settings.json:
#   {
#     "hooks": {
#       "PreToolUse": [
#         {
#           "matcher": "Bash",
#           "hooks": [
#             {
#               "type": "command",
#               "command": ".claude/hooks/check-package.sh"
#             }
#           ]
#         }
#       ]
#     }
#   }
#
# Claude Code feeds the tool call as JSON on stdin. We read it with jq.
# Exit 0 = allow. Exit 2 with a message on stderr = block.

set -euo pipefail

# Read the tool-call payload from stdin and pull out the Bash command.
# (Requires jq; install with `brew install jq` or `apt install jq`.)
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Only act on npm install lines; let everything else through.
if [[ "$COMMAND" != "npm install"* ]]; then
    exit 0
fi

if [[ -z "$COMMAND" ]]; then
    # No command? Nothing for us to check; allow.
    exit 0
fi

# Extract the package names that follow "npm install ".
# This is a deliberately simple parser. Misses some forms, on purpose;
# the agent can ask the user if it really needs something exotic.
PACKAGES=$(echo "$COMMAND" \
    | sed -E 's/^npm install //; s/--save[-a-zA-Z]*//g; s/-[A-Za-z]+//g' \
    | tr ' ' '\n' \
    | grep -v '^$' \
    | grep -v '^-' || true)

if [[ -z "$PACKAGES" ]]; then
    # Plain "npm install" with no package name — just resolving deps.
    exit 0
fi

# Curated allowlist of packages the team has pre-approved.
ALLOWLIST_FILE=".claude/allowlist.txt"

# Read the lockfile/manifest to know what's already installed.
if [[ -f package.json ]]; then
    INSTALLED=$(jq -r '
        ((.dependencies // {}) | keys | .[]),
        ((.devDependencies // {}) | keys | .[]),
        ((.peerDependencies // {}) | keys | .[])
    ' package.json 2>/dev/null || true)
else
    INSTALLED=""
fi

ALLOWLIST=""
if [[ -f "$ALLOWLIST_FILE" ]]; then
    ALLOWLIST=$(grep -v '^#' "$ALLOWLIST_FILE" | grep -v '^$' || true)
fi

BLOCKED=()
for pkg in $PACKAGES; do
    # Strip any @version suffix.
    name="${pkg%@*}"
    if echo "$INSTALLED" | grep -qx "$name"; then
        continue   # already in package.json; allow update
    fi
    if echo "$ALLOWLIST" | grep -qx "$name"; then
        continue   # explicitly allowlisted; allow
    fi
    BLOCKED+=("$name")
done

if [[ ${#BLOCKED[@]} -gt 0 ]]; then
    {
        echo "❌ Blocked: ${BLOCKED[*]}"
        echo
        echo "These packages are not in package.json and not on the allowlist."
        echo "If they belong here, add them manually so the change shows up in a PR diff:"
        echo "  npm install ${BLOCKED[*]}  # in a terminal, with package.json open"
        echo
        echo "Or add them to $ALLOWLIST_FILE (one per line) and explain why in the same commit."
    } >&2
    exit 2     # Claude Code: exit 2 blocks the tool call
fi

# All packages already known. Allow.
exit 0
