CHECKRIDE
API
Home Docs GITHUB
{{ grp.title }}
{{ it.label }}
Everything here is backed by a contract test — a change that breaks it is a breaking change.
CLI · REFERENCE

Commands

Six commands. Invoke as pnpm check (the alias init writes), or directly with pnpm exec checkride <command>. Click a command to expand its flags and an example.

{{ c.name }}
{{ c.desc }}
{{ c.icon }}
{{ c.example }}

Stream discipline: stdout carries machine output only (the summary JSON under --json; otherwise nothing). Human-readable progress goes to stderr. checkride --json | jq . is safe.

CLI · REFERENCE

Flags

The promised flag set. An unknown slot name in --only, --skip, or --include is a usage error (exit 2), never a silently-empty selection — a typo like --only lints can't quietly disable the gate.

Run flags

FLAGEFFECT
{{ f.flag }} {{ f.effect }}

init flags

FLAGEFFECT
{{ f.flag }} {{ f.effect }}
CLI · REFERENCE

Exit codes

The exit codes are a taxonomy, not a number. The 1-vs-2 distinction is a promise: a gate may safely branch on "red build" versus "fix the pipeline". checkride doctor uses the same 0/1 split for "environment ok"/"environment has problems".

0
Every executed check passed. Proceed — but see the vacuous-green rule below: ok: true with checks_run: 0 means nothing was verified.
1
At least one check failed. A verification failure: the work is not done. Read .check/summary.json, then the failing slot's raw output.
2
The harness itself broke or was misused. A malformed config, an unknown command or flag, an internal error — or --strict with zero checks run. Fix the invocation or environment; do not treat as "checks failed".

Vacuous green

"Green because everything passed" and "green because nothing ran" are distinguishable by every consumer, three ways: summary.json carries checks_run; a zero-run gets a loud stderr warning naming why each slot sat out; and --strict turns a zero-run into exit 2. Anything that gates on checkride — CI, commit hooks, other tools — should run with --strict; a human exploring a fresh repo isn't punished by default.

Timeouts and interrupts

Every check runs under a timeout by default — the check's own timeout, else the config-level one, else 600 seconds; 0 at any level disables the cap. A timed-out check is killed (SIGTERM, then SIGKILL after a short grace) and recorded as failed with a timed out after <n>s note — red, never vacuous, never hung. An interrupted run never tears an artifact and never prunes the baseline. The one uncapped catalogue default: the mutation slot ships timeout: 0 — a real stryker run legitimately outlasts the cap, and it's opt-in, never in the gate the cap protects.

OUTPUT · REFERENCE

The .check/ contract

Every run writes to .check/ (gitignored). Treat it as a stable API for agents: the summary is an index, the raw file is the truth.

The files

FILECONTENTS
{{ f.file }} {{ f.contents }}

summary.json

Versioned by schema_version (currently 1) and published as a JSON Schema that ships in the package. Additive-only discipline: under a given version, fields are added, never renamed, removed, or retyped — a consumer written against version 1 keeps parsing every 1.x summary. The checks array order is deterministic (run group sequence, not completion order).

.check/summary.json
{
"schema_version": 1,
"timestamp": "…",
"ok": true,
"checks_run": 8, // checks that actually executed
"total_duration_ms": 4200, // wall-clock for the whole run
"checks": [
{ "name": "lint", "adapter": "oxlint", "description": "…",
"ok": true, "exit_code": 0, "duration_ms": 470, "output_file": "lint.json" }
]
}

Optional per-check fields (skipped, reason, baselined) are present only when meaningful — absence is part of the shape. When a baseline masks findings, the check gains "baselined": <n> counting the grandfathered diagnostics.

The promises

Raw output stays authoritative
The per-tool files are the tool's own bytes — never normalized, reshaped, or filtered. This is the product's thesis and will not change.
Artifacts are crash-consistent
Everything is written temp-file-then-rename. A run killed at any point leaves each file previous-run-consistent or absent — never torn. Parse without guarding.
digest.md presence semantics
Written only under --digest and only when a check failed; a green run removes any stale digest. Its existence always means "this run had failures".
Deterministic ordering
The same selection produces the same checks array order on every run, even when concurrency interleaves completion.
CONFIG · REFERENCE

