AgentSkillsCN

Pre Commit Hooks

预提交钩子

SKILL.md

Pre-Commit Hooks

Skill Purpose: Establish code quality enforcement patterns before commits


Core Skill Pattern

Objective: Set up automated quality checks that run before commits to ensure code standards.

Universal Pattern:

  1. Install hook management tools
  2. Configure staged file processing
  3. Define quality check rules
  4. Set up commit message standards
  5. Test hook functionality

Key Decisions (Project-Specific):

  • Which tools to use (husky vs alternatives)
  • What file types to process
  • Which quality checks to run
  • Commit message format requirements

Project-Specific Implementation Notes

Customize per project:

  • Tool selection based on team preferences
  • File type rules based on project languages
  • Quality gate strictness based on project maturity
  • Integration with existing CI/CD pipeline

Example Implementation (Next.js/Husky Pattern)

Note: This is an example pattern. Adapt tools and configurations based on your specific project requirements.

Prerequisites (Example)

  • Git repository initialized
  • Package management available
  • Code quality tools configured

Example: Next.js/Husky Implementation Steps

Framework-Specific Example: This demonstrates the pattern using Next.js and husky. Adapt for your tech stack.

1. Install Husky and lint-staged

bash
# Install husky and lint-staged
npm install -D husky lint-staged

# Install additional hook tools
npm install -D @commitlint/cli @commitlint/config-conventional commitizen cz-conventional-changelog

# Install file change detection
npm install -D onchange

2. Initialize Husky

bash
# Initialize husky
npm pkg set scripts.prepare="husky install"
npm run prepare

# Create husky directory
mkdir -p .husky

3. Configure lint-staged

Create .lintstagedrc.json:

json
{
  "*.{js,jsx,ts,tsx}": [
    "eslint --fix",
    "prettier --write",
    "git add"
  ],
  "*.{json,md,yml,yaml}": [
    "prettier --write",
    "git add"
  ],
  "*.{css,scss,less}": [
    "prettier --write",
    "git add"
  ],
  "*.{html,xml}": [
    "prettier --write",
    "git add"
  ],
  "package.json": [
    "prettier --write",
    "git add"
  ],
  "*.md": [
    "prettier --write",
    "markdownlint --fix",
    "git add"
  ]
}

Create .lintstagedrc.js (alternative JavaScript config):

javascript
module.exports = {
  '*.{js,jsx,ts,tsx}': [
    (filenames) => {
      // Run ESLint only on changed files
      return `eslint --fix ${filenames.map(f => `"${f}"`).join(' ')}`;
    },
    (filenames) => {
      // Run Prettier only on changed files
      return `prettier --write ${filenames.map(f => `"${f}"`).join(' ')}`;
    },
  ],
  '*.{json,md,yml,yaml}': [
    'prettier --write',
  ],
  '*.{css,scss,less}': [
    'prettier --write',
  ],
  '*.md': [
    'prettier --write',
    'markdownlint --fix',
  ],
  // Custom patterns
  'src/**/*.{ts,tsx}': [
    'npm run type-check -- --noEmit',
  ],
  // Ignore certain files
  '!**/node_modules/**',
  '!**/dist/**',
  '!**/build/**',
  '!**/.next/**',
  '!**/coverage/**',
};

4. Create Pre-Commit Hook

Create .husky/pre-commit:

bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "🔒 Running pre-commit hooks..."

# Run lint-staged
npx lint-staged

# Check for console.log statements
echo "🔍 Checking for console.log statements..."
if git diff --cached --name-only | xargs grep -l "console\.log\|console\.warn" 2>/dev/null; then
  echo "❌ Found console.log or console.warn statements in staged files."
  echo "   Please remove them before committing."
  echo ""
  echo "Files with console statements:"
  git diff --cached --name-only | xargs grep -l "console\.log\|console\.warn" 2>/dev/null
  exit 1
fi

