Rolling out to a team
Hooks only protect the machines that installed them. One person with amont has one protected machine; a team has sixty, plus new hires, plus the laptop that got reimaged on Tuesday. This page is the whole story of closing that gap, and it is honest about the one thing git will not do.
The constraint nobody gets to skip
Git deliberately runs nothing on git clone. A repository cannot install
its own hooks into your machine, and any tool that made it look otherwise
would be a supply-chain attack with good ergonomics. So "the repo declares
it, the clone self-installs" needs a machine-side grant somewhere —
the only question is what shape the grant takes and how often somebody
has to type it.
Three shapes exist, and they compose:
| shape | typed how often | covers |
|---|---|---|
"prepare": "amont init" in package.json | never (npm types it) | JS repositories, on npm install |
amont enroll | once per machine | every future git clone and git init |
amont init | once per repository per machine | that one clone |
The machine grant: amont enroll
$ brew install fredericrous/tap/amont # or the curl installer, or cargo
$ amont enroll --conventions declared
enroll does three things amont install has always done one repository
at a time — puts the binary somewhere stable, populates the template
directory, and (the part install deliberately left to the user) points
init.templateDir at it. From then on every git clone and every
git init on that machine arrives with the shims already in
.git/hooks, resolving whatever amont binary the machine has. Upgrading
the binary upgrades every repository at once; nothing is re-run per
clone.
It refuses to overwrite an init.templateDir that already points
somewhere else — something else installs hooks on that machine, and
silently disabling it is the husky failure one level up. And it is
idempotent: re-running it is a no-op that says so.
Two lines in the onboarding doc — install the binary, amont enroll —
replace a per-clone ritual forever. Repositories cloned before the
grant are the one thing it does not reach: amont init wires one,
amont-fleet install --root ~/work wires all of them.
The repository declaration: amont.conf, and --conventions declared
The objection to a standing grant is real: the same machine clones the team's services and upstream open-source projects, and those did not agree to your commit-subject shape, your branch naming, or your auto-rebase. A grant that imposes house rules on somebody else's repository is a grant people revoke.
--conventions declared (or git config --global amont.conventions declared by hand) splits the checks in two:
- The safety net runs everywhere: merge-conflict markers, leaked
secrets, oversized files, and debug leftovers (
debugger,dbg!(…),breakpoint()) in the diff you are committing. These are mistakes in any codebase, with near-zero false positives, and catching them in an upstream clone is a favour to the upstream. - The conventions wait for a declaration: commit-message shape,
branch patterns, lint and format gates, test suites, audits,
auto-rebase. They run only in a repository that has committed an
amont.conf— even an empty one:
$ echo "# this repository subscribes to amont" > amont.conf
$ git add amont.conf && git commit -m "chore: declare amont"
Presence is the declaration; presence executes nothing, so it needs no
trust decision. What the file says — declared checks, tool pins — stays
trust-gated exactly as before. A held-back stage says so in
one line (N convention check(s) held back … the safety net still runs)
rather than silently doing less, and amont list reports the state in
text and in --json ("conventions_apply").
The default is everywhere: nothing changes for anyone until a machine
opts into declared.
The team recipe
- Each machine, once (onboarding doc, two lines):
$ brew install fredericrous/tap/amont # pick your installer $ amont enroll --conventions declared - Each repository, once ever (committed, travels with the clone):
- commit an
amont.conf— empty declares; custom checks, committed policy (severity/skiplines for the built-ins) andtoolpins can come later; - JS repositories additionally get
"prepare": "amont init"so even an unenrolled machine is covered bynpm install.
- commit an
- Repositories cloned before enrollment:
amont initin one,amont-fleet install --root <dir>for all of them.
New hire day one: install, enroll, clone — protected. No per-clone step, no per-repo step, nothing to forget.
Step 0, before any of it: trial it at warn
The recipe above imposes thirty-odd opinions your team did not pick, and the
honest risk is not that a check is wrong — it is that two of them are wrong
for you, people start reaching for --no-verify, and the habit generalises
to the checks that mattered. A guard that teaches its own bypass is worse than
no guard.
So measure first. On your own machine, in a repository you work in daily:
git config amont.severity.pre-commit warn
Everything runs and reports; nothing blocks. A fortnight later, amont list
tells you what a rollout would have felt like:
problems that did not block
pre-commit-usual-name 61 last 2h ago
pre-commit-ban-terms 4 last 1d ago
66 events over 41 commits, since 12d ago
Then decide per check, with evidence, before anyone else is affected —
amont.severity.<check> warn keeps the ones you want advisory. A check firing
sixty times in a fortnight is a conversation to have with your team, not a
default to inflict on them.
The ledger is local to the machine that recorded it, so this measures your habits, not the team's. That is a real limit of the no-telemetry promise, and the workable version is to ask two or three colleagues to run the same trial and compare — not to look for a fleet-wide number that deliberately does not exist.
What this does not solve
- Hooks remain advisory.
--no-verifystill works, deliberately, and is counted rather than prevented. The guarantee lives in CI, not on laptops; put the same checks there and the hook becomes the fast feedback, not the enforcement — the CI backstop ships copyable workflow templates for exactly that. - Version skew. Enrolled machines resolve whatever binary they have;
two teammates on different amont versions run different check sets until
a shim is newer than a binary (which warns). Commit the floor:
set minVersion 1.11.0inamont.confmakes every older binary say so on each commit — warn-only, but no longer silent. Pin the exact version in your installer of choice if you need more than a floor. - Machines that never enrolled. The fleet dashboard sees one machine's checkouts. A teammate who skipped onboarding is invisible — which is one more reason the real backstop belongs in CI.