Files
hooks/hooks/deny.sh
T
jiantw83 0e9308f60e feat(hooks): 技能名解析與阻擋輸出共用化
What:
- 新增 hooks/skill-name.sh。五支 CLI 各一個子命令,從各自的負載解析出這一次要用哪一支 jsc 技能,印一行「{domain}<TAB>{技能名}」。解不出來就印空字串並回 0。
- 新增 hooks/deny.sh。依當前 CLI 產出四種阻擋形態,訊息從參數或標準輸入進。
- version-guard.sh 與 restart-gate.sh 改用這兩支,各自那份工具名判定與技能名取值一併移除。

Why:
- 兩道閘門原本都拿 Claude 的工具名 Skill 當通用條件。另外四支 CLI 的工具名分別是 Bash、skill、view_file,一律被擋在判定之外,兩道閘門在那四支上長期完全失效,而且一聲都不吭。
- 阻擋形態每支 CLI 都不一樣,判定卻是同一件事。各自留一份輸出邏輯,改了一支忘了另一支,就會做出「判定擋下、CLI 照樣放行」的無聲失效。

How:
- 技能名取值規則只留 skill-name.sh 這一份,環境變數 JSC_SKILL、SKILL 優先。claude 讀 skill 欄位、codex 認 tool_input.command 裡那條 SKILL.md 路徑、copilot 先剝一層字串化 JSON 再讀 toolArgs、antigravity 讀 toolCall.args.AbsolutePath 並另收提示字串、kiro 取提示開頭那個斜線指令。
- 閘門端用 awk 判 NF == 2 才取值。少一欄就當成解析不出來,免得沒有定位字元時 cut -f2 把整行當成技能名,拼出一個不存在的技能名去比對豁免清單。
- deny.sh 定形態:claude、codex、copilot 走 stderr 加 exit 2;antigravity 印 stdout 的單行 deny JSON 並固定回 0,因為那支 CLI 的結束碼語意兩邊文件都沒寫、絕不可靠;kiro 擋不下技能叫用,改印警告後回 0;認不得的代號走 stderr 加 2 這個保守預設。
- 訊息整段走同一條管線送進 deny.sh。分次呼叫會做出好幾份 deny JSON,antigravity 只認第一份,後面幾段使用者永遠看不到。
- 豁免清單與 fail-open 原則不變。這兩支刻意以子行程呼叫、不用 source 載入:讀不到只會讓技能名解不出來而安靜放行,不會反過來擋掉每一次呼叫。

Who:codex、copilot、antigravity、kiro 四支 CLI 的 pre-tool hook 接線修正。
2026-08-31 19:05:48 +08:00

58 lines
2.6 KiB
Bash
Executable File
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#!/usr/bin/env sh
# deny.sh — 產出各 CLI 認得的「擋下這一次呼叫」輸出。
#
# 用途:阻擋形態每支 CLI 都不一樣,判定卻是同一件事。形態留這一份,閘門只管判定:
# 各自留一份輸出邏輯,改了一支忘了另一支,就會出現「判定擋下、CLI 卻沒收到拒絕」的無聲失效。
#
# 用法:deny.sh {cli} [訊息...]
# 訊息從參數來,沒給參數就讀標準輸入。兩者都空就送一句預設訊息,不會產出空的拒絕。
#
# 各 CLI 的阻擋形態:
# claude、codex、copilot 訊息寫 stderr,結束碼 2 就是拒絕
# antigravity stdout 印 {"decision":"deny","reason":"..."}。
# 結束碼語意兩邊文件都沒寫,**絕對不可靠**,所以固定回 0,
# 拒絕整個靠那一行 JSON。也因此 stdout 只准有那一行,
# 呼叫端要給人看的字一律走 stderr。
# kiro 擋不下來。技能叫用是 agent 內部請求,preToolUse 攔不到,
# userPromptSubmit 的非零結束碼也不會擋下那一輪。
# 唯一可用的介入是 stdout 注入,所以改印警告並回 0。
#
# 結束碼:
# 2 拒絕已經送出(claude、codex、copilot,以及認不得的 CLI 代號)。呼叫端直接把它當自己的結束碼。
# 0 拒絕已經送出,但形態不靠結束碼(antigravity 的 stdout JSON),或這支 CLI 根本擋不下來(kiro)。
# 本檔沒有其他結束碼。認不得的代號不另立一種:五支裡有三支是 stderr 加 2,未知代號走這個保守預設,
# 比靜靜放行安全。
set -u
CLI="${1:-}"
shift 2>/dev/null || true
if [ "$#" -gt 0 ]; then
MSG="$*"
else
MSG=""
[ -t 0 ] || MSG=$(cat 2>/dev/null || true)
fi
[ -n "$MSG" ] || MSG='[jsc]:這次呼叫已被 jsc 閘門擋下。'
# 把訊息壓成一個合法的 JSON 字串值:反斜線與雙引號先跳脫,換行與定位字元改成跳脫序列。
# 直接把原文塞進 JSON 會做出解不開的負載,antigravity 收到壞 JSON 等於沒收到拒絕。
json_reason() {
printf '%s' "$MSG" \
| sed 's/\\/\\\\/g; s/"/\\"/g; s/ /\\t/g' \
| awk '{ printf "%s%s", sep, $0; sep = "\\n" } END { printf "\n" }'
}
case "$CLI" in
antigravity)
printf '{"decision":"deny","reason":"%s"}\n' "$(json_reason)"
exit 0 ;;
kiro)
printf '%s\n' "$MSG"
printf '[jsc]:kiro 擋不下技能叫用(CLI 限制),上面這段只是警告,請自己先處理完再繼續。\n'
exit 0 ;;
*)
printf '%s\n' "$MSG" >&2
exit 2 ;;
esac