
The Problem
AI code agents (like Claude Code) navigate codebases by using grep to find files and read to open them. The unit of read is the file.
So what happens when a single file contains 20 functions?
Need one CrossError type → read
→ 19 unnecessary functions come along
→ Context pollution
“Lost in the Middle” (Stanford, 2024) reported that LLM performance drops by more than 30% when relevant information is buried in the middle of the context. “Context Length Alone Hurts LLM Performance” (Amazon, 2025) found that even blank unnecessary tokens cause a 13.9–85% performance degradation.
Research has proven that shorter context is better. But there was no tool to structurally split code so that only the necessary parts get loaded.
filefunc fills that gap. It is a code structure convention and CLI tool for Go application development — backend services, CLI tools, code generators, and SSOT validators.
Core Principle
One file, one concept. Filename = concept name.
Whether it’s a func, type, interface, or a group of const declarations — the rule is the same. Every other rule follows from this single principle.
# Without filefunc
read utils.go → 20 funcs, 19 unnecessary. Context polluted.
# With filefunc
read check_one_file_one_func.go → 1 func. Exactly what's needed.
Not opening 290 unnecessary files matters more than picking the right 5–10.
The AI Agent Is the First-Class Citizen
filefunc’s code structure is designed for AI agents, not humans.
AI agents navigate with grep, not ls. Whether there are 500 or 1000 files, a single rg '//ff:func feature=validate' is enough. More files means each file is smaller — less noise per read. More files is actually better.
The question “won’t there be too many files?” is fair — for humans. But human inconvenience is a view-layer problem (solved by IDE extensions like VSCode plugins). filefunc’s structure doesn’t compromise to suit human browsing habits.
Navigation Changes
Traditional
User request
→ Don't know what's there, so ls, find
→ Open files to understand structure
→ grep again to find related files
→ Open a file, 20 funcs, most irrelevant
→ Navigation cost > actual work time
With filefunc
User request + codebook provided
→ Construct grep query immediately from codebook
→ read 20–30 files (each 1 concept, all valid context)
→ Do the work
Reading 30 files isn’t the problem if all 30 are valid context. The problem is reading 1 file and getting 30 files’ worth of noise along with it.
The Codebook
The codebook occupies the most important position in filefunc’s design. It comes before annotation rules. A well-designed codebook makes grep queries precise; precise grep makes the read list clean.
# 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 keys must be present in every //ff:func and //ff:type annotation — no gaps allowed, to guarantee grep reliability. optional keys are used only when relevant.
The codebook is a project map for AI agents. Without it, the agent starts navigating without knowing the vocabulary. With it, the agent can immediately issue precise queries like feature=validate or type=rule without any preliminary exploration.
Using a value in an annotation that doesn’t exist in the codebook is an ERROR. Normalizing vocabulary through the codebook exposes missing features, duplicate types, and ambiguous classifications. Holes need to be visible to be managed. The codebook itself is also validated — at least one key in required, no duplicate values, lowercase and hyphens only.
Metadata Annotations
Each file starts with annotations at the top, so metadata can be parsed from the first few lines without reading the entire 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 {
| Annotation | Content | Required |
|---|---|---|
//ff:func | feature, type, control metadata for func files | Required for func files |
//ff:type | feature, type metadata for type files | Required for type files |
//ff:what | One-line description — what this func/type does | Required |
//ff:why | Why it was built this way — rationale behind the decision | Optional |
//ff:checked | LLM verification signature (auto-generated by llmc) | Automatic |
The format is //ff:key key1=value1 key2=value2. Immediately searchable with grep/ripgrep, and parseable by tools as structured key-value pairs. Follows the same pattern as Go’s //go:generate and //go:embed directives.
control — 1 func 1 control
control= is required for every func file. The value is one of three:
| control | Meaning | Depth limit |
|---|---|---|
sequence | Sequential execution | 2 |
selection | Branching (switch) | 2 |
iteration | Looping | dimension + 1 |
Based on the Böhm–Jacopini theorem (1966): every program is a combination of sequence, selection, and iteration. filefunc enforces this at the function level — one function, one control flow.
Functions with control=iteration also require dimension=. This specifies the dimensionality of the data being iterated. dimension=1 means a flat list (depth ≤ 2); dimension ≥ 2 requires named type (struct/interface) nesting.
filefunc also validates that the control annotation matches the actual code. If control=selection has no switch, or control=sequence contains a switch or loop, it’s an ERROR.
LLM Navigation Pipeline
Annotations act as a search index — no heavyweight infrastructure like vector embeddings needed.
1. Structural reduction (no LLM needed, just grep)
Build grep query based on codebook
→ Extract 20–30 candidate files
2. Meta filtering (no LLM, or tiny model)
Read only the top annotation of each file
→ Narrow down to 5–10 via name/input/output/what
3. Precision work (large LLM, minimal context)
Full read of 5–10 files only
→ Modify or generate code
Context shrinks at each stage. By the time a large LLM is involved, only the truly necessary files remain.
CLI
validate — Code Structure Rule Validation
filefunc validate # Current directory
filefunc validate /path/to/project # Explicit project root
filefunc validate --format json
Requires go.mod and codebook.yaml at the project root. Read-only. Exits with code 1 on violation.
chain — Call Relationship Tracing
filefunc chain func RunAll # 1st degree (default)
filefunc chain func RunAll --chon 2 # 2nd degree (includes co-called functions)
filefunc chain func RunAll --chon 3 # 3rd degree (maximum)
filefunc chain func RunAll --child-depth 3 # Downstream calls only
filefunc chain func RunAll --parent-depth 3 # Upstream callers only
filefunc chain feature validate # Entire feature
Real-time AST analysis. --chon is the relationship distance. 1st degree is direct caller/callee; 2nd degree includes functions called together.
The existing go callgraph performs full static analysis and produces thousands of nodes. chain traces only within the same feature. The codebook’s feature is the zoom level.
llmc — LLM Verification
filefunc llmc # Current directory
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9
Verifies that //ff:what matches the func body using a local LLM (ollama). Scores from 0.0 to 1.0, default threshold 0.8. On pass, //ff:checked llm=<model> hash=<hash> is written automatically. If the body changes, the hash changes — re-verification required.
This addresses the core problem of annotation drift — //ff:what is natural language and can’t be verified mechanically — using a small LLM. The 1 file 1 func constraint guarantees a 1:1 file-level correspondence, making this approach feasible.
Rules
File Structure Rules
| Rule | On Violation |
|---|---|
| One func per file (filename = function name) | ERROR |
| One type per file (filename = type name) | ERROR |
| Methods: 1 file 1 method | ERROR |
init() cannot stand alone (must be with var or func) | ERROR |
_test.go files may contain multiple funcs | Exception |
| Semantically cohesive const groups may share a file | Exception |
Code Quality Rules
| Rule | On Violation |
|---|---|
| Nesting depth: sequence=2, selection=2, iteration=dimension+1 | ERROR |
| Max 1000 lines per func | ERROR |
| Recommended: sequence/iteration 100 lines, selection 300 lines | WARNING |
Nesting depth limits vary by control type. sequence and selection: depth 2. iteration: dimension + 1 — dimension=1 (flat list) means depth 2, dimension=2 (nested structure) means depth 3. Combined with Go’s early return pattern, most code fits comfortably within these limits.
selection (switch) tends to have longer cases, so the recommended line count is 300.
.ffignore
Place a .ffignore file in the project root to exclude paths from all filefunc commands. Uses the same syntax as .gitignore.
vendor/
*.pb.go
*_gen.go
internal/legacy/
This is for excluding generated code (protobuf, codegen output) or vendored external code where filefunc rules can’t reasonably be enforced.
Integration with whyso
Because func = file, the change history of a function maps exactly to the change history of its file.
whyso history check_ssac_openapi.go # Change history of the CheckSSaCOpenAPI function
When a file contains multiple functions, you have to dig through diffs to figure out which function changed. With filefunc, file change = function change. Zero tracking cost.
Implicit Coupling Detection
whyso coupling check_ssac_openapi.go
Functions modified together in the same request:
check_response_fields.go 8 times
check_err_status.go 5 times
types.go 4 times
If two functions keep appearing together in coupling stats despite having no explicit relationship, that’s a signal of hidden dependency. They might be implementing the same business rule from different angles, implicitly matching formats without an interface, or always breaking together.
Why Go Only
filefunc’s structure doesn’t translate easily to other languages. gofmt enforces code formatting, early return is idiomatic, there are no exceptions, and package = directory. Extending to other languages would require a structural enforcement strategy at the level of gofmt. That’s outside the scope of filefunc.
The target use cases are also specific: backend services, CLI tools, code generators, SSOT validators. Algorithm libraries, low-level systems programming, and performance-critical hot paths are not targets.
Summary
In the LLM era, code structure should be optimized for AI navigation efficiency, not human browsing convenience. filefunc is the first step in that shift.
One file, one concept. Normalize vocabulary with the codebook, attach metadata with annotations, find the exact file with a single grep. No unnecessary code comes along when you read a file. Context pollution is blocked at the structural level.