#!/bin/sh # not-my-branch-guard.sh — PreToolUse(Bash):**不准 commit 到別人正在施工的分支。** # # 🔴 為什麼是硬擋(2026-08-16,總管一小時內犯兩次): # 第一次:把 branch-hold 的落帳 commit 打在 `feat/gitea-arm-per-issue` 上(agent 的分支) # 第二次:把 `ticket decide` 的 commit 打在**同一條**分支上 # ——而且第一次之後,總管在回覆裡寫了「這正是共用工作區那條教訓咬到我自己」。 # **寫下教訓沒有讓他不再犯。** 一小時後同一個錯又來一次。 # # 為什麼會犯:總管的 shell 與 agent 共用同一個工作區,**分支是誰上一次切的就是誰的**。 # `git commit` 不會問「你確定要打在這條上嗎」,它只是照做。 # 而 `git push gitea main` 在那種狀態下會回 **`Everything up-to-date`** # ——**假成功**,因為本機 `main` 確實沒動過。真訊號是「本機 HEAD 與遠端對不上」。 # # 判準:**當前分支出現在 `.claude/branch-holds.md` 裡** ⇒ 那是有人(agent)正在上面施工的 # 分支,或刻意保留的分支。兩種都不該由總管順手 commit 進去。 # # 刻意不擋的: # · 在 `main` 上 commit(那是總管的地盤,另有 main-and-prod-push-guard 管推送) # · 不在 branch-holds 名單上的分支(總管自己開的工作分支) # · 非 commit 的 git 動作(status/log/diff/checkout…) # # 真的要在那條分支上 commit(例如接手一條被放棄的分支): # 把它從 `branch-holds.md` 移除(那本來就是「它不再是別人的」的正確表示法), # 或 `NOT_MY_BRANCH_OK=1 git commit ...`(留痕,commit 說明寫理由)。 set -eu [ "${NOT_MY_BRANCH_OK:-}" = "1" ] && exit 0 PAYLOAD=$(cat 2>/dev/null || echo '{}') CMD=$(printf '%s' "$PAYLOAD" | python3 -c ' import sys, json try: d = json.load(sys.stdin) except Exception: sys.exit(0) print((d.get("tool_input") or {}).get("command") or "") ' 2>/dev/null || true) # 只看 commit(含 -m/-F/--amend) case "$CMD" in *"git commit"*) ;; *) exit 0 ;; esac PROJ="${CLAUDE_PROJECT_DIR:-$(pwd)}" HOLDS="$PROJ/.claude/branch-holds.md" [ -f "$HOLDS" ] || exit 0 BR=$(git -C "$PROJ" rev-parse --abbrev-ref HEAD 2>/dev/null || echo "") [ -n "$BR" ] || exit 0 case "$BR" in main|master|HEAD) exit 0 ;; esac # 分支名有沒有出現在 branch-holds 的條目裡(格式:- `分支名`(repo):理由) grep -q "^- \`${BR}\`" "$HOLDS" 2>/dev/null || exit 0 REASON=$(grep -A1 "^- \`${BR}\`" "$HOLDS" | head -2 | tr '\n' ' ' | cut -c1-160) cat >&2 < ② **已經打錯了要救**: git checkout main && git cherry-pick <那顆 sha> git branch -f $BR gitea/$BR # 把別人的分支還原成遠端狀態 ③ **你真的要接手這條分支**:先把它從 \`.claude/branch-holds.md\` 移除 (那本來就是「它不再是別人的」的正確表示法),再 commit ④ 真有例外:\`NOT_MY_BRANCH_OK=1 git commit ...\`,並在 commit 說明寫理由 EOF exit 2