MÓDULO 3.1

🧩 Estendendo o Understand Anything: language extractors e custom agents

Crie extractors para novas linguagens com tree-sitter, registre no PluginRegistry e adicione agentes customizados que entram no pipeline gravando em .understand-anything/intermediate/.

6
Tópicos
60
Minutos
Avançado
Nível
Hands-on
Tipo
1

🏗️ Arquitetura de plugins: LanguageConfig e PluginRegistry

A arquitetura do Understand Anything é language-neutral por design. Toda lógica específica de linguagem vive em packages/core/src/languages/ como configs — não código forkado. O PluginRegistry mapeia extensões para configs e o resto do pipeline trata todas as linguagens igualmente.

🧠 Conceito principal

Config-first com escape hatch (hybrid). Para 90% das linguagens, uma config declarativa basta. Para AST exóticas, há customAnalyzer?: (node) => AnalysisResult.

  • id, displayName, extensions[] — identidade básica
  • treeSitter.grammarPackage — pacote npm com WASM
  • treeSitter.nodeTypes — map de function/class/import/export
  • concepts[] — termos idiomáticos para enriquecer o prompt

📄 LanguageConfig (exemplo Python)

{
  id: "python",
  displayName: "Python",
  extensions: [".py", ".pyi"],
  treeSitter: {
    grammarPackage: "tree-sitter-python",
    nodeTypes: {
      function: ["function_definition"],
      class:    ["class_definition"],
      import:   ["import_statement", "import_from_statement"],
      export:   [],
      typeAnnotation: ["type"]
    }
  },
  concepts: ["decorators", "list comprehensions", "generators", "dunder methods"],
  filePatterns: { config: "pyproject.toml" }
}
Detecção
Por extensão de arquivo
Registry
Map id → config
Snippet
Markdown por linguagem
Escape hatch
customAnalyzer opcional
2

🌳 Gramáticas tree-sitter em WASM

O Understand Anything usa web-tree-sitter (WASM) em vez do binding nativo. Razão registrada no CLAUDE.md: native bindings fail on darwin/arm64 + Node 24. Toda gramática nova precisa estar disponível como WASM.

📊 Por que WASM

  • Portabilidade: mesma gramática roda em CLI, dashboard browser e CI
  • Sem build nativo: nada de node-gyp, libtree-sitter, toolchain por plataforma
  • Determinístico: versão pinned no package.json = parsing reprodutível

✓ FAZER

  • Usar pacotes tree-sitter-X oficiais que distribuem .wasm
  • Pinar versão exata (sem ^ ou ~)
  • Validar com Parser.Language.load() antes de registrar
  • Mapear nodeTypes lendo o grammar.js ou node-types.json oficial

✗ EVITAR

  • Compilar gramática à mão se há pacote oficial
  • Usar tree-sitter nativo no dashboard (quebra browser)
  • Adivinhar nodeTypes — sempre conferir AST de um snippet
  • Aceitar gramática experimental sem testar fixture

💡 Dica prática

Antes de registrar uma linguagem, use o playground oficial do tree-sitter (tree-sitter.github.io/tree-sitter/playground) com um snippet representativo. Os node types visíveis ali são os mesmos que você deve colocar em nodeTypes.

Format
.wasm
Loader
web-tree-sitter
Source
npm package
Validação
Fixture mínima
3

📝 Snippets de prompt por linguagem

O file-analyzer usa prompt base genérico + snippet markdown da linguagem detectada. Sem snippet, o LLM aplica conceitos de TypeScript em código Python e produz resumos errados.

📄 skills/understand/languages/python.md (exemplo)

## Python idioms

- Decorators (`@staticmethod`, `@property`, `@dataclass`) attach behavior
- Dunder methods (`__init__`, `__call__`, `__enter__`) define protocol
- Context managers via `with` statement
- Generators (`yield`) for lazy iteration
- Type hints are optional but rich (Protocol, TypeVar, Literal)

## Common file roles
- `__init__.py` → package marker / re-exports
- `conftest.py` → pytest fixtures
- `setup.py` / `pyproject.toml` → packaging config

📊 O que entra no snippet

  • Idioms: 5-10 conceitos que aparecem em todo código real da linguagem
  • File roles: nomes de arquivo que carregam semântica especial
  • Pitfalls: armadilhas comuns que o LLM costuma confundir
  • NÃO incluir: documentação genérica da sintaxe (o LLM já sabe)
Local
skills/understand/languages/
Formato
Markdown
Tamanho
200-400 tokens
Estilo
Bullet idiomático
4

🤖 Anatomia de um agente customizado

