Skip to content

Repository files navigation

Unified Check Command

One command to lint, format check and type check your project, and keep a monorepo consistent. uncheck runs the oxlint, oxfmt, tsc and sherif you installed, so you, your git hooks and your coding agents all run the same check.

npx uncheck init    # set up your project, step by step
npx uncheck         # check everything
npx uncheck --fix   # fix what can be fixed, report the rest

Get started

You need Node 22.20 or later and a package.json. In your project folder, run:

npx uncheck init   # or pnpm dlx, yarn dlx, bunx

init asks up to four quick questions. Space toggles a choice and Enter confirms.

  1. Which tools to install: oxlint and oxfmt, plus sherif at a workspace root. It lists the ones you're missing, all selected.
  2. Which tools get a preset config: opt-in, nothing is preselected.
  3. Whether to check every commit: adds a pre-commit hook.
  4. Which coding agents run it after each turn: agents whose config folder you have, such as .claude or .github/hooks, are preselected.

Then it installs everything with your package manager and sets it up:

$ npx uncheck init
uncheck init in /home/me/my-app
✔ Which tools should uncheck install? …  oxlint, oxfmt
✔ Which tools should get a config from the middleapi preset? …  oxlint, oxfmt
✔ Check the staged files before every commit? … yes
✔ Which agents should run uncheck when they finish a turn? …  Claude Code
▶ npm install --save-dev uncheck oxlint oxfmt
✔ oxlint oxlint.config.ts created
✔ oxfmt oxfmt.config.ts created
✔ package.json scripts check, fix and prepare written
✔ pre-commit .git/hooks/pre-commit created
✔ Claude Code .claude/settings.json created

Run npm run check to check the project, and npm run fix to fix what can be fixed.

That's it! TypeScript is up to you: tsc joins in once you install it and add a tsconfig.json. init keeps any check or fix scripts you already have, and running it again only sets up what's missing.

Set up without questions

Without a terminal, npx uncheck init --yes takes the default answers: it installs the missing tools, checks every commit, and sets up the agents whose config folders exist. It writes no preset config.

Or set up by hand: npm i -D uncheck oxlint oxfmt typescript (plus sherif in a monorepo), then add the scripts "check": "uncheck", "fix": "uncheck --fix" and prepare.

Check your project

Run npx uncheck, or npm run check, locally and in CI. Every check runs, even after one fails, so one run shows you every problem:

$ npx uncheck
uncheck in /home/me/my-app
○ sherif skipped, not installed
▶ oxlint --ignore-pattern=node_modules --no-error-on-unmatched-pattern
✔ oxlint passed 67ms
▶ oxfmt --check --no-error-on-unmatched-pattern
Format issues found in above 2 files. Run without `--check` to fix.
✘ oxfmt failed 65ms
▶ tsc -p tsconfig.json --noEmit
src/index.ts(1,14): error TS2322: Type 'string' is not assignable to type 'number'.
✘ tsc failed 384ms

✘ 2 of 3 checks failed: oxfmt, tsc
  rerun with `--fix` to apply oxfmt fixes

A check runs only when your project uses its tool, with the version and config you already have:

Check Checks Runs when
sherif monorepo consistency sherif is installed (needs 1.10+), at the workspace root
oxlint lint rules oxlint is installed (needs 1.60+)
oxfmt formatting oxfmt is installed
tsc types the project has a tsconfig.json

uncheck exits with code 1 when a check fails, when no check could run, or when there's a tsconfig.json but no TypeScript, so a broken setup never passes quietly.

Fix what can be fixed

npx uncheck --fix   # or npm run fix

This applies oxlint's fixes, rewrites the formatting with oxfmt, and applies sherif's fixes, after which sherif runs your install. Type errors are yours to fix.

Pick the checks

npx uncheck --only=oxlint --only=oxfmt   # run only these checks
npx uncheck --skip=tsc                   # skip a check
npx uncheck --require=tsc                # fail when tsc cannot run, instead of skipping it
npx uncheck --cwd packages/app           # run in another folder

You can repeat --only, --skip and --require, and the commit and agent hooks take them too. Run npx uncheck <command> --help to see every flag.

Check specific files

npx uncheck src/index.ts src/cli.ts   # files
npx uncheck src/app                   # a folder
npx uncheck 'src/**/*.test.ts'        # a glob, quoted so your shell leaves it alone
npx uncheck src '!src/generated'      # a folder, minus a part of it
npx uncheck '!**/*.gen.ts'            # everything except some files

Every tool gets the same file list, so they never disagree about what a path means.

  • Folders and globs match the files git knows about: tracked, or new and not ignored.
  • A path that exists is never read as a glob, so 'app/[id]/page.tsx' just works.
  • A path that matches nothing fails the run, unless you pass --no-error-on-unmatched-pattern.
  • tsc checks only the projects that include those files, and the projects that depend on them. sherif runs only when a package.json or pnpm-workspace.yaml is among them.

