Adopt the runner
The reference runner is the Rust crate in this repository, version 0.1.0, edition 2024. From a checkout:
cargo test
cargo run --quiet -- validate
cargo run --quiet -- run --gate beforePR --base <base> --head <head> --json Exit 0 means accepted. Exit 1 means a requirement failed. Exit 3 means acceptance is blocked. Exit 2 means the invocation is invalid.
Decide
closeout decision \
--gate beforePR \
--base <base-commit> \
--head <candidate-commit> \
--candidate-session <id> \
--candidate-model <id> \
--json --candidate-session, --candidate-model, and --candidate-provider are the candidate identity used for independence. An empty value does not satisfy a flag set to true.
Record a review
closeout evidence add \
--gate beforePR \
--item <qualified-id> \
--base <base-commit> \
--head <candidate-commit> \
--session <reviewer-session> \
--model <reviewer-model> \
--findings findings.json findings.json is an array of finding objects. The command writes the next attempt. It does not decide acceptance.
Plugins
The plugins call the closeout binary. They do not contain a second copy of the policy. CLOSEOUT_BIN selects the binary. CLOSEOUT_BASE selects the base revision. When it is unset, the hook uses the same commit for base and head.
Claude Code reads .claude-plugin/marketplace.json. The plugin is plugins/claude. Its Stop and TaskCompleted hooks run hooks/consult.mjs.
Codex reads .agents/plugins/marketplace.json. The plugin is plugins/codex. Its Stop hook runs the same consult script.
OpenCode loads a JavaScript file from .opencode/plugins/. The file in this repository is plugins/opencode/closeout.js. On session idle it can nudge the session at most three times for one head. It does not write an accepted decision.
If stop_hook_active is set, or CLOSEOUT_HOOK=0, the consult script exits 0 and does not treat that silence as acceptance. A missing binary blocks the hook. A repository with no policy file is left alone.