harshith99 Claude Sonnet 4.6 commited on
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 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