#!/usr/bin/env bash
# agent-guard.sh  -  PreToolUse guard for the multi-agent pipeline.
#
# Turns prompt-level rules into deterministic, OS-enforced gates:
#   1. No AI/assistant attribution in git commit messages (Co-Authored-By: Claude,
#      "Generated with Claude Code", robot emoji, Anthropic no-reply address).
#   2. No force-push to a protected branch (main / master / develop), whichever
#      remote, push options or `git -C` directory the push names.
#   3. With MULTI_AGENT_UNATTENDED=1 in the hook's environment only: no outward
#      writes (push, PR, issue, tracker publishers, mutating API calls), no
#      Keychain reads, no fetch outside the network allowlist, no package
#      installs or manifest edits, no writes into protected paths
#      (scripts/unattended_policy.py, schemas/unattended-policy.json).
#
# Contract (Claude Code PreToolUse hook, matcher Bash and Edit|Write|NotebookEdit):
#   - Reads the tool-call JSON on stdin: {"tool_name":..., "tool_input":{...}, "cwd":...}.
#   - Exit 2  -> BLOCK the tool call (reason on stderr, shown to the model).
#   - Exit 0  -> allow.
#
# Safety design:
#   - The command string is ONLY parsed/pattern-matched, NEVER executed or eval'd
#     (the decision core is agent-guard.py; shlex tokenizes without running).
#   - Attended: any internal error (bad JSON, missing python3/helper, empty
#     input) -> exit 0. A guard bug must never break a legitimate tool call.
#   - Unattended: the same errors -> exit 2. Nobody is watching to notice a gap.
#   - No network, no file writes, no secret values printed.
#   - The execs are read-only `git rev-parse` / `git diff --name-only` calls.

set -u

HERE="$(cd "$(dirname "$0")" 2>/dev/null && pwd || true)"
HELPER="$HERE/agent-guard.py"

STRICT=0
[ "${MULTI_AGENT_UNATTENDED:-}" = "1" ] && STRICT=1

refuse_unjudged() {
  if [ "$STRICT" = "1" ]; then
    echo "BLOCKED by agent-guard (unattended): $1, so this tool call cannot be judged and is refused." >&2
    exit 2
  fi
  exit 0
}

[ -f "$HELPER" ] || refuse_unjudged "agent-guard.py is missing"
command -v python3 >/dev/null 2>&1 || refuse_unjudged "python3 is not on PATH"

PAYLOAD="$(cat 2>/dev/null || true)"
[ -z "$PAYLOAD" ] && refuse_unjudged "the hook received no payload"

CUR_BRANCH="$(git rev-parse --abbrev-ref HEAD 2>/dev/null || true)"

DECISION="$(printf '%s' "$PAYLOAD" | CUR_BRANCH="$CUR_BRANCH" python3 "$HELPER" 2>/dev/null || true)"

case "$DECISION" in
  BLOCK_ATTRIB)
    echo "BLOCKED by agent-guard: the commit message carries AI/assistant attribution." >&2
    echo "Remove any 'Co-Authored-By: Claude', 'Generated with Claude Code', robot-emoji, or" >&2
    echo "anthropic no-reply trailer. Commits are authored solely as the user's git identity." >&2
    exit 2 ;;
  BLOCK_FORCE)
    echo "BLOCKED by agent-guard: force-push to a protected branch (main/master/develop) is not allowed." >&2
    echo "Rewriting shared history is a data-loss risk. Push a normal commit, or force-push a feature branch." >&2
    exit 2 ;;
  BLOCK_UNATTENDED*)
    echo "BLOCKED by agent-guard (unattended policy): ${DECISION#BLOCK_UNATTENDED?}." >&2
    echo "This run has nobody watching it. Outward writes go through pr-request.json and the runner;" >&2
    echo "see multi-agent-refs/features/unattended-security.md. Record what is needed and continue." >&2
    exit 2 ;;
  BLOCK_PARSE)
    refuse_unjudged "the guard could not parse the tool call" ;;
  OK)
    exit 0 ;;
  *)
    refuse_unjudged "the guard returned no decision" ;;
esac
