#!/usr/bin/env bash
#
# nohalt's `gh` for agents: everything passes through except the acts that
# accept work or declare it done.
#
# An agent once merged unreviewed work it had been told to leave: the prompt
# said "merge it yourself once green" in one place and "never merge" in
# another, and it obeyed the louder one. The prompt was fixed; this exists
# because a rule with no mechanism behind it is a preference.
#
# Refused: merging or closing pull requests, closing issues (the closed issue
# is the definition of done -- an agent must not be able to declare it),
# releases, and the REST equivalents. Opening PRs, reading, commenting and
# editing bodies are all the agent's job and pass through.
#
# This is a speed bump, not a wall: an agent holding a token can reach the
# API another way. Branch protection is the hard control. So the obvious
# paths are blocked and the rest is logged: an attempt to go around this
# leaves a line saying so.
set -uo pipefail

REAL="${NOHALT_REAL_GH:-}"
SELF="$(readlink -f "${BASH_SOURCE[0]}")"
# Never ourselves: a shim that resolved itself as "the real gh" once re-exec'd
# into itself at 100% CPU for days.
if [[ -z "$REAL" || "$(readlink -f "$REAL" 2>/dev/null)" == "$SELF" ]]; then
  REAL=""
  IFS=: read -ra dirs <<<"$PATH"
  for d in "${dirs[@]}"; do
    [[ -x "$d/gh" && "$(readlink -f "$d/gh")" != "$SELF" ]] && { REAL="$d/gh"; break; }
  done
fi
[[ -n "$REAL" ]] || { echo "gh (nohalt guard): the real gh was not found" >&2; exit 127; }
LOG="${NOHALT_GUARD_LOG:-/dev/null}"

note() { printf '%s  %s  %s\n' "$(date '+%F %T')" "$1" "${*:2}" >>"$LOG" 2>/dev/null || true; }

refuse() {
  note "REFUSED" "gh $*"
  cat >&2 <<'WHY'

gh (nohalt guard): refused.

Merging, closing and releasing are not yours. Green CI means your work is
ready, not that it is accepted -- and a closed issue is the definition of
done, which only a merge may produce.

Instead: make sure the pull request body says what was wrong, what changed
and how it was verified with real output, comment on the issue, and stop.
If you believe this is wrong for your change, say so in the PR body.
Do not go around this through the API: attempts are logged.
WHY
  exit 1
}

case "${1:-}" in
  pr)
    case "${2:-}" in merge | close) refuse "$@" ;; esac ;;
  issue)
    case "${2:-}" in close | delete | transfer) refuse "$@" ;; esac ;;
  release) refuse "$@" ;;
  repo)
    case "${2:-}" in delete | archive | rename | transfer) refuse "$@" ;; esac ;;
  api)
    method=GET body=""
    for ((i = 2; i <= $#; i++)); do
      a="${!i}"
      case "$a" in
        */merge | */merge/* | */merges) refuse "$@" ;;
        -X | --method) j=$((i + 1)); method="${!j:-}" ;;
        -X*) method="${a#-X}" ;;
        --method=*) method="${a#--method=}" ;;
        state=closed | state=close) body=closed ;;
        */git/refs/tags* | */releases*) [[ "$method" != GET ]] && refuse "$@" ;;
      esac
    done
    if [[ "$body" == closed ]]; then refuse "$@"; fi
    ;;
  auth)
    [[ "${2:-}" == token ]] && note "TOKEN-TAKEN" "gh auth token" ;;
esac

exec "$REAL" "$@"
