feature by complai

An execution specification language · MIT

Specs that compile into tests. Nothing lost in between.

A .feat file instructs how a feature is built, predicts every observable effect it will produce, and compiles — deterministically — into complete test suites. Anything unpredicted is a failure.

GitHub ↗ Get started → $ npx feat init
create-user.feat
■ agent zone — freeform ■ compiler zone — strict
spec SPEC-USR-001 "CreateUser"
type command
status verified
construct:
handler at handlers/create-user.ts
Uses Prisma ORM for database access.
scenario "rejects duplicate email":
when: CreateUser { email: "alice@example.com" }
predict rejection DUPLICATE_EMAIL:
response 409 ErrorResponse
database has []

Rejection predicted → zero side effects asserted across every configured service.

LLMs in the translation layer
0
closed reference spaces
4
exemplar conformance corpus
9
its own CI · green
28/28

The problem

Every spec tool has a translation layer. That layer is where the truth leaks.

Gherkin needs step definitions. Contract tools need hand-written provider tests. Markdown specs need a person — or a model — to decide what they mean. Every spec-to-test approach puts a manual translation layer between the contract and the assertions.

That layer is where drift, interpretation, and error live. The spec says one thing; the tests check another; the two diverge silently, and nobody notices until production does.

AI-generated code makes the gap worse, not better. Agents write more of the code, faster — and a model interpreting requirements into tests is the same translation layer, relocated into an LLM: non-deterministic, unverifiable.

Spec-driven workflows tell your agent what to build. Feature proves it got built — to the spec, in its original form.

Show it

One file in. One suite out. Byte for byte.

The prediction is the assertion. No step definitions, no glue code, no model in between. Same spec, same tests — sha256 in the header, byte-compared in CI.

inputs sha256:9c41f2ab60de…
spec SPEC-USR-001 "CreateUser"
context Users
aggregate User
type command
status verified
touches handlers/create-user.ts
construct:
handler at handlers/create-user.ts
imports UserRepository from repositories/user-repository
Uses Prisma ORM for database access.
enforce:
validate input against CreateUserInput
check email uniqueness via UserRepository.findByEmail
insert new user record
rejects DUPLICATE_EMAIL when a user with that email already exists
contract:
input CreateUserInput from "schemas/user-input"
response UserResponse from "schemas/user-response"
error ErrorResponse from "schemas/error"
scenario "successful user creation":
given: no user with email "alice@example.com"
when: CreateUser { email: "alice@example.com", name: "Alice" }
predict success:
response 201 UserResponse
database has [{ action: "INSERT", table: "users" }]
scenario "rejects duplicate email":
given: user with email "alice@example.com" exists
when: CreateUser { email: "alice@example.com", name: "Alice" }
predict rejection DUPLICATE_EMAIL:
response 409 ErrorResponse
database has []

Generated. Committed. Byte-locked to the spec.

Prediction inversion

The test fails on what's missing — and on what you didn't predict.

A spec declares the complete observable footprint of a feature — responses, events, writes, messages — across the services you configure. Predictions are exhaustive: an extra write is a violation, not a surprise.

Side effects can't hide. And queries can't lie — a spec of type query grammatically cannot express a write.

FAIL specs/create-flow.feat
CreateFlow › "successful flow creation"
UNPREDICTED record FlowAuditLogged in eventStore
predicted  eventStore has [ FlowCreated with FlowCreatedEvent ]
captured   [0] FlowCreated ✓ predicted
           [1] FlowAuditLogged ✕ unpredicted
anchor     create-flow.feat › "successful flow creation" › eventStore[1]
           create-flow.feat:36

You didn't write that assertion. You didn't have to.

The loop

Four steps. One lock around them.

feat verify — byte-locked in CI
01
Spec

Author the .feat contract — instructions, predictions, scenarios.

02
Generate

feat generate derives the complete suite. Deterministic — no model in the path.

03
Implement

Agent or human builds against red tests. The spec's construct block is the briefing.

04
Run

Standard Vitest execution. Coverage, reporters, JUnit out of the box.

Drift is a build failure. feat verify re-derives every committed suite from its spec and byte-compares. Touch the tests without touching the spec — or the reverse — and the merge gate goes red.

