Back to blog
Harness Engineering

The harness: maintainability, fitness, and a gate your agent must pass

Letting an agent write code is easy. Stopping it from quietly rotting your architecture is the hard part. SpecShip turns the graph into checks — coupling, god-files, cycles, layering rules — composed into one opt-in CI gate.

Handing a coding agent the keys to your repository is a strange trade. It can move faster than any human reviewer, and it will — including in directions you didn't ask for. A function grows a tenth parameter. A UI module starts reaching into the database layer. Two modules quietly begin importing each other. None of it fails a test. All of it is debt the next change pays for.

The usual answer is "review harder," which doesn't scale against an agent that opens ten PRs a day. SpecShip's answer is different: the same graph that powers retrieval already knows the shape of your codebase. So we turn that shape into checks — and let the agent gate itself against them.

Maintainability, read off the graph

The first piece is a maintainability harness. It derives findings straight from the index — no second parse, no extra tooling — and each one tells you why it flagged:

  • Coupling hotspots — symbols half the codebase depends on. Change one and the blast radius is enormous; the graph already knows the in-degree.
  • Oversized symbols and god-files — the functions and files that have quietly become too big to reason about.
  • Dependency cycles — modules that import each other, directly or through a chain.
  • Dead-code candidates — exported symbols with no inbound edge anywhere in the graph.

Run it from the terminal with specship maintainability, open the Maintainability page in the dashboard, or let your agent call it. The thresholds aren't ours to dictate — what counts as "too coupled" differs per project — so they live in specship.config.json and you tune them as you go.

0
extra parses — it reads the index
4
finding kinds, each with a reason
1
config file tunes every threshold

Architecture fitness: rules the agent must follow

Maintainability tells you where the rot is forming. Fitness functions let you forbid it outright. You declare the rules your architecture depends on, in plain config, and specship fitness checks them against the real dependency graph — not a convention, not a lint heuristic, the actual edges.

specship.config.json
{
  "fitness": {
    "rules": [
      { "forbid": "src/ui/**", "from-importing": "src/db/**" },
      { "layers": ["routes", "services", "db"] },
      { "isolated": "src/payments/**" }
    ]
  }
}

Each violation is reported with its source → target and a file:line, so it's actionable, not a vague "layering smell." Crucially the command exits non-zero when a rule is broken, which is what lets it gate CI. And a rule whose target matches nothing is reported as a config error rather than silently passing — a green check should mean "we checked," never "we found nothing to check."

Your agent can run it too. Fitness is exposed as specship_fitness, so an agent can check its own change against the architecture rules before it ever asks you to merge — and fix the violation in the same turn.

One gate, strictly opt-in

Drift, fitness, maintainability, and a spec → test → verify behaviour check are useful on their own. specship check composes them into a single CI gate — but the design principle that makes it adoptable is restraint:

With no enforce config, it only advises and exits 0. Adding it to an existing repo never breaks the build.

— the opt-in rule

You turn on whichever checks you want to gate, one at a time, as your team agrees on them. A check you haven't opted into still runs and still reports — it just doesn't fail the build. That's the difference between a tool a team actually rolls out and one that gets disabled the first morning it blocks a hotfix.

$ specship check
# gated (enforced):
drift        ok   0 drifted links
fitness     FAIL src/ui/Cart.tsx:42 → src/db/pool.ts  (forbidden)
# advisory (not gating):
maintain    warn 2 god-files, 1 cycle
behaviour   ok   12/12 spec links verified

exit 1

Why this belongs in the graph, not a linter

A linter sees one file at a time and reasons about syntax. Every check here reasons about relationships across the whole repo — in-degree, reachability, layering, the path from a route to a database call. That's a graph question, and the graph is already built and already current. The harness isn't a bolt-on; it's the index, asked a different question.

Letting an agent write code is easy. The hard, valuable part is making sure that a year of agent-written code still has a shape a human can hold in their head. That's harness engineering — and it's the quiet half of what SpecShip does.

#harness#architecture-fitness#maintainability#ci
SE
Selva E.
Building SpecShip · graph, specs & harness
Works on the parsing, storage, and spec layers of SpecShip. Spends an unreasonable amount of time thinking about edges.