Pre-push code quality gate that runs the thermo-nuclear code quality review skill before every git push. If the review fails, push is blocked and you get a formatted block to paste back into your agent.
Default auth is ChatGPT OAuth — the OpenAI backend signs in with your ChatGPT account via thermo-review login (no OpenAI API key). The official API and Cursor SDK are opt-in alternatives.
It runs through one of several interchangeable backends:
| Provider | Runtime | Auth |
|---|---|---|
openai (default) |
OpenAI/ChatGPT tool loop + sandboxed git/file tools | ChatGPT OAuth (thermo-review login); official API opt-in |
claude |
local claude -p agent with read-only repo tools |
Claude Code sign-in (claude / claude setup-token) |
cursor |
Cursor SDK local agent | CURSOR_API_KEY |
panel |
amalgamation — Claude reviews first, ChatGPT independently adjudicates its findings into one verdict | both Claude + OpenAI auth |
git push
→ thermo-review runs locally via the selected backend
→ VERDICT: PASS → push continues
→ VERDICT: BLOCK → push blocked, copy review into your agent
Pick the backend with --provider, the THERMO_REVIEW_PROVIDER env var, or the config file (see Providers). The default is openai using ChatGPT OAuth — run thermo-review login once, then plain git push reviews through the same repo-inspection contract as the other backends. Use --provider claude to review with the local Claude Code CLI, or --provider panel for a two-model amalgamation where Claude's point of view is fed to ChatGPT to adjudicate (see Multi-model amalgamation). The background and sources behind the review structure are in docs/review-methodologies.md.
Most pre-push hooks run linters or tests. This one runs a strict maintainability review focused on:
- Structural regressions and missed simplification opportunities ("code judo")
- Files crossing 1,000 lines
- Spaghetti branching and feature logic leaking into shared paths
- Boundary and abstraction quality
It is intentionally harsh. Passing means the change meets the thermo-nuclear approval bar, not just "it compiles."
flowchart TD
gitPush[git push] --> prePushHook["~/.git-templates/hooks/pre-push"]
prePushHook --> cli["thermo-review hook run"]
cli --> scope[Resolve commits being pushed]
scope --> backend{Provider}
backend -->|cursor| cur[Local Cursor SDK agent]
backend -->|openai| oai[OpenAI/ChatGPT tool loop + git/file tools]
backend -->|claude| cla["Local claude -p agent (read-only repo tools)"]
backend -->|panel| pan["Claude reviews → ChatGPT adjudicates (amalgamation)"]
cur --> parse[Parse VERDICT + SUMMARY]
oai --> parse
cla --> parse
pan --> parse
parse -->|PASS| allow[exit 0 — push proceeds]
parse -->|BLOCK| block[Formatted review + exit 3]
The agent reviews the git diff in scope, inlines the thermo-nuclear skill instructions, and must respond with a machine-parseable verdict before the full review body. The Cursor backend uses the local agent's built-in shell/file access; the OpenAI backend is given sandboxed git_diff / git_log / read_file / list_files tools scoped to the repo root.
thermo-review resolves the backend in this order: --provider <name> flag → THERMO_REVIEW_PROVIDER env → ~/.config/thermo-review/config.json ({"provider": "cursor"}) → default openai.
For the OpenAI provider, auth resolves as: THERMO_REVIEW_OPENAI_AUTH env → config openaiAuth → default chatgpt (ChatGPT OAuth). Set THERMO_REVIEW_OPENAI_AUTH=api or {"openaiAuth":"api"} to use the official API instead.
Uses ChatGPT OAuth (chatgpt auth mode) with sandboxed repo tools, not an inlined/truncated diff. Sign in once, then plain thermo-review review or git push uses this transport:
thermo-review login # opens a browser, completes OAuth on localhost:1455
thermo-review review --provider openai
thermo-review logout # remove stored credentialsCredentials are cached at ~/.config/thermo-review/openai-auth.json (mode 0600) and refreshed automatically before expiry.
The model defaults to gpt-5.5 (run with medium reasoning effort) and is overridable with THERMO_REVIEW_OPENAI_MODEL or the config-file openaiModel key. The OpenAI backend must use git_diff, git_log, list_files, and read_file before returning a verdict; oversized tool outputs return explicit errors so the model narrows scope instead of silently reviewing a prefix.
To use the official API instead of the default ChatGPT OAuth transport, opt in with THERMO_REVIEW_OPENAI_AUTH=api or config {"openaiAuth":"api"} and provide an API key:
export OPENAI_API_KEY="sk-..."
THERMO_REVIEW_OPENAI_AUTH=api thermo-review review --provider openai
⚠️ Known risks — experimental ChatGPT auth. The default ChatGPT OAuth path uses your ChatGPT subscription (not OpenAI Platform API credits) by calling the same ChatGPT backend the Codex CLI uses, and it sends Codex-styleoriginator/User-Agentheaders so the backend accepts the request. OpenAI's own docs steer programmatic/automation workflows toward API keys. Driving an automated pre-push gate this way is a gray area: requests consume your ChatGPT plan allowance (rolling rate limits), and abuse "may result in rate limits, suspension, or termination." Use it for single-user local review only; do not pool or share tokens. The OAuth client id, endpoints, model availability, and backend request shape are reverse-engineered from Codex and undocumented by OpenAI — they can change without notice and break sign-in or reviews.
Runs the review through the local Claude Code CLI in headless print mode (claude -p), which inspects the repo with its own file/git tools — restricted here to a read-only allowlist (Read, Grep, Glob, and read-only git commands), so the reviewer can read the diff, history, and surrounding files but cannot modify the repo.
claude # sign in once (interactive), or:
claude setup-token # long-lived token for hooks/CI
thermo-review review --provider claudeThe model defaults to opus (the latest Opus — the strongest reviewer) and is overridable with THERMO_REVIEW_CLAUDE_MODEL (alias opus/sonnet/haiku, or a full model id) or the config-file claudeModel key. Override to sonnet for a faster, cheaper gate. Reasoning effort defaults to high and is tunable with THERMO_REVIEW_CLAUDE_EFFORT (low|medium|high|xhigh|max). Use a capable model: the gate's strict "verdict line first" output contract needs opus/sonnet; haiku is not recommended. If claude is not on PATH in your hook environment, set THERMO_REVIEW_CLAUDE_BIN to its absolute path. The run is bounded by THERMO_REVIEW_CLAUDE_TIMEOUT_MS (default 600000 — generous because opus+high is slow on a large diff).
thermo-review review --provider cursor
# or: THERMO_REVIEW_PROVIDER=cursor / {"provider":"cursor"} in config.jsonRequires CURSOR_API_KEY and the Cursor IDE / local agent bridge. See setup below.
--provider panel runs two different models and combines them adversarially:
- Claude reviews the diff first (via the
claudebackend above). - ChatGPT then reviews the same diff independently and adjudicates Claude's findings — confirming, refuting, or refining each, and adding what Claude missed — into a single amalgamated verdict.
The non-obvious part is making the second model refine rather than rubber-stamp the first. Naively handing model B model A's verdict triggers well-documented sycophancy + anchoring (a model shown a confident upstream conclusion drifts toward agreement). The panel prompt defends against this with techniques from the LLM-as-judge / ensemble literature:
- Claude's PASS/BLOCK verdict is withheld — ChatGPT sees only the findings, not the conclusion.
- Independent-first — ChatGPT gathers its own evidence and forms its own findings before reading Claude's.
- Per-item CONFIRM / REFUTE / REFINE, each tied to a concrete failure scenario, with explicit license to call false positives.
- Neutral attribution ("an independent first-pass reviewer") so it doesn't defer to "the other model".
The final verdict, summary, and decisions ledger all come from the ChatGPT (adjudicator) leg, so panel behaves like any single backend to the rest of the gate. The panel is atomic and fails closed: if the Claude leg fails (timeout, malformed output, or auth), the push is blocked rather than silently downgrading to a single-model review — when you ask for two-model adjudication and it can't happen, the gate must not quietly review with one model and maybe pass. Requires both Claude sign-in and OpenAI auth.
thermo-review review --provider panel
# or: THERMO_REVIEW_PROVIDER=panel / {"provider":"panel"} in config.jsonThe methodology and primary sources behind this design are in docs/review-methodologies.md.
| Requirement | Notes |
|---|---|
| Node.js 22+ | node -v |
| git | Any recent version |
| ChatGPT account | Default OpenAI auth — used via thermo-review login |
| OpenAI API key | Optional official API auth — OPENAI_API_KEY + THERMO_REVIEW_OPENAI_AUTH=api |
| Cursor IDE | Cursor provider only — with CLI / local agent bridge working |
| Cursor API key | Cursor provider only — Dashboard → Integrations |
The thermo-nuclear skill is bundled with the package, so no plugin install is required. If you have the cursor-team-kit plugin installed in Cursor, its copy is used automatically; otherwise the bundled copy is used. Override either with THERMO_REVIEW_SKILL_PATH=/path/to/SKILL.md.
From source (recommended today):
git clone https://github.com/pzep1/thermo-review-cli.git
cd thermo-review-cli
npm install
npm run build
npm linkVerify:
thermo-review --version
thermo-review --helpYou should see the review and hook subcommands.
Default OpenAI provider:
thermo-review loginOfficial OpenAI API instead:
export OPENAI_API_KEY="sk-..."
export THERMO_REVIEW_OPENAI_AUTH=apiCursor provider:
export CURSOR_API_KEY="cursor_..."Hooks do not always inherit your shell profile. A dedicated env file is more reliable for API-key based modes:
mkdir -p ~/.config/thermo-review
cat > ~/.config/thermo-review/env <<'EOF'
# Optional: use the official OpenAI API instead of default ChatGPT OAuth
# export OPENAI_API_KEY="sk_YOUR_KEY_HERE"
# export THERMO_REVIEW_OPENAI_AUTH=api
# export CURSOR_API_KEY="cursor_YOUR_KEY_HERE" # only for --provider cursor
EOF
chmod 600 ~/.config/thermo-review/envThe pre-push hook sources this file automatically when present; ChatGPT OAuth tokens from thermo-review login are read from ~/.config/thermo-review/openai-auth.json.
Choose based on whether you want this on new repos only or all repos.
thermo-review hook installSets git config --global init.templateDir ~/.git-templates. Repos you git init after this inherit the hook.
thermo-review hook install --global-hooks-pathThis also sets git config --global core.hooksPath ~/.git-templates/hooks, so existing clones use the hook too.
Confirm installation:
ls -la ~/.git-templates/hooks/pre-push
git config --global --get init.templateDir
git config --global --get core.hooksPath # if you used --global-hooks-pathGlobal core.hooksPath bypasses .git/hooks/. Preserve your old hook by renaming it:
mv .git/hooks/pre-push .git/hooks/pre-push.localAfter thermo-review passes, pre-push.local runs automatically.
Before relying on the hook, run a manual review in a real repo:
cd ~/path/to/your-project
git checkout your-feature-branch
thermo-review reviewExpected outcomes:
- PASS — prints
VERDICT: PASS — <summary>, exit code 0 - BLOCK — prints a bordered report with "COPY BELOW INTO CURSOR AGENT", exit code 3
- Config error — not signed in (
thermo-review login) or missing API key when using api auth, exit code 1 with setup instructions
Try JSON output for scripting:
thermo-review review --jsoncd ~/path/to/your-project
git pushYou should see [thermo-review] progress lines on stderr while the agent runs.
Escape hatches:
git push --no-verify # skip all pre-push hooks
THERMO_REVIEW_SKIP=1 git push # skip thermo-review onlythermo-review review
thermo-review review --base main
thermo-review review --provider openai # use the OpenAI backend (ChatGPT auth by default)
thermo-review review --quiet # verdict line only
thermo-review review --json # machine-readable
thermo-review review --skip # no-op, exit 0Every git push runs the review on commits being pushed:
- Update push — diff from remote tip to local tip
- New branch — diff from merge-base with
main/masterto HEAD
Override base branch:
thermo-review review --base develop- Read the summary and priority findings in the terminal
- Copy the section under COPY BELOW INTO CURSOR AGENT
- Paste into Cursor and fix the blockers
- Push again:
git push
The full report is also saved to .git/thermo-review-last.md in your repo for re-copy without re-running.
Example agent prompt prefix:
/thermo-nuclear-code-quality-review
Fix these blockers from pre-push review on branch my-feature:
...
A strict reviewer is only useful if its feedback converges. Without memory, push 2 can suggest the opposite of push 1 ("extract this into a module" → "inline this back"), and the branch circles forever.
To prevent that, each blocked review records its standing structural decisions in a per-branch ledger and reads them back on every subsequent review:
- Location — under the repo's git dir at
thermo-review/tnuk/<branch>.md(typically.git/thermo-review/tnuk/, and the correct per-worktree git dir for linked worktrees). The<branch>part is slugged —/and other non-[A-Za-z0-9._-]characters become-, and a lossy slug gets a short hash suffix so distinct branches never collide. Runls "$(git rev-parse --git-dir)/thermo-review/tnuk/"to find your branch's file, thencatit. Local-only, never committed, never part of the reviewed diff. - On BLOCK — the review emits a delimited decisions block; it is folded into the ledger along with a compact history of blocking rounds.
- On the next review — those decisions are injected into the prompt as binding context. The review must build on them and may not silently reverse a prior decision. A genuine course-correction is allowed only as an explicit, justified reversal on the record — so a wrong early call can still be fixed, but the feedback can't oscillate.
- On a passing push — the ledger is wiped automatically (the branch converged → clean slate for the next piece of work).
The manual thermo-review review dry run is governed by the ledger exactly like a push — it reads prior decisions as binding context and records new ones on BLOCK. Only a real passing push wipes it.
Disable the ledger entirely with THERMO_REVIEW_NO_TNUK=1. The ledger is an aid, never a gate: if reading or writing it ever fails, the review proceeds and the verdict is unaffected.
The agent must start its response with exactly:
VERDICT: PASS|BLOCK
SUMMARY: <one sentence, max 120 chars>
Then the full review. If these lines are missing, the hook fails closed (BLOCK).
| Code | Meaning |
|---|---|
0 |
PASS — push allowed |
1 |
SDK/client startup or config error (check ChatGPT login or API-key auth mode) |
2 |
Agent run error |
3 |
BLOCK — push blocked |
This only applies when you opt into official API auth with THERMO_REVIEW_OPENAI_AUTH=api or config {"openaiAuth":"api"}. Create ~/.config/thermo-review/env (see step 3) or export OPENAI_API_KEY in your shell.
Only needed for --provider cursor. Create ~/.config/thermo-review/env (see step 3) or export the variable in your shell.
A copy of the skill ships with the package, so this should be rare. If you set THERMO_REVIEW_SKILL_PATH or a config skillPath, make sure it points at a readable SKILL.md. Resolution order: THERMO_REVIEW_SKILL_PATH → config skillPath → Cursor plugin cache → bundled copy.
--provider claude (and panel) shell out to the Claude Code CLI. If you see "Claude CLI not found on PATH", install Claude Code and ensure claude is on PATH, or set THERMO_REVIEW_CLAUDE_BIN to its absolute path (hooks don't always inherit your shell PATH). If reviews fail with an auth error, sign in with claude once, or create a long-lived token for hooks/CI with claude setup-token. The default opus (or sonnet) follows the strict "verdict line first" contract; haiku is too weak for it, so don't override the model to haiku.
The default OpenAI auth mode uses ChatGPT OAuth. Run thermo-review login to complete the experimental Sign in with ChatGPT flow. If the browser does not open, copy the printed URL manually. The callback listens on localhost:1455 (falls back to 1457) — make sure that port is free and not blocked by a firewall. If reviews start failing with auth errors after working before, run thermo-review login again (the OAuth client and backend are undocumented and can change).
Run npm link from the cloned repo, or add the project's dist/cli.js to your PATH.
Check global git config:
git config --global core.hooksPath
cat ~/.git-templates/hooks/pre-pushEnsure thermo-review is on PATH in non-interactive shells (npm link usually handles this).
A full agent review takes as long as one agent turn (often 1–5+ minutes), on either backend. This is expected. Use THERMO_REVIEW_SKIP=1 or --no-verify when you need an emergency push.
Make at least one commit before running review.
- Confirm Cursor is installed and the local agent bridge works
- Verify API key at cursor.com/dashboard/integrations
- Try
thermo-review reviewmanually and read the full error on stderr
thermo-review logout # remove stored OpenAI credentials (if used)
thermo-review hook uninstall
npm unlink -g thermo-review-cli
rm -rf ~/.config/thermo-review # optional, removes API key + OpenAI auth filesSee CONTRIBUTING.md.
git clone https://github.com/pzep1/thermo-review-cli.git
cd thermo-review-cli
npm install
npm run dev # watch modesrc/
cli.ts Commander entrypoint (review, login, logout, hook)
config.ts Provider/key/skill resolution + config file
types.ts Shared types (ProviderId, ReviewScope, …)
review/
run.ts Orchestrator: skill → prompt → backend → parse → format
backend.ts ReviewBackend interface + BackendError
provider.ts Backend selection (lazy-imports the chosen backend)
backends/cursor.ts Cursor SDK runner
backends/openai.ts OpenAI backend orchestration + evidence gate
backends/openai-transport.ts Shared OpenAI transport contract
backends/openai-api-transport.ts Official OpenAI API adapter
backends/chatgpt-codex-transport.ts Experimental ChatGPT/Codex adapter
backends/claude.ts Claude backend: claude -p CLI runner (read-only tools)
backends/panel.ts Panel backend: Claude → ChatGPT amalgamation
amalgamate.ts Anti-rubber-stamping adjudication prompt builder (shared)
tools.ts Typed sandboxed git/file tools + evidence tracker
prompt.ts Review prompt builder (shared)
parse-verdict.ts VERDICT/SUMMARY parser (shared)
format-blocked.ts Terminal output formatter (shared)
auth/
openai-oauth.ts Sign in with ChatGPT PKCE flow
token-store.ts Credential storage + refresh
jwt.ts id_token claim decoding
openai-endpoints.ts Experimental ChatGPT OAuth constants
openai-private-backend.ts Narrow ChatGPT/Codex compatibility layer
git/push-scope.ts Pre-push diff scope
hook/install.ts Hook install/uninstall
templates/hooks/pre-push Shell hook template
templates/skills/thermo-nuclear/SKILL.md Bundled review skill
- Review rubric from cursor-team-kit thermo-nuclear skill
- Built with @cursor/sdk, the OpenAI Responses API, and the Claude Code CLI
- Experimental "Sign in with ChatGPT" OAuth flow modeled on the OpenAI Codex CLI
- Multi-model amalgamation design and sources documented in docs/review-methodologies.md