Knowledge graph local com AST — blast-radius em vez de re-leitura

TL;DR

Em review e refactoring multi-arquivo, a pergunta dominante é “o que essa mudança impacta?“. A resposta exige percorrer chamadas, herança, imports e testes — leitura caríssima em tokens. Knowledge graph local resolve isso parsing o codebase com Tree-sitter, armazenando funções/classes/imports como nós e chamadas/herança/cobertura como arestas em SQLite, e expondo via MCP queries de blast-radius. O agente lê o grafo da mudança, não o código completo. Reduções relatadas de 5×–50× em tarefas de review, dependendo do tamanho do monorepo.

O que é

Uma camada de análise estática que constrói um grafo de relações do código, persiste localmente, e responde queries estruturais em vez de obrigar o agente a re-ler arquivos.

Nós:                          Arestas:
  - Function                    - calls          (f1 chama f2)
  - Class                       - extends        (Class A herda B)
  - Module                      - imports        (file imports X)
  - Test                        - tests          (test cobre função)
  - File                        - defines        (file define símbolo)

Query típica em review:

detect_changes(commit=abc123)
  → arquivos modificados: 3
  → funções afetadas: 7
  → blast-radius (callers transitivos): 23 funções em 11 arquivos
  → testes que cobrem: 4 (faltam: 3 funções sem cobertura)

O agente recebe esse resumo (~2 KB) em vez de ler os 11 arquivos completos (~120 KB).

Como funciona

Parsing com Tree-sitter

Tree-sitter é o parser incremental usado pelo GitHub, Atom, Neovim e a maioria das implementações de knowledge graph. Roda em ~100ms por arquivo, suporta 20+ linguagens com grammars maduras (Python, TypeScript, Go, Rust, Java, etc).

Para cada arquivo, o parser extrai:

  • Definições de função/classe/método.
  • Call sites (quem chama quem).
  • Imports e referências entre módulos.
  • Cadeias de herança.
  • Decoradores e atributos relevantes (@pytest.fixture, @Test, etc).

Persistência local

O grafo geralmente vive em SQLite no projeto, em algo como .code-graph/graph.db. Tabelas básicas:

CREATE TABLE nodes (
  id INTEGER PRIMARY KEY,
  type TEXT,           -- function, class, module, test
  name TEXT,
  file TEXT,
  line_start INTEGER,
  line_end INTEGER,
  hash TEXT            -- SHA-256 do conteúdo para detecção de mudança
);
 
CREATE TABLE edges (
  source_id INTEGER,
  target_id INTEGER,
  type TEXT,           -- calls, extends, imports, tests
  confidence REAL      -- 0.0–1.0, extracted vs inferred
);

Atualização incremental

A cada save (ou commit), um hook diffa os hashes dos nós, identifica arquivos modificados, e re-parseia só esses. Em monorepo de 2900 arquivos, atualização típica fica em <2s.

Sem incremental, o grafo vira inútil — ninguém aguenta rebuild de minutos a cada save.

MCP como interface

O grafo é exposto via MCP server com tools como:

  • find_callers(symbol) — quem chama esta função.
  • find_dependents(file) — o que depende deste módulo.
  • blast_radius(commit_or_diff) — todos os impactos transitivos.
  • find_untested(file) — funções sem cobertura.
  • find_hubs() — nós mais conectados (chokepoints arquiteturais).
  • find_bridges() — nós que conectam comunidades distintas.

Configurado uma vez, vira ferramenta padrão em sessões de review e refactor.

Análises secundárias úteis

Grafos sérios não param em blast-radius. Métricas baratas a partir do grafo:

  • Comunidades (Leiden/Louvain) — agrupa código relacionado, identifica módulos coesos vs acoplamento espúrio.
  • Betweenness centrality — nós-ponte que se conectam comunidades; mudanças neles têm impacto desproporcional.
  • Surprise scoring — arestas anômalas (cross-language, periphery-to-hub).
  • Dead code detection — nós sem callers reachable a partir de entry points.
  • Auto-arquitetura — diagrama do projeto gerado do grafo, útil pra onboarding e PR review.

Quando usar

A abordagem se justifica em workflows específicos:

