filefunc — un fichier, un concept

Le problème

Les agents de code IA (comme Claude Code) naviguent dans le code avec grep pour trouver les fichiers, puis read pour les ouvrir. L’unité de read, c’est le fichier.

Que se passe-t-il quand un seul fichier contient 20 fonctions ?

On a besoin d'un seul type CrossError → read
→ 19 fonctions inutiles chargées avec
→ pollution du contexte

L’étude “Lost in the Middle” (Stanford, 2024) a montré que les performances des LLM chutent de plus de 30 % quand l’information pertinente est noyée au milieu du contexte. “Context Length Alone Hurts LLM Performance” (Amazon, 2025) révèle que même des tokens superflus vides dégradent les performances de 13,9 à 85 %.

La recherche le confirme : moins le contexte est long, mieux c’est. Pourtant, il n’existait aucun outil pour découper structurellement le code et n’inclure que le nécessaire.

filefunc comble ce vide. C’est une convention de structure et un outil CLI destinés au développement d’applications Go — services back-end, outils CLI, générateurs de code, validateurs SSOT.


Principe fondamental

Un fichier, un concept. Nom de fichier = nom du concept.

Que ce soit un func, un type, une interface ou un groupe de const, la règle est la même. Toutes les autres règles découlent de ce seul principe.

# Sans filefunc
read utils.go → 20 fonctions, 19 inutiles. Contexte pollué.

# Avec filefunc
read check_one_file_one_func.go → 1 fonction. Exactement ce qu'il faut.

Ne pas ouvrir 290 fichiers inutiles est plus important qu’en sélectionner 5 à 10 pertinents.


Le premier citoyen, c’est l’agent IA

La structure de code de filefunc est conçue pour les agents IA, pas pour les humains.

Les agents IA naviguent avec grep, pas avec ls. Qu’il y ait 500 ou 1000 fichiers, un seul rg '//ff:func feature=validate' suffit. Plus il y a de fichiers, plus chacun est petit — et moins de bruit est chargé à chaque read. C’est un avantage, pas un inconvénient.

On pourrait objecter : “n’est-ce pas trop de fichiers ?” Pour un humain, peut-être. Mais l’inconfort humain se règle au niveau de la couche de présentation (extensions VSCode, etc.). filefunc ne fait aucun compromis sur sa structure au profit du confort humain.


Le parcours de navigation change

Avant

Demande utilisateur
→ ls, find pour explorer sans savoir ce qui existe
→ Ouverture des fichiers pour comprendre la structure
→ Nouveau grep pour trouver les fichiers liés
→ On ouvre un fichier avec 20 fonctions, la plupart inutiles
→ Coût de navigation > temps de travail effectif

Avec filefunc

Demande utilisateur + codebook fourni
→ Le codebook permet de formuler immédiatement la requête grep
→ read de 20 à 30 fichiers (1 concept chacun, tout est contexte valide)
→ Travail

Lire 30 fichiers n’est pas un problème si tous apportent un contexte utile. Le vrai problème, c’est d’ouvrir 1 fichier et de se retrouver avec l’équivalent de 30 fichiers superflus.


Le codebook

Le codebook occupe la place la plus stratégique dans la conception de filefunc. Il passe avant les règles d’annotation. Un codebook bien conçu rend les requêtes grep précises ; des requêtes grep précises donnent une liste de read propre.

# 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]

Les clés required doivent être présentes dans chaque annotation //ff:func et //ff:type sans exception, afin de garantir la fiabilité des requêtes grep. Les clés optional ne sont utilisées que lorsqu’elles sont pertinentes.

Le codebook est la carte du projet pour l’agent IA. Sans codebook, la navigation commence sans vocabulaire. Avec codebook, on peut immédiatement lancer des requêtes précises comme feature=validate ou type=rule, sans phase d’exploration.

Utiliser dans une annotation une valeur absente du codebook est une ERROR. La normalisation du vocabulaire par le codebook révèle dans la liste les features manquantes, les types en double et les classifications ambiguës. Les lacunes doivent être visibles pour être gérées. Le codebook lui-même est soumis à validation : au moins une clé dans required, aucune valeur dupliquée, uniquement des minuscules avec tirets.


