メインコンテンツへスキップ
このクックブックでは、Droidが変更を行う前に、フックを使用してコードの変更を検証し、セキュリティポリシーを強制し、コード品質基準を維持する方法を説明します。

仕組み

検証フックでは以下のことができます:
  1. 安全でない操作をブロック: 機密ファイルやディレクトリの編集を防止
  2. 基準を強制: スタイルガイドとベストプラクティスに対してコードをチェック
  3. セキュリティを検証: シークレット、脆弱性、セキュリティ問題をスキャン
  4. フィードバックを提供: 修正すべき内容についてDroidに具体的なガイダンスを提供
  5. チェックを実行: リンター、型チェッカー、カスタムバリデーターを実行

前提条件

お使いのスタック用の検証ツールをインストールしてください:
npm install -D eslint typescript @typescript-eslint/parser
npm install -D semgrep  # For security scanning

基本的な検証

機密ファイル編集のブロック

Droidが重要なファイルを変更することを防止します。 .factory/hooks/protect-files.shを作成:
#!/bin/bash

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Skip if no file path
if [ -z "$file_path" ]; then
  exit 0
fi

# List of protected patterns
protected_patterns=(
  "\.env"
  "\.env\."
  "package-lock\.json"
  "yarn\.lock"
  "\.git/"
  "node_modules/"
  "dist/"
  "build/"
  "secrets/"
  "\.pem$"
  "\.key$"
  "\.p12$"
  "credentials\.json"
)

# Check if file matches any protected pattern
for pattern in "${protected_patterns[@]}"; do
  if echo "$file_path" | grep -qE "$pattern"; then
    echo "❌ Cannot modify protected file: $file_path" >&2
    echo "This file is protected by project policy." >&2
    echo "If you need to modify it, please do so manually." >&2
    exit 2  # Exit code 2 blocks the operation
  fi
done

exit 0
chmod +x .factory/hooks/protect-files.sh
.factory/settings.jsonに追加:
{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/protect-files.sh",
            "timeout": 3
          }
        ]
      }
    ]
  }
}

TypeScript構文の検証

編集を受け入れる前にTypeScriptファイルの型エラーをチェックします。 .factory/hooks/validate-typescript.shを作成:
#!/bin/bash
set -e

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Only validate TypeScript files
if ! echo "$file_path" | grep -qE '\.(ts|tsx)$'; then
  exit 0
fi

# For Write operations, validate the content directly
if [ "$tool_name" = "Write" ]; then
  content=$(echo "$input" | jq -r '.tool_input.content')
  
  # Write to temp file for validation
  temp_file=$(mktemp --suffix=.ts)
  echo "$content" > "$temp_file"
  
  # Run TypeScript compiler on temp file
  if ! npx tsc --noEmit "$temp_file" 2>&1; then
    rm "$temp_file"
    echo "❌ TypeScript validation failed" >&2
    echo "The code contains type errors. Please fix them before proceeding." >&2
    exit 2
  fi
  
  rm "$temp_file"
fi

# For Edit operations, validate the file will be valid after edit
# (This requires reading the file and applying the edit, which is complex)
# For now, we'll validate post-edit in PostToolUse

exit 0
chmod +x .factory/hooks/validate-typescript.sh
PostToolUse検証の場合:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "jq -r '.tool_input.file_path' | { read file_path; if echo \"$file_path\" | grep -qE '\\.(ts|tsx)$' && [ -f \"$file_path\" ]; then npx tsc --noEmit \"$file_path\" 2>&1 || { echo '⚠️ Type errors detected in '$file_path >&2; }; fi; }",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

セキュリティ検証

シークレット検出

シークレットや認証情報のコミットを防止します。 .factory/hooks/scan-secrets.shを作成:
#!/bin/bash

input=$(cat)
tool_name=$(echo "$input" | jq -r '.tool_name')
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Only scan file write/edit operations
if [ "$tool_name" != "Write" ] && [ "$tool_name" != "Edit" ]; then
  exit 0
fi

# Skip non-text files
if echo "$file_path" | grep -qE '\.(jpg|png|gif|pdf|zip|tar|gz)$'; then
  exit 0
fi

# Get content to scan
if [ "$tool_name" = "Write" ]; then
  content=$(echo "$input" | jq -r '.tool_input.content')
else
  # For Edit, we'd need to check the file after edit (PostToolUse is better)
  exit 0
fi

# Create temp file for scanning
temp_file=$(mktemp)
echo "$content" > "$temp_file"