# Check for TODO/FIXME comments
echo "🔍 Checking for TODO/FIXME comments..."
if git diff --cached --name-only | xargs grep -l "TODO\|FIXME\|HACK\|XXX" 2>/dev/null; then
  echo "⚠️  Found TODO/FIXME/HACK/XXX comments in staged files."
  echo "   Please review them before committing."
  echo ""
  echo "Files with TODO comments:"
  git diff --cached --name-only | xargs grep -l "TODO\|FIXME\|HACK\|XXX" 2>/dev/null
  echo ""
  echo "Continue anyway? (y/n)"
  read -r response
  if [ "$response" != "y" ]; then
    exit 1
  fi
fi

# Check file sizes
echo "🔍 Checking file sizes..."
MAX_FILE_SIZE=1048576  # 1MB
large_files=$(git diff --cached --name-only | xargs du -b 2>/dev/null | awk -v max="$MAX_FILE_SIZE" '$1 > max { print $2 }')

if [ -n "$large_files" ]; then
  echo "⚠️  Found large files in staged changes:"
  echo "$large_files" | while read -r file; do
    size=$(du -h "$file" | cut -f1)
    echo "   $file ($size)"
  done
  echo ""
  echo "Consider removing large files from commits."
fi

# Run type checking on TypeScript files
echo "🔍 Running TypeScript type checking..."
ts_files=$(git diff --cached --name-only | grep -E '\.(ts|tsx)$')
if [ -n "$ts_files" ]; then
  if ! npm run type-check -- --noEmit; then
    echo "❌ TypeScript type checking failed."
    exit 1
  fi
fi

# Check for missing imports
echo "🔍 Checking for missing imports..."
if git diff --cached --name-only | grep -E '\.(js|jsx|ts|tsx)$' | xargs grep -l "import.*from.*['\"]\s*$" 2>/dev/null; then
  echo "⚠️  Some files may have incomplete imports. Please review."
fi

# Validate package.json if changed
if git diff --cached --name-only | grep -q "package.json"; then
  echo "🔍 Validating package.json..."
  if ! npm run validate-package; then
    echo "❌ package.json validation failed."
    exit 1
  fi
fi

echo "✅ Pre-commit hooks passed!"

5. Create Pre-Push Hook

Create .husky/pre-push:

bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "🚀 Running pre-push hooks..."

# Run full test suite
echo "🧪 Running test suite..."
if ! npm run test:ci; then
  echo "❌ Tests failed."
  exit 1
fi

# Run build
echo "🏗️  Running build..."
if ! npm run build; then
  echo "❌ Build failed."
  exit 1
fi

# Run security audit
echo "🔒 Running security audit..."
if npm audit --audit-level moderate; then
  echo "✅ Security audit passed."
else
  echo "⚠️  Security audit found issues."
  echo "   Consider running 'npm audit fix'"
  echo ""
  echo "Continue anyway? (y/n)"
  read -r response
  if [ "$response" != "y" ]; then
    exit 1
  fi
fi

# Check for uncommitted changes
echo "🔍 Checking for uncommitted changes..."
if [ -n "$(git status --porcelain)" ]; then
  echo "⚠️  You have uncommitted changes."
  echo "   Consider committing them before pushing."
  echo ""
  echo "Uncommitted files:"
  git status --porcelain
  echo ""
  echo "Continue anyway? (y/n)"
  read -r response
  if [ "$response" != "y" ]; then
    exit 1
  fi
fi

# Check if current branch is up to date
echo "🔍 Checking if branch is up to date..."
current_branch=$(git branch --show-current)
if git fetch origin "$current_branch" 2>/dev/null; then
  if [ "$(git rev-parse HEAD)" != "$(git rev-parse origin/$current_branch)" ]; then
    echo "⚠️  Your branch is not up to date with origin/$current_branch."
    echo "   Consider pulling latest changes before pushing."
    echo ""
    echo "Continue anyway? (y/n)"
    read -r response
    if [ "$response" != "y" ]; then
      exit 1
    fi
  fi
fi

echo "✅ Pre-push hooks passed!"

6. Create Commit Message Hook

Create .husky/commit-msg:

