CHECKRIDE
Why The gate Pipeline Docs Languages Agents API GITHUB
An agent harness for code repos

Give your agent a definition of done.

One command runs the whole verification pipeline — types, lint, structure, dead code, tests, docs, links, spelling. exit 0 means the work is complete. Agents stop guessing; humans stop re-reviewing half-finished work.

$ pnpm add -D -E checkride ⎘
{{ fileName }}
{{ tk.t }}
21 SLOTS · ONE COMMAND · ONE EXIT CODE
{{ s.name }}
{{ selectionNote }}
01 · WHY CHECKRIDE

Agents write code. They can't tell when to stop.

"Done" in most repos is folklore — fifteen npm scripts, a CI file that runs a different subset, and whatever the person who set it up remembers. checkride replaces that folklore with a contract: one command, one exit code, the same in every repo that adopts it.

A definition of done

pnpm check exits 0 when the work is complete and non-zero when it isn't — with an exit-code taxonomy, timeouts on by default, and a machine-readable summary. A gate can't be talked past.

Lanes to stay inside

Deep modules, enforced mechanically: a folder module's index.ts is its only public surface, and siblings never reach the internals. In a Go module, internal/ draws the same line, and the compiler holds it. Humans and agents work in parallel with minimal merge conflicts.

Raw output, no normalizing

The consumer is an LLM, so checkride never reshapes diagnostics into a common format. Each tool's own JSON lands in .check/ untouched — deleting the layer that makes every meta-runner expensive to extend.

Exit price ≈ zero

checkride runs your pinned tools against your configs and adds no runtime dependency on any of them. Delete the dependency and the config, and the repo is exactly where it was. The worst case of adopting it is ending up where you already are.

02 · THE GATE

A contract, not a script pile.

Scripts are a list of commands. A gate needs semantics: what does each exit code promise, what may a consumer parse, what happens when a tool hangs — and can "green" ever mean "nothing ran"? checkride answers all four, and locks the answers with a contract test suite.

✕ THE SCRIPT PILE
Fifteen scripts, zero promises
lint · lint:fix · test · test:watch · typecheck · check:deps · spell · docs:lint · …
—No exit-code taxonomy — every failure looks the same.
—A hung tool stalls CI forever; nobody set a timeout.
—A check nobody wired into CI is a check that doesn't exist.
—Different names in every repo — the knowledge never transfers.
✓ ONE CONTRACT
One command, one exit code
$ pnpm check --strict
exit 0 done · 1 a check failed · 2 harness broke
+Exit codes are a taxonomy a gate can branch on — "red build" vs "fix the pipeline".
+Every check runs under a timeout by default; a hung tool is killed and recorded red.
+checks_run: 0 is never a silent pass — --strict makes it exit 2.
+Artifacts are crash-consistent — a killed run never tears summary.json.
A flake in a linter is an annoyance. A flake in the thing that decides whether the linter passed is a crack in the floor.
03 · THE AGENT CONTRACT

The agent can't declare victory on a red pipeline.

checkride init writes the contract into the repo: an AGENTS.md stanza stating the "exit 0 = done" rule, and a stop-gate hook for Claude Code and Cursor that fires when the agent tries to finish — a red pipeline blocks the stop and sends the agent back to work until it's green.

When a check fails, the agent reads .check/summary.json, then the failing check's raw output, which a target's check writes under .check/<target>/. On a big red repo, --digest writes a token-bounded excerpt so the agent starts from a capped index, not a wall of text.

Set up the harness →
.claude/settings.json — written by checkride init
{
"hooks": {
"Stop": [{ "hooks": [{
"type": "command",
"command": "sh \"${CLAUDE_PROJECT_DIR:-.}/.claude/hooks/checkride-gate.sh\"",
"timeout": 900,
"statusMessage": "checkride gate — running `pnpm check`"
}] }]
}
}
The script, which checkride owns and refreshes, runs checkride gate: the repo's check with --strict --digest — <pm> run check, or mise exec -- checkride in a repo without Node — answered in the harness's own hook protocol.
AGENTS.md stanza
The rule, the conventions, and the narrow commands — loaded into context by Claude Code, Codex, Cursor, Amp.
Stop gate
The generated hook runs checkride gate when the agent tries to finish — red blocks, under Claude Code and Cursor. Guidance becomes enforcement.
CI backstop
The hook helps the agent locally; a --strict run in CI protects the branch.
04 · THE BASELINE RATCHET