Check every commit

Said yes to this in init? You're all set. (If you ran init before git init, run npm run prepare once to write the hook.)

Otherwise, run npx uncheck init again and say yes, or add the prepare script yourself and run npm run prepare once. Every clone then gets the hook on install:

{
  "scripts": {
    "prepare": "uncheck prepare --pre-commit"
  }
}

Already have a prepare script? Chain them: "prepare": "husky && uncheck prepare --pre-commit".

Now every commit runs uncheck staged --fix, no lint-staged needed. It checks your staged files, fixes what oxlint and oxfmt can, and stages those fixes:

uncheck staged in /home/me/my-app
○ sherif skipped, not installed
▶ oxlint --fix --no-error-on-unmatched-pattern src/y.ts
✔ oxlint passed 111ms
▶ oxfmt --no-error-on-unmatched-pattern src/y.ts
✔ oxfmt passed 106ms
○ tsc skipped, no tsconfig.json found
✔ staged the fixes to src/y.ts

✔ all checks passed (oxlint, oxfmt)

A failing check stops the commit. In a hurry? git commit --no-verify skips the hook.

To change the hook, add flags to the prepare script and run it again, for example "prepare": "uncheck prepare --pre-commit --only=oxlint --only=oxfmt". A flag you pass only by hand is undone by the next install.

Flag Effect
--no-fix Only check, never change your files
--allow-empty Let a commit through when the fixes undo every staged change
--only, --skip, --require Pick the checks the hook runs

Your work stays safe. After git add -p, the unstaged part of a file is set aside during the checks and put back afterwards, even if you press Ctrl-C. If a fix clashes with your unstaged changes, every fix is undone and the commit stops.

Good to know:

  • tsc checks whole projects as they are on disk, so it can report errors in files you didn't stage, or pass thanks to one you forgot to git add. --only=oxlint --only=oxfmt keeps the hook fast and limited to what you staged.
  • Yarn 2+ doesn't run prepare, so use postinstall (init does this for you). In a package you publish, turn postinstall off while packing, for example with pinst.
  • Production installs that skip devDependencies (npm ci --omit=dev, NODE_ENV=production) still run the script, but without uncheck. Append || exit 0 so they pass: "prepare": "uncheck prepare --pre-commit || exit 0".
More about the pre-commit hook
  • An existing shell hook is kept, and uncheck adds its own line. With husky 9 or Vite+, it writes the pre-commit file they run.
  • When uncheck can't safely write the hook, for example outside git, with a global core.hooksPath or when the existing hook isn't a shell script, it says why and your install keeps working.
  • Removing uncheck? Also delete its line from the pre-commit hook and its entries in your agent configs, or they fail.

Check every agent turn

Let your coding agent clean up after itself. Picked your agents in init? You're all set. Otherwise, add uncheck to your project (npm i -D uncheck, the hook never downloads it) and pick your agents from the list:

$ npx uncheck hooks install
✔ Which agents should run uncheck when they finish a turn? …  Claude Code
✔ Claude Code .claude/settings.json created

The hook runs npx --no uncheck hooks run --fix whenever the agent finishes a turn.

In a script, name them instead: npx uncheck hooks install claude cursor.

Agent Name Config file
Claude Code claude .claude/settings.json
CodeBuddy codebuddy .codebuddy/settings.json
Cursor cursor .cursor/hooks.json
GitHub Copilot copilot .github/hooks/uncheck.json

When the agent finishes a turn, the hook checks the files changed since the last commit and fixes what oxlint and oxfmt can. If problems remain, it sends the agent back to fix them, but never twice in a row, so an agent can't get stuck in a loop. Outside git, or before the first commit, it checks and fixes the whole folder.

  • Too slow or noisy? tsc checks whole projects, so it can flag type errors the agent didn't cause. Leave type checks to CI: npx uncheck hooks install --only=oxlint --only=oxfmt. Install again to change the flags.
  • Avoid double runs. Cursor and Copilot CLI also run the hooks in .claude/settings.json. If you set up claude, add cursor or copilot only where they don't read that file.
  • Your config is kept. Other hooks and settings stay, but comments in the file are lost.

Monorepos

Run both init and uncheck at the workspace root: the folder whose package.json has workspaces or whose pnpm-workspace.yaml lists packages. One run checks every package.

sherif checks the workspace as a whole, so it runs only at the root. Configure it in the sherif field of the root package.json, as sherif documents. It only reports in the hooks and when CI is set, so run npx uncheck --fix locally to apply its fixes. Leave out "fix": true, or every run that only reports fails.

TypeScript. uncheck finds every tsconfig.json and follows their references, so a config with another name, like tsconfig.app.json, is checked when a reference leads to it. Projects linked by references are built together with one tsc -b, and the rest are checked with tsc -p --noEmit.

