The pre-commit hook is file-scoped (LEFTHOOK_FILES), so it can't
naturally run the test suite. Pre-push is the right tier for it:
- Runs after all commits are made but before the push leaves
the machine, catching regressions that span multiple commits
- ~3.5s including the tsc step (negligible vs the typical push
round-trip to CI)
- Offline and deterministic, same philosophy as pre-commit
lefthook.yml: new pre-push section, sequential (parallel: false
since there's only one command, but the explicit value documents
the intent that this hook runs commands in order rather than
racing).
project-specs.md: updated the CHECK TIERS table to add the
pre-push column with in it. Updated the rule-of-thumb
list to include the pre-push tier. Updated the 'why' notes to
explain why lives in pre-push rather than pre-commit
(LEFTHOOK_FILES doesn't apply to the test runner).
lefthook and package.json had parallel command definitions for the
same tools (oxlint, oxfmt, cspell). Consolidate by making lefthook
call the npm scripts, with staged files passed via the
LEFTHOOK_FILES env var. The scripts use ${LEFTHOOK_FILES:-<default>}
so they default to the full project when invoked manually and to
the staged-files list when invoked from lefthook.
Changes:
- package.json#check:oxlint: `oxlint ${LEFTHOOK_FILES:-src}`
(lints src/ manually; staged files from lefthook)
- package.json#check:oxfmt: `oxfmt --check ${LEFTHOOK_FILES:-src}`
- package.json#check:cspell: `cspell lint ${LEFTHOOK_FILES:-.}`
(walks CWD manually; staged files from lefthook)
- package.json#check:tsc, check📦 unchanged (no file args)
- lefthook.yml: file-filtered hooks (oxlint, oxfmt, cspell) now use
`sh -c 'LEFTHOOK_FILES="$0" npm run check:*' {staged_files}` to
inject the staged-files list into the env. sort-package-json and
typecheck call npm scripts directly (no file args).
- project-specs.md: document the unification pattern
Why sh -c + env var instead of the simpler 'npm run ... -- {staged_files}':
'oxlint src file.ts' lints the whole src/ tree *plus* file.ts
(oxlint doesn't dedupe paths). Setting LEFTHOOK_FILES as an env
var (which lefthook's 'env:' config does not template) requires
the sh -c wrapper, but it gives the right semantics: when the
var is set, only the explicit files are checked; when unset,
the default (src/ or .) is used.
Verified:
- 'npm run check:oxlint' (no env) lints all of src/
- 'LEFTHOOK_FILES=src/match.ts npm run check:oxlint' lints only that file
- 'npx lefthook run pre-commit' with a staged TS file: cspell output
shows '1/1 src/match.ts' (only staged file, not whole project)
- All 5 hooks pass on a real staged change
- 'lefthook validate' reports 'All good'
- 'npm run check' exits 0
The previous config was a hybrid of v1 (jobs: array) and v1
(top-level commands: block) that no longer validates under
lefthook 2.x. lefthook 2.0.0's schema has top-level 'commands:'
set to 'false'; the v1 jobs: array form still works, but the
orphan named-commands block at the top was silently invalid
(caught by 'lefthook validate').
Migrate to the v2-native 'commands:' (named) form under the hook:
pre-commit:
parallel: true
commands:
oxlint:
glob: ...
run: npx oxlint {staged_files}
...
Changes:
- Add 'min_version: 2.0.0' to declare the v2 schema explicitly
- Replace pre-commit.jobs: array with pre-commit.commands: (named)
- Inline the glob on each command (was a top-level property on each
job in v1)
- Remove the orphan top-level 'commands:' block — it was never
referenced by any hook, and v2 forbids it
- Update project-specs.md: drop the dangling reference to the
lefthook 'commands:' block (it was the dead top-level one);
describe the v2 commands: form
Verified:
- 'lefthook validate' reports 'All good'
- 'lefthook run pre-commit' executes all 5 hooks (oxlint, oxfmt,
sort-package-json, typecheck, cspell); globs correctly skip hooks
on non-matching files (e.g. lefthook.yml alone) and run them on
TS/JS changes
- 'npm run check' exits 0
- 'lefthook dump' shows the normalized v2 config
- Delete public/vite.svg, src/typescript.svg, src/style.css
- Empty public/ directory removed
- .npmignore: drop 'public/' entry (directory no longer exists), add
.oxlintrc.json and .oxfmtrc.json to ignore list
- cspell: add .oxfmtrc, .oxlintrc, .sortpackagerc to dictionary so
config-file name references aren't flagged
- lefthook: drop 'outdated' from pre-commit (kept in commands block for
explicit CI invocation); it shouldn't block commits for being behind on
upstream patch releases
- Drop .eslintrc.cjs and .prettierrc, add .oxlintrc.json and .oxfmtrc.json
- oxlint covers correctness/suspicious/perf/style/restriction categories
plus import and typescript plugins; oxfmt reads the same .prettierrc-style
options we had before
- Update lefthook.yml pre-commit jobs to run oxlint/oxfmt instead of
eslint/prettier
- Update .vscode/extensions.json (oxc replaces eslint/prettier/vitest) and
settings.json (oxc becomes the default formatter for TS/JS)
- Add 'oxlint', 'oxfmt', 'oxc', 'nodenext' to cspell dictionary
- Rewrite README 'Development' section to describe the new toolchain