MÓDULO 3.2

⚡ Performance e escala: .understandignore, token reduction, semantic batching, output chunking

Quatro técnicas que mantêm o pipeline saudável em monorepos de 500+ arquivos: exclusão cirúrgica, redução de 85% nos tokens, batching por comunidade e chunking de saída.

6
Tópicos
60
Minutos
Avançado
Nível
Prático
Tipo
Grafo estrutural do Understand Anything: centenas de nós conectados representando arquivos, classes e funções, com layout ELK e containers por pasta.
Por que escalar importa: este é um grafo estrutural real renderizado pelo dashboard. Cada nó passou pelo pipeline. Sem .understandignore, semantic batching e output chunking, a análise de 500 arquivos como este custaria ~529k tokens e dropparia edges no caminho.
1

🚫 .understandignore: sintaxe gitignore-like

Antes de pensar em otimizar prompt, elimine o que nem deveria estar sendo analisado. O .understandignore usa sintaxe idêntica ao .gitignore e some com vendor, dist, fixtures e geração de código.

🧠 Como funciona

O createIgnoreFilter(projectRoot) carrega patterns nesta ordem:

  • 1.Hardcoded defaults — node_modules/, .git/, dist/, build/, *.lock, *.min.js, etc.
  • 2..understand-anything/.understandignore — junto com o output
  • 3..understandignore na raiz — alternativa visível no repo

Patterns somam. ! de negação no user file pode forçar inclusão (ex: !dist/ reanalisa dist/).

📄 Exemplo .understandignore

