feat/cli-hook-rewire/main
develop
hooks/skill-name.sh
hooks/deny.sh
hooks/codex-hooks.json
hooks/hooks.json
hooks/version-guard.sh
hooks/restart-gate.sh
tools/wire-cli.sh
.codex-plugin/plugin.json
hooks
references/behaviors.md
skills/hooks-install/SKILL.md
六個全部不會報錯——設定寫得看起來正確,CLI 靜默不理,日誌、結束碼與 status 都沒有痕跡。
status
Skill
PreToolUse
actions
JSC_CLI={代號}
第 4 個最險:前三個修完之後一切看起來都對,閘門仍然完全不會作用。
unverified
missing
codex-hooks.json
SessionStart
UserPromptSubmit
Stop
照文件寫不夠,要去問 CLI 自己讀到什麼。這次用到的唯讀管道:agy -p "/hooks"(不吃 quota)、kiro-cli agent validate、codex 的 [hooks.state] 登記內容。copilot 沒有這種管道,所以它的形狀維持未證。
agy -p "/hooks"
kiro-cli agent validate
[hooks.state]
check-behaviors.sh
lint-frontmatter.sh
ste100-lint.sh
lint-scripts.sh
ask
wire-cli.sh smoke codex
lines
JSC_CLI
agentSpawn
userPromptSubmit
kiro 的 verdict 是 degraded,理由是 CLI 限制不是接線問題:技能走 ResolveSkill 這個 agent 內部請求,preToolUse 攔不到技能叫用,userPromptSubmit 的非零結束碼也擋不下那一輪。
degraded
ResolveSkill
preToolUse
write-guard.sh
resources
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 接線修正。
What: - tools/wire-cli.sh 重寫 codex、copilot、antigravity、kiro 四支的接線、purge、status 與 smoke。 - 新增 hooks/codex-hooks.json,由 hooks/hooks.json 推導產生。 - .codex-plugin/plugin.json 加上 hooks 路徑鍵,指向那份檔案。 Why:這四支其實都有能介入的 pre-tool 事件,原本卻接錯位置,抓到五個同一類的無聲失效——設定看起來正確、CLI 靜默不理、不報錯: - codex 的 matcher 用 Skill,但 Codex 沒有 Skill 這個工具,技能是模型自己用 Bash 讀 SKILL.md 載入的。 - codex 的 hooks 鍵寫成內嵌物件,實際規格是路徑字串,內嵌物件解析不了。 - antigravity 的 PreToolUse 寫成 Flat,實際要 matcher 加 hooks 包一層的 Grouped。Flat 的那一段整個被丟掉,hook 名稱照樣登記,檔案讀起來還是對的。 - copilot 的設定寫到 $COPILOT_HOME/hooks/,那是 hook 要跑的腳本目錄、不是設定目錄,設定從來不會被讀。 - 三支新接的命令沒帶 JSC_CLI={代號},閘門認不出自己跑在哪支 CLI 上,一次都擋不下來。 How: - codex:PreToolUse matcher 換成 Bash。codex-hooks.json 由 hooks/hooks.json 整份複製,再把 matcher 的 Skill 改寫成 Bash,其餘事件原樣保留,Claude 那份維持唯一真實來源,第二份絕不手寫。 - copilot:pre-tool hook 併進 ~/.copilot/settings.json 的頂層 hooks 鍵,matcher 用小寫 skill,事件名只寫一種大小寫。那份檔案同時裝著 enabledPlugins 與 extraKnownMarketplaces,所以合併不覆寫:寫前備份、只動 jsc 自己那幾筆、寫後回讀核對最上層鍵與別人的條目,對不上就還原。指引檔改寫到 $COPILOT_HOME 底下。 - antigravity:寫 ~/.gemini/config/hooks.json 的 jsc 段落。PreToolUse 改成 Grouped、matcher 是錨定的 view_file,錨點不能省,省了會連 view_file_outline 一起命中;另接 Flat 的 PreInvocation,攔斜線指令那條不產生工具呼叫的路。 - kiro:hook 宣告搬到 ~/.kiro/agents/jsc.json 的 hooks 鍵,事件只用 agentSpawn、userPromptSubmit、stop,欄位是 command 與 timeout_ms,並把 settings/cli.json 的 chat.defaultAgent 設成 jsc。kiro-cli agent validate 四種情況一律回 0,所以判準看輸出、不看結束碼。 - 四支非 claude 的接線命令一律以 JSC_CLI={代號} 前綴自帶代號,接線時、status 與 smoke 各斷言一次。 - status 從只驗「鍵在不在」改成驗形狀與位置,並新增第三格 unverified,把「驗不了」跟「驗過了」分開,只有 missing 算缺項。 - smoke 新增 26 條接線形狀斷言,每條正向配一條反向,另把五支 CLI 的真實負載直接餵進 restart-gate.sh,驗解析、判定、輸出整條串得起來。前兩段分開看都會顯示正常,中間接不上照樣是全程放行,那正是先前失效的樣子。 Who:codex、copilot、antigravity、kiro 四支 CLI 的 pre-tool hook 接線修正。
What:README.md、references/behaviors.md 與 skills/hooks-install/SKILL.md 同步這一批的接線事實。 Why: - 文件寫著「這些 CLI 沒有 pre-tool hook」。那句話是錯的,也正是三支 CLI 長期沒有守門的原因。照著它報告,會讓使用者以為只有 claude 有保護、其餘四支本來就接不上。 - kiro 的 degraded 原本被寫成接線缺東西,實際上是那支 CLI 擋不下技能叫用。兩件事的處理方式完全不同。 How: - 一支 CLI 一列,列出接線位置、事件與 matcher、阻擋形態與判定,並寫明 claude、codex、copilot、antigravity 回 wired,kiro 回 degraded。 - 新增驗證等級表,把「形狀」與「觸發」分開記:形狀是那支 CLI 真的讀得懂設定,觸發是 hook 真的被叫用過。copilot 的形狀未證、antigravity 與 codex 的觸發未驗證,一律照實列出,不含糊帶過。 - README 補上 hooks/skill-name.sh 與 hooks/deny.sh 兩列,並補 COPILOT_HOME、KIRO_HOME、JSC_ANTIGRAVITY_HOOKS、JSC_COPILOT_INSTRUCTIONS、JSC_ANTIGRAVITY_RULES 幾個環境變數。 - kiro 的接線位置從 .kiro/hooks/jsc-hooks.json 全面改寫成 ~/.kiro/agents/jsc.json。 - 講明 write-guard.sh 三個模式與 SDLC 模型鎖在另外三支上是「還沒接」,不是「沒有 hook 可接」。 - behaviors.md 的完成條件與可驗證跡象跟著改,repair 那一列加上「技能名解析改 skill-name.sh、阻擋形態改 deny.sh,兩支是唯一真實來源」。 Who:codex、copilot、antigravity、kiro 四支 CLI 的 pre-tool hook 接線修正。
What:plugin.json 與 .claude-plugin/plugin.json 的 version 由 0.3.3 改為 0.3.4。 Why: - 這一批補上四支 CLI 的 pre-tool hook 接線,屬於新增能力,要發一個新版本。 - 三份 manifest 的版本號必須一致。版本前置檢查比對的就是這個號,對不上會讓 hook 判出錯誤的落後結論。 How: - 由主流程的 sync-skill-manifest.sh 同步,三份一起改,不手動各寫一次。 - .codex-plugin/plugin.json 那一份的版本號隨接線提交一起進來,因為同一個檔案還加了 hooks 路徑鍵。 Who:jsc-hooks 0.3.4 發佈收尾。
No dependencies set.
The note is not visible to the blocked user.
摘要
變更內容
hooks/skill-name.shhooks/deny.shhooks/codex-hooks.jsonhooks/hooks.json推導產生,讓 Claude 那份維持唯一真實來源hooks/version-guard.sh、hooks/restart-gate.shtools/wire-cli.sh.codex-plugin/plugin.jsonhooks路徑鍵references/behaviors.md、skills/hooks-install/SKILL.md這一批抓到的六個缺陷
六個全部不會報錯——設定寫得看起來正確,CLI 靜默不理,日誌、結束碼與
status都沒有痕跡。Skill,但 Codex 沒有Skill工具hooks鍵寫成內嵌物件,實際是路徑字串PreToolUse寫成 Flat,實際要 Groupedactions鍵都不生成JSC_CLI={代號}status全綠,閘門一次都不擋第 4 個最險:前三個修完之後一切看起來都對,閘門仍然完全不會作用。
設計重點
status從只驗「鍵在不在」改成驗形狀與位置,並新增unverified第三格,把「驗不了」跟「驗過了」分開,只有missing算缺項。前四個缺陷全是舊status漏掉的。codex-hooks.json用推導而非手寫:手寫第二份會漏掉SessionStart、UserPromptSubmit、Stop等其餘事件。方法論
照文件寫不夠,要去問 CLI 自己讀到什麼。這次用到的唯讀管道:
agy -p "/hooks"(不吃 quota)、kiro-cli agent validate、codex 的[hooks.state]登記內容。copilot 沒有這種管道,所以它的形狀維持未證。測試結果
check-behaviors.sh、lint-frontmatter.sh、ste100-lint.sh對 10 個 domain 全部退出 0。lint-scripts.sh9 個退出 0,ask退出 3(沒有腳本可掃)。wire-cli.sh smoke codex退出 0,lines由腳本自印,接線形狀 26 條斷言、每條正向配一條反向。JSC_CLI、用改前版本重組假樹,每一種都抓得到並回非零。驗證等級
agentSpawn、userPromptSubmit實跑觸發kiro 的 verdict 是
degraded,理由是 CLI 限制不是接線問題:技能走ResolveSkill這個 agent 內部請求,preToolUse攔不到技能叫用,userPromptSubmit的非零結束碼也擋不下那一輪。尚未做的事
write-guard.sh三種模式與 SDLC 模型鎖在四支非 claude CLI 上尚未接線。那是還沒做,不是驗不過。resources兩層 glob 能不能修好技能可見性未驗證。kiro 目前預設資源 glob 只掃一層,34 支技能一支都載不到。前置 Push Request
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 接線修正。What: - tools/wire-cli.sh 重寫 codex、copilot、antigravity、kiro 四支的接線、purge、status 與 smoke。 - 新增 hooks/codex-hooks.json,由 hooks/hooks.json 推導產生。 - .codex-plugin/plugin.json 加上 hooks 路徑鍵,指向那份檔案。 Why:這四支其實都有能介入的 pre-tool 事件,原本卻接錯位置,抓到五個同一類的無聲失效——設定看起來正確、CLI 靜默不理、不報錯: - codex 的 matcher 用 Skill,但 Codex 沒有 Skill 這個工具,技能是模型自己用 Bash 讀 SKILL.md 載入的。 - codex 的 hooks 鍵寫成內嵌物件,實際規格是路徑字串,內嵌物件解析不了。 - antigravity 的 PreToolUse 寫成 Flat,實際要 matcher 加 hooks 包一層的 Grouped。Flat 的那一段整個被丟掉,hook 名稱照樣登記,檔案讀起來還是對的。 - copilot 的設定寫到 $COPILOT_HOME/hooks/,那是 hook 要跑的腳本目錄、不是設定目錄,設定從來不會被讀。 - 三支新接的命令沒帶 JSC_CLI={代號},閘門認不出自己跑在哪支 CLI 上,一次都擋不下來。 How: - codex:PreToolUse matcher 換成 Bash。codex-hooks.json 由 hooks/hooks.json 整份複製,再把 matcher 的 Skill 改寫成 Bash,其餘事件原樣保留,Claude 那份維持唯一真實來源,第二份絕不手寫。 - copilot:pre-tool hook 併進 ~/.copilot/settings.json 的頂層 hooks 鍵,matcher 用小寫 skill,事件名只寫一種大小寫。那份檔案同時裝著 enabledPlugins 與 extraKnownMarketplaces,所以合併不覆寫:寫前備份、只動 jsc 自己那幾筆、寫後回讀核對最上層鍵與別人的條目,對不上就還原。指引檔改寫到 $COPILOT_HOME 底下。 - antigravity:寫 ~/.gemini/config/hooks.json 的 jsc 段落。PreToolUse 改成 Grouped、matcher 是錨定的 view_file,錨點不能省,省了會連 view_file_outline 一起命中;另接 Flat 的 PreInvocation,攔斜線指令那條不產生工具呼叫的路。 - kiro:hook 宣告搬到 ~/.kiro/agents/jsc.json 的 hooks 鍵,事件只用 agentSpawn、userPromptSubmit、stop,欄位是 command 與 timeout_ms,並把 settings/cli.json 的 chat.defaultAgent 設成 jsc。kiro-cli agent validate 四種情況一律回 0,所以判準看輸出、不看結束碼。 - 四支非 claude 的接線命令一律以 JSC_CLI={代號} 前綴自帶代號,接線時、status 與 smoke 各斷言一次。 - status 從只驗「鍵在不在」改成驗形狀與位置,並新增第三格 unverified,把「驗不了」跟「驗過了」分開,只有 missing 算缺項。 - smoke 新增 26 條接線形狀斷言,每條正向配一條反向,另把五支 CLI 的真實負載直接餵進 restart-gate.sh,驗解析、判定、輸出整條串得起來。前兩段分開看都會顯示正常,中間接不上照樣是全程放行,那正是先前失效的樣子。 Who:codex、copilot、antigravity、kiro 四支 CLI 的 pre-tool hook 接線修正。