Un buen CLAUDE.md no es un README — captura invariantes que Claude no puede inferir del código. 6 cosas que escribir, 4 que omitir, 5 preguntas.
Mi proyecto Pickful tiene un sistema descentralizado de jurado comunitario, pagos en cripto con x402, Sign-In with Ethereum, configuración multi-base de datos, push en tiempo real — todo stacks que aparecieron en los últimos uno o dos años. Claude entrega estas funciones rápido y limpias.
Pero abre el CLAUDE.md del proyecto y verás: el sistema de jurado y x402 no aparecen — ni una sola vez.
No es un descuido. El propósito de CLAUDE.md nunca fue "describir funciones". Es capturar las cosas que Claude nunca podrá deducir solo leyendo el código.
La gente que escribe CLAUDE.md por primera vez suele tratarlo como un README — describiendo cada función principal:
Claude puede abrir topic_review_service.rb / x402.rb / like_points_service.rb y leerlo con más precisión de la que tú puedes escribir. Una descripción de mil palabras sobre lógica de negocio le cuesta a Claude unos cientos de tokens leer del código — sin deriva interpretativa. El código es el hecho. Las descripciones son de segunda mano.
Lo que realmente hace tropezar a Claude son estas 6 categorías.
El CLAUDE.md de Pickful tiene líneas como estas:
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 línea va contra la suposición por defecto. Ante un proyecto Rails, las suposiciones por defecto de Claude son:
Sin estas líneas en CLAUDE.md, si le pides a Claude añadir una nueva función JS, lo más probable es que instale Webpacker, edite package.json, escriba una config de bundler — todo mal, y mal de forma silenciosa (la app sigue corriendo, pero el pipeline de assets queda contaminado).
Esas líneas en CLAUDE.md le dicen a Claude: no adivines, ya está decidido.
PostgreSQL with 4 separate databases:
- primary - Main application data
- cache - Solid Cache storage
- queue - Solid Queue jobs
- cable - Action Cable subscriptions
Redacción sencilla, pero puede ahorrarte una noche entera. El multi-DB por defecto de Rails 8 es un comportamiento nuevo — Claude no va a verificar por sí solo cuántas bases usas. Una migración aparentemente inocua aterriza en la base equivocada y en desarrollo no da error (las cuatro son PostgreSQL; el schema migra en cualquiera). Pero en producción la tabla de jobs de Solid Queue se cuela en el backup de primary, o un modelo de primary consulta la base de cache — bugs que tardan días en aflorar.
Dos líneas en CLAUDE.md vs. un día entero depurando producción.
/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
Las rutas Claude puede verlas en routes.rb, pero las convenciones de longitud (4-5 caracteres, 3-4 caracteres) están enterradas en la lógica de generación de slug en modelos o servicios. Pide a Claude que añada un tipo nuevo de short link y probablemente genere un slug de 6 caracteres, estilo UUID, o solo dígitos — disonante con el lenguaje visual de todo el sistema.
El rasgo de estas "convenciones": violarlas no genera error, pero el código se siente raro para el siguiente lector. Hay que escribirlas.
VIP status at 400+ points
Posts with 15+ likes are "hot" posts
Ambos números viven en algún lugar del código (User#vip?, un scope Post#hot?). El problema: cuando Claude toca algo adyacente — ajustar recompensas de puntos, añadir una notificación "casi VIP", escribir un cron que fije hot posts — no alinea automáticamente los umbrales en otros sitios.
Resultado: recompensas una tarea con 500 puntos pero el copy dice "puedes volverte VIP" (con 400 ya basta); o haces data seed para una función nueva con menos likes y nunca se cruza el umbral de 15.
La habilidad de Claude para programar es fuerte, pero no tiene sentido numérico del sistema completo. Poner los umbrales clave en CLAUDE.md hace que cada conversación empiece sabiendo que "400 y 15 son números especiales".
- Devise (authentication) + Pundit (authorization)
- Pundit policies in app/policies/
- Check UserPolicy, PostPolicy, etc. for permission rules
El trabajo de esta línea es navegación, no descripción.
Sin ella, cuando Claude necesita añadir un nuevo control de permiso, hay tres posibilidades:
unless current_user.admin? directo en el controllerauthorize? en el modeloCon "Pundit policies in app/policies/" escrito, Claude va siempre a app/policies/ a añadir un archivo de policy, con estilo consistente.
Una línea elimina el "trabajo detectivesco" de Claude cada vez.
Supported locales: en, zh-CN, zh-TW
Testing stack: RSpec + FactoryBot + Capybara + Shoulda Matchers
Al añadir una función nueva, los defaults de Claude son:
Pero tu proyecto realmente necesita:
Violar estas "restricciones externas a nivel proyecto" produce mucho trabajo posterior — traducciones a completar, tests a reescribir. CLAUDE.md fija de una vez "las cosas que hay que hacer siempre".
Tan importante como "escribir obligatorio" es "no escribir". Las siguientes categorías, en cuanto las veas, bórralas:
1. Descripción de funciones
"Sistema de jurado: los usuarios pueden reportar contenido infractor, lo reportado entra en votación pública, los jurados se eligen de..."
→ Claude abre topic_review_service.rb y lo lee con más precisión que tu texto. Meter esto en cada conversación nueva es desperdicio puro.
2. Lo que se lee al instante del árbol de directorios / Gemfile
"app/models/ contiene modelos ActiveRecord", "Usa Rails 8", "La base es PostgreSQL"
→ Claude da un vistazo a la raíz del proyecto y al Gemfile y lo sabe.
3. Conocimiento genérico de programación
"Controllers should be thin, delegate to services", "Evita N+1", "Escribe tests para las funciones principales"
→ Ya está en los datos de entrenamiento de Claude. Escríbelo solo cuando tu proyecto sea inusual — por ejemplo, "no usamos service layer a propósito; la lógica vive en los controllers".
4. Contexto de la tarea actual
"Estamos refactorizando el sistema de pagos; el foco es..."
→ Eso es contexto de conversación, no hecho del proyecto. Meterlo en CLAUDE.md contamina las demás conversaciones.
Pon la prueba de "yo puedo hacerlo" antes del sermón. Después de escribir la sección anterior, pasé mi propio CLAUDE.md de Pickful — 238 líneas — por las 5 preguntas. Resultado: aproximadamente la mitad es desperdicio.
Lo que hay que cortar (~120 líneas):
La mayor parte del bloque de comandos de desarrollo (70 líneas → 10): bin/setup / bin/rails db:migrate / bundle exec rspec / bin/rubocop / bin/brakeman son todos comandos Rails estándar, ya en el entrenamiento de Claude. Conservar solo los tres específicos del proyecto: bin/jobs (worker de Solid Queue), bin/importmap pin (específico de ImportMap), bin/kamal deploy.
Lista Core Domain Models (35 líneas → borrarla entera): listar 20 modelos con su rol es el ejemplo clásico de "CLAUDE.md estilo README" — Claude corre ls app/models/ o lee un modelo y ya sabe. Meterlo en cada conversación es desperdicio puro.
Elementos estándar del Tech Stack (28 líneas → 8): Rails 8 / Devise / Pundit / Tailwind / pg_search son todos legibles al instante desde el Gemfile. Dejar solo los contraintuitivos: Propshaft / ImportMap / Lexxy / x402-rails / Grover.
Conocimiento genérico de programación disperso: "Controllers should be thin", "Use app/jobs/ for async processing", qué prueban request specs vs. model specs — Claude ya lo hace por defecto. Solo consume tokens.
Lo que queda, ~100 líneas: convenciones de ruteo URL, distribución de las 4 DB, umbrales VIP 400 / Hot 15, override de Lexxy, elecciones arquitectónicas contra default, señal de Pundit, lista de locales, FactoryBot (no fixtures).
Pero más importante: ¿qué invariantes faltan?
Auditando me di cuenta de varios que deberían haber entrado y nunca entraron:
238 líneas comprimidas a 100–120, más 5–10 líneas de invariantes antes omitidos — eso está más cerca de la "densidad correcta" de un CLAUDE.md.
Esto no es un tutorial. Es una auditoría a mi propio proyecto — y yo también me equivoqué. La forma correcta de CLAUDE.md es seguir borrando y seguir añadiendo — cualquier proyecto que madure un poco debería tener un CLAUDE.md más corto y más denso con el tiempo.
Cada vez que voy a añadir algo a CLAUDE.md, paso el siguiente checklist:
Las reglas que pasan las 5 se quedan; las que fallan alguna se borran o se reescriben.
El uso correcto de CLAUDE.md no es "presentar el proyecto", es comprimir el conocimiento tácito entre tú y la codebase que Claude nunca podrá completar leyendo el código — elecciones inusuales, umbrales invisibles, convenciones que contradicen defaults, restricciones externas a nivel proyecto.
Cada línea que añades debe responder: "¿Esto Claude no puede leerlo del código?" No puede — se queda. Puede — se borra.
Un CLAUDE.md escrito así suele ser más de la mitad más corto que el primer borrador, pero vale muchísimo más por conversación que miles de palabras de descripción de funciones. Y siempre está "aún sin terminar" — cada nueva función lanzada saca a flote otro invariante que debió estar dentro, y otro párrafo que ya se puede cortar. Borrar y añadir, borrar y añadir — ese es el mantenimiento diario de CLAUDE.md.