#!/bin/bash # subagent-first-guard 的測試(inkstone/ISEP#66) # # 補的三個洞: # 洞一:.sh 從沒進過受管副檔名清單 ⇒ 這個 repo 自己的閘(全是 .sh)從沒被這道閘管過。 # 洞二:「這個 session 派過一次工」被當永久放行——拿掉,不再是放行條件。 # 洞三:「同一 session 只擋一次」讓警報響過一次就對餘生啞掉——改成寬限期,過了再響。 # # 判準:派過工不再免死金牌/閘本身(.sh)進受管清單/寬限期內不鬼打牆、過了要再響/ # CHILD_SESSION=subagent 本人放行/測試檔・system-dev wiki 放行/ # solo-ok 要非空(寫理由)才放行,單純 touch 不算。 cd "$(dirname "$0")/.." || exit 1 H=hooks/subagent-first-guard.sh PASS=0; FAIL=0 run(){ # $1=want_exit $2=file_path $3=sid $4=child(0/1) local want="$1" fp="$2" sid="$3" child="${4:-0}" local payload payload=$(python3 -c 'import json,sys;print(json.dumps({"tool_name":"Edit","tool_input":{"file_path":sys.argv[1]},"session_id":sys.argv[2]}))' "$fp" "$sid") if [ "$child" = "1" ]; then got=$(CLAUDE_CODE_CHILD_SESSION=1 SUBAGENT_GUARD_NAG_TTL="${TTL:-900}" bash -c 'printf "%s" "$1" | bash "$2" >/dev/null 2>&1; echo $?' _ "$payload" "$H") else got=$(env -u CLAUDE_CODE_CHILD_SESSION SUBAGENT_GUARD_NAG_TTL="${TTL:-900}" bash -c 'printf "%s" "$1" | bash "$2" >/dev/null 2>&1; echo $?' _ "$payload" "$H") fi if [ "$got" = "$want" ]; then PASS=$((PASS+1)); printf ' ✅ '; else FAIL=$((FAIL+1)); printf ' ❌ '; fi printf 'want=%s got=%s sid=%s %.70s\n' "$want" "$got" "$sid" "$fp" } clean(){ rm -f "/tmp/.subagent-spawned-$1" "/tmp/.solo-ok-$1" "/tmp/.subagent-guard-warned-$1"; } R="/Users/youlinhsieh/Documents/tech_projects/InkStoneCo" ISEP="/Users/youlinhsieh/Documents/tech_projects/ISEP" echo "── 洞一:閘本身是 .sh,也要被這道閘管(重演 2026-08-27 那次)──" S=t66-hole1-a; clean "$S" run 2 "$R/.claude/hooks/no-ticket-no-dispatch.sh" "$S" 0 S=t66-hole1-b; clean "$S" run 2 "$ISEP/hooks/subagent-first-guard.sh" "$S" 0 echo "── 洞二:派過工不是免死金牌——就算剛派、就算派了很多次,直接手改一樣要擋 ──" S=t66-hole2-a; clean "$S" date +%s > "/tmp/.subagent-spawned-$S" # 剛派過工(此刻) run 2 "$R/.claude/hooks/no-ticket-no-dispatch.sh" "$S" 0 S=t66-hole2-b; clean "$S" echo $(( $(date +%s) - 3600 )) > "/tmp/.subagent-spawned-$S" # 一小時前派過(今天真實情境的重演) run 2 "$R/.claude/hooks/no-ticket-no-dispatch.sh" "$S" 0 echo "── 洞三:警報不能響一次就對餘生啞掉——寬限期內不鬼打牆,過了要再響 ──" S=t66-hole3; clean "$S"; TTL=2 run 2 "$R/matrix/arcrun/foo.py" "$S" 0 # 第一次:沒見過 → 擋,順便留下 warned 時戳 run 0 "$R/matrix/arcrun/foo.py" "$S" 0 # 馬上重試:寬限期內 → 不鬼打牆,放行 sleep 3 run 2 "$R/matrix/arcrun/foo.py" "$S" 0 # 寬限期過了 → 再響 TTL=900 echo "── 不該擋:subagent 本人(CHILD_SESSION=1)自己寫它被派來寫的 code ──" S=t66-child; clean "$S" run 0 "$R/.claude/hooks/no-ticket-no-dispatch.sh" "$S" 1 run 0 "$R/matrix/arcrun/foo.py" "$S" 1 echo "── 不該擋:測試檔 ──" S=t66-test; clean "$S" run 0 "$R/matrix/arcrun/foo_test.py" "$S" 0 run 0 "$R/matrix/arcrun/foo.test.ts" "$S" 0 run 0 "$ISEP/tests/foo.spec.js" "$S" 0 echo "── 不該擋:system-dev/ 的 wiki(即使副檔名受管)──" S=t66-wiki; clean "$S" run 0 "$R/system-dev/wiki/status.md" "$S" 0 run 0 "$R/system-dev/docs/some_script.py" "$S" 0 echo "── 不該擋:非受管副檔名(一般 .sh 不在 hooks 目錄/.md/.json)──" S=t66-nonguard; clean "$S" run 0 "$R/scripts/deploy.sh" "$S" 0 run 0 "$R/README.md" "$S" 0 run 0 "$R/package.json" "$S" 0 echo "── solo-ok:要非空(寫理由)才放行,單純 touch 不算 ──" S=t66-soloempty; clean "$S" touch "/tmp/.solo-ok-$S" run 2 "$R/matrix/arcrun/foo.py" "$S" 0 clean "$S" echo "理由:單行修,緊急止血" > "/tmp/.solo-ok-$S" run 0 "$R/matrix/arcrun/foo.py" "$S" 0 clean "$S" echo "── 真違規不能因為這次改動漏擋:全新 session 直接改一般 code ──" S=t66-fresh; clean "$S" run 2 "$R/matrix/arcrun/graph-executor.ts" "$S" 0 # 清理 for s in t66-hole1-a t66-hole1-b t66-hole2-a t66-hole2-b t66-hole3 t66-child t66-test t66-wiki t66-nonguard t66-soloempty t66-fresh; do clean "$s"; done echo echo "$PASS/$((PASS+FAIL)) 通過" [ "$FAIL" -eq 0 ]