git:20260407.d6d717b to git:20261014.01ed277

54 added, 4 removed. Audit A to A.

---
name: setup-pre-commit
- description: "Infrastructure skill for setting up commit-time quality gates. Use when a repo needs Lefthook-based pre-commit hooks for formatting, linting, type checking, and tests. Supports downstream /execute and /pre-merge, but is not part of the normal feature-delivery pipeline."
+ description: "Infrastructure skill for setting up commit-time quality gates. Use when a repo needs Lefthook-based pre-commit hooks for formatting, linting, type checking, and tests, plus a secret scan at commit and a dependency audit at push. Supports downstream /execute and /pre-merge, but is not part of the normal feature-delivery pipeline."
---
# Setup Pre-Commit Hooks
## Invocation Position
This is an infrastructure skill, not a normal feature-delivery stage.
Use `/setup-pre-commit` when a project needs commit-time formatting, linting, type checking, and test hooks.
Do not use it as part of the default feature pipeline. It prepares the repo so later `/execute` and `/pre-merge` steps have stronger feedback loops.
## What This Sets Up
- **Lefthook** for git hook management (fast, zero-dependency, config-as-code)
- **Formatting + linting** on staged files (Biome preferred; Prettier, ESLint, or others supported)
- **Typecheck** and **test** scripts in the pre-commit hook
+ - **Secret scan** on staged changes in the pre-commit hook (gitleaks, unless the repo already runs a scanner)
+ - **Dependency audit** in the pre-push hook, using the detected package manager's own audit command
## Steps
### 1. Detect Package Manager
Check for `pnpm-lock.yaml` (pnpm), `package-lock.json` (npm), `yarn.lock` (yarn), `bun.lockb` (bun). Use whichever is present. Default to npm if unclear.
Store the detected package manager — it's used for all install and run commands below.
### 2. Detect Existing Formatter/Linter
Before installing anything, check what's already in the project:
```bash
# Check package.json for existing tools
cat package.json | grep -E "biome|prettier|eslint|oxlint|dprint" 2>/dev/null
# Check for config files
ls biome.json biome.jsonc .prettierrc .prettierrc.* prettier.config.* .eslintrc* eslint.config.* 2>/dev/null
```
**If an existing formatter/linter is detected:** use it. Don't install a competing tool.
**If nothing is detected:** recommend Biome (single tool for formatting + linting, fast, zero-config defaults) and confirm with the user before installing.
Ask: "No formatter/linter detected. I'd recommend Biome — it handles both formatting and linting in one tool, is very fast, and works out of the box. Want to go with Biome, or would you prefer Prettier, ESLint, or something else?"
+ The same rule covers the two security gates Step 5 adds. Check for a secret scanner or a dependency audit the repo already runs:
+
+ ```bash
+ # Secret-scanner config
+ ls .gitleaks.toml .gitleaksignore .secrets.baseline .trufflehog* .pre-commit-config.yaml 2>/dev/null
+ # A scanner or an audit already wired into hooks, scripts, or CI
+ grep -rnE "gitleaks|trufflehog|detect-secrets|ggshield|[a-z] audit" lefthook.yml package.json .pre-commit-config.yaml .github/workflows 2>/dev/null
+ ```
+
+ **If a secret scanner is detected:** keep it. Wire Step 5's `secret-scan` command to that tool, or leave the command out when the existing setup already runs it at commit time. A `.gitleaks.toml` or `.gitleaksignore` is configuration gitleaks reads on its own; leave both as they are.
+
+ **If an audit step is detected:** leave it where it is and do not add a second one.
+
+ **If nothing is detected:** use gitleaks and the package manager's audit. Neither needs a question — a repo with no scanner has no preference to override.
+
### 3. Install Dependencies
Install as devDependencies using the detected package manager:
**Lefthook (always):**
```bash
# pnpm
pnpm add -D lefthook
# npm
npm install -D lefthook
# yarn
yarn add -D lefthook
# bun
bun add -D lefthook
```
**Formatter/linter (based on detection or user choice):**
| Tool | Install | Config file |
|------|---------|-------------|
| Biome (preferred) | `@biomejs/biome` | `biome.json` |
| Prettier | `prettier` | `.prettierrc` |
| ESLint | `eslint` | `eslint.config.*` |
| Prettier + ESLint | `prettier eslint` | both |
| oxlint | `oxlint` | `oxlint.json` |
| dprint | `dprint` | `dprint.json` |
+ **Secret scanner (when Step 2 found none):** gitleaks is a standalone binary, not an npm package, so it does not go in devDependencies. Install it by one of the routes in [gitleaks § Installing](https://github.com/gitleaks/gitleaks#installing), and tell the user that everyone who commits to the repo needs it on `PATH`. If you cannot install it (no package manager for it, a restricted sandbox, no network), stop before Step 9 and ask the user: the fail-closed gate would block the smoke-test commit, and dropping `secret-scan` or making it skip when the binary is absent is the user's decision, not yours. **When no user is present to answer** (this skill was invoked by `/init-pipeline` Path B, so by `/execute` Step 0 or an AFK Ralph iteration), do not wait and do not decide: leave `lefthook.yml` as Step 5 wrote it, skip Step 9 (commit nothing), and end the run by reporting to the caller that `secret-scan` is blocked because gitleaks is not on `PATH`, with the uncommitted scaffold left in the working tree. Every "ask the user" in Step 7 follows this same rule. The dependency audit needs no install; it ships with the package manager.
+
### 4. Initialize Lefthook
```bash
npx lefthook install
```
This creates the git hook symlinks. No `.husky/` directory, no `prepare` script needed — Lefthook manages hooks directly via `lefthook.yml`.
Add a `prepare` script to package.json so hooks are installed automatically for anyone who clones the repo:
```json
{
"scripts": {
"prepare": "lefthook install"
}
}
```
### 5. Create `lefthook.yml`
Create `lefthook.yml` at the repo root. The configuration depends on which formatter/linter was detected or chosen.
**With Biome (preferred):**
```yaml
pre-commit:
parallel: true
commands:
format-and-lint:
glob: "*.{js,ts,jsx,tsx,json,css}"
run: npx @biomejs/biome check --write --staged {staged_files}
stage_fixed: true
typecheck:
run: {package_manager} run typecheck
test:
run: {package_manager} run test
```
**With Prettier:**
```yaml
pre-commit:
parallel: true
commands:
format:
glob: "*.{js,ts,jsx,tsx,json,css,md,yml,yaml}"
run: npx prettier --write {staged_files}
stage_fixed: true
typecheck:
run: {package_manager} run typecheck
test:
run: {package_manager} run test
```
**With ESLint:**
```yaml
pre-commit:
parallel: true
commands:
lint:
glob: "*.{js,ts,jsx,tsx}"
run: npx eslint --fix {staged_files}
stage_fixed: true
typecheck:
run: {package_manager} run typecheck
test:
run: {package_manager} run test
```
**With Prettier + ESLint:**
```yaml
pre-commit:
parallel: true
commands:
format:
glob: "*.{js,ts,jsx,tsx,json,css,md,yml,yaml}"
run: npx prettier --write {staged_files}
stage_fixed: true
lint:
glob: "*.{js,ts,jsx,tsx}"
run: npx eslint --fix {staged_files}
stage_fixed: true
typecheck:
run: {package_manager} run typecheck
test:
run: {package_manager} run test
```
+ **Security gates (add to whichever config above was used):**
+
+ ```yaml
+ pre-commit:
+ commands:
+ secret-scan:
+ run: gitleaks git --pre-commit --staged --redact
+
+ pre-push:
+ commands:
+ dependency-audit:
+ run: {package_manager} audit
+ ```
+
+ Merge these into the config rather than appending a second `pre-commit:` key. The audit calls the registry, which makes it too slow for every commit, so it runs on pre-push.
+
+ - **Secret scan.** The command's flags are documented in `gitleaks git --help` and [gitleaks § Commands](https://github.com/gitleaks/gitleaks#commands). When the scan flags a fixture or a test key, suppress that one finding with the scanner's own mechanism — [`gitleaks:allow`](https://github.com/gitleaks/gitleaks#gitleaksallow), [`.gitleaksignore`](https://github.com/gitleaks/gitleaks#gitleaksignore), or an allowlist in [`.gitleaks.toml`](https://github.com/gitleaks/gitleaks#configuration). Do not narrow the command to make a finding go away.
+ - **Dependency audit.** Each manager documents its own severity threshold, ignore list, and exit codes: [npm](https://docs.npmjs.com/cli/commands/npm-audit), [pnpm](https://pnpm.io/cli/audit), [bun](https://bun.sh/docs/pm/cli/audit), [yarn classic](https://classic.yarnpkg.com/en/docs/cli/audit). The yarn exception is under *Adapt the config* below.
+ - **Both gates fail closed.** A finding blocks the commit or push. So does a scanner missing from `PATH`, and so does an npm, pnpm, or bun audit that cannot reach the registry. Leave it that way: no `|| true`, no wrapper that skips when the tool is absent. A gate that passes when it could not run reports the same green as one that ran and found nothing.
+
**Adapt the config:**
- - Replace `{package_manager}` with the detected package manager (pnpm, npm, yarn, bun)
+ - Replace `{package_manager}` with the detected package manager (pnpm, npm, yarn, bun). The one exception is the `dependency-audit` line under yarn 2 and later, where the command is [`yarn npm audit`](https://yarnpkg.com/cli/npm/audit), not `yarn audit`; check `yarn --version` before writing it (yarn 1 keeps [`yarn audit`](https://classic.yarnpkg.com/en/docs/cli/audit))
- If the repo has no `typecheck` script in package.json, omit that command and tell the user
- If the repo has no `test` script in package.json, omit that command and tell the user
- Adjust `glob` patterns to match the project's file types
### 6. Create Formatter Config (if missing)
Only create if no config exists and the user chose to install a new tool.
**Biome (`biome.json`):**
```json
{
"$schema": "https://biomejs.dev/schemas/1.9.4/schema.json",
"organizeImports": {
"enabled": true
},
"formatter": {
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"linter": {
"enabled": true,
"rules": {
"recommended": true
}
}
}
```
**Prettier (`.prettierrc`):**
```json
{
"useTabs": false,
"tabWidth": 2,
"printWidth": 80,
"singleQuote": false,
"trailingComma": "es5",
"semi": true,
"arrowParens": "always"
}
```
### 7. Verify
- [ ] `lefthook.yml` exists at repo root
- [ ] `prepare` script in package.json is `"lefthook install"`
- [ ] Formatter/linter config exists
- [ ] Run `npx lefthook run pre-commit` to verify hooks work
- [ ] No `.husky/` directory exists (if migrating from Husky, remove it)
+ - [ ] The secret scan blocks a staged secret. Skip this check when Step 2 left `secret-scan` out because the repo already scans; otherwise stage a throwaway fixture, run the hook, and confirm `secret-scan` fails; then unstage and delete the fixture. Nothing is committed either way:
+ ```bash
+ printf '%s PRIVATE KEY-----\n%s\n-----END RSA PRIVATE KEY-----\n' '-----BEGIN RSA' "$(openssl rand -base64 48)" > fixture-secret.txt
+ git add fixture-secret.txt
+ npx lefthook run pre-commit # expect secret-scan to fail
+ git reset -q fixture-secret.txt && rm fixture-secret.txt
+ ```
+
+ If `secret-scan` passes with the fixture staged, the gate is not working. Fix it before Step 9 rather than committing a scaffold that reports green.
+ - [ ] Run `npx lefthook run pre-push` and confirm `dependency-audit` ran. Skip this check when Step 2 left `dependency-audit` out because the repo already audits. A failure here that lists advisories is the gate working on the repo's current dependencies; show the user the output and let them decide what to upgrade. Do not weaken the command to get a green run. Setting a severity threshold or an ignore entry (Step 5 links where each manager documents them), including for an advisory that has no fix, is the user's decision, not yours: this gate will block every push until they make it, so ask rather than edit. With no user present, apply Step 3's unattended rule: report the output and the blocked push gate to the caller, edit nothing, and commit nothing.
+
### 8. Clean Up Husky (if migrating)
If the project previously used Husky:
```bash
# Remove Husky artifacts
rm -rf .husky
# Remove Husky from dependencies
{package_manager} remove husky lint-staged
# Remove .lintstagedrc if it exists and is no longer needed
rm -f .lintstagedrc .lintstagedrc.* lint-staged.config.*
```
Update the `prepare` script in package.json from `"husky"` to `"lefthook install"`.
### 9. Commit
Stage all changed/created files and commit with message:
- `chore: add pre-commit hooks (lefthook + biome)` (or substitute the actual formatter name)
+ `chore: add pre-commit hooks (lefthook + biome, secret scan, dependency audit)` (or substitute the actual formatter name, and drop the name of any gate Step 2 found the repo already had)
This will run through the new pre-commit hooks — a good smoke test that everything works.
## Why Lefthook over Husky
- **No node_modules dependency at hook runtime** — Lefthook hooks run directly, no `npx lint-staged` wrapper
- **Config as code** — `lefthook.yml` is declarative and version-controlled; Husky uses shell scripts in `.husky/`
- **Parallel execution** — runs formatting, typecheck, and tests in parallel by default
- **`{staged_files}` built-in** — no need for lint-staged as a separate dependency; Lefthook handles staged file filtering natively
- **`stage_fixed: true`** — automatically re-stages files after formatting fixes, no extra tooling
- **Glob filtering** — only runs tools on matching file types, no wasted cycles
## Why Biome as Default
- **Single tool** — replaces both Prettier (formatting) and ESLint (linting)
- **Fast** — written in Rust, 10-100x faster than Prettier + ESLint
- **Zero config** — works out of the box with sensible defaults
- **Compatible** — 97% Prettier-compatible formatting, growing ESLint rule coverage
- **One config file** — `biome.json` instead of `.prettierrc` + `.eslintrc` + conflicting rules
## Handoff
- **Expected input:** a repo that needs commit-time quality gates
- - **Produces:** working git hooks and tool configuration for commit-time checks
+ - **Produces:** working git hooks and tool configuration for commit-time checks, plus a secret scan at commit and a dependency audit at push
- **Supports downstream:** `/execute` and `/pre-merge` by making basic verification more automatic
- **What comes next:** return to normal feature work with stronger local feedback loops