Volver al blog

Opinión técnica · El porqué, no el qué Junio de 2026

Comentarios de código en la era de los agentes de IA

El código ya se produce en masa y quien más tiempo pasa leyéndolo es un agente de IA. Construí una app entera con uno, en un lenguaje que no domino, y el resultado me obligó a repensar la vieja regla de «el código no se comenta».

Comentarios de código en la era de los agentes de IA

Estamos en un punto en el que el código se produce en masa y quien más lo lee es un agente de IA. Creo que eso obliga a repensar cosas que dábamos por cerradas, y una de ellas es el uso de los comentarios.

Durante años escribí los mínimos comentarios posibles: mantenerlos era caro y acababan mintiendo. Pero la historia ha cambiado: el código lo genera un agente, el lector principal es otro agente, y hay hooks capaces de verificar que un comentario sigue siendo cierto. Escribir un comentario cuesta tokens, sí. ¿Y cuánto cuesta inferir esa misma información cada vez que no está?

Construí una app entera con un agente, en un lenguaje que no domino, y el resultado me obligó a tomarme la pregunta en serio.

La pieza que hace esto sostenible es el arnés del diagrama: hooks de pre-commit y pre-push que lanzan agentes de revisión adversarial sobre cada diff, a la caza de derivas entre el código y sus comentarios (y, como veremos, el resto del conocimiento del proyecto). No es un proceso determinista, pero converge: cada pasada adversarial sincroniza un poco más código y comentarios, y cuantas más revisiones independientes se cruzan, más fácil es distinguir una deriva real de una alucinación.

An agent writing, a harness checking Flow with a feedback loop. An AI agent generates code and why-comments as a single artifact. A harness with pre-commit and pre-push hooks runs an adversarial review over the diff, asking whether each comment is still true after the change. A dashed feedback arrow returns from the harness to the agent. Bottom strip: not deterministic, but each adversarial pass converges code and comments and filters out hallucinated drift. An agent writing, a harness checking comments stop being a leap of faith when something verifies them on every commit AI agent writes the code and the why-comments together also their main reader code + comments one artifact, one diff: they change together git commit · git push harness pre-commit hook pre-push hook adversarial review of the diff: "is each comment still true?" inferential feedback → fix the comment or the code not deterministic — each adversarial pass converges code & comments and filters out hallucinated drift
Un agente escribe código y comentarios como un solo artefacto; un arnés con hooks de pre-commit y pre-push lanza revisiones adversariales sobre el diff. No es determinista, pero cada pasada hace converger código y comentarios y filtra las derivas alucinadas.

El contexto

#s1

No todos los proyectos son iguales: presupuesto, tiempo y riesgo cambian el enfoque. La reflexión vale para cualquier tamaño de producto, pero nace de un contexto muy concreto: la primera celda del diagrama.

El proyecto

  • App iOS: SwiftUI, SwiftData, CloudKit.
  • Desarrollada en solitario con Claude Code.
  • Vengo de TypeScript: no domino Swift ni la plataforma.
  • Sin tablero ni docs de producto: solo el código, el agente y yo.

El resultado

  • Velocidad de desarrollo altísima.
  • Comprendí el proyecto a fondo sin dominar el lenguaje.
  • Muchos más comentarios de los que mi tradición de equipo toleraría.
  • Nunca había escrito tantos porqués: ¿es lo óptimo o estoy normalizando un exceso?
Not every project plays by the same rules Five cells from left to right by team size: one dev, around five, around fifteen, around one hundred, around two hundred people. Each cell lists how the approach shifts with budget, time and risk. The first cell, one dev with a small scope moving fast at low risk, is highlighted in green with a badge reading this post starts here; the reflection applies to every cell. The rightmost cells note that documentation starts living outside the repository. Bottom strip: each size needs its own approach; the bigger the team, the easier docs and code drift apart. Not every project plays by the same rules budget · time · risk · people — the approach shifts with all of them 1 dev experiments small scope move fast · test fast low risk repo = whole truth this post starts here ~5 a small product shared conventions reviews appear repo + a few docs ~15 several squads process shows up ADRs · boards docs start to drift ~100 dedicated product team compliance · audits higher stakes docs live outside the repo ~200 many teams strict constraints slow by design truth is scattered each size needs its own approach — the bigger the team, the easier docs and code drift apart
Cada tamaño de proyecto pide un enfoque distinto según presupuesto, tiempo y riesgo; este post nace en la primera celda (una persona, scope pequeño, riesgo reducido), pero la reflexión aplica a todas.

La regla de siempre: el porqué, no el qué

#s2

