#!/bin/bash
# SPDX-FileCopyrightText: 2025 OmniNode.ai Inc.
# SPDX-License-Identifier: MIT
#
# ONEX_GH_USER_SHIM (OMN-19479) -- a `gh` shim installed ahead of the real
# binary on this host's PATH (scripts/user-bin/install-gh-shim.sh).
#
# Why: the Claude Code harness looks up the current branch's PR link with
# `gh pr view [<branch>] --json url` every few seconds per session, and every
# lookup spends the operator's shared GitHub quota (2026-09-24 plan M4: at
# least 355 GraphQL requests an hour from two sessions). This shim answers
# exactly two read shapes from a 600 s per-(repository, branch) cache and
# hands every other command to the real `gh` unchanged:
#
#   gh pr view [<branch>] --json url
#   gh pr view [<branch>] --json number,url,state
#
# Nothing else is cached. A PR mutation that can change those answers
# (create, ready, close, reopen, merge) purges the cache before it runs.
# A cached "no pull requests found" answer is kept like a positive one; any
# other failure (network, quota, auth) is never cached.
#
# Read routing (OMN-19587): with ONEX_GH_READ_ROUTING=1, the read shapes
# `gh api` GET/HEAD, `pr view|list|checks|diff`, `run view|list`, `repo view`,
# `issue view|list` and `search` are candidates handed to gh_route.py
# (installed next to this file by install-gh-shim.sh; override with
# ONEX_GH_ROUTER); gh_route.py's own classify() is the authority on whether a
# given call actually reads (a viewer-resolving query or a non-OmniNode-ai
# owner still runs on the operator there). `api graphql` is never a
# candidate here: this shim cannot tell a mutation from a read without
# parsing the query the way gh_route.py does, so no graphql call reaches the
# router process at all -- every graphql call, mutation or read, runs on the
# operator unchanged, cleanly. It runs an actual read on the read-only App
# token from
# GH_READ_TOKEN_CMD and falls back to the operator credential when there is
# none. Every other call, and every write, runs on the operator unchanged and
# never reaches the router. With the flag unset or anything but 1 the shim
# runs the real gh exactly as it did before routing existed. Both variables
# default from ~/.config/omni/gh-read-routing.env when unset (OMN-19852).
#
# Search guard (OMN-19852): GitHub's secondary-rate-limited search bucket is
# reserved for the launchd PR watcher. `gh api search/...` and `gh search ...`
# are refused unless ONEX_PR_WATCHER=1; readers use the watcher's local state
# and the canonical clones instead.
#
# Shared call log (OMN-19852): every allowed or refused call appends one
# tab-separated line to ONEX_GH_CALLS_LOG, else ONEX_STATE_DIR/gh-calls.log
# (no log is written when neither is set).
# It contains only timestamp, resolved lane/source, class, method, endpoint,
# exit code and action -- never argv, environment values or tokens.
# ONEX_GH_USAGE_LOG=0 turns both usage logs off for one call.
#
# Usage log (OMN-19585): every invocation appends one JSON line to
# ${XDG_CACHE_HOME:-$HOME/.cache}/omni/gh-usage/<UTC date>.jsonl with the
# timestamp, lane (ONEX_LANE; else ONEX_LANE_ID, ONEX_AGENT_NAME,
# CLAUDE_AGENT_NAME or CLAUDE_SUBAGENT_NAME; else the lane registered for the
# omni_worktrees/<ticket>/<dir> worktree the call is made from; else the Claude
# Code session id; else the parent command, with lane_source saying which),
# command class, HTTP method, repository, endpoint path (gh api only,
# query string dropped), exit code and whether the cache answered. With routing
# on it adds identity (app, operator, operator-fallback) and route_reason. Argv, stdin,
# output and environment values are never written, so no token can reach it.
# scripts/user-bin/gh_usage_report.py turns the log into requests per lane.
# ONEX_GH_USAGE_LOG=0 turns the log off for one call.
#
# This file must stay bash 3.2 compatible: /bin/bash on macOS is 3.2.

ONEX_GH_SHIM_TTL="${ONEX_GH_SHIM_TTL:-600}"

