Skip to content

Repository files navigation

Unified Check Command

uncheck lints, format checks and type checks your project with one command, and keeps a monorepo consistent. It runs the tools your project already has, so you, your git hooks and your coding agents all run the same check.

npm i -D uncheck oxlint oxfmt typescript   # add sherif in a monorepo

npx uncheck                        # check everything
npx uncheck --fix                  # fix what can be fixed, report the rest
npx uncheck prepare --pre-commit   # check every commit
npx uncheck hooks install claude   # check every agent turn

uncheck needs Node 22.20 or later. Install only the tools you want: a check runs when its tool is installed and is skipped otherwise, except that a tsconfig.json without TypeScript installed fails. uncheck always uses the versions you installed.

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

Check your project

$ npx uncheck
uncheck in /home/me/my-app
β—‹ sherif skipped, not installed
β–Ά oxlint
βœ” oxlint passed 67ms
β–Ά oxfmt --check
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

Every check runs, so one run shows every problem. uncheck exits with 1 when a check fails, and also when no check could run, so a broken setup never passes quietly.

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 directory

--only, --skip and --require can be repeated and work on every command. Flags go after the command name (npx uncheck staged --fix), and npx uncheck <command> --help lists them all.

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

Check only some files

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

uncheck turns your paths into one file list that every tool gets, so they never disagree about what a path means. Directories and globs match the files git knows about (tracked, or new and not ignored), dot files included. A path that exists is never read as a glob, so 'app/[id]/page.tsx' and 'app/(marketing)/**' just work. A path that matches nothing fails the run, unless you pass --no-error-on-unmatched-pattern.

tsc then checks only the projects that include one of the files, and sherif runs only when a package.json or pnpm-workspace.yaml is among them.

Run it before every commit

Add a prepare script, so every clone sets up the hook on install, and run it once now:

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

Every commit then runs uncheck staged --fix: it checks the staged files, fixes what oxlint and oxfmt can, and stages those fixes. A failing check blocks the commit, and git commit --no-verify skips the hook. No lint-staged or simple-git-hooks needed.

prepare flag Effect
--no-fix The hook only checks and never changes your files
--allow-empty The hook lets a commit through when the fixes undo every staged change
--only, --skip, --require Written into the hook command

Run prepare again with other flags to change the hook. What it guarantees:

  • You commit what was checked. After git add -p, the unstaged part of a file is set aside while the checks run and put back afterwards, even after Ctrl-C.
  • Nothing is lost. If a fix clashes with your unstaged changes, every fix is undone and the commit stops. Stage the whole file, or stash the rest, and commit again.
  • Only fixes to staged files are staged. During a merge, only files that differ from the branch being merged in are checked.
  • No empty commits. If the fixes undo every staged change, the commit fails, unless you pass --allow-empty.

Good to know:

  • tsc checks whole projects, so it can report errors in files you did not stage. --only=oxlint --only=oxfmt keeps the hook inside the commit.
  • sherif only reports in the hook, since its fixes reach beyond the commit. Run npx uncheck --fix for them.
  • A commit no selected check covers, such as a README change with --only=tsc, passes. Add --require=tsc to make it fail.
  • An existing hook is kept: uncheck adds one line after its setup (comments, source, export, variables) and before its commands. With husky 9 or Vite+, it writes the pre-commit file they run.
  • uncheck writes nothing, and says why, outside a git repository, when core.hooksPath comes from your global or system git config, or when the existing hook is not a shell script. Your install keeps working.
  • The hook runs uncheck through your package manager (pnpm exec, yarn run --silent, bunx --no-install or npx --no), so a missing install fails instead of downloading uncheck.
  • Yarn 2+ does not run prepare. Use postinstall instead, and in a package you publish, turn it off while packing, for example with "prepack": "pinst --disable" and "postpack": "pinst --enable".

Run it after every agent turn

npx uncheck hooks install claude codebuddy   # name the agents
npx uncheck hooks install                    # or pick them from a list
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

Whenever the agent finishes a turn, the hook runs uncheck hooks run --fix. It checks the files changed since the last commit, fixes what it can, and when problems remain, sends the agent back to fix them. That happens at most once per turn, so an agent that cannot fix something is never stuck in a loop.

  • Too slow? Leave the typecheck to CI: npx uncheck hooks install claude --only=oxlint --only=oxfmt. Install again to change the flags.
  • Your config is kept. Other hooks and settings stay, and installing again only updates uncheck's entry. Comments in the file are lost when it is rewritten.
  • Avoid double runs. Cursor and Copilot CLI also run the hooks in .claude/settings.json, so add cursor or copilot next to claude only where they do not read that file.
  • Copilot reads .github/hooks only at the top of the repository, so install copilot from there.

Monorepos

Run uncheck from the workspace root, the folder whose package.json has workspaces or that has a pnpm-workspace.yaml, to check the whole monorepo.

sherif checks the workspace as a whole, so it only runs at the root. Configure it in the sherif field of the root package.json, as sherif documents. With --fix, mismatched versions move to the highest one (unless you set select), and your install runs afterwards (unless you set "noInstall": true). When CI is set, sherif only reports.

TypeScript. uncheck finds every tsconfig.json and follows their references:

  • Projects linked by references are built with one tsc -b, which writes what your configs ask for, such as declarations.
  • Every other project is checked with tsc -p --noEmit, a few at a time.
  • When only some files are checked, tsc runs just the projects that include them, and the projects that reference those. A changed tsconfig selects every project that extends it.

Hooks. Each package that runs uncheck prepare --pre-commit gets its own line in the one pre-commit hook, with its own flags:

#!/bin/sh
# Written by `uncheck prepare`, run it again to change the command.
pnpm exec uncheck staged --fix || exit 1
(cd "packages/a" && pnpm exec uncheck staged --fix --only=oxlint) || exit 1
(cd "packages/b" && pnpm exec uncheck staged --fix) || exit 1

An agent hook installed from a package folder checks only that package, wherever the agent moves to.

Presets

uncheck also ships the lint, format and TypeScript configs the middleapi projects share. They are optional.

// oxlint.config.ts
import { defineConfig } from 'oxlint'
import { middleapi } from 'uncheck/oxlint'

export default defineConfig({ extends: [middleapi] })
// oxfmt.config.ts
import { defineConfig } from 'oxfmt'
import { middleapi } from 'uncheck/oxfmt'

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

The presets need oxlint 1.70+, oxfmt 0.41+ and TypeScript 5.6+. The tsconfig presets load no runtime types, so name yours: "types": ["node"] for Node.js, or "lib": ["ES2022", "DOM", "DOM.Iterable"] for browsers.

Troubleshooting

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

uncheck dist says "No files match". git ignores that folder, so it holds no project files. You can still name an ignored file directly.

A commit stops with "An earlier run left the unstaged versions of your files in …". A pre-commit run was killed before it could put your unstaged changes back. Copy what your files are missing from the folder the message names, delete the folder, 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
Dexter Miguel
Dexter Miguel
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
Alex
Alex
Andrey Gubanov
Andrey Gubanov

With thanks to 36 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