Skip to content

Git Hooks Implementation Plan

For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (- [ ]) syntax for tracking.

Goal: Add lefthook-managed git hooks that block bad commits locally, enforce conventional commit messages, and run tests before push.

Architecture: lefthook manages three hook stages (pre-commit, commit-msg, pre-push) defined in lefthook.yml checked into the repo. commitlint validates commit message format via bunx using the existing bun installation. No new runtimes — lefthook is a Go binary installed via go install.

Tech Stack: lefthook (Go binary), commitlint (@commitlint/cli + @commitlint/config-conventional), bun (already present), gofmt, golangci-lint (already present)


File Map

File Action Responsibility
lefthook.yml Create Hook stage definitions (pre-commit, commit-msg, pre-push)
commitlint.config.ts Create Conventional commit rules
web/package.json Modify Add commitlint devDeps + type-check script
Makefile Modify Add hooks target; add lefthook to tools target
docs/developer-guide.md Modify Add Git Hooks section (what each hook does, install, skip, verify)

Task 1: Commit existing ADR and spec files

Two untracked docs files must be committed before adding hook infrastructure (so the commit-msg hook doesn't reject them later).

Files: - Commit: docs/adr/002-git-hooks-lefthook.md - Commit: docs/superpowers/specs/2026-04-11-git-hooks-design.md

  • [ ] Step 1: Verify the files are present and untracked
rtk git status

Expected output includes:

?? docs/adr/002-git-hooks-lefthook.md
?? docs/superpowers/specs/2026-04-11-git-hooks-design.md

  • [ ] Step 2: Stage and commit the docs
rtk git add docs/adr/002-git-hooks-lefthook.md docs/superpowers/specs/2026-04-11-git-hooks-design.md
rtk git commit -m "docs(adr): add ADR-002 git hooks lefthook and design spec"

Expected: commit succeeds with 2 files changed.


Task 2: Add commitlint packages and type-check script to web/package.json

The pre-commit hook calls bun run type-check. commitlint needs its packages in web/package.json so bunx commitlint resolves from the local install.

Files: - Modify: web/package.json

  • [ ] Step 1: Add devDependencies and script

In web/package.json, add to the "scripts" block:

"type-check": "tsc --noEmit"

Add to "devDependencies":

"@commitlint/cli": "^19.0.0",
"@commitlint/config-conventional": "^19.0.0",
"@commitlint/types": "^19.0.0"

The final "scripts" block:

"scripts": {
  "dev": "vite",
  "build": "tsc && vite build",
  "preview": "vite preview",
  "test": "vitest run",
  "test:watch": "vitest",
  "type-check": "tsc --noEmit"
}

The final "devDependencies" block (add three entries, keep existing ones):

"devDependencies": {
  "@commitlint/cli": "^19.0.0",
  "@commitlint/config-conventional": "^19.0.0",
  "@commitlint/types": "^19.0.0",
  "@testing-library/jest-dom": "^6.9.1",
  "@testing-library/react": "^16.3.2",
  "@testing-library/user-event": "^14.6.1",
  "@types/prismjs": "^1.26.6",
  "@types/react": "^18.3.5",
  "@types/react-dom": "^18.3.0",
  "@vitejs/plugin-react": "^6.0.1",
  "@vitest/coverage-v8": "^4.1.2",
  "autoprefixer": "^10.4.20",
  "jsdom": "^29.0.1",
  "postcss": "^8.4.45",
  "tailwindcss": "^3.4.10",
  "tailwindcss-animate": "^1.0.7",
  "typescript": "^5.5.3",
  "vite": "^8.0.3",
  "vitest": "^4.1.2"
}

  • [ ] Step 2: Install dependencies
cd web && bun install

Expected: bun installs @commitlint/cli, @commitlint/config-conventional, @commitlint/types. Lock file updated.

  • [ ] Step 3: Verify type-check script runs
cd web && bun run type-check

Expected: exits 0, no TypeScript errors.

  • [ ] Step 4: Verify commitlint binary resolves
cd web && bunx commitlint --version

Expected: prints a version string like 19.x.x.

  • [ ] Step 5: Commit
rtk git add web/package.json web/bun.lockb
rtk git commit -m "chore(web): add commitlint deps and type-check script"

Expected: commit succeeds with 2 files changed.


Task 3: Create commitlint.config.ts at repo root

Defines the conventional commit rules that the commit-msg hook enforces.

Files: - Create: commitlint.config.ts

  • [ ] Step 1: Create the file

Create commitlint.config.ts at the repo root with this exact content:

import type { UserConfig } from "@commitlint/types";

const config: UserConfig = {
  extends: ["@commitlint/config-conventional"],
  rules: {
    "subject-max-length": [2, "always", 100],
    "scope-case": [2, "always", "lower-case"],
    "type-enum": [
      2,
      "always",
      ["feat", "fix", "chore", "docs", "refactor", "test", "ci", "build", "perf", "revert"],
    ],
  },
};

export default config;
  • [ ] Step 2: Verify commitlint can parse the config
echo "feat: test message" | bunx --cwd web commitlint --config commitlint.config.ts

Expected: exits 0, no errors.

  • [ ] Step 3: Verify a bad message is rejected
echo "bad commit message" | bunx --cwd web commitlint --config commitlint.config.ts

Expected: exits non-zero, output contains subject may not be empty or type may not be empty.

  • [ ] Step 4: Commit
rtk git add commitlint.config.ts
rtk git commit -m "chore: add commitlint config"

Expected: commit succeeds with 1 file changed.


Task 4: Create lefthook.yml at repo root

Defines all three hook stages. pre-commit runs in parallel; pre-push runs tests with priority ordering to avoid CGO conflicts.

Files: - Create: lefthook.yml

  • [ ] Step 1: Create the file

Create lefthook.yml at the repo root with this exact content:

pre-commit:
  parallel: true
  commands:
    go-fmt:
      run: test -z "$(gofmt -l .)"
      fail_text: "Go files need formatting. Run: make format"
    go-lint:
      run: golangci-lint run
    web-lint:
      run: cd web && bun run type-check

commit-msg:
  commands:
    commitlint:
      run: bunx --cwd web commitlint --edit {1}

pre-push:
  commands:
    go-test:
      run: make test
      priority: 1
    web-test:
      run: make web-test
      priority: 1
    arch-test:
      run: make arch-test
      priority: 2
  • [ ] Step 2: Verify lefthook can parse the config (requires lefthook installed)

If lefthook is already installed:

lefthook version

If not installed yet, skip — Task 5 (Makefile) adds make hooks which installs it.

  • [ ] Step 3: Commit
rtk git add lefthook.yml
rtk git commit -m "chore: add lefthook hook definitions"

Expected: commit succeeds with 1 file changed.


Task 5: Update Makefile — add hooks target and lefthook to tools

Files: - Modify: Makefile

  • [ ] Step 1: Add hooks to .PHONY

Find the .PHONY line in the Makefile:

.PHONY: all check build test lint format run clean help \
        test-coverage test-race vet \
        web-install web-build web-lint web-test \
        e2e-install e2e-test docs-screenshots \
        helm-lint docker-build docker-scan \
        deps tools arch-test web-test-coverage

Replace with:

.PHONY: all check build test lint format run clean help \
        test-coverage test-race vet \
        web-install web-build web-lint web-test \
        e2e-install e2e-test docs-screenshots \
        helm-lint docker-build docker-scan \
        deps tools hooks arch-test web-test-coverage

  • [ ] Step 2: Update the tools target to include lefthook

Find:

## tools: Install development tools (golangci-lint, arch-go)
tools:
    go install github.com/golangci/golangci-lint/v2/cmd/[email protected]
    go install -v github.com/arch-go/arch-go/v2@latest

Replace with:

## tools: Install development tools (golangci-lint, arch-go, lefthook)
tools:
    go install github.com/golangci/golangci-lint/v2/cmd/[email protected]
    go install -v github.com/arch-go/arch-go/v2@latest
    go install github.com/evilmartians/lefthook@latest

  • [ ] Step 3: Add the hooks target after tools

After the tools target, add:

## hooks: Install git hooks via lefthook (run once after cloning)
hooks:
    go install github.com/evilmartians/lefthook@latest
    lefthook install

  • [ ] Step 4: Verify make help lists the new target
make help

Expected output includes:

  hooks   : Install git hooks via lefthook (run once after cloning)
  tools   : Install development tools (golangci-lint, arch-go, lefthook)

  • [ ] Step 5: Commit
rtk git add Makefile
rtk git commit -m "chore: add make hooks target and lefthook to tools"

Expected: commit succeeds with 1 file changed.


Task 6: Update docs/developer-guide.md

Add Git Hooks section covering what each hook does, how to install, how to skip, and how to verify.

Files: - Modify: docs/developer-guide.md

  • [ ] Step 1: Add Git Hooks to the Table of Contents

Find the Table of Contents block and add - [Git Hooks](#git-hooks) after - [Linting](#linting):

- [Linting](#linting)
- [Git Hooks](#git-hooks)
- [Writing Plugins](#writing-plugins)
  • [ ] Step 2: Add Git Hooks section after the Linting section

Find the ## Writing Plugins heading and insert the following section before it:

## Git Hooks

AgentLens uses [lefthook](https://github.com/evilmartians/lefthook) to manage git hooks checked into the repo. Hooks provide a local quality gate that catches issues before they reach CI.

### What each hook does

| Hook | When | Jobs |
|------|------|------|
| **pre-commit** | On every `git commit` | `gofmt` check (parallel), `golangci-lint` (parallel), TypeScript type-check (parallel) |
| **commit-msg** | After pre-commit | commitlint validates conventional commit format |
| **pre-push** | On `git push` | `make test` + `make web-test` (parallel), then `make arch-test` |

### Install hooks (once per clone)

```bash
make hooks

Run this once after cloning. It installs the lefthook binary and activates all hooks. It is not run automatically by make all to avoid surprising contributors.

Verify hooks are installed

lefthook list

Expected output lists pre-commit, commit-msg, and pre-push.

Commit message format

Hooks enforce Conventional Commits:

<type>[(scope)]: <subject>

Allowed types: feat, fix, chore, docs, refactor, test, ci, build, perf, revert

Examples:

feat: add kubernetes source plugin
feat(api): expose capabilities endpoint
fix(auth): correct lockout timer reset
chore: bump go version to 1.26.1

Skipping hooks

git commit --no-verify -m "wip: ..."   # skip pre-commit + commit-msg
git push --no-verify                    # skip pre-push
LEFTHOOK=0 git commit ...              # disable lefthook entirely

- [ ] **Step 3: Update Makefile targets table**

Find the table row for `make tools` and update it:

From:
```markdown
| `make tools` | Install golangci-lint |

To:

| `make tools` | Install golangci-lint, arch-go, lefthook |
| `make hooks` | Install git hooks via lefthook (run once after cloning) |

  • [ ] Step 4: Commit
rtk git add docs/developer-guide.md
rtk git commit -m "docs: add git hooks section to developer guide"

Expected: commit succeeds with 1 file changed.


Task 7: Activate and verify end-to-end

Run make hooks and verify all three hook stages fire correctly.

Files: none (verification only)

  • [ ] Step 1: Install hooks
make hooks

Expected: lefthook installs, output includes Lefthook configuration was installed. No errors.

  • [ ] Step 2: Verify hooks are listed
lefthook list

Expected output:

pre-commit
commit-msg
pre-push

  • [ ] Step 3: Verify pre-commit fires on a staged change

Create a temp file, stage it, and attempt a commit:

echo "package main" > /tmp/test_hooks_temp.go
git add /tmp/test_hooks_temp.go 2>/dev/null || true
# Stage an actual change in the repo to trigger pre-commit
touch /tmp/hook_test && git status

Or simply verify the hook files exist in .git/hooks/:

ls -la .git/hooks/pre-commit .git/hooks/commit-msg .git/hooks/pre-push

Expected: all three files exist and are executable.

  • [ ] Step 4: Test commit-msg hook rejects bad message
git stash -u 2>/dev/null || true  # ensure clean state
# Create a trivial change to enable a commit attempt
echo "" >> .gitignore
git add .gitignore
git commit -m "bad commit message"

Expected: commit is blocked with an error from commitlint about invalid format.

Clean up:

git restore .gitignore

  • [ ] Step 5: Test commit-msg hook accepts good message
echo "" >> .gitignore
git add .gitignore
git commit -m "chore: verify commitlint hook works"

Expected: commit succeeds (pre-commit and commit-msg both pass).

If this was just a test commit, undo it:

git reset HEAD~1
git restore .gitignore


Self-Review

Spec coverage check

Spec requirement Covered by task
lefthook.yml with pre-commit (gofmt, golangci-lint, web type-check, parallel) Task 4
commit-msg via commitlint Tasks 3 + 4
pre-push (go-test + web-test parallel, arch-test sequential) Task 4
make hooks target Task 5
lefthook added to make tools Task 5
@commitlint/cli + @commitlint/config-conventional devDeps Task 2
commitlint.config.ts at root with allowed types Task 3
type-check script in web/package.json Task 2
docs/developer-guide.md Git Hooks section Task 6
Commit existing ADR-002 + spec Task 1

All spec requirements covered. ✓