#!/bin/bash
# arcrun-guard.sh — 用戶專案的 arcrun PreToolUse guard（由 acr install-harness 裝進 .claude/hooks/）
#
# 對象：在「用 arcrun 開發」的專案裡工作的 CC。擋它走歪（退回自寫 Python / 不用 recipe / 未經同意暴露）。
# 與 arcrun repo 開發版 hook 完全不同（那個擋的是開發 arcrun 本身）。
#
# 鐵則（user-cc-harness design §0.5）：每次擋下/提醒都要給「具體怎麼做才對」的正路，不只說「不行」。
#
# 退出碼：0=允許（可附 stderr 提醒）；2=硬擋（stderr 回給 CC）。
# 分級（design §4）：多數用「提醒不硬擋」（避免誤殺正常 python）；硬擋只留給「未經同意暴露資料」。

set -o pipefail
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name // ""')
CMD=$(echo "$INPUT" | jq -r '.tool_input.command // ""')

remind() {
  # 提醒但放行（exit 0）。CC 看到 stderr，自己判斷是否真要繼續。
  echo "💡 arcrun 提醒：$1" >&2
  echo "   正路：$2" >&2
  exit 0
}
block() {
  echo "❌ arcrun guard 擋下：$1" >&2
  echo "   正路：$2" >&2
  exit 2
}

