Buscar/Crie um app que reúne eventos de atividade de muitas fontes e os mostra em várias superfícies

Crie um app que reúne eventos de atividade de muitas fontes e os mostra em várias superfícies

Para um app que reúne eventos de muitas ferramentas e os mostra numa extensão de editor e num servidor à parte. Mantém as camadas numa só direção, transforma cada fonte em um só tipo de evento, aceita novas fontes sem mexer no resto e salva o estado com segurança.

Para usar estas regras de IA
## Faça as dependências seguirem numa só direção para que as superfícies nunca importem umas às outras Mantenha as dependências fluindo em um único sentido através de camadas nomeadas. Uma camada `core` contém apenas definições de protocolo e interface, sem nenhum efeito colateral em tempo de execução. Uma camada de runtime depende apenas de `core`. Cada superfície host — por exemplo, um adaptador de extensão de editor e um servidor de linha de comando independente — depende de `core` mais o runtime, mas as superfícies nunca se importam entre si. Declare a direção permitida explicitamente e a imponha, porque um único import que cruza essa fronteira acopla silenciosamente duas superfícies que precisam permanecer substituíveis de forma independente. ### Por que isso funciona Uma camada de contrato livre de dependências permite que cada superfície e cada integração compile contra os mesmos tipos sem trazer código específico do host. Novas superfícies compõem as peças compartilhadas; elas nunca acessam lateralmente os internos de outra superfície. Quando você não consegue adicionar uma superfície sem editar uma já existente, a estrutura em camadas já está quebrada. ## Gere os bindings tipados a partir de um único arquivo de protocolo e confira-os na CI Deixe que um único contrato legível por máquina seja a fonte única da verdade para o protocolo de comunicação e, a partir dele, gere os bindings tipados em vez de escrevê-los à mão. Trate o arquivo gerado como somente leitura: adicione um aviso de geração automática e nunca o edite manualmente. Adicione uma etapa de integração contínua que regenera os bindings e falha o build em qualquer diff não vazio, para que o contrato e o código nunca se distanciem. ### Fixe a versão do gerador Fixe a versão do contrato naquilo que seu gerador de código realmente suporta. Uma versão mais nova do schema que o gerador não consegue tratar pode degradar silenciosamente os tipos gerados para um tipo genérico permissivo, eliminando toda a segurança de tipos sem um erro evidente. Registre por que a versão está fixada e revise apenas quando a ferramenta acompanhar. ## Transforme cada fonte num mesmo tipo de evento dentro de um único hub Agregue toda a atividade por meio de um único hub. Qualquer que seja a fonte — eventos enviados via HTTP ou linhas extraídas de arquivos de log e transcrição — normalize cada uma para um único tipo de evento canônico antes que entre no runtime. O código downstream faz o dispatch com base no tipo do evento, nunca em nomes de ação ou de ferramenta específicos da fonte, de modo que adicionar uma fonte não pode se propagar para o runtime ou para a UI. ### Mantenha o conjunto de eventos pequeno e fechado Defina um pequeno conjunto fechado de tipos de evento: início e fim de sessão, início e fim de trabalho, ciclo de vida de subtarefa, progresso e solicitação de permissão. A única responsabilidade de cada caminho de ingestão é produzir esses eventos canônicos. Tudo o que vem depois do hub é agnóstico em relação à fonte e permanece estável à medida que novas fontes chegam. ## Esconda a conexão atrás de uma única interface de transporte Coloque a comunicação de baixo nível atrás de uma única interface de transporte com uma superfície mínima: enviar uma mensagem, assinar mensagens recebidas, expor uma promise de prontidão e um estado de conexão, e descartar. Implemente-a uma vez por ambiente — por exemplo, um canal de mensagens em processo para uma superfície incorporada e um socket com reconexão para uma superfície independente — e alterne entre as implementações em exatamente uma função fábrica. Tudo o que está downstream depende da interface e nunca sabe qual transporte está ativo. ### A resiliência mora na implementação Dê à implementação em rede um backoff exponencial e uma fila de envio que se esvazia na reconexão. A implementação em processo pode se reportar como conectada durante todo o seu ciclo de vida. Os chamadores observam comportamento idêntico em ambos os casos, então nenhum código de funcionalidade contém ramificações específicas de transporte. ## Adicione cada integração como um provider sem mexer no resto do código Modele cada integração externa como um provider que implementa uma única interface: normalizar um evento bruto em um evento canônico, instalar, desinstalar e consultar seus hooks, formatar o status e declarar quais de seus nomes de ação são isentos de regras específicas. Ofereça capacidades opcionais — um fallback de polling, um modo estendido multi-ator — como métodos opcionais, para que integrações simples permaneçam enxutas. ### O teste de uma fronteira limpa Uma nova integração deve ser um único novo subdiretório que implementa a interface, sem nenhuma alteração no runtime, na UI ou em qualquer provider existente. Se adicionar uma fonte força edições em outros lugares, a fronteira está vazando e precisa ser reforçada antes que mais integrações se acumulem sobre a rachadura. ## Compartilhe um núcleo de runtime e um store entre as duas superfícies Deixe que ambas as superfícies host componham o mesmo núcleo de runtime; apenas o namespace de persistência, o transporte e qualquer adaptador específico do host diferem. Encaminhe toda mudança de estado por meio de um único store que é dono dos dados, emite eventos de mutação tipados e é a fonte única da verdade. Inscreva uma única camada de broadcast nesses eventos e traduza-os em mensagens de protocolo de saída ali — nenhum outro módulo chama um método de transporte diretamente. ### Mantenha a costura com o host estreita Exponha exatamente uma interface de callback entre o runtime e seu host para as poucas coisas que são genuinamente específicas do host: iniciar trabalho, abrir pastas, importar e exportar. Manter essa costura estreita é exatamente o que permite que duas superfícies compartilhem o núcleo sem alterações, então só a alargue quando uma diferença real de host exigir isso. ## Use primeiro os sinais de push e mantenha o polling de arquivos como reserva Quando existir um sinal rápido e confiável, prefira-o, e mantenha um fallback mais lento para quando ele estiver ausente. Um canal push de callbacks de evento via HTTP fornece sinais instantâneos e ordenados; fazer polling de arquivos é o fallback para quando o push não está instalado. Controle os dois para que nunca contem em duplicidade: uma flag de entrega por item somada a um toggle global decidem se os timers do fallback são executados. ### Separe a leitura de conteúdo da derivação de estado Alguns dados podem vir apenas do caminho de fallback — progresso granular, texto de status — então mantenha o polling rodando para isso mesmo em modo push, mas suprima os timers derivados do fallback (prompts de permissão, detecção de inatividade) assim que o push for confirmado para aquele item. Separar "ler conteúdo" de "derivar estado" é o que permite que os dois modos coexistam sem conflito. ## Salve o estado separado por superfície e confira a cópia antes de remover a antiga Isole o estado persistido por superfície em namespaces separados, para que duas superfícies rodando em paralelo nunca se sobreponham. Grave arquivos de forma atômica — escreva um arquivo temporário e depois renomeie — e marque suas próprias gravações para que um observador entre processos não as releia como mudanças externas. Combine a observação de arquivos com um backup por polling, porque observadores de arquivo nativos são pouco confiáveis em algumas plataformas. ### Verifique antes de limpar Ao migrar estado legado, verifique antes de limpar: grave no novo local, leia de volta e só então remova a chave antiga. Enquanto algo permanecer não migrado, exiba um aviso não bloqueante em vez de assumir sucesso. Nunca apague a fonte da verdade antes que a cópia esteja confirmada em disco. ## Reúna as constantes num só lugar e imponha isso com regras de lint Centralize todo número e string mágicos — constantes de tempo, identificadores, literais de protocolo, tokens de estilo — em um pequeno número de módulos de constantes, um por camada, e nunca os embuta diretamente no código. Valores embutidos direto no código se desalinham, duplicam e escondem acoplamentos em que refatorações futuras tropeçam. ### Imponha com lint, não com vigilância Sustente a política com regras de lint personalizadas configuradas em nível de erro, de modo que violações bloqueiem merges: proíba literais de cor e estilo embutidos fora do arquivo de tokens, exija os tokens compartilhados de sombra e espaçamento, exija a referência de fonte compartilhada. Uma regra que uma máquina impõe sobrevive à rotatividade de colaboradores; uma convenção escrita apenas em um documento não sobrevive. ## Teste em camadas e use testes de ponta a ponta no código que muda com frequência Use uma estratégia de testes em camadas, cada camada com a ferramenta certa. Testes unitários e de integração rápidos cobrem a lógica pura: mutações de estado, roteamento de eventos, parsing, migração, atomicidade do instalador. Testes de ponta a ponta operam as superfícies reais e verificam resultados visíveis ao usuário, em vez de detalhes internos. ### Prefira costuras estáveis para contribuição aberta Para código que colaboradores mudam constantemente, como os internos da UI, prefira testes de ponta a ponta que fixam o comportamento visível ao usuário em vez de testes unitários presos aos internos, para que refatorações não forcem colaboradores a reescrever testes internos em cima do trabalho de funcionalidades. Mantenha cada teste isolado — com seu próprio diretório temporário, workspace e diretório de dados — para que nenhum estado vaze entre testes, e verifique divergências em qualquer inventário de testes autogerado no CI exatamente como você verifica divergências em código gerado. ## Pule entradas malformadas em vez de travar e marque cada linha de log Degrade graciosamente: capture e registre erros, mas nunca deixe que eles derrubem o host. Ignore silenciosamente uma linha de entrada malformada em vez de abortar todo o stream. Quando um asset ou recurso estiver ausente, registre um aviso e continue com um valor nulo ou de fallback em vez de falhar toda a operação. ### Torne os logs pesquisáveis Prefixe cada linha de log com uma tag de contexto entre colchetes que nomeia o subsistema, para que um stream de console simples continue pesquisável. Decida deliberadamente se você precisa de relatório de erros centralizado ou telemetria; se optar por não ter, registre essa decisão e mantenha o log local consistente, para que futuros leitores não fiquem no escuro. ## Leia com segurança arquivos que não param de crescer e dê tempo suficiente aos testes assíncronos Essas regras são baratas de enunciar e caras de reaprender. ### Lendo arquivos somente para acréscimo Combine qualquer observador de arquivo nativo com polling periódico em um intervalo curto, e carregue linhas parciais não terminadas em um buffer entre leituras, porque um leitor pode pegar um arquivo no meio de uma escrita. Uma sessão limpa ou reiniciada geralmente começa um arquivo totalmente novo, enquanto o antigo simplesmente para, então detecte a troca pelo conteúdo em vez de assumir que o mesmo handle continua crescendo. ### Orçamentos de tempo em testes Ao verificar um resultado no final de um pipeline assíncrono, reserve tempo para a latência de cada etapa — o intervalo entre o sinal disparador e o sinal derivado, o atraso do timer, e a folga entre processos mais a de renderização. Um timeout menor que o orçamento somado é instável por construção. ### Renderização e batching Mantenha o estado de animação de alta frequência em um store imperativo em vez de no estado do framework, e atrase levemente as transições de "trabalho concluído" para que o batching da UI não esconda do usuário estados ativos breves.