Annotations de métadonnées

Chaque fichier porte une annotation en tête. L’objectif : saisir les métadonnées en quelques lignes, sans avoir à lire tout le body.

//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 {
AnnotationContenuObligatoire
//ff:funcMétadonnées du fichier func : feature, type, control, etc.Obligatoire pour fichiers func
//ff:typeMétadonnées du fichier type : feature, type, etc.Obligatoire pour fichiers type
//ff:whatDescription en 1 ligne — ce que fait cette fonction/ce typeObligatoire
//ff:whyPourquoi ce choix de conception — justification de la décisionOptionnel
//ff:checkedSignature de validation LLM (générée automatiquement par llmc)Automatique

Le format est //ff:key key1=value1 key2=value2. Immédiatement searchable avec grep/ripgrep, et parsable par les outils grâce au format key-value structuré. C’est le même pattern que les directives Go //go:generate et //go:embed.

control — 1 func, 1 control

control= est obligatoire dans tous les fichiers func. La valeur est l’une des trois suivantes :

controlSignificationLimite de profondeur
sequenceExécution séquentielle2
selectionBranchement (switch)2
iterationBoucledimension + 1

Fondé sur le théorème de Böhm-Jacopini (1966) : tout programme est une combinaison de trois structures de contrôle — sequence, selection, iteration. filefunc applique ce principe au niveau de la fonction : une fonction, un flux de contrôle.

Pour les fonctions control=iteration, dimension= est également obligatoire. Il précise la dimension des données parcourues. dimension=1 correspond à une liste plate (profondeur ≤ 2) ; dimension ≥ 2 exige des types nommés (struct/interface) imbriqués.

filefunc vérifie aussi la cohérence entre le control déclaré et le code réel. Si control=selection est déclaré mais qu’il n’y a pas de switch, ou si control=sequence est déclaré avec un switch ou une boucle, c’est une ERROR.


Pipeline de navigation LLM

Les annotations jouent le rôle d’index de recherche, sans infrastructure lourde comme l’embedding vectoriel.

1. Réduction structurelle (sans LLM, grep)
   Construction de la requête grep à partir du codebook
   → Extraction de 20 à 30 fichiers candidats

2. Jugement sur les métadonnées (sans LLM ou avec un très petit modèle)
   Lecture des seules annotations en tête de chaque fichier
   → Réduction à 5 à 10 fichiers via name/input/output/what

3. Travail de précision (grand LLM, contexte minimal)
   Full read de seulement 5 à 10 fichiers
   → Modification ou génération de code

Le contexte se réduit à mesure que le pipeline avance. Quand le grand LLM intervient, seuls les fichiers vraiment nécessaires sont présents.


CLI

validate — vérification des règles de structure

filefunc validate                    # répertoire courant
filefunc validate /path/to/project   # racine de projet explicite
filefunc validate --format json

Un go.mod et un codebook.yaml sont nécessaires à la racine du projet. Lecture seule. Exit code 1 en cas de violation.

chain — suivi des relations d’appel

filefunc chain func RunAll              # 1er degré (défaut)
filefunc chain func RunAll --chon 2     # 2e degré (fonctions appelées ensemble incluses)
filefunc chain func RunAll --chon 3     # 3e degré (maximum)
filefunc chain func RunAll --child-depth 3   # appels descendants uniquement
filefunc chain func RunAll --parent-depth 3  # appelants amont uniquement
filefunc chain feature validate         # feature entière

Analyse AST en temps réel. --chon est la distance de relation. Le 1er degré correspond aux appels directs ; le 2e inclut les fonctions appelées dans le même contexte.

L’outil standard go callgraph analyse statiquement tous les appels et produit des milliers de nœuds. chain, lui, ne suit les relations qu’à l’intérieur d’un même feature. Le feature du codebook est le niveau de zoom.

llmc — validation LLM

filefunc llmc                           # répertoire courant
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9

