feat(examples): add production-ready commands, hooks, and comprehensive documentation

Added 3 production slash commands:
- /pr: PR creation with complexity scoring and scope analysis
- /release-notes: Generate release notes in 3 formats with migration detection
- /sonarqube: Analyze SonarCloud quality issues for PRs

Added 2 production hooks:
- dangerous-actions-blocker.sh: PreToolUse security hook blocking destructive operations
- notification.sh: Contextual macOS alerts with sound mappings

Created comprehensive hooks documentation (examples/hooks/README.md)

Improved README discoverability:
- Moved "What's Inside" to line 24 for immediate visibility
- Added DeepWiki interactive documentation explorer section
- Added "Ready-to-Use Examples" section with command/hook tables

Extended guide documentation:
- Expanded bash mode (!) with 9 concrete examples
- Documented file references (@) with usage patterns
- Updated statistics: guide now 8,505 lines (+837 lines, +10.9%)

All templates are fully generic with no project-specific references.

Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
Florian BRUNIAUX 2026-01-10 17:30:30 +01:00
parent 2dd2744721
commit 96f0435291
10 changed files with 1698 additions and 19 deletions

View file

@ -0,0 +1,151 @@
#!/bin/bash
# Hook: PreToolUse - Block dangerous actions
# Exit 0 = allow, Exit 2 = block (stderr message shown to Claude)
#
# Place in: .claude/hooks/dangerous-actions-blocker.sh
# Register in: .claude/settings.json under PreToolUse event
set -e
# Read JSON from stdin
INPUT=$(cat)
TOOL_NAME=$(echo "$INPUT" | jq -r '.tool_name // empty')
TOOL_INPUT=$(echo "$INPUT" | jq -r '.tool_input // empty')
# === BASH: Dangerous commands ===
if [[ "$TOOL_NAME" == "Bash" ]]; then
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
# Dangerous patterns
DANGEROUS_PATTERNS=(
"rm -rf /"
"rm -rf ~"
"rm -rf \$HOME"
"dd if="
"mkfs"
":(){:|:&};:" # Fork bomb
"> /dev/sda"
"chmod -R 777 /"
"chown -R"
"sudo rm"
"DROP DATABASE"
"DROP TABLE"
"--no-preserve-root"
)
for pattern in "${DANGEROUS_PATTERNS[@]}"; do
if [[ "$COMMAND" == *"$pattern"* ]]; then
echo "BLOCKED: Dangerous command detected: '$pattern'" >&2
exit 2
fi
done
# Block force push to main/master
if echo "$COMMAND" | grep -qE "git push.*(-f|--force).*(main|master)"; then
echo "BLOCKED: Force push to main/master is forbidden" >&2
exit 2
fi
# Block npm publish without confirmation
if echo "$COMMAND" | grep -qE "npm publish|pnpm publish|yarn publish"; then
echo "BLOCKED: Package publication requires manual confirmation" >&2
exit 2
fi
# Check for potential secrets in command
SECRET_PATTERNS=(
"password="
"secret="
"api_key="
"apikey="
"token="
"aws_access_key"
"aws_secret"
"private_key"
)
for pattern in "${SECRET_PATTERNS[@]}"; do
if echo "$COMMAND" | grep -qi "$pattern"; then
echo "BLOCKED: Potential secret detected in command: '$pattern'" >&2
exit 2
fi
done
fi
# === EDIT/WRITE: Sensitive files ===
if [[ "$TOOL_NAME" == "Edit" || "$TOOL_NAME" == "Write" ]]; then
FILE_PATH=$(echo "$TOOL_INPUT" | jq -r '.file_path // empty')
# Protected files
PROTECTED_FILES=(
".env"
".env.local"
".env.production"
".env.development"
"credentials.json"
"serviceAccountKey.json"
"id_rsa"
"id_ed25519"
"id_ecdsa"
".npmrc"
".pypirc"
"secrets.yml"
"secrets.yaml"
)
FILENAME=$(basename "$FILE_PATH")
for protected in "${PROTECTED_FILES[@]}"; do
if [[ "$FILENAME" == "$protected" ]]; then
echo "BLOCKED: Editing sensitive file '$FILENAME' is forbidden" >&2
exit 2
fi
done
# Block editing outside project (with configurable exceptions)
PROJECT_DIR="${CLAUDE_PROJECT_DIR:-$(pwd)}"
CLAUDE_HOME="${HOME}/.claude"
# Allowed paths (configurable via environment variable)
# Format: colon-separated paths - e.g., ALLOWED_PATHS="/custom/path:/other/path"
EXTRA_ALLOWED="${ALLOWED_PATHS:-}"
# Check if path is allowed
is_allowed=false
# Current project
[[ "$FILE_PATH" == "$PROJECT_DIR"* ]] && is_allowed=true
# Claude Code directory (~/.claude/) - plans, logs, settings
[[ "$FILE_PATH" == "$CLAUDE_HOME"* ]] && is_allowed=true
# Temporary files
[[ "$FILE_PATH" == "/tmp"* ]] && is_allowed=true
# Additional configured paths
if [[ -n "$EXTRA_ALLOWED" ]]; then
IFS=':' read -ra EXTRA_PATHS <<< "$EXTRA_ALLOWED"
for allowed_path in "${EXTRA_PATHS[@]}"; do
[[ "$FILE_PATH" == "$allowed_path"* ]] && is_allowed=true
done
fi
if [[ "$is_allowed" == "false" ]]; then
echo "BLOCKED: Editing outside project is forbidden: $FILE_PATH" >&2
echo "Allowed paths: $PROJECT_DIR, $CLAUDE_HOME, /tmp" >&2
[[ -n "$EXTRA_ALLOWED" ]] && echo "Additional allowed: $EXTRA_ALLOWED" >&2
exit 2
fi
fi
# === DELETE: Always warn ===
if [[ "$TOOL_NAME" == "Bash" ]]; then
COMMAND=$(echo "$TOOL_INPUT" | jq -r '.command // empty')
if echo "$COMMAND" | grep -qE "rm -r|rmdir|unlink"; then
# Warning but not blocking (exit 0)
echo '{"systemMessage": "Warning: File deletion detected. Verify this is intentional."}'
fi
fi
# Allow by default
exit 0