Esto no lo he inventado yo: está documentado desde hace décadas, y Robert C. Martin lo recoge en Clean Code (el buen comentario es el que explica la intención). La versión corta del consenso:

  • ¿Comentarios, sí o no? Sí, los justos: cada uno es texto que hay que mantener y que puede mentir.
  • ¿El qué? Nunca. El lector, humano o agente, infiere el qué del código más rápido y con más fiabilidad que de cualquier explicación escrita. Con IA, además, es coste puro en tokens.
  • ¿El porqué? Siempre que el código no pueda mostrarlo: el bug del framework que se esquiva, el contrato interno, el comportamiento que reportó un usuario. Aquí la información no está en el código a ningún precio.
  • ¿Dónde se complica? Cuando el porqué suena a requisito: no sabemos cuándo va en documentación separada y cuándo pegado al código. Ese es el verdadero debate, y lo dejo para más abajo.
El mismo stop, ahora con el porqué debajo: «zona de alta siniestralidad». La señal ya no narra lo evidente: te dice por qué está ahí.
El mismo stop, ahora con el porqué debajo: «zona de alta siniestralidad». La señal ya no narra lo evidente: te dice por qué está ahí.
Two kinds of comments Side-by-side comparison. Left card, WHAT comments: they narrate what the next line does; the agent infers the what from code faster and more reliably than from written text, and text can lie; verdict in red: pure cost, do not write them. Right card, WHY comments: they document a constraint the code cannot show, like a platform bug being dodged, a cache invalidation contract, or behavior observed by a real user; verdict in green: that information is not in the code at any price, write them. Bottom strip: the classic rule was never wrong, it was incomplete — it only ever targeted the WHAT. Two kinds of comments the whole debate hinges on this distinction WHAT comments narrate what the next line does · the agent infers the what from code · faster and more reliable than text · and text can lie pure cost — don't write them WHY comments a constraint the code cannot show · the platform bug being dodged · the cache invalidation contract · behavior observed by a real user not in the code at any price — write them the classic rule was never wrong — it was incomplete it only ever targeted the WHAT
Los comentarios de qué son coste puro; los de porqué guardan información que no está en el código a ningún precio.

Dos comentarios de mi código real

#s3

Los dos bloques que siguen los escribió el agente en mi código real. La pregunta para cada uno es la misma: ¿qué información contienen que el código no contiene?

El primero gestiona la identidad del usuario al importar una planilla. El código muestra un estado y un @AppStorage; no muestra por qué la identidad no siempre se resuelve sola, por qué existe un selector «¿Quién eres tú?» ni por qué el nombre se ancla a la grafía de la planilla recibida. Y deja una pista clave: ADR-0005 §4, el comentario local apuntando a la decisión transversal. El segundo es críptico a propósito: el comentario documenta el contrato, por qué la identidad del modelo no basta y hay que comparar por valor.

Ejemplo 1 · la identidad del usuario
/// Planilla recibida cuyo receptor no se
/// resolvió solo: presenta el selector
/// "¿Quién eres tú?" (ADR-0005 §4).
@State private var identityPick: IncomingPlanilla?

/// Nombre del usuario (su fila isSelf).
/// Auto-resuelve la identidad al importar
/// un fichero y se ancla a la grafía de
/// la planilla recibida.
@AppStorage("import.userName") private var userName: String = ""
Sin los comentarios, el selector parece eliminable: la valla de Chesterton en estado puro. Y la referencia al ADR convierte el comentario en un enlace a la decisión completa, sin duplicarla.
Ejemplo 2 · el contrato de la caché
/// Firma de contenido de los turnos
/// personalizados. `onChange` la observa
/// para refrescar la caché cuando cambia
/// cualquier campo, no solo el alta o
/// baja: editar nombre, color o
/// clasificación no cambia la identidad
/// del modelo: se compara por valor.
private var customShiftSignature: [String] {
    customShiftModels.map {
        "\($0.code)\u{1}\($0.label)\u{1}\($0.colorHex)\u{1}\($0.isWork)"
    }
}
Exactamente el tipo de código frágil que, sin contrato escrito, se rompe en silencio cuando alguien añade un campo al modelo.

Una auditoría como banco de pruebas

#s4

Hace poco lancé sobre el proyecto una auditoría completa de seguridad: varios agentes en paralelo, movidos por Fable, el último modelo de frontera de Claude Code, leyendo todo el código fuente. El objetivo era buscar leaks de seguridad, no evaluar cómo de bien estaba autodocumentado el proyecto. Y aun así, la auditoría no solo agradeció que el código estuviera comentado: me lo demostró. Repetí exactamente el mismo análisis tras eliminar todos los comentarios, y la comparativa nos proporcionó algo más de advertencia y perspectiva sobre estos dos caminos:

  • Con comentarios: la auditoría citó la densidad de porqués como lo que le evitó re-derivar cada decisión. Fue más barata, más rápida y con un veredicto más fiable.
  • Sin comentarios: sobre el mismo código sin un solo comentario, gastó muchos más tokens, tardó más y alucinó más. Cada sesión vuelve a pagar la re-derivación y cada edición equivocada añade un pico.
Two paths, two effort curves Two small line charts side by side, same scale. Left, without why-comments: cumulative effort climbs steeply because every session re-derives the same knowledge, and wrong edits add spikes marked with crosses. Right, with why-comments: a small initial cost to write them, then an almost flat line of roughly 100 to 200 tokens per read; a dashed reference shows how high the other path ends. Bottom strip: the gap grows with every session — a why-comment is paid once and read forever. Two paths, two effort curves cumulative effort, session after session, over the same file WITHOUT why-comments ✗ = wrong edit → regression every session re-derives sessions WITH why-comments where the other path ends written once · ~100–200 tokens per read sessions cumulative effort the gap grows with every session — a why-comment is paid once and read forever
Dos gráficas con la misma escala: sin comentarios de porqué, el esfuerzo acumulado crece con cada sesión y suma picos por ediciones equivocadas; con ellos, se paga una vez al escribir y un peaje pequeño por lectura.

Más allá de los números, esto es lo que dejó la auditoría:

  • El veredicto me sorprendió: sin los comentarios, los hacks documentados de SwiftUI habrían parecido ruido a limpiar, y la auditoría me habría empujado a introducir regresiones.
  • Razonamiento persistido entre sesiones: el agente escribió cada comentario con el contexto completo (el bug reproducido, la alternativa descartada); cada sesión futura, de la IA o de un humano, lo hereda por el precio de leerlo.
  • ¿Y el exceso de contexto? Degrada cuando es irrelevante o contradictorio; el comentario co-localizado es lo contrario: solo entra en contexto cuando se toca ese fichero.
  • El riesgo real es la obsolescencia: un comentario incorrecto es un bug y se revisa como un test roto. En un flujo agéntico, un hook de pre-commit puede vigilar esa deriva (da para un post aparte); y cuando la caducidad no nace de un diff sino de fuera del repo: #s5.
The asymmetric economics of a why-comment Two columns compared. Left, cost when the comment is present: a small yellow box of roughly 100 to 200 tokens, paid each time the file enters context — small and predictable. Right, cost when the comment is absent: a tall red stack with re-derivation (compile, run, reproduce the bug, git archaeology — expensive) and a wrong edit that ships a regression — very expensive. A bottom strip notes that why-comments are persisted reasoning: every future session inherits the work for the price of reading it. The asymmetric economics of a why-comment what you pay with it vs. what you pay without it cost when PRESENT ~100–200 tokens paid each time the file enters context small · predictable · bounded cost when ABSENT re-derivation compile · run · reproduce the bug git archaeology expensive wrong edit → regression "this looks removable" · removed very expensive unbounded · paid when you least expect it why-comments are persisted reasoning across sessions every future session inherits the work for the price of reading it
La economía es asimétrica: el comentario se paga en tokens en cada lectura; su ausencia, en re-derivación cara o en ediciones equivocadas carísimas.
// la lección

Una revisión es tan buena como el contexto que puede leer: sin el porqué escrito, un hack que esquiva un bug parece ruido que limpiar.

Cuando el comentario caduca por motivos externos

#s5

El ejemplo del bug del framework deja una pregunta en el aire: ¿cómo detectas que la siguiente versión de la librería arregla el bug? Vuelve al hack de SwiftUI de la auditoría: hoy el workaround esquiva un bug real, y su comentario es lo que protege el código de una limpieza bienintencionada. El día que Apple arregle ese bug en un SDK nuevo, sin avisar, la situación se invierte: el workaround pasa a ser complejidad muerta y el comentario, cierto hasta ayer, empieza a mentir. El arnés vigila la deriva entre código y comentario, pero esta deriva no nace en el repo: llega de fuera.

El primer instinto es alimentar el hook con los changelogs de las dependencias. No se sostiene:

  • No hay fuente estructurada: puede ser un CHANGELOG.md, GitHub Releases, un post en un blog o nada en absoluto.
  • Son incompletos por naturaleza: fixes agrupados en «misc» o arreglados de rebote sin documentar.
  • El match comentario↔changelog es semántico, no léxico, y su coste es asimétrico: el falso positivo retira un workaround vigente y te regala una regresión; el falso negativo deja complejidad muerta para siempre.
  • En mi caso real ni siquiera hay changelog: para los bugs de SwiftUI, las release notes de Apple son incompletas y Feedback Assistant es privado.
  • La salida no es un parser de changelogs mejor: es convertir el comentario en algo que una máquina pueda verificar. El porqué se sigue escribiendo igual; lo que se estandariza son las anclas que lo acompañan. Tres, de más fuerte a más débil:
AnclaCómo avisaSu límite
Test canarioUn test que reproduce el bug y fija el comportamiento roto: XCTExpectFailure pasa mientras el bug se reproduce y falla el día que el framework lo arregla (equivalentes: withKnownIssue, test.failing, xfail). Señal computacional pura, y el único sensor que también detecta que un SDK nuevo rompe distinto.No todo bug es testeable: visuales, timing, device-only.
Issue públicoEl estado del issue es estructurado y consultable por API (gh api … --jq .state): un issue cerrado es el propio proyecto afirmando el fix, con su PR y su milestone. Si no existe y la librería es open source, reportarlo es el mejor ratio coste/beneficio de la escalera.Necesita tracker público; el de Apple es privado.
Cota de versióndep=swiftui sdk<=15.4: el bump del lockfile o del SDK marca el comentario para reevaluación, no para retirada. Es el patrón de expiring-todo-comments (ESLint) y todo_or_die (Ruby).La más débil: dice «mira esto», no «está arreglado».
La escalera de anclas, de más fuerte a más débil: cuanto más arriba, más se parece la señal a un test en rojo y menos a una opinión.

Las tres no compiten: se apilan, cada una donde compensa su coste:

  • La cota de versión, en todos los workarounds sin excepción: cuesta cero y un linter puede exigirla: un WORKAROUND sin dep= y versión no pasa.
  • El issue, en todos los que tengan tracker, creándolo cuando aún no existe.
  • El canario, solo donde duele: workarounds cuya regresión sale cara (pérdida de datos) o cuyo bug es silencioso.
  • Las tres juntas en un workaround crítico no son redundancia: son defensa en profundidad.
  • ¿Y el changelog? Queda como último recurso: un agente con el changelog y el diff entre versiones como contexto, cuyo output es siempre una propuesta con evidencia citada, nunca una retirada automática. La confirmación final es conductual: canario en verde o verificación manual.
// el detector

El bump de una dependencia lista los tags afectados y cada uno se resuelve con el detector más fuerte disponible: canario, issue, reevaluación; solo el caso residual quema un agente leyendo changelogs. El formato exacto del comentario estandarizado queda fijado en las reglas (#s10); el pipeline completo ya es harness engineering y da para su propio post.

¿Dónde debe vivir cada conocimiento?

#s6

Lo que nace observando comentarios en el código acaba evolucionando en una pregunta más amplia: dónde almacenamos la información del proyecto. Es el quid de la cuestión, y una objeción razonable que cabe plantearse: eso que escribes en comentarios son requisitos, funcionales o no funcionales, y deberían vivir en un documento aparte. Es verdad que es fácil mezclar comentarios con documentación. Pero muchos de estos porqués no son requisitos de producto a ningún nivel: son bugs del framework, contratos internos, alternativas descartadas. El test que propongo no es ¿es funcional o no funcional?, sino: ¿con qué cambia este conocimiento?

Knowledge lives where it changes A decision tree with a step zero. Step zero: let the code say it — clear names, types, invariants; with DDD, the domain model speaks the business language. Only what the code cannot show needs a home, and the root question is: what does this knowledge change with? Four branches. If it changes with the code, it belongs in an inline why-comment next to the code (cache contract, framework workaround). If it changes with the product, it belongs in a living spec such as openspec/specs, and the code points to it. If it changes with the architecture, it belongs in an ADR (encryption strategy), which survives code rewrites. If it is observable behavior, it belongs in a test, which is executable and cannot lie. Bottom caption: one screen means comment, crosses modules means ADR, product language means spec, observable behavior means test. Knowledge lives where it changes not functional vs. non-functional — ask: what does it change with? step 0 — let the code say it clear names · types · invariants — with DDD, the domain model speaks the business language only what the code cannot show needs a home below what does this knowledge change with? the code the product the architecture the behavior why-comment inline, next to the code cache contract framework workaround a separate doc guarantees it's missed spec · OpenSpec living, in the repo "free plan: daily limit" openspec/specs/ product language — and the code points to it ADR context · forces · options encryption strategy docs/adr/ADR-0005 crosses modules, survives code rewrites test executable — it can't lie "picker shows up when identity unresolved" freezes the observable what; an outdated test is red one screen → comment · crosses modules → ADR product language → spec · observable behavior → test
El conocimiento vive donde cambia: comentario si cambia con el código, spec (OpenSpec) si cambia con el producto, ADR si cambia con la arquitectura, test si es comportamiento observable.
Dónde viveCuándo usarloEjemplo
Spec (OpenSpec)Comportamiento de producto: cambia cuando cambia el producto.«El plan gratuito tiene un límite diario de planillas.»
ADRDecisión transversal: cruza módulos y sobrevive a reescrituras del código.ADR-0005: cómo se resuelve la identidad del usuario en toda la app.
Comentario de porquéRestricción local: cambia con el código y se ve entera desde una función. Si depende de una decisión transversal, enlaza el ADR.El contrato de la caché; el hack que esquiva un bug de SwiftUI (ADR-0005 §4).
TestComportamiento observable: debe seguir siendo cierto aunque el código se reescriba.«Importar dos veces la misma planilla no duplica turnos.»
Cuatro destinos para el conocimiento, cada uno con su ritmo de cambio. En caso de duda, la pregunta es siempre la misma: ¿con qué cambia este conocimiento?

La tabla clasifica; lo que no dice es por dónde empezar ni cómo se relacionan sus filas entre sí. En corto:

  • La primera opción es siempre el propio código: buenos nombres, tipos, invariantes y, con DDD, el propio modelo de dominio. Solo hay que buscar fila a lo que el código no puede mostrar.
  • Las filas se enlazan entre sí: un porqué que cambia con el código puede vivir en un documento aparte si la función deja un comentario apuntando a él. Funciona, pero son dos piezas que mantener sincronizadas y hace falta un arnés que vigile la deriva; ese trabajo desaparece cuando el porqué va pegado al código.
  • Y en sentido inverso: el comportamiento de producto vive en su spec, pero el código debería apuntar a ella.
  • Con ese criterio, la objeción se desmonta sola: «si la identidad no se resuelve sola, pregunta al usuario» sí es un micro-requisito; el anclaje a la grafía es implementación pura; y la parte transversal ni se copia ni se pierde: se enlaza (ADR-0005).
  • ¿Y no serán ADRs encubiertos? El comentario de porqué es, en el fondo, un micro-ADR inline: demasiado pequeño para merecer fichero propio, demasiado no obvio para omitirlo. En mi proyecto conviven sin fricción: ADRs para decisiones transversales, comentarios para restricciones locales.
  • El test merece mención propia: es la única documentación que no puede mentir mucho tiempo (un test desactualizado es un test rojo) y cumple cuatro propósitos según el momento: especificar antes de escribir el código, verificar al escribirlo, proteger como red de regresión y documentar con ejemplos ejecutables.
  • Test y comentario son complementos, no sustitutos: el test congela el qué observable; el comentario guarda el porqué que el test no puede afirmar. Sin el test, un agente puede romper el comportamiento sin enterarse; sin el comentario, puede «arreglar» código correcto.
// la heurística

Si la decisión cruza módulos o sobrevive a reescrituras del código, pide un ADR. Si es una restricción local que vive y muere con el código que la rodea, normalmente basta un comentario.

Specs y ADRs en el repositorio

#s7

Para gestionar las especificaciones, ahora mismo me parece muy relevante OpenSpec: specs vivas de lo ya construido y propuestas de cambio que, al archivarse, actualizan las specs. Y el directorio de ADRs conviene tenerlo también repositado.

Que todo viva en el repositorio hace trivial la integración con los agentes: pueden leer las specs y los ADRs, citarlos (como el ADR-0005 del ejemplo 1) y vigilar su sincronía con el código. Abajo, el ciclo de OpenSpec y el árbol mínimo que esperaría encontrar.

Specs that live in the repo The OpenSpec cycle as a flow. A proposal in openspec/changes describes what should change. It gets built in the code: source, tests and why-comments. Archiving the completed change updates the living specs in openspec/specs, which describe what is true now. A dashed return arrow notes that the next change starts from updated truth. Bottom strip: an agent reads it, cites it, like ADR-0005 in example 1, and polices its sync with the code. Specs that live in the repo the OpenSpec cycle: propose → build → archive — the spec catches up with reality proposal openspec/changes/ what SHOULD change build src/ · tests/ code + whys + tests archive change done syncs the specs living spec openspec/specs/ what IS true now the next change starts from updated truth repo-resident → an agent reads it, cites it (ADR-0005 §4) and polices its sync with the code
Ciclo de OpenSpec: una propuesta en changes/ se construye en el código y, al archivarse, actualiza las specs vivas; el siguiente cambio parte de una verdad actualizada, y un agente puede leerla, citarla y vigilar su sincronía.
repo/
├── openspec/
│   ├── project.md      # convenciones y contexto
│   ├── specs/          # lo que YA está construido
│   └── changes/        # propuestas en vuelo
│       └── archive/    # al completarse, actualizan specs/
├── docs/
│   └── adr/            # decisiones transversales
│       └── ADR-0005-identidad-usuario.md
├── src/                # código + porqués co-localizados
└── tests/              # el qué observable, congelado
El árbol mínimo: especificaciones (OpenSpec), decisiones (ADRs), código y tests en el mismo repositorio, legibles por humanos y por agentes.

La estructura crece con el proyecto

#s8

Ese árbol es un punto de partida, no una talla única. Y la documentación no sube por niveles: se compone por grupos de piezas. Está el código con sus porqués y los tests de lo crítico (el suelo común de cualquier repo); las decisiones (ADRs, convenciones); el comportamiento de producto (specs vivas, contratos); y el modelo compartido: DDD, donde el código comparte el modelo de dominio con producto (el lenguaje ubicuo), se vuelve en gran parte autodocumentado y sus unit tests de dominio son lo más parecido que existe a una spec ejecutable; los comentarios que quedan ahí son los técnicos: frameworks, contratos, rendimiento.

Cada proyecto combina los grupos que su contexto pide, y la mezcla no es determinista: cuanta más gente y más riesgo, más rica tiende a ser la combinación, pero es una tendencia, no una regla. Este proyecto es unipersonal y tiene ADRs; tengo otro, también unipersonal, con OpenSpec; hay proyectos sin DDD y proyectos que no testean igual. Y algo ha cambiado con los agentes: en un proyecto greenfield, dejar la documentación montada desde el día uno es más barato que nunca, porque el agente la genera y la mantiene contigo.

Documentation: combinations, not levels Four groups of documentation pieces shown side by side, with no hierarchy between them. In the code: why-comments and critical-path tests, the common floor of any repo. Decisions: ADRs and shared conventions. Product behavior: living specs such as OpenSpec and boundary contracts. Shared model: DDD with ubiquitous language and domain unit tests acting as executable specs. Below, three real projects mix the groups differently: this iOS app, solo, combines the code floor with ADRs; another solo project combines the floor with living specs; a multi-team product combines the floor with ADRs, specs, DDD and derived product docs. Bottom strip: every project composes its own mix — size and risk push toward richer combinations, as a tendency, not a rule. Documentation: combinations, not levels independent groups of pieces every project mixes differently — an example map, not a taxonomy in the code why-comments critical-path tests the common floor of any repo decisions ADRs shared conventions cross-cutting · durable product behavior living specs (OpenSpec) boundary contracts changes with the product shared model DDD · ubiquitous language domain unit tests ≈ executable specs same pieces, different combinations — three real examples this iOS app · solo code · whys · tests ADRs another solo project code · whys · tests living specs (OpenSpec) a multi-team product code · whys · tests ADRs specs DDD · domain tests derived product docs the floor repeats everywhere — everything else is a choice per project, not a stage to unlock every project composes its own mix — size and risk push toward richer combinations, as a tendency with agents, wiring documentation from day one is cheaper than ever — and the mix can grow with the project
Cuatro grupos de piezas de documentación (código, decisiones, comportamiento de producto, modelo compartido) y tres proyectos reales que los combinan de forma distinta; el tamaño y el riesgo empujan hacia combinaciones más ricas como tendencia, no como regla.

¿Se puede meter toda la documentación que exige un proyecto de cien personas en uno de una? Poderse, se puede. La pregunta es qué obtienes a cambio: pierdes velocidad hoy y el beneficio no crece al mismo ritmo; son rendimientos decrecientes. En el lado contrario de la balanza, diseñar la estructura desde el principio abarata adoptarla después. ¿Dónde parar, entonces? No hay una respuesta universal: depende de la naturaleza del proyecto, y cada escenario salda la balanza de forma distinta:

  • Un MVP o un experimento: estructura mínima. Muchos proyectos se quedan por el camino; si este muere pronto, no habrás enterrado tiempo en documentación que nadie leerá: lo habrás usado de forma más eficiente.
  • Un producto pequeño con intención de crecer: los proyectos tienden a crecer, y lo difícil es gestionar ese crecimiento. La dedicación que inviertes al inicio es la que se cobra al final: cada porqué y cada decisión escritos hoy son contexto que nadie tendrá que reconstruir mañana.
  • Algo pensado para escalar desde el día uno: montar la documentación exhaustiva de entrada no es incorrecto, es una inversión; con agentes, además, cuesta menos que nunca.
  • Mi caso: en el proyecto de este post me ha funcionado empezar pequeño e ir incrementando la documentación junto al proyecto. Es la respuesta para esta naturaleza de proyecto, no una verdad universal.
The structure sweet spot A line chart. The horizontal axis is the weight of documentation structure: specs, ADRs, DDD, process. The vertical axis is value to the project. A solid line, benefit, rises fast and then flattens: diminishing returns. A dashed red line, cost in speed, starts low and compounds upward. A shaded green band marks the sweet spot where benefit most exceeds cost, with a dashed arrow noting that the sweet spot moves right as the project grows. A note adds: designing for growth early is cheaper than retrofitting, but don't buy the whole suit on day one. The structure sweet spot a 1-person project can carry 100-person structure — the question is what you get back sweet spot for this size, today value to the project structure: specs · ADRs · DDD · process benefit — flattens out cost in speed — compounds the sweet spot moves right as the project grows designing for growth early is cheaper than retrofitting — just don't buy the whole suit on day one
Beneficio frente a coste en velocidad según el peso de la estructura: el beneficio se aplana (rendimientos decrecientes), el coste se dispara, y el punto óptimo se desplaza a la derecha a medida que el proyecto crece.
// el criterio

No hay un óptimo universal de documentación: lo marca la naturaleza del proyecto, y se mueve con ella.

Cuando la localidad no basta: la vista agregada

#s9

Todo lo anterior optimiza una sola cosa: la localidad. Cada porqué vive pegado a su uso, perfecto para editar y para un agente que toca un único fichero. Pero en el extremo alto de la escala de la sección anterior sobrevive una objeción legítima, la que plantea un responsable de QA, un auditor o alguien de compliance: con el conocimiento distribuido, ¿cómo respondo a ¿cuáles son todos los requisitos funcionales?, ¿qué test cubre cada uno?, ¿qué NFR justifica este ADR?, ¿qué cambió con esta feature? La localidad dispersa justo la vista global que necesita la auditoría. Es la tensión clásica, localidad de referencia frente a trazabilidad, y solo aparece en el extremo regulado y multiequipo de la escalera: un MVP en solitario nunca hace estas preguntas; un producto bajo auditoría las hace a todas horas.

The aggregate view is generated, not authored Locality of reference versus traceability, resolved by a projection. On the left, four distributed sources of truth, each living where it changes: a spec in openspec/specs contributes the functional requirement, an ADR in docs/adr contributes the non-functional justification, an inline why-comment in src contributes the local reasoning, and a test contributes coverage. A thick solid arrow labelled generated in CI flows left to right into the aggregate view on the right: a generated FR/NFR inventory and traceability matrix with columns ID, Type, Tests and Status. Two rows are covered (FR-014 functional and NFR-003 privacy, both with tests, green), and one row is a gap (NFR-004 performance, no test, amber): an empty Tests cell is a red flag, like an uncovered line. A dashed red arrow pointing back from the matrix to the sources is crossed out: maintaining the matrix by hand recreates a second source of truth that drifts. The matrix is read, never authored. The aggregate view is generated, not authored locality of reference vs. traceability — a projection resolves the tension sources — distributed, each where it changes spec → FR openspec/specs/ ADR → NFR why docs/adr/ comment → local why inline, in src/ test → coverage tests/ generated · CI read-only projection aggregate view — generated index FR/NFR inventory + traceability matrix ID Type Tests Status FR-014 Func covered NFR-003 Privacy covered NFR-004 Perf no test an empty Tests cell is a red flag, like an uncovered line — the view surfaces gaps full row: ID · Type · Source · ADR · Tests · Status hand-maintained authoring the matrix by hand = a second source that drifts the matrix is a projection of the repo — read it, never write it by hand
Cuatro fuentes distribuidas (spec, ADR, comentario, test) a la izquierda; un inventario FR/NFR y una matriz de trazabilidad generados a la derecha. La matriz se genera en CI como proyección de solo lectura; mantenerla a mano aparece tachado, porque recrea una segunda fuente de verdad que diverge.
IDTypeSourceADRTestsStatus
FR-014Functionalimport-planilla.mdImportPlanillaTestscovered
NFR-003Privacyprivacy.mdADR-0007PrivacyTestscovered
NFR-004Performanceperf.mdADR-0008gap: no test
Ilustrativa: esta app en solitario no la necesita. Lo importante es la forma: cada fila se genera, y la celda Tests vacía de NFR-004 es una alerta que la propia vista destapa, igual que la cobertura destapa una línea sin test.

El arreglo ingenuo es un requirements-index.md y una traceability-matrix.md mantenidos a mano. No se sostiene: una matriz a mano es una segunda fuente de verdad que diverge de specs, tests y ADRs, y necesitaría su propio harness vigilando la deriva; estarías resolviendo el problema recreando justo lo que este post combate. El arreglo es de una palabra: la vista agregada es derivada, no escrita. Un agente o un script recorre lo que ya existe y computa el inventario: los requisitos funcionales desde las specs, la justificación de los NFR desde los ADRs, la cobertura desde los nombres de los tests, el estado desde el delta spec↔código que el post ya llama computable. Es una salida del harness, como un informe de cobertura: se regenera en CI, nunca se edita.

  • Dos ejes ortogonales, no uno que sustituye al otro: ¿con qué cambia? decide dónde vive la fuente (la heurística anterior); ¿funcional o no funcional? es la lente con la que ordena la vista agregada. Un requisito se escribe una vez en su spec y aparece como fila; no se guarda dos veces.
  • Los NFR son donde esto rinde: «borra el PDF tras procesarlo» se reparte entre una spec (la regla), un ADR (el cómo) y un test (la prueba). Un FR suele vivir en una sola spec; un NFR casi nunca. La matriz es lo que recompone el NFR disperso en una única fila auditable.
  • La vista informa y audita a la vez: un FR sin test, o un NFR sin ADR que lo justifique, sale como hueco, la misma alerta roja que una línea sin cubrir. La matriz no solo responde preguntas, destapa lo que falta.
  • «¿Qué cambió con esta feature?» se vuelve un diff: regeneras la vista en dos commits y los comparas, en vez de una sesión de arqueología por specs, ADRs y tests.
// la proyección

La vista agregada es una proyección del repo, no una copia paralela: se escribe una vez en specs, ADRs y tests; se lee de muchas formas. El día que se mantiene a mano, ya ha empezado a mentir.

Las reglas, en corto

#s10

Para mi contexto actual, desarrollo en solitario con IA, el enfoque ha funcionado muy bien, y la idea de fondo se exporta: la documentación se puede repositar. Cuando specs, ADRs y porqués comparten repositorio con el código, el clásico «los docs van por un lado y el código por otro» desaparece, y el crecimiento del sistema sale beneficiado: quien desarrolla, humano o agente, usa la documentación sin salir del repo en vez de ir a buscarla fuera. Y cuando producto y desarrollo no son el mismo equipo, el riesgo es el comportamiento de producto atrapado en el código, donde un tester o un PM nunca lo encontrará; ahí se pueden plantear herramientas que sincronicen la documentación y la proyecten hacia fuera, frontends que generen la vista de producto desde el repositorio, en lugar de mantener dos fuentes que divergen. En equipo, eso sí, negociaría la densidad: ocho líneas de doc-comment tienen un coste de lectura humana que el agente no paga.

  • Nunca el qué, siempre el porqué que el código no puede mostrar: la regla clásica de «no comentar» no estaba mal, estaba incompleta. Siempre fue una regla contra los comentarios de qué; los dominios con mucha restricción no obvia por línea necesitan más comentarios de porqué.
  • Un comentario incorrecto es un bug: se revisa y se mantiene como el código. El qué observable se documenta en un test, la única documentación que se verifica sola.
  • El conocimiento vive donde cambia, y todo repositado: porqués con el código, decisiones en ADRs, comportamiento de producto en specs, el qué observable en tests. Un solo repositorio, legible por humanos y por agentes.
  • Un arnés vigila la sincronía (harness engineering): hooks de pre-commit y pre-push que lanzan agentes adversariales sobre cada diff, a la caza de derivas entre el código fuente del repo y la documentación del repo.
  • El comentario también caduca por motivos externos (#s5): el framework arregla el bug y el workaround se convierte en complejidad muerta. Todo workaround lleva al menos un ancla verificable por máquina: test canario > issue público > cota de versión; un porqué sin ancla no se admite.
  • Los deltas se identifican, no se intuyen: con specs vivas (OpenSpec es un gran apoyo aquí), la distancia entre lo que la spec promete y lo que el código hace se vuelve computable, y un agente puede leerla, citarla y reconciliarla.
  • En el peldaño de auditoría, la vista agregada se genera, no se escribe (#s9): el inventario FR/NFR y la matriz de trazabilidad son una proyección computada desde specs, ADRs y tests, nunca una segunda fuente mantenida a mano.
  • La sincronía no es determinista, pero converge: cada pasada adversarial acerca código y documentación, y cuantas más revisiones independientes se cruzan, más fácil es distinguir una deriva real de una alucinación.
Tres carreteras, la misma norma: circular a 30. La primera añade una señal redundante que narra lo evidente: ruido que cuesta atención y tokens sin aportar información. Las otras dos añaden contexto, el radar que verifica el límite y los niños que te dicen por qué existe, y conduces más atento.
Tres carreteras, la misma norma: circular a 30. La primera añade una señal redundante que narra lo evidente: ruido que cuesta atención y tokens sin aportar información. Las otras dos añaden contexto, el radar que verifica el límite y los niños que te dicen por qué existe, y conduces más atento.
/// WORKAROUND(FrameworkBug): dep=swiftui sdk<=15.4
/// issue=FB13241001
/// repro=Tests/Canaries/FB13241001_Test.swift
/// <el porqué, redactado como siempre>
El estándar para comentarios de workaround, salido de #s5: todo WORKAROUND lleva al menos un ancla verificable por máquina, en orden de preferencia repro > issue > cota de versión. Un porqué sin ancla no pasa el lint.
// lo que ha cambiado

La regla es la de siempre; lo nuevo es que ahora se puede medir: el coste de un comentario se cuenta en tokens y su beneficio se nota en la calidad de las ediciones. Con esa medida, el comentario de porqué es de las inversiones más rentables del repositorio.

Temas de esta historia