Adopt in an afternoon — not after a cleanup project.

checkride init --baseline grandfathers the diagnostics a repo has today, so day-one runs pass — while any new diagnostic still fails. "Don't make it worse" becomes the definition of done for legacy code.

The baseline is a ratchet: fixing a grandfathered finding prunes it from the committed file permanently — it only ever shrinks. Debt is paid down monotonically instead of being ignored, or blocking adoption. And if a merge ever clobbers the file, checkride recover rebuilds it from git history. To see what a change grandfathers, checkride baseline diff lists the keys it adds and removes.

old debt masked · new debt fails · fixed debt pruned
checkride.baseline.json committed · only ever shrinks
{
"schema_version": 1,
"slots": {
"lint": ["src/legacy.ts:no-explicit-any:Unexpected any"],
"spell": ["docs/old.md::teh"]
}
}
Stable fingerprints (file:rule:message) that survive line moves — not raw output.
05 · THE PIPELINE

Twenty-one slots, filled by the tools you already have.

A slot is a role in the pipeline; an adapter is the concrete tool that fills it. Zero-config: checkride runs the first adapter whose config file exists and skips slots with no detected tool. The core has no runtime dependency on any checked tool — your repo owns the pinned versions. The table shows the TypeScript and repo-wide tools; the other packs fill the same slots with their own.

Opt-in slots stay out of the default run, so adopting checkride — or bumping its version — never turns a repo red on a check it didn't ask for. The six from build to snippets are the publish-ready bundle: they gate the artifact a consumer actually installs. unclaimed joins the default run once the config declares targets.

SLOT role default tool default run?
{{ row.name }} {{ row.role }} {{ row.tool }} {{ row.def }}
✅ default run · ○ opt-in — enable with --include <slot>, --all, or by naming it in checkride.config.json · with targets — runs once the config declares targets · full catalogue in the API reference
06 · PACKS AND TARGETS

TypeScript-first, not TypeScript-only.

A pack fills the slots with one language's tools, and runs each from the copy the repo pins — its lockfile, go.mod, rust-toolchain.toml, uv.lock or .terraform-version, then a mise.toml, then PATH. A monorepo names its parts as targets, and each part is checked by its pack from its own root.

{{ pk.name }}
{{ pk.what }}
{{ pk.tools }}
PINNED BY {{ pk.pins }}
custom
Anything else
A custom check runs any command, under the same timeout, exit codes and .check/ output as the rest.
checkride.config.json
{
"targets": {
"web": { "lang": "ts", "root": "apps/web" },
"api": { "lang": "go", "root": "services/api" },
"infra": { "lang": "terraform", "root": "infra" }
}
}
+A target's checks are named <target>:<slot> — api:lint, web:test — and write their raw output under .check/<target>/. The repo-wide checks run once for the whole repo, and --only api runs that one target's checks alone.
+unclaimed fails when a toolchain marker, such as a package.json or a go.mod, sits outside every target of its language: a directory of code no check reads.
+checkride target add declares a target; init and --discover propose them from the repo's own files.
+No Node? init --lang go or --lang rust writes a mise.toml that pins checkride beside the language's tools, and the gate runs mise exec -- checkride.
Every pack, target and pin → Get started in a Go or Rust repo →

Make exit 0 mean done.

Install, run init, and the gate is up — alias, agent stanza, stop-gate hooks for Claude Code and Cursor, all of it. Existing repos adopt with a baseline; new ones scaffold green out of the box.

$ pnpm dlx checkride init ⎘
Open the docs →
CHECKRIDE

An agent harness for code repositories. One command, one exit code — a portable definition of done.

Apache-2.0 · © 2026
COMMANDS
checkride init doctor fix baseline recover target agent-setup hooks gate triage qa