The AI writes the code.
Two gates decide
if it counts.
hexa scaffolds a project, lets an AI agent build it, and keeps the work only when your test command exits 0 and the architecture grade holds. One binary. No daemon, no database.
curl -fsSL https://raw.githubusercontent.com/gaberger/hexa/main/install.sh | bash
$ hexa scaffold "A bookmark service: SQLite store, HTTP API, tag search" --lang rust --grade A ✓ 8 files (rust) — gate: cargo test✓ floor gate green: cargo test✓ 2 designs → 2 critiques → build GREEN✓ gate re-run: PASS — 27 test(s) ran✓ architecture grade: A+ — 100/100 (floor A) $
Generating code stopped being the bottleneck. Checking it didn't.
An AI agent can ship a feature whose tests pass and whose shape is wrong: a use case that imports a database driver, a domain type that depends on an HTTP client. The suite stays green.
Tests answer does it run. Nothing answers is it still the system I designed. hexa adds that second question, as a gate.
| Spec-driven | Gate-driven (hexa) | |
|---|---|---|
| The artifact | prose | a command |
| Can it fail? | no | yes, with an exit code |
| When code drifts | the spec quietly stops being true | the gate goes red |
| What decides done | a person reading two documents | the exit code |
A deterministic floor, an AI build, and two gates it has to pass.
The model only touches step 3. Everything before it is reproducible, and everything after it is a command with an exit code.
Floor
A skeleton from templates compiled into the binary. Byte-identical on every machine.
deterministicFloor gate
Does the skeleton build and test here, before any model call?
exit 0AI build
Designs, critiques, then code onto the floor. The only step a model runs.
modelDoes it run?
Your test command, e.g. cargo test.
Is it the shape?
The architecture grade must meet your floor.
grade ≥ AA floor you can reproduce
A scaffold you can't reproduce isn't a foundation, it's a draft. The floor gate runs first because a skeleton that won't build makes everything after it meaningless.
Commits only on exit 0
hexa edits, runs your command, and commits only if it passes. Otherwise the edit reverts. A model that wanders commits nothing.
Rules travel with the project
Every scaffold ships .hexa/ADR-rules.toml. It's the contract the project keeps being graded against, not a starting point you leave behind.
The loop is recorded
.hexa/loop.json keeps each session's decision, gate and evidence. Hooks read it, and feature-sized edits with no gate are stopped.
A decision becomes sealed tests before any code exists.
The scenarios come from the decision record and its ports, never the code. A person signs off on what "correct" means, and no agent commit may edit the tests.
Draft
Given/when/then scenarios from the ADR and its ports alone.
hexa spec draft <ADR>Accept
You agree on what "correct" will mean.
hexa spec accept <ADR>Seal
One test per scenario. Each must fail first.
hexa spec tests <ADR>Verify
Every sealed test runs alone. Drift fails the build.
hexa spec verify --ciInstall once. Every verb is gated.
The script installs one binary from the latest release. Then hexa bootstrap sets up prerequisites, the inference server and config, and hexa self-update keeps it current.
# the floor alone: deterministic, runnable $ hexa init ./myapp --scaffold --lang rust # the floor plus what you described, # gated on the build AND the grade $ hexa scaffold "A bookmark service: SQLite store, HTTP API, tag search" \ --target ./myapp --lang rust --grade A
$ hexa do run "make add() return a + b, not a - b" \ --file src/lib.rs \ --evidence "cargo test --test add"
$ hexa analyze . # grade + rule violations $ hexa analyze . --grade A # fail below A $ hexa analyze . --grade A --exit-code # and on any violation $ hexa graph consumers <path> # who depends on this
# adversarial: hunt by lens, verify each finding, fix under the gate $ hexa harden src/domain/merge.rs --gate "cargo test -p mycrate" # cooperative: diverge, red-team, synthesize, build to the gate $ hexa build "a bounded work queue" --target src/queue --gate "cargo test" # either one, past any terminal or tool timeout $ hexa harden src/ --gate "cargo test" --detach
$ hexa loop adr ADR-2609131800 # the decision $ hexa loop gate "cargo test --test add" # before the code $ hexa loop evidence "cargo test -- --nocapture" $ hexa loop stage done # evidence into the ADR $ hexa loop # every session's state $ hexa spend # tokens and dollars
$ hexa doctor # non-zero when a check fails $ hexa go # the next right thing $ hexa --version # the commit it was built from
Longer walkthrough: Getting started. hexa --help lists every verb.
Because its rules can be checked by a machine.
"Good separation of concerns" can't be graded. Dependencies pointing inward can. hexa analyze walks the AST of every file to check these six rules. Hover one to see its layer.
domain/ imports only domain/ports/ imports domain/ onlyusecases/ imports domain/ and ports/ onlyports/, never the domain directly. A primary adapter may also call usecases/.Rule 4 is the one that breaks. Ports re-export the domain types adapters need, so each adapter has one edge into the core and swapping a database touches one file.
A model that wanders commits nothing.
One loop, in process. It gets graph context and ranked lessons, reads and checks with a short list of guarded tools (no arbitrary shell), and proposes one edit. Your evidence command decides.
A pass that exercised nothing is rejected. A failed edit reverts atomically. With several models configured, the first edit that passes wins.
Each has a compiler, and a compiler is a gate.
A wrong shape fails in seconds, before any test runs. And the grade needs imports that are explicit at build time; duck typing and runtime loading hide the boundaries.
Strictest. crate::, super::, inline paths and paths inside macros are all read.
Module paths from go.mod decide what's inside the project.
Most forgiving. tsconfig aliases, require() and import() are graded.
Strictness makes the gate stronger and the task harder: weaker models fail Rust and Go where they pass TypeScript. A gate a model can't pass is doing its job.
Six projects, each from one sentence.
Every gate re-run from a clean build. The tests were broken on purpose to prove they fail: a passing test proves nothing until you've watched it go red.
linkstore-svc
Real I/O: HTTP, SQLite, a migration, 12 end-to-end tests against a live port.
game-2048-ts
The gate was broken five ways and failed every time.
url-shortener-rs
A+ on the architecture grade.
game-life-rs
Pure logic, no I/O, from one sentence.
ratelimiter-proof
hexa harden found three bugs behind fourteen passing tests.
game-ttt-go
A perfect minimax player.
Repaired code it didn't write
Ten single-token bugs in a 1,685-file project. Each line restored exactly, no test edited.
Refactored code it didn't write
Every boundary violation found, plus five more. A ports layer added, all 177 tests green.
Found what 14 tests missed
A u128 overflow made a rate limiter limit nothing. Only an adversary reading the code caught it.
Every number has a command that checks it in docs/EVIDENCE.md.
Local models have a ceiling, and it depends on the language.
Same task, pass rate per model. The leaderboard's top local model came last here, so rank doesn't predict how a model does in the loop. Measure yours with hexa bench agentic.
| Model | Rust | TypeScript | Go |
|---|---|---|---|
| devstral-small-2:24b | |||
| gemma3:12b | |||
| qwen2.5-coder:14b | |||
| gpt-oss:20b |
hexa grades itself, and passes.
Seven crates, one binary, about 67k lines of Rust. Every source file has a layer and every import between crates is checked. A ratchet test fails on any new violation.
Map: ARCHITECTURE.md. Decisions: the append-only ADR ledger.
The badge says how it was made. CI keeps the grade true.
Wardian
Small apps, sealed, served from your own machine.
What it doesn't do yet.
Kept on the page on purpose.
Finding a bug from a failing test
Given a rule, hexa finds every file that breaks it. Given only a failing test name, it found the right file once in ten.
No "refactor to a grade" verb
The refactoring trial ran through hexa build with the analyzer as its gate. It worked; it isn't a verb yet.
What a static import can't show
Code a macro generates from an allowed crate, reflection, and FFI. Modules named at run time get a warning, not a pass.
It doesn't write the code
A frontier model does. hexa adds the floor, the gates, the grade and the adversary: it makes a capable model disciplined.
Stop reading two documents. Read the exit code.
One binary for Rust, Go and TypeScript. MIT licensed.
curl -fsSL https://raw.githubusercontent.com/gaberger/hexa/main/install.sh | bash