chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"description": "Enforce conventional commit message format for all git commits. Validates commit messages follow the pattern: type(scope): description. Supported types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert. Ensures consistent commit history for changelog generation and semantic versioning.",
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/conventional-commits.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
command = tool_input.get("command", "")
|
||||
|
||||
# Only validate git commit commands
|
||||
if tool_name != "Bash" or "git commit" not in command:
|
||||
sys.exit(0)
|
||||
|
||||
# Extract commit message from -m flag
|
||||
# Handle both -m "message" and -m 'message' formats
|
||||
match = re.search(r'git commit.*?-m\s+["\']([^"\']+)["\']', command)
|
||||
if not match:
|
||||
# Also try heredoc format: -m "$(cat <<'EOF' ... EOF)"
|
||||
heredoc_match = re.search(r'git commit.*?-m\s+"?\$\(cat\s+<<["\']?EOF["\']?\s*\n(.+?)\nEOF', command, re.DOTALL)
|
||||
if heredoc_match:
|
||||
commit_msg = heredoc_match.group(1).strip()
|
||||
else:
|
||||
sys.exit(0) # Can't extract message, allow it
|
||||
else:
|
||||
commit_msg = match.group(1)
|
||||
|
||||
# Check if message follows Conventional Commits format
|
||||
# Format: type(scope)?: description
|
||||
# Types: feat, fix, docs, style, refactor, perf, test, chore, ci, build, revert
|
||||
conventional_pattern = r'^(feat|fix|docs|style|refactor|perf|test|chore|ci|build|revert)(\(.+\))?:\s.+'
|
||||
|
||||
if not re.match(conventional_pattern, commit_msg):
|
||||
reason = f"""❌ Invalid commit message format
|
||||
|
||||
Your message: {commit_msg}
|
||||
|
||||
Commit messages must follow Conventional Commits:
|
||||
type(scope): description
|
||||
|
||||
Types:
|
||||
feat: New feature
|
||||
fix: Bug fix
|
||||
docs: Documentation changes
|
||||
style: Code style changes (formatting)
|
||||
refactor: Code refactoring
|
||||
perf: Performance improvements
|
||||
test: Adding or updating tests
|
||||
chore: Maintenance tasks
|
||||
ci: CI/CD changes
|
||||
build: Build system changes
|
||||
revert: Revert previous commit
|
||||
|
||||
Examples:
|
||||
✅ feat: add user authentication
|
||||
✅ feat(auth): implement JWT tokens
|
||||
✅ fix: resolve memory leak in parser
|
||||
✅ fix(api): handle null responses
|
||||
✅ docs: update API documentation
|
||||
|
||||
Invalid:
|
||||
❌ Added new feature (no type)
|
||||
❌ feat:add feature (missing space after colon)
|
||||
❌ feature: add login (wrong type, use 'feat')
|
||||
|
||||
💡 Tip: Start your message with one of the types above followed by a colon and space."""
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": reason
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
sys.exit(0)
|
||||
|
||||
# Allow the command
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"description": "Prevent direct pushes to protected branches (main, develop). Blocks git push commands targeting main or develop branches to enforce Git Flow workflow. Requires using feature/release/hotfix branches and pull requests instead of direct commits to protected branches.",
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/prevent-direct-push.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
command = tool_input.get("command", "")
|
||||
|
||||
# Only validate git push commands
|
||||
if tool_name != "Bash" or "git push" not in command:
|
||||
sys.exit(0)
|
||||
|
||||
# Get current branch
|
||||
try:
|
||||
current_branch = subprocess.check_output(
|
||||
["git", "branch", "--show-current"],
|
||||
stderr=subprocess.DEVNULL,
|
||||
text=True
|
||||
).strip()
|
||||
except:
|
||||
current_branch = ""
|
||||
|
||||
# Check if pushing to main or develop
|
||||
push_cmd = command
|
||||
is_force_push = "--force" in push_cmd or "-f" in push_cmd
|
||||
|
||||
# Check if command or current branch targets protected branches
|
||||
targets_protected = (
|
||||
"origin main" in push_cmd or
|
||||
"origin develop" in push_cmd or
|
||||
current_branch in ["main", "develop"]
|
||||
)
|
||||
|
||||
# Block direct push to main/develop (unless force push which is already dangerous)
|
||||
if targets_protected and not is_force_push:
|
||||
if current_branch in ["main", "develop"] or "origin main" in push_cmd or "origin develop" in push_cmd:
|
||||
reason = f"""❌ Direct push to main/develop is not allowed!
|
||||
|
||||
Protected branches:
|
||||
- main (production)
|
||||
- develop (integration)
|
||||
|
||||
Git Flow workflow:
|
||||
1. Create a feature branch:
|
||||
/feature <name>
|
||||
|
||||
2. Make your changes and commit
|
||||
|
||||
3. Push feature branch:
|
||||
git push origin feature/<name>
|
||||
|
||||
4. Create pull request:
|
||||
gh pr create
|
||||
|
||||
5. After approval, merge with:
|
||||
/finish
|
||||
|
||||
For releases:
|
||||
/release <version> → PR → /finish
|
||||
|
||||
For hotfixes:
|
||||
/hotfix <name> → PR → /finish
|
||||
|
||||
Current branch: {current_branch}
|
||||
|
||||
💡 Use feature/release/hotfix branches instead of pushing directly to main/develop."""
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": reason
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
sys.exit(0)
|
||||
|
||||
# Allow the command
|
||||
sys.exit(0)
|
||||
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"description": "Validate Git Flow branch naming conventions before checkout. Ensures branches follow the pattern: feature/*, release/v*.*.*, hotfix/*. Prevents creation of branches that don't follow Git Flow standards.",
|
||||
"hooks": {
|
||||
"PreToolUse": [
|
||||
{
|
||||
"matcher": "Bash",
|
||||
"hooks": [
|
||||
{
|
||||
"type": "command",
|
||||
"command": "python3 \"$CLAUDE_PROJECT_DIR\"/.claude/hooks/validate-branch-name.py"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
#!/usr/bin/env python3
|
||||
import json
|
||||
import sys
|
||||
import re
|
||||
|
||||
try:
|
||||
input_data = json.load(sys.stdin)
|
||||
except json.JSONDecodeError as e:
|
||||
print(f"Error: Invalid JSON input: {e}", file=sys.stderr)
|
||||
sys.exit(1)
|
||||
|
||||
tool_name = input_data.get("tool_name", "")
|
||||
tool_input = input_data.get("tool_input", {})
|
||||
command = tool_input.get("command", "")
|
||||
|
||||
# Only validate git checkout -b commands
|
||||
if tool_name != "Bash" or "git checkout -b" not in command:
|
||||
sys.exit(0)
|
||||
|
||||
# Extract branch name
|
||||
match = re.search(r'git checkout -b\s+([^\s]+)', command)
|
||||
if not match:
|
||||
sys.exit(0)
|
||||
|
||||
branch_name = match.group(1)
|
||||
|
||||
# Allow main and develop branches
|
||||
if branch_name in ["main", "develop"]:
|
||||
sys.exit(0)
|
||||
|
||||
# Validate Git Flow naming convention
|
||||
if not re.match(r'^(feature|release|hotfix)/', branch_name):
|
||||
reason = f"""❌ Invalid Git Flow branch name: {branch_name}
|
||||
|
||||
Git Flow branches must follow these patterns:
|
||||
• feature/<descriptive-name>
|
||||
• release/v<MAJOR>.<MINOR>.<PATCH>
|
||||
• hotfix/<descriptive-name>
|
||||
|
||||
Examples:
|
||||
✅ feature/user-authentication
|
||||
✅ release/v1.2.0
|
||||
✅ hotfix/critical-security-fix
|
||||
|
||||
Invalid:
|
||||
❌ {branch_name} (missing Git Flow prefix)
|
||||
❌ feat/something (use 'feature/' not 'feat/')
|
||||
❌ fix/bug (use 'hotfix/' not 'fix/')
|
||||
|
||||
💡 Use Git Flow commands instead:
|
||||
/feature <name> - Create feature branch
|
||||
/release <version> - Create release branch
|
||||
/hotfix <name> - Create hotfix branch"""
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": reason
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
sys.exit(0)
|
||||
|
||||
# Validate release version format
|
||||
if branch_name.startswith("release/"):
|
||||
if not re.match(r'^release/v\d+\.\d+\.\d+(-[a-zA-Z0-9.]+)?$', branch_name):
|
||||
reason = f"""❌ Invalid release version: {branch_name}
|
||||
|
||||
Release branches must follow semantic versioning:
|
||||
release/vMAJOR.MINOR.PATCH[-prerelease]
|
||||
|
||||
Valid examples:
|
||||
✅ release/v1.0.0
|
||||
✅ release/v2.1.3
|
||||
✅ release/v1.0.0-beta.1
|
||||
|
||||
Invalid:
|
||||
❌ release/1.0.0 (missing 'v' prefix)
|
||||
❌ release/v1.0 (incomplete version)
|
||||
❌ {branch_name}
|
||||
|
||||
💡 Use: /release v1.2.0"""
|
||||
|
||||
output = {
|
||||
"hookSpecificOutput": {
|
||||
"hookEventName": "PreToolUse",
|
||||
"permissionDecision": "deny",
|
||||
"permissionDecisionReason": reason
|
||||
}
|
||||
}
|
||||
print(json.dumps(output))
|
||||
sys.exit(0)
|
||||
|
||||
# Allow the command
|
||||
sys.exit(0)
|
||||
Reference in New Issue
Block a user