bash
#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "📝 Validating commit message..."

# Check commit message format
commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|breaking|ci|build|revert)(\(.+\))?: .{1,50}'

if ! grep -qE "$commit_regex" "$1"; then
  echo "❌ Invalid commit message format!"
  echo ""
  echo "Commit message must follow the Conventional Commits format:"
  echo "  type(scope): description"
  echo ""
  echo "Types: feat, fix, docs, style, refactor, test, chore, perf, breaking, ci, build, revert"
  echo "Scope: optional, in parentheses"
  echo "Description: imperative mood, no period, max 50 characters"
  echo ""
  echo "Examples:"
  echo "  feat(auth): add user authentication"
  echo "  fix(api): resolve null reference error"
  echo "  docs: update API documentation"
  echo "  refactor(components): extract common button logic"
  echo ""
  echo "Please rewrite your commit message and try again."
  exit 1
fi

# Check message length
message_length=$(wc -m < "$1")
if [ "$message_length" -gt 72 ]; then
  echo "⚠️  Commit message is longer than 72 characters."
  echo "   Consider shortening it for better readability."
  echo ""
  echo "Continue anyway? (y/n)"
  read -r response
  if [ "$response" != "y" ]; then
    exit 1
  fi
fi

# Check for proper capitalization
if grep -qE "^[a-z]" "$1"; then
  echo "✅ Commit message starts with lowercase (conventional)"
elif grep -qE "^[A-Z]" "$1"; then
  echo "⚠️  Commit message starts with uppercase."
  echo "   Conventional commits typically start with lowercase."
  echo ""
  echo "Continue anyway? (y/n)"
  read -r response
  if [ "$response" != "y" ]; then
    exit 1
  fi
fi

echo "✅ Commit message validation passed!"

7. Configure Commitlint

Create commitlint.config.js:

javascript
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    // Type rules
    'type-enum': [
      2,
      'always',
      [
        'feat',     // New feature
        'fix',      // Bug fix
        'docs',     // Documentation
        'style',    // Code style (formatting, etc.)
        'refactor', // Refactoring
        'test',     // Tests
        'chore',    // Maintenance tasks
        'perf',     // Performance improvements
        'breaking', // Breaking changes
        'ci',       // CI/CD changes
        'build',    // Build system changes
        'revert',   // Revert previous commit
      ],
    ],
    'type-empty': [2, 'never'],
    'type-case': [2, 'always', 'lower-case'],
    
    // Scope rules
    'scope-empty': [0, 'never'], // Allow empty scope
    'scope-case': [2, 'always', 'lower-case'],
    'scope-enum': [
      0,
      'always',
      [
        'auth',      // Authentication
        'api',       // API
        'ui',        // User interface
        'db',        // Database
        'config',    // Configuration
        'deps',      // Dependencies
        'docs',      // Documentation
        'tests',     // Tests
        'utils',     // Utilities
        'types',     // TypeScript types
        'hooks',     // React hooks
        'components', // React components
        'pages',     // Next.js pages
        'layouts',   // Layouts
        'styles',    // Styles
        'scripts',   // Scripts
        'build',     // Build
        'deploy',    // Deployment
      ],
    ],
    
    // Subject rules
    'subject-empty': [2, 'never'],
    'subject-case': [2, 'never', ['lower-case', 'sentence-case']],
    'subject-full-stop': [2, 'never', '.'],
    'subject-max-length': [2, 'always', 50],
    
    // Body rules
    'body-leading-blank': [1, 'always'],
    'body-max-line-length': [2, 'always', 72],
    
    // Footer rules
    'footer-leading-blank': [1, 'always'],
    'footer-max-line-length': [2, 'always', 72],
    
    // General rules
    'header-max-length': [2, 'always', 72],
  },
};

8. Configure Commitizen

Create .czrc:

