#!/bin/bash
# .claude/hooks/pre-write-guard.sh
# arcrun PreToolUse guard for Write / Edit / MultiEdit
#
# 職責：擋下會違反 CLAUDE rules 的檔案寫入操作
# 退出 code：
#   0 = 允許
#   2 = 擋下（stderr 訊息會回傳給 CC）
#
# 依賴：jq

set -o pipefail

INPUT=$(cat)

FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // .tool_input.path // ""')
# 取得將要寫入的內容（Write: content；Edit: new_string；MultiEdit: edits[].new_string 全部串起來）
CONTENT=$(echo "$INPUT" | jq -r '
  .tool_input.content
  // .tool_input.new_string
  // (.tool_input.edits // [] | map(.new_string // "") | join("\n"))
  // ""
')

block() {
  local rule="$1"
  local reason="$2"
  local fix="$3"
  cat >&2 <<EOF
❌ BLOCKED by arcrun CLAUDE rules
違反項：${rule}
檔案：${FILE_PATH}
原因：${reason}
正確做法：${fix}
參考：.claude/rules/02-forbidden.md
EOF
  exit 2
}

# ─────────────────────────────────────────────────────────────────────────────
# 規則 1.1：registry/components/ 下不准 TS（除非是 AssemblyScript）
# ─────────────────────────────────────────────────────────────────────────────
if [[ "$FILE_PATH" == *"registry/components/"* && "$FILE_PATH" == *.ts ]]; then
  # 允許 asconfig.json 同目錄的 AssemblyScript
  COMP_DIR=$(dirname "$FILE_PATH")
  if [[ ! -f "$COMP_DIR/asconfig.json" ]]; then
    block "1.1" \
      "registry/components/ 下禁止 TypeScript（除非是 AssemblyScript 且同目錄有 asconfig.json）" \
      "零件必須用 TinyGo (main.go) 或 AssemblyScript 實作並編譯成 .wasm"
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# 規則 1.2：禁止在非法位置新增 auth/credential 實作
# ─────────────────────────────────────────────────────────────────────────────
# 合法位置：registry/components/auth_static_key | auth_oauth2 | auth_service_account | auth_mtls
if [[ "$FILE_PATH" =~ auth[-_](static[-_]key|oauth2|service[-_]account|mtls) ]]; then
  if [[ "$FILE_PATH" != *"registry/components/auth_"* ]]; then
    block "1.2" \
      "auth primitive 實作只能放在 registry/components/auth_<type>/" \
      "改去 registry/components/auth_static_key/ 等目錄，用 TinyGo 實作 main.go"
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# 規則 2.1：禁止新增含特定關鍵字的 TS 檔案（credential-injector / jwt-signer 等）
# ─────────────────────────────────────────────────────────────────────────────
if [[ "$FILE_PATH" == *.ts ]]; then
  BASE=$(basename "$FILE_PATH")
  # 既有的 credential-injector.ts / jwt-signer.ts 允許修改（為了刪除），但不准新增同名
  if [[ "$BASE" =~ ^(credential[-_]injector|jwt[-_]signer)\.ts$ ]]; then
    if [[ ! -f "$FILE_PATH" ]]; then
      block "2.1" \
        "禁止新增 ${BASE}（Phase 1-3 的目標是刪除此類檔案，不是重建）" \
        "credential 注入 / JWT signing 屬於 WASM 零件職責，改去 registry/components/auth_*/"
    fi
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# 規則 2.2：cypher-executor TS 裡不准實作業務邏輯（只准 wasi-shim.ts 做 crypto）
# ─────────────────────────────────────────────────────────────────────────────
if [[ "$FILE_PATH" == *"cypher-executor/src/"* && "$FILE_PATH" == *.ts ]]; then
  BASE=$(basename "$FILE_PATH")

  # 通用類別：種子資料檔（*-seeds.ts）= installer 要灌進 KV 的 recipe 種子清單。
  #   其中的 endpoint / {{template}} 字串是「資料欄位」（宣告 recipe 打哪 / 怎麼注入），
  #   會被序列化寫進 RECIPES KV，**不是** TS 裡的呼叫 / 展開實作；真正執行仍走零件 / WASM primitive。
  #   故整類豁免 endpoint / template 字串檢查。這是「種子資料檔」這個普遍類別的規則，
  #   不是為某個零件 / recipe 開的特例（richblack 2026-06-06 原則：不為單一零件改全域規則）。
  IS_SEED_DATA_FILE=false
  if [[ "$BASE" == *-seeds.ts ]]; then
    IS_SEED_DATA_FILE=true
  fi

  # crypto.subtle.decrypt：只准在 wasi-shim.ts
  if echo "$CONTENT" | grep -qE "crypto\.subtle\.decrypt"; then
    if [[ "$BASE" != "wasi-shim.ts" ]]; then
      block "2.2" \
        "AES-GCM 解密（crypto.subtle.decrypt）只准出現在 wasi-shim.ts 的 crypto_decrypt host function" \
        "把解密邏輯移到 wasi-shim.ts 的 host function；或讓 WASM 零件透過 u6u.crypto_decrypt 呼叫"
    fi
  fi

  # crypto.subtle.sign with RSASSA：只准在 wasi-shim.ts
  if echo "$CONTENT" | grep -qE "crypto\.subtle\.sign.*RSASSA"; then
    if [[ "$BASE" != "wasi-shim.ts" ]]; then
      block "2.2" \
        "RS256 簽章只准出現在 wasi-shim.ts 的 crypto_sign_rs256 host function" \
        "把簽章移到 wasi-shim.ts；或讓 auth_service_account WASM 透過 u6u.crypto_sign_rs256 呼叫"
    fi
  fi

  # Template 展開：{{secret.X}} 或 {{runtime.X}} 屬於 WASM 職責
  # 例外：種子資料檔（*-seeds.ts）裡的 {{secret.X}} / {{runtime.X}} 是「資料字面值」而非 TS 展開邏輯，
  #       真正的展開仍在 WASM auth primitive 內完成。
  if [[ "$IS_SEED_DATA_FILE" == "false" ]] && echo "$CONTENT" | grep -qE "\{\{(secret|runtime)\." ; then
    block "2.2" \
      "Template 展開（{{secret.X}} / {{runtime.X}}）屬於 WASM auth primitive 職責" \
      "把這段邏輯改寫到 registry/components/auth_static_key/main.go（TinyGo）"
  fi

  # Hard-code 的 BUILTIN_API_RECIPES / BUILTIN_CREDENTIALS_MAP 新增
  if echo "$CONTENT" | grep -qE "(BUILTIN_API_RECIPES|BUILTIN_CREDENTIALS_MAP)\s*[:=]"; then
    # 允許「把它設成空物件」或「刪除」，但不准新增實作
    if echo "$CONTENT" | grep -qE "BUILTIN_API_RECIPES.*=.*\{\s*[a-zA-Z]"; then
      block "2.2" \
        "禁止在 TS 裡新增 BUILTIN_API_RECIPES / BUILTIN_CREDENTIALS_MAP 實作" \
        "API 呼叫邏輯屬於各自的 WASM 零件（gmail.wasm / telegram.wasm 等），cypher-executor 只做 routing"
    fi
  fi

  # Hard-code API endpoint 實作
  # 例外：種子資料檔（*-seeds.ts）的 endpoint 字串（sheets.googleapis.com 等）是 recipe 的
  #       「資料欄位」（宣告這個 recipe 預設打哪），會被序列化寫進 RECIPES KV，**不是** TS 裡的呼叫實作；
  #       真正的 API 呼叫仍走原本零件 / http_request 路徑。
  HARDCODED_APIS=(
    "gmail\.googleapis\.com/gmail/v1/users/me/messages/send"
    "api\.telegram\.org/bot.*sendMessage"
    "sheets\.googleapis\.com/v4/spreadsheets"
    "notify-api\.line\.me/api/notify"
  )
  for PATTERN in "${HARDCODED_APIS[@]}"; do
    if echo "$CONTENT" | grep -qE "$PATTERN"; then
      # 允許 wasi-shim.ts（http_request host function proxy）+ 種子資料檔（*-seeds.ts）
      if [[ "$BASE" != "wasi-shim.ts" && "$IS_SEED_DATA_FILE" == "false" ]]; then
        block "2.2" \
          "禁止在 cypher-executor TS 裡 hard-code API endpoint（偵測到: $PATTERN）" \
          "把 API 呼叫移到對應的 WASM 零件（registry/components/gmail/main.go 等）"
      fi
    fi
  done

  # exchangeGoogleJwt / 類似 token exchange function
  if echo "$CONTENT" | grep -qE "(exchangeGoogleJwt|exchangeServiceAccountJwt|signGoogleJwt)"; then
    if [[ "$BASE" != "wasi-shim.ts" ]]; then
      block "2.2" \
        "Token exchange 邏輯屬於 auth_service_account WASM 零件" \
        "改到 registry/components/auth_service_account/main.go"
    fi
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# 規則 3.3：禁止建立 *-v2 / new-* / *-worker 類複製貼上目錄
# ─────────────────────────────────────────────────────────────────────────────
if [[ "$FILE_PATH" =~ /(auth|credential|jwt|oauth|gmail|telegram|google-sheets|line-notify|http-request)[-_](v2|v3|new|worker|backup|temp)/ ]]; then
  block "3.3" \
    "禁止為同一零件建立平行目錄（v2/new/worker/backup 等）" \
    "直接修改 registry/components/<name>/main.go 即可；需要版本管理請用 git branch"
fi
if [[ "$FILE_PATH" =~ /new-(auth|credential|jwt|oauth|gmail|telegram)/ ]]; then
  block "3.3" \
    "禁止為同一零件建立 new-<name>/ 平行目錄" \
    "直接修改 registry/components/<name>/main.go"
fi

# ─────────────────────────────────────────────────────────────────────────────
# 規則 7.x：薄殼原則（能力長在 API，介面只暴露）— 見 .claude/rules/07-thin-shell.md
# 只擋語法層可偵測的反例；recipe 層拼裝 / 藏在 helper 的邏輯需 code review 把關（07 §6）。
# 範圍：薄殼介面 = cli/src/ 與 arcrun-mcp/src/（不含 cypher-executor，那是 API 本體）。
# ─────────────────────────────────────────────────────────────────────────────
block7() {
  cat >&2 <<EOF
❌ BLOCKED by arcrun CLAUDE rules
違反項：7（薄殼原則）
檔案：${FILE_PATH}
原因：$1
正確做法：$2
參考：.claude/rules/07-thin-shell.md
EOF
  exit 2
}

if [[ ( "$FILE_PATH" == *"/cli/src/"* || "$FILE_PATH" == *"arcrun-mcp/src/"* ) \
      && ( "$FILE_PATH" == *.ts || "$FILE_PATH" == *.js ) ]]; then

  # 7.1 seed 邏輯不准寫在介面層（§4.1 反例）。seed 是 API 行為，由 API 端點完成。
  #     允許薄殼「呼叫一個 seed 端點」（單一 fetch），但不准在介面層定義 seed 編排函式。
  if echo "$CONTENT" | grep -qE "(function|const|async)\s+(seedApiRecipes|seedAuthRecipes|seedRecipes)\b"; then
    block7 \
      "seed 編排邏輯寫在介面層（CLI/MCP），這是壓測 §4.1 的根因——seed 應是 API 行為" \
      "把 seed 改成 API 端點（cypher-executor POST /init/seed 或部署完成時自動 seed），薄殼只呼叫該端點一次"
  fi

  # 7.2 「先 update 失敗再 insert」/「先 GET 找有則 PATCH 無則 POST」這類 upsert 拼裝
  #     在同一函式內同時出現 update/patch 與 insert/post 的條件分支 = 把 API 缺的能力拼在介面層。
  if echo "$CONTENT" | grep -qiE "method:\s*['\"]PATCH['\"]" && echo "$CONTENT" | grep -qiE "method:\s*['\"]POST['\"]"; then
    if echo "$CONTENT" | grep -qiE "(upsert|找到.*PATCH|沒找到.*POST|exists.*update.*else.*insert|if.*found.*patch)"; then
      block7 \
        "在介面層拼裝 upsert（先找/PATCH 否則 POST）——這是能力，該在 API 提供單一 upsert 端點" \
        "請 cypher-executor 提供 upsert 端點（API 內部做 GET→PATCH/POST），薄殼只呼叫該端點"
    fi
  fi

  # 7.3 client 端「全部成功才做下一步」這類補 API 缺口的 gate（§4.1 deployFullyOk 反例）
  if echo "$CONTENT" | grep -qE "(deployFullyOk|全部成功.*test|/全部成功/)"; then
    block7 \
      "用 client 端字串判斷 gate 後續行為（deployFullyOk 類）——脆弱且把編排責任推給介面層" \
      "讓 API 保證後置狀態（部署成功即 recipe 就緒），薄殼不做這種 gate"
  fi
fi

# ─────────────────────────────────────────────────────────────────────────────
# 規則 4.3：禁止自行在 docs/3-specs/ 下建新 SDD 目錄
# ─────────────────────────────────────────────────────────────────────────────
if [[ "$FILE_PATH" == *"docs/3-specs/"* ]]; then
  # 檢查是否在已知 SDD 目錄內
  KNOWN_SDDS=(
    "docs/3-specs/arcrun"
    "docs/3-specs/arcrun-core-mvp"
    "docs/3-specs/arcrun-platform-evolution"
    "docs/3-specs/component-registry-canon"
    "docs/3-specs/component-gatekeeping"  # 2026-05-29 richblack 確認新建（Phase 3 把關）
    "docs/3-specs/data-exfil-warning"     # 2026-05-30 richblack 確認新建（資料外流警示）
    "docs/3-specs/user-cc-harness"        # 2026-06-03 richblack 確認新建（用戶 CC harness 安裝）
    "docs/3-specs/llm-interface"          # 2026-05-16 richblack 確認新建（AI 操盤手介面）
    "docs/3-specs/recipe-system"          # richblack 確認新建（recipe 系統）
    "docs/3-specs/resumable-workflow"     # richblack 確認新建（可恢復工作流）
    "docs/3-specs/workflow-discovery"     # 2026-06-27 總管 issue #8 交辦新建（工作流 description slot + search_workflow，北極星入口缺口）
    "docs/3-specs/thin-shell-alignment"   # 2026-06-27 總管 issue #11 交辦新建（CLI/MCP 薄殼漂移全面盤點 + 防複發機制）
    "docs/3-specs/portal-auth"            # 2026-07-13 總管 issue #24/#25 交辦新建（RAG Portal 多人授權，rag-wave1 T1/T2）
  )
  IN_KNOWN=false
  for K in "${KNOWN_SDDS[@]}"; do
    if [[ "$FILE_PATH" == *"$K/"* ]]; then
      IN_KNOWN=true
      break
    fi
  done
  if [[ "$IN_KNOWN" == "false" ]]; then
    block "4.3" \
      "禁止自行在 docs/3-specs/ 下建立新的頂層 SDD 目錄" \
      "正確程序（見 .claude/rules/00-sdd-protocol.md「新增 SDD 的完整程序」）：(1) 先與 richblack 確認要新建此 SDD；(2) 取得明確確認後，在本檔 KNOWN_SDDS 陣列加一行 \"docs/3-specs/<目錄名>\"（執行已授權任務的必要步驟）；(3) 白名單放行後才寫 design/tasks。若是現有 SDD 的補充檔，改放已知 SDD 目錄下。"
  fi
fi

exit 0