# Pattern-based secret detection
secret_patterns=(
  "AKIA[0-9A-Z]{16}"  # AWS Access Key
  "AIza[0-9A-Za-z\\-_]{35}"  # Google API Key
  "sk-[a-zA-Z0-9]{32,}"  # OpenAI API Key
  "[a-f0-9]{32}"  # Generic 32-char hex (MD5)
  "ghp_[a-zA-Z0-9]{36}"  # GitHub Personal Access Token
  "glpat-[a-zA-Z0-9\\-]{20}"  # GitLab Personal Access Token
)

found_secrets=0

for pattern in "${secret_patterns[@]}"; do
  if grep -qE "$pattern" "$temp_file"; then
    echo "❌ Potential secret detected matching pattern: $pattern" >&2
    found_secrets=1
  fi
done

# Also check for common variable names with suspicious values
if grep -qE "(password|secret|key|token|api_key)\s*[:=]\s*['\"][^'\"]{8,}" "$temp_file"; then
  echo "⚠️ Suspicious credential-like assignment detected" >&2
  echo "Review: $(grep -E "(password|secret|key|token|api_key)\s*[:=]" "$temp_file" | head -1)" >&2
  found_secrets=1
fi

rm "$temp_file"

if [ $found_secrets -eq 1 ]; then
  echo "" >&2
  echo "Please use environment variables or secure secret management instead." >&2
  exit 2  # Block the operation
fi

exit 0
chmod +x .factory/hooks/scan-secrets.sh
.factory/settings.jsonに追加:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/scan-secrets.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

依存関係セキュリティスキャン

脆弱な依存関係をチェックします。 .factory/hooks/check-deps.shを作成:
#!/bin/bash

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

# Only check package files
if [[ ! "$file_path" =~ (package\.json|requirements\.txt|go\.mod|Cargo\.toml)$ ]]; then
  exit 0
fi

echo "🔍 Checking for vulnerable dependencies..."

case "$file_path" in
  *package.json)
    if command -v npm &> /dev/null; then
      # Run npm audit
      if ! npm audit --audit-level=high 2>&1; then
        echo "⚠️ Vulnerable dependencies detected" >&2
        echo "Run 'npm audit fix' to resolve issues" >&2
        # Don't block, just warn
        exit 0
      fi
    fi
    ;;
    
  *requirements.txt)
    if command -v pip-audit &> /dev/null; then
      if ! pip-audit -r "$file_path" 2>&1; then
        echo "⚠️ Vulnerable Python packages detected" >&2
        exit 0
      fi
    fi
    ;;
    
  *Cargo.toml)
    if command -v cargo-audit &> /dev/null; then
      if ! cargo audit 2>&1; then
        echo "⚠️ Vulnerable Rust crates detected" >&2
        exit 0
      fi
    fi
    ;;
esac

exit 0
chmod +x .factory/hooks/check-deps.sh
.factory/settings.jsonに追加:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/check-deps.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

コード品質検証

リンティングルールの強制

変更を受け入れる前にリンティングルールに対してコードを検証します。 .factory/hooks/lint-check.shを作成:
#!/bin/bash

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')

if [ ! -f "$file_path" ]; then
  # File doesn't exist yet (Write operation), skip for now
  exit 0
fi

# Run appropriate linter based on file type
case "$file_path" in
  *.ts|*.tsx|*.js|*.jsx)
    if command -v eslint &> /dev/null; then
      if ! eslint "$file_path" 2>&1; then
        echo "" >&2
        echo "❌ ESLint found issues in $file_path" >&2
        echo "Please fix the linting errors above." >&2
        exit 2  # Block the operation
      fi
      echo "✓ ESLint passed for $file_path"
    fi
    ;;
    
  *.py)
    if command -v flake8 &> /dev/null; then
      if ! flake8 "$file_path" 2>&1; then
        echo "" >&2
        echo "❌ Flake8 found issues in $file_path" >&2
        exit 2
      fi
      echo "✓ Flake8 passed for $file_path"
    fi
    ;;
    
  *.go)
    if command -v golint &> /dev/null; then
      if ! golint "$file_path" 2>&1; then
        echo "" >&2
        echo "❌ Golint found issues in $file_path" >&2
        exit 2
      fi
    fi
    ;;
    
  *.rs)
    if command -v clippy &> /dev/null; then
      if ! cargo clippy -- -D warnings 2>&1; then
        echo "❌ Clippy found issues" >&2
        exit 2
      fi
    fi
    ;;
esac

exit 0
chmod +x .factory/hooks/lint-check.sh
.factory/settings.jsonに追加:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/lint-check.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

複雑度チェック

