diff --git a/lefthook.yml b/lefthook.yml index 404c23d..9fb368a 100644 --- a/lefthook.yml +++ b/lefthook.yml @@ -13,3 +13,9 @@ pre-commit: run: sh -c 'LEFTHOOK_FILES="$*" npm run check:cspell' sh {staged_files} typecheck: run: npm run check:tsc + +pre-push: + parallel: false + commands: + test: + run: npm test diff --git a/project-specs.md b/project-specs.md index b83a000..dd6f128 100644 --- a/project-specs.md +++ b/project-specs.md @@ -296,7 +296,8 @@ script does not belong in the standard pipeline. ### HOOKS - Lefthook runs the relevant `check:*` scripts on staged files in - parallel for pre-commit. See `lefthook.yml`. + parallel for pre-commit, and `npm test` on pre-push. See + `lefthook.yml`. ### CHECK TIERS — what runs where and why @@ -307,22 +308,28 @@ deliberate design choice; the rule of thumb is: - **pre-commit hook**: fast, file-scoped, offline, deterministic. Catches what _you just changed_ in under a second or two. +- **pre-push hook**: runs the unit test suite (`npm test`, + including a full `tsc` over the project). ~500ms. Catches + runtime/logic bugs across the whole codebase before the push + leaves your machine. The `tsc` step is technically redundant + with pre-commit but validates against unstaged changes too. - **`npm run check`**: full project audit. Slower, may query the network, runs everything that's not in pre-commit. - **CI build job**: authoritative. Runs `npm run check` and `npm run test:ci` on every push and PR. The ground truth — if CI passes, the codebase is clean. -| Script | pre-commit | `npm run check` | CI build | CI publish | -| ----------------- | :--------: | :-------------: | :------: | :--------: | -| `check:tsc` | ✅ | ✅ | ✅ | — | -| `check:oxlint` | ✅ | ✅ | ✅ | — | -| `check:oxfmt` | ✅ | ✅ | ✅ | — | -| `check:cspell` | ✅ | ✅ | ✅ | — | -| `check:knip` | — | ✅ | ✅ | — | -| `check:outdated` | — | ✅ | ✅ | — | -| `publish:publint` | — | — | — | ✅ | -| `publish:attw` | — | — | — | ✅ | +| Script | pre-commit | pre-push | `npm run check` | CI build | CI publish | +| ----------------- | :--------: | :------: | :-------------: | :------: | :--------: | +| `check:tsc` | ✅ | ✅ | ✅ | ✅ | — | +| `check:oxlint` | ✅ | — | ✅ | ✅ | — | +| `check:oxfmt` | ✅ | — | ✅ | ✅ | — | +| `check:cspell` | ✅ | — | ✅ | ✅ | — | +| `test` | — | ✅ | — | ✅ | — | +| `check:knip` | — | — | ✅ | ✅ | — | +| `check:outdated` | — | — | ✅ | ✅ | — | +| `publish:publint` | — | — | — | — | ✅ | +| `publish:attw` | — | — | — | — | ✅ | #### Why these splits? @@ -332,10 +339,19 @@ deliberate design choice; the rule of thumb is: `LEFTHOOK_FILES` env var convention. They give instant feedback on what you typed. -- **`check:knip` is NOT in pre-commit** because it scans the whole - project (not staged files) and takes ~4s. Adding it would - roughly triple pre-commit time. It's still fast enough to run - locally before pushing, and CI catches it regardless. +- **`test` (and the `tsc` it includes) is in pre-push** because it + runs the whole test suite across the whole project (~500ms + for the current 6 tests). The pre-commit `LEFTHOOK_FILES` + convention doesn't apply to the test runner, so pre-commit + isn't the right home. Pre-push is the natural place: it runs + after all commits are made but before the push leaves the + machine, catching regressions that span multiple commits. + +- **`check:knip` is NOT in pre-commit or pre-push** because it + scans the whole project (not staged files) and takes ~4s. + Adding it would noticeably slow both hooks. It's still fast + enough to run locally before pushing, and CI catches it + regardless. - **`check:outdated` is NOT in pre-commit** because (a) it queries the npm registry (~6.5s) which means network dependency in a