#!/bin/bash # install-artifact-guard.sh — 實例不准改機制(分離規格 防糾纏閘 S1) # # 掛 PreToolUse(matcher: Write|Edit|MultiEdit)。**排在整條鏈的最前面**—— # 範圍最大的規則先擋,錯誤訊息才會指向最根本的那條。 # # ── 擋什麼 ────────────────────────────────────── # 寫入「安裝產物區」= 框架發下來的機制檔(hook、範本、安裝腳本、plugin 目錄)。 # 判準來自 system-dev/.template-manifest:**凡是框架管的檔(class=overwrite)都算**。 # 不另寫一份路徑表——兩處維護同一條規則,遲早不同步。 # # ── 為什麼要擋(實測數據,不是潔癖)───────────────── # 拿一個真實使用中的實例對照框架:6 支框架 hook 裡 **4 支已被手改**, # 另有 11 支是實例自己發明的,而**沒有任何機制知道這件事**。 # 兩層後果: # ① 框架之後修好的 bug,永遠送不到那些被手改的檔 # ② 手改的內容沒人審、也不會回饋給框架 ⇒ 同一個坑每個實例各踩一次 # 機制要改就回上游改,一次修全家;在自己家裡改,只有自己受惠、且下次更新就孤立。 # # ── 怎麼放行 ──────────────────────────────────── # 框架 repo 自己開發時本來就要改這些檔 ⇒ 兩種放行方式: # · repo 根目錄有 .sdt-framework-dev 檔(框架 repo 自帶並 commit,零記憶負擔) # · 環境變數 SDT_FRAMEWORK_DEV=1(臨時情境) # 注意:官方**沒有** --framework-dev 這個 CLI 參數(實查,非記憶),別去找。 # # 誠實限制:擋直接寫檔。bash 繞道改檔擋不到。留痕可審,不宣稱防偽。 set -uo pipefail HOOK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" . "$HOOK_DIR/lib/role-lib.sh" 2>/dev/null || exit 0 # ── 框架開發模式 → 放行 ──────────────────────────── ROOT="$(sdt_repo_root)" [ -f "$ROOT/.sdt-framework-dev" ] && exit 0 [ "${SDT_FRAMEWORK_DEV:-}" = "1" ] && exit 0 INPUT="$(cat)" FILE_PATH="$(sdt_file_path "$INPUT")" [ -z "$FILE_PATH" ] && exit 0 REL="$(sdt_rel_path "$FILE_PATH")" MANIFEST="$ROOT/system-dev/.template-manifest" is_managed_artifact() { # ⓪ 使用者的自訂插槽 → 一律放行,**這一條要排在最前面** # 它們存在的目的就是給實例填自己的規則,而且 update 不會覆蓋它們。 # (踩過:本 hook 的錯誤訊息叫人「去改 pre-write-guard.sh」, # 結果自己把那支也擋了——叫人走的路自己堵住,是最糟的一種閘。) case "$1" in .claude/hooks/pre-write-guard.sh) return 1 ;; *.template.sh|*.template.md) return 1 ;; .claude/settings.json) return 1 ;; esac # ① manifest 是權威:它說這是框架管的邏輯檔(class=overwrite)才擋 if [ -f "$MANIFEST" ]; then if awk -F'\t' -v d="$1" '!/^#/ && $1==d && $2=="overwrite" {found=1} END{exit !found}' "$MANIFEST"; then return 0 fi fi # ② manifest 還沒有/這個檔還沒登記(舊實例、或框架剛加的新檔) # → 退回路徑慣例,別因為沒表就整條規則失效 case "$1" in .claude/hooks/*|.claude/plugins/*) return 0 ;; system-dev/scripts/*) return 0 ;; esac return 1 } if is_managed_artifact "$REL"; then cat >&2 <