WorkflowKnowledge graph compensa?
Code review de PR em monorepoSim
Refactoring que toca código compartilhadoSim
”Posso remover esta função?” / dead codeSim
Análise de impacto antes de mudar uma APISim
Coding “verde” em projeto pequenoNão
Geração de código novoMarginal

Pareia bem com 03 - Indexação semântica: semantic search acha “onde fala de X”; knowledge graph acha “o que X afeta”.

Custo da abordagem

  • Build inicial: ~10s para projeto de 500 arquivos; minutos para monorepo grande.
  • Atualização incremental: <2s típico, via hooks.
  • Espaço em disco: SQLite de poucos MB para projetos médios; algumas centenas de MB em monorepos com 10k+ arquivos.
  • Manutenção: Tree-sitter grammars precisam estar atualizadas pra linguagens menos populares; edges inferidos (não-extraídos diretamente do AST) têm precisão variável.

Custo de infra é menor que indexação semântica (sem API key, sem vector DB cloud), mas requer setup correto de hooks e disciplina de re-build em mudanças de configuração.

Armadilhas

Confundir recall com precisão. Blast-radius bem desenhado prioriza recall (não perder impacto real) sobre precisão (over-predict é aceitável). Mas se a precisão fica em 0.2, o agente é inundado de falsos positivos e perde o sinal. Implementações bem calibradas ficam em F1 ~0.5 com recall 1.0 — isso é “conservador funcional”, não “ruído”.

Edges inferidos vendidos como extraídos. Tree-sitter extrai call sites com alta confiança quando a chamada é direta (foo()). Em chamadas dinâmicas (obj[method](), callbacks, decorators meta), a aresta é inferida com confidence <1.0. Implementações maduras anotam essa confiança; as ruins tratam tudo como extraído e o grafo vira ficção.

Grafo desatualizado. Sem hooks de save/commit, o grafo defasa em horas; em uma semana, o agente trabalha com mapa de ontem. Sempre verifique: o hook está configurado? O daemon roda? find_callers retorna resultado coerente com grep?

Languages sem grammar maduro. Tree-sitter cobre bem Python/TS/Go/Rust/Java/Ruby; menos bem Solidity, Zig, Julia, R; mal Bash, COBOL, código legado. Em projeto poliglota exótico, o grafo fica com buracos.

Dependência de ferramenta unitária. Se você ata o workflow a um MCP que some (mantenedor abandonou, repo é tomado), você perde o investimento. Vale optar por implementações com formato exportável (GraphML, Neo4j Cypher) que permitem migrar.

Tomar stars como prova de qualidade. O espaço de MCPs de knowledge graph tem repos com crescimento de stars/forks anormal (>15k em <3 meses, fork-to-star ratio ~10%) — sinais de star inflation, não de adoção real. Avalie o código, os benchmarks reproduzíveis, e idealmente teste num repo seu antes de incorporar no workflow.

Relação com outras abordagens deste galho

Knowledge graph age no eixo estrutural (calls, imports, herança); indexação semântica age no eixo conceitual (similaridade de significado). Combinadas, cobrem as duas perguntas dominantes em codebase grande:

  • “Onde fala disso?” → semantic search
  • “O que isso afeta?” → knowledge graph

Lazy-load (01) e sandboxing (02) atuam em camadas anteriores (boot da sessão, output de tools). As quatro compõem sem conflito.

Veja também

Aprofundamento

Referências externas

  • tirth8205/code-review-graph — implementação Python/MCP com Tree-sitter, SQLite, blast-radius, comunidades via Leiden, hubs/bridges, export GraphML/Neo4j/Obsidian. MIT, 24 linguagens, benchmarks reproduzíveis. Cuidado: repo de ~3 meses com ~17k stars e 1846 forks — métricas suspeitas de star inflation (fork ratio anormal). A técnica é legítima; o repo específico merece due diligence antes de adotar em projeto profissional. Leia o código, reproduza os benchmarks num repo seu, decida.
  • Conceitos relacionados: Tree-sitter (parser incremental), Leiden community detection (algoritmo padrão pra clustering de grafos), GraphML / Cypher (formatos portáveis pra grafos).

Consumiu? Faça uma glosa em 02-Glosas/ se quiser destilar mais.