Blog / Dev assisté par IA

Notre configuration type pour Claude Code : CLAUDE.md, hooks et règles de revue

Keltio · Template · 10 min de lecture

Un agent de code sans configuration partagée, c'est une pratique par développeur. Voici la configuration de départ que nous posons dans un dépôt avant de laisser une équipe utiliser Claude Code : fichier d'instructions, commandes, sous-agent de revue, hooks, permissions et revue en CI. Chaque extrait est commenté et vérifié contre la documentation officielle (code.claude.com/docs) à la date de publication. Adaptez les commandes à votre stack.

Vue d'ensemble : ce qui est versionné

Tout ce qui suit vit dans le dépôt, passe en revue comme du code et s'applique à toute l'équipe dès le clonage :

.
├── CLAUDE.md                     # instructions du projet, lues à chaque session
├── .claude/
│   ├── settings.json             # permissions + hooks, partagés (commités)
│   ├── rules/
│   │   └── api.md                # règles chargées seulement pour certains chemins
│   ├── skills/
│   │   ├── spec/SKILL.md         # commande /spec
│   │   └── plan/SKILL.md         # commande /plan
│   ├── agents/
│   │   └── code-reviewer.md      # sous-agent de revue
│   └── hooks/
│       ├── block-secrets.sh
│       ├── lint-changed.sh
│       └── run-tests.sh
└── .github/workflows/
    └── claude-review.yml         # revue IA sur chaque PR

Les préférences personnelles vont ailleurs : ~/.claude/settings.json et ~/.claude/CLAUDE.md (toutes vos sessions), .claude/settings.local.json et CLAUDE.local.md (ce projet, non commités). Côté paramètres, l'ordre de priorité est : paramètres gérés par l'organisation, arguments de ligne de commande, local, projet, utilisateur. Les listes de permissions de ces fichiers se cumulent au lieu de se remplacer.

1. La structure du CLAUDE.md

Le CLAUDE.md est chargé dans le contexte au début de chaque session. La documentation recommande de rester sous 200 lignes : au-delà, il coûte du contexte et il est moins bien suivi. Ce n'est pas de la configuration appliquée de force, ce sont des consignes. Elles doivent donc être précises et vérifiables.

# Projet : facturation-api

## Commandes
- Installer : `npm ci`
- Tests rapides : `npm run test:quick` (moins de 60 s, à lancer après chaque étape)
- Tests complets : `npm test`
- Lint + types : `npm run lint && npm run typecheck`

## Architecture
- Couches : routes/ → services/ → repositories/. Jamais d'accès ORM hors repositories/.
- Montants : entiers en centimes. Formatage uniquement via src/utils/money.ts.
- Nouvelle dépendance npm : la proposer, ne pas l'installer.

## Façon de travailler
- Tâche non triviale : lire la spec, proposer un plan, attendre validation.
- Implémenter une étape du plan à la fois, lancer les tests rapides, s'arrêter.
- Si un test échoue trois fois de suite, s'arrêter et expliquer.

## Interdits
- Ne jamais lire ni modifier .env*, secrets/, infra/prod/.
- Ne jamais désactiver un test ou un contrôle de lint pour faire passer la CI.

## Références
- Conventions de nommage : @docs/conventions.md
- Workflow git : @docs/git-workflow.md

Ce qui compte, section par section :

  • Commandes : ce que l'agent ne peut pas deviner. C'est la section la plus rentable.
  • Architecture : trois à cinq règles, celles qu'une revue de code rappelle le plus souvent.
  • Façon de travailler : c'est ici que le workflow spec, plan, code, revue devient le comportement par défaut.
  • Références : la syntaxe @chemin importe un fichier dans le contexte (jusqu'à quatre niveaux d'imports imbriqués). Les fichiers importés sont chargés au démarrage : ils comptent dans le budget de contexte.

Pour les consignes qui ne concernent qu'une partie du code, utilisez .claude/rules/ avec un filtre de chemins. La règle n'est chargée que quand Claude lit un fichier correspondant :

