filefunc — eine Datei, ein Konzept

Das Problem

KI-Code-Agenten (wie Claude Code) navigieren durch Code, indem sie mit grep Dateien suchen und mit read öffnen. Die Einheit von read ist die Datei.

Was passiert, wenn eine Datei 20 Funktionen enthält?

CrossError type wird benötigt → read
→ 19 unnötige Funktionen kommen mit
→ Context-Verschmutzung

„Lost in the Middle" (Stanford, 2024) berichtete, dass die LLM-Leistung um mehr als 30 % sinkt, wenn relevante Informationen in der Mitte des Kontexts vergraben sind. „Context Length Alone Hurts LLM Performance" (Amazon, 2025) zeigte, dass unnötige Token — selbst wenn es nur Leerzeichen sind — die Leistung um 13,9 bis 85 % senken.

Die Forschung hat bewiesen: je kürzer der Kontext, desto besser. Doch es fehlte ein Werkzeug, das Code strukturell so aufteilt, dass nur das Notwendige enthalten ist.

filefunc füllt diese Lücke. Es ist eine Codestruktur-Konvention und ein CLI-Tool für Go-Anwendungen — Backend-Services, CLI-Tools, Code-Generatoren, SSOT-Validatoren.


Grundprinzip

Eine Datei, ein Konzept. Dateiname = Konzeptname.

Ob func, type, interface oder eine zusammengehörige const-Gruppe — das Prinzip gilt überall. Alle Regeln leiten sich aus diesem einen Grundsatz ab.

# Ohne filefunc
read utils.go → 20 funcs, 19 unnötig. Context-Verschmutzung.

# Mit filefunc
read check_one_file_one_func.go → 1 func. Genau das Benötigte.

Wichtiger als die 5–10 richtigen Dateien zu öffnen ist es, die 290 falschen gar nicht erst zu öffnen.


Der erste Bürger ist der KI-Agent

Die Codestruktur von filefunc ist nicht auf Menschen, sondern auf KI-Agenten ausgerichtet.

KI-Agenten navigieren nicht mit ls, sondern mit grep. Ob 500 oder 1000 Dateien — ein einziges rg '//ff:func feature=validate' reicht. Je mehr Dateien, desto kleiner jede einzelne, und desto weniger Rauschen kommt bei einem read mit — ein klarer Vorteil.

„Werden es nicht zu viele Dateien?" — für Menschen ja. Aber die Unannehmlichkeit für Menschen löst man auf der View-Schicht (z. B. VSCode-Erweiterungen). Die Struktur von filefunc wird nicht zugunsten menschlicher Bequemlichkeit kompromittiert.


Die Navigationswege ändern sich

Vorher

Nutzeranfrage
→ ls, find, weil unklar was vorhanden ist
→ Datei öffnen, Struktur verstehen
→ Erneut grep, um verwandte Dateien zu finden
→ Geöffnet: 20 funcs, die meisten unnötig
→ Navigationskosten > eigentliche Arbeitszeit

Mit filefunc

Nutzeranfrage + Codebook bereitgestellt
→ Codebook lesen, grep-Query sofort formulieren
→ 20–30 Dateien lesen (je 1 Konzept, alles nützlicher Kontext)
→ Arbeit erledigen

30 Dateien zu lesen ist kein Problem, wenn alle 30 nützlichen Kontext liefern. Das Problem ist, wenn eine einzige Datei den Inhalt von 30 mitschleppt.


Das Codebook

Das Codebook nimmt in filefuncs Design die zentrale Stelle ein — noch vor den Annotation-Regeln. Ein gut gestaltetes Codebook macht grep-Queries präzise, und präzise grep-Queries halten die read-Liste sauber.

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

required-Schlüssel müssen in jeder //ff:func- und //ff:type-Annotation vorhanden sein — um die Zuverlässigkeit von grep zu gewährleisten: keine Lücken bei required. optional-Schlüssel werden nur verwendet, wenn sie relevant sind.

Das Codebook ist die Projektkarte für den KI-Agenten. Ohne Codebook beginnt die Navigation ohne bekanntes Vokabular. Mit Codebook können sofort präzise Queries wie feature=validate oder type=rule abgesetzt werden — ganz ohne Erkundung.

Werte, die nicht im Codebook stehen, führen in Annotationen zu einem ERROR. Durch die Normalisierung des Vokabulars über das Codebook werden fehlende features, doppelte types und unklare Einordnungen sichtbar. Nur wenn Lücken sichtbar sind, lassen sie sich verwalten. Das Codebook selbst wird ebenfalls validiert — mindestens ein Schlüssel unter required, keine doppelten Werte, nur Kleinbuchstaben und Bindestriche.


