Un agents.md en cada repo: documentar el proyecto para que la IA no se lo invente
Llevo un fichero de contexto en casi todos mis repositorios. Funciona muy bien hasta que se queda obsoleto: entonces es peor que no tener nada, porque la IA lo lee con la misma confianza que el código.
IABuenas prácticasDocumentaciónNext.js
Llevo un fichero de contexto —agents.md, AGENTS.md o CLAUDE.md, según el repo y el momento— en casi todos mis proyectos. La idea es sencilla: si voy a trabajar con un agente, prefiero gastar diez minutos escribiendo cómo está montado el proyecto que repetirlo en cada conversación.
Funciona. Pero hace poco me llevé un recordatorio incómodo de cuál es el modo de fallo.
El fichero que mentía
Estaba rehaciendo la documentación de este mismo portfolio y me puse a comprobar, línea por línea, lo que decía su agents.md. Tres cosas no cuadraban:
El fichero decía que el acceso a datos del blog vivía en y el de proyectos en . Abrí los dos. Su contenido completo era este:
Uno.
lib/supabase/blog.ts
lib/supabase/projects.ts
// Este archivo ya no se usa. Los server actions están en:
// - app/actions/blog.ts (para la página principal)
// - app/dashboard/blog/actions.ts (para el dashboard)
export {}
Un stub vacío. Y el comentario tampoco acertaba: app/actions/ no existe. El código real está solo en app/dashboard/blog/actions.ts, del que tiran tanto el dashboard como las páginas públicas. Es decir: la documentación apuntaba a un fichero muerto, y el fichero muerto apuntaba a una carpeta inexistente. Dos saltos hasta la nada.
Dos. La sección de dependencias listaba next: 16.0.3, react: 19.2.0, zod: 3.25.76. El package.json iba por 16.0.7, 19.2.1 y zod: 4.1.13. La diferencia de parche da igual. El salto de mayor de Zod no: la API de validación cambió entre la 3 y la 4, y un agente que se crea la 3 escribe esquemas con la sintaxis antigua.
Tres. Decía que las rutas se protegen con un middleware en lib/supabase/middleware.ts. Eso es medio verdad y la mitad falsa importa: Next.js 16 renombró el middleware.ts de la raíz a proxy.ts, y ese proxy.ts es el que declara el matcher. lib/supabase/middleware.ts solo exporta la función updateSession que el proxy llama. Un agente al que le digas "añade una ruta protegida" y se crea la documentación, se va a poner a buscar un middleware.ts que no está.
Por si acaso, en el mismo repaso me encontré otra: el README pide una variable NEXT_PUBLIC_SUPABASE_ANON_KEY, pero el código lee NEXT_PUBLIC_SUPABASE_PUBLISHABLE_OR_ANON_KEY. Ese error no lo cometió ninguna IA, lo cometí yo, y llevaba ahí meses.
Por qué esto es peor que no documentar
Cuando un agente no tiene contexto, lo busca. Hace grep, abre ficheros, sigue imports. Es más lento, pero lo que encuentra es verdad, porque el código es la única fuente que no puede estar desactualizada respecto a sí misma.
Cuando le das un fichero de contexto, se lo cree. Y hace bien en creérselo: se lo has puesto tú, en la raíz del repositorio, con aspecto de documento oficial. Una documentación obsoleta no se degrada con elegancia, convierte una búsqueda lenta y correcta en una respuesta rápida y equivocada.
Con una persona nueva en el equipo esto se corrige solo: abre el fichero, ve que no cuadra, pregunta. El agente no pregunta. Sigue adelante y te propone un cambio coherente con un proyecto que ya no existe.
Qué escribo ahora en esos ficheros
Después de este episodio he cambiado el criterio. La regla es: documentar solo lo que no se deduce del código, y que sea barato de verificar.
Fuera:
Listas de versiones de dependencias. El package.json ya está ahí y siempre tiene razón. Escribir las versiones a mano es fabricar una segunda fuente de verdad que empieza a divergir el día siguiente.
Árboles de directorios.ls es más rápido y no caduca.
Inventarios de componentes. "Los componentes base son Button, Card, Dialog, Input..." no aporta nada sobre lo que ya dice components/ui/.
Buenas prácticas genéricas. "No uses any", "valida los inputs". Eso no es contexto de este proyecto, es ruido.
Dentro:
Dónde vive el acceso a datos, si no está donde se esperaría. En este repo está en server actions del dashboard de las que tiran también las páginas públicas. Eso no lo adivina nadie, y es justo lo que hay que saber antes de tocar nada.
Los ficheros que son trampas. Los stubs vacíos, lo que parece que se usa y no se usa. Ahora lib/supabase/blog.ts está señalado explícitamente como "no añadir código aquí".
Las convenciones con consecuencia. Los formularios usan camelCase y la base de datos snake_case, y la traducción se hace a mano en los actions. Si no lo sabes, escribes publishedAt en un insert y te comes un error a las dos horas.
Las cosas que te van a morder. No metas código entre createServerClient y getClaims(). No caches el cliente de Supabase en una variable global. Cada mutación llama a revalidatePath() de las rutas pública y de dashboard.
Las discrepancias conocidas. El asunto del nombre de la variable de entorno está anotado, con la nota de que el README se equivoca y el código manda.
Qué otros documentos no son de fiar. Al final del nuevo fichero hay un apartado que dice literalmente que existe un agents.md con datos desactualizados y que se prefiera el nuevo. Es el aviso más útil de todo el documento.
Un detalle sobre precedencia
Si mantienes dos ficheros de contexto en el mismo repo —porque distintas herramientas leen distintos nombres— vas a tener el problema al cuadrado. Mi solución de momento es poco elegante pero funciona: el fichero nuevo dice explícitamente cuál es la fuente buena y por qué el otro no lo es. Prefiero un puntero incómodo a dos documentos igual de creíbles contradiciéndose.
La conclusión que me llevo
El fichero de contexto es código. Tiene el mismo problema que cualquier comentario: se escribe una vez, se lee muchas, y nadie lo actualiza cuando cambia lo que describe.
La diferencia es que un comentario obsoleto lo lee una persona, que arquea la ceja y mira el código. El fichero obsoleto lo lee un agente, que no arquea nada.
Así que ahora, cuando toco la arquitectura de un proyecto, el fichero de contexto entra en el mismo commit. Y cada cierto tiempo hago lo que hice aquel día: abrirlo y comprobar afirmación por afirmación. Se tarda menos de lo que parece, y siempre encuentro algo.