Un bon CLAUDE.md n'est pas un README — il capture les invariants que Claude ne peut pas déduire du code. 6 à écrire, 4 à éviter, 5 questions.
Mon projet Pickful contient un système de jury communautaire décentralisé, des paiements crypto x402, Sign-In with Ethereum, une configuration multi-base, du push en temps réel — que des stacks sortis ces deux dernières années. Claude livre ces fonctionnalités vite et proprement.
Mais ouvre le CLAUDE.md du projet et tu verras : système de jury et x402 n'y apparaissent pas — pas une seule fois.
Ce n'est pas un oubli. Le rôle du CLAUDE.md n'a jamais été de « décrire les fonctionnalités ». C'est de capturer ce que Claude ne pourra jamais déduire en lisant le code.
Les gens qui écrivent un CLAUDE.md pour la première fois le traitent souvent comme un README — en décrivant chaque fonctionnalité principale :
Claude ouvre topic_review_service.rb / x402.rb / like_points_service.rb et le lit plus précisément que tu ne l'écris jamais. Une description de mille mots sur la logique métier coûte à Claude quelques centaines de tokens à lire depuis le code — et sans dérive d'interprétation. Le code est le fait. Les descriptions, une information de seconde main.
Ce qui fait réellement trébucher Claude, ce sont ces 6 catégories.
Le CLAUDE.md de Pickful contient ce genre de lignes :
Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
config.lexxy.override_action_text_defaults = false
Chaque ligne va à l'encontre du pari par défaut. Face à un projet Rails, les suppositions par défaut de Claude sont :
Sans ces lignes dans CLAUDE.md, demande à Claude d'ajouter une nouvelle fonctionnalité JS : forte probabilité qu'il installe Webpacker, modifie package.json, écrive une config bundler — tout faux, et faux en silence (l'app tourne, mais le pipeline d'assets est pollué).
Ces lignes dans CLAUDE.md disent à Claude : ne devine pas, c'est décidé.
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
Formulation sobre, mais ça peut t'économiser une nuit entière. Le multi-DB par défaut de Rails 8 est un comportement nouveau — Claude ne va pas vérifier de lui-même combien tu en utilises. Une migration apparemment anodine qui tombe dans la mauvaise base ne lève pas d'erreur en dev (les quatre sont en PostgreSQL, le schema passe partout). Mais en prod, la table des jobs de Solid Queue se glisse dans le backup de primary, ou un modèle de primary requête la base de cache — des bugs qui mettent des jours à remonter.
Deux lignes dans CLAUDE.md contre une journée de debug en prod.
/p-{slug} - Short post URLs (4-5 char alphanumeric)
/t-{slug} - Topic URLs (3-4 char alphanumeric)
/s-{code} - Short URL redirects (3-4 char alphanumeric)
/r-{referral} - Referral links
Les routes, Claude peut les voir dans routes.rb. Mais les conventions de longueur (4-5 caractères, 3-4 caractères) sont enfouies dans la logique de génération de slug, côté modèle ou service. Demande à Claude un nouveau type de short link, il générera probablement un slug de 6 caractères, style UUID ou purement numérique — en décalage avec le langage visuel du système.
Caractéristique de ces « conventions » : les enfreindre ne provoque pas d'erreur, mais le code paraît bancal au prochain lecteur. À écrire, obligatoirement.
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
Les deux nombres vivent quelque part dans le code (User#vip?, scope Post#hot?). Le problème : quand Claude touche à quelque chose de proche — ajuster des récompenses, ajouter une notification « presque VIP », écrire un cron qui épingle les hot posts — il n'aligne pas automatiquement les seuils ailleurs.
Résultat : tu récompenses une tâche par 500 points mais le wording dit « tu peux devenir VIP » (400 suffit) ; ou tu alimentes des seed data pour une nouvelle fonctionnalité avec trop peu de likes, le seuil 15 n'est jamais franchi.
Claude a une belle maîtrise du code, mais pas de sens numérique à l'échelle du système. Mettre les seuils clés dans CLAUDE.md fait que chaque conversation démarre en sachant que « 400 et 15 sont des nombres spéciaux ».
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
Le rôle de cette ligne est la navigation, pas la description.
Sans elle, quand Claude doit ajouter un nouveau contrôle de permission, trois possibilités :
unless current_user.admin? en dur dans un controllerauthorize? dans un modèleAvec « Pundit policies in app/policies/ » écrit, Claude va à chaque fois ajouter un fichier policy dans app/policies/ — style cohérent.
Une ligne supprime le « travail de détective » de Claude à chaque fois.
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
En ajoutant une nouvelle fonctionnalité, les defaults de Claude sont :
Alors que ton projet a besoin en vrai :
Enfreindre ces « contraintes externes à l'échelle du projet » génère un tas de travail d'après — traductions à compléter, tests à réécrire. Écrire dans CLAUDE.md, c'est clouer une fois pour toutes « ce qu'il faut faire à chaque fois ».
Aussi important que « à écrire absolument » : « ne pas écrire ». Les catégories ci-dessous, tu vois = tu supprimes :
1. Descriptions de fonctionnalités
« Système de jury : les utilisateurs peuvent signaler du contenu non conforme, le signalé passe à un vote public, les jurés sont choisis parmi... »
→ Claude ouvre topic_review_service.rb et le lit plus précisément que tu ne l'as écrit. Balancer ça dans chaque nouvelle conversation est du gâchis pur.
2. Ce qui se lit instantanément dans l'arborescence / le Gemfile
« app/models/ contient des modèles ActiveRecord », « Utilise Rails 8 », « BD PostgreSQL »
→ Claude jette un œil à la racine et au Gemfile, il sait.
3. Savoir de programmation général
« Controllers should be thin, delegate to services », « Éviter les N+1 », « Écrire des tests pour les fonctionnalités principales »
→ C'est déjà dans les données d'entraînement de Claude. N'écris ça que si ton projet est atypique — par exemple « volontairement, on n'utilise pas de service layer ; la logique vit dans les controllers ».
4. Contexte de la tâche en cours
« On est en train de refactorer le système de paiement, le focus est... »
→ Ça, c'est du contexte de conversation, pas un fait du projet. Le glisser dans CLAUDE.md pollue toutes les autres conversations.
Placer la preuve « je peux le faire » avant le discours. Après avoir écrit la section précédente, j'ai repassé mon propre CLAUDE.md de Pickful — 238 lignes — par les 5 questions. Résultat : environ la moitié est du gâchis.
À couper (~120 lignes):
La majorité du bloc de commandes de dev (70 lignes → 10) : bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman sont toutes des commandes Rails standard, déjà dans l'entraînement de Claude. On garde seulement les trois spécifiques au projet : bin/jobs (worker Solid Queue), bin/importmap pin (propre à ImportMap), bin/kamal deploy.
Liste Core Domain Models (35 lignes → virer intégralement) : lister 20 modèles avec leurs rôles, c'est l'exemple type du « CLAUDE.md façon README » — Claude lance ls app/models/ ou lit un fichier modèle et sait. Balancer ça dans chaque conversation est du gâchis pur.
Éléments standards dans Tech Stack (28 lignes → 8) : Rails 8 / Devise / Pundit / Tailwind / pg_search se lisent tous immédiatement depuis le Gemfile. On garde uniquement les contre-intuitifs : Propshaft / ImportMap / Lexxy / x402-rails / Grover.
Quelques lignes de savoir générique dispersées : « Controllers should be thin », « Use app/jobs/ for async processing », ce que testent request specs vs. model specs — Claude fait ça par défaut. Des tokens brûlés.
Ce qu'il reste, ~100 lignes : conventions de routing URL, répartition des 4 DB, seuils VIP 400 / Hot 15, override Lexxy, choix d'archi anti-défaut, panneau Pundit, liste des locales, FactoryBot (pas fixtures).
Mais encore plus important : quels invariants manquent ?
Pendant l'audit j'ai réalisé quelques trucs que j'aurais dû ajouter et que je n'ai jamais ajoutés :
238 lignes compressées à 100–120, plus 5–10 lignes d'invariants précédemment omis — ça se rapproche de la « bonne densité » d'un CLAUDE.md.
Ceci n'est pas un tutoriel. C'est un audit de mon propre projet — et je me suis trompé aussi. La forme correcte d'un CLAUDE.md, c'est continuer à supprimer et à ajouter — tout projet qui mûrit un peu devrait avoir un CLAUDE.md plus court et plus dense avec le temps.
Chaque fois que je veux ajouter quelque chose dans CLAUDE.md, je passe par la checklist suivante :
Les règles qui passent les 5 restent ; celles qui en ratent une, on supprime ou on réécrit.
Le bon usage du CLAUDE.md, ce n'est pas « présenter le projet », c'est compresser le savoir tacite entre toi et la codebase que Claude ne comblera jamais en lisant du code — choix inhabituels, seuils invisibles, conventions contraires aux defaults, contraintes externes à l'échelle du projet.
Chaque ligne que tu ajoutes doit répondre à : « Ça, est-ce que Claude ne peut pas le lire dans le code ? » Non — ça reste. Oui — ça dégage.
Un CLAUDE.md écrit de cette façon est généralement plus de moitié plus court que la version initiale, mais sa valeur par conversation dépasse de loin des milliers de mots de description de fonctionnalités. Et il n'est jamais « fini » — chaque nouvelle fonctionnalité que tu livres révèle un autre invariant qui aurait dû s'y trouver, et un paragraphe qui peut désormais partir. Supprimer et ajouter, supprimer et ajouter — c'est la maintenance quotidienne du CLAUDE.md.