setup-pre-commit
Skill1 starsChanged 43 days agoMITView on GitHub
Set up a Husky pre-commit hook in the current project: finds where the hook belongs, keeps the formatter the project already uses instead of forcing Prettier, reads the CI pipeline for checks worth running before a commit and adds only the ones the user picks.
124 other projects use a version of this.
Adopted by
Most recently taken up by
- 5thlayer/skillworkstheir own version, first seen Nov 8, 2026
- dfirmin/compasstheir own version, first seen Nov 8, 2026
- op3616windows/skillstheir own version, first seen Nov 8, 2026
- kazemcodes/skill-treetheir own version, first seen Nov 8, 2026
- shkodnik1917/claude-brainlabtheir own version, first seen Nov 8, 2026
- Tresnanda/kyberntheir own version, first seen Nov 8, 2026
Used with
Projects with it also use migrate-to-shoehorn, writing-beats and 10 more.
Other files people use alongside this one.
Files found in the same public projects as a version of this one, more often than they turn up anywhere else. Each project counts once, large collections count for less, and this file's own repository is left out.
simo97Skillmigrate-to-shoehorn
Migrate test files from `as` type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace `as` in tests, or needs partial test data.
Also in 73 projects that use this
LesaniSkillwriting-beats
Shape an article as a journey of beats, choose-your-own-adventure style. The user picks a starting beat from the raw material, you write only that beat, then offer options for where to pivot next, beat by beat, until the article reaches a natural end. Use when the user has raw material and wants to assemble it as a narrative rather than an argument.
Also in 63 projects that use this
liufeng1976Skillsetup-ts-deep-modules
Wire dependency-cruiser into a TypeScript repo so each package is a deep module — implementation hidden in subfolders, reachable only through its entry-point files. User-invoked.
Also in 41 projects that use this
LesaniSkillwriting-shape
Take a markdown file of raw material and shape it into an article through a conversational session — drafting candidate openings, growing the piece paragraph by paragraph, arguing about format (lists, tables, callouts, quotes) at each step. Use when the user has a pile of notes, fragments, or a rough draft and wants help turning it into something publishable.
Also in 63 projects that use this
simo97Skilledit-article
The user will invoke this skill to help them edit an article.
Also in 38 projects that use this
RoentekSkillloop-me
Grill me about specs for the workflows I want to build, within this workspace.
Also in 52 projects that use this
- +3 more from simo97
- +1 more from Lesani
Other versions124 projects use a version of this.
Word for word · 0
None found.
Edited · 0
None found.
Wrote their own version · 124
- mattpocock/skills★ 309ksame name
- vinvcn/mattpocock-skills-zh-CN★ 5.5ksame name
- devcxl/mattpocock-skills-zh★ 522same name
- amazingloft999-droid/mattpocock-skills★ 422same name
- InditexTech/weavejs★ 266same name
- loulanyue/skills★ 231same name
- tt-a1i/matt-skills-with-to-goal★ 192same name
- aurorasoft2/Matt_Pocock_Skills★ 133same name
- Tresnanda/kybern★ 83same name
- VisualxIntelligence/mattpocok-skills★ 55same name
114 more not listed
How matches are found. Word for word: the exact same text. Edited: shares passages with this file (“99% same” means 99% of its five-word phrases also appear here; 10% or more counts). Their own version: similar meaning, or, weakest, the same name. We can’t tell which project had it first. Projects are counted among the 200 most-starred candidates, and mentions are links to the repository we found, up to 50 per site, so both counts are a floor.
In this repository15 other files it gives its agents.
- CLAUDE.mdCLAUDE.md
- test-authorSkill
- discover-setupSkill
- discoverSkill
- discussSkill
- do-code-reviewSkill
- doSkill
- improve-promptSkill
- journeySkill
- prototypeSkill
- sketchSkill
- specSkill
- testing-policySkill
- test-triageSkill
- ticketsSkill
The file
We split this skill into the rules it gives an agent
12 rules. 0 are common, 4 are distinctive, and 8 are very specific to this project.
0
Common
Most of the 112 edited versions we read keep these.
4
Distinctive
This author's own choices. Worth a look.
8
Very specific
Tied to their project's names, files or tools. Probably skip.
- Common
- Distinctive
- Very specific
- Distinctive: line 36
Stop and report, writing nothing, when:
- Distinctive: line 66
When the project has no formatter, say before asking that adopting one reformats every file the first time it is staged, so the next diffs …
- Distinctive: line 77
Read only the jobs that run on a push or a pull request.
- Distinctive: line 92
Every step that survives is a candidate.
- Very specific: line 100
Cost: run the command once at the package root with a five-minute timeout, and record the wall time, or failed locally with the first error …
- Very specific: line 159
Add as dev dependencies husky, lint-staged, and the formatter only when step 2 decided to add one.
- Very specific: line 161
Run husky init when .husky/ does not exist.
- Very specific: line 164
When the package root is not the git root, follow the Husky subdirectory recipe: .husky/ stays in the package root, the prepare script …
- Very specific: line 169
Write .husky/pre-commit (Husky v9 needs no shebang): lint-staged first, then the picked whole-repo commands ordered fastest first, so a …
- Very specific: line 172
Write or extend the lint-staged config: the step 2 globs, plus every picked staged candidate on the extensions it handles.
- Very specific: line 174
Only when step 2 decided to add Prettier and no config exists, write .prettierrc:
- Very specific: line 193
Run the hook's commands once, as the hook would: stage one changed file, then run lint-staged and every picked whole-repo command.
“Common” is measured: kept by at least 6 in 10 of the 112 edited versions of this file we could read. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.
Files
Set up a Husky pre-commit hook in this project. Runs inline: it asks the user, installs dependencies and writes files into the project.
The goal is a hook that fails a commit for the reasons CI would fail it, and fast enough that
nobody skips it with --no-verify. A hook that takes minutes gets bypassed and then protects
nothing, so every choice below trades coverage against the seconds it adds to each commit, and the
user makes the trade with the facts in front of them. That is why the project's own tools win over
any default, why every CI check is offered with its measured cost instead of added, and why step 5
is the first step that writes.
Arguments: $ARGUMENTS (optional: the path of the package the hook serves, in a monorepo)
1. Find where the hook goes
Git runs one set of hooks for the whole repository, so every commit anywhere in it goes through the hook, even when the code the hook checks lives in one subdirectory. Establish, and keep for every later step:
- Git root:
git rev-parse --show-toplevel, wherecore.hooksPathis resolved from. - Package root: the directory whose
package.jsonowns the dev dependencies and the scripts the hook calls. The argument when one was given; otherwise the git root when it has apackage.json; otherwise the singlepackage.jsonbelow it (ignorenode_modules). More than one candidate and no workspace root: ask which one with AskUserQuestion. - Workspace:
pnpm-workspace.yaml, aworkspacesfield,turbo.jsonornx.jsonat the package root means the scripts may fan out over packages; note it for step 3. - Package manager: the
packageManagerfield first, then the lockfile (pnpm-lock.yamlpnpm,yarn.lockyarn,bun.lockorbun.lockbbun,package-lock.jsonnpm). Default to npm. Every command below uses its runner (npx,pnpm exec,yarn,bunx) and itsrun.
Stop and report, writing nothing, when:
| Found | Why it stops |
No package.json anywhere in the repo | Husky needs Node; say so and name the hook manager that fits the stack (pre-commit for Python, lefthook for a polyglot repo) instead of forcing one |
.pre-commit-config.yaml, lefthook.yml, simple-git-hooks in package.json, or git config core.hooksPath pointing anywhere but a .husky/_ | Another hook manager already owns the hooks; a second one silently replaces the first. Name it and ask whether to migrate its steps into Husky or leave it |
A hand-written .git/hooks/pre-commit (not a .sample) | Husky would bypass it. Show it and ask whether its commands move into the new hook |
An existing .husky/ is not a stop: the run becomes a refresh, and step 5 extends the existing
pre-commit instead of replacing it.
2. Decide the staged-file tools
lint-staged runs tools on the staged files only, which is the fast part of the hook. Decide what
it runs from what the project already uses, because a formatter the project does not use rewrites
files the team never agreed to format:
| The project has | lint-staged runs |
A Prettier config (.prettierrc*, prettier.config.*, a prettier key in package.json) or prettier in its dependencies | prettier --ignore-unknown --write |
biome.json or biome.jsonc | biome check --write --no-errors-on-unmatched on the extensions Biome handles; no Prettier |
dprint.json, or deno.json with fmt | that formatter's write command; no Prettier |
ESLint running Prettier as a rule (eslint-plugin-prettier) | eslint --fix only; a second Prettier pass would fight it |
| No formatter at all | nothing yet: ask whether to add Prettier, with its defaults below, or to run no formatter |
An existing ESLint config adds eslint --fix on the extensions it lints, unless CI does not run
ESLint, in which case it becomes a candidate in step 3 instead. An existing lint-staged config
(.lintstagedrc*, lint-staged.config.*, a lint-staged key in package.json) is extended,
never replaced, and its globs win over the table.
When the project has no formatter, say before asking that adopting one reformats every file the first time it is staged, so the next diffs will be noisy until the codebase has been formatted once.
3. Read the CI pipeline for candidates
Find the pipeline files at the git root: .github/workflows/*.yml|yaml, .gitlab-ci.yml,
.circleci/config.yml, azure-pipelines.yml, bitbucket-pipelines.yml, Jenkinsfile,
.travis.yml, .buildkite/, .woodpecker/. None found: say so and skip to step 4 with the step 2
tools only.
Read only the jobs that run on a push or a pull request; skip release, deploy, schedule and manual
triggers. For every run step, resolve it to the command a developer would type at the package
root: follow npm run <x> / make <x> / just <x> to what it runs, rewrite it for the detected
package manager, and drop the CI-only flags (--ci, --reporter=junit, coverage upload flags).
Drop a step, without offering it, when it is:
- plumbing: checkout, install, cache, setup of a language or a tool;
- producing or shipping an artifact: build for release, docker build or push, publish, deploy, release, coverage or report upload;
- dependent on something a laptop does not have: a
secrets.*or CI-only variable, a service container, a browser or a device farm (end-to-end suites); - already covered by step 2 (the format or lint check lint-staged runs on staged files);
- a matrix repeat: keep one entry for the whole matrix.
Every step that survives is a candidate. For each one, record:
- Source:
<file>:<job>:<step name>. - Command: the local command; confirm the script or binary exists at the package root, and
mark it
missing locallywhen it does not. - Scope:
stagedwhen the tool takes file arguments and can run through lint-staged (a linter, a spell checker, a header check),whole-repootherwise (typecheck, the test suite, a codegen drift check). - Cost: run the command once at the package root with a five-minute timeout, and record the
wall time, or
failed locallywith the first error line. Astagedcandidate is timed on the whole repo and the record says so, since the hook will run it on far fewer files. Time only a check-only command, one that reads the tree and exits with a verdict (tsc --noEmit,eslint .,vitest run), because a run here is a side effect on the user's project before they agreed to anything. A command that rewrites files (--fix,--write, codegen), reaches a database or the network, or needs credentials is recorded asnot measured, with the reason.
A commitlint step is not a pre-commit candidate: it checks the message, so offer it separately as
a commit-msg hook.
<examples>
<example>
CI step, in `.github/workflows/ci.yml`, job `check`, on `pull_request`:
`run: pnpm run typecheck`, where the script is `tsc --noEmit`.
Candidate: source `ci.yml:check:Typecheck`, command `pnpm run typecheck`, scope `whole-repo`,
cost `12s`.
AskUserQuestion option: label `pnpm run typecheck (Recommended)`, description
`ci.yml:check:Typecheck · whole-repo · 12s`.
</example>
<example>
CI step, in `.gitlab-ci.yml`, job `lint`, on merge requests: `npx cspell "src/**"`.
Candidate: scope `staged` (cspell takes file arguments, so lint-staged runs it as `cspell` on
`*.{ts,md}`), cost `41s on the whole repo, far less on staged files`. Recommended: a `staged`
check costs only what was staged.
</example>
<example>
CI step, in `.github/workflows/ci.yml`, job `test`, with a `postgres` service container:
`run: npm test -- --ci --coverage`.
Dropped: needs a service container a laptop does not have running. Listed in the report as
`ci.yml:test:Test · dropped: service container postgres`.
</example>
<example>
CI step, job `unit` on a matrix of Node 20 and 22: `run: npm run test:unit`, where the suite
needs no service. Candidate once for the whole matrix, cost `2m 40s`. Offered without
`(Recommended)`, with the description `ci.yml:unit:Unit · whole-repo · 2m 40s, adds this to every
commit`.
</example>
</examples>4. Let the user choose
Show the step 2 plan in one short list (the lint-staged globs and commands, the dependencies it
installs), then offer the candidates with AskUserQuestion, multiSelect: true. Each option's
label is the command; its description is the source, the scope and the cost. Recommend a
candidate, by listing it first with (Recommended) in the label, only when it is staged, or
whole-repo and under about 30 seconds. A candidate that is missing locally, failed locally
or not measured is offered unrecommended, with the reason in its description, so the user
still sees it and decides. With more than four candidates, group them into
up to four questions by kind (lint, types, tests, other). A candidate the user does not pick is not
added.
Whole-repo checks run on every commit and block it, so a hook that takes minutes teaches the team
to commit with --no-verify. Say so once, next to the total of the picked costs.
5. Install
At the package root, with the detected package manager:
- Add as dev dependencies
husky,lint-staged, and the formatter only when step 2 decided to add one. Skip what is already there. - Run
husky initwhen.husky/does not exist. It writes apreparescript and a sample.husky/pre-committhat runs the tests: the next step overwrites that sample. An existingpreparescript keeps its command and gainshuskywith&&. - When the package root is not the git root, follow the Husky subdirectory recipe:
.husky/stays in the package root, thepreparescript becomescd <relative path to git root> && husky <package root, relative to the git root>/.husky, and the hook's first line iscd <package root, relative to the git root>, since Git runs hooks from the git root. - Write
.husky/pre-commit(Husky v9 needs no shebang): lint-staged first, then the pickedwhole-repocommands ordered fastest first, so a cheap failure stops the commit before the slow ones run. On a refresh, append only the lines that are not already there. - Write or extend the lint-staged config: the step 2 globs, plus every picked
stagedcandidate on the extensions it handles. A new config goes to.lintstagedrc. Only when step 2 decided to add Prettier and no config exists, write
.prettierrc:{ "useTabs": false, "tabWidth": 2, "printWidth": 80, "singleQuote": false, "trailingComma": "es5", "semi": true, "arrowParens": "always" }
6. Verify
.husky/pre-commitexists and is executable.- The
preparescript runshusky, andgit config core.hooksPathreads.husky/_(or<package root>/.husky/_in a subdirectory) after it ran. - Run the hook's commands once, as the hook would: stage one changed file, then run
lint-stagedand every picked whole-repo command. Report a failure with its output and keep the line in the hook: a check that fails here would fail CI too, so the failure is a finding for the user, not a defect of the hook.
7. Report and commit
Report, in this order: the git root and the package root, the formatter decision and why, each
candidate with the choice the user made, each dropped CI step with the rule that dropped it, the files written or extended, the
dependencies added, and the measured cost of the whole hook. Then show the diff and ask whether to commit it with the
message chore: add pre-commit hooks (husky + lint-staged). The commit is the smoke test: it runs
through the new hook. When the hook fails that commit, show its output and stop with the changes
staged; bypassing it with --no-verify would hide the one result this commit exists to show.
Without a yes, leave the changes in the working tree.
Does it work?
Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.
No reviews yet
People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.
- It worked
- Partly
- It didn't
Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.
Your trail
Files you open will collect here, so you can find them again.
Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.
Now do this for your own.
Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.