# ci.yml — the whole integration
- run: npx feat verify # drift gate
- run: npx feat run --reporter junit

The guarantees

Deterministic

Same spec, same tests, byte for byte. The generated header carries an inputs sha256 — no timestamps, no model, nothing to interpret.

Closed

Four closed reference spaces — schemas, service keys, commands, actors. A typo isn't a silent mismatch; it's a parse error with a hint, at compile time.

Enforced

feat verify byte-locks committed suites to their specs in CI. The spec you agreed to is the spec that shipped — provably.

Dogfood

It runs on itself.

Feature's own components are specified in .feat. The nine-exemplar corpus is its conformance suite. Its CI chain is its own product: feat verify && feat run.

Before the toolchain trusted its own compiler, every suite was hand-derived, then machine derivation was flipped on and diffed: zero semantic disagreements across all 27 original cases.

github.com/mmmnt/feature · CI
$ feat init
  ✓ feat.config.json written · adapters: handler, fs
$ feat generate
  ✓ 1 suite derived from 1 spec · inputs sha256:9c41f2…
$ feat run
  ✕ 2 failed — handler not implemented
  # implement handlers/create-user.ts — agent or human
$ feat run
  ✓ 2 passed · junit report written
$ feat verify
  ✓ byte-identical — the suite is the spec, provably

The full loop, on the toolchain's own repo. 28/28 green.

The adversarial benchmark

One planted defect. Every tool's own workflow. One binary question.

We plant one deliberate defect in a small feature, run each tool's full recommended workflow against it, and ask: did the tool's own verification catch it? A lane only counts if its tests passed the clean implementation first. Where competitors succeed — Cucumber on drift, pinned assertions on B5a and B6 — it's published too.

Scenario
Feature
@mmmnt/feature 0.1.4
Spec Kit
7bdf6c5 · HEAD 2026-07-16
Cucumber
@cucumber/cucumber 12.9.0
Plain agent
claude-fable-5 + node:test
AWS Kiro
pending
Codeplain
pending
✓ caught · ✕ not caught · ◌ pending · every cell requires a green baseline first · select a scenario for its breakdown
last verified 2026-07-17 · re-run on every release, weekly for drift

"Not caught" is a statement about that tool's produced verification on this scenario — not about the tool overall. Every cell regenerates from the harness; pending lanes stay visibly pending.

B1 · The unpredicted side effect caught · run 2026-07-16
01
The defect we planted
Create user also inserts an audit_log row nobody asked for.
02
What conventional tests saw
Green. Spec Kit was steelmanned (tests explicitly requested), Cucumber's suite was professional-grade, the plain agent's was the strongest of the three — and every assertion they contain still holds.
03
The failing signal
UNPREDICTED record FILE_WRITTEN
anchor SPEC-B1-001 › "successful user creation" › datastore[1]
You didn't write that assertion. The spec did.
  1. 01 Same defect for everyone — one planted bug, every tool’s full workflow.
  2. 02 Steelman setups — each tool configured the way its docs recommend.
  3. 03 Binary outcomes — caught or not caught. No scoring judgment calls.
  4. 04 Date-stamped — every result carries its version and run date.
  5. 05 Misses published too — scenarios where competitors succeed ship as well.
$ ./bench/run.sh --scenario b1 --tool all
# reproducible from scripts · harness repo opening soon

Ecosystem

Adapters, not lock-in.

Your infrastructure, your keys

Service names in predictions are yours — eventStore, database, whatever your architecture has. First-party adapters ship for handler, http, and fs.

handler http fs

Write one in an afternoon

An adapter is a plain package exporting one function. No plugin registry, no framework ceremony.

// @you/feat-adapter-redis
export function createAdapter(config) { … }

Vitest-native output

Generated suites are standard Vitest — coverage, IDE debugging, reporters, JUnit all work unchanged. If you loved Given/When/Then and hated maintaining step definitions: the scenario shape is familiar. The step-definition layer is gone.

Clone it. Break it. Every claim on this page is reproducible.

The samples above run verbatim against the released toolchain. Start with the language guide, or go straight to feat init.

Star on GitHub ↗ $ npx feat init