Pitch: Eu criei o Concord para encontrar diferenças entre ferramentas. O primeiro projeto que ele colocou contra a parede foi ele mesmo.
Há alguns dias comecei a desenvolver o Concord, uma CLI open-source escrita em Rust para comparar o comportamento de linters e formatters.
A ideia nasceu de uma pergunta aparentemente simples:
Se eu trocar ESLint por Biome ou Oxlint, como posso saber o que realmente mudou?
Não apenas se a configuração foi convertida ou se o novo comando termina sem erros. Queria saber quais diagnósticos deixaram de aparecer, quais surgiram, quais mudaram de posição ou severidade e onde dois formatters produzem resultados diferentes.
Hoje existem ferramentas excelentes para migrar configurações. Mas ainda é difícil responder, de forma concreta:
- O novo linter cobre os mesmos casos?
- Essa atualização introduziu uma regressão?
- A diferença é real ou apenas causada por configuração?
- É possível transformar um arquivo enorme em uma reprodução mínima do problema?
Foi a partir disso que comecei o Concord.
O que o Concord faz
O Concord executa ferramentas já instaladas no projeto e normaliza suas saídas para um modelo comum.
Atualmente ele suporta:
- ESLint, Biome e Oxlint para lint;
- Prettier, Biome e Oxfmt para formatação;
- relatórios no terminal e em JSON;
- comparação determinística de diagnósticos;
- detecção de mudanças em severidade, mensagem e posição;
- verificação de idempotência dos formatters;
- redução automática de arquivos que reproduzem uma divergência.
Um comando pode ser assim:
concord compare lint \
--baseline eslint \
--candidate biome \
src
Ou:
concord compare format \
--baseline prettier \
--candidate oxfmt \
.
A ferramenta não tenta decidir qual resultado é “melhor”. Ela apresenta o que foi observado.
Essa distinção é importante. O baseline não é tratado como verdade absoluta, e uma diferença não significa automaticamente que existe um bug. Pode ser uma regra desativada, um plugin específico, uma opção diferente ou uma decisão legítima de design.
O Concord existe para tornar essas diferenças visíveis e investigáveis.
A primeira versão estava pronta. Pelo menos era o que os testes diziam.
A v0.1 nasceu com uma base que, no papel, parecia bastante sólida.
Todos os comandos principais estavam implementados. Os adapters interpretavam as saídas estruturadas das ferramentas. Existiam testes unitários, testes de CLI, executáveis falsos para simular falhas e smoke tests com versões reais das ferramentas.
A suíte passava.
O Clippy passava com warnings tratados como erros.
A CI passava no Linux, macOS e Windows.
Era tentador olhar para tudo aquilo e concluir:
Está funcionando.
Mas testes controlados possuem uma característica perigosa: normalmente testamos os cenários que conseguimos imaginar.
Eu não queria que o Concord fosse uma ferramenta que funcionasse perfeitamente apenas dentro do aquário construído para ela. Então decidi levá-lo para um projeto real.
Escolhi o TabNews.
Colocando o Concord contra o TabNews
A avaliação começou pequena, no diretório pages/, com 58 arquivos.
Depois foi ampliada para 199 arquivos em diferentes partes do projeto.
Por fim, o Concord foi executado sobre todos os 364 arquivos suportados e rastreados pelo Git naquele commit do TabNews.
As versões utilizadas foram fixadas:
ESLint: 9.39.4
Biome: 2.5.5
Oxlint: 1.75.0
Prettier: 3.8.3
Oxfmt: 0.60.0
Além dos relatórios produzidos pelo Concord, algumas diferenças foram verificadas diretamente, executando cada ferramenta fora dele.
Isso era essencial.
Um relatório pode parecer preciso e ainda estar construído sobre uma normalização errada.
E foi exatamente isso que aconteceu.
O primeiro bug: o Concord estava dizendo a verdade com as coordenadas erradas
Durante a comparação entre ESLint e Biome, alguns diagnósticos chamaram atenção.
Ao investigar a saída original do Biome 2.5.5, descobri dois problemas no adapter do Concord.
O reporter RDJSON utilizado não trazia a severidade da maneira esperada. Quando esse campo não aparecia, o Concord assumia info.
Isso era incorreto.
Ausência de informação não significa severidade informativa. O programa estava inventando um dado plausível.
Além disso, as linhas e colunas fornecidas pelo Biome já começavam em 1, mas o Concord adicionava mais 1 durante a normalização.
Um warning na posição:
1:7
podia aparecer no relatório como:
info em 2:8
O diagnóstico continuava parecendo convincente. O arquivo existia, a mensagem existia e a regra existia. Mas sua severidade e localização estavam erradas.
Essa falha afetava mais do que a apresentação. Ela podia alterar o matching, as métricas, a detecção de mudanças de severidade e a identidade usada pelo redutor.
A correção foi mudar o adapter para usar a saída JSON estruturada do Biome, preservar a severidade real e manter corretamente as coordenadas baseadas em 1.
Depois foram adicionados testes de regressão com saídas representativas do Biome 2.5.5.
O segundo bug: o redutor encontrou uma resposta certa para a pergunta errada
Uma das partes mais interessantes do Concord é o redutor.
Imagine que um arquivo com 121 linhas contém uma divergência entre ESLint e Biome. O Concord tenta remover partes desse arquivo, executando as duas ferramentas repetidamente, até encontrar um exemplo mínimo que ainda reproduza a diferença.
Durante o teste real, selecionei uma divergência relacionada à variável error:
This variable error is unused.
O problema era que o arquivo possuía outro diagnóstico da mesma regra, relacionado a uma variável diferente chamada userTryingToGet.
A assinatura interna do redutor guardava apenas informações muito amplas:
- de qual lado vinha a divergência;
- sua categoria;
- a regra envolvida.
Durante a redução, o diagnóstico de error desapareceu, mas o outro diagnóstico da mesma regra continuou existindo.
O redutor aceitou isso.
Ele produziu um arquivo menor, mas já não preservava aquilo que eu havia selecionado.
Esse tipo de bug é especialmente traiçoeiro porque o resultado continua parecendo válido. Existe uma divergência, a regra é a mesma e o arquivo ficou menor. Só que o alvo mudou durante o processo.
A assinatura foi fortalecida para incluir também mensagens e severidades dos diagnósticos envolvidos.
Depois da correção, a redução foi repetida:
Original: 121 linhas, 3.329 bytes
Reduzido: 4 linhas, 85 bytes
Tentativas: 23
Duração: 64,8 segundos
A comparação do arquivo final foi executada novamente e confirmou exatamente:
This variable error is unused.
Dessa vez, o Concord não preservou apenas uma divergência parecida. Preservou a divergência escolhida.
Os testes também mostraram diferenças que não eram bugs
Nem tudo que aparece diferente representa uma falha.
Na comparação entre Prettier e Oxfmt, alguns package.json divergiam porque o Oxfmt ativava sortPackageJson por padrão.
O Prettier não realiza essa ordenação.
Depois de configurar:
{
"sortPackageJson": false
}
os resultados se alinharam.
Nos arquivos suportados restantes, Prettier e Oxfmt produziram conteúdo idêntico.
Também foi encontrada uma limitação da versão testada do Oxfmt: ela recusava package-lock.json quando o conteúdo era enviado por stdin com --stdin-filepath.
Isso foi registrado como limitação da ferramenta naquele modo de execução, não como bug do Concord.
Esse processo me lembrou de algo importante:
Uma boa ferramenta de comparação não deve apenas encontrar diferenças. Ela deve ajudar a separar diferença de comportamento, diferença de configuração e falha operacional.
Contar sintomas é fácil. Entender a causa é outra história.
O que mudou na v0.1.1
As correções foram publicadas na versão v0.1.1.
Foram adicionados testes específicos para:
- severidade dos diagnósticos do Biome;
- linhas e colunas na normalização;
- campos ausentes;
- preservação do alvo selecionado durante a redução;
- dois diagnósticos diferentes pertencentes à mesma regra.
No estado atual:
36 testes aprovados
0 falhas
A validação também passou em:
- Linux;
- macOS;
- Windows;
- build release com lockfile;
- smoke tests reais com ESLint, Biome, Oxlint, Prettier e Oxfmt.
O desenvolvimento passou por uma PR pública, com CI antes do merge, changelog e um relatório sanitizado da avaliação no TabNews.
O que mais gostei nesse processo
É fácil tratar um bug encontrado logo após o lançamento como algo constrangedor.
Eu vejo de outra forma.
O objetivo de testar em um projeto real era justamente encontrar o que os testes internos não conseguiam enxergar.
Se nada tivesse aparecido, eu provavelmente ficaria mais confortável, mas não necessariamente mais correto.
Os dois bugs encontrados tinham algo em comum: ambos produziam resultados plausíveis.
A severidade errada parecia possível.
A posição deslocada parecia próxima o suficiente.
O arquivo reduzido ainda continha uma divergência da mesma regra.
Esses são os bugs mais perigosos em ferramentas de análise. Não explodem. Não mostram uma tela vermelha. Apenas fazem uma afirmação quase certa.
E “quase certa” é uma base ruim para uma ferramenta cuja principal proposta é precisão.
Ainda não considero o Concord terminado
O redutor continua textual, não orientado a AST.
A correlação entre regras ainda depende de aliases incompletos.
Matches prováveis continuam sendo heurísticas, não equivalência semântica.
Versões futuras das ferramentas podem alterar seus formatos estruturados.
Alguns arquivos especiais não são aceitos por todos os formatters.
Essas limitações estão documentadas porque não quero que o Concord pareça mais confiável do que realmente é.
Ao mesmo tempo, agora existe uma base concreta:
- execução segura;
- resultados determinísticos;
- testes multiplataforma;
- comparação real de linters e formatters;
- redução funcional;
- validação em um projeto de produção;
- regressões construídas a partir de falhas reais.
Próximos passos
Antes de adicionar funcionalidades maiores, quero continuar submetendo o Concord a projetos reais.
Quero descobrir onde o matching ainda é permissivo demais, onde adapters quebram, quais diferenças são ruído de configuração e quais realmente podem indicar regressões nas ferramentas.
O objetivo não é acumular funcionalidades rapidamente.
É fazer o Concord merecer a confiança necessária para responder uma pergunta difícil:
O que realmente muda quando eu substituo ou atualizo uma ferramenta do meu projeto?
O repositório está disponível aqui:
https://github.com/ruidosujeira/Concord
O projeto ainda está no começo. Mas talvez essa seja a parte mais interessante.
Existe uma diferença grande entre um software que nunca falhou e um software que já foi colocado contra casos reais, falhou de maneira compreendida e voltou mais preciso.
Estou tentando construir o segundo.