# --- find the real gh: walk PATH past this file and any other shim copy -----
self="${BASH_SOURCE[0]}"
real=""
# PATH is split by parameter expansion, not `read <<< "$PATH"`: under Homebrew
# bash 5.3.9 a here-string read of this host's 1.6 kB PATH hung twice at
# 2026-09-25T14:52Z (load average 72) and did not reproduce at 15:05Z. The
# shim must never be able to hang a gh call, and this form has no temp file.
_rest="$PATH:"
while [ -n "$_rest" ]; do
  _d="${_rest%%:*}"
  _rest="${_rest#*:}"
  [ -n "$_d" ] || continue
  _c="$_d/gh"
  [ -f "$_c" ] && [ -x "$_c" ] || continue
  [ "$_c" -ef "$self" ] && continue
  # Another copy of this shim (a second install, or the source tree on PATH)
  # would recurse forever; skip anything carrying the marker.
  if head -c 512 "$_c" 2>/dev/null | grep -q 'ONEX_GH_USER_SHIM'; then
    continue
  fi
  real="$_c"
  break
done
if [ -z "$real" ]; then
  echo "gh shim (OMN-19479): no real gh found on PATH after $self" >&2
  exit 127
fi

# --- read-routing defaults (OMN-19852) -----------------------------------------
# A session, a launchd job and a Codex lane each start with their own environment,
# so an exported flag reaches only the shells that happened to inherit it. When
# ONEX_GH_READ_ROUTING or GH_READ_TOKEN_CMD is unset, take it from the one host
# file ${XDG_CONFIG_HOME:-$HOME/.config}/omni/gh-read-routing.env (KEY=VALUE
# lines; only these two keys are read, nothing is evaluated). The environment
# wins, so ONEX_GH_READ_ROUTING=0 still turns routing off for one call.
#
# A trailing " # note" on the same line as the value (an operator's own
# disable annotation is the observed case, OMN-19852) is stripped, comment and
# the whitespace before it: without this, "ONEX_GH_READ_ROUTING=1  # re-enabled"
# would compare unequal to "1" and silently run every read on the operator
# while the file reads enabled.
_conf_strip_comment() {
  local v="$1"
  case "$v" in *[[:space:]]'#'*) v="${v%%[[:space:]]\#*}" ;; esac
  while :; do
    case "$v" in
      *[[:space:]]) v="${v%[[:space:]]}" ;;
      *) break ;;
    esac
  done
  printf '%s' "$v"
}
_routing_conf="${ONEX_GH_READ_ROUTING_CONF:-${XDG_CONFIG_HOME:-$HOME/.config}/omni/gh-read-routing.env}"
if [ -f "$_routing_conf" ] && { [ -z "${ONEX_GH_READ_ROUTING+x}" ] || [ -z "${GH_READ_TOKEN_CMD+x}" ]; }; then
  while IFS= read -r _line || [ -n "$_line" ]; do
    case "$_line" in
      ONEX_GH_READ_ROUTING=*)
        if [ -z "${ONEX_GH_READ_ROUTING+x}" ]; then
          _conf_v="$(_conf_strip_comment "${_line#*=}")"
          export ONEX_GH_READ_ROUTING="$_conf_v"
        fi ;;
      GH_READ_TOKEN_CMD=*)
        if [ -z "${GH_READ_TOKEN_CMD+x}" ]; then
          _conf_v="$(_conf_strip_comment "${_line#*=}")"
          export GH_READ_TOKEN_CMD="$_conf_v"
        fi ;;
    esac
  done < "$_routing_conf"
fi

_cache_root="${XDG_CACHE_HOME:-$HOME/.cache}/omni/gh-shim/prview"

# --- usage log (OMN-19585) -----------------------------------------------------

# Keep only characters that are safe inside a log value and meaningful in a
# repo, endpoint or lane name, and blank anything shaped like a GitHub token.
_clean() {
  local v="$1"
  v="${v//[^A-Za-z0-9._\/:@+=, -]/}"
  case "$v" in
    *ghp_*|*gho_*|*ghu_*|*ghs_*|*ghr_*|*github_pat_*) v="REDACTED" ;;
  esac
  printf '%s' "${v:0:200}"
}

