diff --git a/openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/.openspec.yaml b/openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/.openspec.yaml new file mode 100644 index 0000000..231e3ab --- /dev/null +++ b/openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-18 diff --git a/openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/notes.md b/openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/notes.md new file mode 100644 index 0000000..63c5df9 --- /dev/null +++ b/openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/notes.md @@ -0,0 +1,47 @@ +# agent-claude-add-global-hooks-installer-script-2026-05-18-12-49 (minimal / T1) + +Branch: `agent/claude/add-global-hooks-installer-script-2026-05-18-12-49` + +Add an opt-in installer that wires guardex's `.githooks/*` into the user's +**global** `core.hooksPath`, so the hooks fire in every existing and future +repo on the machine. Surfaced via `npm run guardex:install-global` — not +`postinstall`, because silently mutating `git config --global` on every +npm install would surprise downstream users. + +## Files + +- `scripts/install-global-hooks.sh` — new. Idempotent. Refuses to overwrite + an existing `core.hooksPath` set to a different directory. +- `package.json` — adds `"guardex:install-global"` script entry. + +## Behavior + +Running the installer: + +1. `mkdir -p ${XDG_CONFIG_HOME:-$HOME/.config}/git/hooks` +2. Symlinks `pre-commit`, `pre-push`, `post-checkout`, `post-merge` from the + gitguardex repo's `.githooks/` into that dir. +3. Sets `git config --global core.hooksPath` to that dir (only if currently + unset OR already pointing there). + +Reverse: + +```bash +git config --global --unset core.hooksPath +``` + +Per-repo opt-out: + +```bash +git config core.hooksPath .git/hooks +``` + +## Handoff + +- Handoff: change=`agent-claude-add-global-hooks-installer-script-2026-05-18-12-49`; branch=`agent/claude/add-global-hooks-installer-script-2026-05-18-12-49`; scope=`scripts/install-global-hooks.sh + package.json`; action=`finish via PR`. + +## Cleanup + +- [ ] Run: `gx branch finish --branch agent/claude/add-global-hooks-installer-script-2026-05-18-12-49 --base main --via-pr --wait-for-merge --cleanup` +- [ ] Record PR URL + `MERGED` state in the completion handoff. +- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`). diff --git a/package.json b/package.json index a18ca11..bf20685 100644 --- a/package.json +++ b/package.json @@ -18,6 +18,7 @@ "agent:branch:merge": "bash ./scripts/agent-branch-merge.sh", "agent:cleanup": "gx cleanup", "agent:hooks:install": "bash ./scripts/install-agent-git-hooks.sh", + "guardex:install-global": "bash ./scripts/install-global-hooks.sh", "agent:locks:claim": "python3 ./scripts/agent-file-locks.py claim", "agent:locks:allow-delete": "python3 ./scripts/agent-file-locks.py allow-delete", "agent:locks:release": "python3 ./scripts/agent-file-locks.py release", diff --git a/scripts/install-global-hooks.sh b/scripts/install-global-hooks.sh new file mode 100755 index 0000000..30a53ae --- /dev/null +++ b/scripts/install-global-hooks.sh @@ -0,0 +1,54 @@ +#!/usr/bin/env bash +# Wire guardex's hooks into the user's GLOBAL git config so they fire in +# EVERY existing and future repo on this machine. Idempotent; safe to re-run. +# +# What this does: +# 1. Ensures ~/.config/git/hooks/ exists. +# 2. Symlinks the four guardex hooks (pre-commit, pre-push, post-checkout, +# post-merge) from this repo's .githooks/ into the global hooks dir. +# 3. Points `git config --global core.hooksPath` at it. +# +# Safety: +# - If core.hooksPath is already set globally to a DIFFERENT path, this +# script prints the existing value and exits 0 without overwriting. +# - Repo-local `core.hooksPath` settings override the global one, so a +# single repo can opt out with `git config core.hooksPath .git/hooks`. +# - Reverse with `git config --global --unset core.hooksPath`. +# +# Intended invocation: +# bash ~/Documents/gitguardex/scripts/install-global-hooks.sh +# npm run guardex:install-global + +set -euo pipefail + +REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)" +HOOKS_SRC="$REPO_ROOT/.githooks" +HOOKS_DST="${XDG_CONFIG_HOME:-$HOME/.config}/git/hooks" + +if [[ ! -d "$HOOKS_SRC" ]]; then + echo "[guardex] missing $HOOKS_SRC — run from the gitguardex repo." >&2 + exit 1 +fi + +mkdir -p "$HOOKS_DST" + +linked=0 +for h in pre-commit pre-push post-checkout post-merge; do + if [[ -f "$HOOKS_SRC/$h" ]]; then + ln -sfn "$HOOKS_SRC/$h" "$HOOKS_DST/$h" + linked=$((linked + 1)) + fi +done +echo "[guardex] symlinked $linked hooks → $HOOKS_DST" + +current="$(git config --global --get core.hooksPath 2>/dev/null || true)" +if [[ -n "$current" && "$current" != "$HOOKS_DST" ]]; then + echo "[guardex] core.hooksPath already set to: $current" + echo "[guardex] not overwriting. To switch: git config --global core.hooksPath '$HOOKS_DST'" + exit 0 +fi + +git config --global core.hooksPath "$HOOKS_DST" +echo "[guardex] global core.hooksPath → $HOOKS_DST" +echo "[guardex] disable globally: git config --global --unset core.hooksPath" +echo "[guardex] opt-out one repo: git config core.hooksPath .git/hooks"