
O problema
Agentes de código com IA (como o Claude Code) navegam pelo código usando grep para encontrar arquivos e read para abri-los. A unidade do read é o arquivo.
Mas o que acontece quando um único arquivo contém 20 funções?
Preciso do tipo CrossError, então faço read
→ 19 funções desnecessárias vêm junto
→ contaminação de contexto
O estudo “Lost in the Middle” (Stanford, 2024) reportou que o desempenho do LLM cai mais de 30% quando informações relevantes ficam enterradas no meio do contexto. “Context Length Alone Hurts LLM Performance” (Amazon, 2025) revelou que tokens desnecessários — mesmo que sejam espaços em branco — causam queda de desempenho entre 13,9% e 85%.
A pesquisa prova que “quanto menor o contexto, melhor”. No entanto, não havia ferramentas para dividir o código estruturalmente e incluir apenas o que é necessário.
filefunc preenche essa lacuna. É uma convenção de estrutura de código e uma ferramenta CLI para desenvolvimento de aplicações Go — serviços backend, ferramentas CLI, geradores de código, validadores SSOT.
Princípio central
Um arquivo, um conceito. Nome do arquivo = nome do conceito.
Vale para func, type, interface ou um conjunto de const. Todas as regras derivam desse único princípio.
# Sem filefunc
read utils.go → 20 funções, 19 desnecessárias. Contaminação de contexto.
# Com filefunc
read check_one_file_one_func.go → 1 função. Exatamente o que é necessário.
Não abrir 290 arquivos desnecessários é mais importante do que selecionar os 5-10 necessários.
O cidadão de primeira classe é o agente de IA
A estrutura de código do filefunc é pensada para agentes de IA, não para humanos.
Agentes de IA navegam com grep, não com ls. Sejam 500 ou 1000 arquivos, um único rg '//ff:func feature=validate' resolve. Quanto mais arquivos, menores eles são individualmente — e menos ruído acompanha cada read. Isso é uma vantagem.
A pergunta “não haverá arquivos demais?” pode surgir. Para humanos, sim. Mas o inconveniente humano é resolvido na camada de visualização (extensões do VSCode, por exemplo). A estrutura do filefunc não faz concessões para se adaptar ao fluxo humano.
O fluxo de navegação muda
Antes
Solicitação do usuário
→ ls, find porque não sabe o que existe
→ abre arquivo para entender a estrutura
→ grep de novo para encontrar arquivos relacionados
→ abre e encontra 20 funções, a maioria desnecessária
→ custo de navegação > tempo de trabalho real
Com filefunc
Solicitação do usuário + codebook fornecido
→ consulta o codebook e monta a query grep imediatamente
→ read de 20-30 arquivos (cada um = 1 conceito, tudo contexto válido)
→ trabalho
Fazer read de 30 arquivos não é problema se todos forem contexto válido. O problema é fazer read de 1 arquivo e receber o equivalente a 30.
Codebook
O codebook ocupa a posição mais importante no design do filefunc. Ele vem antes das regras de anotação. Um codebook bem projetado torna as queries grep precisas, e queries precisas resultam em listas de read limpas.
# codebook.yaml
required:
feature: [validate, annotate, chain, parse, codebook, report, cli]
type: [command, rule, parser, walker, model, formatter, loader, util]
optional:
pattern: [error-collection, file-visitor, rule-registry]
level: [error, warning, info]
As chaves required devem estar presentes em todas as anotações //ff:func e //ff:type. Isso garante a confiabilidade do grep — nenhuma chave required fica em branco. As chaves optional são usadas apenas quando pertinentes.
O codebook é o mapa do projeto para o agente de IA. Sem ele, a navegação começa sem conhecer o vocabulário. Com ele, queries precisas como feature=validate ou type=rule podem ser formuladas diretamente, sem exploração prévia.
Usar um valor que não existe no codebook em uma anotação é ERROR. Normalizar o vocabulário com o codebook expõe features ausentes, types duplicados e classificações ambíguas na listagem. Só é possível gerenciar o que pode ser visto. O próprio codebook também é validado — mínimo de 1 chave em required, valores duplicados não são permitidos, apenas letras minúsculas e hífens.
Anotações de metadados
As anotações são colocadas no topo de cada arquivo, para que os metadados possam ser lidos nas primeiras linhas sem fazer read do body inteiro.
//ff:func feature=validate type=rule control=sequence
//ff:what F1: validates one func per file
//ff:why Primary citizen is AI agent. 1 file 1 concept prevents context pollution.
//ff:checked llm=gpt-oss:20b hash=a3f8c1d2
func CheckOneFileOneFunc(gf *model.GoFile) []model.Violation {
| Anotação | Conteúdo | Obrigatória |
|---|---|---|
//ff:func | Metadados do arquivo func: feature, type, control etc. | Obrigatória para arquivos func |
//ff:type | Metadados do arquivo type: feature, type etc. | Obrigatória para arquivos type |
//ff:what | Descrição em 1 linha — o que esta função/tipo faz | Obrigatória |
//ff:why | Por que foi criada desta forma — justificativa da decisão | Opcional |
//ff:checked | Assinatura de validação do LLM (gerada automaticamente pelo llmc) | Automática |
O formato é //ff:key key1=value1 key2=value2. Permite busca imediata com grep/ripgrep e parsing por ferramentas graças aos key-values estruturados. Segue o mesmo padrão das convenções //go:generate e //go:embed do Go.
control — 1 func 1 control
control= é obrigatório em todos os arquivos func. O valor deve ser um dos três:
| control | Significado | Limite de depth |
|---|---|---|
sequence | Execução sequencial | 2 |
selection | Ramificação (switch) | 2 |
iteration | Repetição (loop) | dimension + 1 |
Baseia-se no teorema de Böhm-Jacopini (1966): todo programa é uma combinação de três estruturas de controle — sequence, selection e iteration. filefunc aplica isso no nível da função: cada função possui apenas um fluxo de controle.
Funções com control=iteration também requerem dimension=. Isso especifica a dimensão dos dados percorridos. dimension=1 indica uma lista plana (depth ≤ 2); dimension ≥ 2 exige aninhamento de tipos nomeados (struct/interface).
filefunc também valida a consistência entre o control declarado e o código real. Se control=selection mas não há switch, ou control=sequence mas há switch ou loop, é ERROR.
Pipeline de navegação do LLM
As anotações funcionam como um índice de busca. Opera sem infraestrutura pesada como embeddings vetoriais.
1. Redução estrutural (sem LLM, grep)
Monta query grep baseada no codebook
→ extrai 20-30 arquivos candidatos
2. Triagem por metadados (sem LLM ou LLM mínimo)
Lê apenas as anotações do topo de cada arquivo
→ reduz para 5-10 arquivos com base em name/input/output/what
3. Trabalho preciso (LLM grande, contexto mínimo)
read completo de apenas 5-10 arquivos
→ modificação/geração de código
O contexto diminui conforme as etapas avançam. Quando o LLM grande entra em ação, restam apenas os arquivos realmente necessários.
CLI
validate — validação das regras de estrutura de código
filefunc validate # diretório atual
filefunc validate /path/to/project # raiz do projeto explícita
filefunc validate --format json
Requer go.mod e codebook.yaml na raiz do projeto. Somente leitura. Violações resultam em exit code 1.
chain — rastreamento de relações de chamada
filefunc chain func RunAll # 1 grau (padrão)
filefunc chain func RunAll --chon 2 # 2 graus (inclui funções chamadas juntas)
filefunc chain func RunAll --chon 3 # 3 graus (máximo)
filefunc chain func RunAll --child-depth 3 # apenas chamadas descendentes
filefunc chain func RunAll --parent-depth 3 # apenas chamadores ascendentes
filefunc chain feature validate # feature completa
Análise de AST em tempo real. --chon define a distância do relacionamento. 1 grau inclui chamadas diretas e quem chama diretamente; 2 graus inclui funções chamadas em conjunto.
O go callgraph convencional faz análise estática de todas as chamadas, gerando milhares de nós. chain rastreia apenas dentro da mesma feature. A feature do codebook funciona como nível de zoom.
llmc — validação por LLM
filefunc llmc # diretório atual
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9
Valida se //ff:what corresponde ao body da função usando um LLM local (ollama). Pontua de 0,0 a 1,0, com limiar padrão de 0,8. Ao passar, registra automaticamente //ff:checked llm=nome-do-modelo hash=hash. Se o body mudar, o hash muda e a revalidação é necessária.
Isso resolve o problema central de drift nas anotações — //ff:what é linguagem natural e não pode ser verificado mecanicamente — usando um LLM pequeno. A abordagem é viável porque a correspondência 1 file 1 func garante uma relação 1:1 por arquivo.
Regras
Regras de estrutura de arquivo
| Regra | Violação |
|---|---|
| Um func por arquivo (nome do arquivo = nome da função) | ERROR |
| Um type por arquivo (nome do arquivo = nome do tipo) | ERROR |
| Métodos: 1 file 1 method | ERROR |
| init() não pode estar sozinho (deve acompanhar var ou func) | ERROR |
_test.go permite múltiplos func | Exceção |
| const semanticamente agrupados podem compartilhar um arquivo | Exceção |
Regras de qualidade de código
| Regra | Violação |
|---|---|
| Profundidade de aninhamento: sequence=2, selection=2, iteration=dimension+1 | ERROR |
| Máximo de 1000 linhas por func | ERROR |
| Recomendado: sequence/iteration 100 linhas, selection 300 linhas | WARNING |
A profundidade de aninhamento varia conforme o tipo de control. sequence e selection permitem depth 2. iteration permite dimension + 1 — dimension=1 (lista plana) resulta em depth 2, dimension=2 (estrutura aninhada) resulta em depth 3. Combinado com o padrão de early return do Go, a maioria do código se enquadra nesses limites.
selection (switch) tende a ter cases longos, então o limite recomendado de linhas é mais generoso: 300.
.ffignore
Colocar .ffignore na raiz do projeto exclui os caminhos listados de todos os comandos filefunc. Usa a mesma sintaxe do .gitignore.
vendor/
*.pb.go
*_gen.go
internal/legacy/
Destinado a excluir código onde as regras filefunc não podem ser aplicadas — como código gerado (saída de protobuf, codegen) ou código de vendor externo.
Integração com whyso
Como func = file, o histórico de alterações por função corresponde exatamente ao nível de arquivo.
whyso history check_ssac_openapi.go # histórico de alterações da função CheckSSaCOpenAPI
Quando um arquivo contém várias funções, é preciso vasculhar o diff para saber qual função mudou. Com filefunc, mudança no arquivo = mudança na função. Custo de rastreamento zero.
Detecção de acoplamento implícito
whyso coupling check_ssac_openapi.go
Funções modificadas juntas na mesma solicitação:
check_response_fields.go 8 vezes
check_err_status.go 5 vezes
types.go 4 vezes
Quando algo aparece repetidamente nas estatísticas de coupling sem uma relação explícita, é sinal de dependência oculta. Pode ser funções implementando a mesma regra de negócio por ângulos diferentes, um format sendo correspondido implicitamente sem interface, ou bugs que sempre aparecem juntos.
Por que apenas Go?
Estruturar código com filefunc em outras linguagens não é simples. gofmt impõe o formato do código, early return é convenção, não há exceções, e pacote = diretório. Expandir para outras linguagens exigiria uma estratégia de imposição estrutural equivalente ao gofmt. Isso está fora do escopo do filefunc.
Os alvos de aplicação também são claros: serviços backend, ferramentas CLI, geradores de código, validadores SSOT. Bibliotecas de algoritmos, programação de sistemas de baixo nível e hotpaths críticos de desempenho não são alvos.
Conclusão
A estrutura de código na era dos LLMs deve ser orientada à eficiência de navegação da IA, não à conveniência de navegação humana. filefunc é o primeiro passo dessa transição.
Um arquivo, um conceito. O codebook normaliza o vocabulário, as anotações adicionam metadados, e um único grep encontra o arquivo exato. Nenhum código desnecessário acompanha o read. A prevenção de contaminação de contexto é resolvida pela própria estrutura de arquivos.
Código: github.com/park-jun-woo/filefunc