# Resolve attribution once for both the usage line and the read router.
_lane_done=0
_resolve_lane() {
  [ "$_lane_done" -eq 1 ] && return 0
  _lane_done=1
  _lane=""; _lane_src=""
  local agent="" cwd="" suffix="" ticket="" repo_dir="" worktree=""
  local registry_dir="" record="" hash="" registered="" parent=""
  if [ -n "${ONEX_LANE:-}" ]; then
    _lane="$(_clean "$ONEX_LANE")"; _lane_src="env"
    return
  fi
  if [ -n "${ONEX_LANE_ID:-}" ]; then agent="$ONEX_LANE_ID"
  elif [ -n "${ONEX_AGENT_NAME:-}" ]; then agent="$ONEX_AGENT_NAME"
  elif [ -n "${CLAUDE_AGENT_NAME:-}" ]; then agent="$CLAUDE_AGENT_NAME"
  elif [ -n "${CLAUDE_SUBAGENT_NAME:-}" ]; then agent="$CLAUDE_SUBAGENT_NAME"
  fi
  if [ -n "$agent" ]; then
    _lane="$(_clean "$agent")"; _lane_src="agent-env"
    return
  fi
  cwd="$(pwd -P 2>/dev/null)"
  case "$cwd" in
    */omni_worktrees/*/*)
      suffix="${cwd#*/omni_worktrees/}"
      ticket="${suffix%%/*}"
      suffix="${suffix#*/}"
      repo_dir="${suffix%%/*}"
      worktree="${cwd%%/omni_worktrees/*}/omni_worktrees/$ticket/$repo_dir"
      if [ -n "${ONEX_LANE_REGISTRY_ROOT:-}" ]; then
        registry_dir="$ONEX_LANE_REGISTRY_ROOT/lane_identity"
      elif [ -n "${OMNI_HOME:-}" ]; then
        registry_dir="$OMNI_HOME/.onex_state/lane_identity"
      fi
      if [ -n "$registry_dir" ]; then
        hash="$(_sha "$worktree" | cut -c1-32)"
        record="$registry_dir/$hash.json"
        if [ -n "$hash" ] && [ -r "$record" ]; then
          registered="$(sed -n 's/.*"lane"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/p' "$record" 2>/dev/null | head -n 1)"
          if [ -n "$registered" ]; then
            _lane="$(_clean "$registered")"; _lane_src="registry"
            return
          fi
        fi
      fi
      ;;
  esac
  if [ -n "${CLAUDE_CODE_SESSION_ID:-}" ]; then
    _lane="session:$(_clean "$CLAUDE_CODE_SESSION_ID")"; _lane_src="session"
    return
  fi
  parent="$(basename "$(ps -o comm= -p "$PPID" 2>/dev/null)" 2>/dev/null)"
  _lane="parent:$(_clean "$parent")"; _lane_src="parent"
}

# Sets _cls, _method and _repo, _endpoint for the argv in "$@".
_classify() {
  _cls=""; _method=""; _repo=""; _endpoint=""
  local group="${1:-}" sub="${2:-}" a prev="" repo_flag="" has_field=0 is_mutation=0
  for a in "$@"; do
    case "$prev" in
      -R|--repo) repo_flag="$a" ;;
      -X|--method) _method="$a" ;;
    esac
    case "$a" in
      --repo=*) repo_flag="${a#--repo=}" ;;
      --method=*) _method="${a#--method=}" ;;
      -f|-F|--field|--raw-field|--input|--field=*|--raw-field=*|--input=*) has_field=1 ;;
    esac
    case "$a" in *mutation*) is_mutation=1 ;; esac
    prev="$a"
  done
  if [ "$group" = "api" ]; then
    # First positional after `api` that is not the value of an option.
    local skip=0
    shift
    for a in "$@"; do
      if [ "$skip" -eq 1 ]; then skip=0; continue; fi
      case "$a" in
        -X|--method|-H|--header|-f|--raw-field|-F|--field|--input|-q|--jq|-t|--template|--cache|--hostname|-p|--preview) skip=1 ;;
        -*) ;;
        *) _endpoint="${a%%\?*}"; break ;;
      esac
    done
    _endpoint="${_endpoint#/}"
    if [ "$_endpoint" = "graphql" ]; then
      _cls="api graphql"
      if [ "$is_mutation" -eq 1 ]; then _method="MUTATION"; else _method="POST"; fi
    else
      _cls="api"
      if [ -z "$_method" ]; then
        if [ "$has_field" -eq 1 ]; then _method="POST"; else _method="GET"; fi
      fi
      case "$_endpoint" in
        repos/*/*) _repo="${_endpoint#repos/}"; _repo="${_repo#*/}"; _repo="${_repo%%/*}" ;;
      esac
    fi
    _method="$(printf '%s' "$_method" | tr '[:lower:]' '[:upper:]')"
  else
    case "$sub" in
      ""|-*) _cls="$group" ;;
      *) _cls="$group $sub" ;;
    esac
    case "$group" in
      ""|-*|auth|config|alias|extension|help|completion|version|status) _method="LOCAL" ;;
      search|browse) _method="GET" ;;
      *)
        case "$sub" in
          view|list|checks|diff|status|download|watch|ls) _method="GET" ;;
          *) _method="WRITE" ;;
        esac
        ;;
    esac
    [ "$group" = "status" ] && _method="GET"
    if [ -n "$repo_flag" ]; then
      _repo="${repo_flag##*/}"
    else
      case "$group" in
        pr|issue|run|workflow|release|label|repo|cache|ruleset)
          _repo="$(git config --get remote.origin.url 2>/dev/null)"
          _repo="${_repo%.git}"; _repo="${_repo##*/}"; _repo="${_repo##*:}"
          ;;
      esac
    fi
  fi
}

