Initial commit

This commit is contained in:
Zhongwei Li
2025-11-30 09:06:38 +08:00
commit ed3e4c84c3
76 changed files with 20449 additions and 0 deletions

View File

@@ -0,0 +1,610 @@
# Hook Patterns Library
Reusable patterns for common hook use cases.
## Pattern: File Path Validation
Safely validate and sanitize file paths in hooks.
```bash
validate_file_path() {
local path="$1"
# Remove null/empty
if [ -z "$path" ] || [ "$path" == "null" ]; then
return 1
fi
# Must be absolute path
if [[ ! "$path" =~ ^/ ]]; then
return 1
fi
# Must exist
if [ ! -f "$path" ]; then
return 1
fi
# Check file extension whitelist
if [[ ! "$path" =~ \.(ts|tsx|js|jsx|py|rs|go|java)$ ]]; then
return 1
fi
return 0
}
# Usage
if validate_file_path "$file_path"; then
# Safe to operate on file
process_file "$file_path"
fi
```
## Pattern: Finding Project Root
Locate the project root directory from any file path.
```bash
find_project_root() {
local dir="$1"
# Start from file's directory
if [ -f "$dir" ]; then
dir=$(dirname "$dir")
fi
# Walk up until finding markers
while [ "$dir" != "/" ]; do
# Check for project markers
if [ -f "$dir/package.json" ] || \
[ -f "$dir/Cargo.toml" ] || \
[ -f "$dir/go.mod" ] || \
[ -d "$dir/.git" ]; then
echo "$dir"
return 0
fi
dir=$(dirname "$dir")
done
return 1
}
# Usage
project_root=$(find_project_root "$file_path")
if [ -n "$project_root" ]; then
cd "$project_root"
npm run build
fi
```
## Pattern: Conditional Hook Execution
Run hook only when certain conditions are met.
```bash
#!/bin/bash
# Configuration
MIN_CHANGES=3
TARGET_REPO="backend"
# Check if should run
should_run() {
# Count recent edits
local edit_count=$(tail -20 ~/.claude/edit-log.txt | wc -l)
if [ "$edit_count" -lt "$MIN_CHANGES" ]; then
return 1
fi
# Check if target repo was modified
if ! tail -20 ~/.claude/edit-log.txt | grep -q "$TARGET_REPO"; then
return 1
fi
return 0
}
# Main execution
if ! should_run; then
echo '{}'
exit 0
fi
# Run actual hook logic
perform_build_check
```
## Pattern: Rate Limiting
Prevent hooks from running too frequently.
```bash
#!/bin/bash
RATE_LIMIT_FILE="/tmp/hook-last-run"
MIN_INTERVAL=30 # seconds
# Check if enough time has passed
should_run() {
if [ ! -f "$RATE_LIMIT_FILE" ]; then
return 0
fi
local last_run=$(cat "$RATE_LIMIT_FILE")
local now=$(date +%s)
local elapsed=$((now - last_run))
if [ "$elapsed" -lt "$MIN_INTERVAL" ]; then
echo "Skipping (ran ${elapsed}s ago, min interval ${MIN_INTERVAL}s)"
return 1
fi
return 0
}
# Update last run time
mark_run() {
date +%s > "$RATE_LIMIT_FILE"
}
# Usage
if should_run; then
perform_expensive_operation
mark_run
fi
echo '{}'
```
## Pattern: Multi-Project Detection
Detect which project/repo a file belongs to.
```bash
detect_project() {
local file="$1"
local project_root="/Users/myuser/projects"
# Extract project name from path
if [[ "$file" =~ $project_root/([^/]+) ]]; then
echo "${BASH_REMATCH[1]}"
return 0
fi
echo "unknown"
return 1
}
# Usage
project=$(detect_project "$file_path")
case "$project" in
"frontend")
npm --prefix ~/projects/frontend run build
;;
"backend")
cargo build --manifest-path ~/projects/backend/Cargo.toml
;;
*)
echo "Unknown project: $project"
;;
esac
```
## Pattern: Graceful Degradation
Handle failures gracefully without blocking workflow.
```bash
#!/bin/bash
# Try operation with fallback
try_with_fallback() {
local primary_cmd="$1"
local fallback_cmd="$2"
local description="$3"
echo "Attempting: $description"
# Try primary command
if eval "$primary_cmd" 2>/dev/null; then
echo "✅ Success"
return 0
fi
echo "⚠️ Primary failed, trying fallback..."
# Try fallback
if eval "$fallback_cmd" 2>/dev/null; then
echo "✅ Fallback succeeded"
return 0
fi
echo "❌ Both failed, continuing anyway"
return 1
}
# Usage
try_with_fallback \
"npm run build" \
"npm run build:dev" \
"Building project"
# Always return empty response (non-blocking)
echo '{}'
```
## Pattern: Parallel Execution
Run multiple checks in parallel for speed.
```bash
#!/bin/bash
# Run checks in parallel
run_parallel_checks() {
local pids=()
# Start each check in background
check_typescript &
pids+=($!)
check_eslint &
pids+=($!)
check_tests &
pids+=($!)
# Wait for all to complete
local exit_code=0
for pid in "${pids[@]}"; do
wait "$pid" || exit_code=1
done
return $exit_code
}
check_typescript() {
npx tsc --noEmit > /tmp/tsc-output.txt 2>&1
if [ $? -ne 0 ]; then
echo "TypeScript errors found"
return 1
fi
}
check_eslint() {
npx eslint . > /tmp/eslint-output.txt 2>&1
}
check_tests() {
npm test > /tmp/test-output.txt 2>&1
}
# Usage
if run_parallel_checks; then
echo "✅ All checks passed"
else
echo "⚠️ Some checks failed"
cat /tmp/tsc-output.txt
cat /tmp/eslint-output.txt
fi
echo '{}'
```
## Pattern: Smart Caching
Cache results to avoid redundant work.
```bash
#!/bin/bash
CACHE_DIR="$HOME/.claude/hook-cache"
mkdir -p "$CACHE_DIR"
# Generate cache key
cache_key() {
local file="$1"
echo -n "$file:$(stat -f %m "$file" 2>/dev/null || stat -c %Y "$file")" | md5sum | cut -d' ' -f1
}
# Check cache
check_cache() {
local file="$1"
local key=$(cache_key "$file")
local cache_file="$CACHE_DIR/$key"
if [ -f "$cache_file" ]; then
# Cache hit
cat "$cache_file"
return 0
fi
return 1
}
# Update cache
update_cache() {
local file="$1"
local result="$2"
local key=$(cache_key "$file")
local cache_file="$CACHE_DIR/$key"
echo "$result" > "$cache_file"
# Clean old cache entries (older than 1 day)
find "$CACHE_DIR" -type f -mtime +1 -delete 2>/dev/null
}
# Usage
if cached=$(check_cache "$file_path"); then
echo "Cache hit: $cached"
else
result=$(expensive_operation "$file_path")
update_cache "$file_path" "$result"
echo "Computed: $result"
fi
```
## Pattern: Progressive Output
Show progress for long-running hooks.
```bash
#!/bin/bash
# Progress indicator
show_progress() {
local message="$1"
echo -n "$message..."
}
complete_progress() {
local status="$1"
if [ "$status" == "success" ]; then
echo " ✅"
else
echo " ❌"
fi
}
# Usage
show_progress "Running TypeScript compiler"
if npx tsc --noEmit 2>/dev/null; then
complete_progress "success"
else
complete_progress "failure"
fi
show_progress "Running linter"
if npx eslint . 2>/dev/null; then
complete_progress "success"
else
complete_progress "failure"
fi
echo '{}'
```
## Pattern: Context Injection
Inject helpful context into Claude's prompt.
```javascript
// UserPromptSubmit hook
function injectContext(prompt) {
const context = [];
// Add relevant documentation
if (prompt.includes('API')) {
context.push('📖 API Documentation: https://docs.example.com/api');
}
// Add recent changes
const recentFiles = getRecentlyEditedFiles();
if (recentFiles.length > 0) {
context.push(`📝 Recently edited: ${recentFiles.join(', ')}`);
}
// Add project status
const buildStatus = getLastBuildStatus();
if (!buildStatus.passed) {
context.push(`⚠️ Current build has ${buildStatus.errorCount} errors`);
}
if (context.length === 0) {
return { decision: 'approve' };
}
return {
decision: 'approve',
additionalContext: `\n\n---\n${context.join('\n')}\n---\n`
};
}
```
## Pattern: Error Accumulation
Collect multiple errors before reporting.
```bash
#!/bin/bash
ERRORS=()
# Add error to collection
add_error() {
ERRORS+=("$1")
}
# Report all errors
report_errors() {
if [ ${#ERRORS[@]} -eq 0 ]; then
echo "✅ No errors found"
return 0
fi
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
echo "⚠️ Found ${#ERRORS[@]} issue(s):"
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
local i=1
for error in "${ERRORS[@]}"; do
echo "$i. $error"
((i++))
done
echo "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━"
return 1
}
# Usage
if ! run_typescript_check; then
add_error "TypeScript compilation failed"
fi
if ! run_lint_check; then
add_error "Linting issues found"
fi
if ! run_test_check; then
add_error "Tests failing"
fi
report_errors
echo '{}'
```
## Pattern: Conditional Blocking
Block only on critical errors, warn on others.
```bash
#!/bin/bash
ERROR_LEVEL="none" # none, warning, critical
# Check for issues
check_critical_issues() {
if grep -q "FIXME\|XXX\|TODO: CRITICAL" "$file_path"; then
ERROR_LEVEL="critical"
return 1
fi
return 0
}
check_warnings() {
if grep -q "console.log\|debugger" "$file_path"; then
ERROR_LEVEL="warning"
return 1
fi
return 0
}
# Run checks
check_critical_issues
check_warnings
# Return appropriate decision
case "$ERROR_LEVEL" in
"critical")
echo '{
"decision": "block",
"reason": "🚫 CRITICAL: Found critical TODOs or FIXMEs that must be addressed"
}' | jq -c '.'
;;
"warning")
echo "⚠️ Warning: Found debug statements (console.log, debugger)"
echo '{}'
;;
*)
echo '{}'
;;
esac
```
## Pattern: Hook Coordination
Coordinate between multiple hooks using shared state.
```bash
# Hook 1: Track state
#!/bin/bash
STATE_FILE="/tmp/hook-state.json"
# Update state
jq -n \
--arg timestamp "$(date +%s)" \
--arg files "$files_edited" \
'{lastRun: $timestamp, filesEdited: ($files | split(","))}' \
> "$STATE_FILE"
echo '{}'
```
```bash
# Hook 2: Read state
#!/bin/bash
STATE_FILE="/tmp/hook-state.json"
if [ -f "$STATE_FILE" ]; then
last_run=$(jq -r '.lastRun' "$STATE_FILE")
files=$(jq -r '.filesEdited[]' "$STATE_FILE")
# Use state from previous hook
for file in $files; do
process_file "$file"
done
fi
echo '{}'
```
## Pattern: User Notification
Notify user of important events without blocking.
```bash
#!/bin/bash
# Send desktop notification (macOS)
notify_macos() {
osascript -e "display notification \"$1\" with title \"Claude Code Hook\""
}
# Send desktop notification (Linux)
notify_linux() {
notify-send "Claude Code Hook" "$1"
}
# Notify based on OS
notify() {
local message="$1"
case "$OSTYPE" in
darwin*)
notify_macos "$message"
;;
linux*)
notify_linux "$message"
;;
esac
}
# Usage
if [ "$error_count" -gt 10 ]; then
notify "⚠️ Build has $error_count errors"
fi
echo '{}'
```
## Remember
- **Keep it simple** - Start with basic patterns, add complexity only when needed
- **Test thoroughly** - Test each pattern in isolation before combining
- **Fail gracefully** - Non-blocking hooks should never crash workflow
- **Log everything** - You'll need it for debugging
- **Document patterns** - Future you will thank present you