Buscar/Modificar un depurador de bajo nivel escribiendo primero las pruebas, sin romper otras plataformas

Modificar un depurador de bajo nivel escribiendo primero las pruebas, sin romper otras plataformas

Úsalo cuando una IA modifica un depurador de un lenguaje compilado que funciona en varios sistemas y backends. La IA escribe antes una prueba que falla, aísla el código de cada sistema, revisa todos los backends al cambiar interfaces compartidas y empieza los commits con el subsistema.

Para usar estas reglas de IA
## Leer la capa responsable de un comportamiento antes de editar el depurador Un depurador para un lenguaje compilado es un sistema complejo y multicapa. Trabajar en él de forma efectiva requiere comprender los mecanismos internos de depuración, los formatos binarios de información de depuración, el control de procesos específico del sistema operativo y los mecanismos internos del runtime del lenguaje. Trata cada cambio como algo que afecta a un sistema donde la corrección entre plataformas y backends importa más que la velocidad bruta. Un error sutil en el acceso a memoria, el manejo de registros o el comportamiento del backend se manifiesta como un fallo difícil de reproducir en una sola plataforma. Antes de editar, lee la capa circundante para entender qué abstracción es responsable del comportamiento que estás modificando. Los cambios en interfaces compartidas se propagan hacia afuera a todos los backends que las implementan, así que delimita el radio de impacto antes de empezar. ## Escribir una prueba que falle antes de cambiar el código y nunca saltarse pruebas Todos los cambios de código deben seguir el desarrollo guiado por pruebas (TDD). Esto no es opcional. Sigue el ciclo Rojo-Verde-Refactorizar-Verificar: 1. **ROJO** - escribe primero una prueba que falle; debe fallar por la razón correcta. 2. **VERDE** - escribe el código mínimo necesario para que pase. 3. **REFACTORIZAR** - limpia el código manteniendo las pruebas en verde. 4. **VERIFICAR** - ejecuta la suite completa de pruebas antes de hacer commit. Reglas obligatorias: 1. Nunca escribas código de implementación sin antes tener una prueba que falle. 2. Nunca hagas commit de código sin pruebas. 3. Nunca omitas pruebas ni las marques como omitidas para que el CI pase. 4. Nunca deshabilites pruebas existentes - corrige la prueba o corrige el código. 5. Cada prueba verifica un comportamiento específico. 6. Los nombres de las pruebas describen el escenario que se está probando. Cuando escribir una prueba primero resulte difícil, esa dificultad es una señal, no una excusa. Normalmente significa que una función está haciendo demasiado, que las dependencias están fuertemente acopladas o que los requisitos no están claros. No te saltes el TDD - corrige el problema de diseño que ha puesto al descubierto. ## Decidir dónde va cada tipo de prueba y lograr que den siempre el mismo resultado Dónde deben ubicarse las pruebas: - **Pruebas unitarias**: colócalas junto al código que cubren. - **Pruebas de integración**: un directorio dedicado de pruebas de extremo a extremo. - **Pruebas específicas de plataforma**: protégelas con etiquetas de compilación / compilación condicional. - **Pruebas específicas de backend**: ejercita cada backend por separado. - **Fixtures**: mantenlas como código fuente que se compila durante la ejecución de las pruebas, no como binarios precompilados; usa el ayudante compartido de localización de fixtures en lugar de programar tu propia búsqueda de rutas. Estándares de calidad: - Prueba el comportamiento, no la implementación. - Prefiere una sola aserción por prueba, para que un fallo señale una única causa. - Nombra con claridad, usando un patrón separado por guiones bajos que indique qué se prueba, el escenario y el resultado esperado. - Reutiliza las utilidades de prueba existentes en lugar de reinventarlas. - Las pruebas deben ser deterministas - sin valores aleatorios, sin condiciones de carrera. - En las pruebas de formato de salida, verifica la salida esperada completa en lugar de una subcadena. La comparación de la salida completa detecta regresiones - espacios en blanco adicionales, campos reordenados, cambios de formato - que las comprobaciones de subcadena pasan por alto en silencio. ## Separar entre capas la interfaz de solo lectura y la que cambia el estado El sistema está organizado en capas con una separación clara de responsabilidades: una CLI en la parte superior, una capa de servicio/protocolo debajo de ella, operaciones de alto nivel por debajo de esa, una abstracción de proceso/objetivo bajo esas capas, y en la base implementaciones de backend intercambiables (control directo de procesos a nivel de sistema operativo, un cliente de protocolo remoto, análisis de volcados fuera de línea, trazado a nivel de kernel). Usa un diseño de interfaz de dos niveles en el límite de la abstracción: - Una interfaz pública de solo lectura que los llamadores usan para observar el estado. - Una interfaz interna separada que contiene todas las operaciones que modifican el estado. - Un objeto contenedor con estado alrededor de la interfaz interna, más una abstracción de grupo para gestionar varios objetivos a la vez. Los backends son intercambiables detrás de la interfaz interna. Cuando modifiques esa interfaz, el cambio normalmente se aplica a TODAS las implementaciones - ten en cuenta cada backend, no solo el que tienes delante. ## Poner el código de cada sistema operativo y arquitectura en sus propios archivos Aísla el código específico de sistema operativo y arquitectura mediante compilación condicional basada en nombres de archivo - archivos fuente por sistema operativo y archivos fuente por arquitectura, seleccionados por el sistema de compilación en lugar de mediante ramificación en tiempo de ejecución. - Nunca pongas código específico de plataforma en archivos genéricos. - Confina el código específico de plataforma a los backends que realmente lo necesitan (el backend de control directo del sistema operativo y el backend de protocolo remoto), no a la lógica central compartida. - Mantén los detalles de registros, ABI y convenciones de llamada en archivos específicos por arquitectura cuando se admiten muchas arquitecturas. Bien hecho, el núcleo compartido se mantiene portable y añadir una nueva plataforma se convierte en cuestión de añadir archivos, no de reescribir código genérico. ## Evitar errores comunes al leer la memoria del proceso y poner puntos de interrupción 1. **No modifiques el estado compartido directamente** - pasa por los métodos de la interfaz interna que proporcionan bloqueo y consistencia del estado. 2. **Las operaciones específicas de backend difieren** - una acción conceptual (por ejemplo, insertar un punto de interrupción) se implementa de forma distinta en cada backend; nunca asumas un único mecanismo. 3. **Distingue entre abstracciones similares** - un hilo del sistema operativo y una unidad de concurrencia a nivel de lenguaje no son lo mismo; sabe a cuál de los dos apunta una operación. La mayoría de las operaciones actúan sobre la unidad a nivel de lenguaje, no sobre el hilo en bruto. 4. **Seguridad de memoria** - maneja siempre los errores al leer memoria del proceso objetivo. El proceso puede morir, la memoria puede desmapearse o una dirección puede ser inválida. 5. **No añadas pasadas redundantes** - si una pasada existente ya itera sobre todas las unidades (unidades de compilación, símbolos, etc.), añade ahí tu comprobación por unidad en lugar de escanear por separado. 6. **Coloca la instrumentación después del prólogo** - establece puntos de interrupción/sondas después del prólogo de cualquier función en lugar de en el punto de entrada bruto, ya que el prólogo de comprobación de pila de un runtime puede volver a dispararse si se instrumenta en la entrada. ## Empezar los mensajes de commit con el subsistema afectado y explicar el porqué Usa un formato de commit basado en subsistemas: una línea de asunto que nombra el subsistema afectado seguida de dos puntos y un breve resumen de lo que cambió, luego una línea en blanco, luego un párrafo de cuerpo que explica por qué se hizo el cambio, y finalmente una línea de cierre que hace referencia al issue que corrige. - Antepón al asunto el subsistema afectado para que el historial sea fácil de explorar por área. - Mantén la línea de asunto concisa y limitada (alrededor de 70 caracteres). - Usa el cuerpo para explicar por qué se hizo el cambio, con ajuste de línea (alrededor de 80 caracteres). - Haz referencia al issue que motivó el cambio para que cada cambio quede vinculado a su causa. Los mensajes estructurados mantienen el registro de cambios navegable y facilitan reconstruir por qué se hizo cualquier cambio dado.