# _call_log <exit code> <action: allowed|refused>
_call_log() {
  [ "${ONEX_GH_USAGE_LOG:-1}" = "0" ] && return 0
  local file="" dir="" ts="" oversized=""
  if [ -n "${ONEX_GH_CALLS_LOG:-}" ]; then
    file="$ONEX_GH_CALLS_LOG"
  elif [ -n "${ONEX_STATE_DIR:-}" ]; then
    file="$ONEX_STATE_DIR/gh-calls.log"
  else
    return 0
  fi
  case "$file" in
    */*) dir="${file%/*}" ;;
    *) dir="." ;;
  esac
  mkdir -p "$dir" 2>/dev/null || return 0
  if [ $(( $$ % 50 )) -eq 0 ] && [ -f "$file" ]; then
    oversized="$(find "$file" -prune -type f -size +20480k -print 2>/dev/null)"
    if [ -n "$oversized" ]; then
      mv -f "$file" "$file.1" 2>/dev/null || true
    fi
  fi
  _resolve_lane
  ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  printf '%s\t%s\t%s\t%s\t%s\t%s\t%s\t%s\n' \
    "$(_clean "$ts")" "$(_clean "$_lane")" "$(_clean "$_lane_src")" \
    "$(_clean "$_cls")" "$(_clean "$_method")" "$(_clean "$_endpoint")" \
    "$(_clean "$1")" "$(_clean "$2")" >> "$file" 2>/dev/null || true
}

# _log <exit code> <cache: hit|miss|none>
_log() {
  [ "${ONEX_GH_USAGE_LOG:-1}" = "0" ] && return 0
  _call_log "$1" allowed
  local dir="${XDG_CACHE_HOME:-$HOME/.cache}/omni/gh-usage"
  local ts session ticket="" identity="" route_reason=""
  ts="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
  session="$(_clean "${CLAUDE_CODE_SESSION_ID:-}")"
  case "$PWD" in
    */omni_worktrees/OMN-*) ticket="${PWD#*/omni_worktrees/}"; ticket="${ticket%%/*}" ;;
  esac
  mkdir -p "$dir" 2>/dev/null || return 0
  _resolve_lane
  if [ -n "$_route" ]; then
    identity="${_route%% *}"
    case "$_route" in *\ *) route_reason="${_route#* }" ;; esac
    printf '{"ts":"%s","lane":"%s","lane_source":"%s","session":"%s","ticket":"%s","cls":"%s","method":"%s","repo":"%s","endpoint":"%s","exit":%s,"cache":"%s","identity":"%s","route_reason":"%s","pid":%s}\n' \
      "$ts" "$_lane" "$_lane_src" "$session" "$(_clean "$ticket")" "$(_clean "$_cls")" "$(_clean "$_method")" \
      "$(_clean "$_repo")" "$(_clean "$_endpoint")" "$1" "$2" "$(_clean "$identity")" "$(_clean "$route_reason")" "$$" \
      >> "$dir/${ts%%T*}.jsonl" 2>/dev/null || true
  else
    printf '{"ts":"%s","lane":"%s","lane_source":"%s","session":"%s","ticket":"%s","cls":"%s","method":"%s","repo":"%s","endpoint":"%s","exit":%s,"cache":"%s","pid":%s}\n' \
      "$ts" "$_lane" "$_lane_src" "$session" "$(_clean "$ticket")" "$(_clean "$_cls")" "$(_clean "$_method")" \
      "$(_clean "$_repo")" "$(_clean "$_endpoint")" "$1" "$2" "$$" \
      >> "$dir/${ts%%T*}.jsonl" 2>/dev/null || true
  fi
}

