Run the full pre-merge gate on the working tree, then commit — but only if every check passes. Never commit code that fails a check.
First — is there anything to ship?
Run git status --porcelain. If it is empty (no staged, unstaged, or untracked changes), stop and report "working tree clean — nothing to commit." Do not run the gate or git commit. Otherwise continue.
Detect the project's commands
Do not assume a stack. Read the manifest and config to find the real commands:
- Node:
package.jsonscripts(look forformat/prettier,lint/eslint,typecheck/tsc --noEmit,test). Respect the lockfile for the runner (pnpm/yarn/npm/bun). - Other ecosystems:
pyproject.toml/ruff/black/mypy/pytest,Cargo.toml(cargo fmt/clippy/test),go.mod(gofmt/go vet/go test),Makefiletargets,justfile, or CI workflow steps in.github/workflows. - If a stage has no configured command, say so and skip it — do not invent one or install tooling. One exception: if a
tsconfig.jsonexists andtscis available but no typecheck script is defined, still runtsc --noEmit— a real type gate a reviewer would expect.
Run the gate in this order, stopping at the first failure
- Format — run the formatter in write mode if that is the project norm. A write-mode formatter reformats files rather than "failing"; treat it as failed only if it errors (unparseable file). Its edits are picked up when you stage before committing.
- Lint — run the linter. Do not add blanket disables to pass it.
- Typecheck — run the type checker.
- Test — run the test suite (whole suite unless the project only tests changed scopes).
Prefer the exact repo-defined script over a raw tool invocation so flags/config match CI. Run scoped to changed files only if the project's own scripts do.
On the first failure — STOP
Do not run later stages. Do not commit. Report:
- Which stage and command failed.
- The essential error output (file, line, rule/type).
- The concrete fix. Apply it only if it is small, unambiguous, and clearly correct (e.g. formatter output, an import, a type annotation). Re-run from stage 1 after fixing. For anything judgemental or risky, describe the fix and hand back.
If all stages pass — stage, then self-review
Stage the intended changes first (git add the files that belong in this change, including any files the formatter rewrote), so the review sees exactly what will be committed. Then run git diff --staged and read every hunk. Reject-and-report (unstage/undo, do not commit) if you find:
- Debug leftovers:
console.log,print,dbg!, commented-out code,TODO/FIXMEyou just added. - Secrets, API keys, tokens, or hardcoded credentials.
- A stray
.only/.skip,debugger, or disabled test. - Unrelated or unintended changes, or a deleted file that looks accidental.
- Anything obviously broken the automated checks would not catch (off-by-one, wrong operator, dead branch).
Commit
Create one or more commits from the staged changes in Conventional Commits format: type(scope): subject where type is feat|fix|refactor|perf|docs|test|chore|build|ci.
- Subject: imperative mood, lowercase, no trailing period, under ~72 chars.
- Split into separate commits when the diff spans unrelated concerns; keep each commit atomic and self-consistent (stage each subset explicitly before its commit).
- Add a body only when the "why" is non-obvious. Add
BREAKING CHANGE:footer when the public API changes.
Report back
After committing, run git log --oneline -n <count> and report: each commit's short SHA and subject, which stages ran vs. were skipped (and why), and confirmation the tree is now clean. This is your final output to the caller.
Hard rules
- Never
--no-verify, never skip or weaken a check to get green, never commit with a failing stage. - Never push, open a PR, or touch remotes — this command ends at the local commit.
- Match the repo's existing commit style if it diverges from the above; conform to what is already there.