# hooks/lib/path-resolve.sh — 共用:判斷一個檔案路徑「歸不歸某個 git repo 管」。 # 不是獨立掛的閘(沒進 hooks.json),給其他 PreToolUse 閘 `source` 用的函式庫。 # # 背景(inkstone/InkStoneCo#22):sdd-guard.sh 曾經把 scratchpad 暫存檔 # (`/private/tmp/.../scratchpad/foo.py`)誤判成「repo 裡的 code 變動」而擋下—— # 因為它只會「猜專案根($CLAUDE_PROJECT_DIR 或 cwd)+往上找 3-specs」, # 猜錯或猜不到時,找不到 3-specs 就一律當「找不到 SDD」擋下,連「這條路徑根本不在 # 任何 repo 裡、SDD 這件事天生管不到它」都沒判斷過。 # # path_in_git_worktree 提供一個不必先猜對專案根的判法:直接問 git # 「這個路徑在不在某個 repo 的工作樹裡」。不必窮舉暫存區的路徑關鍵字(/tmp、scratchpad…), # 任何真的不在 git repo 裡的路徑,一律視同「這是暫存/非受管檔案」。 # # 同一個 `${CLAUDE_PROJECT_DIR:-$(pwd)}` 猜根目錄寫法,實測(2026-08-20)還出現在: # component-guard.sh、factory-idle-guard.sh、github-contact-guard.sh、 # history-first-guard.sh、main-and-prod-push-guard.sh、no-ticket-no-dispatch.sh、 # not-my-branch-guard.sh、release-tag-guard.sh、skill-deploy-drift-guard.sh、 # stage-before-prod-guard.sh、unpushed-police.sh、wiki-first-police.sh。 # 另有 empty-handed-stop-guard.sh、issue-status-autoflip.sh 直接寫 `$CLAUDE_PROJECT_DIR` # (無 `:-` fallback)—— # (claim-verify-police.sh/subagent-claim-worksheet.sh 已於 ISEP#60 整組移除, # 它們守的東西改由 Gitea 原生三格承接,見 ISEP#59/PR #58) # 這批在該變數未設時行為又不一樣,同一個病的另一種長相。 # 這些全部沒有本檔「先確認到底在不在 repo 裡」的判斷;本檔先在 sdd-guard.sh 落地, # 其餘要不要跟進、要不要改用這支共用函式,另案處理,不在本票(#22)範圍內一次改完。 # # 用法: # source "$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/lib/path-resolve.sh" # if ! path_in_git_worktree "$FILE_PATH"; then # # 不在任何 git repo 裡 ⇒ 這支閘通常管不到,多半該放行 # fi # path_in_git_worktree # 回傳 0=這個路徑落在某個 git 工作樹裡;1=不在任何 git repo 裡(含路徑本身不存在的情況)。 # 做法:從路徑的目錄部分開始,往上找到「第一個真的存在的祖先目錄」, # 對那個目錄問 `git rev-parse --is-inside-work-tree`。 # 為什麼要往上找存在的祖先,不能直接對 dirname 問: # 要在 repo 裡建一個還沒建立的子目錄下的新檔案時,dirname 也不存在, # 若不往上找,`git -C <不存在的目錄>` 會直接失敗 ⇒ 誤判成「不在 repo 裡」 # ⇒ 放行了本來該擋的東西(fail-open 的洞,不是這支函式該製造的)。 path_in_git_worktree() { local p="$1" d d=$(dirname -- "$p") while [ ! -d "$d" ] && [ "$d" != "/" ]; do d=$(dirname -- "$d") done [ -d "$d" ] || return 1 git -C "$d" rev-parse --is-inside-work-tree >/dev/null 2>&1 }