# --- is this one of the two cacheable read shapes? ---------------------------
# Returns 0 and sets _branch_arg and _fields when it is.
_cacheable() {
  [ "${1:-}" = "pr" ] && [ "${2:-}" = "view" ] || return 1
  _branch_arg=""
  _fields=""
  case "$#" in
    4) [ "$3" = "--json" ] || return 1; _fields="$4" ;;
    5)
      case "$3" in -*|"") return 1 ;; esac
      [ "$4" = "--json" ] || return 1
      _branch_arg="$3"; _fields="$5" ;;
    *) return 1 ;;
  esac
  case "$_fields" in url|number,url,state) return 0 ;; esac
  return 1
}

_sha() {
  if command -v shasum >/dev/null 2>&1; then
    printf '%s' "$1" | shasum -a 256 | cut -c1-40
  else
    printf '%s' "$1" | sha256sum | cut -c1-40
  fi
}

_classify "$@"
_route=""

# --- secondary-rate-limited search guard (OMN-19852) -------------------------
_search_call=0
if [ "$_cls" = "api" ]; then
  case "$_endpoint" in search/*|http://*/search/*|https://*/search/*) _search_call=1 ;; esac
else
  case "$_cls" in search|search\ *) _search_call=1 ;; esac
fi
if [ "$_search_call" -eq 1 ] && [ "${ONEX_PR_WATCHER:-}" != "1" ]; then
  printf '%s\n' \
    'REFUSED (OMN-19852): GitHub search is the secondary-rate-limited bucket that took queue-status down at 21:5xZ on 2026-09-28.' \
    'Read PR state from state.json under the .claude_scratch/pr-watcher directory of the workspace root; use merge-drain pr_state_local.py: python3 pr_state_local.py --pr OmniNode-ai/<repo>#<n>, or python3 pr_state_local.py --status.' \
    'For merge counts, use git -C <canonical clone> log origin/dev on the clone that canonical-clone-sync keeps fetched.' >&2
  _call_log 1 refused
  exit 1
fi
unset _search_call

# Run one gh call and leave its routing identity/reason in _route. With routing
# disabled this is deliberately the same single command as the old shim path.
_run_gh() {
  _route=""
  if [ "${ONEX_GH_READ_ROUTING:-}" != "1" ]; then
    "$real" "$@"
    return $?
  fi

  # A candidate is only ever "worth asking the router about" -- gh_route.py's own
  # classify() is the one place that decides read vs not (mutation, viewer-resolving,
  # non-OmniNode-ai owner, unscoped search all still land on the operator there), so
  # widening this set is safe: it was narrower than what the router actually
  # classifies as a read (OMN-19852 P2 -- `gh search`, `repo view`, `issue view|list`,
  # `pr diff` and `api graphql` reads were silently running on the operator, on this
  # host and everywhere else the shim is installed, because they never reached the
  # router at all).
  local candidate=0 router="" py="" rf="" rc=0
  if [ "$_cls" = "api" ] && { [ "$_method" = "GET" ] || [ "$_method" = "HEAD" ]; }; then
    candidate=1
  else
    case "$_cls" in
      "pr view"|"pr list"|"pr checks"|"pr diff"|"run view"|"run list"|"repo view"|"issue view"|"issue list") candidate=1 ;;
      "search "*) candidate=1 ;;
    esac
  fi
  if [ "$candidate" -ne 1 ]; then
    "$real" "$@"
    rc=$?
    _route="operator shim-not-a-read"
    return "$rc"
  fi

  if [ -n "${ONEX_GH_ROUTER:-}" ]; then
    router="$ONEX_GH_ROUTER"
  else
    router="$(cd "$(dirname "$self")" 2>/dev/null && pwd)/gh_route.py"
  fi
  # The router is stdlib-only and makes no network connection itself (the
  # real gh and the mint command do), so any python3 on PATH can run it;
  # python3.13 first because that is the project interpreter.
  if [ -n "${ONEX_GH_ROUTE_PYTHON:-}" ]; then
    py="$ONEX_GH_ROUTE_PYTHON"
  else
    py="$(command -v python3.13 2>/dev/null)"
    [ -n "$py" ] || py="$(command -v python3 2>/dev/null)"
  fi
  if [ ! -f "$router" ] || [ ! -r "$router" ] || [ -z "$py" ] || [ ! -x "$py" ]; then
    "$real" "$@"
    rc=$?
    _route="operator-fallback router-unavailable"
    return "$rc"
  fi
  rf="$(mktemp "${TMPDIR:-/tmp}/onex-gh-route.XXXXXX" 2>/dev/null)"
  if [ -z "$rf" ]; then
    "$real" "$@"
    rc=$?
    _route="operator-fallback router-unavailable"
    return "$rc"
  fi
  _resolve_lane
  ONEX_LANE="$_lane" "$py" "$router" exec --result-file "$rf" -- "$@"
  rc=$?
  IFS= read -r _route < "$rf" || true
  rm -f "$rf" 2>/dev/null
  if [ -z "$_route" ] && [ "$rc" -ne 0 ]; then
    "$real" "$@"
    rc=$?
    _route="operator-fallback router-failed"
  fi
  return "$rc"
}

# owner/repo of the working directory's origin remote, or "" outside a clone.
_origin_repo() {
  local url
  url="$(git config --get remote.origin.url 2>/dev/null)"
  url="${url%.git}"
  url="${url##*:}"
  case "$url" in */*/*) url="${url#"${url%/*/*}"/}" ;; esac
  printf '%s' "$url"
}