Metadata-Annotationen

Jede Datei erhält am Anfang eine Annotation. So lässt sich die Metainformation aus den ersten Zeilen entnehmen, ohne den gesamten Body lesen zu müssen.

//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 {
AnnotationInhaltPflicht
//ff:funcMetadaten für func-Dateien: feature, type, control usw.Pflicht für func-Dateien
//ff:typeMetadaten für type-Dateien: feature, type usw.Pflicht für type-Dateien
//ff:whatEinzeilige Beschreibung — was tut diese Funktion/dieser Typ?Pflicht
//ff:whyWarum so umgesetzt — die Begründung hinter der EntscheidungOptional
//ff:checkedLLM-Verifikationssignatur (automatisch von llmc generiert)Automatisch

Das Format ist //ff:key key1=value1 key2=value2. Sofort mit grep/ripgrep durchsuchbar und durch das strukturierte key-value-Format werkzeuglesbar. Das Muster entspricht Go-Konventionen wie //go:generate und //go:embed.

control — 1 func 1 control

control= ist für alle func-Dateien Pflicht. Der Wert ist einer von dreien:

controlBedeutungTiefenbegrenzung
sequenceSequenzielle Ausführung2
selectionVerzweigung (switch)2
iterationWiederholung (loop)dimension + 1

Dies basiert auf dem Böhm-Jacopini-Theorem (1966): Jedes Programm lässt sich als Kombination der drei Kontrollstrukturen sequence, selection und iteration ausdrücken. filefunc erzwingt dies auf Funktionsebene — eine Funktion hat genau einen Kontrollfluss.

Funktionen mit control=iteration müssen zusätzlich dimension= angeben — die Dimension der iterierten Daten. dimension=1 bedeutet eine flache Liste (depth ≤ 2), dimension ≥ 2 erfordert benannte Typen (struct/interface) für verschachtelte Strukturen.

filefunc prüft auch die Übereinstimmung zwischen control und tatsächlichem Code. Hat eine Funktion control=selection, enthält aber kein switch, oder hat sie control=sequence, enthält aber ein switch oder einen loop — das ist ein ERROR.


LLM-Navigationspipeline

Annotationen fungieren als Suchindex. Der Betrieb erfordert keine schwere Infrastruktur wie Vektor-Embeddings.

1. Strukturelle Eingrenzung (kein LLM nötig, grep)
   grep-Query auf Basis des Codebooks formulieren
   → 20–30 Kandidatendateien extrahieren

2. Meta-Beurteilung (kein LLM oder nur sehr kleines LLM nötig)
   Nur die Annotationen am Dateianfang lesen
   → Auf 5–10 Dateien anhand von name/input/output/what eingrenzen

3. Präzisionsarbeit (großes LLM, minimaler Kontext)
   Nur 5–10 Dateien vollständig lesen
   → Code bearbeiten oder generieren

Mit jedem Schritt schrumpft der Kontext. Wenn das große LLM zum Einsatz kommt, sind nur noch die wirklich benötigten Dateien übrig.


CLI

validate — Codestrukturregeln prüfen

filefunc validate                    # aktuelles Verzeichnis
filefunc validate /path/to/project   # expliziter Projektstamm
filefunc validate --format json

Im Projektstamm werden go.mod und codebook.yaml benötigt. Nur lesend. Bei Verstößen: exit code 1.

chain — Aufrufbeziehungen verfolgen

filefunc chain func RunAll              # 1. Grad (Standard)
filefunc chain func RunAll --chon 2     # 2. Grad (einschließlich gemeinsam aufgerufener Funktionen)
filefunc chain func RunAll --chon 3     # 3. Grad (Maximum)
filefunc chain func RunAll --child-depth 3   # nur Unteraufrufe
filefunc chain func RunAll --parent-depth 3  # nur übergeordnete Aufrufer
filefunc chain feature validate         # gesamtes feature

Echtzeit-AST-Analyse. --chon ist der Beziehungsabstand. Grad 1 umfasst direkte Aufrufer und Aufgerufene, Grad 2 schließt Funktionen ein, die gemeinsam aufgerufen werden.

Das klassische go callgraph analysiert alle Aufrufe statisch und liefert Tausende von Knoten. chain verfolgt nur innerhalb desselben feature. Das feature im Codebook ist der Zoom-Level.

llmc — LLM-Verifikation

filefunc llmc                           # aktuelles Verzeichnis
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9

