Skip to content

Commit d60fafa

Browse files
authored
feat(scripts): install-global-hooks.sh — opt-in global core.hooksPath (#603)
Adds scripts/install-global-hooks.sh and a `guardex:install-global` npm run script. Wires the four guardex hooks (pre-commit, pre-push, post-checkout, post-merge) into ${XDG_CONFIG_HOME:-$HOME/.config}/git/hooks/ as symlinks, then sets git config --global core.hooksPath to that dir. Opt-in by design — not postinstall — so downstream consumers on shared machines and CI runners are not surprised by silent global git config mutation. Idempotent and safe to re-run. Refuses to overwrite an existing core.hooksPath that points elsewhere. T1 lane; minimal openspec scaffold updated in openspec/changes/agent-claude-add-global-hooks-installer-script-2026-05-18-12-49/notes.md
1 parent 14d398c commit d60fafa

4 files changed

Lines changed: 104 additions & 0 deletions

File tree

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
schema: spec-driven
2+
created: 2026-05-18
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
# agent-claude-add-global-hooks-installer-script-2026-05-18-12-49 (minimal / T1)
2+
3+
Branch: `agent/claude/add-global-hooks-installer-script-2026-05-18-12-49`
4+
5+
Add an opt-in installer that wires guardex's `.githooks/*` into the user's
6+
**global** `core.hooksPath`, so the hooks fire in every existing and future
7+
repo on the machine. Surfaced via `npm run guardex:install-global` — not
8+
`postinstall`, because silently mutating `git config --global` on every
9+
npm install would surprise downstream users.
10+
11+
## Files
12+
13+
- `scripts/install-global-hooks.sh` — new. Idempotent. Refuses to overwrite
14+
an existing `core.hooksPath` set to a different directory.
15+
- `package.json` — adds `"guardex:install-global"` script entry.
16+
17+
## Behavior
18+
19+
Running the installer:
20+
21+
1. `mkdir -p ${XDG_CONFIG_HOME:-$HOME/.config}/git/hooks`
22+
2. Symlinks `pre-commit`, `pre-push`, `post-checkout`, `post-merge` from the
23+
gitguardex repo's `.githooks/` into that dir.
24+
3. Sets `git config --global core.hooksPath` to that dir (only if currently
25+
unset OR already pointing there).
26+
27+
Reverse:
28+
29+
```bash
30+
git config --global --unset core.hooksPath
31+
```
32+
33+
Per-repo opt-out:
34+
35+
```bash
36+
git config core.hooksPath .git/hooks
37+
```
38+
39+
## Handoff
40+
41+
- 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`.
42+
43+
## Cleanup
44+
45+
- [ ] 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`
46+
- [ ] Record PR URL + `MERGED` state in the completion handoff.
47+
- [ ] Confirm sandbox worktree is gone (`git worktree list`, `git branch -a`).

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
"agent:branch:merge": "bash ./scripts/agent-branch-merge.sh",
1919
"agent:cleanup": "gx cleanup",
2020
"agent:hooks:install": "bash ./scripts/install-agent-git-hooks.sh",
21+
"guardex:install-global": "bash ./scripts/install-global-hooks.sh",
2122
"agent:locks:claim": "python3 ./scripts/agent-file-locks.py claim",
2223
"agent:locks:allow-delete": "python3 ./scripts/agent-file-locks.py allow-delete",
2324
"agent:locks:release": "python3 ./scripts/agent-file-locks.py release",

scripts/install-global-hooks.sh

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
#!/usr/bin/env bash
2+
# Wire guardex's hooks into the user's GLOBAL git config so they fire in
3+
# EVERY existing and future repo on this machine. Idempotent; safe to re-run.
4+
#
5+
# What this does:
6+
# 1. Ensures ~/.config/git/hooks/ exists.
7+
# 2. Symlinks the four guardex hooks (pre-commit, pre-push, post-checkout,
8+
# post-merge) from this repo's .githooks/ into the global hooks dir.
9+
# 3. Points `git config --global core.hooksPath` at it.
10+
#
11+
# Safety:
12+
# - If core.hooksPath is already set globally to a DIFFERENT path, this
13+
# script prints the existing value and exits 0 without overwriting.
14+
# - Repo-local `core.hooksPath` settings override the global one, so a
15+
# single repo can opt out with `git config core.hooksPath .git/hooks`.
16+
# - Reverse with `git config --global --unset core.hooksPath`.
17+
#
18+
# Intended invocation:
19+
# bash ~/Documents/gitguardex/scripts/install-global-hooks.sh
20+
# npm run guardex:install-global
21+
22+
set -euo pipefail
23+
24+
REPO_ROOT="$(cd "$(dirname "$0")/.." && pwd)"
25+
HOOKS_SRC="$REPO_ROOT/.githooks"
26+
HOOKS_DST="${XDG_CONFIG_HOME:-$HOME/.config}/git/hooks"
27+
28+
if [[ ! -d "$HOOKS_SRC" ]]; then
29+
echo "[guardex] missing $HOOKS_SRC — run from the gitguardex repo." >&2
30+
exit 1
31+
fi
32+
33+
mkdir -p "$HOOKS_DST"
34+
35+
linked=0
36+
for h in pre-commit pre-push post-checkout post-merge; do
37+
if [[ -f "$HOOKS_SRC/$h" ]]; then
38+
ln -sfn "$HOOKS_SRC/$h" "$HOOKS_DST/$h"
39+
linked=$((linked + 1))
40+
fi
41+
done
42+
echo "[guardex] symlinked $linked hooks → $HOOKS_DST"
43+
44+
current="$(git config --global --get core.hooksPath 2>/dev/null || true)"
45+
if [[ -n "$current" && "$current" != "$HOOKS_DST" ]]; then
46+
echo "[guardex] core.hooksPath already set to: $current"
47+
echo "[guardex] not overwriting. To switch: git config --global core.hooksPath '$HOOKS_DST'"
48+
exit 0
49+
fi
50+
51+
git config --global core.hooksPath "$HOOKS_DST"
52+
echo "[guardex] global core.hooksPath → $HOOKS_DST"
53+
echo "[guardex] disable globally: git config --global --unset core.hooksPath"
54+
echo "[guardex] opt-out one repo: git config core.hooksPath .git/hooks"

0 commit comments

Comments
 (0)