# --- merge-queue bypass guard (OMN-17427) -------------------------------------
# omnibase_infra#4197 was pulled from the dev merge queue for failed checks at
# 14:15:03Z on 2026-09-28 and then landed anyway by a raw REST PUT merge at
# 14:27:17Z: the merge-drain workflow's own general rule told the land lane to
# do exactly that ("Merges by REST squash on the exact head: gh api -X PUT
# .../pulls/<n>/merge"), with no carve-out for a queue-protected branch, while
# a second, repo-specific instruction a few lines later in the same prompt said
# "never a REST merge". A prompt-text fix (repo_drains.js, this same ticket)
# removes that contradiction, but a land lane is an LLM reading a prompt, and a
# text rule is advice, not a control. This is the mechanical stop that holds
# regardless of what any prompt says: it resolves the merge-queue rule LIVE for
# the EXACT repo+branch a merge call targets -- never a hardcoded repo list, so
# a queue ruleset added to a second repo tomorrow is covered with no code
# change here -- and refuses the two shapes that bypass a queue: a raw
# `gh api ... -X PUT pulls/<n>/merge`, and `gh pr merge ... --admin`. A bare
# `gh pr merge` naming neither `--match-head-commit` nor `--auto` is also
# refused on a queue branch: those two flags are the only forms every sanctioned
# caller in this codebase uses (merge-drain SKILL.md, repo_drains.js,
# arm_green_prs.py), so an invocation carrying neither is unverified, not
# merely unusual, and this guard's own fail-closed rule (below) treats it the
# same as a call it could not resolve at all.
#
# Fail-closed, narrowly: only these two risky shapes are ever evaluated (a
# read, a safe enqueue-form merge, and everything that is not `pr merge` or a
# `pulls/.../merge` PUT never reaches this block at all, so an API hiccup here
# can never brick unrelated gh traffic). Once a call IS one of the two risky
# shapes, an unresolvable repo, PR number, base branch or rules read is BLOCKED
# rather than assumed safe -- CLAUDE.md rule 16: an unverified merge-queue
# status is never treated as "no queue". The one-line remedy on a false-positive
# block is always available and always safe: retry with the sanctioned enqueue
# form, `gh pr merge <n> --repo <owner>/<repo> --squash --match-head-commit
# <head>`.
_mq_is_rest_put=0
_mq_is_merge_call=0
_mq_admin=0
_mq_safe_flag=0
_mq_repo=""
_mq_pr_num=""
if [ "${1:-}" = "pr" ] && [ "${2:-}" = "merge" ]; then
  _mq_is_merge_call=1
  case "${3:-}" in ''|-*) ;; *) _mq_pr_num="$3" ;; esac
  _mq_prev=""
  for _mq_a in "$@"; do
    case "$_mq_prev" in -R|--repo) _mq_repo="$_mq_a" ;; esac
    case "$_mq_a" in
      --repo=*) _mq_repo="${_mq_a#--repo=}" ;;
      --admin) _mq_admin=1 ;;
      --match-head-commit|--auto) _mq_safe_flag=1 ;;
    esac
    _mq_prev="$_mq_a"
  done
  [ -n "$_mq_repo" ] || _mq_repo="$(_origin_repo)"