# ── 硬擋：未經人類同意的暴露動作（明確越界，mindset §5）──────────────
# 非互動環境下 CC 自己跑「部署對外 webhook / push recipe」= 替人類決定公開。
#
# 壓測 §9.5 修正（A6）：舊版 grep "acr push" 連「在 echo/heredoc 裡**提到** push 字串」
# 都擋 → CC 連「把可貼上的指令印給使用者看」都做不到，反而違反 §0.5「擋下必指正路」鐵則。
# 故先判斷此命令是「展示指令」還是「真的執行 push」，只擋後者。
#
# 誠實限制（mindset §7）：shell 命令層**無法 100% 乾淨區分**「執行」與「展示」
# （例：`echo "跑 acr push"` 與 `acr push` 對單純 grep 都命中）。以下是**啟發式**，
# 目的是降低誤殺、讓「印出正路指令」這條合法用途能通，不是完美防護。邊角情況可能漏判，
# 但漏判方向是「偏向放行展示」——而真正的暴露（執行）仍受 TTY/env 把關。
if echo "$CMD" | grep -qE "acr (push|recipe push)\b"; then
  # 目標（壓測 §9.5）：擋「真的執行 push」，放行「把 push 指令印給使用者看」。
  #
  # 作法：把 heredoc 主體（cat/印出用的多行文字）先抽掉，剩下的才是「實際會被 shell 執行的命令列」，
  # 再看 push 是否出現在那裡的「命令位置」（行首 / ; & | && || 之後）。
  #   執行 → 命中：`acr push x`、`cd f && acr push x`、`echo done; acr push x`
  #   展示 → 不命中：`echo "跑 acr push x"`、`cat <<EOF\n acr push x \nEOF`（push 在 heredoc 主體內）
  #
  # 抽掉 heredoc 主體：刪掉從 `<<EOF`（或任何 <<TOKEN）那行的下一行起、到單獨一行 TOKEN 為止的內容。
  # 用 awk 做簡單狀態機（只處理最常見的 `<<TOKEN` / `<<-TOKEN`，不含複雜巢狀——夠用且誠實）。
  EXEC_PART=$(printf '%s\n' "$CMD" | awk '
    BEGIN{inhd=0}
    {
      if (inhd) { if ($0 ~ "^[[:space:]]*" term "[[:space:]]*$") { inhd=0 }; next }
      line=$0
      if (match(line, /<<-?[[:space:]]*"?'"'"'?[A-Za-z_][A-Za-z0-9_]*"?'"'"'?/)) {
        t=substr(line, RSTART, RLENGTH); gsub(/^<<-?[[:space:]]*["'"'"']?/, "", t); gsub(/["'"'"']$/, "", t)
        term=t; inhd=1
      }
      print line
    }')

  # 誠實限制（mindset §7）：shell 層無法 100% 乾淨區分「執行」與「展示」。awk heredoc 抽取只覆蓋
  # 常見形式（`<<EOF` / `<<-EOF` / 引號 token）；`bash <<EOF ... acr push` 這種「heredoc 內容其實
  # 會被執行」的偷渡，這裡會誤放——但它已被上游安全分類器擋下（壓測 §9.6 實證）。取捨上偏向
  # 「放行展示」以保住 §0.5「擋下也要能印出正路指令」這條合法用途，不假裝此啟發式完美。
  if echo "$EXEC_PART" | grep -qE "(^|[;&|][[:space:]]*)acr[[:space:]]+(push|recipe[[:space:]]+push)\b"; then
    if [ ! -t 0 ] && [ "${ARCRUN_HUMAN_CONFIRMED:-}" != "1" ]; then
      block "在非互動環境自動執行暴露動作（acr push / recipe push 會讓東西可被外部呼叫）" \
        "交人類在終端機執行（真 TTY 會自動放行）。可把指令完整複製給使用者貼上自己跑：\`acr push <你的 workflow.yaml>\`。或使用者先在對話明示同意後親自於終端機執行。不要替使用者決定公開。（部署前的正路見 arcrun-mindset Skill：先 \`acr validate\`）"
    fi
  fi
fi

# ── 提醒（不硬擋）：退回自寫 Python/Node 一次性自動化 ──────────────────
# 「我先用 Python 測試」這類退回熟悉工具的傾向。python 不絕對錯（可能跑測試），故提醒不擋。
if echo "$CMD" | grep -qE "(^|[;&| ])(python3?|node)[ ]+[^ ]+\.(py|js|mjs|ts)\b"; then
  # 排除明顯的測試 / 既有工具呼叫（pytest / npm test / jest 等）降低誤判
  if ! echo "$CMD" | grep -qE "(pytest|jest|vitest|npm (run )?test|mocha|\btest_)"; then
    remind "偵測到用 python/node 跑腳本。這專案用 Arcrun，串服務/自動化不要自刻一次性腳本。" \
      "讀 arcrun-mindset Skill，先把需求寫成「意圖」串（\`input >> ON_SUCCESS >> <下一步>\`，邊只有 ON_SUCCESS 與「對每個 X」），再用 \`acr search <關鍵字>\` 查哪些零件/recipe 存在，最後才寫 workflow.yaml → \`acr validate\` → \`acr run\`。若這確實不是自動化（例如跑測試/別的工具），忽略本提醒。"
  fi
fi

# ── 提醒（不硬擋）：自寫打固定 API 的 script，而非 recipe ──────────────
if echo "$CMD" | grep -qE "(curl|fetch|requests\.(get|post)|axios).*https?://"; then
  remind "偵測到自己打外部 API。Arcrun 裡「打固定 endpoint」應寫成 recipe，不自刻 HTTP 呼叫。" \
    "先 \`acr recipe search <服務名>\` 看有沒有現成的；沒有就自己寫幾行 YAML（canonical_id/endpoint/method/auth_service）用 \`acr recipe push\` 投稿，workflow 裡用 \`http_request\` ＋ 該 recipe 引用它。缺 recipe 就自己補，不必問人。寫法見 arcrun-mindset Skill。"
fi

# ── 提醒（不硬擋）：把 code 節點當成缺零件的替代品（「腹語術」）──────────────
# 查詢回 not_found 就改寫成 code = 表面用 Arcrun、實際全寫 JS。這是現世代最常見的走歪。
if [ "$TOOL" = "Write" ] || [ "$TOOL" = "Edit" ] || [ "$TOOL" = "MultiEdit" ]; then
  FILE=$(echo "$INPUT" | jq -r '.tool_input.file_path // ""')
  CONTENT=$(echo "$INPUT" | jq -r '.tool_input.content // .tool_input.new_string // ""')
  if echo "$FILE" | grep -qE '\.(ya?ml)$' && echo "$CONTENT" | grep -qE 'component:[[:space:]]*["'"'"']?code\b'; then
    # 只在 code 內容看起來在做流程控制/取代零件時提醒（含 if/for/fetch），單純整形不吵
    if echo "$CONTENT" | grep -qE '\b(if[[:space:]]*\(|for[[:space:]]*\(|fetch\(|await[[:space:]]+fetch)'; then
      remind "workflow 裡的 \`code\` 節點含流程控制/HTTP 呼叫——這可能是「腹語術」（表面用 Arcrun、實際全寫 JS）。" \
        "\`code\` 只用於局部整形（例：剝掉 LLM 回應的雜訊、切段落）。缺外部 API → 寫 recipe（\`acr recipe push\`）；缺計算能力 → 投稿零件 PR；要判斷 → 用條件邊（\`if_control\` 配 \`ON_TRUE\`／\`ON_FALSE\`，或 \`switch\`／\`try_catch\` 配 \`ON_BRANCH\`），不要寫 code 判斷。每個寫進 code 的 if 都是沒被測過的新 bug。見 arcrun-mindset Skill。"
    fi
  fi
fi

exit 0
