About setup-git-hooks
setup-git-hooks is a skill that configures repository Git hooks using the built-in core.hooksPath setting. It assigns staged-file formatting and checks to available tools, then runs configured typecheck and build scripts before commits.
Key features
- 01Configure hooks through core.hooksPath
- 02Run lint-staged with Biome
- 03Optionally add Prettier for uncovered languages
- 04Run configured typecheck and build scripts
- 05Check hook setup and commit its files
Use cases
- 01Add pre-commit checks to a repository
- 02Set up formatting for project languages
- 03Replace Husky after confirming migration
Install
Copy the command and run it in a terminal.
npx skills add https:/ / github.com/ dyrits/ skills --skill setup-git-hooksAfter installing, type/setup-git-hooksin Claude Code to confirm it loaded.
Bundled filesagents/openai.yamlapm.yml
Details
From the project READMESetup Git Hooks
What This Sets Up
core.hooksPathpointing at a committed.githooks/dir (git built-in, no Husky needed)- lint-staged running Biome on the languages it supports
- Biome config (if missing)
- Prettier for the languages Biome leaves uncovered, only when the user opts in
- typecheck and build scripts in the pre-commit hook
Why not Husky: Husky's only job is copying runners into .git/hooks/. git config core.hooksPath .githooks does the same with zero dependencies. A prepare script sets the config on every npm install, so teammates are covered on clone.
Steps
1. Detect package manager and existing hook tooling
Check for package-lock.json (npm), pnpm-lock.yaml (pnpm), yarn.lock (yarn), bun.lockb (bun). Use whichever is present. Default to npm if unclear.
If the repository already uses Husky (a .husky/ directory or prepare: husky), ask the user now, before anything is installed or written, whether to migrate off it or keep Husky and stop here.
2. Map formatter coverage
List the languages the repository contains, then check which ones the Biome version you will install formats and lints (its documentation or biome --help). Some languages, HTML among them in Biome 2.x, are covered only once enabled in biome.json; count them as covered and enable them in step 7. Keep a formatter the project already uses (oxfmt, dprint, Prettier) and route its languages to it.
When languages remain uncovered, offer Prettier for them, naming each language, and let the user decline.
Completion: every language in the repository is assigned to Biome, an existing formatter, Prettier (accepted), or no formatter (declined).
3. Install dependencies
Install lint-staged and @biomejs/biome as devDependencies, plus prettier only when the user accepted it. Skip any tool the repository already has.
4. Create .githooks/pre-commit
Write this file and make it executable (chmod +x .githooks/pre-commit):
#!/bin/sh
npx lint-staged
npm run typecheck --if-present
npm run build --if-present
Adapt: Replace npm with detected package manager (pnpm --if-present is not supported, so check package.json for the script first and omit the line if missing). Omit typecheck or build if the repository has no such script in package.json, and tell the user.
5. Point git at the hooks dir
git config core.hooksPath .githooks
This is local config; new clones need it too. Add a prepare script to package.json so it runs on every install:
{
"scripts": {
"prepare": "git config core.hooksPath .githooks"
}
}
Merge into existing scripts; if prepare already exists, append the git config call (e.g. &&).
6. Create .lintstagedrc
Biome handles format and lint (check) for the languages assigned to it in step 2. Add a Prettier entry only for languages the user assigned to Prettier:
{
"*.{js,jsx,ts,tsx,json,jsonc,css,graphql}": "biome check --write --no-errors-on-unmatched",
"*.{html,md,mdx,yaml,yml,scss,vue,svelte,astro}": "prettier --write"
}
Give each glob exactly one tool, and list only the extensions the repository contains.
7. Create biome.json and .prettierrc (if missing)
Only create a config if none exists (check for biome.json, biome.jsonc, .prettierrc, .prettierrc.json, prettier.config.*). Enable the opt-in languages step 2 assigned to Biome. Write .prettierrc only when Prettier was accepted.
Biome defaults (biome.json). Ask the user for base formatter preferences before writing (see questions below), then write the file:
{
"$schema": "https://biomejs.dev/schemas/latest/schema.json",
"formatter": {
"enabled": true,
"indentWidth": 2,
"lineWidth": 160,
"trailingComma": "none"
},
"javascript": {
"formatter": {
"quoteStyle": "double",
"semicolons": "always",
"arrowParentheses": "always"
}
},
"json": {
"formatter": { "enabled": true }
},
"css": {
"formatter": { "enabled": true }
},
"linter": { "enabled": true },
"organizeImports": { "enabled": true },
"assist": {
"actions": {
"source": {
"useSortedAttributes": "on",
"useSortedKeys": "on",
"useSortedProperties": "on"
}
}
}
}
Ask the user for base parameters before writing biome.json (skip any already answered or fixed above):
- Tabs or spaces (default: spaces, width 2)?
- Line width (default: 160)?
- Quote style for JS/TS (default: double)?
- Semicolons and arrow parens (defaults: always / always)?
Prettier defaults (.prettierrc, when accepted, mirrors Biome base):
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 160,
"singleQuote": false,
"trailingComma": "none",
"semi": true,
"arrowParens": "always"
}
8. Verify
-
.githooks/pre-commitexists and is executable -
git config core.hooksPathprints.githooks -
preparescript in package.json setscore.hooksPath -
.lintstagedrcexists -
biome.jsonexists (or pre-existing Biome config found) -
prettierconfig exists, when Prettier was accepted - Run
npx lint-stagedto verify it works
9. Commit
Stage only the files this setup created or changed: .githooks/, .lintstagedrc, the formatter configs, package.json, and the lockfile. When other uncommitted changes are present and the branch is the default branch, ask before committing. Commit with a message naming the tools installed, for example Add git hooks via core.hooksPath (lint-staged + biome).
The commit runs through the new pre-commit hook: a good smoke test that everything works.
Notes
core.hooksPathoverrides.git/hooks/entirely; hooks in.git/hooks/stop running- The
preparescript needs a git repository present;npm installin a non-git context fails unless guarded (e.g.prepare": "git rev-parse && git config core.hooksPath .githooks || true"in published packages) - Biome
checkruns both format and lint in one pass;--no-errors-on-unmatchedkeeps lint-staged quiet when no supported files are staged - Prettier handles only what Biome does not, so the two never fight over the same file
- The pre-commit runs lint-staged first (fast, staged-only), then full typecheck and build
- If the team needs more than pre-commit (parallel steps, per-glob runners without lint-staged), suggest lefthook as the next step up; Husky only adds convenience wrappers over what core.hooksPath already does