# Exclusões adicionais
docs/legacy/
**/__snapshots__/
**/*.generated.ts
storybook-static/

# Force-include algo que o default exclui
!dist/types.d.ts

💡 Dica prática

Na primeira execução, o pipeline auto-gera um .understandignore comentado e pausa. Revise antes de continuar — 30 segundos de leitura podem economizar 10 minutos de análise inútil.

Lib
npm ignore
Sintaxe
.gitignore-compat
Override
! força inclusão
Auto-gen
Comentado, primeira run
2

💰 Onde os tokens somem (e como recuperar 85%)

Em projeto TS+React de 500 arquivos, o baseline é ~529.000 tokens de input. Metade é desperdício puro: o mesmo allProjectFiles repetido em cada batch.

📊 Dados de pesquisa — breakdown 500-file

  • ~167.000 tokens (50%) — allProjectFiles × 67 batches (puro repeat)
  • ~134.000 tokens (40%) — file-analyzer-prompt × 67 batches
  • ~80.000 tokens (24%) — payload do tour builder
  • ~68.000 tokens (20%) — addendums de linguagem × 67 batches
  • ~58.000 tokens (17%) — graph reviewer recebendo grafo + inventário
  • ~22.000 tokens (7%) — architecture analyzer

🔧 Cinco mudanças (C1–C5)

C1  Pre-resolve imports no scanner   → mata 167k tokens repetidos
C2  Encolher file-analyzer prompt    → -40% no prompt
C3  Lazy language addendum           → só carrega se linguagem aparecer
C4  Summary cache p/ tour builder    → reaproveita Phase 2
C5  Reviewer com inventory comprimido → -50% no Phase 6

Total: -85% a -90% em 500-file project
Baseline
529k tokens
Pós C1–C5
~60k tokens
Schema
Backward-compat
Flags
--full / incremental
3

🧠 Semantic batching com Louvain

Count-based batching (20-30 files/batch) quebra módulos no meio. Arquivos que se importam mutuamente acabam em batches diferentes — e o file-analyzer só vê edges dentro do batch. Calls / related / inherits / implements entre módulos somem.

✗ Count-based (antigo)

  • Files por contagem fixa (25 cada)
  • auth/login.ts no batch 3, auth/session.ts no 7 → edge perdida
  • Cross-module: só sobrevive imports (recuperado pelo safety net)
  • calls/related/inherits dropados silenciosamente

✓ Louvain (novo)

  • Community detection no grafo de imports
  • Módulo auth/* fica num único batch
  • Edges semânticas preservadas dentro da comunidade
  • neighborMap dá contexto cross-batch sem inflar token

📄 compute-batches.mjs (fase 1.5)

node $SKILL_DIR/compute-batches.mjs $PROJECT_ROOT [--changed-files=...]

Input:  .understand-anything/intermediate/scan-result.json
Output: .understand-anything/intermediate/batches.json

Algoritmo:
  1. Constrói grafo (nós=arquivos, edges=imports) com graphology
  2. Roda Louvain modularity para detectar comunidades
  3. Cada comunidade vira um batch (split se exceder threshold)
  4. Anexa neighborMap (vizinhos próximos fora do batch)

💡 Dica prática

Pura algorítmica — sem LLM. Roda em milissegundos mesmo com 500 arquivos. Se quiser incremental, passe --changed-files=... para batchar só o que mudou.

.understandignore filtra, Louvain agrupa, chunking divide o output Diagrama em três faixas mostrando: paths sendo filtrados por .understandignore antes do pipeline, arquivos restantes agrupados em batches semânticos por Louvain, e cada batch sendo dividido em partes para caber no context window do LLM. 1. .understandignore filtra paths antes do pipeline src/auth/*.ts src/api/*.ts node_modules/ dist/*.min.js .understandignore gitignore-like patterns ! força inclusão src/auth/*.ts src/api/*.ts vendor/dist descartados 2. Louvain agrupa por comunidade (mantém módulo junto) batch 1 · auth community login.ts session.ts jwt.ts batch 2 · api community routes.ts handlers.ts batch 3 · store community orderSlice.ts cartSlice.ts 3. Output chunking: sem (overflow) vs com (cabe no budget) sem chunking batch-3.json > max_tokens → minimal mode, edges dropped overflow com chunking batch-3-part-1.json batch-3-part-2.json batch-3-part-3.json cada parte ≤ budget context window budget
Três defesas em sequência: .understandignore remove ruído antes da análise, Louvain mantém cada módulo num único batch (preservando edges calls/related) e o chunking divide saídas grandes em batch-i-part-k.json para caber no budget de Write — evitando o "minimal output mode" do issue #159.
4

✂️ Output chunking: batch-i-part-k.json

Em LLM backends com output cap baixo (notório: Bedrock OPUS, max_tokens 4096-8192), o file-analyzer enche o budget de Write antes de terminar e entra em "minimal output mode" — dropando nodes/edges em silêncio.

⚠️ Sintoma do issue #159

"Batch X failed (output limit)" aparece no log. O grafo final tem buracos. Pior: o agente não falha visivelmente — só produz output pobre. Bedrock OPUS em projetos de 100+ arquivos pega muito.

🛠️ Solução: self-check + split

O file-analyzer agora:

  • 1.Estima tamanho do JSON antes de chamar Write
  • 2.Se exceder threshold, escreve batch-i-part-1.json, part-2.json...
  • 3.Cada part é JSON válido completo (nodes + edges parciais)
  • 4.O merge-batch-graphs.py já aceita glob multi-part — zero código novo

📄 Naming convention

intermediate/
├── batch-1.json           # cabe inteiro — single file
├── batch-2-part-1.json    # batch grande dividido
├── batch-2-part-2.json
├── batch-2-part-3.json
└── batch-3.json

Merge regex aceita ambos: batch-(-part-)?.json
Threshold
~3500 tokens out
Backend alvo
Bedrock OPUS
Merge
Sem mudança no .py
Backward-compat
Single-file OK
5

📦 Import map pré-resolvido (deep dive em C1)

A maior economia vem do C1: parsear imports na Fase 1 e escrever um importMap no scan-result.json. Cada batch recebe só os imports relevantes, não a lista de 500 arquivos.

📄 scan-result.json — campo novo

{
  "importMap": {
    "src/index.ts": ["src/utils.ts", "src/config.ts"],
    "src/utils.ts": [],
    "src/components/App.tsx": ["src/hooks/useAuth.ts", "src/store/index.ts"]
  }
}

Regras:
- Keys e values são project-relative paths
- Externals (node_modules, builtins) são omitidos
- Por-linguagem: TS import/require, Python import/from, Go import blocks
1

Step 8 no scanner

Determinístico, não-LLM

Para cada arquivo source: lê conteúdo, extrai imports per-language, resolve paths relativos contra a lista descoberta.

2

Phase 2 mudou

Per-batch context

file-analyzer recebe só o subset do importMap relativo aos arquivos do batch. -2.500 tokens × 67 batches.

3

Safety net

recover_imports_from_scan

Em merge-batch-graphs.py, recupera deterministicamente edges imports a partir do importMap mesmo se o LLM não emitiu.

💡 Dica prática

O safety net não recupera calls/related/inherits/implements — esses dependem do LLM. Por isso semantic batching (tópico 3) é complementar ao C1: um trata imports, o outro semântica.

6

📈 Medir antes de otimizar

Otimizar sem medir é chute. Conte tokens por fase antes e depois de cada mudança. Sem profile, você acha que o problema é o LLM quando é o file list repetido 67 vezes.

📏 Métricas mínimas

  • Tokens input por fase — somar prompts + payloads
  • Tokens output por fase — detectar quando bateu cap
  • Wall time por fase — paralelização ajudando?
  • Cobertura de edges — % de edges semânticas vs apenas imports

✓ FAZER

  • Benchmark fixo (500-file TS project) reproduzível
  • Comparar antes/depois com a MESMA seed
  • Logar contagens em intermediate/metrics.json
  • Regression test no CI: tokens não podem aumentar 10%+

✗ EVITAR

  • Comparar runs com projetos diferentes
  • Confundir output cap com qualidade do LLM
  • Otimizar fase 7% (architecture) antes da fase 50% (file list)
  • Aceitar regressão sem isolar a causa

💡 Dica prática

Use o próprio u-any como benchmark recorrente — ele tem TS + agentes + dashboard. Roda em ~5 min e cabe nos limites de qualquer backend. Bom para CI smoke test.

📌 Resumo do módulo

.understandignore primeiro — não analise o que não importa
C1–C5 = -85% tokens — em 500-file project
Louvain > count-based — preserva edges semânticas cross-module
Output chunking salva Bedrock OPUS — multi-part JSON sem mudar merge
importMap = safety net determinístico — edges imports nunca somem
Meça antes de otimizar — benchmark fixo, regression test no CI

Próximo módulo:

3.3 — Camada de domínio e escala visual: business domain knowledge + graph layout scaling