home / skills / aj-geddes / useful-ai-prompts / git-hooks-setup

git-hooks-setup skill

/skills/git-hooks-setup

This skill helps you configure Git hooks with Husky to enforce linting, testing, and commit standards before pushes.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill git-hooks-setup

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
5.9 KB
---
name: git-hooks-setup
description: Implement Git hooks using Husky, pre-commit, and custom scripts. Enforce code quality, linting, and testing before commits and pushes.
---

# Git Hooks Setup

## Overview

Configure Git hooks to enforce code quality standards, run automated checks, and prevent problematic commits from being pushed to shared repositories.

## When to Use

- Pre-commit code quality checks
- Commit message validation
- Preventing secrets in commits
- Running tests before push
- Code formatting enforcement
- Linting configuration
- Team-wide standards enforcement

## Implementation Examples

### 1. **Husky Installation and Configuration**

```bash
#!/bin/bash
# setup-husky.sh

# Install Husky
npm install husky --save-dev

# Initialize Husky
npx husky install

# Create pre-commit hook
npx husky add .husky/pre-commit "npm run lint"

# Create commit-msg hook
npx husky add .husky/commit-msg 'npx --no -- commitlint --edit "$1"'

# Create pre-push hook
npx husky add .husky/pre-push "npm run test"

# Create post-merge hook
npx husky add .husky/post-merge "npm install"
```

### 2. **Pre-commit Hook (Node.js)**

```bash
#!/usr/bin/env node
# .husky/pre-commit

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

console.log('šŸ” Running pre-commit checks...\n');

try {
  // Get staged files
  const stagedFiles = execSync('git diff --cached --name-only', { encoding: 'utf-8' })
    .split('\n')
    .filter(file => file && (file.endsWith('.js') || file.endsWith('.ts')))
    .join(' ');

  if (!stagedFiles) {
    console.log('āœ… No JavaScript/TypeScript files to check');
    process.exit(0);
  }

  // Run linter on staged files
  console.log('šŸ“ Running ESLint...');
  execSync(`npx eslint ${stagedFiles} --fix`, { stdio: 'inherit' });

  // Run Prettier
  console.log('✨ Running Prettier...');
  execSync(`npx prettier --write ${stagedFiles}`, { stdio: 'inherit' });

  // Stage the fixed files
  console.log('šŸ“¦ Staging fixed files...');
  execSync(`git add ${stagedFiles}`);

  console.log('\nāœ… Pre-commit checks passed!');
} catch (error) {
  console.error('āŒ Pre-commit checks failed!');
  process.exit(1);
}
```

### 3. **Commit Message Validation**

```bash
#!/bin/bash
# .husky/commit-msg

# Validate commit message format
COMMIT_MSG=$(<"$1")

# Pattern: type(scope): description
PATTERN="^(feat|fix|docs|style|refactor|test|chore|perf)(\([a-z\-]+\))?: .{1,50}"

if ! [[ $COMMIT_MSG =~ $PATTERN ]]; then
    echo "āŒ Invalid commit message format"
    echo "Format: type(scope): description"
    echo "Types: feat, fix, docs, style, refactor, test, chore, perf"
    echo ""
    echo "Examples:"
    echo "  feat: add new feature"
    echo "  fix(auth): resolve login bug"
    echo "  docs: update README"
    exit 1
fi

# Check message length
FIRST_LINE=$(echo "$COMMIT_MSG" | head -n1)
if [ ${#FIRST_LINE} -gt 72 ]; then
    echo "āŒ Commit message too long (max 72 characters)"
    exit 1
fi

echo "āœ… Commit message is valid"
```

### 4. **Commitlint Configuration**

```javascript
// commitlint.config.js
module.exports = {
  extends: ['@commitlint/config-conventional'],
  rules: {
    'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'style', 'refactor', 'test', 'chore']],
    'subject-case': [2, 'never', ['start-case', 'pascal-case', 'upper-case']],
    'type-empty': [2, 'never']
  }
};
```

### 5. **Pre-push Hook (Comprehensive)**

```bash
#!/usr/bin/env bash
# .husky/pre-push
BRANCH=$(git rev-parse --abbrev-ref HEAD)

# Prevent direct pushes to main
if [[ "$BRANCH" =~ ^(main|master)$ ]]; then
  echo "āŒ Direct push to $BRANCH not allowed"
  exit 1
fi

npm test && npm run lint && npm run build
```

### 6. **Pre-commit Framework (Python)**

