checkride ships a Claude Code plugin from the package root: /checkride:check (triage a red gate to one root cause) and /checkride:qa (read the quality artifacts — mutation, dead, dupes, health). Bundled, not published separately: same npm package, same version, one install — which is what keeps a reader and the format it reads from drifting apart.
The plugin runs nothing new. No extra command, no flag, no config, no hook. Both skills read what a checkride run already wrote; the check skill additionally runs the repo's existing check script so there is something current to read.
Reading the summary by hand is the procedure the AGENTS.md stanza teaches, and it works — but it is wrong in two directions at once. It is unbounded: in checkride's own repo, mutation.json is 2.2 MB — opening it spends the context you need for the fix. It is credulous: every run overwrites summary.json, so an ok: true on disk may describe three of seventeen slots from fourteen minutes ago. So each skill pairs a deterministic reader — measures artifacts instead of opening them, pins the schema, dates every file against the run that claims it — with model judgment: ranking simultaneous failures into one root cause, or deciding which surviving mutant deserves a test.
There is no build step — the readers ship prebuilt in the package's dist/ and import node: builtins only. And nothing about the plugin is required: the CLI, exit codes, and .check/ contract are unchanged whether it is installed or not.
Both readers are first-class CLI commands, so any agent — or you — can run them directly. What you lose is the skill: the procedure and the judgment that turn a report into a diagnosis.
What the skills deliberately do not do: fix anything (proposing the fix is the deliverable), normalize tool output (the raw file is the truth), gate (the readers report; the gate gates), or touch the baseline — when the run's own warnings say a merge clobbered checkride.baseline.json, the skill names checkride recover as the remedy, and running it stays the human's call.
checkride wires both the same way — a stop gate, an edit marker, a baseline guard, and the two reader skills; checkride agent-setup writes all of it (Cursor is included when .cursor/ exists, or with --harness cursor). The wiring is symmetric. The harnesses are not.
The Cursor gate's config entry (timeout: 900, loop_limit: null, failClosed: true) is checkride-owned — edits are restored on the next agent-setup. The supported off switch is --remove-hook gate, not waiting out a loop cap.
With "Include third-party Plugins, Skills, and other configs" enabled — and it ships enabled by default — Cursor loads hooks from .claude/settings.json too and runs all matching hooks from every source. A repo wired for both harnesses would fire two full pipelines for one turn, into one .check/. checkride resolves it inside checkride gate: when the Claude-protocol run detects a registered native Cursor gate, it stands down and lets the one gate that can speak Cursor's protocol answer. The check is deliberately narrow — it defers only when a Cursor gate is actually registered, so a stale environment variable costs a duplicate run, never a missing gate.
Cursor has a plugin system; checkride deliberately doesn't use it — a Cursor plugin can't invoke a script it shipped itself, installs per user rather than per repo, and distributes from git rather than npm. So agent-setup writes the same two skills into the repo at .cursor/skills/, from the same source files the plugin ships. Three differences follow: the names are prefixed (/checkride-check, /checkride-qa — a repo-local skill named check would shadow the bare command everywhere); they are committed and reviewed with the repo, refreshed by the next agent-setup rather than a plugin update; and they invoke checkride triage / checkride qa directly — which is exactly why those commands exist.
The third verdict is the environment's, not the code's: nothing executed, so nothing in .check/ describes the turn. The usual cause is the hook's Node.
Agent harnesses run hooks in a non-login shell. A version manager puts its shims on PATH from a shell rc file, so the hook never sees them — it gets the machine's default node, not your terminal's. With a repo pinning engines.node and the wrong Node arriving, the package manager refuses to run anything at all — while doctor, running in your terminal on the right Node, says everything is fine.
checkride handles it in two halves. It aligns when it can: if .nvmrc or .node-version names an exact version the running Node fails, the gate prepends a matching already-installed interpreter's bin to the child's PATH (searched under nvm, fnm, nodenv, asdf, volta, n — nothing downloaded, never silently: every aligned run prints the line). It names the cause when it cannot: no matching install means a "could not run" verdict that says which Node arrived, what the pin wants, and what to do — never a misleading red.
Three practical levers: add a .nvmrc so there is an exact version to align to (engines.node is a range — a compatibility declaration, not an instruction to switch interpreters); set CHECKRIDE_NODE_BIN to a bin directory to pick the interpreter yourself (off disables alignment entirely); and run checkride doctor — its node pin row answers the one question your shell can't: if a hook arrives on a different Node, can checkride put the pinned one back?