elif [ "$_cls" = "api" ] && [ "$_method" = "PUT" ]; then
  case "$_endpoint" in
    repos/*/pulls/*/merge)
      _mq_is_rest_put=1
      _mq_repo="${_endpoint#repos/}"; _mq_repo="${_mq_repo%%/pulls/*}"
      _mq_pr_num="${_endpoint#repos/*/pulls/}"; _mq_pr_num="${_mq_pr_num%%/merge}"
      ;;
  esac
fi

if [ "$_mq_is_rest_put" -eq 1 ] || { [ "$_mq_is_merge_call" -eq 1 ] && { [ "$_mq_admin" -eq 1 ] || [ "$_mq_safe_flag" -eq 0 ]; }; }; then
  _mq_shape="gh api -X PUT .../pulls/<n>/merge"
  if [ "$_mq_is_merge_call" -eq 1 ]; then
    _mq_shape="gh pr merge"
    [ "$_mq_admin" -eq 1 ] && _mq_shape="$_mq_shape --admin"
    if [ "$_mq_safe_flag" -eq 0 ] && [ "$_mq_admin" -eq 0 ]; then
      _mq_shape="$_mq_shape (neither --match-head-commit nor --auto)"
    fi
  fi
  _mq_branch=""
  if [ -n "$_mq_repo" ] && [ -n "$_mq_pr_num" ]; then
    _mq_branch="$("$real" pr view "$_mq_pr_num" --repo "$_mq_repo" --json baseRefName --jq .baseRefName 2>/dev/null)"
  fi
  _mq_gated=-1  # -1 unresolved (fail closed), 0 no queue rule, 1 queue rule present
  if [ -n "$_mq_repo" ] && [ -n "$_mq_branch" ]; then
    _mq_rules="$("$real" api "repos/$_mq_repo/rules/branches/$_mq_branch" 2>/dev/null)"
    if [ -n "$_mq_rules" ]; then
      case "$_mq_rules" in
        *'"type":"merge_queue"'*|*'"type": "merge_queue"'*) _mq_gated=1 ;;
        *) _mq_gated=0 ;;
      esac
    fi
  fi
  if [ "$_mq_gated" -ne 0 ]; then
    _mq_reason="repo=${_mq_repo:-<unresolved>} pr=${_mq_pr_num:-<unresolved>} base=${_mq_branch:-<unresolved>}"
    if [ "$_mq_gated" -eq 1 ]; then
      _mq_why="BLOCKED (OMN-17427): $_mq_shape bypasses the merge_queue rule on $_mq_reason. This is the queue's own rulesets API answer (repos/$_mq_repo/rules/branches/$_mq_branch), read live just now -- not a guess. The only sanctioned landing verb on a queue-protected branch is: gh pr merge $_mq_pr_num --repo $_mq_repo --squash --match-head-commit <head>  (this enqueues rather than merging, and still exits 0 -- read back mergedAt/mergeQueueEntry before calling it merged). A PR the queue dequeued for failed checks is RED, not merged: fix the cause and re-push, or leave it blocked. Never retry this exact call."
    else
      _mq_why="BLOCKED (OMN-17427): $_mq_shape could not be confirmed safe -- $_mq_reason, and the merge-queue rules read (repos/${_mq_repo:-<unresolved>}/rules/branches/${_mq_branch:-<unresolved>}) did not return a usable answer (network, auth, an unresolved PR/base, or a real API error). An unverified merge-queue status is never treated as 'no queue' (CLAUDE.md rule 16). Retry with the sanctioned enqueue form instead, which is safe on every branch whether or not it is queue-protected: gh pr merge <n> --repo <owner>/<repo> --squash --match-head-commit <head>  -- or resolve the read failure and retry this exact call."
    fi
    printf '%s\n' "$_mq_why" >&2
    _mq_log_dir="${XDG_CACHE_HOME:-$HOME/.cache}/omni/gh-usage"
    mkdir -p "$_mq_log_dir" 2>/dev/null || true
    printf '{"ts":"%s","guard":"merge_queue_bypass","gated":%s,"shape":"%s","repo":"%s","pr":"%s","base":"%s","pid":%s}\n' \
      "$(date -u +%Y-%m-%dT%H:%M:%SZ)" "$_mq_gated" "$(_clean "$_mq_shape")" "$(_clean "${_mq_repo:-}")" "$(_clean "${_mq_pr_num:-}")" "$(_clean "${_mq_branch:-}")" "$$" \
      >> "$_mq_log_dir/merge-queue-guard.jsonl" 2>/dev/null || true
    _call_log 1 refused
    exit 1
  fi