Configuration

checkride.config.json is optional — add it only to deviate from the defaults. The "$schema" pointer turns on validation and autocompletion in editors; the schema ships in the package.

checkride.config.json
{
"extends": "@acme/checkride-preset", // inherit a shared preset, then override
"timeout": 1200, // global per-check timeout, seconds (default 600)
"checks": {
"format": "prettier", // enable an opt-in slot
"lint": "biome", // pick an alternate adapter
"spell": false, // disable a slot
"test": { "use": "vitest", "timeout": 0, "changedArgs": ["--changed", "origin/main"] },
"tidy": { // custom check that runs FIRST
"command": "pnpm", "args": ["exec", "some-formatter", "--write"], "order": "first"
},
"licenses": { // custom check (runs last by default)
"command": "node", "args": ["scripts/check-licenses.mjs"]
}
}
}

Custom checks, detect & extends

+Custom checks (keyed by any non-slot name) run any command; the exit code participates in the gate like every built-in. They run after the catalogue by default; "order": "first" runs one ahead of everything — handy for a bespoke formatter. This is also the non-TypeScript hatch: go vet or cargo check becomes an entry, and one exit code gates the whole repo.
+"detect": ["<file>", …] gates a custom check on marker files: it runs only when one exists, and is skipped, not failed, otherwise — so a shared config stays safe across repos that don't all use the same tools.
+"extends" inherits a file path or installed package (or an array, layered left to right; local config wins). Objects deep-merge; arrays and scalars replace outright. A missing base or a self-extending loop fails fast. One org-wide preset + detect = fleet policy without a campaign of PRs.

Ordering: waves

Every check has an effective order — config entry, else adapter default, else slot default, else 'any'. Checks sharing a wave run concurrently through a bounded pool (--concurrency sizes it; --bail goes fully sequential). Numbering your own waves? Use gaps — 10, 20, 30.

ORDERTHE PROMISE
{{ o.name }} {{ o.promise }}
CONFIG · REFERENCE

The slot catalogue

A slot is a role in the pipeline (cheapest first); an adapter is the concrete tool that fills it. One blessed default per slot; alternates run if their config is detected, but init only scaffolds the blessed one. Opt-in slots join a run via --include, --all, or by being named in checks.

SLOT role blessed default alternates default run?
{{ r.slot }} {{ r.role }} {{ r.tool }} {{ r.alts }} {{ r.def }}

Zero-config: for each slot, checkride runs the first adapter whose config file exists and skips slots with no detected tool. Package-manager-agnostic: it detects pnpm/npm/yarn/bun from packageManager or the lockfile and translates pnpm exec into that manager's form. Exceptions: security is pnpm-only; pack is npm/pnpm-only — both report themselves unavailable elsewhere rather than failing.

PROGRAMMATIC · REFERENCE

Programmatic API

The CLI is the primary interface, but every command is also a function. Everything exported from the package root is public and semver-bound; everything else is internal by definition, even if technically importable.

run-checks.ts
import { runChecks, type RunResult } from 'checkride';

const result: RunResult = await runChecks({ cwd: process.cwd(), strict: true });
process.exitCode = result.exitCode;

The result carries the same summary written to .check/summary.json, plus the exit code the CLI would return.

Functions & values

{{ x.name }}

Exported types

{{ x.name }}

Versioning & pin policy

checkride follows semver. Pre-1.0, minor versions may break (the semver 0.x rule) — consumers should pin exactly ("checkride": "0.5.0", no caret; that's the -E in the install command) and upgrade deliberately. Post-1.0, a caret range is the intended usage. Any change touching a promised surface names itself in the CHANGELOG under a Contract heading — a reader scanning release notes finds every contract-relevant change without reading diffs.

THE TESTED ENVELOPE
Every push runs unit, contract, and end-to-end suites (generated projects, installed and checked for real) on macOS and Linux, at Node 22.18.0 (the exact supported floor) and Node 24, across pnpm, npm, yarn, and bun. Beyond 70% line coverage, the suite is itself tested: Stryker mutation testing runs with a hard floor. Windows is not tested and not claimed — an honest "not yet" over an asserted "works everywhere".