AgentSkillsCN

quality-patterns

为 Claude Code 钩子与自动化提供生产就绪的模式。适用于实施安全钩子、自动格式化、CI/CD 集成、审计日志记录,或通知系统时使用。

SKILL.md
--- frontmatter
name: quality-patterns
description: Production-ready patterns for Claude Code hooks and automation. Use when implementing security hooks, auto-formatting, CI/CD integration, audit logging, or notification systems.

Quality Patterns Library

When to Use This Skill

  • Implementing security validation hooks
  • Setting up auto-formatting workflows
  • Integrating with CI/CD pipelines
  • Adding audit logging
  • Building notification systems
  • Creating guardrails for safe automation

Security Hook Patterns

Block Dangerous Commands

settings.json:

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/security-check.sh"
          }
        ]
      }
    ]
  }
}

security-check.sh:

bash
#!/bin/bash
INPUT=$(cat)
COMMAND=$(echo "$INPUT" | jq -r '.tool_input.command // empty')

# Dangerous patterns to block
BLOCKED_PATTERNS=(
  'rm -rf /'
  'rm -rf ~'
  'rm -rf \*'
  'sudo rm'
  'chmod 777'
  '> /dev/sd'
  'mkfs\.'
  'dd if='
  ':(){:|:&};:'
  'curl.*| bash'
  'wget.*| sh'
  'eval.*\$'
)

for pattern in "${BLOCKED_PATTERNS[@]}"; do
  if echo "$COMMAND" | grep -qE "$pattern"; then
    echo "BLOCKED: Dangerous command pattern: $pattern" >&2
    exit 2
  fi
done

# Block credential file operations
if echo "$COMMAND" | grep -qE '\.(env|pem|key|credentials|secrets)'; then
  echo "BLOCKED: Potential credential file access" >&2
  exit 2
fi

# Block production database commands
if echo "$COMMAND" | grep -qiE 'production|prod' && \
   echo "$COMMAND" | grep -qiE 'DROP|DELETE|TRUNCATE'; then
  echo "BLOCKED: Destructive production database operation" >&2
  exit 2
fi

exit 0

Protect Sensitive Files

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Read|Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/protect-files.sh"
          }
        ]
      }
    ]
  }
}

protect-files.sh:

bash
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

PROTECTED=(
  '.env'
  '.env.local'
  '.env.production'
  'credentials.json'
  'secrets.yaml'
  '.aws/credentials'
  '.ssh/'
  'id_rsa'
  '*.pem'
  '*.key'
)

for pattern in "${PROTECTED[@]}"; do
  if [[ "$FILE_PATH" == *"$pattern"* ]]; then
    echo "BLOCKED: Protected file: $FILE_PATH" >&2
    exit 2
  fi
done

exit 0

Auto-Formatting Patterns

Multi-Language Formatter

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/auto-format.sh"
          }
        ]
      }
    ]
  }
}

auto-format.sh:

bash
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# JavaScript/TypeScript
if [[ "$FILE_PATH" =~ \.(js|jsx|ts|tsx|json)$ ]]; then
  prettier --write "$FILE_PATH" 2>/dev/null || true
  eslint "$FILE_PATH" --fix 2>/dev/null || true
fi

# Python
if [[ "$FILE_PATH" =~ \.py$ ]]; then
  black "$FILE_PATH" 2>/dev/null || true
  isort "$FILE_PATH" 2>/dev/null || true
fi

# Rust
if [[ "$FILE_PATH" =~ \.rs$ ]]; then
  rustfmt "$FILE_PATH" 2>/dev/null || true
fi

# Go
if [[ "$FILE_PATH" =~ \.go$ ]]; then
  gofmt -w "$FILE_PATH" 2>/dev/null || true
fi

# YAML
if [[ "$FILE_PATH" =~ \.(yaml|yml)$ ]]; then
  yamlfmt "$FILE_PATH" 2>/dev/null || true
fi

exit 0

Lint After Format

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "prettier --write \"$CLAUDE_FILE_PATHS\" 2>/dev/null || true",
            "timeout": 10
          },
          {
            "type": "command",
            "command": "eslint \"$CLAUDE_FILE_PATHS\" --fix 2>/dev/null || true",
            "timeout": 15
          },
          {
            "type": "command",
            "command": "npm run typecheck 2>&1 | head -20 || true",
            "async": true,
            "timeout": 30
          }
        ]
      }
    ]
  }
}

CI/CD Integration Patterns

GitHub Actions Workflow

.github/workflows/claude-code.yml:

yaml
name: Claude Code
on:
  issue_comment:
    types: [created]
  pull_request_review_comment:
    types: [created]

jobs:
  claude:
    if: contains(github.event.comment.body, '@claude')
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}

Test on File Change

json
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/run-related-tests.sh",
            "async": true,
            "timeout": 120
          }
        ]
      }
    ]
  }
}

run-related-tests.sh:

bash
#!/bin/bash
INPUT=$(cat)
FILE_PATH=$(echo "$INPUT" | jq -r '.tool_input.file_path // empty')

# Skip non-source files
if [[ ! "$FILE_PATH" =~ \.(ts|tsx|js|jsx)$ ]]; then
  exit 0
