Free

Un buen CLAUDE.md no describe funciones: solo captura lo que Claude no puede ver leyendo el código

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.

Describir funciones es desperdicio de tokens

La gente que escribe CLAUDE.md por primera vez suele tratarlo como un README — describiendo cada función principal:

  • "El sistema de jurado permite a los usuarios reportar contenido; lo reportado entra en votación pública donde los jurados..."
  • "Los pagos x402 disparan transferencias on-chain mediante el código de estado HTTP 402..."
  • "Los likes otorgan puntos; a los 400 puntos el usuario se vuelve VIP..."

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.

Las 6 categorías que realmente salvan

1. Elecciones arquitectónicas contraintuitivas

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:

  • Los activos van por Sprockets (la inercia de proyectos viejos)
  • JS usa Webpacker o esbuild
  • El frontend es React, o una mezcla de Stimulus + Turbo
  • El rich text es ActionText vanilla

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.

2. Distribución multi-base de datos

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.

3. Las "convenciones invisibles" del ruteo URL

/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.

4. Umbrales de negocio hardcoded

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".

5. Señal del stack de auth/authz

- 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:

  • Escribir unless current_user.admin? directo en el controller
  • Desenterrar restos de CanCan que ya no se usan
  • Inventar su propio método authorize? en el modelo

Con "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.

6. Restricciones externas a nivel proyecto

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:

  • Añadir solo strings en inglés
  • Escribir tests con Minitest + fixtures (default de Rails)

Pero tu proyecto realmente necesita:

  • Traducciones en 3 locales
  • RSpec + FactoryBot, no fixtures

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".

El otro lado: esto es puro desperdicio

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.

Auditoría real: mi propio CLAUDE.md también se puede cortar por la mitad

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:

  • Números enterrados en el sistema de jurado (umbral de votación, requisitos de elegibilidad del jurado) — nada expuesto en CLAUDE.md
  • Si x402 tiene chain id, dirección de contrato, env vars requeridas — sin esto Claude no grepeará el archivo de config y se inventará valores
  • Reglas especiales en los servicios de trading de puntos / Referral

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.

5 preguntas antes de añadir una regla

Cada vez que voy a añadir algo a CLAUDE.md, paso el siguiente checklist:

  1. ¿Puede Claude deducir esta regla leyendo 3 archivos? Si sí — no la escribas, que lea.
  2. ¿Esta regla es contraintuitiva? (Umbrales inusuales, elecciones de librería no mainstream, config que contradice los defaults upstream.) Si sí — hay que escribirla.
  3. ¿Es una invariante de toda la codebase, o afecta a un solo archivo? Las de un solo archivo van como comentario en el código, no suben a CLAUDE.md.
  4. ¿Violar esta regla hará que Claude se equivoque silenciosamente? (Sin error, pero semántica mal — base incorrecta, traducción faltante, policy no consultada.) Si sí — obligatoria.
  5. ¿Se explica en 3 líneas? Si no — es que tú mismo no lo has pensado bien. No la escribas aún.

Las reglas que pasan las 5 se quedan; las que fallan alguna se borran o se reescriben.

Resumen en una línea

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.