json
{
  "path": "cz-conventional-changelog",
  "types": {
    "feat": {
      "description": "New feature",
      "title": "Features"
    },
    "fix": {
      "description": "Bug fix",
      "title": "Bug Fixes"
    },
    "docs": {
      "description": "Documentation",
      "title": "Documentation"
    },
    "style": {
      "description": "Code style (formatting, etc.)",
      "title": "Styles"
    },
    "refactor": {
      "description": "Refactoring",
      "title": "Code Refactoring"
    },
    "test": {
      "description": "Tests",
      "title": "Tests"
    },
    "chore": {
      "description": "Maintenance tasks",
      "title": "Chores"
    },
    "perf": {
      "description": "Performance improvements",
      "title": "Performance Improvements"
    },
    "breaking": {
      "description": "Breaking changes",
      "title": "Breaking Changes"
    },
    "ci": {
      "description": "CI/CD changes",
      "title": "Continuous Integration"
    },
    "build": {
      "description": "Build system changes",
      "title": "Build System"
    },
    "revert": {
      "description": "Revert previous commit",
      "title": "Reverts"
    }
  },
  "scopes": {
    "auth": "Authentication",
    "api": "API",
    "ui": "User Interface",
    "db": "Database",
    "config": "Configuration",
    "deps": "Dependencies",
    "docs": "Documentation",
    "tests": "Tests",
    "utils": "Utilities",
    "types": "TypeScript Types",
    "hooks": "React Hooks",
    "components": "React Components",
    "pages": "Next.js Pages",
    "layouts": "Layouts",
    "styles": "Styles",
    "scripts": "Scripts",
    "build": "Build",
    "deploy": "Deployment"
  }
}

9. Create Custom Hook Scripts

Create scripts/setup-hooks.js:

javascript
#!/usr/bin/env node

const fs = require('fs');
const path = require('path');
const { execSync } = require('child_process');

// ANSI color codes
const colors = {
  reset: '\x1b[0m',
  red: '\x1b[31m',
  green: '\x1b[32m',
  yellow: '\x1b[33m',
  blue: '\x1b[34m',
  cyan: '\x1b[36m',
  magenta: '\x1b[35m',
};

function colorLog(message, color = 'reset') {
  console.log(`${colors[color]}${message}${colors.reset}`);
}

function createHookFile(hookName, content) {
  const hookPath = path.join(process.cwd(), '.husky', hookName);
  
  try {
    fs.writeFileSync(hookPath, content, { mode: 0o755 });
    colorLog(`✅ Created ${hookName}`, 'green');
    return true;
  } catch (error) {
    colorLog(`❌ Failed to create ${hookName}`, 'red');
    colorLog(error.message, 'red');
    return false;
  }
}

function setupHusky() {
  colorLog('🔧 Setting up Husky hooks', 'magenta');
  colorLog('=========================', 'magenta');
  
  // Ensure .husky directory exists
  const huskyDir = path.join(process.cwd(), '.husky');
  if (!fs.existsSync(huskyDir)) {
    fs.mkdirSync(huskyDir, { recursive: true });
    colorLog('✅ Created .husky directory', 'green');
  }
  
  // Create hooks
  const hooks = [
    {
      name: 'pre-commit',
      content: `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "🔒 Running pre-commit hooks..."

# Run lint-staged
npx lint-staged

# Check for console.log statements
echo "🔍 Checking for console.log statements..."
if git diff --cached --name-only | xargs grep -l "console\\.log\\|console\\.warn" 2>/dev/null; then
  echo "❌ Found console.log or console.warn statements in staged files."
  echo "   Please remove them before committing."
  exit 1
fi

echo "✅ Pre-commit hooks passed!"
`,
    },
    {
      name: 'pre-push',
      content: `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "🚀 Running pre-push hooks..."

# Run test suite
echo "🧪 Running test suite..."
if ! npm run test:ci; then
  echo "❌ Tests failed."
  exit 1
fi

# Run build
echo "🏗️  Running build..."
if ! npm run build; then
  echo "❌ Build failed."
  exit 1
fi

echo "✅ Pre-push hooks passed!"
`,
    },
    {
      name: 'commit-msg',
      content: `#!/usr/bin/env sh
. "$(dirname -- "$0")/_/husky.sh"

echo "📝 Validating commit message..."

commit_regex='^(feat|fix|docs|style|refactor|test|chore|perf|breaking|ci|build|revert)(\\(.+\\))?: .{1,50}'

if ! grep -qE "$commit_regex" "$1"; then
  echo "❌ Invalid commit message format!"
  echo "Commit message must follow: type(scope): description"
  exit 1
fi

echo "✅ Commit message validation passed!"
`,
    },
  ];
  
  let successCount = 0;
  
  for (const hook of hooks) {
    if (createHookFile(hook.name, hook.content)) {
      successCount++;
    }
  }
  
  colorLog(`\n📊 Setup Summary: ${successCount}/${hooks.length} hooks created`, 
    successCount === hooks.length ? 'green' : 'yellow');
  
  return successCount === hooks.length;
}

