← All projects

mido

A CLI that runs six checks and blocks the change until everything passes. Built for my workflow with AI agents.

  • 10.4klines of Rust
  • 349tests
  • 97.8%coverage
  • 100%mutants killed (102)
Role
Author and maintainer
Stack
Rust · CLI · MCP · cargo-mutants · Nix
Links
GitHub

Context

“If you want to pass through here, you should at least equip a sword and shield!” — Mido, guarding the way into Kokiri Forest

mido is a command-line tool that runs a six-gate verification ladder — syntax, size, analysis, tests, coverage and mutation — against an entire workspace, in cost order, and blocks the way until every gate passes. If the name comes from the character who won’t let you in without proof, the behavior matches.

It was born from the problem of verifying code written with AI agents: when an LLM writes the change, something has to prove the change holds. The first diff it measured was the frontend crate of Maya.

The problem

Quality tooling usually warns; it doesn’t decide. For a flow where an agent writes and another agent reads the result, that is not enough:

  • a green pipeline proves very little: tests that execute code without asserting behavior pass just the same;
  • “did not run” is usually treated as “passed” — the most dangerous category there is;
  • and a verdict must be machine-consumable: no visual flourish, no interaction, no interpretation.

What was missing wasn’t another linter; it was a ladder with a contract: one order, one vocabulary, and exit codes for an agent to branch on.

How it was built

A single Rust crate, library + binary, organized in layers:

  • gate — the leaf of the system: the vocabulary of the six gates (names, order, fix hints) lives here and only here, consumed by the CLI, the config schema and the reports;
  • gates — one module per gate, with exhaustive dispatch;
  • language seam — all stack-specific behavior (default commands, output parsers, target detection) sits behind one module per language; today, Rust;
  • strict configuration — .mido.toml has a versioned schema and an unknown key is an error: the ladder refuses to guess what a typo meant;
  • reports — panels and markdown; and an MCP server that exposes the ladder as tools (list_targets, run_ladder) straight to protocol-speaking agents.

The verdict is stamped on the exact revision it measured — commit plus dirty-state hash — so a green result from before the last edit cannot be replayed on it.

Decisions and trade-offs

  • Opinionated by design. No plugins, no custom gates: the ladder is the opinion. It limits extensibility and in exchange guarantees that “green” always means the same thing.
  • Commands are argv arrays, never shell lines. What .mido.toml declares is exactly what runs — an agent editing the config cannot smuggle a pipeline into a gate.
  • Skipped is never passed. A gate turned off reports SKIPPED; a gate missing its tooling reports INCOMPLETE. Neither becomes a green verdict. “Did not run” is not evidence.
  • Cost-ordered. The cheap gate runs first; mutation, which is expensive, runs last — fail fast, verify deeply only when it pays off.
  • Deterministic output for machines. When output is piped, color and the live status line disappear: what an agent captures is a stable, diffable log.

Quality and verification

mido verifies itself with its own ladder. In the last fully documented cycle:

  • 349 tests, unit and integration — the integration tests exercise the compiled binary and the MCP server over stdio;
  • 97.8% coverage;
  • 102 mutants killed, 100% — no survivors in the fresh sweep. (The configured floor is 70%; the result is what the tests actually kill, not what the floor allows.)
  • size and analysis gates clean.
╭─ mido ──────────────────────────────────────────────╮
│ target   workspace (./)                             │
│ revision febf29d1                                   │
│ dirty    bddc06f7                                   │
│ measured 14 files (14 rust)                         │
╰─────────────────────────────────────────────────────╯
  Cargo.toml
  src/style.rs

  [1/1] ✓ syntax  clean
      format: 0 file(s) with diffs
      lint: 0 diagnostic(s)
      typecheck: 0 diagnostic(s)

╭─ verdict ──╮
│ SHIP-READY │
╰────────────╯

The process also found bugs in its own mutation bookkeeping — cached results being read as if they came from a fresh run, producing fast, empty verdicts. The fix was making every run own its output directory and scoring only what executed. A verification tool is worth exactly as much as it is honest.

Results

The ladder has a clear verdict contract: SHIP-READY (exit 0), BLOCKED (exit 1) and INCOMPLETE (exit 2), with a failure report that works as the next agent’s prompt: ladder position, the gate’s contract, the evidence, and fix hints written as instructions. Today it guards a commercial product in development — and it is the basis on which I accept AI-written changes in my own code.

What I learned

Verification is only worth it if it is honest about what did not run. And the most fruitful design decision was assuming the reader is a machine: exit codes, JSON and MCP changed the whole tool — from the shape of its output to the way errors are written.