Vérifie avec un LLM local (ollama) que //ff:what correspond au corps de la fonction. Score de 0,0 à 1,0, seuil par défaut 0,8. En cas de succès, //ff:checked llm=nom_du_modèle hash=hash est enregistré automatiquement. Si le body change, le hash diffère et une nouvelle validation est requise.

C’est la solution au problème fondamental du drift d’annotation — //ff:what, étant en langage naturel, ne peut pas être vérifié mécaniquement — résolue ici par un petit LLM. L’approche est viable précisément parce que 1 file 1 func garantit une correspondance 1:1 au niveau du fichier.


Règles

Règles de structure de fichier

RègleEn cas de violation
Un func par fichier (nom de fichier = nom de fonction)ERROR
Un type par fichier (nom de fichier = nom de type)ERROR
Méthodes : 1 file 1 methodERROR
init() ne peut pas être seul (doit accompagner un var ou un func)ERROR
Les fichiers _test.go peuvent contenir plusieurs fonctionsException
Les const sémantiquement liés peuvent être regroupés dans le même fichierException

Règles de qualité de code

RègleEn cas de violation
Profondeur d’imbrication : sequence=2, selection=2, iteration=dimension+1ERROR
Maximum 1000 lignes par fonctionERROR
Recommandation : sequence/iteration 100 lignes, selection 300 lignesWARNING

La profondeur d’imbrication varie selon le type de control. sequence et selection : profondeur 2. iteration : dimension + 1 — dimension=1 (liste plate) donne profondeur 2, dimension=2 (structure imbriquée) donne profondeur 3. Combiné avec le pattern early return de Go, la grande majorité du code s’inscrit naturellement dans ces limites.

selection (switch) ayant tendance à produire des case longs, la recommandation de lignes est élargie à 300.


.ffignore

Placer un fichier .ffignore à la racine du projet exclut les chemins correspondants de toutes les commandes filefunc. Même syntaxe que .gitignore.

vendor/
*.pb.go
*_gen.go
internal/legacy/

L’objectif est d’exclure le code sur lequel les règles filefunc ne peuvent pas être appliquées : code généré (protobuf, sorties de codegen), bibliothèques vendorisées externes.


Intégration avec whyso

Puisque func = file, l’historique des modifications au niveau de la fonction correspond exactement à l’historique au niveau du fichier.

whyso history check_ssac_openapi.go   # historique des modifications de CheckSSaCOpenAPI

Quand un fichier contient plusieurs fonctions, il faut parcourir le diff pour savoir laquelle a changé. Avec filefunc, modification du fichier = modification de la fonction. Coût de traçabilité : zéro.

Détection du couplage implicite

whyso coupling check_ssac_openapi.go

Fonctions modifiées ensemble dans les mêmes commits :
  check_response_fields.go  8 fois
  check_err_status.go       5 fois
  types.go                  4 fois

Quand des fichiers sans relation explicite apparaissent régulièrement dans les statistiques de couplage, c’est le signe d’une dépendance cachée — fonctions implémentant la même règle métier sous des angles différents, alignement implicite de format sans interface, bugs qui surgissent toujours ensemble.


Pourquoi Go seulement ?

La structuration filefunc est difficile à appliquer hors de Go. gofmt impose le formatage du code, l’early return est une convention établie, il n’y a pas d’exceptions, et package = répertoire. Étendre filefunc à d’autres langages nécessiterait une stratégie d’imposition de structure équivalente à gofmt — ce qui dépasse le périmètre de filefunc.

Le champ d’application est également clairement défini : services back-end, outils CLI, générateurs de code, validateurs SSOT. Les bibliothèques algorithmiques, la programmation système bas niveau et les hotpaths critiques en performance ne sont pas dans le périmètre.


En résumé

À l’ère des LLM, la structure du code doit être optimisée pour l’efficacité de navigation de l’IA, pas pour la commodité de navigation humaine. filefunc est le premier pas de cette transition.

Un fichier, un concept. Le codebook normalise le vocabulaire, les annotations attachent les métadonnées, un seul grep trouve le bon fichier. Un read ne charge jamais de code superflu. La structure du fichier elle-même prévient la pollution du contexte.

Code : github.com/park-jun-woo/filefunc