Docs
Install the plugin
Two commands in a Claude Code session.
/plugin marketplace add Grade-Inc/corund-plugin /plugin install corund
It needs Python 3.9+ (the system python3 on macOS is enough, and no
third-party packages), git, and a JS test runner on PATH. Jest and
Vitest are what this version was built and measured against. The plugin is MIT-licensed, runs
entirely on your machine, and uploads nothing.
What the hook checks
The new and changed tests on this change — nothing else.
It reads your working tree against HEAD, takes the test files you touched, and runs
only those. It does not run your suite, and it does not look at tests you did not change.
On stop, Corund builds a candidate commit from the dirty tree, reverts the
non-test half against HEAD, and runs those tests on both trees. If a test passes
with the change and passes without it, the stop is refused with two lines:
corund: these tests pass with your non-test changes reverted — they do not test what you wrote. Write a test that fails against the old code, then finish.
The plugin also denies git commit --no-verify and
HUSKY=0: commit hooks cannot be skipped from inside the session.
Every invocation appends to ~/.corund-hook.log — what was
detected, the verdict, and what was returned. A hook that did nothing and a hook that never ran
are otherwise indistinguishable.
When it stays quiet
Silence is the normal case. Corund allows the stop, and prints nothing, when:
- no test file changed against
HEAD— the common case, and it costs nothing - only test files changed — there is no non-test diff to revert
- the change touches no source file — reverting a README, a config file or a lockfile cannot make a test fail, so a test passing without it proves nothing about it
- the tests do fail on the reverted tree — the change is covered
- it could not measure: a missing runner, a timeout, a crash, a tree mid-rebase
That last one is deliberate. A check that blocks on its own blindness is an accusation without proof, and this tool exists to refuse exactly that.
Configuration
Environment variables, all optional:
- CORUND_TEST_COMMAND — default
npx jest - CORUND_RUNNER — default
auto - CORUND_TEST_GLOBS — which paths count as tests
- CORUND_TIMEOUT_MINUTES — default 5
Limits
Stated rather than footnoted.
It proves a test ran and fails on revert. It does not verify the test asserts the correct value. A change whose code and test are wrong in agreement is not decided.
It catches a new test that does not exercise new code. It does not catch a weakened test on an existing function.
On JavaScript, the block is green-on-revert only. A test that
fails on the reverted tree by erroring — TypeError: discount is not a function,
a failed import — is not a witness. Corund reports that as unproven and allows the stop. It
does not treat a crash as proof that your test works.
On Jest, a failed assertion carries no frame, so Corund cannot confirm the red came from the test's own body rather than a helper or a setup file. The receipt says so when it applies.
Also a GitHub Action
The same red-on-revert engine runs on pull requests in your own CI, where it covers Python as well. It is a separate, open-source thing and it is not what this page is about — the plugin above needs none of it. If you want it on the PR too, the Action's own repository has the workflow snippet and its inputs.
Scope
Corund catches agents that take shortcuts to green. It does not defend against an attacker who already controls your test infrastructure -- that's a different problem, and the receipt names where that line is.
Security
The plugin runs locally and talks to nothing. It reads your working tree, makes
a temporary index and a throwaway commit object, runs your test command in a temporary worktree,
and deletes it. HEAD, your index and your stash are never touched, and no code,
diff or test output leaves your machine.
The Action runs inside your own CI with the permissions your workflow grants it:
checks: write, pull-requests: write, contents: read.
Customer code is not retained past run scope — the diff, the reverted tree and the full
test output exist only for the duration of the check run. The receipt itself is kept, and a
finding can quote the single line that triggered it, up to 160 characters.
Sources
Every figure this site cites is pinned to a named external report, with report, publisher, date, and URL, in one place: /dossier#sources. A figure that cannot be pinned is dropped, not softened.