Um bom CLAUDE.md não é um README — captura invariantes que o Claude não consegue inferir do código. 6 coisas para escrever, 4 para pular, 5 perguntas.
Meu projeto Pickful tem sistema de júri comunitário descentralizado, pagamentos em cripto com x402, Sign-In with Ethereum, multi-banco de dados, push em tempo real — todas stacks que surgiram nos últimos um ou dois anos. O Claude entrega essas features rápido e bem.
Mas abre o CLAUDE.md do projeto e você vai perceber: sistema de júri e x402 — essas palavras não aparecem, nem uma vez.
Isso não é descuido. O propósito do CLAUDE.md nunca foi "descrever features". É capturar aquilo que o Claude jamais vai deduzir só lendo o código.
Quem escreve CLAUDE.md pela primeira vez costuma tratá-lo como README — descrevendo cada feature principal:
Conteúdo assim o Claude abre topic_review_service.rb / x402.rb / like_points_service.rb e lê com mais precisão do que você escreve. Mil palavras de lógica de negócio custam algumas centenas de tokens pro Claude ler direto do código — sem desvio interpretativo. Código é fato. Descrição é informação de segunda mão.
O que realmente faz o Claude tropeçar são estas 6 categorias.
O CLAUDE.md do Pickful tem linhas assim:
Propshaft (not Sprockets)
ImportMap (no JavaScript bundler)
Hotwire: Turbo Frames, Turbo Streams, Stimulus
Lexxy gem overrides ActionText:
config.lexxy.override_action_text_defaults = false
Cada linha vai contra o palpite default. Vendo um projeto Rails, os palpites default do Claude são:
Sem essas linhas no CLAUDE.md, peça ao Claude pra adicionar uma feature JS nova: alta chance de ele instalar Webpacker, mexer em package.json, escrever config de bundler — tudo errado, e errado de forma silenciosa (o app roda, mas o pipeline de assets ficou contaminado).
Essas linhas no CLAUDE.md dizem pro Claude: não adivinhe, já foi decidido.
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
Redação simples, mas pode economizar uma noite inteira. Multi-DB default do Rails 8 é um comportamento novo, o Claude não vai por conta própria conferir quantos bancos você usa. Uma migration que parece inocente pousa no banco errado e em dev não dá erro (os quatro são PostgreSQL, o schema migra em qualquer um). Só que em produção a tabela de jobs do Solid Queue vaza pro backup do primary, ou um modelo do primary consulta o banco de cache — bugs que levam dias pra emergir.
Duas linhas no CLAUDE.md vs. um dia caçando bug em produção.
/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
As rotas o Claude enxerga em routes.rb, mas as convenções de comprimento (4-5 chars, 3-4 chars) estão enterradas na lógica de geração de slug em modelos ou services. Peça ao Claude um novo tipo de short link e é provável que ele gere slug de 6 caracteres, estilo UUID, ou só dígitos — em descompasso com a linguagem visual do sistema todo.
O traço dessas "convenções": violá-las não produz erro, mas o código soa estranho pro próximo leitor. Precisa escrever.
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
Os dois números vivem em algum lugar do código (User#vip?, scope Post#hot?). O problema: quando o Claude mexe em algo adjacente — ajustar recompensas, adicionar notificação "quase VIP", escrever cron pra fixar hot posts — ele não alinha automaticamente os thresholds em outros lugares.
Resultado: você recompensa uma tarefa com 500 pontos mas a copy diz "você pode virar VIP" (com 400 já basta); ou faz seed de uma feature nova com poucas curtidas e nada cruza o threshold de 15.
A capacidade de código do Claude é forte, mas ele não tem senso numérico do sistema inteiro. Pondo thresholds-chave no CLAUDE.md, toda conversa começa sabendo que "400 e 15 são números especiais".
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
O papel desta linha é navegação, não descrição.
Sem ela, quando o Claude precisa adicionar um novo controle de permissão, três possibilidades:
unless current_user.admin? direto no controllerauthorize? próprio no modelCom "Pundit policies in app/policies/" escrito, o Claude vai direto em app/policies/ adicionar um arquivo de policy — estilo consistente.
Uma linha economiza o "trabalho de detetive" do Claude toda vez.
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
Ao adicionar uma feature nova, os defaults do Claude são:
Mas seu projeto precisa de fato:
Violar essas "restrições externas do projeto inteiro" gera monte de retrabalho — traduções a completar, testes a reescrever. O CLAUDE.md prega de uma vez "o que tem que ser feito toda vez".
Tão importante quanto "tem que escrever" é "não escreva". As categorias abaixo, viu = apague:
1. Descrição de feature
"Sistema de júri: usuários podem denunciar conteúdo inadequado, o denunciado entra em votação pública, jurados são sorteados de..."
→ O Claude abre topic_review_service.rb e lê com mais precisão do que você escreveu. Empurrar isso em toda nova conversa é puro desperdício.
2. O que se lê na hora pela árvore de diretórios / Gemfile
"app/models/ tem modelos ActiveRecord", "Usa Rails 8", "Banco é PostgreSQL"
→ O Claude dá uma olhada na raiz do projeto e no Gemfile e sabe.
3. Conhecimento genérico de programação
"Controllers should be thin, delegate to services", "Evite N+1", "Escreva testes das features principais"
→ Já está no treino do Claude. Só escreva se seu projeto for anormal — tipo "de propósito não usamos service layer; a lógica vive nos controllers".
4. Contexto da tarefa atual
"Estamos agora refatorando o sistema de pagamentos; o foco é..."
→ Isso é contexto de conversa, não fato do projeto. Jogar no CLAUDE.md contamina todas as outras conversas.
Coloque a prova de "eu consigo fazer" antes do discurso. Depois de escrever a seção acima, passei meu próprio CLAUDE.md do Pickful — 238 linhas — pelas 5 perguntas. Resultado: cerca de metade é desperdício.
O que cortar (~120 linhas):
Quase todo o bloco de comandos de desenvolvimento (70 linhas → 10): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman são todos comandos Rails padrão, já no treino do Claude. Só ficam os três específicos do projeto: bin/jobs (worker do Solid Queue), bin/importmap pin (específico do ImportMap), bin/kamal deploy.
Lista Core Domain Models (35 linhas → apagar toda): listar 20 modelos com o papel de cada um é o exemplo mais clássico de "CLAUDE.md estilo README" — o Claude roda ls app/models/ ou lê um arquivo de model e sabe. Jogar isso em toda conversa é desperdício puro.
Itens padrão do Tech Stack (28 linhas → 8): Rails 8 / Devise / Pundit / Tailwind / pg_search são todos legíveis na hora pelo Gemfile. Só ficam os contra-intuitivos: Propshaft / ImportMap / Lexxy / x402-rails / Grover.
Conhecimento genérico de programação espalhado: "Controllers should be thin", "Use app/jobs/ for async processing", o que request specs vs. model specs testam — o Claude já faz por default. Só queima token.
O que resta, ~100 linhas: convenções de roteamento URL, divisão dos 4 bancos, thresholds VIP 400 / Hot 15, override do Lexxy, escolhas arquiteturais anti-default, placa Pundit, lista de locales, FactoryBot (não fixtures).
Mais importante: quais invariantes ainda faltam?
Auditando, percebi alguns que eu deveria ter adicionado e nunca adicionei:
238 linhas comprimidas a 100–120, mais 5–10 linhas de invariantes antes esquecidos — isso está mais próximo da "densidade certa" pra um CLAUDE.md.
Isto não é tutorial. É uma auditoria do meu próprio projeto — e eu também errei. A forma correta de CLAUDE.md é ir apagando e ir adicionando — qualquer projeto que amadureça um pouco deveria ter um CLAUDE.md cada vez mais curto e mais denso.
Toda vez que penso em adicionar algo no CLAUDE.md, rodo o checklist abaixo:
Regras que passam nas 5 ficam; qualquer uma que não passa se apaga ou reescreve.
O uso correto do CLAUDE.md não é "apresentar o projeto", é comprimir o conhecimento tácito entre você e a codebase que o Claude nunca vai preencher lendo código — escolhas incomuns, thresholds invisíveis, convenções que contradizem defaults, restrições externas do projeto inteiro.
Cada linha que você adiciona deve responder: "Isto aqui, o Claude não consegue ler do código?" Não consegue — fica. Consegue — apaga.
Um CLAUDE.md escrito assim costuma ser bem mais que metade do tamanho do primeiro rascunho, mas vale muito mais por conversa do que milhares de palavras de descrição de feature. E ele está sempre "ainda não certo" — toda feature nova revela outro invariante que deveria estar lá, e outro parágrafo que agora dá pra cortar. Apaga e adiciona, apaga e adiciona — essa é a manutenção diária do CLAUDE.md.