# Git Hooks

This directory contains git hooks that are tracked in version control and synced across projects.

## Why Track Git Hooks?

Git hooks in `.git/hooks/` are not tracked by git, so they don't sync across machines or projects. We solve this by:

1. Storing hooks in `.claude/git-hooks/` (tracked in version control)
2. Using **Lefthook** for hook management (recommended)
3. Or using `persuade sync` for automatic syncing to `.git/hooks/`

## ✨ Recommended: Lefthook

[Lefthook](https://github.com/evilmartians/lefthook) is a fast, modern Git hooks manager. It provides:
- Declarative YAML configuration
- Parallel hook execution
- Cross-platform support
- Local overrides via `lefthook-local.yml`

### Installation

**Recommended (via dev deps):**
```bash
# Creates venv, installs lefthook + other dev deps, and sets up hooks
just setup

# Or manually:
pip install -e ".[dev]"  # lefthook is included in dev dependencies
lefthook install
```

**Alternative (standalone lefthook):**
```bash
# Install lefthook system-wide (choose one)
brew install lefthook      # macOS/Linux (Homebrew)
npm install -g lefthook    # Node.js
go install github.com/evilmartians/lefthook@latest  # Go

# Then install hooks
lefthook install
```

### Configuration

Hooks are configured in `lefthook.yml` at the project root:

```yaml
pre-push:
  commands:
    protect-main:
      run: bash .claude/git-hooks/pre-push

commit-msg:
  commands:
    atomic-guidance:
      run: bash .claude/git-hooks/commit-msg {1}
```

### How It Works

- `lefthook install` creates lightweight shims in `.git/hooks/`
- When a Git hook fires, lefthook runs the configured commands
- The existing shell scripts in `.claude/git-hooks/` are reused

## Alternative: Sync Engine

The sync engine provides automatic hook installation without lefthook:

```python
"git-hooks/*": {
    "category": FileCategory.SYNC_ALWAYS,
    "destination": ".git/hooks/{filename}",
    "exclude": ["*.md", "README*"],  # Don't sync docs to .git/hooks/
}
```

This means:
- `git-hooks/pre-push` → `.git/hooks/pre-push` ✅
- `git-hooks/pre-commit` → `.git/hooks/pre-commit` ✅
- `git-hooks/commit-msg` → `.git/hooks/commit-msg` ✅
- `git-hooks/README.md` → `.claude/git-hooks/README.md` ✅ (stays in .claude/)

## Available Hooks

### `pre-push`

Prevents direct pushes to main/master branches.

**What it does:**
- Checks current branch before push
- Blocks if on main/master
- Shows error with correct workflow
- Exit code 1 = blocked, 0 = allowed

**Why it exists:**
- Enforces "ALWAYS use pull requests" policy
- Prevents accidental pushes to protected branches
- Works for both humans and AI assistants

**Bypass (not recommended):**
```bash
git push --no-verify
```

### `commit-msg`

Provides guidance on atomic commits (non-blocking).

**What it does:**
- Checks for "and" in commit messages (suggests non-atomic commit)
- Warns if many files changed (>10 files)
- Warns if many lines changed (>500 lines)
- **Does NOT block** - only provides educational warnings
- Exit code 0 = always allowed

**Why it exists:**
- Encourages atomic commit best practices
- Reminds developers to split large commits
- Educational rather than enforcement
- Works for both humans and AI assistants

**What triggers warnings:**
```bash
# Warning: "and" linking multiple changes
git commit -m "feat: add login and fix validation"

# Warning: Large commit
git add .  # Many files staged
git commit -m "feat: major refactor"
```

**Example output:**
```
Atomic Commit Warning: Message contains "and" linking multiple changes

  Your message: feat: add login form and fix validation error

  Consider splitting into separate commits:
  - One commit per logical change
  - Each commit should do ONE thing

  Tip: If you used "and", it's usually two commits.

────────────────────────────────────────────────────────────────
This is guidance only - commit will proceed.
See: .claude/knowledge/git/atomic-commits.md
────────────────────────────────────────────────────────────────
```

**Bypass (if you know what you're doing):**
```bash
git commit --no-verify -m "feat: intentionally large commit"
```

## Installation

### Recommended: Lefthook

```bash
# Full setup (creates venv, installs dev deps including lefthook, sets up hooks)
just setup

# Or if you already have a venv:
pip install -e ".[dev]"  # lefthook is included in dev dependencies
lefthook install
```

Or install hooks only:
```bash
just hooks  # Runs lefthook install
```

### Alternative: Persuade CLI

Run the persuade sync command to copy hooks directly:

```bash
pip install persuade
persuade sync .
```

The sync will:
- ✅ Copy all hooks to `.git/hooks/`
- ✅ Make them executable
- ✅ Preserve permissions
- ✅ Keep documentation in `.claude/git-hooks/`

### Manual (Legacy - Deprecated)

**Note:** The manual installation script is deprecated:

```bash
bash .claude/scripts/install-git-hooks.sh
```

**Recommendation:** Use lefthook instead - it's the standard approach across projects.

## Adding New Hooks

1. Create the hook file in `.claude/git-hooks/`
   - Use standard git hook names (pre-commit, pre-push, etc.)
   - Make it executable: `chmod +x .claude/git-hooks/pre-commit`
   - Add clear error messages and documentation

2. Test locally using the sync tool:
   ```bash
   persuade sync .
   # Try to trigger the hook
   git commit --allow-empty -m "test"
   # or
   git push --dry-run
   ```

3. Commit to version control:
   ```bash
   git add .claude/git-hooks/pre-commit
   git commit -m "feat: add pre-commit hook for X"
   ```

4. The hook will automatically sync to other projects when they run the sync tool

## Common Hooks

- **pre-commit**: Runs before commit (e.g., linting, tests)
- **pre-push**: Runs before push (e.g., block protected branches)
- **commit-msg**: Validates commit message format
- **prepare-commit-msg**: Modifies commit message template

## Sync Configuration

In `persuade/constants.py`:

```python
FILE_RULES = {
    # Git hooks with destination mapping - automatically installed to .git/hooks/
    "git-hooks/*": {
        "category": FileCategory.SYNC_ALWAYS,
        "destination": ".git/hooks/{filename}",
        "exclude": ["*.md", "README*"],  # Keep documentation in .claude/
    },
}

REQUIRED_DIRECTORIES = [
    "git-hooks",  # Create directory in new projects
]

EXECUTABLE_PATTERNS = [
    "git-hooks/*",  # Make all hooks executable
]
```

## Troubleshooting

### Hook not running

1. Check if installed:
   ```bash
   ls -la .git/hooks/pre-push
   ```

2. If not present, run sync:
   ```bash
   persuade sync .
   ```

3. Check if executable:
   ```bash
   ls -la .git/hooks/pre-push  # Should show -rwxr-xr-x
   ```

4. If not executable, the sync tool should have made it executable automatically. If it's still not working, check the file permissions in the source:
   ```bash
   ls -la .claude/git-hooks/pre-push
   chmod +x .claude/git-hooks/pre-push  # Make it executable
   git add .claude/git-hooks/pre-push
   git commit -m "fix: make hook executable"
   ```

### Want to bypass a hook

Use `--no-verify` (not recommended):
```bash
git commit --no-verify
git push --no-verify
```

### Hook blocking legitimate operations

Review the hook logic in `.claude/git-hooks/<hook-name>` and update if needed.

## Related Documentation

- **Git Hooks**: https://git-scm.com/docs/githooks
- **Claude Code Rules**: `.claude/standards/CLAUDE_CODE_RULES.md`
- **Persuade CLI**: `persuade --help`
