Chapitre 14.3 — Snippets de hooks
⏱️ TL;DR — Les recettes de
settings.json(niveau projet,.claude/settings.json) pour automatiser la qualité et poser des garde-fous. Voir Partie 7 pour le pourquoi. Vérifie la syntaxe courante (noms d’événements, matcher) dans la doc ou viaupdate-config— la structure est stable, les détails peuvent bouger.
Chaîne complète (format + lint + tests + garde-fous) (→ 7.4)
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{ "type": "command", "command": "npx prettier --write ." },
{ "type": "command", "command": "npx eslint --fix ." }
]
}
],
"Stop": [
{
"hooks": [
{ "type": "command", "command": "npm run typecheck" },
{ "type": "command", "command": "npm test" }
]
}
],
"PreToolUse": [
{
"matcher": "Edit|Write",
"hooks": [{ "type": "command", "command": "node .claude/hooks/protect-paths.mjs" }]
},
{
"matcher": "Bash",
"hooks": [{ "type": "command", "command": "node .claude/hooks/git-guard.mjs" }]
}
]
},
"permissions": {
"allow": ["Bash(npm test)", "Bash(npm run lint)", "Bash(npm run typecheck)", "Bash(git status)", "Bash(git diff)"],
"deny": ["Bash(git push --force*)", "Bash(rm -rf*)"]
}
}Garde-fou « fichiers protégés » (→ 7.3)
Esquisse de .claude/hooks/protect-paths.mjs (à adapter au format d’entrée exact, cf. doc) :
// Refuse l'édition de fichiers protégés, avec un message explicatif.
import { readFileSync } from 'node:fs'
const PROTECTED = [/^prisma\/migrations\//, /(^|\/)\.env/, /\.generated\./, /lock(file)?$/i]
// Lire l'événement (chemin visé) depuis l'entrée standard — cf. doc pour le format.
const input = JSON.parse(readFileSync(0, 'utf8'))
const path = input?.tool_input?.file_path ?? ''
if (PROTECTED.some((re) => re.test(path))) {
console.error(
`Édition refusée : ${path} est protégé. ` +
`Migrations : génère-en une nouvelle. .env : utilise .env.example / variables d'env.`
)
process.exit(2) // code d'échec = bloque l'action (cf. doc pour la convention exacte)
}Garde-fou « git » (main / force push) (→ 7.3)
Esquisse de .claude/hooks/git-guard.mjs :
import { readFileSync } from 'node:fs'
import { execSync } from 'node:child_process'
const input = JSON.parse(readFileSync(0, 'utf8'))
const cmd = input?.tool_input?.command ?? ''
const branch = execSync('git rev-parse --abbrev-ref HEAD').toString().trim()
const onMain = branch === 'main' || branch === 'master'
if (/--force|-f\b/.test(cmd) && /git\s+push/.test(cmd)) {
console.error('push --force refusé. Travaille sur une branche, sans réécrire l’historique.')
process.exit(2)
}
if (onMain && /git\s+(commit|push)/.test(cmd)) {
console.error(`commit/push sur ${branch} refusé. Crée une branche de travail.`)
process.exit(2)
}⚠️ Rappel — Un garde-fou doit expliquer en bloquant (message clair + quoi faire à la place), sinon l’agent réessaie en boucle (7.3). Le format exact des données reçues par un hook et la convention de code de sortie sont à vérifier dans la doc.
CI alignée (GitHub Actions) (→ 11.5)
.github/workflows/ci.yml — mêmes vérifications que les hooks :
name: CI
on: [push, pull_request]
jobs:
check:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with: { node-version: 20, cache: npm }
- run: npm ci
- run: npm run lint
- run: npm run typecheck
- run: npm test
- run: npm run buildRecette suivante : Skills & commandes types.