
El problema
Los agentes de código IA (como Claude Code) exploran el código usando grep para encontrar archivos y read para abrirlos. La unidad de read es el archivo.
¿Qué ocurre cuando un archivo contiene 20 funciones?
Necesitas el tipo CrossError, haces read
→ 19 funciones innecesarias vienen incluidas
→ contaminación del contexto
“Lost in the Middle” (Stanford, 2024) reportó que el rendimiento de los LLM cae más de un 30% cuando la información relevante queda enterrada en el centro del contexto. “Context Length Alone Hurts LLM Performance” (Amazon, 2025) reveló que los tokens innecesarios, aunque sean espacios en blanco, degradan el rendimiento entre un 13,9% y un 85%.
La investigación demuestra que “cuanto más corto el contexto, mejor”. Sin embargo, no existía ninguna herramienta que partiera el código de forma estructural para incluir solo lo necesario.
filefunc ocupa ese hueco. Es una convención de estructura de código y una herramienta CLI para el desarrollo de aplicaciones Go: servicios backend, herramientas CLI, generadores de código y validadores SSOT.
Principio fundamental
Un concepto por archivo. Nombre del archivo = nombre del concepto.
Aplica igual a func, type, interface o un grupo de const. Todas las demás reglas se derivan de este único principio.
# Sin filefunc
read utils.go → 20 func, 19 innecesarias. Contexto contaminado.
# Con filefunc
read check_one_file_one_func.go → 1 func. Exactamente lo que se necesita.
Es más importante no abrir los 290 archivos innecesarios que abrir los 5-10 necesarios.
El ciudadano de primera clase es el agente IA
La estructura de código de filefunc está diseñada para agentes IA, no para personas.
Los agentes IA exploran con grep, no con ls. Sean 500 o 1000 archivos, un solo rg '//ff:func feature=validate' es suficiente. Cuantos más archivos haya, más pequeño es cada uno, y menos ruido se arrastra en cada read — lo que resulta ventajoso.
Puede surgir la pregunta: “¿No habrá demasiados archivos?”. Para una persona, sí. Pero la incomodidad humana se resuelve en la capa de visualización (extensiones de VSCode, etc.). La estructura de filefunc no se compromete para adaptarse al ser humano.
El flujo de exploración cambia
Tradicional
Petición del usuario
→ No saber qué hay, usar ls y find
→ Abrir archivos para entender la estructura
→ Volver a hacer grep para encontrar archivos relacionados
→ Al abrirlo, 20 func, la mayoría innecesarias
→ Coste de exploración > tiempo de trabajo real
Con filefunc
Petición del usuario + codebook proporcionado
→ Ver el codebook y construir la query grep de inmediato
→ read de 20-30 archivos (cada uno = 1 concepto, todo contexto válido)
→ Trabajar
Si se leen 30 archivos y todos son contexto válido, los 30 no son un problema. El problema es leer 1 archivo y que arrastre el equivalente a 30.
El codebook
El codebook ocupa el lugar más importante en el diseño de filefunc. Va antes que las reglas de anotación. Un codebook bien diseñado produce queries grep precisas; un grep preciso genera una lista de read limpia.
# 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]
Las claves required deben estar presentes en todas las anotaciones //ff:func y //ff:type sin excepción. Esto garantiza la fiabilidad del grep — las claves required no tienen huecos. Las claves optional solo se usan cuando son relevantes.
El codebook es el mapa del proyecto para el agente IA. Sin codebook, la exploración comienza sin conocer el vocabulario. Con codebook, se pueden lanzar de inmediato queries precisas como feature=validate o type=rule, sin necesidad de exploración previa.
Usar en una anotación un valor que no esté en el codebook es un ERROR. Normalizar el vocabulario con el codebook hace visibles los features faltantes, los types duplicados y las clasificaciones ambiguas. Solo cuando los huecos son visibles es posible gestionarlos. El propio codebook también es objeto de validación: mínimo 1 clave en required, no se permiten valores duplicados, solo minúsculas con guiones.
Anotaciones de metadatos
Se añade una anotación al inicio de cada archivo para que los metadatos sean legibles en las primeras líneas, sin necesidad de leer todo el cuerpo.
//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 {
| Anotación | Contenido | Obligatoria |
|---|---|---|
//ff:func | Metadatos del archivo func: feature, type, control, etc. | Obligatoria en archivos func |
//ff:type | Metadatos del archivo type: feature, type, etc. | Obligatoria en archivos type |
//ff:what | Descripción en 1 línea: qué hace esta función/tipo | Obligatoria |
//ff:why | Por qué se construyó así — justificación de la decisión | Opcional |
//ff:checked | Firma de verificación LLM (generada automáticamente por llmc) | Automática |
El formato es //ff:key key1=value1 key2=value2. Permite búsqueda inmediata con grep/ripgrep y puede ser parseado por herramientas gracias a su estructura key-value. Sigue el mismo patrón de las convenciones //go:generate y //go:embed de Go.
control — 1 func 1 control
control= es obligatorio en todos los archivos func. El valor es uno de tres:
| control | Significado | Límite de profundidad |
|---|---|---|
sequence | Ejecución secuencial | 2 |
selection | Ramificación (switch) | 2 |
iteration | Iteración (loop) | dimension + 1 |
Se basa en el teorema de Böhm-Jacopini (1966): todo programa es una combinación de tres estructuras de control — sequence, selection e iteration. filefunc aplica esto a nivel de función: una función tiene un único flujo de control.
Las funciones con control=iteration también requieren dimension=. Especifica la dimensión de los datos iterados. Con dimension=1, se trata de una lista plana (profundidad ≤ 2); con dimension ≥ 2, se necesita anidamiento de tipos con nombre (struct/interface).
filefunc también verifica la coherencia entre control y el código real. Si control=selection pero no hay switch, o si control=sequence pero hay un switch o loop, es ERROR.
Pipeline de exploración LLM
Las anotaciones actúan como un índice de búsqueda, sin necesidad de infraestructura pesada como embeddings vectoriales.
1. Reducción estructural (sin LLM, con grep)
Construir query grep basada en el codebook
→ Extraer 20-30 archivos candidatos
2. Juicio por metadatos (sin LLM o con uno muy pequeño)
Leer solo las anotaciones del inicio de cada archivo
→ Reducir a 5-10 mediante name/input/output/what
3. Trabajo de precisión (LLM grande, contexto mínimo)
Full read de solo 5-10 archivos
→ Modificación/generación de código
A medida que avanza el proceso, el contexto se reduce. Cuando se incorpora el LLM grande, solo quedan los archivos realmente necesarios.
CLI
validate — verificación de reglas de estructura de código
filefunc validate # directorio actual
filefunc validate /path/to/project # raíz del proyecto explícita
filefunc validate --format json
Requiere go.mod y codebook.yaml en la raíz del proyecto. Solo lectura. Sale con código 1 si hay violaciones.
chain — rastreo de relaciones de llamada
filefunc chain func RunAll # 1er grado (por defecto)
filefunc chain func RunAll --chon 2 # 2º grado (incluye funciones co-invocadas)
filefunc chain func RunAll --chon 3 # 3er grado (máximo)
filefunc chain func RunAll --child-depth 3 # solo llamadas descendentes
filefunc chain func RunAll --parent-depth 3 # solo llamadores ascendentes
filefunc chain feature validate # toda la feature
Análisis AST en tiempo real. --chon es la distancia de relación. El 1er grado incluye llamadas directas hacia y desde la función; el 2º grado añade las funciones co-invocadas.
El go callgraph tradicional analiza estáticamente todas las llamadas y produce miles de nodos. chain solo rastrea dentro de la misma feature. La feature del codebook actúa como nivel de zoom.
llmc — verificación LLM
filefunc llmc # directorio actual
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9
Verifica mediante un LLM local (ollama) que //ff:what sea coherente con el cuerpo de la función. Produce una puntuación de 0,0 a 1,0 con un umbral por defecto de 0,8. Si supera el umbral, registra automáticamente //ff:checked llm=nombre-del-modelo hash=hash. Si el cuerpo cambia, el hash cambia y se requiere reverificación.
Resuelve el problema central del annotation drift — //ff:what es lenguaje natural y no puede verificarse mecánicamente — usando un LLM pequeño. Es posible porque 1 file 1 func garantiza una correspondencia 1:1 a nivel de archivo.
Reglas
Reglas de estructura de archivo
| Regla | En caso de violación |
|---|---|
| Una func por archivo (nombre del archivo = nombre de la función) | ERROR |
| Un type por archivo (nombre del archivo = nombre del tipo) | ERROR |
| Métodos: 1 file 1 method | ERROR |
| init() no puede estar solo (debe acompañar a var o func) | ERROR |
_test.go permite múltiples func | Excepción |
| Los const semánticamente agrupados pueden estar en el mismo archivo | Excepción |
Reglas de calidad de código
| Regla | En caso de violación |
|---|---|
| Profundidad de anidamiento: sequence=2, selection=2, iteration=dimension+1 | ERROR |
| Máximo 1000 líneas por func | ERROR |
| Recomendado: sequence/iteration 100 líneas, selection 300 líneas | WARNING |
La profundidad de anidamiento varía según el tipo de control. sequence y selection tienen profundidad 2. iteration tiene dimension + 1: con dimension=1 (lista plana), profundidad 2; con dimension=2 (estructura anidada), profundidad 3. Combinado con el patrón de early return de Go, la gran mayoría del código cabe dentro de estos límites.
selection (switch) tiende a tener cases largos, por lo que el límite recomendado de líneas es más amplio: 300.
.ffignore
Colocar un .ffignore en la raíz del proyecto excluye las rutas indicadas de todos los comandos de filefunc. La sintaxis es idéntica a .gitignore.
vendor/
*.pb.go
*_gen.go
internal/legacy/
Sirve para excluir código que no puede someterse a las reglas de filefunc, como código generado (protobuf, salida de codegen) o código externo de vendors.
Integración con whyso
Como func = file, el historial de cambios a nivel de función se corresponde exactamente con el historial a nivel de archivo.
whyso history check_ssac_openapi.go # historial de cambios de la función CheckSSaCOpenAPI
Cuando un archivo contiene varias funciones, hay que revisar el diff para saber cuál cambió. Con filefunc, cambio de archivo = cambio de función. Coste de rastreo: cero.
Detección de acoplamiento implícito
whyso coupling check_ssac_openapi.go
Funciones modificadas juntas en la misma petición:
check_response_fields.go 8 veces
check_err_status.go 5 veces
types.go 4 veces
Si algo aparece repetidamente en las estadísticas de coupling sin una relación explícita, es señal de una dependencia oculta: funciones que implementan la misma regla de negocio desde ángulos distintos, formato implícitamente acordado sin una interface, o bugs que siempre aparecen juntos.
Por qué solo Go
Con otros lenguajes, estructurar el código al estilo filefunc es difícil. En Go, gofmt impone el formato del código, el early return es convención, no hay excepciones y el paquete equivale al directorio. Extenderlo a otros lenguajes requeriría una estrategia de imposición estructural al nivel de gofmt. Eso queda fuera del alcance de filefunc.
Los casos de uso también están bien definidos: servicios backend, herramientas CLI, generadores de código y validadores SSOT. Las librerías de algoritmos, la programación de sistemas de bajo nivel y los hot paths críticos para el rendimiento no son objetivos.
Conclusión
En la era de los LLM, la estructura del código debe adaptarse a la eficiencia de exploración de la IA, no a la comodidad de navegación humana. filefunc es el primer paso de esa transición.
Un concepto por archivo. El codebook normaliza el vocabulario, las anotaciones adjuntan metadatos, y un único grep encuentra los archivos exactos. Un read no arrastra código innecesario. La propia estructura del archivo se encarga de bloquear la contaminación del contexto.
Código: github.com/park-jun-woo/filefunc