Hooks
Exohook runs the project’s validation checks at the right moments (before a
commit, before a push, and in CI) from a single declarative source. The
configuration lives in .config/exo/hooks.toml. Git hooks and the GitHub
Actions workflow are both projections of that file, not independent copies
that can drift (RFC 0137).
Workflows and checks
Section titled “Workflows and checks”A hooks.toml defines two things: checks and workflows. A check is a
single command with a scope filter. A workflow is an ordered list of checks
that run together against a defined scope of changes.
The repository’s current workflows:
| Workflow | Scope | Runs | Purpose |
|---|---|---|---|
dev | uncommitted | format, lint, type-check, check, TOML and link verification | Fast feedback on working-tree changes |
coherence | staged | the same fast checks | Pre-commit gate |
gate | committed-not-pushed | test | Pre-push gate |
ci | HEAD | test, coverage | Authoritative check on the pushed commit |
coherence and gate are bound to git hooks:
[hooks]pre_commit = "coherence"pre_push = "gate"The dev workflow has no scope of committed changes; it operates on the
uncommitted working tree, so an agent can run it while editing without staging
first.
Mutating vs. verifying checks
Section titled “Mutating vs. verifying checks”A check declares a category. Most checks are pure verifications: they read
state and report pass or fail. A check marked category = "mutate" is allowed
to rewrite files (for example, rust-fmt reformats source, and rust-clippy
ships a fix_command). The distinction matters because mutating checks change
the working tree as a side effect of running; verifying checks never do.
Checks can be filtered to the files they care about. rust-fmt only runs on
**/*.rs; verify-exo-links only runs on **/*.md. A check whose filter
matches none of the changed files is skipped.
Projections: git hooks and CI
Section titled “Projections: git hooks and CI”The same lane definitions drive both local enforcement and CI:
| Projection | Output |
|---|---|
| Git hooks | .git/hooks/pre-commit, .git/hooks/pre-push |
| GitHub Actions | .github/workflows/exo-ci.yml |
The CI workflow is generated from the ci lane (RFC 0137):
exohook ci emit github-actionsThe generator emits one GitHub Actions job per check in the lane, so CI runs
exactly what the local ci lane runs. Each job sets up only the toolchain its
command needs; jobs run in parallel. The generated file carries an
# AUTO-GENERATED by exohook header. Exohook regenerates any file carrying that
header after a successful command, and leaves files without it untouched.
Removing the header opts a file out of regeneration. To disable the projection
entirely, set enabled = false under [projections.github_actions].
Because the workflow is generated, it is the kind of file that re-emits on every
run. Treat .github/workflows/exo-ci.yml as derived: regenerate it through
exohook rather than editing it by hand, and do not commit incidental
regeneration drift.
Terminal viewport feedback
Section titled “Terminal viewport feedback”Long-running checks such as builds and tests can appear to hang when they produce no visible output, leading to premature abandonment. Exohook renders a rolling terminal viewport for each running check: the last few lines as they would appear in a real terminal, including in-place progress bars driven by carriage returns. Output is parsed through a VTE state machine so escape sequences, carriage returns, and line feeds are handled the way a terminal would handle them. The viewport shows what you would see right now, not just completed lines, so a check that is working but quiet still shows motion.