Voltar ao Blog
AI & Development3 de junho de 20259 min de leitura

Use IA para Consertar o Que a IA Quebrou: A Ponte entre SonarQube e Copilot

DR

Doug Ribeiro

9 min de leitura

O Copilot gera código rápido. O SonarQube te diz que esse código tem problemas. Nada conecta os dois. O desenvolvedor tem que ler o relatório do Sonar, interpretar, voltar para o editor e manualmente pedir para o Copilot consertar aquilo que o Copilot escreveu em primeiro lugar. Isso é uma lacuna de workflow, não de ferramentas, e é o agente de alto impacto mais barato que você pode construir agora.

O padrão é fácil de imaginar. Um PR recebe problemas sinalizados, alguns são corrigidos, o resto passa como 'não bloqueador' e um ou dois são lidos errado. Quando um sênior revisa, o PR já tem dívida de qualidade embutida que o ferramental detectou e um humano descartou. O humano não ignorou por preguiça. Ele ignorou porque o feedback era um link para um dashboard, não uma mudança acionável.

O Ciclo Que Está Faltando

PR aberto SonarQube analisa Agente lê o relatório Agente gera o prompt de correção Desenvolvedor cola, revisa, commita

O agente tem uma única tarefa: transformar um problema do Sonar em um prompt que o desenvolvedor possa colar no Copilot sem pensar. É só isso. Ele não está reescrevendo o código. Ele não está abrindo PRs automaticamente. Ele está fechando a lacuna entre 'aqui está um problema' e 'aqui está um pedido de correção com contexto completo'.

Uma conferida na licença antes de você planejar o fim de semana. Análise de pull request não existe no SonarQube Community Build. Ela exige Developer Edition ou superior, e o Community rejeita sonar.pullrequest.key de cara. Então o caminho gratuito é a análise de branch: rode o scanner na main, deixe o agente ler os problemas que apareceram desde a última análise, e poste os prompts no commit de merge em vez do PR aberto. Se você quer o trigger de PR sem pagar, o plugin comunitário de branches (mc1arke/sonarqube-community-branch-plugin) adiciona análise de branch e de PR ao Community Build. Ele é mantido pela comunidade e não tem suporte da SonarSource, então leia a matriz de compatibilidade antes de plugar seu time nisso. Tudo abaixo é o mesmo agente nos dois casos. Só mudam o trigger e o lugar onde ele comenta.

Por Que Este Primeiro

Quando alguém me pergunta qual deveria ser o primeiro agente deles, é isso que eu recomendo para qualquer equipe que já usa Sonar e Copilot. Três razões:

  • A entrada é estruturada. O SonarQube tem uma API limpa que retorna os problemas com rule ID, severidade e localização do arquivo. Você não está parseando HTML ou fazendo scraping.
  • A saída é um blob de texto. O agente produz um prompt. Sem execução de código, sem risco de produção, sem dependências de runtime.
  • O valor é óbvio já na primeira semana. Os desenvolvedores sentem a economia de tempo logo no primeiro PR que tem avisos do Sonar.

Compare isso com um agente de triagem de bugs ou um agente de revisão de código. Ambos são valiosos. Ambos exigem 10x mais esforço para chegar a uma qualidade utilizável. A ponte para o SonarQube é um projeto de fim de semana que é usado todo dia.

O Que o Agente Realmente Faz

Trigger: o webhook do SonarQube que dispara quando a análise termina. Num PR se a sua edição faz análise de pull request, na branch se não faz. Uma GitHub Action que roda depois que o Sonar conclui funciona do mesmo jeito.

Passos, em ordem:

  • Puxe a lista de problemas desse PR no Sonar. Filtre INFO, mantenha BLOCKER, CRITICAL, MAJOR.
  • Para cada problema, leia o arquivo na faixa de linhas sinalizada. Inclua 10 linhas de contexto acima e abaixo.
  • Agrupe os problemas por arquivo. Um prompt do Copilot por arquivo, não por problema. Desenvolvedores querem corrigir por arquivo.
  • Gere um prompt estruturado: a regra, por que importa, o trecho, o padrão de correção. Inclua quaisquer convenções do projeto vindas da sua base de conhecimento.
  • Poste um único comentário no PR com os prompts, colapsáveis por arquivo.

Como o Prompt Fica

markdown
## Fix prompt for `src/orders/order-summary.component.ts`

SonarQube flagged 3 issues in this file. Paste this into Copilot or Claude Code:

---

In `order-summary.component.ts`, fix the following issues while preserving existing behavior:

1. **Line 42 (typescript:S1481)**: unused local variable `totals`.
   Remove the variable. If it was intended for debugging, delete it, do not comment it out. The team convention is to remove dead code rather than leave it behind.

2. **Line 67 (typescript:S4123)**: `await` should only be used with promises.
   `formatAmount` returns a string, not a Promise. Drop the `await` keyword on that call.

3. **Line 88 (typescript:S6544)**: Promise returned in a context expecting `void`.
   The async callback passed to `addEventListener` returns a Promise, which is unhandled. Wrap the body in a non-async IIFE or handle the returned Promise explicitly. Prefer the IIFE pattern since the team avoids bare async handlers on DOM events.

Do not modify any file other than `order-summary.component.ts`. Keep imports sorted alphabetically. Return only the full updated file content, no explanation.

Observe o que tem nesse prompt que não está num comentário padrão do Sonar: a descrição da regra traduzida para linguagem humana, a convenção da equipe, a restrição rígida sobre o que não modificar, o formato de saída. Essa é a parte que o desenvolvedor fazia na cabeça. Agora o agente faz.

Ligando Tudo

Este post casa com o post do monorepo. O agente reutiliza ReActAgent de libs/base_agent/ e mora em agents/sonar_bridge/. Menos de 100 linhas de código real, a maior parte HTTP glue. O que importa é o formato: um punhado de ferramentas tipadas, um playbook e uma função de build que o runtime chama.

python
# libs/tools/sonar.py
import os
import requests

SONAR_URL = os.environ["SONAR_URL"]
SONAR_TOKEN = os.environ["SONAR_TOKEN"]

PROJECT_KEY = os.environ["SONAR_PROJECT_KEY"]

def get_issues_for_pr(pr_key: str) -> list[dict]:
    r = requests.get(
        f"{SONAR_URL}/api/issues/search",
        params={
            # componentKeys is not optional. Without a project scope the
            # search does not come back with this PR's issues.
            "componentKeys": PROJECT_KEY,
            "pullRequest": pr_key,
            # Legacy taxonomy, still accepted. On SonarQube 10.2 and up
            # the current filter is impactSeverities=HIGH,MEDIUM.
            "severities": "BLOCKER,CRITICAL,MAJOR",
        },
        auth=(SONAR_TOKEN, ""),
        timeout=15,
    )
    r.raise_for_status()
    return r.json()["issues"]


# libs/tools/github.py
import os
import requests

GITHUB_TOKEN = os.environ["GITHUB_TOKEN"]

def read_file_range(repo: str, ref: str, path: str, start: int, end: int) -> str:
    # Returns the flagged range plus 10 lines of context on each side.
    r = requests.get(
        f"https://api.github.com/repos/{repo}/contents/{path}",
        params={"ref": ref},
        headers={
            "Authorization": f"Bearer {GITHUB_TOKEN}",
            "Accept": "application/vnd.github.raw",
        },
        timeout=15,
    )
    r.raise_for_status()
    lines = r.text.splitlines()
    lo = max(0, start - 11)
    hi = min(len(lines), end + 10)
    return "\n".join(lines[lo:hi])

def comment_on_pr(repo: str, pr_number: int, body: str) -> None:
    r = requests.post(
        f"https://api.github.com/repos/{repo}/issues/{pr_number}/comments",
        headers={"Authorization": f"Bearer {GITHUB_TOKEN}"},
        json={"body": body},
        timeout=15,
    )
    r.raise_for_status()

Três ferramentas, dois arquivos. A do Sonar limita a busca ao projeto e filtra por severidade antes do agente ver a lista, então ruído de baixa prioridade nunca chega ao prompt nem ao orçamento de tokens. O read do GitHub retorna uma janela em torno das linhas sinalizadas para o agente conseguir raciocinar sobre a correção em contexto, não só sobre o rule ID. O comentário do GitHub é uma única escrita no final da execução, imposta pelo playbook em vez de pelo código, então o comportamento fica visível e alterável sem um redeploy.

python
# agents/sonar_bridge/playbook.py
PLAYBOOK = """You are the Sonar-to-Copilot bridge agent.

Goal: turn a Sonar analysis report on a pull request into prompts a
developer can paste into Copilot or Claude Code without context-switching.

Process, in order:
1. Call get_issues_for_pr with the PR key from the trigger. Project
   scope and severity are already handled by the tool.
2. Group the remaining issues by file path.
3. For each file, call read_file_range on the flagged line ranges to
   pull the code into context.
4. For each file, synthesize one Copilot-ready prompt. Apply project
   conventions retrieved from the knowledge base.
5. Call comment_on_pr once with all per-file prompts in a single
   collapsible comment.

Hard rules:
- You generate prompts. You do not generate code.
- comment_on_pr is called at most once per run.
- If there are zero qualifying issues, exit without commenting.
"""