過度に複雑なコードを拒否します。 .factory/hooks/check-complexity.pyを作成:
#!/usr/bin/env python3
"""
Check code complexity and reject changes that are too complex.
Uses radon for Python, or custom heuristics for other languages.
"""
import json
import sys
import subprocess
import re

def check_python_complexity(file_path):
    """Check Python file complexity using radon."""
    try:
        result = subprocess.run(
            ['radon', 'cc', file_path, '-s', '-n', 'C'],
            capture_output=True,
            text=True,
            timeout=10
        )
        
        if result.returncode == 0 and result.stdout:
            print(f"❌ Code complexity too high in {file_path}", file=sys.stderr)
            print(result.stdout, file=sys.stderr)
            print("\nPlease simplify the code by:", file=sys.stderr)
            print("- Breaking down large functions", file=sys.stderr)
            print("- Reducing nesting levels", file=sys.stderr)
            print("- Extracting helper functions", file=sys.stderr)
            return False
            
    except (subprocess.SubprocessError, FileNotFoundError):
        pass
    
    return True

def check_js_complexity(file_path):
    """Basic complexity check for JavaScript/TypeScript."""
    with open(file_path, 'r') as f:
        content = f.read()
    
    # Count nesting level (very basic check)
    max_nesting = 0
    current_nesting = 0
    
    for char in content:
        if char == '{':
            current_nesting += 1
            max_nesting = max(max_nesting, current_nesting)
        elif char == '}':
            current_nesting -= 1
    
    if max_nesting > 5:
        print(f"⚠️ High nesting level ({max_nesting}) in {file_path}", file=sys.stderr)
        print("Consider refactoring to reduce nesting.", file=sys.stderr)
        # Warning only, don't block
    
    return True

try:
    input_data = json.load(sys.stdin)
    file_path = input_data.get('tool_input', {}).get('file_path', '')
    
    if not file_path:
        sys.exit(0)
    
    if file_path.endswith('.py'):
        if not check_python_complexity(file_path):
            sys.exit(2)
    elif file_path.endswith(('.js', '.jsx', '.ts', '.tsx')):
        if not check_js_complexity(file_path):
            sys.exit(2)
    
except Exception as e:
    print(f"Error checking complexity: {e}", file=sys.stderr)
    sys.exit(0)  # Don't block on errors
chmod +x .factory/hooks/check-complexity.py
pip install radon  # For Python complexity checking
.factory/settings.jsonに追加:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/check-complexity.py",
            "timeout": 15
          }
        ]
      }
    ]
  }
}

高度な検証

カスタムビジネスロジック検証

ドメイン固有のルールを強制します。 .factory/hooks/validate-business-logic.shを作成:
#!/bin/bash

input=$(cat)
file_path=$(echo "$input" | jq -r '.tool_input.file_path // ""')
content=$(echo "$input" | jq -r '.tool_input.content // ""')

# Example: Ensure API routes have authentication
if echo "$file_path" | grep -qE 'routes/.*\.ts$'; then
  if echo "$content" | grep -qE 'router\.(get|post|put|delete)' && \
     ! echo "$content" | grep -qE '(authenticate|requireAuth|isAuthenticated)'; then
    echo "❌ API routes must include authentication middleware" >&2
    echo "Add authenticate() or requireAuth() to your route handlers." >&2
    exit 2
  fi
fi

# Example: Ensure database queries use parameterized statements
if echo "$content" | grep -qE 'db\.query\([^?]*\$\{'; then
  echo "❌ SQL injection risk detected" >&2
  echo "Use parameterized queries instead of string interpolation." >&2
  echo "Bad:  db.query(\`SELECT * FROM users WHERE id = \${id}\`)" >&2
  echo "Good: db.query('SELECT * FROM users WHERE id = ?', [id])" >&2
  exit 2
fi

# Example: Ensure React components have prop type validation
if echo "$file_path" | grep -qE 'components/.*\.(tsx|jsx)$'; then
  if echo "$content" | grep -qE 'export (const|function)' && \
     ! echo "$content" | grep -qE '(PropTypes|interface.*Props|type.*Props)'; then
    echo "⚠️ React component should have prop type definitions" >&2
    echo "Consider adding TypeScript interfaces or PropTypes." >&2
    # Warning only, don't block
  fi
fi

exit 0
chmod +x .factory/hooks/validate-business-logic.sh
.factory/settings.jsonに追加:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/validate-business-logic.sh",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

アーキテクチャ準拠

コードがアーキテクチャパターンに従うことを確保します。 .factory/hooks/check-architecture.pyを作成:
#!/usr/bin/env python3
"""
Enforce architectural boundaries and patterns.
"""
import json
import sys
import os
import re