Prüft mit einem lokalen LLM (ollama), ob //ff:what mit dem func-Body übereinstimmt. Score von 0,0 bis 1,0, Standard-Schwellenwert 0,8. Bei Bestehen wird //ff:checked llm=Modellname hash=Hash automatisch eingetragen. Ändert sich der Body, ändert sich der Hash — eine erneute Verifikation wird nötig.

Dies löst das Kernanproblem von Annotation-Drift — //ff:what ist natürliche Sprache und lässt sich nicht maschinell verifizieren — mit einem kleinen LLM. Da 1 file 1 func gilt, ist eine 1:1-Zuordnung auf Dateiebene garantiert, was diesen Ansatz ermöglicht.


Regeln

Dateistrukturregeln

RegelBei Verstoß
Eine func pro Datei (Dateiname = Funktionsname)ERROR
Einen type pro Datei (Dateiname = Typname)ERROR
Methoden: 1 file 1 methodERROR
init() darf nicht allein stehen (muss mit var oder func kombiniert werden)ERROR
_test.go darf mehrere funcs enthaltenAusnahme
Semantisch zusammengehörige const-Gruppen dürfen in einer Datei stehenAusnahme

Code-Qualitätsregeln

RegelBei Verstoß
Verschachtelungstiefe: sequence=2, selection=2, iteration=dimension+1ERROR
Maximale Funktionslänge: 1000 ZeilenERROR
Empfohlene Funktionslänge: sequence/iteration 100 Zeilen, selection 300 ZeilenWARNING

Die Verschachtelungstiefe hängt vom control-Typ ab. sequence und selection: Tiefe 2. iteration: dimension + 1 — dimension=1 (flache Liste) ergibt Tiefe 2, dimension=2 (verschachtelte Struktur) ergibt Tiefe 3. In Kombination mit Gos early-return-Muster fallen die meisten Implementierungen in diese Grenzen.

selection (switch) neigt zu längeren cases, daher ist der Empfehlungswert großzügiger bei 300 Zeilen.


.ffignore

Eine .ffignore-Datei im Projektstamm schließt die angegebenen Pfade aus allen filefunc-Befehlen aus. Gleiche Syntax wie .gitignore.

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

Damit lassen sich generierter Code (protobuf, Code-Generatoren) oder externer Vendor-Code ausschließen, für den filefunc-Regeln nicht erzwungen werden sollen.


Integration mit whyso

Da func = file gilt, fällt die Änderungshistorie auf Funktionsebene exakt auf Dateiebene.

whyso history check_ssac_openapi.go   # Änderungshistorie der Funktion CheckSSaCOpenAPI

Enthält eine Datei mehrere Funktionen, muss man Diffs durchsuchen, um herauszufinden, welche Funktion geändert wurde. Mit filefunc gilt: Dateiänderung = Funktionsänderung. Null Nachverfolgungskosten.

Implizite Kopplung erkennen

whyso coupling check_ssac_openapi.go

Gemeinsam geänderte Funktionen in derselben Anfrage:
  check_response_fields.go  8-mal
  check_err_status.go       5-mal
  types.go                  4-mal

Wenn eine Datei trotz fehlender expliziter Beziehung immer wieder in der Kopplungsstatistik auftaucht, ist das ein Signal für verborgene Abhängigkeiten — Funktionen, die dieselbe Geschäftsregel aus verschiedenen Winkeln implementieren, implizit aufeinander abgestimmte Formate ohne interface, oder Bugs, die stets gemeinsam auftreten.


Warum nur Go?

Ohne Go ist die filefunc-Strukturierung kaum praktikabel. gofmt erzwingt das Code-Format, early return ist Konvention, es gibt keine Ausnahmen, und Package = Verzeichnis. Eine Erweiterung auf andere Sprachen würde eine strukturerzwingende Strategie auf gofmt-Niveau erfordern — das liegt außerhalb des Rahmens von filefunc.

Die Zieldomäne ist ebenfalls klar umrissen: Backend-Services, CLI-Tools, Code-Generatoren, SSOT-Validatoren. Algorithmus-Bibliotheken, Low-Level-Systemprogrammierung und performance-kritische Hot-Paths fallen nicht darunter.


Fazit

Codestruktur im LLM-Zeitalter sollte nicht an menschliche Navigationsgewohnheiten angepasst sein, sondern an die Navigationseffizienz von KI. filefunc ist der erste Schritt dieses Wandels.

Eine Datei, ein Konzept. Das Codebook normalisiert das Vokabular, Annotationen liefern Metadaten, ein einziger grep-Aufruf findet die richtigen Dateien. Ein read zieht keinen unnötigen Code nach sich. Die Dateistruktur selbst verhindert Context-Verschmutzung.

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