```yaml
# .pre-commit-config.yaml
repos:
  - repo: https://github.com/pre-commit/pre-commit-hooks
    rev: v4.4.0
    hooks:
      - id: trailing-whitespace
      - id: end-of-file-fixer
      - id: check-yaml
      - id: check-added-large-files
        args: ['--maxkb=1000']
      - id: detect-private-key
      - id: check-merge-conflict

  - repo: https://github.com/psf/black
    rev: 23.3.0
    hooks:
      - id: black
        language_version: python3.11

  - repo: https://github.com/PyCQA/flake8
    rev: 6.0.0
    hooks:
      - id: flake8
        args: ['--max-line-length=88', '--extend-ignore=E203,W503']

  - repo: https://github.com/PyCQA/isort
    rev: 5.12.0
    hooks:
      - id: isort
        args: ['--profile', 'black']

  - repo: https://github.com/Yelp/detect-secrets
    rev: v1.4.0
    hooks:
      - id: detect-secrets
        args: ['--baseline', '.secrets.baseline']

  - repo: https://github.com/commitizen-tools/commitizen
    rev: 3.5.2
    hooks:
      - id: commitizen
        stages: [commit-msg]
```

### 7. **Secret Detection Hook**

```bash
#!/bin/bash
# .husky/pre-commit-secrets
git diff --cached | grep -E 'password|api_key|secret|token' && exit 1
echo "āœ… No secrets detected"
```

### 8. **Husky in package.json**

```json
{
  "scripts": { "prepare": "husky install" },
  "devDependencies": {
    "husky": "^8.0.0",
    "@commitlint/cli": "^17.0.0"
  },
  "lint-staged": {
    "*.{js,ts}": "eslint --fix"
  }
}
```

## Best Practices

### āœ… DO
- Enforce pre-commit linting and formatting
- Validate commit message format
- Scan for secrets before commit
- Run tests on pre-push
- Skip hooks only with `--no-verify` (rarely)
- Document hook requirements in README
- Use consistent hook configuration
- Make hooks fast (< 5 seconds)
- Provide helpful error messages
- Allow developers to bypass with clear warnings

### āŒ DON'T
- Skip checks with `--no-verify`
- Store secrets in committed files
- Use inconsistent implementations
- Ignore hook errors
- Run full test suite on pre-commit

## Resources

- [Husky Documentation](https://typicode.github.io/husky/)
- [pre-commit Framework](https://pre-commit.com/)
- [Commitlint](https://commitlint.js.org/)
- [lint-staged](https://github.com/okonet/lint-staged)
- [Git Hooks Documentation](https://git-scm.com/docs/githooks)

Overview

This skill implements Git hooks using Husky, pre-commit, and custom scripts to enforce code quality, linting, and testing before commits and pushes. It provides ready-made hook scripts for pre-commit, commit-msg, pre-push, secret scanning, and integration with tools like ESLint, Prettier, Commitlint, and pre-commit. The setup ensures problematic commits are blocked early and developer workflows remain fast and predictable.

How this skill works

The skill installs and initializes Husky, adds hook scripts into the .husky directory, and wires npm scripts or tool commands into those hooks. Pre-commit hooks run linters, formatters, and secret detectors on staged files and automatically stage fixes. Commit-msg hooks validate message format with Commitlint or shell patterns. Pre-push hooks run tests and build steps and can block direct pushes to protected branches.

When to use it

  • Enforce linting and formatting on staged files before commit
  • Validate commit message format for consistent history
  • Prevent secrets or large files from being committed
  • Run unit tests or builds before pushing to remote
  • Enforce team-wide standards across languages and repos

Best practices

  • Keep hooks fast (aim <5s) and run heavy checks on pre-push or CI
  • Run linters and formatters on staged files and auto-stage fixes
  • Validate commit messages with Commitlint or a small regex for clarity
  • Scan for secrets and large files before commit; maintain a baseline for detector tools
  • Document required hooks and how to bypass with --no-verify (only for emergencies)

Example use cases

  • Husky pre-commit that runs ESLint and Prettier on staged .js/.ts files and restages fixes
  • Commit-msg hook that enforces conventional commit types and max 72-char subject line
  • Pre-push hook that blocks direct pushes to main/master and runs tests + build
  • pre-commit-config.yaml for Python projects with Black, Flake8, isort, and detect-secrets hooks
  • Secret-detection script that fails the commit if common secret patterns are found

FAQ

How do developers bypass hooks for emergency commits?

Use git commit or push with --no-verify, but document and reserve this only for urgent fixes after review.

Will hooks slow down commits for large repos?

Keep hooks targeted to staged files and lightweight checks; move full test suites to pre-push or CI to avoid slow commits.