function installDependencies() {
  colorLog('\n📦 Installing dependencies...', 'blue');
  
  try {
    execSync('npm install --save-dev husky lint-staged @commitlint/cli @commitlint/config-conventional', { stdio: 'inherit' });
    colorLog('✅ Dependencies installed', 'green');
    return true;
  } catch (error) {
    colorLog('❌ Failed to install dependencies', 'red');
    return false;
  }
}

function configurePackageJson() {
  colorLog('\n📝 Configuring package.json...', 'blue');
  
  try {
    // Read package.json
    const packageJsonPath = path.join(process.cwd(), 'package.json');
    const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
    
    // Add scripts
    packageJson.scripts = {
      ...packageJson.scripts,
      'prepare': 'husky install',
      'commit': 'git-cz',
    };
    
    // Add lint-staged config
    packageJson['lint-staged'] = {
      '*.{js,jsx,ts,tsx}': [
        'eslint --fix',
        'prettier --write',
      ],
      '*.{json,md,yml,yaml}': [
        'prettier --write',
      ],
    };
    
    // Write back package.json
    fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2));
    
    colorLog('✅ package.json configured', 'green');
    return true;
  } catch (error) {
    colorLog('❌ Failed to configure package.json', 'red');
    return false;
  }
}

function main() {
  colorLog('🚀 Setting up Git hooks for Zeus framework', 'magenta');
  colorLog('======================================', 'magenta');
  
  let allSuccess = true;
  
  // Install dependencies
  if (!installDependencies()) {
    allSuccess = false;
  }
  
  // Configure package.json
  if (!configurePackageJson()) {
    allSuccess = false;
  }
  
  // Setup Husky hooks
  if (!setupHusky()) {
    allSuccess = false;
  }
  
  // Initialize husky
  try {
    execSync('npm run prepare', { stdio: 'inherit' });
    colorLog('✅ Husky initialized', 'green');
  } catch (error) {
    colorLog('❌ Failed to initialize Husky', 'red');
    allSuccess = false;
  }
  
  colorLog('\n📊 Setup Summary', 'magenta');
  colorLog('================', 'magenta');
  
  if (allSuccess) {
    colorLog('✅ Git hooks setup completed successfully!', 'green');
    colorLog('\nNext steps:', 'cyan');
    colorLog('1. Commit your changes to include the hooks', 'cyan');
    colorLog('2. Try making a commit to test the hooks', 'cyan');
    colorLog('3. Use "npm run commit" for guided commits', 'cyan');
  } else {
    colorLog('❌ Some setup steps failed. Please check the errors above.', 'red');
  }
}

if (require.main === module) {
  main();
}

module.exports = { setupHusky, installDependencies, configurePackageJson };

10. Update Package.json Scripts

Update package.json scripts:

json
{
  "scripts": {
    "prepare": "husky install",
    "commit": "git-cz",
    "hooks:setup": "node scripts/setup-hooks.js",
    "hooks:test": "npx lint-staged --debug",
    "hooks:uninstall": "npm uninstall husky"
  }
}

Code Examples

Using Commitizen for Guided Commits

