Buscar/Alterar um depurador de baixo nível escrevendo os testes primeiro, sem quebrar outras plataformas

Alterar um depurador de baixo nível escrevendo os testes primeiro, sem quebrar outras plataformas

Use quando uma IA altera um depurador de linguagem compilada que roda em vários sistemas e backends. A IA escreve antes um teste que falha, separa o código de cada sistema em arquivos próprios, revisa todos os backends ao mudar interfaces compartilhadas e inicia os commits pelo subsistema.

Para usar estas regras de IA
## Ler a camada responsável por um comportamento antes de editar o depurador Um depurador para uma linguagem compilada é um sistema complexo e multicamadas. Trabalhar nele com eficácia exige compreensão dos internos de depuração, dos formatos binários de informações de depuração, do controle de processos específico do sistema operacional e dos internos do runtime da linguagem. Trate cada alteração como algo que afeta um sistema no qual a correção entre plataformas e backends importa mais do que a velocidade bruta. Um erro sutil no acesso à memória, no tratamento de registradores ou no comportamento de um backend se manifesta como uma falha difícil de reproduzir, e apenas em uma única plataforma. Antes de editar, leia a camada ao redor para entender qual abstração é dona do comportamento que você está alterando. Mudanças em interfaces compartilhadas se propagam para todos os backends que as implementam, então avalie o raio de impacto antes de começar. ## Escrever um teste que falha antes de mudar o código e nunca pular testes Todas as alterações de código devem seguir o desenvolvimento orientado a testes (TDD). Isso não é opcional. Siga o ciclo Red-Green-Refactor-Verify (Vermelho-Verde-Refatorar-Verificar): 1. **RED (Vermelho)** - escreva primeiro um teste que falhe; ele precisa falhar pelo motivo certo. 2. **GREEN (Verde)** - escreva o código mínimo necessário para fazê-lo passar. 3. **REFACTOR (Refatorar)** - limpe o código mantendo os testes passando. 4. **VERIFY (Verificar)** - execute toda a suíte de testes antes de fazer o commit. Regras obrigatórias: 1. Nunca escreva código de implementação sem antes ter um teste que falhe. 2. Nunca faça commit de código sem testes. 3. Nunca pule testes nem os marque como ignorados só para fazer a CI passar. 4. Nunca desative testes existentes - corrija o teste ou corrija o código. 5. Cada teste verifica um comportamento específico. 6. Os nomes dos testes descrevem o cenário testado. Quando escrever um teste primeiro parece difícil, essa dificuldade é um sinal, não uma desculpa. Geralmente significa que uma função está fazendo coisa demais, que as dependências estão fortemente acopladas ou que os requisitos não estão claros. Não pule o TDD - corrija o problema de design que ele revelou. ## Definir onde fica cada tipo de teste e manter os resultados sempre iguais Onde os testes devem ficar: - **Testes unitários**: junto do código que cobrem. - **Testes de integração**: um diretório dedicado de testes ponta a ponta. - **Testes específicos de plataforma**: controlados por tags de build / compilação condicional. - **Testes específicos de backend**: exercitam cada backend separadamente. - **Fixtures**: mantidas como código-fonte compilado durante a execução dos testes, não como binários pré-compilados; use o helper compartilhado de localização de fixtures em vez de implementar a busca de caminho na mão. Padrões de qualidade: - Teste o comportamento, não a implementação. - Prefira uma asserção por teste, para que uma falha aponte para uma única causa. - Nomeie com clareza, usando um padrão separado por underscore com o que é testado, o cenário e o resultado esperado. - Reutilize os utilitários de teste existentes em vez de reinventá-los. - Os testes devem ser determinísticos - sem valores aleatórios, sem condições de corrida. - Para testes de formato de saída, valide a saída completa esperada em vez de uma substring. A comparação da saída completa detecta regressões - espaços em branco extras, campos reordenados, mudanças de formato - que verificações de substring deixam passar silenciosamente. ## Separar entre camadas a interface somente leitura e a que muda o estado O sistema é organizado em camadas com clara separação de responsabilidades: uma CLI no topo, uma camada de serviço/protocolo logo abaixo, operações de alto nível abaixo dela, uma abstração de processo/alvo abaixo dessas, e implementações de backend plugáveis na base (controle direto de processos em nível de sistema operacional, um cliente de protocolo remoto, análise offline de dumps, rastreamento em nível de kernel). Use um design de interface de dois níveis na fronteira da abstração: - Uma interface pública somente leitura, usada pelos chamadores para observar o estado. - Uma interface interna separada, que carrega todas as operações que modificam o estado. - Um objeto wrapper com estado em torno da interface interna, além de uma abstração de grupo para gerenciar vários alvos ao mesmo tempo. Os backends são plugáveis por trás da interface interna. Quando você modifica essa interface, a mudança normalmente se aplica a TODAS as implementações - leve em conta cada backend, não apenas o que está na sua frente. ## Colocar o código de cada sistema operacional e arquitetura em arquivos próprios Isole o código específico de sistema operacional e de arquitetura usando compilação condicional baseada em nome de arquivo - arquivos-fonte por sistema operacional e arquivos-fonte por arquitetura, selecionados pelo sistema de build em vez de por desvios em tempo de execução. - Nunca coloque código específico de plataforma em arquivos genéricos. - Restrinja o código específico de plataforma aos backends que realmente precisam dele (o backend de controle direto do sistema operacional e o backend de protocolo remoto), e não à lógica central compartilhada. - Mantenha detalhes de registradores, ABI e convenção de chamada em arquivos por arquitetura quando houver suporte a várias arquiteturas. Feito corretamente, o núcleo compartilhado permanece portável, e adicionar uma nova plataforma passa a ser uma questão de adicionar arquivos, não de reescrever código genérico. ## Evitar erros comuns ao ler a memória do processo e definir breakpoints 1. **Não modifique o estado compartilhado diretamente** - passe pelos métodos da interface interna, que garantem travamento (locking) e consistência de estado. 2. **Operações específicas de backend variam** - uma mesma ação conceitual (por exemplo, inserir um breakpoint) é implementada de forma diferente em cada backend; nunca presuma um único mecanismo. 3. **Distinga abstrações semelhantes** - uma thread do sistema operacional e uma unidade de concorrência em nível de linguagem não são a mesma coisa; saiba qual delas uma operação tem como alvo. A maioria das operações age sobre a unidade em nível de linguagem, não sobre a thread bruta. 4. **Segurança de memória** - sempre trate erros ao ler a memória do processo alvo. O processo pode morrer, a memória pode estar desmapeada, ou um endereço pode ser inválido. 5. **Não adicione passagens redundantes** - se uma passagem existente já itera sobre todas as unidades (unidades de compilação, símbolos, etc.), adicione sua verificação por unidade ali, em vez de fazer uma varredura separada. 6. **Posicione a instrumentação depois do prólogo** - defina breakpoints/probes depois do prólogo de qualquer função, em vez de no ponto de entrada bruto, já que o prólogo de verificação de pilha de um runtime pode ser disparado novamente se instrumentado logo na entrada. ## Começar as mensagens de commit pelo subsistema afetado e explicar o porquê Use um formato de commit baseado em subsistema: uma linha de assunto nomeando o subsistema afetado, seguida de dois-pontos e um breve resumo do que mudou; depois uma linha em branco; depois um parágrafo de corpo explicando por que a mudança foi feita; e por fim uma linha de fechamento referenciando o issue que ela corrige. - Prefixe o assunto com o subsistema afetado, para que o histórico possa ser lido rapidamente por área. - Mantenha a linha de assunto concisa e com um limite (cerca de 70 caracteres). - Use o corpo para explicar por que a mudança foi feita, com quebra de linha (cerca de 80 caracteres). - Referencie o issue motivador, para que cada mudança se conecte à sua causa. Mensagens estruturadas mantêm o histórico de mudanças navegável e facilitam reconstruir por que uma determinada mudança foi feita.