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
hooksto .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. ✓