#!/usr/bin/env bash
# AY Framework -- ghost-op scan (task 88).
# Rejects a PLAN that contains "ghost ops": human-only operations an agent must
# never perform. Called by /go (Phase 4, VERIFY) so a plan carrying a push / merge
# / PR / publish step is caught BEFORE build, not at commit time. Complements the
# runtime guardrail hook (task 80, hooks/operator-guardrails.sh) which blocks the
# same ops at the Bash-tool layer -- this is the earlier, plan-level gate.
#
# Usage:
#   ayf-ghost-scan <path>...    # scan files and/or directories (recursive)
#   ayf-ghost-scan -            # scan text on stdin
#   ayf-ghost-scan              # default: scan the plan folder .ay/plans/
#
# Exit codes: 0 = clean, 1 = ghost op(s) found (offenders on stdout),
#             64 = usage error.
#
# Ghost ops (human-only), kept in sync with hooks/operator-guardrails.sh:
#   git push · git merge · gh pr create|merge · npm|yarn|pnpm publish
#
# Quoted strings are stripped before matching, so a commit message or prose that
# merely mentions "push" does not false-positive. Pure bash + grep/sed, no deps.
# Works on Mac, Linux, Windows (Git Bash).

set -uo pipefail

# Combined extended-regex of human-only operations. Leading (^|[^A-Za-z-]) avoids
# matching inside longer words (e.g. "pushed", "merged").
GHOST_RE='(^|[^A-Za-z-])git[[:space:]]+push([[:space:]]|$)'
GHOST_RE="${GHOST_RE}|(^|[^A-Za-z-])git[[:space:]]+merge([[:space:]]|$)"
GHOST_RE="${GHOST_RE}|(^|[^A-Za-z-])gh[[:space:]]+pr[[:space:]]+(create|merge)"
GHOST_RE="${GHOST_RE}|(^|[^A-Za-z-])(npm|yarn|pnpm)[[:space:]]+publish"

usage() {
  echo "usage: ayf-ghost-scan <path>... | -   (default: scan .ay/plans/)" >&2
  exit 64
}

FOUND=0

# Scan one file: strip quoted strings (preserving line count so line numbers stay
# accurate), grep the stripped copy, then print the ORIGINAL offending line.
scan_file() {
  local f="$1"
  [ -f "$f" ] || return 0
  # Skip binary files.
  if grep -Iq . "$f" 2>/dev/null; then :; else return 0; fi

  local stripped hits n
  stripped="$(sed "s/'[^']*'//g; s/\"[^\"]*\"//g" "$f" 2>/dev/null || true)"
  hits="$(printf '%s\n' "$stripped" | grep -nE "$GHOST_RE" 2>/dev/null | cut -d: -f1 || true)"
  [ -n "$hits" ] || return 0
  for n in $hits; do
    FOUND=1
    printf '%s:%s: %s\n' "$f" "$n" "$(sed -n "${n}p" "$f" | sed -E 's/^[[:space:]]+//')"
  done
}

scan_path() {
  local p="$1"
  if [ -d "$p" ]; then
    # Recurse over regular files; skip .git and node_modules noise.
    local file
    while IFS= read -r file; do
      scan_file "$file"
    done < <(find "$p" -type f \
                  -not -path '*/.git/*' \
                  -not -path '*/node_modules/*' 2>/dev/null)
  elif [ -f "$p" ]; then
    scan_file "$p"
  fi
}

# --- Resolve targets ------------------------------------------------------
if [ "$#" -eq 1 ] && [ "$1" = "-" ]; then
  # stdin mode: scan a single synthetic buffer.
  TMP_IN="$(mktemp 2>/dev/null || echo "/tmp/ayf-ghost-$$")"
  trap 'rm -f "$TMP_IN"' EXIT
  cat > "$TMP_IN"
  scan_file "$TMP_IN"
elif [ "$#" -ge 1 ]; then
  for target in "$@"; do
    [ "$target" = "-" ] && usage
    scan_path "$target"
  done
else
  # Default: the plan folder, resolved from the git worktree's common dir.
  AY_ROOT="$(dirname "$(git rev-parse --git-common-dir 2>/dev/null)")/.ay" 2>/dev/null || AY_ROOT=".ay"
  PLANS="${AY_ROOT}/plans"
  [ -d "$PLANS" ] || PLANS=".ay/plans"
  if [ ! -d "$PLANS" ]; then
    echo "ayf-ghost-scan: no plan folder found ($PLANS) -- nothing to scan" >&2
    exit 0
  fi
  scan_path "$PLANS"
fi

# --- Report ---------------------------------------------------------------
if [ "$FOUND" -ne 0 ]; then
  echo "GHOST-OP DETECTED: plan contains human-only operations (push / merge / PR / publish)." >&2
  echo "Strip the offending step(s) and re-verify. Agents commit only -- the human pushes, merges, and publishes." >&2
  exit 1
fi

exit 0