bash
# Interactive commit creation
npm run commit

# Or directly
git-cz

Manual Hook Testing

bash
# Test pre-commit hooks
git add .
git commit -m "feat: test commit"

# Test pre-push hooks
git push origin main

# Test commit message validation
git commit -m "invalid commit message"

Custom Hook Configuration

javascript
// .lintstagedrc.js - Advanced configuration
module.exports = {
  '*.{js,jsx,ts,tsx}': [
    // Run ESLint with specific rules for staged files
    (filenames) => `eslint --fix --max-warnings 0 ${filenames.join(' ')}`,
    
    // Run Prettier
    'prettier --write',
    
    // Run TypeScript check only on TypeScript files
    (filenames) => {
      const tsFiles = filenames.filter(f => f.match(/\.(ts|tsx)$/));
      if (tsFiles.length > 0) {
        return `npx tsc --noEmit ${tsFiles.join(' ')}`;
      }
    },
  ],
  
  // Parallel execution for different file types
  '*.{json,md,yml,yaml}': ['prettier --write'],
  '*.{css,scss,less}': ['prettier --write'],
  
  // Custom commands
  'package.json': [
    'npm run validate-package',
    'prettier --write',
  ],
  
  // Ignore patterns
  '!**/node_modules/**',
  '!**/dist/**',
  '!**/.next/**',
};

Hook Debugging

bash
# Debug lint-staged
npx lint-staged --debug

# Test specific files
npx lint-staged --verbose src/components/Button.tsx

# Bypass hooks (not recommended)
git commit --no-verify -m "feat: bypass hooks"

Configuration Templates

Complete .lintstagedrc.json

json
{
  "*.{js,jsx,ts,tsx}": [
    "eslint --fix",
    "prettier --write"
  ],
  "*.{json,md,yml,yaml}": [
    "prettier --write"
  ],
  "*.{css,scss,less}": [
    "prettier --write"
  ],
  "*.md": [
    "prettier --write",
    "markdownlint --fix"
  ],
  "package.json": [
    "prettier --write",
    "npm run validate-package"
  ],
  "src/**/*.{ts,tsx}": [
    "npm run type-check -- --noEmit"
  ]
}

Complete commitlint.config.js

javascript
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [
      2,
      'always',
      ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore', 'perf', 'breaking', 'ci', 'build', 'revert'],
    ],
    'type-empty': [2, 'never'],
    'type-case': [2, 'always', 'lower-case'],
    'scope-empty': [0, 'never'],
    'scope-case': [2, 'always', 'lower-case'],
    'subject-empty': [2, 'never'],
    'subject-case': [2, 'never', ['lower-case', 'sentence-case']],
    'subject-full-stop': [2, 'never', '.'],
    'subject-max-length': [2, 'always', 50],
    'header-max-length': [2, 'always', 72],
  },
};

Best Practices

  1. Use conventional commits - Standardized message format
  2. Configure proper file patterns - Efficient lint-staged execution
  3. Use commitizen - Guided commit creation
  4. Test hooks before deployment - Ensure they work correctly
  5. Use appropriate validation levels - Balance speed and quality
  6. Document hook behavior - Team understanding
  7. Use bypass sparingly --no-verify only when necessary
  8. Monitor hook performance - Don't slow down development

Stop Conditions

STOP and report if:

  • Husky installation fails
  • Hook scripts don't execute
  • lint-staged configuration errors
  • Commit message validation fails
  • Pre-push hooks break workflow

Expected Outcomes:

  • Husky installed and configured
  • All hooks functional
  • lint-staged working correctly
  • Commit messages validated
  • Code quality enforced

Verification Checklist

  • Husky installed successfully
  • All hook scripts created and executable
  • lint-staged configured correctly
  • Commitlint configuration working
  • Commitizen configured
  • Pre-commit hooks functional
  • Pre-push hooks working
  • Commit message validation active
  • Package.json scripts updated
  • Hook setup script functional

Version: 1.0.0 Last Updated: 2026-01-31 Skill Category: Architecture - CI