Git hooks
Catch spec drift at commit time, before CI. Each recipe runs agnostic-ai sync --check whenever a spec or agnostic-ai.yaml is staged, blocking the commit if any generated file is out of date.
The same sync --check powers the CI gate. Running it locally shortens the feedback loop from "push, wait, fail" to "commit, fix, commit".
For ignored outputs, run sync when bootstrapping each checkout before enabling a drift hook. In CI, use the ignored-output recipe. A local drift check compares your working tree, not only staged files.
Why a pre-commit hook
- Drift surfaces when you create it, not 30 seconds into the next CI run.
- Same gate on every contributor and machine.
- Opt-in per checkout, so a fresh clone still works without setup.
pre-commit (Python)
pre-commit is the common choice in polyglot repos. Add .pre-commit-config.yaml:
repos:
- repo: local
hooks:
- id: agnostic-ai-check
name: agnostic-ai sync --check
entry: agnostic-ai sync --check
language: system
pass_filenames: false
files: '^(\.agnostic-ai/|agnostic-ai\.yaml$|agnostic\.config\.yaml$)'
Install once per checkout:
pre-commit install
files: scopes the hook to spec changes; unrelated commits skip the check. pass_filenames: false runs the binary on the whole project (like CI) instead of passing each staged path.
lefthook
lefthook is a single Go binary, no runtime dependency. This repo dogfoods it; see lefthook.yml.
Add to lefthook.yml:
pre-commit:
commands:
agnostic-ai-check:
glob: "{.agnostic-ai/**,agnostic-ai.yaml}"
run: agnostic-ai sync --check
Install once per checkout:
lefthook install
glob: keeps the hook silent unless a spec or the config is staged.
husky + lint-staged
In Node projects, husky plus lint-staged is the standard pairing.
package.json:
{
"scripts": {
"prepare": "husky"
},
"lint-staged": {
"{.agnostic-ai/**,agnostic-ai.yaml}": "agnostic-ai sync --check --"
}
}
.husky/pre-commit:
npx lint-staged
Install once per checkout:
npm install
The trailing -- swallows the staged paths lint-staged appends; sync --check reads the project root, not individual files.
Regenerate on checkout
The hooks above catch drift at commit time. They do not help when generated outputs are gitignored (gitignore.enabled: true): a fresh clone or a new git worktree then starts with no CLAUDE.md, rules, or hooks until someone runs sync. A contributor cloning the repo runs sync by hand; automated worktree creation does not, so an AI session opened there finds no config.
A post-checkout hook closes the gap. git checkout, git clone, and git worktree add all fire it, so outputs regenerate themselves.
lefthook (lefthook.yml):
post-checkout:
commands:
sync:
run: agnostic-ai sync
Plain git (.git/hooks/post-checkout, chmod +x):
#!/bin/sh
agnostic-ai sync
post-checkout receives three arguments; a file checkout passes 0 as the third. Guard on it if you only want the hook on branch and worktree switches:
#!/bin/sh
[ "$3" = "1" ] || exit 0 # 1 = branch checkout, 0 = file checkout
agnostic-ai sync
This needs agnostic-ai on PATH in every environment that checks out the repo. If contributors may lack the CLI, commit the generated outputs instead of gitignoring them.
Tips
- The hook needs
agnostic-aionPATH. Document the install inCONTRIBUTING.mdso new contributors avoidcommand not foundon their first commit. - To recover from drift, run
agnostic-ai syncand stage the regenerated outputs alongside the spec change. - Set
gitignore.enabled: trueinagnostic-ai.yamlto keep generated outputs out of git. The hook still catches drift becausesync --checkignoresgitignore. - Skip a hook for one commit with
git commit --no-verify. Save it for emergencies.