ARCHITECTURE_RULES = {
    # Frontend components shouldn't import from backend
    r'src/frontend/.*\.tsx?$': {
        'forbidden_imports': [r'src/backend/', r'../backend/'],
        'message': 'Frontend code cannot import from backend'
    },
    
    # Domain layer shouldn't import from infrastructure
    r'src/domain/.*\.ts$': {
        'forbidden_imports': [r'src/infrastructure/', r'express', r'axios'],
        'message': 'Domain layer must be framework-agnostic'
    },
    
    # Tests shouldn't import from src
    r'tests/.*\.test\.ts$': {
        'forbidden_imports': [r'src/(?!test-utils)'],
        'message': 'Tests should use public APIs, not internal imports'
    },
}

def check_imports(file_path, content):
    """Check if imports violate architecture rules."""
    for pattern, rule in ARCHITECTURE_RULES.items():
        if re.search(pattern, file_path):
            # Extract all imports from the file
            import_pattern = r'from [\'"](.+?)[\'"]|import .+ from [\'"](.+?)[\'"]'
            imports = re.findall(import_pattern, content)
            
            for imp in imports:
                import_path = imp[0] or imp[1]
                
                # Check against forbidden patterns
                for forbidden in rule['forbidden_imports']:
                    if re.search(forbidden, import_path):
                        print(f"❌ Architecture violation in {file_path}", file=sys.stderr)
                        print(f"   {rule['message']}", file=sys.stderr)
                        print(f"   Forbidden import: {import_path}", file=sys.stderr)
                        return False
    
    return True

try:
    input_data = json.load(sys.stdin)
    file_path = input_data.get('tool_input', {}).get('file_path', '')
    content = input_data.get('tool_input', {}).get('content', '')
    
    if not file_path or not content:
        sys.exit(0)
    
    if not check_imports(file_path, content):
        sys.exit(2)
    
except Exception as e:
    print(f"Error: {e}", file=sys.stderr)
    sys.exit(0)
chmod +x .factory/hooks/check-architecture.py
.factory/settings.jsonに追加:
{
  "hooks": {
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "\"$DROID_PROJECT_DIR\"/.factory/hooks/check-architecture.py",
            "timeout": 10
          }
        ]
      }
    ]
  }
}

ベストプラクティス

1

Provide clear feedback

When blocking operations, explain what’s wrong and how to fix it:
echo "❌ Problem: TypeScript errors found" >&2
echo "Solution: Fix the type errors shown above" >&2
echo "Example: Add proper type annotations to parameters" >&2
exit 2
2

Use warnings vs. errors appropriately

Not everything needs to block Droid:
# Warning (exit 0) - inform but don't block
echo "⚠️ Code complexity is high, consider refactoring" >&2
exit 0

# Error (exit 2) - block the operation
echo "❌ Secret detected, cannot proceed" >&2
exit 2
3

Performance matters

Keep validation fast:
# Use grep for quick checks before expensive operations
if grep -q "password" "$file"; then
  # Only run expensive scan if simple check passes
  run_detailed_security_scan "$file"
fi
4

Make rules configurable

Allow teams to customize validation:
# Read config from project settings
MAX_COMPLEXITY="${DROID_MAX_COMPLEXITY:-10}"
ENFORCE_TESTS="${DROID_ENFORCE_TESTS:-false}"
5

Test your validators

Create test files that should pass and fail:
# Create test cases
echo '{"tool_input":{"file_path":"test.ts","content":"..."}}' | \
  .factory/hooks/validate.sh

トラブルシューティング

偽陽性

問題: 検証が正当なコードをブロックする 解決策: 除外パターンを追加:
# Skip test files
if echo "$file_path" | grep -qE '\.(test|spec)\.(ts|js)$'; then
  exit 0
fi

# Skip generated files
if echo "$file_path" | grep -qE '(generated|\.gen\.)'; then
  exit 0
fi

検証が遅すぎる

問題: フックの実行に時間がかかりすぎる 解決策: 検証を最適化:
# Cache validation results
CACHE_FILE="/tmp/droid-validation-$(md5sum "$file_path" | cut -d' ' -f1)"

if [ -f "$CACHE_FILE" ]; then
  # File hasn't changed, use cached result
  exit 0
fi

# Run validation...
touch "$CACHE_FILE"

CIとの不整合

問題: フックは通るがCIが失敗する 解決策: 同じツールと設定を使用:
# Use exact same commands as CI
# .github/workflows/ci.yml:
#   npm run lint

# Hook should run:
npm run lint "$file_path"

関連項目