fi
unset _mq_a _mq_prev _mq_shape _mq_reason _mq_why _mq_rules _mq_log_dir


# A PR mutation can change the cached answers for its repository: drop that
# repository's entries before it runs (every entry when the repository is
# unknown). Entries are named <repo hash>.<key>, so lookups in other
# repositories stay cached.
if [ "${1:-}" = "pr" ]; then
  case "${2:-}" in
    create|ready|close|reopen|merge)
      _mut_repo=""
      _prev=""
      for _a in "$@"; do
        case "$_prev" in -R|--repo) _mut_repo="$_a" ;; esac
        case "$_a" in --repo=*) _mut_repo="${_a#--repo=}" ;; esac
        _prev="$_a"
      done
      [ -n "$_mut_repo" ] || _mut_repo="$(_origin_repo)"
      if [ -n "$_mut_repo" ]; then
        rm -f "$_cache_root/$(_sha "$_mut_repo" | cut -c1-16)".* 2>/dev/null
      else
        rm -f "$_cache_root"/* 2>/dev/null
      fi
      ;;
  esac
fi

if _cacheable "$@"; then
  _top="$(git rev-parse --show-toplevel 2>/dev/null)"
  _branch="$_branch_arg"
  if [ -z "$_branch" ] && [ -n "$_top" ]; then
    _branch="$(git symbolic-ref --short -q HEAD 2>/dev/null)"
  fi
  if [ -n "$_top" ] && [ -n "$_branch" ]; then
    _repo_id="$(_origin_repo)"
    [ -n "$_repo_id" ] || _repo_id="$_top"
    _key="$(_sha "$_repo_id" | cut -c1-16).$(_sha "$_top|$_branch|$_fields")"
    _entry="$_cache_root/$_key"
    _now="$(date +%s)"
    if [ -f "$_entry.meta" ]; then
      read -r _rc _at < "$_entry.meta"
      case "$_rc$_at" in
        *[!0-9]*|"") ;;
        *)
          if [ $((_now - _at)) -lt "$ONEX_GH_SHIM_TTL" ]; then
            cat "$_entry.out" 2>/dev/null
            cat "$_entry.err" >&2 2>/dev/null
            _log "$_rc" hit
            exit "$_rc"
          fi
          ;;
      esac
    fi
    mkdir -p "$_cache_root" 2>/dev/null
    _tmp="$_entry.tmp.$$"
    _run_gh "$@" > "$_tmp.out" 2> "$_tmp.err"
    _rc=$?
    cat "$_tmp.out"
    cat "$_tmp.err" >&2
    _keep=0
    if [ "$_rc" -eq 0 ]; then
      _keep=1
    elif grep -qi 'no pull requests found' "$_tmp.err" 2>/dev/null; then
      _keep=1
    fi
    if [ "$_keep" -eq 1 ]; then
      # .meta is written last and read first, so a reader never sees a
      # half-written entry as fresh.
      mv -f "$_tmp.out" "$_entry.out" 2>/dev/null
      mv -f "$_tmp.err" "$_entry.err" 2>/dev/null
      printf '%s %s\n' "$_rc" "$_now" > "$_tmp.meta" 2>/dev/null \
        && mv -f "$_tmp.meta" "$_entry.meta" 2>/dev/null
    fi
    rm -f "$_tmp.out" "$_tmp.err" "$_tmp.meta" 2>/dev/null
    _log "$_rc" miss
    exit "$_rc"
  fi
fi

# Pass-through. Run rather than exec, so the exit code can be logged; stdin,
# stdout, stderr and the terminal are inherited unchanged.
_run_gh "$@"
_rc=$?
_log "$_rc" none
exit "$_rc"
