Spaces:
Runtime error
Runtime error
Commit Β·
95d4e02
1
Parent(s): 5c44de2
Hooks: add pre-commit docs guard for git and Claude Code
Browse files- scripts/hook-pre-commit-docs.sh: blocks git commit when .py/.js/.html/.css files are staged but README.md or CLAUDE.md are not
- scripts/install-hooks.sh: one-shot setup that installs the git hook and registers the Claude Code PreToolUse hook into .claude/settings.json
- CLAUDE.md: document the hooks and the install command
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- CLAUDE.md +11 -0
- scripts/hook-pre-commit-docs.sh +27 -0
- scripts/install-hooks.sh +77 -0
CLAUDE.md
CHANGED
|
@@ -178,6 +178,17 @@ is `deploy/hf-spaces/TASKS.md`. Non-obvious bits:
|
|
| 178 |
asked. Only commit/push when explicitly asked.
|
| 179 |
- End commit messages with the `Co-Authored-By: Claude` trailer.
|
| 180 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 181 |
## Quick commands
|
| 182 |
|
| 183 |
```bash
|
|
|
|
| 178 |
asked. Only commit/push when explicitly asked.
|
| 179 |
- End commit messages with the `Co-Authored-By: Claude` trailer.
|
| 180 |
|
| 181 |
+
## Hooks (enforced automatically)
|
| 182 |
+
|
| 183 |
+
Two hooks block commits that stage source files without updating both docs:
|
| 184 |
+
|
| 185 |
+
| Hook | When it fires | Source |
|
| 186 |
+
|---|---|---|
|
| 187 |
+
| `scripts/hook-pre-commit-docs.sh` | `git commit` (terminal) | installed to `.git/hooks/pre-commit` |
|
| 188 |
+
| `.claude/hooks/pre-commit-docs-check.sh` | Claude Code `Bash` tool with `git commit` | registered in `.claude/settings.json` `PreToolUse` |
|
| 189 |
+
|
| 190 |
+
Run `sh scripts/install-hooks.sh` after cloning to install both. `.git/hooks/` and `.claude/` are not tracked by git β the canonical hook source lives in `scripts/`.
|
| 191 |
+
|
| 192 |
## Quick commands
|
| 193 |
|
| 194 |
```bash
|
scripts/hook-pre-commit-docs.sh
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
# Git pre-commit hook: block commits that stage source files without updating README.md + CLAUDE.md.
|
| 3 |
+
# Installed to .git/hooks/pre-commit by scripts/install-hooks.sh.
|
| 4 |
+
|
| 5 |
+
STAGED=$(git diff --cached --name-only)
|
| 6 |
+
|
| 7 |
+
# Only enforce when at least one source file is changing
|
| 8 |
+
SOURCE_CHANGED=$(echo "$STAGED" | grep -E '\.(py|js|html|css)$' | grep -v '^tests/' | head -1)
|
| 9 |
+
[ -z "$SOURCE_CHANGED" ] && exit 0
|
| 10 |
+
|
| 11 |
+
README_STAGED=$(echo "$STAGED" | grep '^README\.md$')
|
| 12 |
+
CLAUDE_STAGED=$(echo "$STAGED" | grep '^CLAUDE\.md$')
|
| 13 |
+
|
| 14 |
+
if [ -z "$README_STAGED" ] || [ -z "$CLAUDE_STAGED" ]; then
|
| 15 |
+
echo ""
|
| 16 |
+
echo " Commit blocked: source files staged but docs not updated"
|
| 17 |
+
echo ""
|
| 18 |
+
[ -z "$README_STAGED" ] && echo " β’ README.md not staged"
|
| 19 |
+
[ -z "$CLAUDE_STAGED" ] && echo " β’ CLAUDE.md not staged"
|
| 20 |
+
echo ""
|
| 21 |
+
echo " Update both to reflect your changes, git add them, then retry."
|
| 22 |
+
echo " Bypass with: git commit --no-verify"
|
| 23 |
+
echo ""
|
| 24 |
+
exit 1
|
| 25 |
+
fi
|
| 26 |
+
|
| 27 |
+
exit 0
|
scripts/install-hooks.sh
ADDED
|
@@ -0,0 +1,77 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/bin/sh
|
| 2 |
+
# Install git hook and Claude Code hook for this repo.
|
| 3 |
+
# Run once after cloning: sh scripts/install-hooks.sh
|
| 4 |
+
|
| 5 |
+
set -e
|
| 6 |
+
REPO_ROOT=$(git rev-parse --show-toplevel)
|
| 7 |
+
|
| 8 |
+
# ββ Git pre-commit hook ββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 9 |
+
HOOK_DST="$REPO_ROOT/.git/hooks/pre-commit"
|
| 10 |
+
cp "$REPO_ROOT/scripts/hook-pre-commit-docs.sh" "$HOOK_DST"
|
| 11 |
+
chmod +x "$HOOK_DST"
|
| 12 |
+
echo "β git pre-commit hook installed β .git/hooks/pre-commit"
|
| 13 |
+
|
| 14 |
+
# ββ Claude Code PreToolUse hook ββββββββββββββββββββββββββββββββββββββββββββ
|
| 15 |
+
CLAUDE_DIR="$REPO_ROOT/.claude"
|
| 16 |
+
CLAUDE_HOOKS="$CLAUDE_DIR/hooks"
|
| 17 |
+
CLAUDE_SETTINGS="$CLAUDE_DIR/settings.json"
|
| 18 |
+
|
| 19 |
+
mkdir -p "$CLAUDE_HOOKS"
|
| 20 |
+
|
| 21 |
+
# Copy the hook script into .claude/hooks/
|
| 22 |
+
cp "$REPO_ROOT/scripts/hook-pre-commit-docs.sh" "$CLAUDE_HOOKS/pre-commit-docs-check.sh"
|
| 23 |
+
chmod +x "$CLAUDE_HOOKS/pre-commit-docs-check.sh"
|
| 24 |
+
|
| 25 |
+
# Merge the hook entry into .claude/settings.json
|
| 26 |
+
if [ -f "$CLAUDE_SETTINGS" ]; then
|
| 27 |
+
# Add hooks key if not already present using Python
|
| 28 |
+
python3 - "$CLAUDE_SETTINGS" <<'PYEOF'
|
| 29 |
+
import sys, json
|
| 30 |
+
|
| 31 |
+
path = sys.argv[1]
|
| 32 |
+
with open(path) as f:
|
| 33 |
+
cfg = json.load(f)
|
| 34 |
+
|
| 35 |
+
hook_entry = {
|
| 36 |
+
"type": "command",
|
| 37 |
+
"command": "bash .claude/hooks/pre-commit-docs-check.sh"
|
| 38 |
+
}
|
| 39 |
+
hooks = cfg.setdefault("hooks", {})
|
| 40 |
+
pre = hooks.setdefault("PreToolUse", [])
|
| 41 |
+
|
| 42 |
+
# Find or create Bash matcher
|
| 43 |
+
bash_matchers = [m for m in pre if m.get("matcher") == "Bash"]
|
| 44 |
+
if bash_matchers:
|
| 45 |
+
existing = bash_matchers[0].setdefault("hooks", [])
|
| 46 |
+
cmds = [h.get("command") for h in existing]
|
| 47 |
+
if hook_entry["command"] not in cmds:
|
| 48 |
+
existing.append(hook_entry)
|
| 49 |
+
else:
|
| 50 |
+
pre.append({"matcher": "Bash", "hooks": [hook_entry]})
|
| 51 |
+
|
| 52 |
+
with open(path, "w") as f:
|
| 53 |
+
json.dump(cfg, f, indent=2)
|
| 54 |
+
f.write("\n")
|
| 55 |
+
|
| 56 |
+
print("β Claude Code PreToolUse hook registered β .claude/settings.json")
|
| 57 |
+
PYEOF
|
| 58 |
+
else
|
| 59 |
+
cat > "$CLAUDE_SETTINGS" <<JSON
|
| 60 |
+
{
|
| 61 |
+
"hooks": {
|
| 62 |
+
"PreToolUse": [
|
| 63 |
+
{
|
| 64 |
+
"matcher": "Bash",
|
| 65 |
+
"hooks": [
|
| 66 |
+
{
|
| 67 |
+
"type": "command",
|
| 68 |
+
"command": "bash .claude/hooks/pre-commit-docs-check.sh"
|
| 69 |
+
}
|
| 70 |
+
]
|
| 71 |
+
}
|
| 72 |
+
]
|
| 73 |
+
}
|
| 74 |
+
}
|
| 75 |
+
JSON
|
| 76 |
+
echo "β Claude Code PreToolUse hook registered β .claude/settings.json (created)"
|
| 77 |
+
fi
|