Hooks. The root's pre-commit hook already covers every package. A package whose own prepare script runs uncheck prepare --pre-commit adds a line with its own flags, which also runs when a commit touches that package. An agent hook installed in a package checks only that package; add uncheck to the workspace root too if the agent may work outside it.

How uncheck runs tsc
  • tsc -b writes what your configs ask for, such as declarations. A project in a references graph that would write JavaScript next to its sources, like the tsconfig.node.json of older Vite templates, is checked with tsc -p --noEmit instead, and so are the projects that reference it. If git ignores that JavaScript, the project stays in tsc -b.
  • In a folder without its own tsconfig.json, such as a package, uncheck uses the nearest tsconfig.json above it in the same git repository, if that one includes files of the folder.

Presets

uncheck ships the lint, format and TypeScript configs the middleapi projects share. They're optional, and init can write the oxlint and oxfmt ones for you. They need oxlint 1.70+, oxfmt 0.43+ and TypeScript 5.6+. Copying one by hand? Name the file .mts unless your package.json has "type": "module".

// oxlint.config.ts: oxlint's defaults plus a few rules that catch real bugs
import { defineConfig } from 'oxlint'
import { middleapi } from 'uncheck/oxlint'

export default defineConfig({ extends: [middleapi] })
// oxfmt.config.ts: no semicolons, single quotes and sorted imports
import { defineConfig } from 'oxfmt'
import { middleapi } from 'uncheck/oxfmt'

export default defineConfig({ ...middleapi })
// tsconfig.json: extend `uncheck/tsconfig/middleapi` to type check only,
// or `uncheck/tsconfig/middleapi/lib` in a package that emits declarations to dist
{
  "extends": "uncheck/tsconfig/middleapi",
  "compilerOptions": { "types": ["node"] },
  "include": ["src"],
}

The tsconfig presets include no Node.js or browser types, so add the ones you need: "types": ["node"] for Node.js, or "lib": ["ES2022", "DOM", "DOM.Iterable"] for browsers.

Troubleshooting

"✘ nothing to check". No check could run, and the message says why. Usually no tool is installed yet, so run npx uncheck init. A "○ nothing to check" line is only a notice, and the run passes.

A path looks like a command, a flag or an exclusion. Start it with ./: ./staged, ./-draft.ts, './!notes.ts'.

npx uncheck dist says "No files match". git ignores that folder. You can still name an ignored file directly.

"detected dubious ownership". git doesn't trust a repository another user owns, for example in a container mount, so the hooks can't run there. Run the safe.directory command that git status prints, then npm run prepare to write the hook.

A commit stops with "An earlier run left the unstaged versions of your files in …". A pre-commit run was killed, or couldn't put your unstaged changes back. Unless another commit is still running, copy back what your files are missing from that folder, delete it, and commit again.

Sponsors

Like what we build over at middleapi? You can help keep it going through GitHub Sponsors or Open Collective. Every bit helps! 🚀

ScreenshotOne.comScreenshotOne.com
The screenshot API for developers
YuzuYuzu
We're hiring NYC based engineers
MisskeyHQMisskeyHQ
Decentralized microblogging SNS born on Earth

Special Sponsors

Guillermo Rauch
Guillermo Rauch

Premium Sponsors

Nexa
Nexa

Organization Sponsors

LN Markets
LN Markets

Sponsors

Reece McDonald
Reece McDonald
あわわわとーにゅ
あわわわとーにゅ
nk
nk
supastarter
supastarter
herrfugbaum
herrfugbaum
Ryota Murakami
Ryota Murakami
David Cramer
David Cramer
Valerii Petryniak
Valerii Petryniak
Valerii Strilets
Valerii Strilets
Kyle Mistele
Kyle Mistele
christ12938
christ12938
Ryan Soderberg
Ryan Soderberg
shota
shota
Ellis Driscoll
Ellis Driscoll
Hoang Nguyen
Hoang Nguyen
Orestis Ioannou
Orestis Ioannou
Stefan Smiljkovic
Stefan Smiljkovic

Backers

David Walsh
David Walsh
IPv4Addr
IPv4Addr
Robbe Vaes
Robbe Vaes
Aidan Sunbury
Aidan Sunbury
soonoo
soonoo
Kevin Porten
Kevin Porten
Denis
Denis
Christopher Kapic
Christopher Kapic
Tom Ballinger
Tom Ballinger
Sam
Sam
Titoine
Titoine
Igor Makowski
Igor Makowski
hanayashiki
hanayashiki
Lev Dubinets
Lev Dubinets
Kelly Peilin Chan
Kelly Peilin Chan
Guy Ariely
Guy Ariely
PaulSenon
PaulSenon
Shotaro Nakamura
Shotaro Nakamura
Alex
Alex
Andrey Gubanov
Andrey Gubanov

With thanks to 38 past sponsors who helped get us here.

License

Distributed under the MIT License. See LICENCE for more information.

About

Unified check command that lints, formats, and type checks in one command for your project

Resources

Code of conduct

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages