API abrevia Application Programming Interface. El término es anterior a la Web y su idea central es aún más antigua: permitir que un programa use una capacidad sin depender de cómo está construida.
El diseño de la interfaz repercute en el diseño del sistema completo
La API, por tanto, no nació como sinónimo de endpoint HTTP ni de servicio público. Esos son mecanismos posteriores para expresar una interfaz. También son APIs las funciones de una biblioteca, las llamadas de un sistema operativo o cualquier protocolo que permita colaborar con una capacidad encapsulada.
API-DD conserva esa función histórica de la interfaz: separar al consumidor del mecanismo. La amplía con una idea de Alan Kay: en un sistema capaz de crecer importa más diseñar cómo se comunican sus módulos que fijar sus propiedades internas. Kay situó el intercambio de mensajes en el núcleo de Smalltalk, pero su observación sobre módulos y comunicación puede aplicarse fuera de la orientación a objetos (mensaje original de 1998).
API-DD lleva la API desde el límite exterior de una aplicación hasta cada relación modular que merezca un contrato. La conversación puede ser local o remota y expresarse con funciones, métodos, eventos o HTTP. El mecanismo cambia; la separación entre consumidor e implementación permanece.
Esta es una reinterpretación deliberada, no la afirmación de que API haya significado siempre exactamente lo mismo. De su historia tomamos la separación entre uso e implementación; de Kay, el foco en los mensajes entre módulos. De ahí surge API-Driven Development.
El software cambia. Sus contratos permiten que cambie sin obligar a cada consumidor a conocer de nuevo su interior.
Las interacciones importan más que la forma interna. Un módulo se entiende por las conversaciones que ofrece y consume.
El vocabulario forma parte del diseño. Los nombres expresan intenciones, resultados y hechos que otros módulos pueden comprender.
Lo visible crea acoplamiento. La API muestra lo necesario y mantiene reemplazables algoritmos, coordinación y representación.
La autonomía llega hasta cada resultado. Un módulo completo y válido cumple su contrato sin pedir al consumidor que repare su estado, y entrega resultados que no comparten referencias mutables.
El contrato puede verificarse desde fuera. Los tests actúan como consumidores y observan resultados, estado o mensajes públicos.
Estos fundamentos pueden aplicarse de forma recursiva cuando un módulo se descompone en otros módulos con contratos propios. API-DD no prescribe una arquitectura, un paradigma ni un orden de trabajo. Ofrece una perspectiva para diseñar las conversaciones que mantienen unido el sistema.
API-Driven Development (API-DD) propone diseñar cada módulo como una API: hacer visibles los mensajes que acepta, las garantías que conserva, los detalles que oculta y las APIs que necesita.
El punto de partida no es la clase, la carpeta ni el patrón que vamos a utilizar. Es el contrato mediante el que un módulo colabora con los demás. Antes de resolver su interior, aclaramos qué API ofrece y qué compromisos deben sobrevivir a cualquier implementación correcta. Es una perspectiva de diseño, no una arquitectura ni un conjunto de reglas obligatorias.
En este libro, módulo no significa necesariamente un módulo del lenguaje, un paquete, un servicio desplegable ni un archivo. Es una parte encapsulada del software que otros utilizan mediante un contrato. Según la escala, puede materializarse como un conjunto de funciones, un tipo, un paquete, un proceso o una combinación de ellos.
Término
Uso en API-DD
Módulo
Parte encapsulada del software que ofrece comportamiento a otros
Consumidor
Actor, sistema o módulo que depende de ese comportamiento
API
Protocolo mediante el que un módulo se relaciona con sus consumidores
Mensaje
Petición, respuesta o hecho que cruza esa API
Proveedor
Implementación que satisface el contrato
Una API puede expresarse con funciones, métodos, eventos, endpoints o cualquier otro mecanismo. Lo importante es la conversación, no su sintaxis.
Diseñar la API exige identificar al consumidor, los mensajes que necesita, lo que puede observar y las garantías que deben conservarse. También separa los detalles ocultos y las APIs que el módulo necesita. Esas decisiones forman el contrato; la firma es solo su representación más visible.
Formatter es el módulo y Format es un mensaje de su API. Text, Style, FormattedText y UnsupportedStyle forman el vocabulario del contrato. Conviene precisar qué significan y qué puede hacer el consumidor con ellos, pero no son APIs independientes por el mero hecho de aparecer en la firma.
El contrato promete que un estilo admitido produce texto formateado, uno desconocido devuelve un error distinguible y el original no cambia. La librería utilizada permanece oculta.
El consumidor no necesita conocer si el proveedor usa una plantilla, un árbol intermedio o una librería externa. Esas decisiones pueden cambiar mientras se conserven las garantías.
Para diseñar una API resulta útil conceptualizarla como un módulo visto desde fuera. El módulo reúne una capacidad y conserva su implementación; la API es el límite por el que otros colaboran con él.
No son exactamente lo mismo. Un módulo puede ofrecer más de una conversación a consumidores diferentes y también consumir otras APIs. La equivalencia sirve como herramienta de diseño: cuando aparece una API relevante, buscamos el módulo responsable de sostener su contrato.
text
módulo
├── API ofrecida → consumidores
├── implementación oculta
└── APIs consumidas → otros módulos
El módulo puede materializarse como una función, un tipo, un paquete, un proceso o varios elementos coordinados. Su forma técnica no determina su escala conceptual.
API-DD pone el interés en lo que ocurre entre módulos: mensajes, respuestas, errores, efectos y garantías. Esta mirada sigue la idea de Alan Kay presentada en la introducción: los sistemas crecen mejor cuando se diseña cómo se comunican sus módulos, no cuando se fija de antemano todo su interior.
Mirar las interacciones aclara qué necesita el consumidor, qué módulo responde, qué puede observarse y qué decisiones internas pueden cambiar. También muestra las APIs que ese módulo consume.
El algoritmo sigue siendo importante, pero pertenece a otro nivel. Primero se distingue qué debe sobrevivir a cualquier implementación correcta y después se elige cómo conseguirlo.
Cuando un módulo se descompone en módulos con contratos propios, los cinco fundamentos pueden aplicarse de nuevo en cada límite:
Fundamento
Pregunta que reaparece
Recursión
¿Qué módulos y conversaciones existen en esta escala?
Vocabulario
¿Qué significan sus nombres y mensajes?
Visibilidad
¿Qué necesita conocer cada consumidor?
Autonomía
¿Puede el módulo cumplir su contrato y entregar resultados sin estado mutable compartido?
Testeabilidad
¿Puede verificarse mediante observaciones públicas?
La recursión no convierte cada parámetro, helper o estructura en otra API. Value, Result o Error forman parte del vocabulario de un mensaje. Solo pasan a considerarse módulos cuando reúnen comportamiento, tienen consumidores o necesitan evolucionar mediante un contrato propio.
La perspectiva se repite cuando una capacidad tiene otro consumidor, garantías propias o evolución independiente, o cuando la interacción cruza un límite técnico u organizativo relevante.
Se detiene cuando la decisión solo explica cómo trabaja el módulo actual. Un bucle, un índice o una función auxiliar no necesitan una API propia si ninguna relación externa depende de ellos.
Esto evita confundir recursión con una jerarquía infinita de interfaces. El objetivo es reconocer límites útiles, no fabricar capas.
Pueden existir muchas implementaciones de Stage. Cada una puede revisarse otra vez como módulo, mientras que el recorrido del slice sigue siendo un detalle de Pipeline.
Nombra la API, el módulo responsable, sus consumidores y las APIs que necesita. Solo profundiza en colaboraciones con contrato propio; algoritmos y helpers permanecen dentro del módulo. La descomposición es útil si aclara una relación real.
La recursión sigue las conversaciones entre módulos, no cada línea de código.
I · Fundamentos · Fundamento 2 de 5
Fundamento 2. Vocabulario
Nombrar módulos, mensajes y valores desde la conversación que necesita el consumidor.
Un nombre no decora una solución terminada. Decide qué concepto verá el consumidor y qué podrá esperar de él.
Cuando una API usa palabras como execute, data o response, obliga a leer la implementación para descubrir su significado. Cuando nombra una intención, un resultado o una situación reconocible, permite entender el contrato sin abrir la implementación.
El vocabulario de una API debería seguir siendo verdadero aunque cambie su implementación.
Todo valor que cruza una API necesita significado, reglas de validez e igualdad, una representación de la ausencia y un propietario para su estado mutable. El consumidor solo debería observar la representación que el contrato decida conservar.
La respuesta no siempre requiere un tipo nuevo. Un tipo propio aporta cuando nombra una diferencia, protege una invariante o impide una combinación inválida. Si el contexto ya evita la confusión y las reglas son las mismas, separar dos valores solo añade ceremonia.
No sabemos qué se ejecuta, qué contiene la respuesta ni qué puede salir mal. La misma capacidad puede expresarse así:
text
Queue.Enqueue(Task) → Position | QueueFull
Queue aporta el contexto, Enqueue expresa la intención, Task nombra la entrada, Position el resultado y QueueFull una situación ante la que el consumidor puede actuar.
La implementación puede usar memoria, archivos o un sistema remoto. Ninguna de esas decisiones obliga a cambiar el mensaje.
Los nombres públicos son el vocabulario compartido entre un módulo y sus consumidores. Merecen más estabilidad que los identificadores internos porque aparecen en llamadas, documentación, tests y, a veces, datos serializados.
Cada lenguaje expresa esa frontera de otra forma. En Go, un identificador exportado comienza con mayúscula; los nombres con minúscula permanecen dentro del paquete. Un directorio internal también permite limitar qué parte del árbol puede importar un paquete.
Exportar no mejora un nombre ni convierte un tipo en una buena abstracción. Primero se identifica qué necesita nombrar el consumidor; después se le da la visibilidad correspondiente. Los helpers y representaciones internas pueden usar palabras más técnicas sin contaminar la conversación pública.
El nombre de un módulo debe indicar la capacidad que reúne, no el patrón con el que fue construido.
Manager, Service, Handler, Facade o Helper suelen ser débiles cuando aparecen solos. Clasifican una estructura técnica, pero no explican qué ofrece. QueueManager, por ejemplo, permite imaginar casi cualquier responsabilidad; Queue establece un contexto concreto para mensajes como Enqueue, Next o Remove.
Esto no convierte los nombres técnicos en un error universal. En la composición puede ser útil distinguir MemoryQueue de RemoteQueue, porque allí el consumidor elige una implementación. La API que ambas proporcionan puede seguir llamándose Queue.
La pregunta útil es: ¿el consumidor necesita conocer este mecanismo? Si la respuesta es no, el nombre técnico pertenece al interior.
Los mensajes expresan intenciones o hechos, no pasos internos.
Un verbo como Enqueue permite anticipar el efecto. Run, Execute, Process o Handle solo son precisos cuando ejecutar, procesar o despachar constituye realmente la capacidad del módulo.
La prueba más sencilla consiste en leer el uso como una frase:
text
position = queue.Enqueue(task)
Si para entenderla hay que traducir categorías del framework, el contrato todavía habla desde la implementación.
Un tipo merece nombre cuando representa una diferencia que importa. Task, Position y Capacity dicen más que Data, Item o ValueObject, siempre que esas sean las palabras del contexto real.
Los sufijos que repiten la categoría técnica suelen sobrar:
text
TaskModel
PositionValueObject
QueueResponseDTO
Pueden ser necesarios dentro de un adaptador que traduce dos representaciones, pero no deberían propagarse a la API principal por accidente.
Las unidades también forman parte del significado. Delay puede resultar ambiguo si el consumidor necesita distinguir milisegundos de segundos. No siempre hace falta un nombre más largo; sí hace falta conservar la diferencia que evita un uso incorrecto.
Un booleano se entiende mejor como una proposición:
text
queue.IsFull()
queue.Contains(taskID)
Check, Flag o Status no aclaran qué significa true. Si existen más de dos resultados relevantes, probablemente el contrato necesite un estado nombrado en lugar de un booleano.
Los errores públicos describen situaciones ante las que el consumidor puede reaccionar. QueueFull permite esperar o elegir otra cola. DatabaseError filtra infraestructura y quizá no ofrece ninguna decisión útil. Los detalles técnicos pueden conservarse como causa interna, log o diagnóstico sin convertirse en vocabulario estable.
Un evento nombra un hecho que ya ocurrió. TaskQueued evita confundir la notificación con la petición EnqueueTask. El pasado también ayuda a que el nombre siga siendo cierto aunque cambie el transporte.
El vocabulario funciona como un sistema, no como una lista de términos aislados. Módulo, mensaje, argumentos y resultados deben poder leerse juntos como una conversación.
En el primer caso, módulo, mensaje y argumento reparten el significado. En el segundo, las categorías técnicas añaden longitud sin explicar mejor el contrato.
Un nombre corto puede ser ambiguo y uno largo puede compensar un contexto mal elegido. La meta es usar las palabras mínimas que conserven el significado donde se leen.
Para encontrar el nombre, describe primero la intención del consumidor: «quiero añadir esta tarea a la cola». Separa contexto, acción, valores y resultados; usa las palabras del problema y lee la llamada completa. Después imagina otra implementación: si el nombre deja de ser cierto, todavía describe el mecanismo.
Si cuesta nombrar un módulo, puede haber responsabilidades mezcladas o una abstracción prematura. En ese caso conviene volver a la conversación antes de buscar un sinónimo más elegante.
Esta práctica coincide con el lenguaje ubicuo de DDD: los nombres ganan precisión dentro de un contexto explícito, no en un diccionario universal (DDD Reference, Eric Evans). API-DD añade una comprobación concreta: el vocabulario debe funcionar desde el módulo consumidor y sobrevivir a implementaciones alternativas.
Una vez publicada, una palabra forma parte del contrato. Cambiarla puede romper compilación, mensajes serializados, documentación, métricas o integraciones.
Una migración puede requerir introducir el nombre nuevo junto al anterior, adaptar consumidores y retirar el alias cuando ya no exista dependencia. Que el nombre nuevo sea mejor no vuelve inocuo el cambio.
Un buen nombre explica la conversación y no delata el mecanismo que la hace posible.
I · Fundamentos · Fundamento 3 de 5
Fundamento 3. Visibilidad
Mostrar lo que necesita el consumidor y mantener reemplazable la implementación.
Una API es el protocolo que permite a dos módulos colaborar. Su contrato reúne la información mínima que un consumidor necesita para usarla sin conocer su implementación.
Una firma puede mostrar nombres y tipos:
text
Set.Add(Value) → AddResult
Pero no explica por sí sola si se permiten duplicados, cómo se compara un valor, qué cambia después de la llamada ni qué ocurrirá al repetirla. Esas garantías también pertenecen al contrato.
Cada elemento visible crea una dependencia: el consumidor puede utilizarlo y el proveedor tendrá que conservarlo o migrarlo. Reducir visibilidad disminuye acoplamiento solo cuando la API sigue expresando toda la capacidad necesaria.
¿Qué significan entradas, resultados, errores y eventos?
Validez
¿Qué valores y secuencias se aceptan?
Garantías
¿Qué resultado, estado o efecto se conserva?
Visibilidad
¿Qué consumidores pueden acceder al contrato?
Compatibilidad
¿Qué usos anteriores deben seguir funcionando?
No todas las APIs necesitan documentar cada dimensión con el mismo detalle. La profundidad depende del riesgo y del número de consumidores. Un módulo local con una operación pura puede quedar claro con una firma y dos ejemplos; un protocolo compartido necesitará más precisión.
Su contrato establece que añadir un valor ausente devuelve Added y hace que Contains sea verdadero. Repetirlo devuelve AlreadyPresent sin aumentar Size.
El contrato aún necesita una decisión sobre igualdad: ¿cuándo representan dos valores lo mismo? No necesita decidir si la implementación usa una tabla hash, un árbol o una lista. Cualquiera de ellas es válida si conserva las garantías.
Este ejemplo también muestra la diferencia entre API y vocabulario. Value, Added y AlreadyPresent forman parte de los mensajes. No son tres APIs nuevas. Solo necesitarían contratos independientes si adquirieran comportamiento, consumidores o evolución propios.
Una decisión pertenece a la API cuando un consumidor legítimo necesita utilizarla o distinguirla y el proveedor está dispuesto a conservarla.
Pertenece al contrato
Permanece en la implementación
Intenciones que el consumidor puede expresar
Secuencia interna de llamadas
Valores y diferencias que cambian su conducta
Estructuras intermedias
Errores ante los que puede actuar
Fallos técnicos ya traducidos
Estado y efectos observables
Algoritmos y mecanismos de coordinación
Orden o cantidad cuando alteran el resultado externo
Optimización y distribución del trabajo
Una superficie pequeña es útil si permite expresar la capacidad completa. Ocultar una diferencia necesaria no reduce acoplamiento: obliga al consumidor a deducirla o a buscarla en la implementación.
El resultado inmediato es solo una forma de observación. Un mensaje puede modificar estado que luego se consulta mediante la API o producir un efecto dirigido a otro módulo.
El contrato debe nombrar esos efectos cuando el consumidor depende de ellos. No debe publicar la coordinación utilizada para conseguirlos.
En el ejemplo del conjunto, Size y Contains permiten observar el estado sin exponer su representación. El consumidor puede comprobar la garantía sin recibir la colección interna ni modificarla por referencia.
Cuando la API devuelve colecciones o estructuras mutables, hay que decidir quién conserva su propiedad. Si siguen perteneciendo al proveedor, una copia o una vista inmutable puede evitar cambios accidentales. Si la propiedad se transfiere, conviene decirlo de forma explícita.
Una operación es idempotente cuando repetir la misma intención produce el mismo efecto observable que ejecutarla una vez. No significa que el proveedor ejecute una sola instrucción ni que devuelva la misma instancia.
Set.Add es idempotente respecto del contenido: repetir el mismo valor no crea otra entrada. La respuesta puede cambiar de Added a AlreadyPresent y la garantía seguir siendo válida, porque el estado final no cambia.
La idempotencia solo merece entrar en el contrato cuando existen reintentos, repeticiones o entregas duplicadas que el consumidor deba poder manejar. Añadirla sin necesidad introduce identidad, estado y costes de retención.
Una API publicada acumula consumidores. Cambiar un nombre, una regla de validez, un error o el significado de un campo puede romperlos aunque el código siga compilando.
Antes de modificar el contrato hay que conocer sus consumidores y el comportamiento que observan. Si el cambio no puede ser aditivo, se necesita una convivencia temporal y una condición explícita para retirar la versión anterior.
La compatibilidad no exige conservar para siempre una mala decisión. Exige tratar su corrección como una migración y no como un refactor interno.
La autonomía no aparece únicamente en el módulo más grande. Puede fomentarse de forma granular cada vez que la perspectiva de API-DD se aplica recursivamente.
Escala
Qué significa autonomía
Módulo
Reúne la capacidad y protege sus invariantes
API
Ofrece una conversación suficiente sin revelar cómo se coordina el interior
Mensaje de resultado
Tiene significado propio y no comparte estado mutable con quien lo produjo
Autonomía no significa aislamiento. Un módulo puede consumir otras APIs y un resultado puede contener varios valores. La diferencia es que cada elemento conserva un límite claro: ningún consumidor necesita reparar su estado, completar su significado ni conocer una representación ajena.
Aplicar el fundamento a un resultado no lo convierte en otro módulo. El resultado sigue siendo vocabulario de la API, pero también necesita propiedad y validez propias.
Una API es autónoma cuando el consumidor puede expresar una intención y comprender la respuesta dentro de la misma conversación. No necesita abrir la implementación, consultar una estructura interna ni coordinar colaboradores que pertenecen al proveedor.
Una secuencia pública puede formar parte de un protocolo real. Pierde autonomía cuando sus pasos solo existen para terminar de montar el módulo:
text
value := Interval{}
value.SetStart(2)
value.SetEnd(8)
value.Validate()
La intención completa puede expresarse en un solo mensaje:
text
Interval.New(2, 8) → Interval | InvalidInterval
La autonomía tampoco exige que cada respuesta incluya todos los datos imaginables. Incluye lo necesario para que el consumidor actúe ante las diferencias que el contrato reconoce.
Un módulo es completo cuando reúne lo necesario para cumplir el contrato que ofrece. El consumidor usa su capacidad mediante la API sin completar pasos internos, corregir su estado ni decidir cómo deben coordinarse sus dependencias.
Completo no significa autosuficiente. El módulo puede leer, calcular o producir efectos mediante otras APIs. Su responsabilidad consiste en gobernar esas colaboraciones y traducirlas al contrato que ofrece.
Una señal de incompletitud aparece cuando varios consumidores repiten la misma coordinación para obtener una capacidad que debería pertenecer al módulo. La solución no es esconder cualquier secuencia: es asignar la responsabilidad al límite que puede garantizarla.
Un módulo válido conserva sus invariantes en todos los estados observables. Puede rechazar una entrada o devolver un error; evita continuar con un estado incoherente que otro consumidor tenga que descubrir después.
La validez se protege en cada entrada de información: construcción, cambios de estado, datos externos, persistencia y resultados de otras APIs.
Un estado inicial vacío puede ser válido. También puede ser inválido pero seguro. La API debe distinguirlo antes de producir un efecto incorrecto.
Un resultado autónomo pertenece a la conversación que lo recibe. Su significado está completo y su contenido no cambia porque el proveedor continúe trabajando ni porque otro consumidor lo utilice.
Esto requiere evitar referencias compartidas a estado mutable. Devolver directamente un slice, un mapa o un puntero interno permite que el consumidor modifique al proveedor y que el proveedor altere un resultado ya entregado. Ambos dejan de ser autónomos.
Puede conservarse la autonomía mediante valores copiados, campos privados y operaciones de lectura, o una transferencia explícita de propiedad. Si copiar resulta demasiado costoso, el módulo puede ofrecer una API de recorrido sin exponer su colección interna.
Go no tiene una declaración general de inmutabilidad. Aquí, inmutable significa que la API pública no permite cambiar el resultado y que el proveedor no puede alterarlo después de entregarlo. Un struct copiado puede contener slices, mapas o punteros que aún comparten memoria; la autonomía debe llegar hasta cada referencia mutable, no detenerse en el tipo exterior.
New produce un módulo válido o un error. Add responde de forma segura incluso ante el valor cero de Go. Snapshot no conserva el slice de Collection ni lo expone: Len y At permiten leerlo sin ofrecer una operación de mutación.
En Go, todo tipo concreto tiene un valor cero. Publicar New no lo elimina. El contrato puede tratarlo como útil, como ausencia o como inválido pero seguro. La especificación de Go define el mecanismo; la API define su significado.
Ausente y presente con valor cero tampoco son siempre equivalentes. Si la diferencia cambia el comportamiento, puede representarse mediante (T, bool), un puntero o un resultado nombrado. Los formatos de transporte se traducen en el adaptador para que el módulo reciba significado, no detalles de serialización.
Una dependencia forma parte de la construcción cuando el módulo no puede cumplir su contrato sin ella. Recibirla mediante una API hace visible qué necesita, pero no traslada su coordinación al consumidor.
No hace falta introducir una interfaz para cada helper. Una dependencia merece contrato propio cuando tiene otros consumidores o proveedores, cruza un límite relevante o evoluciona de forma autónoma. En ese punto los mismos fundamentos pueden aplicarse de nuevo.
La API debe expresar una intención completa, y el módulo debe gobernar sus dependencias e invariantes. La creación produce un valor válido o un fallo explícito. Cada resultado contiene lo necesario para actuar y deja clara la propiedad de cualquier slice, mapa, puntero o elemento mutable.
La autonomía se conserva desde el módulo hasta el resultado que cruza su API.
I · Fundamentos · Fundamento 5 de 5
Fundamento 5. Testeabilidad
Verificar resultados, estado y mensajes sin convertir el interior en especificación.
Un test funcional representa a un consumidor del módulo. Entra por su API y comprueba resultados, estado o mensajes que pertenecen al contrato.
La caja negra no tiene que abarcar todo el sistema. Puede ser un módulo pequeño siempre que la prueba respete su límite y no convierta la coordinación interna en una promesa pública.
text
test → API → implementación
└── API consumida → colaborador
La pregunta principal es qué debe seguir siendo cierto para el consumidor. La elección entre implementación real, doble de prueba o integración viene después.
Los helpers utilizados, el reparto entre objetos internos y el algoritmo elegido quedan fuera. También quedan fuera el número y el orden de llamadas cuando no cambian el resultado observable.
Una prueba funcional debería aceptar dos implementaciones que produzcan los mismos resultados, el mismo estado visible y los mismos mensajes contractuales.
Cache.Refresh(Key) → Value | SourceUnavailable
Cache.Get(Key) → Value | NotFound
Cache consume otra API:
text
Source.Load(Key) → Value | error
Queremos demostrar una sola garantía: si la fuente falla durante una actualización, el valor anterior continúa disponible.
El test ejecuta la implementación real de Cache y prepara un doble de Source que devuelve un fallo. Después observa únicamente la API:
text
dado un valor almacenado para una clave
cuando Refresh recibe SourceUnavailable
entonces devuelve SourceUnavailable
y Get conserva el valor anterior
La prueba no necesita saber si la caché escribe primero en una copia, usa un bloqueo o revierte una asignación. Tampoco necesita una fuente remota real, porque el riesgo observado es la reacción del módulo al fallo, no el protocolo de red.
Una prueba diferente debería comprobar el adaptador real si el riesgo estuviera en la serialización, la configuración o el transporte.
Doble de prueba es el término general para cualquier sustitución usada durante una prueba. Dentro de esa familia conviene mantener los significados conocidos, en lugar de redefinir fake para abarcarlo todo.
Término
Uso
Implementación real
La misma implementación usada fuera del test
Fake
Implementación funcional simplificada, como un almacenamiento en memoria
Stub
Devuelve respuestas preparadas para controlar una entrada indirecta
Spy
Registra mensajes para poder observarlos después
Mock
Declara y verifica expectativas sobre interacciones
La terminología procede de la taxonomía recogida por Gerard Meszaros y resumida por Martin Fowler en Test Double. Saber el nombre ayuda, pero la decisión importante sigue siendo qué comportamiento sustituye el doble y qué riesgo deja sin cubrir.
En el ejemplo anterior basta un stub de Source: prepara el fallo que el caso necesita. Un fake funcional sería útil si muchos tests necesitaran una fuente en memoria con reglas estables. Un mock solo tendría sentido si una interacción concreta formara parte del contrato.
La implementación real ofrece la mayor fidelidad y es la primera opción cuando resulta rápida, determinista, hermética, segura y fácil de construir. La guía de Software Engineering at Google sobre test doubles propone el mismo punto de partida pragmático.
Situación
Elección habitual
Colaborador rápido y determinista
Implementación real
Muchos casos necesitan semántica estable sin infraestructura
Fake funcional
Un caso necesita una respuesta excepcional
Stub local
El contrato incluye un mensaje saliente
Spy o mock
El riesgo depende de protocolo, transacción o configuración
Integración con el adaptador real
No es necesario sustituir valores, entidades o funciones puras solo porque colaboren en el caso. Tampoco hace falta levantar infraestructura real para demostrar una regla que no depende de ella.
Un mensaje saliente puede formar parte del contrato. En ese caso un spy o un mock permite observar su contenido, cantidad u orden.
La expectativa se limita al contenido, la cantidad o el orden solo cuando esa diferencia cambia el efecto para otro consumidor.
Comprobar que se llamó a un mapper, una query concreta o un helper privado congela la implementación. Comprobar que se publicó un mensaje requerido protege el contrato.
Una interacción solo demuestra que el mensaje se intentó enviar. No demuestra que el receptor real lo acepte. Cuando esa compatibilidad es el riesgo, hace falta una prueba del adaptador o una integración.
Un fake funcional implementa un subconjunto declarado del contrato. Para los casos soportados acepta las mismas entradas y conserva resultados, errores y estado. Debe ser determinista, aislar cada test y hacer visibles las capacidades o propiedades que omite.
No necesita copiar la infraestructura. Una fuente en memoria puede reproducir lectura, ausencia y versiones sin simular red o latencia. Si el caso trata precisamente de esas propiedades omitidas, el fake deja de ser una prueba suficiente.
Un fake compartido acumula responsabilidad y merece tests propios. Cuando sea viable, una misma suite de contrato puede ejecutarse contra el fake y el adaptador real para detectar divergencias.
Una herramienta de IA puede explorar un repositorio, proponer una API, escribir tests e implementar código con rapidez. Esa velocidad no resuelve por sí sola qué comportamiento necesita el sistema. Si el encargo es ambiguo, el resultado puede ser técnicamente plausible y aun así resolver otro problema.
API-DD es compatible con este modo de trabajo porque ofrece una perspectiva para explicitar el límite de cada módulo: quién lo consume, qué mensajes intercambia, qué garantiza y qué mantiene oculto. No impone una arquitectura, un proceso ni una división entre el trabajo humano y el de la IA. Ayuda a convertir decisiones difusas en un contrato que ambos pueden revisar.
La diferencia práctica está en el criterio de éxito. «Genera el código para esta funcionalidad» invita a completar huecos por probabilidad. «Implementa esta API y demuestra estos casos» reduce la ambigüedad y permite evaluar el resultado por su comportamiento.
Antes de generar una implementación conviene describir las conversaciones afectadas. Para cada módulo basta con responder lo necesario:
Decisión
Qué aclara para la IA
Consumidor
Desde qué necesidad debe diseñarse el cambio
Mensajes
Qué operaciones, resultados y errores puede usar
Garantías
Qué comportamiento debe conservarse
Límite
Qué archivos y módulos pertenecen al cambio
Detalles internos
Qué decisiones puede tomar libremente la implementación
APIs consumidas
Qué colaboraciones existen y cuáles pueden sustituirse en un test
Este mapa reduce dos fallos frecuentes. El primero es ampliar el cambio con abstracciones que nadie pidió. El segundo es copiar detalles del mecanismo en la API: nombres de una librería, estructuras de persistencia o pasos internos que luego quedan convertidos en contrato.
No todos los huecos deben rellenarse antes de empezar. Algunos se descubren al investigar el código o al escribir el primer test. Lo importante es reconocer qué es una decisión pendiente en vez de dejar que una respuesta generada la tome de forma accidental.
Un caso de prueba expresa una diferencia observable: una entrada, una acción y un resultado, estado o mensaje que importa al consumidor. Al ejecutarlo se convierte además en feedback para la persona y para la IA.
Un buen conjunto de casos cubre las diferencias relevantes sin repetir la misma regla con datos decorativos. Por ejemplo:
text
dado un búfer vacío con capacidad uno
cuando se añade un valor
entonces el valor queda disponible
dado un búfer lleno
cuando se intenta añadir otro valor
entonces informa que está lleno y conserva el primero
El segundo caso es un test de contraste porque prepara una situación en la que dos comportamientos plausibles producen resultados distintos: rechazar el valor nuevo y conservar el primero, o aceptar el nuevo y reemplazar el anterior. El contraste no consiste en ejecutar las dos implementaciones a la vez; está en elegir observaciones que hagan pasar el contrato acordado y fallar la alternativa. Si el caso pasa con ambos comportamientos, no los está contrastando, aunque el test esté verde.
Estos casos dicen más que una petición genérica de «manejar errores». También dejan libertad para usar una lista, un arreglo circular u otra representación. El test protege el contrato; no dicta el recorrido interno.
Ver el test fallar antes de implementar aporta una evidencia sencilla: la prueba puede detectar la ausencia del comportamiento. Verlo pasar después confirma que esa implementación satisface el ejemplo. Ninguna de las dos señales demuestra por sí sola que el diseño esté completo, pero juntas son más fiables que aceptar código porque parece correcto.
Spec-Driven Development con la especificación en el código#
API-DD es compatible con Spec-Driven Development. En esta forma de aplicarlo, la especificación ejecutable no es un documento externo que la implementación deba interpretar: es el archivo —o el conjunto— de tests versionado junto al código de producción.
La documentación puede explicar contexto y motivaciones. La parte del contrato que debe aceptar la implementación queda en código ejecutable: API pública, casos relevantes, resultados esperados y límites. El recorrido interno queda fuera.
Esto acerca la especificación al lugar donde puede contradecirse. Si cambia la firma de la API, el test deja de compilar. Si cambia una garantía, el test falla. El repositorio conserva juntos el contrato ejecutable, la implementación y la historia de ambos.
También permite separar el trabajo según la dificultad de cada etapa:
text
intención + repositorio
↓
modelo con mayor capacidad de razonamiento
↓
API + casos + tests en rojo + límites explícitos
↓
modelo más simple, acotado por esa especificación
↓
implementación + tests en verde
La fase de especificación concentra la ambigüedad: hay que investigar consumidores, distinguir alternativas plausibles, decidir el vocabulario y seleccionar los casos que protegen riesgos reales. Ahí puede aportar más un modelo con mayor capacidad de razonamiento y contexto. Cuando la API y los tests ya fijan el resultado observable, una implementación rutinaria puede delegarse a un modelo más simple que reciba feedback inmediato al ejecutar la suite.
Esta separación no es una garantía ni una obligación. Un algoritmo difícil o un cambio de alto riesgo puede necesitar el modelo más capaz también durante la implementación. Y el modelo que implementa no debe modificar los tests para conseguir el verde: si descubre que el contrato es imposible, incompleto o contradictorio, debe devolver esa evidencia a la fase de especificación.
La especificación tiene además un límite deliberado: solo exige los casos y garantías que contiene. Una suite verde no demuestra comportamientos que nunca fueron representados. Por eso el trabajo más importante ocurre antes de implementar: elegir la API, las diferencias observables, los límites y las casuísticas que merecen convertirse en evidencia ejecutable.
El reparto cambia con el riesgo. Las personas aportan intención, prioridades y decisiones de producto o arquitectura; la IA investiga el repositorio, propone casos, implementa y ejecuta verificaciones. Las decisiones ambiguas y el resultado observable se revisan en común.
Delegar una tarea no significa delegar su criterio de aceptación. Cuanto mayor sea el impacto de una decisión, más explícita debe quedar antes de convertirla en código. En cambios rutinarios, los tests y las convenciones del repositorio pueden proporcionar casi todo ese contexto.
Investiga consumidores, comportamiento actual y preguntas abiertas.
Define la API y expresa cada diferencia observable importante en un test que falle por la razón esperada.
Implementa sin reescribir la especificación ni ampliar el contrato.
Ejecuta las verificaciones y revisa el diff como consumidor de la API.
El ciclo puede volver atrás. Un test difícil de escribir quizá revele una API incómoda; una implementación puede mostrar que faltaba representar un resultado. Corregir el contrato en ese momento es parte del diseño, no un fracaso del proceso.
El resultado se degrada cuando falta el consumidor o el comportamiento esperado, el contexto oculta las restricciones, la IA inventa decisiones o los tests fijan el mecanismo. También cuando se adapta la prueba al código generado o se da el cambio por terminado sin ejecutar las verificaciones reales.
La solución no es escribir un prompt enorme. Es entregar contexto seleccionado: contrato, casos, límites, convenciones y comandos de verificación. Los apéndices ofrecen plantillas breves para investigar y acordar el contrato y para implementar y verificarlo.
El test describe el contrato de un búfer con capacidad uno. La implementación no está incluida a propósito: podría ser escrita por una persona o generada con IA y seguiría siendo evaluada por las mismas observaciones.
go
import"testing"funcTestBoundedBufferContract(t *testing.T) {
buffer :=NewBoundedBuffer[int](1)
if got := buffer.Push(7); got !=Stored {
t.Fatalf("expected Stored, got %v", got)
}
if got := buffer.Push(8); got !=Full {
t.Fatalf("expected Full, got %v", got)
}
value, ok := buffer.Pop()
if!ok || value !=7 {
t.Fatalf("expected first value, got %v, %v", value, ok)
}
}
El caso fija capacidad, respuesta y conservación del primer valor. No fija clases auxiliares, número de llamadas ni estructura interna. Esa libertad permite que la IA proponga una implementación y que el equipo la cambie después sin alterar el contrato.
Pensar en APIs y casos de prueba estrecha el espacio de soluciones sin elegir de antemano el mecanismo. La IA recibe nombres con significado, límites concretos y ejemplos ejecutables; el equipo recibe una forma objetiva de revisar el resultado.
El beneficio no es que todo código generado sea correcto. Es que deja de evaluarse solo por su apariencia: debe respetar el contrato, superar los casos acordados y conservar la libertad interna del módulo.
La IA acelera una propuesta; el contrato y las pruebas permiten decidir si esa propuesta sirve.
III · TDD, DDD y Hexagonal · Capítulo 8 de 8
API-DD junto a TDD, DDD y arquitectura hexagonal
Combinar enfoques por las preguntas que responden.
DDD ayuda a descubrir conceptos, invariantes, lenguaje y límites de modelo. Cuando dos áreas utilizan una palabra parecida, permite decidir si comparten significado o necesitan representaciones distintas.
API-DD puede aprovechar ese resultado para diseñar las conversaciones que los consumidores necesitan. No decide por sí solo cuál es el modelo correcto ni requiere que el trabajo empiece por DDD.
Un port representa una conversación con propósito; los adaptadores conectan mecanismos concretos a ella. Desde API-DD, ese port puede mirarse como una API: mensajes, vocabulario, garantías y efectos.
No todo módulo necesita convertirse en un port. Dos módulos internos pueden colaborar mediante una API local sin representar un límite arquitectónico de la aplicación. Convertir cada relación en port añadiría visibilidad y sustitución sin una necesidad real.
La intención original de puertos y adaptadores está descrita por Alistair Cockburn en Hexagonal Architecture.
TDD aporta el ciclo de feedback: elegir el siguiente comportamiento, escribir un test que falle, implementarlo y refactorizar. API-DD ayuda a formular ese comportamiento como una garantía observable de un módulo.
text
garantía del contrato → rojo → verde → refactor
El test puede descubrir que el contrato estaba incompleto. En ese caso se revisa la decisión antes de continuar; no se fuerza la implementación para conservar una especificación equivocada.
El ciclo se apoya en la descripción de TDD de Martin Fowler, basada en el trabajo de Kent Beck.
Supongamos que un módulo necesita guardar y recuperar bytes por clave. Esta API puede actuar como port de salida cuando existen proveedores intercambiables.
La arquitectura hexagonal orienta esta dependencia hacia la necesidad del consumidor. API-DD ayuda a hacer visibles decisiones como qué significa ausencia, quién posee los bytes devueltos y qué errores deben distinguirse. TDD permite implementar esas garantías una a una. Si nunca habrá otro proveedor ni un límite relevante, una interfaz separada puede ser innecesaria.
No. Una API puede expresarse mediante un tipo concreto, funciones, métodos o mensajes. Una interface técnica aporta cuando un consumidor necesita sustitución o desacoplamiento, no como requisito ceremonial.
No. Se puede empezar por cualquier módulo, regla o conversación cuyo contrato y riesgo estén claros. API-DD ofrece una perspectiva para revisar cada módulo como una API, sin prescribir una dirección temporal para descubrir el sistema.
¿Un test con varios módulos deja de ser unitario?#
La cantidad de objetos o módulos no determina qué riesgo cubre la prueba. Resulta más útil declarar la API observada y qué implementaciones participan que discutir una etiqueta universal.
¿Observar un mensaje saliente rompe la caja negra?#
No cuando ese mensaje forma parte del efecto prometido. Sí cuando se comprueba una colaboración interna que otra implementación correcta podría resolver de manera distinta.
Aclara conceptos y límites con el modelado disponible. Después identifica las conversaciones entre módulos, expresa sus garantías mediante APIs y tests, e implementa en ciclos pequeños sin alterar el contrato.
El trabajo real no será lineal. Un nombre descubierto durante un test puede cambiar el modelo; una restricción arquitectónica puede obligar a revisar la API. La separación de preguntas sirve para entender la decisión, no para imponer fases rígidas.
DDD aclara el significado, la arquitectura orienta las relaciones, TDD guía el cambio y API-DD ayuda a diseñar la conversación.
IV · Guía operativa · Apéndice A de 2
API-DD Prompt 1: discover the contract and write failing tests
Prompt breve para investigar un cambio y dejar evidencia funcional en rojo.
Give the whole Prompt block to the root AI as one immutable instruction prefix. Append the repository, task, and product context only in the execution envelope at the end. The AI investigates, asks only blocking contract questions, and writes executable failing tests. It does not implement the feature.
Keep the instruction prefix byte-for-byte stable when the runtime can cache or share prompts. Recursive workers run this same Prompt 1; they receive only a small module execution envelope, never a rewritten or reduced version of its rules.
You are the API-DD contract and test agent.
PROMPT_ID=API-DD-PROMPT1-RECURSIVE-V1
Read the execution envelope at the end before acting. `MODE` determines whether this invocation is the root coordinator or one recursively scoped module agent. All rules in this prompt apply in both modes unless a rule explicitly assigns user interaction or repository-wide coordination to the root.
## Mission
Discover the observable contract.
Express the whole confirmed scope as the smallest complete set of black-box tests.
Leave those tests red.
Do not implement the production feature.
The tests are the executable spec for Prompt 2.
## Code clarity rule
Do not add explanatory comments to production code or tests. The code should be self-explanatory through domain names, structure, and behavior. If a clarification would otherwise need a comment, express that clarification as an executable test case with a domain-specific name, explicit input, and expected outcome. A test is the contract; comments are not a substitute for coverage.
Prompt 1 may change only:
- Tests.
- Test-only support.
- The minimum inert public API shape required for tests to compile/load.
Prompt 1 MUST NOT create, modify, or apply:
- Database or data migrations.
- Production schemas or backfills.
- Production adapters or feature behavior.
- Deployment/runtime configuration.
- Generated production artifacts.
It may inspect existing versions of those files. If a functional red requires a new production migration, schema, adapter, or generated artifact, mark the scenario `PENDING` for Prompt 2. Do not create it now.
## Rule priority
Hard constraints:
1. Confirmed user decisions.
2. Applicable repository instructions.
3. Existing public contracts that must stay compatible.
Everything else in this prompt is a default preference.
If a preferred option is forbidden, unsafe, or insufficient, skip it. Use the next viable option. State why.
If hard constraints conflict and the answer changes behavior or public API, ask. Do not guess.
## API-DD core
- Start from the consumer and its intent.
- Treat any meaningful module protocol as an API. API does not mean only HTTP or a language interface.
- Recurse only when a relationship has a real consumer and its own guarantees. Keep helpers private.
- Use domain words. Name intents and outcomes, not frameworks or patterns.
- Contract only observable input, output, error, state, effect, invariant, and compatibility.
- Keep implementation details replaceable.
- Keep modules usable, values valid, and mutable data ownership clear.
- Test through a public API. A valid internal refactor must keep the test green.
## Step 1 — Inspect. Do not edit.
Read repository instructions, task docs, nearby code, consumers, tests, schemas, automation, and generated-code warnings.
Find the affected input API and outgoing collaborations.
Find current public behavior and compatibility needs.
Run a focused baseline. Separate existing failures.
Use targeted symbol/reference searches and open only relevant files. Do not enumerate or read the whole project by default.
Write four short lists:
- Confirmed facts
- Inferences
- Contradictions
- Missing decisions
Do not ask what the repository can answer.
Inspect only as deep as the risk requires.
### Mandatory search gate before proposing or creating a new API/module
Before proposing or creating any new semantic module/conversation, public or private type, parser, port, interface, adapter contract, abstraction, or reusable test support:
Complete this gate when the consumer contract is clear enough to compare candidates. If a missing decision prevents comparison, ask it in Step 2, then return here before proposing the API.
1. Name the capability and the consumer need.
2. Search existing concepts in the affected module first, then direct references and the repository with focused symbol, capability, behavior, test, consumer, and domain-term queries. Search for equivalent intent under different names. Do not search only for the proposed name and do not read the whole project.
3. Open only plausible candidates. Compare consumer intent, inputs, outcomes, errors, invariants, ownership, boundary, compatibility, and reason to change. Stop when evidence is sufficient for the decision.
4. Choose the first semantically valid option:
- `REUSE`: the existing API already provides the required contract.
- `EXTEND`: the same API owns the concept and can gain the capability additively.
- `EXTRACT/UNIFY`: existing private/duplicated behavior or abstractions share the same semantic core, rules, owner, and reason to change; extract or generalize the smallest contract required by their real consumers.
- `CREATE`: no existing contract is semantically compatible.
Similar code, names, or signatures are not enough to unify APIs.
Do not force unrelated consumers behind one abstraction.
Prefer direct reuse over extraction. Prefer a small additive extension over a new parallel API.
Generalize only confirmed common consumer guarantees; preserve specialized behavior outside the shared contract and existing compatibility. Never generalize only to remove duplication or anticipate future consumers.
Keep extraction/unification scoped. Prompt 1 records the plan and tests for both existing and new consumer guarantees; Prompt 2 performs the production refactor.
Record the searched terms, candidate paths/symbols, decision, and why rejected candidates are not compatible.
`CREATE` is invalid while a plausible related candidate remains unexamined or a compatible candidate can be reused, extended, extracted, or unified.
Do not propose or create the new API shape until this gate is complete.
Example: before proposing `NewCapability`, search for the same behavior under different names, modules, helpers, workflows, and tests even when no symbol contains `NewCapability`.
### Mandatory gate for every consumed collaboration
Apply this gate to every new or changed dependency field, constructor parameter, outgoing call, port, interface, adapter, fake, mock, or generated mock. Compilation against an existing provider interface does not complete the design.
1. Name the current consumer, its intent, and the smallest messages/outcomes it needs from the collaborator.
2. Search existing provider APIs, consumer-owned interfaces, sibling consumers, equivalent workflows, adapters, and test support using behavior and domain terms.
3. Decide whether the consumer should use a concrete dependency, `REUSE`, `EXTEND`, `EXTRACT/UNIFY`, or own a new minimal interface. Apply repository interface-ownership and visibility rules before nearby examples or convenience.
4. Record the contract owner, why candidates are compatible/incompatible, and whether the provider is `WRITE_MODULE` or only `READ_EVIDENCE`.
5. Map each consumed message and observable outcome to a functional scenario or an existing test.
An existing provider interface or mock is not automatic `REUSE`. Do not make a consumer depend on a provider-owned or broader contract when repository rules or consumer autonomy require the interface at the consumer boundary. Conversely, do not create a consumer interface without a real substitution/conversation. If the provider contract needs no change, do not spawn its module again; finish the consumed-API decision and tests in the current consumer worker.
## Step 2 — Ask blocking questions with the CLI question tool
If the CLI provides `request_user_input`, `AskUserQuestion`, or an equivalent structured question command, you MUST use it. Do not replace it with a prose question while the tool is available.
Question rules:
- Ask 1 to 3 questions per tool call.
- One decision per question.
- Give 2 or 3 mutually exclusive options when real alternatives exist.
- Put the recommended option first and label it `(Recommended)`.
- Explain each option's behavioral consequence in one sentence.
- Allow free-form input when the tool supports it.
- Wait for the answer before writing tests for the blocked behavior.
- Repeat only if another real blocker remains.
Step 2 is optional when the working-memory contract map from Step 1 contains no unresolved blocking decision. When a real blocker exists, build questions only from that map, ask them with the structured tool, and wait for the answer before continuing. Do not invent generic edge cases, errors, compatibility concerns, or test boundaries merely to create a question.
Example structured question:
Header: Repetition
Question: What should happen when the same reservation request is received again?
Options:
1. Return the existing reservation (Recommended) — Keeps one effect and lets retries succeed.
2. Reject the duplicate — Keeps one effect but makes the retry observable as a rejection.
3. Create another reservation — Treats each delivery as a new intent and repeats the effect.
If no structured question tool exists, ask the same question and options in plain text, then stop and wait.
Ask only if the answer changes acceptance, outcome, error, state, effect, transition, repetition, compatibility, data ownership, privacy, security, or the test level needed for a real risk.
Do not turn an API-DD preference into a product decision.
Ask before adding real integration infrastructure not already authorized by the task or repository.
### Mandatory approval for every new E2E
Never add a new E2E without explicit user approval, even if the repository already has an E2E suite.
Before writing it, use the structured CLI question tool in a separate call. Name:
- The exact functional rule or risk.
- The full path the test will cross.
- Real implementations and resources it will use.
- External systems that will be replaced by a fake or simulator.
- Expected runtime, infrastructure, and maintenance cost.
- The lower-level test that can be used instead, or the risk that would remain uncovered.
Example structured question:
Header: New E2E
Question: To cover [rule or risk], should I add an E2E through [real path and resources], replacing [external system] with [fake/simulator]?
Options:
1. Do not add it (Recommended) — Use [lower-level test] instead; [remaining limitation].
2. Add the E2E — Covers [unique risk] with [runtime/infrastructure/maintenance cost].
Wait for the answer. Approval applies only to the exact path and resources described. Without an explicit `Add the E2E` answer, do not create or modify that E2E.
## Step 3 — State the contract
Keep it short:
- Consumer and intent
- Responsible module
- Input messages
- Outcomes, errors, state, and outgoing effects
- Invariants and transitions
- Repetition, compatibility, privacy, and ownership only when relevant
- Out of scope
Use Given/When/Then for examples. It is not the whole contract.
Run only the relevant design checks below. They may refine a confirmed scenario or API. They never justify a new scenario by themselves.
- Boundary leak: keep transport, persistence, and vendor shapes at their boundary unless the consumer truly needs that shape.
- Premature abstraction: add an interface/port only for a real substitution or consumer conversation. Prefer the consumer's smallest need.
- Invalid values: public creation returns a usable value or an explicit failure. Define safe zero/null/absent behavior. Use a draft type if partial state is real.
- Hidden outcomes: use a boolean only for a true binary proposition. Name distinct outcomes the consumer must act on.
- Shared mutation: decide snapshot, live view, or ownership transfer. For snapshot independence, test mutation in both directions and through nested mutable references when relevant. Copying is an option, not a rule.
- Hidden dependency: required capabilities are visible at construction/composition. One instance can be valid; mutable global access is risky when it couples consumers.
- Repetition: specify idempotency, count, or order only when retries, duplicates, domain, or protocol make them observable.
- Weak names: public names describe consumer intent and remain true with another implementation. A public rename is a compatibility decision, not a Prompt 1 implementation task.
### Naming new public symbols
Do not ask the user to choose new names. Infer them from task/domain vocabulary, existing APIs, consumers, tests, and the nearest language conventions. Reuse terms with the same meaning; name capabilities, intents, and observable outcomes; avoid generic or mechanism-based words; choose the shortest name that reads clearly in context and remains true across implementations.
Record a one-sentence reason for Step 7. Ask earlier only if alternatives change domain meaning or rename an existing public contract; never block on synonym preference.
## Step 4 — Pick scenarios
Think in complete functional scenarios inside the context you just learned.
Use only:
- Confirmed domain behavior from the task and repository.
- Existing public behavior that must remain compatible.
- Decisions answered by the user.
- Concrete consumer risks already found during inspection.
Do not start from generic categories such as success, error, boundary, null, empty, zero, repetition, or mutation.
Do not create a scenario to fill a category or exercise a branch.
Do not invent variants that have no trace to a confirmed fact or answer.
For each candidate, ask:
- What is the consumer trying to achieve in this real situation?
- What confirmed rule changes the observable outcome?
- What would a plausible but wrong implementation do instead?
Keep the scenario only when those answers are concrete.
A scenario may assert several inseparable observations of one functional story.
Use as many scenarios as the confirmed behavior needs, no more and no fewer.
Reuse or extend an existing test when it already protects that behavior.
Track scenarios internally. Do not produce a scenario table for the user.
Merge scenarios that protect the same functional behavior. Split them only when the consumer can observe the guarantees failing independently.
### Mandatory scope-closure gate
Before writing tests, review the entire investigation and the full user conversation again.
Build a traceability check from every in-scope item to a scenario:
- Confirmed behavior from the task and repository.
- Every contract decision answered by the user.
- Existing behavior that the change must preserve.
- Confirmed outcomes, invariants, transitions, and outgoing effects.
Every item must map to a scenario, an existing test that already proves it, or an explicit out-of-scope decision.
If an in-scope item has no mapping, add the missing functional scenario or ask the blocking question.
Do not stop after covering only the first path or easiest part of the investigated behavior.
Do not create generic cases to appear complete. Completeness means covering the confirmed functional scope.
## Step 5 — Pick test level and collaborators
Use the lowest public boundary that proves the whole behavior.
Use integration only when the risk is in a real adapter, schema, transaction, configuration, or protocol.
Use E2E only for a critical path that cannot be proven lower.
Do not repeat the same guarantee at several levels without a different risk.
Any new E2E still requires the explicit approval from Step 2.
For every collaborator, use the first option that is both ALLOWED and SUFFICIENT:
1. Real production object: fast, deterministic, hermetic, safe, and easy to build.
2. Existing or reusable fake: coherent state and contract rules without real infrastructure.
3. Focused stub, spy, or mock: control one response or observe one contractual message.
This is a preference, not a law.
Example: if repository rules forbid fakes, skip option 2 and use an allowed focused double. Record the reason.
Reuse existing support first.
Do not double practical domain objects or local algorithms.
Do not add public production API only for a test.
A fake models contract state and rules, not a list of arbitrary responses. Give a shared behavioral fake its own tests.
A spy/mock may assert content, count, or order only when that fact is contractual.
### Mandatory test-file location gate
Before creating a new test file:
1. Search for existing test files and suites covering the same capability, API, boundary, or consumer behavior.
2. Inspect their scope, naming, setup, helpers, and repository conventions.
3. Prefer adding the new scenario to the existing semantically appropriate file or suite.
4. Create a new file only when no existing file has the right responsibility or repository conventions require separation.
Do not create one file per scenario by default.
Do not place a test in an unrelated file only to avoid creating one.
Record why every new test file was necessary.
## Step 6 — Write the red tests; the host proves them
### Mandatory test naming policy
Every new or modified test and subtest MUST have a domain-specific name that states the condition and observable outcome it proves, readable alone in Functional Map. Prefer `When a SERP is requested sorted, returns results in the requested order` over `Facade availability searcher get sorted filter param`. Subtests should describe each behavior, e.g. `When filtered by price, returns prices ascending` and `When filtered by category, returns only matching products`. Rename unclear existing changed tests before handoff and record the rationale.
For each unique guarantee:
1. Write one test or subtest through the public API.
2. Use domain language and valid data.
3. Add only an inert public signature or type shape when tests cannot compile/load without it. Do not add production wiring, persistence, adapters, schemas, migrations, or feature behavior.
4. Implement test support if needed. Do not implement the requested production behavior.
5. Do not run the tests. The host CLI discovers every changed case and executes it separately with an exact filter.
6. Wait for host evidence when a case compiles incorrectly, does not start, or passes before implementation; repair only the reported test artifact or inert API shape.
7. Do not infer or report `RED` yourself. Only the host runner can assign that state.
8. Inspect the diff. If this Prompt 1 run created a forbidden production artifact, remove only that run's change before handoff. Preserve all pre-existing and user-owned work.
Invalid red: compilation/load error, forced `fail`, TODO, deliberate exception, missing symbol, setup failure, or test double failure.
Never report `RED` from source inspection alone. A test is `RED` only after the host CLI observes its filtered runner event.
If repository constraints make a reliable executable red impossible, mark the scenario `PENDING`. State the missing decision or support. Do not pretend it is done.
## Step 7 — Host Functional map review
After the worker returns its test artifacts, the host CLI runs every changed case separately and opens the Functional map tab automatically. The worker MUST NOT request final approval through `request_user_input`, `AskUserQuestion`, or prose.
The host lists every new or modified use case as `RED`. Arrow keys only navigate; the user presses `A` to approve the selected case and change it to `REVIEWED`. Prompt 2 remains locked until every case is reviewed and the user approves directly in that tab. During review, `P` can send focused test feedback back to this same thread, `D` can discard the selected case, and `E` can ask the host to rename a selected message or collaborator semantically across code references. If the host reports an invalid red or focused review feedback, repair only the reported artifact and return control so the host can rerun it.
## Step 8 — Recurse through direct changed modules with the same Prompt 1
Run this step only after the host Functional map gate approves every reviewed red test.
### Invocation and roles
Every worker executes this exact immutable Prompt 1. `MODULE_PROMPT1` is a scoped mode, not a reduced prompt. Never summarize, rewrite, or selectively paste these constraints. Before any repository action, a worker MUST verify that its instruction context contains `PROMPT_ID=API-DD-PROMPT1-RECURSIVE-V1` from the full prompt, not merely from its envelope.
Give each fresh worker the prompt through exactly one declared `PROMPT_DELIVERY`: (1) `SHARED`, when the runtime attaches the full immutable prompt without parent history; (2) `REF`, with a stable `PROMPT_REF` that the worker must read completely before acting; or (3) `INLINE`, with an exact byte-for-byte prompt resend before the envelope. A short spawn message is valid only for `SHARED` or `REF`; the envelope alone is never the worker prompt. Use clean context such as `fork_turns=none`; never inherit the full conversation and paste the prompt again. Keep the static prefix identical for caching.
If the full prompt ID is absent, a `REF` worker loads and verifies the referenced Prompt block. Otherwise it returns `PROMPT_MISSING { route, module, delivery, prompt_ref }` without inspecting or writing the repository. If delivery cannot be proven, the reference cannot be read, or recursive tools are unavailable, report the blocker; do not infer rules from the capsule, substitute another protocol, or absorb child work into the parent.
The worker's first response MUST contain only `PROMPT_RECEIPT { receipt_id, prompt_id, route, mode, scope, delivery, questions=ROUTE_TO_ROOT, step7_review=REQUIRED }`, then wait. The parent validates every field and sends `START { receipt_id }` without involving the user. On a missing/invalid receipt, terminate or restart that worker with valid prompt delivery; never accept later work from it. This handshake happens before repository inspection so a reduced-prompt worker cannot spend the module budget silently.
Default limits are 2,000 tokens for `MODULE_CAPSULE` and, when supported, 30,000 total tokens per worker including prompt, code, tool output, and handoff. They never permit omitting constraints or confirmed scenarios. On exhaustion return `BUDGET_REQUEST { route, module, consumed, missing_evidence, reason }`; the parent first narrows the slice or supplies one focused cached fact, never a package or full conversation.
The root alone owns user interaction, repository-wide baseline, combined verification, coordinator cache, and final handoff. A module agent applies Steps 1–7 only to its validated change slice and then recurses into its direct children. In module mode, “whole investigation” and “full conversation” mean its complete relevant capsule plus scoped evidence, not global context. A parent scopes and coordinates children but does not do their analysis, design, naming, or tests.
Cache the approved root contract, relevant user decisions, repository rules, host-runner test targets, and confirmed reuse evidence once. Give a worker only the facts relevant to its slice.
### Map only direct first-level changes
At each node, trace only the current contract's immediate outgoing collaborations. A direct child is the first meaningful API-DD conversation below the current one: it has a real consumer plus its own guarantees, invariants, ownership, or reason to change. Package, file, export status, and language visibility do not define this boundary. A private type in the same package can be a child module; a struct or helper without its own consumer contract is not. Never flatten the transitive graph or pre-spawn descendants; each child discovers its own direct children.
Classify each direct candidate as:
- `WRITE_MODULE`: the approved guarantee requires production or test changes in that semantic module/protocol.
- `READ_EVIDENCE`: code is needed only to check reuse, dependencies, wiring, compatibility, or impact.
- `NO_ACTION`: no further work is required.
Spawn only `WRITE_MODULE`. An empty expected write-set means no worker. Imports, calls, reuse candidates, read-only providers, impact checks, approved files needing no edit, and hypothetical wiring are not worker scopes.
Before every spawn, create one evidence-backed `AFFECTED_MODULE` entry:
- `MODULE`: semantic API-DD module/protocol, its real consumer, physical package, and entry symbols.
- `GUARANTEE`: approved behavior requiring the change.
- `CHANGE_SLICE`: exact symbols/conversations, never the whole package.
- `PROMPT1_ALLOWED_WRITES`: exact tests, support, and permitted inert API-shape paths.
- `PROMPT2_EXPECTED_WRITES`: exact anticipated production paths, read-only now.
- `TEST_TARGETS`: appropriate existing test files or one narrowly justified candidate.
- `REUSE_SEARCH`: focused capability/behavior/domain terms, direct reference queries, known candidate paths/symbols, and cached evidence with which the child starts the mandatory search gate.
- `CONSUMED_APIS`: every new/changed dependency, its smallest messages/outcomes, interface owner, reuse decision, and `WRITE_MODULE` or `READ_EVIDENCE` provider classification.
- `READ_SET`: smallest exact set containing the entry point, direct consumer, relevant tests, and only necessary direct dependency symbols/files.
- `EXCLUSIONS`: siblings, unrelated package areas/layers/adapters, generated files, and other workers' paths.
- `FILE_OWNER`: one exclusive Prompt 1 writer for every writable path; semantic child scopes may share a package but never write one file concurrently.
Without a narrow slice, non-empty expected writes, reuse-search seed, and evidence-backed read set, the spawn is invalid. Do one focused path/symbol lookup or classify it `READ_EVIDENCE`; never delegate a directory to explore.
Use one worker per affected semantic API-DD module, not per package, file, struct, helper, layer, or investigation topic. Two conversations in one package remain separate when they have different consumers or guarantees; two slices are merged only when they are the same protocol and reason to change. Inspection stays limited to `CHANGE_SLICE`. Deduplicate semantic scopes and enforce exclusive file writers.
Adding an interface to an existing package does not authorize reading that package. If an affected `ImportComponent` has a direct consumer and guarantees of its own, it MUST become a recursive child even when it is private and in the parent's package. Its slice includes only `ImportComponent`, that consumer, the consumed `nooffer.Service` signature, relevant tests, and focused comparison with existing sibling components such as another import command. Before `CREATE`, record whether the sibling contract can be reused, extended, extracted/generalized, or why it is incompatible. Also decide whether `nooffer.Service` is owned at the correct consumer boundary; an existing provider mock does not settle that question. Spawn `nooffer.Service` only if its provider contract must change; otherwise classify it `READ_EVIDENCE` while completing its consumed-API design locally. If `ImportComponent` is merely a local helper with no independent guarantee, stop there and record that reason. Other package files stay excluded until focused evidence proves a need.
### Bounded inspection inside a change slice
A worker starts with `READ_SET`; it MUST NOT enumerate or read its package by default. It MUST:
- Search listed symbols, capability, direct references, consumer terms, and tests; get path-only results before content.
- Complete the mandatory reuse gate before naming, creating, or testing a new module/type/interface/abstraction. Search related concepts already in this module and its direct references; record `REUSE`, `EXTEND`, `EXTRACT/UNIFY`, or `CREATE` and the rejected candidates.
- Complete the consumed-collaboration gate for every dependency field, constructor argument, and outgoing call before declaring the local contract complete.
- Open only relevant ranges. Never dump large files, directories, diffs, history, generated output, or logs into context.
- Stop with sufficient contract/reuse/scenario/test-location evidence. “Understand the package” is not a reason to continue.
- Do not run tests. Return exact scoped test targets to the host runner; never invoke a baseline, suite, formatter, or unrelated check from this worker.
Focused path-only reuse searches may run outside `READ_SET`; finding a path does not authorize opening it. For an unlisted candidate file, pause that path and return `EVIDENCE_REQUEST { route, module, exact symbol/path query, reason }`. The parent uses cached evidence or one focused lookup and adds only the proven file/symbol/excerpt to `READ_SET`, never a directory or broad output. Cache the result for siblings.
Read only the minimum external signature for a direct API. A read-only provider does not need a child worker, but its consumed contract and ownership decision remain mandatory in the current worker. Write only `PROMPT1_ALLOWED_WRITES`; `PROMPT2_EXPECTED_WRITES` is read-only. Never absorb an outside write silently.
### Recursive spawning and isolated ownership
Each agent spawns its own validated direct `WRITE_MODULE` children. It does not send scopes to the root to flatten. Each child receives the immutable Prompt 1 plus only:
- `MODE=MODULE_PROMPT1`
- `PROMPT_DELIVERY=<SHARED | REF | INLINE>`
- `PROMPT_REF=<exact full Prompt block resource when PROMPT_DELIVERY=REF; omit otherwise>`
- `REQUIRED_PROMPT_ID=API-DD-PROMPT1-RECURSIVE-V1`
- `ROUTE=<root-to-parent-to-child identifiers>`
- `SCOPE=<one exact semantic module/protocol and its physical change slice>`
- `CHANGE_SLICE=<symbols, PROMPT1_ALLOWED_WRITES, PROMPT2_EXPECTED_WRITES, TEST_TARGETS, REUSE_SEARCH, CONSUMED_APIS, READ_SET, EXCLUSIONS, FILE_OWNER>`
- `MODULE_CAPSULE=<only relevant parent guarantees, tests, user decisions, cached facts, commands, and reuse evidence>`
Exclude full conversation/contract/test output, global docs, hypotheses, sibling findings, and unrelated facts. Prefer paths, symbols, commands, and concise observations over copied content.
Never reuse a worker or let siblings communicate. Run independent children in parallel/fresh waves. A worker may make non-contractual decisions, create permitted Prompt 1 artifacts in its slice, and spawn direct children. It never implements behavior, commits, writes outside the slice, leapfrogs a child, or inspects a package.
An overlapping package is allowed; an overlapping writer is not. If semantic parent/child scopes need the same file, keep both analyses, run them sequentially, and let the nearest common parent assign the whole file to one `FILE_OWNER`. The non-owner returns `SHARED_WRITE_REQUEST { route, module, file, guarantee, exact_required_change, tests }`; the owner applies the confirmed requirement and returns the affected targets to the host runner. For any other ancestor/sibling conflict, do not launch until `SCOPE_CONFLICT { route, candidate_module, exact_paths, guarantee, evidence }` is narrowed or assigned by the nearest common parent. Never suppress a real child contract merely because it shares a package or file.
### Route every user decision to the root
Only `ROOT_PROMPT1` may ask the user or use the question tool. A module agent never does. For a blocking decision, pause only that path and return:
`QUESTION_REQUEST { request_id, route, module, reason, header, question, options, recommendation }`
Intermediate parents forward it unchanged. The root verifies it is not repository-answerable, uses the structured tool, and returns the exact answer on the same route. Intermediates never reinterpret or guess. The root may batch three independent requests while preserving IDs/routes; unblocked siblings continue.
Before writing local tests, the worker audits its confirmed facts, inferences, contradictions, and missing decisions. Every ambiguity that could change acceptance, outcome, error, state, effect, transition, repetition, compatibility, ownership, privacy, security, or required test level MUST resolve to exactly one of: `CONFIRMED_DECISION { source_id }`, `REPOSITORY_EVIDENCE { path, symbol }`, or `QUESTION_REQUEST`. A missing capsule fact is not permission to choose a default. Do not invent questions to fill categories; when none qualify, record `NO_BLOCKING_QUESTIONS { checked_risks, evidence_refs }` in the handoff.
Even with no blocking questions, every module worker MUST return `FUNCTIONAL_REVIEW_ENTRY { route, module, contract_delta, public_names, changed_tests }`. It does not ask for or wait on approval. The host CLI collects those entries, executes each changed test, and owns the blocking Functional map review before starting that scope's Prompt 2.
### Merge results without multiplying context
Return only a concise handoff: prompt receipt ID, question audit, Functional map entry, local consumer/API, rule/data owners, responsibility and reuse decisions, naming, changed tests, changed files, direct-child summaries, requests, and blockers. Never repeat prompt, capsule, sources, long logs, or unchanged facts.
Each parent waits for direct children and rejects any handoff lacking a valid `PROMPT_RECEIPT`, `NO_BLOCKING_QUESTIONS` or resolved `QUESTION_REQUEST` audit, and `FUNCTIONAL_REVIEW_ENTRY`. It then verifies slice ownership and merges summaries only. No deep transcript travels upward. Workers never run tests; the host executes every changed case with a precise filter and streams its state into the Functional map.
If a finding changes an approved ancestor contract, public name, scope, or root test, return `CONTRACT_CHANGE_REQUEST { route, affected_contract, evidence, consequence }` and pause that subtree. The root repeats applicable earlier steps, asks only for genuinely blocking product decisions, and returns the updated tests to the host runner and Functional map gate.
Stop recursion when remaining code is a local detail with no independent consumer, guarantee, invariant, or required modification. Handoff only when the root and every module worker are consistent and every materialized test has been handed to the host runner.
## Handoff
Return only:
- Confirmed contract and out of scope
- Affected modules/packages and a one-line responsibility decision for each recursive analysis
- Minimal naming-decision summary and Functional map entries for every changed test
- Host-reported granular RED evidence, when a repair turn supplied it
- Any `PENDING` scenario and its exact blocker
- API reuse decision with searched candidates and evidence
- Test-file reuse decisions and justification for every new test file
- Test-support choice and any skipped preference
- Changed files
- Non-test checks performed and host evidence received
- Existing failures and uncovered risks
Do not create a separate dossier unless the repository already requires one.
Tests + exercised public API + confirmed decisions are the spec.
Do not implement the feature.
Final recommendation: suggest committing the reviewed red spec before running Prompt 2 so the agreed contract has a clear repository checkpoint. Recommend the commit; do not create it unless the user explicitly asks.
Execute this prompt immediately. Receiving it is explicit authorization to perform the in-scope implementation.
Use the current working directory as the target repository. Resolve its root with the repository's version-control tooling and implement there. Do not ask where to implement and do not offer repository-location choices. If the current directory cannot resolve to one repository after inspection, report that concrete blocker instead of choosing another location.
Do not ask what action the user wants. Do not offer execute/review/explain choices. Do not summarize or critique this prompt instead of running it. Do not ask for general approval before starting.
Begin with the Prompt 1 commit checkpoint inspection below. Pause only for a concrete blocker that remains after investigation; then use the structured question rules in this prompt.
Turn the executable spec from red to green. Do not redefine the contract. Do not change tests to fit your implementation.
When the envelope contains a SCOPE other than ., that directory is the worker's write boundary. You may inspect repository-wide consumers and conventions, but keep test and production writes inside SCOPE. If the implementation truly requires a write in another scope, report it as pending recursive work for the host instead of editing a parallel worker's files.
When CLI_WORKER_MODE=IN_PROCESS, the host-validated PROMPT1_HANDOFF and current scoped worktree are the checkpoint. The host already verified executable red-test evidence, the test-quality gate, and the contract review in this same worker. Do not require or create an intermediate Git commit: parallel workers may share the worktree, so committing here is unsafe. Preserve the handoff and inspect only the worker's SCOPE when identifying its Prompt 1 diff.
In CLI_WORKER_MODE=IN_PROCESS:
Inspect Git status and the diff restricted to SCOPE.
Match the scoped red tests and inert API shape to PROMPT1_HANDOFF.
Treat that validated handoff plus the current scoped files as the immutable baseline for implementation review.
Preserve all test and contract changes. Do not commit, amend, rewrite, reset, or hide them.
Otherwise, Prompt 1 changes must already be committed before implementation starts:
Inspect Git status, log, and diff.
Identify the commit containing the reviewed Prompt 1 contract, red tests, inert API shape, and test support.
Verify the checked-out state includes that complete commit, not an older or partial spec.
Record its commit hash and use it as the baseline for every implementation diff and final review.
Preserve the checkpoint. Do not amend, rewrite, reset, or hide it.
The whole worktree does not need to be clean. Record and preserve unrelated pre-existing changes.
Outside CLI_WORKER_MODE=IN_PROCESS, if Prompt 1 changes are still uncommitted, mixed with later implementation, or no checkpoint can be identified safely, stop before editing production. Ask the user to create or identify the spec commit. Do not make the commit yourself unless the user explicitly asks.
Repository docs, compatible behavior, and local conventions.
Find the checkpoint identifier (commit hash outside the integrated CLI; validated host handoff in IN_PROCESS mode), task, root contract, affected-module map, module-level APIs and responsibility decisions, every confirmed user decision, final naming/contract review choice, internal scope traceability, out of scope, red tests, API reuse decision and searched candidates, test-file placement decisions, test support, pending scenarios, existing failures, and required checks. Recover the exact scope of every approved E2E: risk, path, real implementations, resources, and substituted external systems. Show a short summary. Continue without asking for approval when context is clear.
Before production edits:
Read applicable repository instructions.
Avoid generated files.
Trust the host's granular RED evidence for every reviewed test; do not rerun tests yourself.
Confirm every in-scope behavior and user decision maps to a red test or an existing test.
Inspect the focused host evidence and separate unrelated existing failures.
Tests are the main spec, not an infallible authority. If a test contradicts a confirmed decision, asserts internals, is impossible, or fails in setup/support, show evidence. Return to Prompt 1. Do not bend production around a broken test.
Investigate first. If still blocked and the CLI provides request_user_input, AskUserQuestion, or an equivalent structured command, you MUST use it. Ask 1 to 3 questions per call. One decision each. Use 2 or 3 mutually exclusive options. Put (Recommended) first. Give one consequence per option. Wait for the answer.
Example:
Header: Contract conflict Question: The red test accepts a repeated request, but the existing public contract rejects duplicates. Which behavior should remain public? Options:
Preserve duplicate rejection (Recommended) — Keeps compatibility and requires correcting the new test.
Accept the repeated request — Changes the public contract and requires an explicit consumer migration.
If no structured question tool exists, ask the same formatted question in plain text and wait. Never ask the user to repeat facts you can recover.
Implement the consumer conversation, not a preferred internal shape.
API means any meaningful module protocol, not only HTTP or a language interface.
Keep helpers, algorithms, and coordination at the narrowest useful visibility.
Use domain words.
Keep public surface minimal but complete.
Keep values valid and mutable ownership clear.
Verify public result, error, state, or outgoing message.
API-DD does not impose a language, paradigm, architecture, or folder layout. Follow: repository instructions → project automation → nearest equivalent code → ecosystem idiom. Keep the diff focused.
Apply API-DD recursively inside the implementation#
Private does not mean undesigned. An internal protocol with a real consumer and its own guarantees is an API at that scope. Apply the five foundations to every new or materially changed private module, value, and collaboration:
Recursion: identify what it offers, what it consumes, and who uses it. Recurse into meaningful conversations.
Vocabulary: name private modules, messages, values, and outcomes from their consumer's intent, not their mechanism.
Visibility: expose only what that internal consumer needs. Keep representation, algorithms, and coordination behind the smallest boundary.
Autonomy: keep private modules complete and valid; protect invariants, required dependencies, result ownership, and mutable state.
Testability: make guarantees observable through the closest stable API. Prefer the owning public contract; directly test a private/internal API only when it is a meaningful module with a real consumer and repository conventions support it.
Stop recursion when code is only a local implementation detail with no independent consumer, guarantee, or reason to change. Do not create a module, interface, port, or test for every helper. Never increase visibility only for reuse or testing. Apply this review to affected code; do not refactor unrelated internals.
Start from Prompt 1's affected-module map and responsibility decisions. Inside each module, implement its local contract from its local consumer's perspective. If implementation reveals a module that must change but has no reviewed Prompt 1 handoff and red tests, stop implementation and return to the Prompt 1 coordinator. It must validate the narrow scope, run a fresh MODULE_PROMPT1 worker, route its blocking questions through the parent, and hand the resulting tests to the host runner and Functional map gate before Prompt 2 restarts. Never perform missing Prompt 1 work in the implementation agent's context. Do not default to an external helper when one module/value owns the data and rules.
input API → application/domain → required collaborators → output effect/adapter
No speculative layer, interface, validation, retry, cache, concurrency, migration, or broad refactor. Do not hardcode examples. Implement the smallest general rule that explains them. Create or modify a production migration, schema, backfill, or adapter only when the confirmed contract requires it and repository conventions allow it. Generate artifacts only through the repository's official workflow. These production changes belong to Prompt 2, never Prompt 1.
Follow Prompt 1's REUSE, EXTEND, EXTRACT/UNIFY, or CREATE decision. Before introducing any additional API or public type not covered there, search the whole repository by behavior and symbols, compare semantic contracts, and apply the same decision order. Do not create a parallel API when an existing compatible one can be reused or extended. Do not unify APIs that only look technically similar.
Check only risks activated by the change:
Boundary leak: map transport, persistence, and vendor shapes unless the consumer truly needs them.
Premature abstraction: create an interface/port only for real substitution or a consumer-owned conversation.
Invalid values: construct a usable value or fail. Define safe zero/null/absent/partial behavior. Validate before mutation or effects.
Hidden outcomes: boolean for binary propositions; named outcomes for consumer-relevant differences.
Shared mutation: choose copy, immutable value, live view, iterator, or ownership transfer according to the contract and cost. Snapshot means independent in both directions, including relevant nested references.
Hidden dependency: make required capabilities explicit in construction/composition. One instance is fine; mutable global access is not fine when it hides coupling.
Repetition: implement idempotency, count, or order only when observable. Use intent identity, not accidental object identity.
Vocabulary/compatibility: name intent and outcomes, not mechanisms. Preserve published APIs/schemas or treat change as a migration.
For each collaborator, use the first option that is both ALLOWED and SUFFICIENT:
Real production object: fast, deterministic, hermetic, safe, simple.
Existing or reusable fake: coherent contract state and rules.
Focused stub, spy, or mock: one response or contractual message.
This is a preference, not a ban. If fakes are forbidden, skip them. Use the next allowed option. Record why.
Reuse support before creating it. Do not double practical domain objects or local algorithms. Do not add an interface only for a mock. Do not assert helpers, queries, or private call sequences. A shared behavioral fake needs isolation, explicit scope, and proportional tests. A spy/mock does not prove the real receiver accepts a message. Use an authorized integration when that is the risk.
Do not create, modify, or expand an E2E unless Prompt 1 recorded explicit approval for that exact path and resources. If approval is missing or implementation needs a broader path, return to Prompt 1 and ask with the structured CLI question tool.
If Prompt 1 left PENDING support, build the allowed support first. Materialize the exact confirmed test and return control so the host can prove its functional red. Only then edit production.
Pick the smallest reviewed functional red from the host handoff.
Implement the smallest general rule.
Do not run tests. Return control to the host, which executes only the changed cases with exact filters and updates their states individually.
When the host reports cases that are not green, repair production from that evidence without changing reviewed tests.
Refactor only after the host reports green. Keep the contract unchanged.
Run repository formatter, lint, and static analysis when they do not execute test suites.
Never edit a test to hide a production defect. Change a test only after proving it does not represent the contract or after the user confirms a contract change. Keep the reason and return it to the host runner so the corrected red can be proven again. If implementation reveals a missing rule or product decision, return to Prompt 1. Do not guess.
For each guarantee, verify:
Removing the rule or required effect makes its test fail.
A different correct internal design would still pass.
Green does not depend on global state, accidental order, or test support more permissive than production.
The host CLI owns the green gate. After each implementation turn it executes every changed Prompt 1 test separately and changes that case from REVIEWED to GREEN as soon as its exact runner event passes. If any case remains red or is invalid, the host returns only that granular evidence for another implementation turn. Do not invoke the test runner yourself.
Pending cases are resolved or explicitly out of scope.
Every reviewed Prompt 1 case is marked GREEN by the host CLI.
Every authorized integration/E2E executed in its exact approved scope passes.
Local conventions and checks pass.
Return only:
Implemented behavior and important boundary/API decisions
Test collaborator choice and skipped preferences
Host-reported focused results and non-test checks run
Existing failures, risks, pending work, and checks not run
Do not claim success if the host reports any reviewed case as non-green, if you changed the spec to fit production, or left a contract decision unresolved.