Deterministic, verified codemods for your repo — fixes that can't break your build, at zero token cost, gated behind a strict verifier and staged for review before they ever touch your working tree.
Ant's core is a no-LLM, deterministic fixer: mechanical codemods (remove an unused import, delete dead code, flatten a redundant else) whose every proposed change runs a verifier gate — does it still compile? do the affected tests pass? is the smell actually gone? — before it's staged. A wrong fix can't compile, so it's discarded and you're told why. No model, no API key, no tokens.
The core idea: detection is allowed to be imperfect because verification is strict. A fix that fails any required check is skipped, never silently applied — and a skip is a trust signal, not a hidden error. Nothing is written to your working tree until you explicitly apply it, on a new branch by default.
LLM-assisted species exist too (for smells no codemod can safely fix — n+1 queries, nil-deref guards), but they're a secondary, propose-only tier: opt-in, always staged for a human, and off by default. The deterministic path is the product; the model is an add-on.
Internally Ant runs as a parallel colony of workers ("ants"), one per finding — see How it works.
Status: v1 feature-complete. MIT licensed. Pure-Go, CGO-free single binary — runs anywhere
godoes, including arm64 (Raspberry Pi / Jetson / Apple silicon).
A 30-second tour of the loop — scout → fix → verify → review — built from real terminal runs. Watch the colony fix one finding and skip two because the compile gate caught fixes that would break the build.
▶ Watch the full-quality video (MP4) — the GIF above is a downscaled preview.
Rendered deterministically from HTML/CSS with HyperFrames; the composition source lives in
docs/media/hyperframes/.
# Install (see Installation for alternatives)
curl -fsSL https://raw.githubusercontent.com/gitpcl/ant/main/install.sh | sh
# 1. Scout — detect and report, change nothing
ant # severity-led digest: every high finding, medium/low folded to species counts
ant scout ./path # scout a subtree (noise dirs vendor/node_modules/.git/testdata ignored by default)
ant scout --all # list every finding (the full flat list) instead of the digest
ant scout ./path --detail # add the code snippet to each finding
# 2. Fix — produce verified diffs into a staging area (working tree untouched)
ant fix
# 3. Review — walk the staged diffs: accept / skip / diff / explain / next / quit
ant review
# 4. Apply — land the accepted diffs, on a new branch by default
ant applyPrerequisite: the built-in species detect via
ast-grep, so it must be on yourPATH. Without it,scout/fixexit cleanly with code 2 and a clear message. See docs/guide/install.md.
Ant follows a stage-then-apply model — the same shape as git add → git commit:
| Command | What it does | Touches your code? |
|---|---|---|
ant / ant scout |
Run detectors, report findings | No |
ant fix |
Detect → fix → verify → stage verified diffs | No (only --apply does) |
ant review |
Walk staged diffs with full provenance | No |
ant apply |
Land accepted diffs (new branch by default; --no-branch for current) |
Yes |
ant init |
Scaffold an ant.toml |
Writes config |
ant species list / install / remove |
Manage detection species | No / clones into .ant/species/ / No |
Global flags: --json, --fail-on=<low\|medium\|high>, --config, --fixer, --model, --concurrency.
A species bundles a detector + a fix strategy + a verifier set + a trust default. Trust is granted per species — never globally.
These are no-LLM codemods. They auto-apply because a wrong fix can't survive the verifier gate — compile rebuilds the patched tree and detector-clears proves the smell is gone.
| Species | Languages | Notes |
|---|---|---|
unused-import |
Go, TS | removes an import that's never referenced; compile gate makes a wrong removal impossible |
dead-code |
Go, TS | annotation-driven removal of unreachable code, gated by compile |
unused-variable |
Go, TS | drops a declared-but-unused variable |
redundant-else |
Go | flattens an else after a terminating if-branch into a guard clause |
js-eqeqeq |
TS, JS | rewrites ==/!= to ===/!== |
redundant-conversion |
TS | removes a no-op type conversion |
unreachable-code |
TS | deletes code after a terminating statement |
Plus a set of formatter/linter auto-fix species (formatter-drift, prettier-format, ruff-format/ruff-autofix, isort-imports, eslint-autofix, pint-format, …) that run your existing tools' safe-fix subset under the same gate. Run ant species list for the full catalog.
For smells no codemod can safely fix. These are verified but always staged for a human, and — as of v1 — not auto-fixed unless you wire a fixer (--fixer rawmodel); the default fixer is deterministic. They never auto-land.
| Species | Default | Notes |
|---|---|---|
n+1-query |
propose-only | model fix, always staged for review |
nil-deref |
propose-only | guards a likely nil dereference |
missing-await |
propose-only | un-awaited goroutine / missing synchronization |
ai-slop |
disabled | fuzzy classifier; too noisy to enable by default — opt-in only |
…and ~25 more across Go / TS / Python / PHP / Vue. See ADR 0002.
Trust is the heart of the product (and the reason you can let it touch your code):
- Verified or skipped, never silent. Every proposed fix runs the verifier gate:
diff-bounded→compile→detector-clears→tests:affected. Fail any required check → discarded and surfaced as a skip with the reason. - Per-species trust, no global switch.
ant.tomloverrides a species' default; there is no "trust everything" flag. - Freshly-installed species are forced propose-only until you've reviewed them once — regardless of what their manifest claims. Installing a community species can never auto-apply on first run.
ant species installruns no repo code. It clones, validates structure, and copies only well-formed species folders. A repo'sverify.sh/Makefile/go:generatenever executes at install time.- Trust state lives on your machine, not in the repo. "Reviewed once" state is stored in a user-local directory (
$ANT_TRUST_HOMEor<os-user-config-dir>/ant/trust/), keyed by the repo's absolute path — so a repository you merely scan can't ship its own trust state to grant its species auto-apply or scan-time script exec.
ant scout is a composable CI gate:
ant scout --json --fail-on=high
# exit 0 = nothing at/above threshold · 1 = threshold tripped · 2 = operational error--json emits a stable, golden-tested event stream (run.start … run.end) that the front-doors and your own tooling can parse. See docs/guide/ci.md.
Drop the gate into a workflow with the official Action — pin it to a release tag (not a branch) so the installed binary is reproducible:
# .github/workflows/ant.yml
name: ant
on: [pull_request]
jobs:
scout:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: gitpcl/ant@v1 # pin to a release tag; the binary version follows it
with:
fail-on: high # low | medium | high
# path: ./services/api # optional subtree
# config: ant.toml # optionalThe Action installs the matching ant binary (checksum-verified by install.sh, cached across runs) and runs ant scout --json --fail-on=<input>. Job passes on exit 0, fails on exit 1 (threshold tripped), and errors on exit 2.
# curl | sh — detects OS/arch, verifies the checksum, aborts on mismatch
curl -fsSL https://raw.githubusercontent.com/gitpcl/ant/main/install.sh | sh
# go install
go install github.com/gitpcl/ant/cmd/ant@latest
# or download a prebuilt static binary from ReleasesPrebuilt targets: linux amd64/arm64, darwin amd64/arm64, windows amd64. The arm64 builds make Ant a first-class citizen on Raspberry Pi / Jetson. Full matrix in docs/guide/install.md.
Windows note: a handful of built-in species (
hardcoded-secret,unused-dependency,dead-config, and others using thecommanddetector kind) run a POSIXshscript and need a POSIX shell onPATH— not present on stock Windows. Install Git for Windows (addssh.exe) or run inside WSL. Without one,ant scout/ant fixexit with code 2 and name the missing binary; everyast-grep-based species is unaffected.
Each method resolves the newest release on its own — no version pinning required.
# go install — rebuilds the latest tagged version
go install github.com/gitpcl/ant/cmd/ant@latest
# curl | sh — installs the newest release (re-run to upgrade in place)
curl -fsSL https://raw.githubusercontent.com/gitpcl/ant/main/install.sh | sh-
curl … | shis latest-capable: with noANT_VERSIONset it fetches the most recent release via GitHub's/releases/latest/download/redirect. Pinning is still supported but optional —ANT_VERSION=v0.1.0(or0.1.0) installs a specific tag. -
Confirm what you're running with
ant --version. -
Homebrew: the cask is wired in
.goreleaser.yamland publishes togitpcl/homebrew-tapon release:brew tap gitpcl/tap brew install --cask ant brew upgrade --cask ant
Activation is gated on a one-time maintainer setup (creating the public
gitpcl/homebrew-taprepo and theHOMEBREW_TAP_TOKENsecret, then flippingskip_uploadtofalse) — until that's done the cask renders but does not upload, so releases never break. The exact steps are in the.goreleaser.yamlcomment and.harness/progress_log.md.
cmd/ant/ (thin CLI: parse flags, call the engine, render)
│
▼
internal/engine/ (the library — all logic lives here)
colony/ scheduler + worker pool + the run loop (+ optional trails)
detect/ Detector adapters → shells out to ast-grep (a plugin boundary)
fix/ Fixer adapters — supported: deterministic · rawmodel · community: claudecode · codex · pi
verify/ diff-bounded · compile · detector-clears · tests:affected
species/ manifest model · registry · go:embed built-ins · trust model
stage/ · store/ · events/ · config/ · telemetry/
- Detection is a plugin boundary, not a build dependency — Ant shells out to
ast-grep(the default), withsemgrep/eslint/commandavailable per species. The detector is chosen per species, matching the confidence a species needs (ADR 0004). - Fixers are pluggable, with two supported tiers:
deterministictransforms (no model — the default) and a provider-agnostic OpenAI-compatiblerawmodel. Harness adapters that drive Claude Code / Codex / Pi one-shot are community-maintained (kept wired, not officially supported). The model is always configured, never hardcoded. tests:affectedruns only the tests impacted by a diff (coverage-map → import-graph → package-fallback) and reports which strategy it used — never silently running the whole suite.- Front-doors (
adapters/): thin TypeScript shells (Claude Code skill, Pi extension) that exec the binary and parse--json. - Telemetry is opt-in and off by default — when enabled it sends only privacy-safe aggregates (species usage, accept rate, verifier catch rate), never code, paths, or diffs.
The hard architectural rule: all logic lives in internal/engine; cmd/ant only parses and renders — enforced by a boundary test.
Zero-config by default. ant init scaffolds a commented file:
[colony]
# Default fixer is "deterministic" — LLM species are detected but not auto-fixed.
# Opt into LLM fixes by setting a supported fixer and a model:
# supported: deterministic (default) · rawmodel (provider-agnostic)
# community-maintained: pi · claudecode · codex
# fixer = "rawmodel"
# model = "qwen2.5-coder"
[ignore]
paths = ["vendor/", "node_modules/"]
[species.unused-import]
auto_apply = true
[species.ai-slop]
enabled = true # opt into the disabled-by-default fuzzy species- Quickstart — the full scout → fix → review → apply loop
- Install — every install method + the OS/arch matrix
- CI mode —
--fail-on, exit codes, the--jsonstream, a GitHub Actions snippet - Species authoring — write and publish your own species
- Design decisions — ADRs: command model · launch species · license & trails · detector strategy & confidence tiers
- PRD · Technical spec
Maintainer-facing. Shipping a release is pushing a semver tag.
# Cut a release: tag with semver and push the tag.
git tag v0.1.0
git push origin v0.1.0Pushing a v* tag triggers .github/workflows/release.yml, which runs goreleaser to cross-compile every target, build the archives + a ant_checksums.txt file, and publish a GitHub Release automatically (release.draft: false). Auto-publish is intentional: curl … install.sh | sh resolves "latest" through GitHub's /releases/latest endpoints, which only see published (non-draft, non-prerelease) releases — so the tag push is the deliberate ship action.
Only v* tags trigger a release. sprint-NNN-complete tags are harness checkpoints, not releases — they do not match the workflow's v* filter and publish nothing. Releases always use semver vX.Y.Z.
Manual fallback (publishes from your machine; requires a token with contents: write):
GITHUB_TOKEN=… goreleaser release --cleanBefore tagging, you can validate the config and dry-run the full release locally without publishing:
goreleaser check # validate .goreleaser.yaml
goreleaser release --snapshot --clean # build all archives + checksums under dist/, publishes nothingThe fastest way to extend Ant is to author a species — no Go required, just a species.toml + an ast-grep rule (and a fix prompt for LLM species). See the species-authoring guide. Community species install via ant species install <git-url> and are propose-only until you've reviewed them once.
MIT © Pedro Lopes
