Buscar/Crea una app que reúne eventos de actividad de muchas fuentes y los muestra en varias superficies

Crea una app que reúne eventos de actividad de muchas fuentes y los muestra en varias superficies

Para una app que reúne eventos de muchas herramientas y los muestra en una extensión de editor y en un servidor aparte. Mantiene las capas en una sola dirección, convierte cada fuente en un tipo de evento, deja añadir fuentes sin tocar lo demás y guarda el estado con seguridad.

Para usar estas reglas de IA
## Haz que las dependencias vayan en una sola dirección y que las superficies no se importen entre sí Haz que las dependencias fluyan en una sola dirección a través de capas con nombre. Una capa `core` contiene únicamente definiciones de protocolo e interfaces, sin efectos secundarios en tiempo de ejecución. Una capa de runtime depende solo de `core`. Cada superficie anfitriona —por ejemplo, un adaptador de extensión de editor y un servidor de línea de comandos independiente— depende de `core` más el runtime, pero las superficies nunca se importan entre sí. Declara explícitamente la dirección permitida y hazla cumplir, porque una sola importación que cruce el límite acopla silenciosamente dos superficies que deben seguir siendo intercambiables de forma independiente. ### Por qué funciona Una capa de contrato libre de dependencias permite que cada superficie y cada integración compile contra los mismos tipos sin arrastrar código específico del host. Las superficies nuevas componen las piezas compartidas; nunca acceden lateralmente a los internos de otra superficie. Cuando no puedes añadir una superficie sin editar una existente, la separación en capas ya se ha roto. ## Genera los bindings tipados desde un único archivo de protocolo y compruébalos en CI Deja que un único contrato legible por máquina sea la fuente de verdad para el protocolo de comunicación, y genera a partir de él los bindings tipados en lugar de escribirlos a mano. Trata el archivo generado como de solo lectura: añádele un aviso de generación automática y nunca lo edites manualmente. Agrega un paso de integración continua que regenere los bindings y haga fallar la build ante cualquier diferencia no vacía, de modo que el contrato y el código nunca puedan desincronizarse. ### Fija la versión del generador Fija la versión del contrato a lo que tu generador de código realmente soporta. Una versión de esquema más reciente que el generador no puede manejar puede degradar silenciosamente los tipos generados a un comodín permisivo, eliminando toda la seguridad de tipos sin un error evidente. Documenta por qué se fijó esa versión y revísala solo cuando las herramientas se pongan al día. ## Convierte cada fuente en un mismo tipo de evento dentro de un único hub Agrega toda la actividad a través de un único hub. Sea cual sea la fuente —eventos enviados por HTTP o líneas analizadas de archivos de registro y transcripción— normaliza cada uno a un único tipo de evento canónico antes de que entre al runtime. El código posterior despacha según el tipo del evento, nunca según nombres de acción o herramienta específicos de la fuente, de modo que añadir una fuente no puede repercutir en el runtime ni en la interfaz. ### Mantén el conjunto de eventos pequeño y cerrado Define un conjunto pequeño y cerrado de tipos de evento: inicio y fin de sesión, inicio y fin de trabajo, ciclo de vida de subtareas, progreso y solicitud de permiso. La única responsabilidad de cada ruta de ingesta es producir estos eventos canónicos. Todo lo que está más allá del hub es agnóstico respecto a la fuente y se mantiene estable a medida que llegan fuentes nuevas. ## Oculta la conexión detrás de una única interfaz de transporte Coloca la comunicación detrás de una única interfaz de transporte con una superficie mínima: enviar un mensaje, suscribirse a mensajes entrantes, exponer una promesa de disponibilidad y un estado de conexión, y liberar recursos. Impleméntala una vez por entorno —por ejemplo, un canal de mensajes dentro del mismo proceso para una superficie embebida y un socket con reconexión para una independiente— y bifurca entre implementaciones en exactamente una función fábrica. Todo lo que depende de ella más adelante depende de la interfaz y nunca sabe qué transporte está activo. ### La resiliencia vive en la implementación Dale a la implementación en red un backoff exponencial y una cola de envío que se vacíe al reconectar. La implementación dentro del mismo proceso puede reportarse como conectada durante todo su ciclo de vida. Quien la llama observa el mismo comportamiento en ambos casos, así que ningún código de funcionalidad contiene ramificaciones específicas de transporte. ## Añade cada integración como un proveedor sin tocar el resto del código Modela cada integración externa como un proveedor que implementa una única interfaz: normalizar un evento en bruto a uno canónico, instalar, desinstalar y consultar sus hooks, formatear el estado, y declarar qué nombres de acción propios quedan exentos de reglas específicas. Ofrece capacidades opcionales —un mecanismo de respaldo por sondeo, un modo extendido multi-actor— como métodos opcionales, para que las integraciones simples se mantengan pequeñas. ### La prueba de un límite limpio Una integración nueva debería consistir en un único subdirectorio nuevo que implemente la interfaz, sin ningún cambio en el runtime, la interfaz de usuario o cualquier proveedor existente. Si añadir una fuente obliga a editar otras partes, el límite tiene fugas y necesita reforzarse antes de que se acumulen más integraciones sobre la grieta. ## Comparte un núcleo de runtime y un almacén entre ambas superficies Haz que ambas superficies anfitrionas compongan el mismo núcleo de runtime; solo difieren el espacio de nombres de persistencia, el transporte y cualquier adaptador específico del host. Enruta todo cambio de estado a través de un único almacén que posee los datos, emite eventos de mutación tipados y es la fuente única de verdad. Suscribe una única capa de difusión a esos eventos y traduce ahí mismo a mensajes de protocolo salientes; ningún otro módulo llama directamente a un método de transporte. ### Mantén estrecha la costura con el host Expón exactamente una interfaz de callbacks entre el runtime y su host para las pocas cosas que son genuinamente específicas del host: lanzar trabajo, abrir carpetas, importar y exportar. Mantener esa costura estrecha es precisamente lo que permite que dos superficies compartan el núcleo sin modificarlo, así que ensánchala solo cuando una diferencia real del host lo exija. ## Usa primero las señales push y deja el sondeo de archivos como respaldo Cuando exista una señal rápida y confiable, prefiérela, y mantén un respaldo más lento para cuando no esté disponible. Un canal push de callbacks de eventos sobre HTTP ofrece señales instantáneas y ordenadas; sondear archivos es el respaldo cuando el push no está instalado. Controla ambos mecanismos para que nunca cuenten dos veces: un indicador de entrega por elemento más un interruptor global deciden si los temporizadores del respaldo se ejecutan. ### Separa la lectura del contenido de la derivación del estado Algunos datos pueden llegar solo por la ruta de respaldo —progreso detallado, texto de estado— así que mantén el sondeo activo para eso incluso en modo push, pero suprime los temporizadores derivados del respaldo (avisos de permiso, detección de inactividad) una vez que el push esté confirmado para ese elemento. Separar «leer contenido» de «derivar estado» es lo que permite que ambos modos coexistan sin conflicto. ## Guarda el estado por separado en cada superficie y verifica la copia antes de quitar la antigua Separa por espacio de nombres el estado persistido según la superficie, para que dos superficies ejecutándose en paralelo nunca se pisen entre sí. Escribe los archivos de forma atómica —escribe un archivo temporal y luego renómbralo— y marca tus propias escrituras para que un observador entre procesos no las vuelva a leer como cambios externos. Combina la vigilancia de archivos con un respaldo por sondeo, porque los observadores nativos de archivos no son confiables en algunas plataformas. ### Verifica antes de limpiar Al migrar estado heredado, verifica antes de limpiar: escribe en la nueva ubicación, vuelve a leerla, y solo entonces elimina la clave antigua. Mientras quede algo sin migrar, muestra una advertencia no bloqueante en lugar de asumir que todo salió bien. Nunca borres la fuente de verdad antes de confirmar la copia en disco. ## Reúne las constantes en un solo lugar y hazlo cumplir con reglas de lint Centraliza cada número y cadena mágicos —constantes de tiempo, identificadores, literales de protocolo, tokens de estilo— en un pequeño número de módulos de constantes, uno por capa, y nunca los pongas en línea. Los valores incrustados en el código se desalinean, se duplican y ocultan acoplamientos con los que tropiezan las refactorizaciones posteriores. ### Hazlo cumplir con lint, no con vigilancia Respalda la política con reglas de lint personalizadas configuradas en nivel de error, de modo que las violaciones bloqueen los merges: prohíbe los literales de color y estilo en línea fuera del archivo de tokens, exige los tokens compartidos de sombra y espaciado, exige la referencia de fuente compartida. Una regla que aplica una máquina sobrevive a la rotación de colaboradores; una convención escrita solo en un documento no. ## Prueba por capas y usa pruebas de extremo a extremo para el código que cambia a menudo Usa una estrategia de pruebas por capas, cada capa con la herramienta adecuada. Las pruebas unitarias y de integración rápidas cubren la lógica pura: mutaciones de estado, enrutamiento de eventos, análisis, migración, atomicidad del instalador. Las pruebas end-to-end operan las superficies reales y verifican resultados visibles para el usuario en lugar de internos. ### Prefiere costuras estables para la contribución abierta Para el código que los colaboradores cambian constantemente, como los internos de la interfaz de usuario, prefiere pruebas end-to-end que fijen el comportamiento visible para el usuario antes que pruebas unitarias atadas a los internos, de modo que las refactorizaciones no obliguen a los colaboradores a reescribir pruebas internas además del trabajo de la funcionalidad. Mantén cada prueba aislada —con su propio directorio temporal, espacio de trabajo y directorio de datos— para que no haya fugas de estado entre pruebas, y verifica en CI cualquier inventario de pruebas autogenerado en busca de desviaciones, exactamente igual que verificas el código generado. ## Omite la entrada mal formada en vez de fallar y etiqueta cada línea de registro Degrádate con elegancia: captura y registra los errores, pero nunca dejes que hagan caer al host. Omite silenciosamente una línea de entrada malformada en lugar de abortar todo el flujo. Cuando falte un recurso o activo, registra una advertencia y continúa con un valor nulo o de respaldo en lugar de hacer fallar toda la operación. ### Haz que los registros sean fáciles de buscar Antepón a cada línea de registro una etiqueta de contexto entre corchetes que nombre el subsistema, para que un flujo de consola plano siga siendo buscable. Decide deliberadamente si necesitas reporte centralizado de errores o telemetría; si decides que no, deja constancia de esa decisión y mantén el registro local consistente para que futuros lectores no se queden adivinando. ## Lee con seguridad archivos que siguen creciendo y da tiempo suficiente a las pruebas asíncronas Estas reglas son baratas de enunciar y caras de volver a aprender. ### Lectura de archivos de solo anexado Combina cualquier observador nativo de archivos con un sondeo periódico a intervalos cortos, y conserva en un búfer las líneas parciales sin terminar entre lecturas, porque un lector puede sorprender un archivo a mitad de escritura. Una sesión limpiada o reiniciada suele comenzar un archivo completamente nuevo mientras el antiguo simplemente se detiene, así que detecta el cambio por el contenido en lugar de asumir que el mismo identificador de archivo sigue creciendo. ### Presupuestos de tiempo en las pruebas Cuando verifiques un resultado al final de una canalización asíncrona, presupuesta la latencia de cada etapa: el intervalo entre la señal que dispara el proceso y la señal derivada, el retraso del temporizador, y el margen entre procesos más el de renderizado. Un tiempo de espera más corto que la suma de ese presupuesto es inestable por construcción. ### Renderizado y agrupamiento en lotes Mantén el estado de animación de alta frecuencia en un almacén imperativo en lugar de en el estado del framework, y retrasa ligeramente las transiciones de «trabajo terminado» para que el agrupamiento en lotes de la interfaz no oculte al usuario estados activos breves.