diff --git a/plugins/git-flow/hooks/branch-check.sh b/plugins/git-flow/hooks/branch-check.sh new file mode 100755 index 0000000..72527db --- /dev/null +++ b/plugins/git-flow/hooks/branch-check.sh @@ -0,0 +1,102 @@ +#!/bin/bash +# git-flow branch name validation hook +# Validates branch names follow the convention: / +# Command hook - guaranteed predictable behavior + +# Read tool input from stdin (JSON format) +INPUT=$(cat) + +# Extract command from JSON input +# The Bash tool sends {"command": "..."} format +COMMAND=$(echo "$INPUT" | grep -o '"command"[[:space:]]*:[[:space:]]*"[^"]*"' | head -1 | sed 's/.*"command"[[:space:]]*:[[:space:]]*"\([^"]*\)".*/\1/') + +# If no command found, exit silently (allow) +if [ -z "$COMMAND" ]; then + exit 0 +fi + +# Check if this is a branch creation command +# Patterns: git checkout -b, git branch (without -d/-D), git switch -c/-C +IS_BRANCH_CREATE=false +BRANCH_NAME="" + +# git checkout -b +if echo "$COMMAND" | grep -qE 'git\s+checkout\s+(-b|--branch)\s+'; then + IS_BRANCH_CREATE=true + BRANCH_NAME=$(echo "$COMMAND" | sed -n 's/.*git\s\+checkout\s\+\(-b\|--branch\)\s\+\([^ ]*\).*/\2/p') +fi + +# git switch -c/-C +if echo "$COMMAND" | grep -qE 'git\s+switch\s+(-c|-C|--create|--force-create)\s+'; then + IS_BRANCH_CREATE=true + BRANCH_NAME=$(echo "$COMMAND" | sed -n 's/.*git\s\+switch\s\+\(-c\|-C\|--create\|--force-create\)\s\+\([^ ]*\).*/\2/p') +fi + +# git branch (without -d/-D/-m/-M which are delete/rename) +if echo "$COMMAND" | grep -qE 'git\s+branch\s+[^-]' && ! echo "$COMMAND" | grep -qE 'git\s+branch\s+(-d|-D|-m|-M|--delete|--move|--list|--show-current)'; then + IS_BRANCH_CREATE=true + BRANCH_NAME=$(echo "$COMMAND" | sed -n 's/.*git\s\+branch\s\+\([^ -][^ ]*\).*/\1/p') +fi + +# If not a branch creation command, exit silently (allow) +if [ "$IS_BRANCH_CREATE" = false ]; then + exit 0 +fi + +# If we couldn't extract the branch name, exit silently (allow) +if [ -z "$BRANCH_NAME" ]; then + exit 0 +fi + +# Remove any quotes from branch name +BRANCH_NAME=$(echo "$BRANCH_NAME" | tr -d '"' | tr -d "'") + +# Skip validation for special branches +case "$BRANCH_NAME" in + main|master|develop|development|staging|release|hotfix) + exit 0 + ;; +esac + +# Allowed branch types +VALID_TYPES="feat|fix|chore|docs|refactor|test|perf|debug" + +# Validate branch name format: / +# Description: lowercase letters, numbers, hyphens only, max 50 chars total +if ! echo "$BRANCH_NAME" | grep -qE "^($VALID_TYPES)/[a-z0-9][a-z0-9-]*$"; then + echo "" + echo "[git-flow] Branch name validation failed" + echo "" + echo "Branch: $BRANCH_NAME" + echo "" + echo "Expected format: /" + echo "" + echo "Valid types: feat, fix, chore, docs, refactor, test, perf, debug" + echo "" + echo "Description rules:" + echo " - Lowercase letters, numbers, and hyphens only" + echo " - Must start with letter or number" + echo " - No spaces or special characters" + echo "" + echo "Examples:" + echo " feat/add-user-auth" + echo " fix/login-timeout" + echo " chore/update-deps" + echo " docs/api-reference" + echo "" + exit 1 +fi + +# Check total length (max 50 chars) +if [ ${#BRANCH_NAME} -gt 50 ]; then + echo "" + echo "[git-flow] Branch name too long" + echo "" + echo "Branch: $BRANCH_NAME (${#BRANCH_NAME} chars)" + echo "Maximum: 50 characters" + echo "" + exit 1 +fi + +# Valid branch name +exit 0 diff --git a/plugins/git-flow/hooks/commit-msg-check.sh b/plugins/git-flow/hooks/commit-msg-check.sh new file mode 100755 index 0000000..89eef46 --- /dev/null +++ b/plugins/git-flow/hooks/commit-msg-check.sh @@ -0,0 +1,74 @@ +#!/bin/bash +# git-flow commit message validation hook +# Validates git commit messages follow conventional commit format +# PreToolUse hook for Bash commands - type: command + +# Read tool input from stdin +INPUT=$(cat) + +# Use Python to properly parse JSON and extract the command +COMMAND=$(echo "$INPUT" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('command',''))" 2>/dev/null) + +# If no command or python failed, allow through +if [ -z "$COMMAND" ]; then + exit 0 +fi + +# Check if it is a git commit command with -m flag +if ! echo "$COMMAND" | grep -qE 'git\s+commit.*-m'; then + # Not a git commit with -m, allow through + exit 0 +fi + +# Extract commit message - handle various quoting styles +# Try double quotes first +COMMIT_MSG=$(echo "$COMMAND" | sed -n 's/.*-m[[:space:]]*"\([^"]*\)".*/\1/p') +# If empty, try single quotes +if [ -z "$COMMIT_MSG" ]; then + COMMIT_MSG=$(echo "$COMMAND" | sed -n "s/.*-m[[:space:]]*'\\([^']*\\)'.*/\\1/p") +fi +# If still empty, try HEREDOC pattern +if [ -z "$COMMIT_MSG" ]; then + if echo "$COMMAND" | grep -qE -- '-m[[:space:]]+"\$\(cat <<'; then + # HEREDOC pattern - too complex to parse, allow through + exit 0 + fi +fi + +# If no message extracted, allow through +if [ -z "$COMMIT_MSG" ]; then + exit 0 +fi + +# Validate conventional commit format +# Format: (): +# or: : +# Valid types: feat, fix, docs, style, refactor, perf, test, chore, build, ci + +VALID_TYPES="feat|fix|docs|style|refactor|perf|test|chore|build|ci" + +# Check if message matches conventional commit format +if echo "$COMMIT_MSG" | grep -qE "^($VALID_TYPES)(\([a-zA-Z0-9_-]+\))?:[[:space:]]+.+"; then + # Valid format + exit 0 +fi + +# Invalid format - output warning +echo "[git-flow] WARNING: Commit message does not follow conventional commit format" +echo "" +echo "Expected format: (): " +echo " or: : " +echo "" +echo "Valid types: feat, fix, docs, style, refactor, perf, test, chore, build, ci" +echo "" +echo "Examples:" +echo " feat(auth): add password reset functionality" +echo " fix: resolve login timeout issue" +echo " docs(readme): update installation instructions" +echo "" +echo "Your message: $COMMIT_MSG" +echo "" +echo "To proceed anyway, use /commit command which auto-generates valid messages." + +# Exit with non-zero to block +exit 1 diff --git a/plugins/git-flow/hooks/hooks.json b/plugins/git-flow/hooks/hooks.json new file mode 100644 index 0000000..7860e41 --- /dev/null +++ b/plugins/git-flow/hooks/hooks.json @@ -0,0 +1,19 @@ +{ + "hooks": { + "PreToolUse": [ + { + "matcher": "Bash", + "hooks": [ + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/branch-check.sh" + }, + { + "type": "command", + "command": "${CLAUDE_PLUGIN_ROOT}/hooks/commit-msg-check.sh" + } + ] + } + ] + } +}