Skip to content

Repository files navigation

🐜 Ant

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 go does, including arm64 (Raspberry Pi / Jetson / Apple silicon).


Watch it work

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.

Ant — scout, fix, verify, review

▶ 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/.


Quick start

# 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 apply

Prerequisite: the built-in species detect via ast-grep, so it must be on your PATH. Without it, scout/fix exit cleanly with code 2 and a clear message. See docs/guide/install.md.


The git-shaped loop

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.


Built-in species

A species bundles a detector + a fix strategy + a verifier set + a trust default. Trust is granted per species — never globally.

Deterministic — auto-apply, zero tokens (the core)

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.

LLM-assisted — propose-only, secondary tier (opt-in)

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.


The trust model

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.toml overrides 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 install runs no repo code. It clones, validates structure, and copies only well-formed species folders. A repo's verify.sh / Makefile / go:generate never 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_HOME or <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.

CI mode

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.

GitHub Action

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           # optional

The 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.


Installation

# 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 Releases

Prebuilt 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 the command detector kind) run a POSIX sh script and need a POSIX shell on PATH — not present on stock Windows. Install Git for Windows (adds sh.exe) or run inside WSL. Without one, ant scout/ant fix exit with code 2 and name the missing binary; every ast-grep-based species is unaffected.


Updating

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 … | sh is latest-capable: with no ANT_VERSION set it fetches the most recent release via GitHub's /releases/latest/download/ redirect. Pinning is still supported but optional — ANT_VERSION=v0.1.0 (or 0.1.0) installs a specific tag.

  • Confirm what you're running with ant --version.

  • Homebrew: the cask is wired in .goreleaser.yaml and publishes to gitpcl/homebrew-tap on 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-tap repo and the HOMEBREW_TAP_TOKEN secret, then flipping skip_upload to false) — until that's done the cask renders but does not upload, so releases never break. The exact steps are in the .goreleaser.yaml comment and .harness/progress_log.md.


How it works

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), with semgrep/eslint/command available per species. The detector is chosen per species, matching the confidence a species needs (ADR 0004).
  • Fixers are pluggable, with two supported tiers: deterministic transforms (no model — the default) and a provider-agnostic OpenAI-compatible rawmodel. 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:affected runs 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.


Configuration (ant.toml)

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

Documentation

  • Quickstart — the full scout → fix → review → apply loop
  • Install — every install method + the OS/arch matrix
  • CI mode — --fail-on, exit codes, the --json stream, 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

Releasing

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.0

Pushing 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 --clean

Before 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 nothing

Contributing

The 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.

License

MIT © Pedro Lopes

About

Autonomous code-cleanup CLI: a colony of ants that detect, fix, and verify code smells, gated by a per-species trust model.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Used by

Contributors

Languages