---
paths:
  - "src/routes/**/*.ts"
---

# Règles API
- Toute route valide son entrée avec le schéma zod associé.
- Format d'erreur unique : { code, message }, jamais de stack trace.
- Toute nouvelle route a un test d'intégration.

La commande /init génère un premier CLAUDE.md à partir du code : bon point de départ, à élaguer ensuite.

2. Commandes et sous-agents

Les commandes sont des fichiers Markdown. Les « custom commands » ont été fusionnées dans les skills : .claude/skills/spec/SKILL.md crée la commande /spec (les anciens fichiers .claude/commands/*.md fonctionnent toujours).

---
description: Rédige une spec courte à partir d'un ticket
disable-model-invocation: true
---

Rédige une spec d'une page pour : $ARGUMENTS

Sections obligatoires : Objectif, Périmètre (inclus / exclu), Règles métier,
Contraintes techniques (couches, code existant à réutiliser), Critères
d'acceptation sous forme de cases à cocher testables.
Pose-moi les questions nécessaires avant d'écrire. N'écris aucun code.

disable-model-invocation: true réserve la commande à un déclenchement manuel. $ARGUMENTS reçoit le texte tapé après /spec.

Les sous-agents sont des assistants spécialisés, avec leur propre prompt système, leurs outils et leur contexte. Un relecteur en lecture seule est le premier à créer :

---
name: code-reviewer
description: Relit le diff courant avant commit. À utiliser après chaque étape d'implémentation.
tools: Read, Glob, Grep, Bash
model: sonnet
---

Tu relis le diff non commité (git diff) de ce dépôt.
Vérifie dans l'ordre : respect des couches décrites dans CLAUDE.md,
réutilisation du code existant, gestion d'erreurs, tests couvrant les
critères d'acceptation, absence de secrets.
Réponds par une liste courte : bloquant / à corriger / suggestion.
Ne modifie aucun fichier.

Le champ tools limite ce que le sous-agent peut faire. Pour un relecteur, pas d'Edit ni de Write.

3. Les hooks : secrets, lint, tests

Les hooks sont des commandes que Claude Code exécute à des moments précis du cycle de vie. Contrairement au CLAUDE.md, ils ne dépendent pas de la bonne volonté du modèle. Ils reçoivent un JSON sur l'entrée standard et répondent par leur code de sortie. Le code 2 bloque (sur PreToolUse, l'appel d'outil est annulé et le message d'erreur est transmis à Claude). Attention : un code 1 n'est pas bloquant.

Voici la section hooks de .claude/settings.json :

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-secrets.sh",
            "args": []
          }
        ]
      }
    ],
    "PostToolUse": [
      {
        "matcher": "Write|Edit",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/lint-changed.sh",
            "args": []
          }
        ]
      }
    ],
    "Stop": [
      {
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/run-tests.sh",
            "args": [],
            "timeout": 300
          }
        ]
      }
    ]
  }
}
  • matcher filtre sur le nom de l'outil (Write|Edit : création et modification de fichiers).
  • ${CLAUDE_PROJECT_DIR} pointe vers la racine du projet, quel que soit le répertoire courant. Avec "args": [], la commande est lancée directement, sans shell, ce que la documentation recommande pour les chemins.
  • Stop se déclenche quand Claude a fini de répondre : c'est le bon moment pour les tests.

Hook secrets (PreToolUse) : refuse l'écriture d'un secret avant qu'il n'atterrisse sur le disque.

#!/bin/bash
# Bloque l'écriture de secrets probables et de fichiers sensibles
input=$(cat)
file=$(jq -r '.tool_input.file_path // ""' <<<"$input")
content=$(jq -r '.tool_input.content // .tool_input.new_string // ""' <<<"$input")

case "$file" in
  *.env|*.env.*|*.pem|*.key)
    echo "Bloqué : fichier sensible ($file). Demande à l'utilisateur." >&2
    exit 2 ;;
esac

if grep -Eq 'AKIA[0-9A-Z]{16}|-----BEGIN [A-Z ]*PRIVATE KEY-----|gh[pousr]_[A-Za-z0-9]{36}' <<<"$content"; then
  echo "Bloqué : secret probable dans $file. Utilise une variable d'environnement." >&2
  exit 2
fi
exit 0

Les motifs sont des exemples : branchez plutôt votre scanner de secrets habituel s'il sait lire l'entrée standard.

Hook lint (PostToolUse) : l'outil a déjà écrit, mais un code 2 renvoie l'erreur de lint à Claude, qui corrige dans la foulée.

#!/bin/bash
file=$(jq -r '.tool_input.file_path // ""')
case "$file" in *.ts|*.tsx) ;; *) exit 0 ;; esac
if ! out=$(npx eslint "$file" 2>&1); then
  echo "$out" >&2
  exit 2
fi
exit 0

Hook tests (Stop) : un code 2 empêche Claude de s'arrêter et lui transmet la sortie des tests.

#!/bin/bash
input=$(cat)
# Déjà relancé par ce hook : on laisse la main pour éviter une boucle
[ "$(jq -r '.stop_hook_active' <<<"$input")" = "true" ] && exit 0
cd "$CLAUDE_PROJECT_DIR" || exit 0
if ! out=$(npm run test:quick 2>&1); then
  echo "Tests en échec, corrige avant de terminer :" >&2
  echo "$out" | tail -n 40 >&2
  exit 2
fi
exit 0

Rendez les scripts exécutables (chmod +x) et testez-les une première fois : un chemin mal saisi produit une erreur non bloquante, et le garde-fou est silencieusement inactif.

Contexte paiement. Sur un périmètre soumis à PCI-DSS, ajoutez un hook PreToolUse qui cherche des numéros de carte plausibles (suite de 13 à 19 chiffres qui passe le contrôle de Luhn, hors liste de numéros de test de votre prestataire), et le même contrôle sur l'événement UserPromptSubmit, qui reçoit le texte saisi dans le champ prompt : un code 2 rejette le prompt avant son envoi au modèle. Cela couvre une partie du risque (données collées ou écrites), pas la conformité : elle se traite avec votre QSA.

4. Les permissions

Les règles deny sont évaluées en premier, puis ask, puis allow. Une règle allow ne peut pas créer d'exception à une règle deny.

{
  "$schema": "https://json.schemastore.org/claude-code-settings.json",
  "permissions": {
    "allow": [
      "Bash(npm run test *)",
      "Bash(npm run lint)",
      "Bash(npm run typecheck)",
      "Bash(git status)",
      "Bash(git diff *)",
      "Bash(git log *)"
    ],
    "ask": [
      "Bash(git push *)",
      "Bash(npm install *)",
      "WebFetch"
    ],
    "deny": [
      "Read(./.env)",
      "Read(./.env.*)",
      "Read(./secrets/**)",
      "Edit(./infra/prod/**)",
      "Bash(curl *)"
    ]
  }
}
  • allow : les commandes sans risque que l'on ne veut plus valider à la main. Placez le * après la sous-commande : Bash(git log *) n'autorise que git log, Bash(git *) autorise tout git.
  • ask : ce qui sort du poste (push, nouvelles dépendances, accès web).
  • deny : un Read refusé bloque aussi l'écriture sur ce chemin. Les règles de lecture s'appliquent aux outils de fichiers et aux commandes shell reconnues (cat, head…), pas à toute commande qui lirait un fichier indirectement.

Les permissions ne sont pas un bac à sable. Pour une isolation réelle, combinez-les avec le sandboxing de Claude Code ou un conteneur. Et réservez le mode qui saute toutes les validations aux environnements jetables.

5. La revue en CI

Dernière couche : une revue automatique sur chaque PR. Ce workflow reprend l'exemple de la documentation officielle (action anthropics/claude-code-action@v1 et plugin code-review) :

name: Code Review
on:
  pull_request:
    types: [opened, synchronize, ready_for_review, reopened]
jobs:
  review:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      pull-requests: read
      issues: read
      id-token: write
    steps:
      - uses: actions/checkout@v6
        with:
          fetch-depth: 1
      - uses: anthropics/claude-code-action@v1
        with:
          anthropic_api_key: ${{ secrets.ANTHROPIC_API_KEY }}
          plugin_marketplaces: "https://github.com/anthropics/claude-code.git"
          plugins: "code-review@claude-code-plugins"
          prompt: "/code-review:code-review --comment ${{ github.repository }}/pull/${{ github.event.pull_request.number }}"
          claude_args: '--allowedTools "mcp__github_inline_comment__create_inline_comment"'

L'option --comment publie les remarques en commentaires sur la PR ; le CLAUDE.md du dépôt est lu à chaque exécution, d'où l'intérêt de le garder court. Réglage du bruit, coût par PR et limites : nous y consacrons un article dédié.

6. Adapter la configuration au contexte

Le socle est commun, les réglages varient :

  • Embarqué / industriel : permissions très restrictives sur les commandes de flashage et de déploiement matériel, tests lancés sur simulateur dans le hook Stop, règles de chemins pour le code critique (MISRA ou équivalent).
  • IT de gestion / SaaS : hooks de tests rapides, règles d'API, revue en CI sur toutes les PR.
  • Services financiers : deny étendu (données de production, dumps), hooks secrets et numéros de carte, accès web en ask ou refusé.

Pour une organisation multi-sites ou multi-agences, les paramètres gérés (managed-settings.json, MDM ou console d'administration) s'imposent à tous les postes. Par exemple, permissions.disableBypassPermissionsMode à "disable" interdit le mode sans validation, et allowManagedHooksOnly ne laisse tourner que les hooks définis par l'organisation. Une configuration versionnée et appliquée converge plus vite qu'une charte. C'est aussi ce qu'une DSI cliente veut voir avant d'autoriser un agent de code sur son code.

7. Les erreurs fréquentes

  1. Un CLAUDE.md de 600 lignes. Il coûte du contexte et il est moins bien suivi. Élaguez, déplacez vers .claude/rules/ ou des skills.
  2. Des règles de sécurité seulement dans le CLAUDE.md. Ce sont des consignes. Ce qui doit être garanti va dans les permissions ou les hooks.
  3. Un hook qui sort en code 1. Il ne bloque rien. Utilisez exit 2.
  4. Un hook Stop sans garde anti-boucle. Vérifiez stop_hook_active.
  5. Des tests complets dans un hook. Dix minutes à chaque réponse, et l'équipe désactive le hook. Gardez une suite rapide.
  6. **Bash(git *) en allow**, qui autorise aussi git push --force.
  7. La configuration dans settings.local.json. Elle n'est pas partagée. Ce qui est commun va dans .claude/settings.json, commité.
  8. Aucune revue de la configuration elle-même. Un changement de settings.json passe en PR comme le reste.

Cette configuration commentée sert aussi de support de formation : la parcourir ligne à ligne avec une équipe, sur son propre dépôt, en une heure, vaut mieux qu'une présentation générale.

Par où commencer

  1. Lancez /init sur un dépôt pilote, puis ramenez le CLAUDE.md sous 100 lignes avec les commandes et vos cinq règles d'architecture.
  2. Ajoutez les permissions deny sur les secrets et la production, et ask sur le push.
  3. Installez le hook secrets, testez-le en demandant volontairement à Claude d'écrire une fausse clé.
  4. Ajoutez les hooks lint et tests rapides, mesurez leur durée, ajustez.
  5. Branchez la revue en CI et relisez ses remarques pendant deux semaines avant d'étendre au reste des dépôts.

Nous accompagnons les équipes de développement dans la configuration et le déploiement de Claude Code et des agents de code. Si vous voulez en parler : [email protected]

Un sujet similaire chez vous ?

Nous en parlons volontiers, sans engagement : 30 minutes pour faire le point sur votre contexte.

Une conversation pour résoudre vos enjeux