Set up a fast, parallel pre-commit pipeline for full-stack projects.
Tool choices: Lefthook (not Husky), Ruff (not Black/Flake8), Biome (not ESLint/Prettier). Why: speed, simplicity, one tool per job.
Process
1. Detect project structure
Scan the repo to determine:
- Has Python backend? — look for
pyproject.toml,requirements.txt, orbackend/dir - Has JS/TS frontend? — look for
package.json,frontend/, orsrc/with.ts/.tsxfiles - Has existing hooks? — check for
.husky/,lefthook.yml,.pre-commit-config.yaml
If existing hooks are found, ask the user before overwriting.
2. Install dependencies
Lefthook (always):
# macOS
brew install lefthook
# or npm
npm install -D lefthook
Python (if backend detected):
# Ruff — installed via uvx, no project dependency needed
# Verify: uvx ruff --version
Frontend (if JS/TS detected):
npm install -D @biomejs/biome
Commitlint (always):
npm install -D @commitlint/cli @commitlint/config-conventional
3. Create lefthook.yml
Adapt based on detected structure. Full-stack example:
pre-commit:
parallel: true
commands:
ruff-check:
glob: "backend/**/*.py"
run: uvx ruff check {staged_files}
ruff-format:
glob: "backend/**/*.py"
run: uvx ruff format --check {staged_files}
biome-check:
glob: "frontend/**/*.{ts,tsx,js,jsx,json}"
root: "frontend/"
run: npx biome check {staged_files}
commit-msg:
commands:
commitlint:
run: npx commitlint --edit {1}
Adapt rules:
- Python-only project: remove
biome-check, removeroot - Frontend-only project: remove
ruff-*commands - Monorepo: adjust
globandrootto match directory layout
4. Create commitlint.config.mjs
export default {
extends: ["@commitlint/config-conventional"],
rules: {
"scope-empty": [2, "never"],
"subject-max-length": [2, "always", 72],
},
};
Ask the user what scopes to enforce. If unclear, omit scope-enum and only enforce scope-empty.
5. Install hooks and verify
lefthook install
Run a smoke test:
# Should pass on clean code
lefthook run pre-commit
6. Commit the setup
Stage all config files and commit:
chore: add pre-commit hooks (lefthook + ruff + biome + commitlint)