Agentes vivem em understand-anything-plugin/agents/*.md. Cada um é um arquivo markdown com frontmatter YAML declarando contrato e corpo de prompt descrevendo a tarefa.

📄 Frontmatter mínimo

---
name: security-analyzer
description: Identifica padrões de risco de segurança e escreve security-report.json
tools: [Read, Glob, Grep, Write]
---

You are a security-focused subagent. Read each file in
`.understand-anything/intermediate/scan-result.json`, look for...

Output: write findings to
`.understand-anything/intermediate/security-report.json` with schema {...}.

⚠️ Atenção — campo model

NÃO incluir model: inherit no frontmatter. Foi removido na issue #167 porque opencode (e ferramentas não-Claude) tratam "inherit" como id literal de modelo e quebram com ProviderModelNotFoundError. Omita o campo — cada plataforma usa seu default.

✓ FAZER

  • Declarar todas as tools usadas em tools:
  • Escrever output sempre em .understand-anything/intermediate/
  • Definir schema do JSON de saída no prompt
  • Description curta mas com trigger phrases reais

✗ EVITAR

  • Incluir model: inherit (issue #167)
  • Devolver dados grandes pelo contexto em vez de Write
  • Depender de variáveis de ambiente sem fallback
  • Esquecer cleanup do intermediate após assembly
5

🔌 Onde plugar no pipeline (timeline das fases)

O /understand é orquestrado em 6+ fases sequenciais. Cada uma lê e escreve arquivos em intermediate/. Saber onde seu agente entra define o que ele recebe e o que pode produzir.

Extractor de Rust e custom agent plugados no pipeline Diagrama mostrando uma nova gramática tree-sitter (Rust) registrada no PluginRegistry alimentando o file-analyzer, e um custom agent lendo e gravando JSON em .understand-anything/intermediate. Pipeline /understand project-scanner compute-batches file-analyzer merge + arch tour + reviewer tree-sitter-rust grammar.wasm PluginRegistry.register() ponto de inserção #1 security-analyzer.md custom agent lê assembled-graph.json escreve security-report.json ponto de inserção #2 .understand-anything/intermediate/ scan-result.json · batches.json · batch-i-part-k.json · assembled-graph.json · security-report.json
Pontos de inserção do pipeline: uma nova gramática (ex.: tree-sitter-rust) entra via PluginRegistry e é consumida pelo file-analyzer. Um agente customizado pluga depois do merge, lendo assembled-graph.json e gravando JSON enriquecido em intermediate/.
1

Fase 1 — project-scanner

Determinístico (sem LLM)

Lista arquivos, detecta linguagem, pré-resolve imports. Escreve scan-result.json.

2

Fase 1.5 — compute-batches

Algorítmico (Louvain)

Lê scan-result, agrupa arquivos por comunidade no grafo de imports. Escreve batches.json.

3

Fase 2 — file-analyzer (×N)

LLM, paralelo

Um agente por batch. Escreve batch-i.json ou multi-part. Bom ponto para análises de código focadas em arquivo.

4

Fase 3 — merge + architecture-analyzer

Algorítmico + LLM

Merge dos batches → assembled-graph.json. Architecture analyzer adiciona camadas. Bom ponto para análises de alto nível.

5

Fase 4 — tour-builder + graph-reviewer

LLM

Cria tours guiados e revisa o grafo final. Escreve knowledge-graph.json definitivo + cleanup do intermediate.

💡 Dica prática

Plugue agentes que enriquecem nós existentes entre Fase 3 e Fase 4 (depois do assembly, antes do reviewer). Agentes que filtram ou substituem arquivos devem entrar logo após Fase 1.

6

🧪 Validação, schema e testes

Toda extensão precisa de fixture mínima, schema validation e snapshot vitest. Sem isso, regressões aparecem só quando um usuário tropeça no schema validator banner do dashboard.

🛡️ Estratégia de teste em 3 camadas

  • 1.Unit (vitest): testa parsing de fixture pequena na nova linguagem — pnpm --filter @understand-anything/core test
  • 2.Schema: valida que o output do agente bate com o tipo KnowledgeGraph em packages/core/src/schema.ts
  • 3.Dogfood: rode /understand --full no próprio repo do u-any — se o grafo regrediu, você quebrou algo

📄 Fixture típica

tests/fixtures/python/
├── simple_module.py          # 1 função + 1 import
├── class_with_methods.py     # 1 classe + 3 métodos
├── decorators_demo.py        # decorators e dunder
└── expected/
    ├── simple_module.json    # snapshot do scan-result
    └── class_with_methods.json

💡 Dica prática

Antes de abrir PR, copie sua versão modificada para ~/.claude/plugins/cache/understand-anything/.../<VERSION> e rode /understand --full em um projeto Python real. O dogfood pega o que o teste não pega.

Vitest
Unit + snapshot
Schema
KnowledgeGraph type
Dogfood
Em projeto real
CI
Antes do release

📌 Resumo do módulo

Arquitetura é language-neutral — config-first com escape hatch para AST exóticas
Gramáticas em WASM — web-tree-sitter, nada de native bindings
Snippet markdown por linguagem — idioms + file roles + pitfalls
Agentes: omita model: inherit — quebra opencode (issue #167)
Escreva sempre em intermediate/ — nunca devolva dado grande pelo contexto
Teste em 3 camadas — vitest + schema + dogfood

Próximo módulo:

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