fi

# Find and run related test file
TEST_FILE="${FILE_PATH%.ts}.test.ts"
TEST_FILE="${TEST_FILE%.tsx}.test.tsx"
TEST_FILE="${TEST_FILE%.js}.test.js"

if [ -f "$TEST_FILE" ]; then
  npm test -- --testPathPattern="$(basename "$TEST_FILE")" 2>&1 | tail -30
  EXIT_CODE=$?
  if [ $EXIT_CODE -ne 0 ]; then
    echo "{\"systemMessage\": \"Tests failed after editing $FILE_PATH\"}"
  else
    echo "{\"systemMessage\": \"Tests passed for $FILE_PATH\"}"
  fi
fi

exit 0

Pre-Commit Validation

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(git commit:*)",
        "hooks": [
          {
            "type": "command",
            "command": "npm run lint && npm test"
          }
        ]
      }
    ]
  }
}

Audit Logging Patterns

Comprehensive Audit Log

json
{
  "hooks": {
    "SessionStart": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-session.sh start"
          }
        ]
      }
    ],
    "SessionEnd": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-session.sh end"
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit|Bash",
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/audit-tool.sh"
          }
        ]
      }
    ]
  }
}

audit-tool.sh:

bash
#!/bin/bash
INPUT=$(cat)
TOOL=$(echo "$INPUT" | jq -r '.tool_name')
TIMESTAMP=$(date -u +%Y-%m-%dT%H:%M:%SZ)
SESSION=$(echo "$INPUT" | jq -r '.session_id')

LOG_DIR="$CLAUDE_PROJECT_DIR/.claude/logs"
mkdir -p "$LOG_DIR"

LOG_ENTRY=$(jq -n \
  --arg ts "$TIMESTAMP" \
  --arg tool "$TOOL" \
  --arg session "$SESSION" \
  --argjson input "$(echo "$INPUT" | jq '.tool_input')" \
  '{timestamp: $ts, tool: $tool, session: $session, input: $input}')

echo "$LOG_ENTRY" >> "$LOG_DIR/audit.jsonl"
exit 0

JSON Lines Format

Audit logs use JSONL format for easy querying:

bash
# Find all Bash commands in last session
jq -s 'map(select(.tool == "Bash"))' .claude/logs/audit.jsonl

# Find file modifications
jq -s 'map(select(.tool == "Write" or .tool == "Edit"))' .claude/logs/audit.jsonl

Notification Patterns

Slack Notification

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "\"$CLAUDE_PROJECT_DIR\"/.claude/hooks/notify-slack.sh",
            "async": true
          }
        ]
      }
    ]
  }
}

notify-slack.sh:

bash
#!/bin/bash
WEBHOOK="${SLACK_WEBHOOK_URL:-}"
[ -z "$WEBHOOK" ] && exit 0

INPUT=$(cat)
MESSAGE=$(echo "$INPUT" | jq -r '.message // "Claude Code task completed"')

curl -X POST "$WEBHOOK" \
  -H 'Content-Type: application/json' \
  -d "{\"text\": \"✅ $MESSAGE\"}" \
  2>/dev/null

exit 0

Desktop Notification

json
{
  "hooks": {
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "osascript -e 'display notification \"Task completed\" with title \"Claude Code\"' 2>/dev/null || true",
            "async": true
          }
        ]
      }
    ]
  }
}

Guardrail Patterns

Confirm Before Deploy

json
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash(npm run deploy:*)|Bash(./deploy:*)",
        "hooks": [
          {
            "type": "prompt",
            "prompt": "A deploy command is about to run. Verify this is intentional. If deploying to production, return JSON with decision:'deny'. Context: $ARGUMENTS",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

Rate Limiting

rate-limit.sh:

bash
#!/bin/bash
TOOL=$1
LIMIT_FILE="/tmp/claude-rate-limit-$TOOL"
LIMIT=10  # max calls per minute

# Get current count
COUNT=$(cat "$LIMIT_FILE" 2>/dev/null || echo "0 0")
LAST_RESET=$(echo "$COUNT" | cut -d' ' -f1)
CALL_COUNT=$(echo "$COUNT" | cut -d' ' -f2)
NOW=$(date +%s)

# Reset if minute passed
if [ $((NOW - LAST_RESET)) -gt 60 ]; then
  echo "$NOW 1" > "$LIMIT_FILE"
  exit 0
fi

# Check limit
if [ "$CALL_COUNT" -ge "$LIMIT" ]; then
  echo "BLOCKED: Rate limit exceeded for $TOOL" >&2
  exit 2
fi

# Increment
echo "$LAST_RESET $((CALL_COUNT + 1))" > "$LIMIT_FILE"
exit 0

Anti-Patterns to Avoid

❌ Blocking hooks that timeout

Long-running hooks block Claude. Use async: true for slow operations.

❌ Hooks that fail on missing tools

Always check if tools exist: command -v prettier &>/dev/null && prettier ...

❌ Verbose logging

Don't log every operation. Log significant actions only.

❌ Security through obscurity

Don't rely on hiding commands. Use explicit deny lists.

❌ Hard-coded paths

Use $CLAUDE_PROJECT_DIR for project paths.