hook-standardslisted
Install: claude install-skill jurislm/jurislm-tools
## 分層原則
Hook 是強制層,`CLAUDE.md` 的常駐文字是判斷層。**兩者並存,不互相取代。**
Hook 只看得到 Bash 指令的字面。Claude 走 MCP 工具、走 `Edit`、或在還沒下指令的判斷
階段就決定要做某件事時,hook 完全看不到。把一條安全禁令只寫進 hook,等於只擋住了
Bash 那一條路徑。
因此:**安全禁令的文字不因為有了 hook 就從 `CLAUDE.md` 移出。** 新增 hook 是加上一層
強制,不是把規則搬家。
## 入選判準
一句話:**漏做一次就會出事嗎?**
| 情況 | 該進 hook 嗎 |
|---|---|
| 零例外必須被擋,且代價不可逆 | 是 |
| 壞掉時無聲——不報錯、不警告 | 是。沒有 hook 就沒有人會發現 |
| 需要看上下文才知道對不對 | 否,留在 `CLAUDE.md` |
| 只是「最好這樣做」 | 否 |
第二列是 `worktree-hookspath-fix.sh` 的存在理由:git 找不到 `core.hooksPath` 指向的目錄
就直接不跑 hook,不報錯也不警告。除非有人主動去 `git config --get core.hooksPath`,
否則沒人會發現防護已經沒了。
## deny 與 ask
| 決定 | 什麼時候用 | 例子 |
|---|---|---|
| `deny` | 零例外,且有明確的替代路徑可以講給呼叫者聽 | 直推預設分支 → 走 feature 分支開 PR |
| `ask` | 動作本身合法,對不對取決於只有呼叫者知道的上下文 | `rm -rf` 的目標路徑是不是真的那一個 |
`deny` 的理由**必須包含替代路徑**。只說「不准」會讓呼叫者原地重試或繞路,兩種都比
放行更糟。
## 匹配紀律
三條都有實戰來源,不是預防性的潔癖。
1. **錨定真實的指令位置,不比對裸字串。**
`git-guard.sh` 早期用裸子字串比對跳過 hook 的旗標,結果 `echo "... <旗標> ..."` 被
擋,連 hook 檔本身都無法透過 Bash 編輯。現在錨定在行首,或 `;` `&` `|` `(` `&&`
`||` 之後。殘留誤判:引號內看起來像 git 指令的字串仍可能命中——要根治需要真正的
shell parser,不值得那個複雜度,所以寫進 catalog 的「不攔什麼」欄位而不是假裝沒有。
2. **列舉選項,不用開放式 pattern。**
`git[^;&|]*push` 會命中 `git log --grep push`,而它不 push 任何東西。`git-guard.sh`
改為列舉 git 的 global option(`-C`、`-c`、`--git-dir=`、`--work-tree=`、
`--no-pager`、`-P`)再接 `push`。
3. **解析失敗一律退回最保守的答案,絕不因此放行。**
`git-guard.sh` 判斷「這個指令推去哪個 repo」的順序是 `git -C <path>` → push 之前
最後一個 `cd` → session cwd。路徑含變數、subshell、glob,或根本不是工作樹時,退回
session cwd 再判一次,不會因為解析不出來就放行。
這條的來源是一次誤擋:早期版本問的是「ses