45 lines
2.1 KiB
Bash
Executable File
45 lines
2.1 KiB
Bash
Executable File
#!/usr/bin/env bash
|
|
# PreToolUse hook: block file changes on main/master and on detached HEAD.
|
|
# Covers Edit/Write/MultiEdit directly and Bash commands that look like writes.
|
|
# Single exception: commits/pushes to main that touch only openspec/ (archive step).
|
|
set -euo pipefail
|
|
input=$(cat)
|
|
eval "$(printf '%s' "$input" | python3 -c '
|
|
import sys, json, shlex
|
|
d = json.load(sys.stdin); ti = d.get("tool_input", {}) or {}
|
|
print("cwd=" + shlex.quote(d.get("cwd", "")))
|
|
print("tool=" + shlex.quote(d.get("tool_name", "")))
|
|
print("cmd=" + shlex.quote(ti.get("command", "") or ""))' 2>/dev/null || echo 'cwd=; tool=; cmd=')"
|
|
[ -n "$cwd" ] || exit 0
|
|
git -C "$cwd" rev-parse --is-inside-work-tree >/dev/null 2>&1 || exit 0
|
|
branch=$(git -C "$cwd" branch --show-current 2>/dev/null || true)
|
|
case "$branch" in main|master|"") ;; *) exit 0 ;; esac
|
|
label=${branch:-"detached HEAD"}
|
|
|
|
only_openspec() { # $1 = git diff range/args; true if every changed path is under openspec/
|
|
local files
|
|
files=$(git -C "$cwd" diff --name-only $1 2>/dev/null || true)
|
|
[ -n "$files" ] && ! printf '%s\n' "$files" | grep -vq '^openspec/'
|
|
}
|
|
|
|
if [ "$tool" = "Bash" ]; then
|
|
case "$cmd" in
|
|
*"git worktree"*|*"git checkout -b"*|*"git switch -c"*|*"openspec archive"*) exit 0 ;;
|
|
esac
|
|
# archive exception: commit of staged openspec-only changes, push of openspec-only commits
|
|
if printf '%s' "$cmd" | grep -Eq '\bgit\s+commit\b'; then
|
|
only_openspec "--cached" && exit 0
|
|
fi
|
|
if printf '%s' "$cmd" | grep -Eq '\bgit\s+push\b'; then
|
|
only_openspec "origin/main...HEAD" && exit 0
|
|
fi
|
|
if printf '%s' "$cmd" | grep -Eq '(^|[^<])>{1,2}[^&]|\btee\b|\bsed\s+-i|\bmv\b|\bcp\b|\brm\b|\btouch\b|\bmkdir\b|\bgit\s+(add|commit|push|apply|stash\s+pop)\b|\bnpm\s+(i|install|init)\b|\bpip\s+install\b'; then
|
|
echo "BLOCKED: shell write on $label ($(printf '%s' "$cmd" | head -c 80)). Create an isolated worktree first (superpowers:using-git-worktrees). Only 'openspec archive' commits may land on main directly." >&2
|
|
exit 2
|
|
fi
|
|
exit 0
|
|
fi
|
|
|
|
echo "BLOCKED: you are on $label. Create an isolated worktree first (superpowers:using-git-worktrees), never edit main directly." >&2
|
|
exit 2
|