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.
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.
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.
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".
"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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
The result carries the same summary written to .check/summary.json, plus the exit code the CLI would return.
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.