🏗️ 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" }
}
🌳 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-Xoficiais que distribuem .wasm - ✓Pinar versão exata (sem ^ ou ~)
- ✓Validar com
Parser.Language.load()antes de registrar - ✓Mapear nodeTypes lendo o
grammar.jsou node-types.json oficial
✗ EVITAR
- ✗Compilar gramática à mão se há pacote oficial
- ✗Usar
tree-sitternativo 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.
📝 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)
🤖 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
🔌 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.
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/.
Fase 1 — project-scanner
Determinístico (sem LLM)
Lista arquivos, detecta linguagem, pré-resolve imports. Escreve scan-result.json.
Fase 1.5 — compute-batches
Algorítmico (Louvain)
Lê scan-result, agrupa arquivos por comunidade no grafo de imports. Escreve batches.json.
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.
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.
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.
🧪 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
KnowledgeGraphempackages/core/src/schema.ts - 3.Dogfood: rode
/understand --fullno 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.
📌 Resumo do módulo
model: inherit — quebra opencode (issue #167)Próximo módulo:
3.2 — Performance e escala: .understandignore, token reduction, semantic batching, output chunking