filefunc — un concepto por archivo

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ónContenidoObligatoria
//ff:funcMetadatos del archivo func: feature, type, control, etc.Obligatoria en archivos func
//ff:typeMetadatos del archivo type: feature, type, etc.Obligatoria en archivos type
//ff:whatDescripción en 1 línea: qué hace esta función/tipoObligatoria
//ff:whyPor qué se construyó así — justificación de la decisiónOpcional
//ff:checkedFirma 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:

controlSignificadoLímite de profundidad
sequenceEjecución secuencial2
selectionRamificación (switch)2
iterationIteració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

ReglaEn 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 methodERROR
init() no puede estar solo (debe acompañar a var o func)ERROR
_test.go permite múltiples funcExcepción
Los const semánticamente agrupados pueden estar en el mismo archivoExcepción

Reglas de calidad de código

ReglaEn caso de violación
Profundidad de anidamiento: sequence=2, selection=2, iteration=dimension+1ERROR
Máximo 1000 líneas por funcERROR
Recomendado: sequence/iteration 100 líneas, selection 300 líneasWARNING

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