O playbook é onde o comportamento do agente é especificado. Repare no que tem nele: os passos do processo, as regras rígidas, o formato de saída. O que não tem: a orientação de tradução regra a regra. Isso vive na base de conhecimento, recuperado em tempo de execução, porque convenções de equipe mudam e eu não quero redeployar o agente toda vez que mudam.

python
# agents/sonar_bridge/agent.py
from libs.base_agent import ReActAgent, AgentConfig
from libs.memory import MemoryStack
from libs.telemetry import Telemetry
from libs.tools import sonar, github
from .playbook import PLAYBOOK

def build() -> ReActAgent:
    return ReActAgent(
        config=AgentConfig(
            name="sonar_bridge",
            version="0.1.0",
            playbook=PLAYBOOK,
            tools=[
                sonar.get_issues_for_pr,
                github.read_file_range,
                github.comment_on_pr,
            ],
            # Confidence in the prompt it drafted for a file. Below
            # this, the agent lists the issue and writes no prompt.
            confidence_threshold=0.85,
        ),
        memory=MemoryStack.default(),
        telemetry=Telemetry.default(),
    )

A última peça é o trigger. Um handler FastAPI em apps/api/ recebe o webhook do Sonar, extrai o PR key e chama build().run({"pr_key": ...}). O agente base faz o resto: puxa execuções passadas similares do vector store, executa o loop ReAct, grava telemetria no Postgres, posta o comentário no GitHub. O threshold fica acima do padrão da biblioteca porque um prompt de correção errado custa mais caro do que um prompt que não existe. Um desenvolvedor que cola no Copilot um prompt errado e confiante entrega a mudança errada achando que o Sonar aprovou. Se você construiu a estrutura do monorepo primeiro, adicionar um novo agente é basicamente escrever os três arquivos acima e os testes que acompanham.

A Parte Não Óbvia: Convenções da Equipe

Um problema do Sonar fora de contexto é uma violação de regra. Um problema do Sonar com contexto de equipe é uma diretiva específica. O agente precisa saber coisas como 'preferimos async/await em vez de observables crus', 'nunca usamos var', 'nosso padrão de null-check é optional chaining, não ternário'. É aí que a sua base de conhecimento entra, seja um vault do Obsidian, uma pasta de markdown no repo ou um espaço do Notion.

O agente lê a regra, busca a convenção da equipe para aquele tipo de regra e funde as duas. Essa é a diferença entre 'adicione um null check' (genérico) e 'adicione um null check usando optional chaining, o padrão que usamos em services/' (específico e consistente com o codebase).

O Que Medir

Se você colocar isso em produção, acompanhe dois números.

  • Tempo médio do relatório do Sonar até o commit da correção. Antes do agente, isso provavelmente era medido em dias ou nunca. Depois, deve ser em horas.
  • Porcentagem de problemas sinalizados que são de fato corrigidos antes do merge. Antes do agente, meça a sua linha de base, porque ela costuma ser mais baixa do que o time imagina. Depois, compare com essa linha de base e com nada mais. Um número do time de outra pessoa não é uma meta.

Não acompanhe 'problemas resolvidos'. Desenvolvedores podem fechar problemas marcando como won't fix, e esse número mente. Acompanhe mudanças reais de código nas linhas sinalizadas.

O Que Pode Dar Errado

Dois modos de falha para se preparar.

Primeiro, o agente gera prompts para problemas que na verdade são falsos positivos. O Sonar tem esses. Se o agente não souber quais regras sua equipe habitualmente suprime, ele produz ruído. A correção é alimentar o agente com seu histórico de supressão para que ele aprenda quais regras rebaixar ou pular inteiramente. Mesmo vector store, query diferente.

Segundo, os desenvolvedores começam a tratar a saída do prompt como sendo a correção. Colam no Copilot, aceitam a sugestão, commitam. Sem revisão. Isso derrota todo o propósito. O agente é uma ferramenta de rascunho, não um tomador de decisão. Estabeleça a expectativa cedo de que o desenvolvedor continua dono da correção.

O Copilot acelerou a escrita de código. O SonarQube acelerou a detecção dos defeitos. A lacuna entre os dois era um workflow, não uma ferramenta. É aí que mora o agente útil mais barato.

Referências

AIAgentsSonarQubeCopilotCode Quality