Skip to content

Commit 456c197

Browse files
david22swanclaude
andcommitted
(CAT-2609) Add CLAUDE.md guidance and Claude Code safety hooks
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 3913121 commit 456c197

6 files changed

Lines changed: 86 additions & 0 deletions

File tree

.claude/hooks/_parse_input.sh

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
#!/usr/bin/env bash
2+
# Shared helper: reads hook JSON from stdin and sets $cmd to the Bash tool command.
3+
# Sources into each hook with: . "$(dirname "$0")/_parse_input.sh"
4+
# Exits 0 (allow) if no JSON parser is available.
5+
6+
input=$(cat)
7+
8+
if command -v jq >/dev/null 2>&1; then
9+
cmd=$(printf '%s' "$input" | jq -r '.tool_input.command // ""' 2>/dev/null || echo "")
10+
elif command -v python3 >/dev/null 2>&1; then
11+
cmd=$(printf '%s' "$input" | python3 -c "import sys,json; print(json.load(sys.stdin).get('tool_input',{}).get('command',''))" 2>/dev/null || echo "")
12+
else
13+
exit 0
14+
fi

.claude/hooks/no-main-commits.sh

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never work directly on the main or master branch.
3+
# Blocks: git commit when current branch is main or master.
4+
5+
. "$(dirname "$0")/_parse_input.sh"
6+
7+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*git commit'; then
8+
branch=$(git rev-parse --abbrev-ref HEAD 2>/dev/null)
9+
if [ "$branch" = "main" ] || [ "$branch" = "master" ]; then
10+
echo "{\"continue\":false,\"stopReason\":\"Project rule: never work directly on the $branch branch.\"}"
11+
fi
12+
fi

.claude/hooks/no-pr-merge.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never merge a pull request.
3+
# Blocks: gh pr merge
4+
5+
. "$(dirname "$0")/_parse_input.sh"
6+
7+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*gh pr merge'; then
8+
echo '{"continue":false,"stopReason":"Project rule: never merge a pull request."}'
9+
fi

.claude/hooks/no-push.sh

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never push a branch without explicit instruction.
3+
# Blocks: git push
4+
5+
. "$(dirname "$0")/_parse_input.sh"
6+
7+
if echo "$cmd" | grep -qE '^git push'; then
8+
echo '{"continue":false,"stopReason":"Project rule: never push a branch without explicit instruction."}'
9+
fi

.claude/hooks/no-rm.sh

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
#!/usr/bin/env bash
2+
# Project rule: never delete a file without explicit permission.
3+
# Blocks: rm as the leading command token after a shell separator.
4+
# Does not cover sudo rm, /bin/rm, or find -exec rm variants.
5+
6+
. "$(dirname "$0")/_parse_input.sh"
7+
8+
if echo "$cmd" | grep -qE '(^|[;&|])[[:space:]]*rm([[:space:]]|$)'; then
9+
echo '{"continue":false,"stopReason":"Project rule: never delete a file without explicit permission."}'
10+
fi

.claude/settings.json

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
{
2+
"hooks": {
3+
"PreToolUse": [
4+
{
5+
"matcher": "Bash",
6+
"hooks": [
7+
{
8+
"type": "command",
9+
"command": ".claude/hooks/no-pr-merge.sh",
10+
"timeout": 5,
11+
"statusMessage": "Checking project rules..."
12+
},
13+
{
14+
"type": "command",
15+
"command": ".claude/hooks/no-main-commits.sh",
16+
"timeout": 5
17+
},
18+
{
19+
"type": "command",
20+
"command": ".claude/hooks/no-push.sh",
21+
"timeout": 5
22+
},
23+
{
24+
"type": "command",
25+
"command": ".claude/hooks/no-rm.sh",
26+
"timeout": 5
27+
}
28+
]
29+
}
30+
]
31+
}
32+
}

0 commit comments

Comments
 (0)