View file

@ -0,0 +1,62 @@
#!/bin/bash
# Hook: Notification - macOS alerts with contextual sounds
# Plays different sound based on notification type
#
# Place in: .claude/hooks/notification.sh
# Register in: .claude/settings.json under Notification event
#
# Note: macOS only - requires afplay and osascript
set -e
# Read JSON from stdin
INPUT=$(cat)
# Extract message and title
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude Code Notification"')
TITLE=$(echo "$INPUT" | jq -r '.title // "Claude Code"')
# Select sound based on context
select_sound() {
local msg="$1"
local msg_lower=$(echo "$msg" | tr '[:upper:]' '[:lower:]')
# Success / Completion
if echo "$msg_lower" | grep -qE "(completed|terminé|fini|done|success|réussi|validé|finished)"; then
echo "/System/Library/Sounds/Hero.aiff"
return
fi
# Error / Failure
if echo "$msg_lower" | grep -qE "(error|erreur|failed|échec|échoué|problem|problème|failure)"; then
echo "/System/Library/Sounds/Basso.aiff"
return
fi
# Waiting / Permission
if echo "$msg_lower" | grep -qE "(waiting|attente|permission|approval|input|question|prompt)"; then
echo "/System/Library/Sounds/Submarine.aiff"
return
fi
# Warning / Attention
if echo "$msg_lower" | grep -qE "(warning|attention|caution|alert|avertissement)"; then
echo "/System/Library/Sounds/Sosumi.aiff"
return
fi
# Default
echo "/System/Library/Sounds/Ping.aiff"
}
SOUND_FILE=$(select_sound "$MESSAGE")
# Play sound (in background to avoid blocking)
if [[ -f "$SOUND_FILE" ]]; then
afplay "$SOUND_FILE" &
fi
# Display macOS notification
osascript -e "display notification \"$MESSAGE\" with title \"$TITLE\" sound name \"\"" 2>/dev/null || true
exit 0