filefunc — 1ファイル1コンセプト

問題

AIコードエージェント(Claude Codeなど)はコードを探索するとき、grepでファイルを探し、readでファイルを開く。readの単位はファイルだ。

では、1つのファイルに関数が20個あるとどうなるか。

CrossError typeが1つ必要でread
→ 不要な関数19個が付いてくる
→ コンテキスト汚染

“Lost in the Middle”(Stanford、2024)は、関連情報がコンテキストの中間に埋もれると LLM の性能が30%以上低下すると報告した。“Context Length Alone Hurts LLM Performance”(Amazon、2025)は、不要なトークンが空白であっても性能が13.9〜85%低下すると明らかにした。

研究は「コンテキストは短いほど良い」と証明した。しかし、コードを構造的に分割して必要なものだけを渡すツールがなかった。

filefuncはその空白を埋める。Goアプリケーション開発 — バックエンドサービス、CLIツール、コードジェネレーター、SSOT検証ツール — のためのコード構造コンベンションであり、CLIツールだ。


核心原則

1ファイル1コンセプト。ファイル名 = コンセプト名。

func であれ type であれ interface であれ const のまとまりであれ、同じだ。この原則1つからすべてのルールが派生する。

# filefuncなし
read utils.go → func 20個、19個は不要。コンテキスト汚染。

# filefunc
read check_one_file_one_func.go → func 1個。正確に必要なもの。

必要な5〜10個を取ることより、不要な290個を開かないことの方が重要だ。


ファーストクラスはAIエージェントだ

filefuncのコード構造は、人間ではなくAIエージェントに合わせて設計されている。

AIエージェントは ls ではなく grep で探索する。ファイルが500個でも1000個でも、rg '//ff:func feature=validate' を1回実行するだけで済む。ファイルが多いほど各ファイルは小さくなり、read1回で付いてくるノイズが減るため、むしろ有利だ。

「ファイルが多くなりすぎないか」という疑問が出るかもしれない。人間にとってはそうかもしれない。しかし人間の不便さはビューレイヤー(VSCode拡張など)で解決する。filefuncの構造を人間に合わせて妥協することはしない。


探索動線が変わる

従来

ユーザーリクエスト
→ 何があるか分からずls、find
→ ファイルを開いて構造を把握
→ 関連ファイルを探してまたgrep
→ 開いたらfunc 20個、ほとんど不要
→ 探索コスト > 実際の作業時間

filefunc

ユーザーリクエスト + コードブック提供
→ コードブックを見てgrepクエリを即座に構成
→ ファイル20〜30個をread(各1コンセプト、すべて有効コンテキスト)
→ 作業

30個をreadしてもすべて有効なコンテキストなら、30個は問題ではない。1個をreadしたら30個分が付いてくることが問題だ。


コードブック

コードブックはfilefuncの設計において最も重要な位置を占める。アノテーションのルールよりコードブックが先だ。コードブックが適切に設計されていれば grepクエリが精密になり、grepが精密であれば readリストが整理される。

# 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 キーはすべての //ff:func//ff:type アノテーションに必ず存在しなければならない。grepの信頼性を保証するためだ — required キーに空白は許されない。optional キーは関連する場合のみ使用する。

コードブックはAIエージェントのプロジェクトマップだ。コードブックがなければ語彙を知らない状態で探索を始めることになる。コードブックがあれば feature=validatetype=rule のような正確なクエリを探索なしに即座に投げられる。

コードブックにない値をアノテーションに使用するとERRORだ。コードブックで語彙を正規化すれば、抜けているfeature、重複したtype、曖昧な分類がリストから見えてくる。穴が見えなければ管理できない。コードブック自体も検証対象だ — required に最低1つのキー、重複値禁止、小文字+ハイフンのみ許可。


メタデータアノテーション

各ファイルの先頭にアノテーションを付ける。body全体をreadしなくても、先頭数行でメタを把握できるように。

//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 {
アノテーション内容必須
//ff:funcfunc ファイルの feature、type、control 等のメタfunc ファイル必須
//ff:typetype ファイルの feature、type 等のメタtype ファイル必須
//ff:what1行説明 — この関数/typeが何をするか必須
//ff:whyなぜこう作ったか — 設計判断の根拠任意
//ff:checkedLLM検証署名(llmcが自動生成)自動

形式は //ff:key key1=value1 key2=value2。grep/ripgrepで即時検索でき、構造化されたkey-valueでツールがパースできる。Goの //go:generate//go:embed の慣例と同じパターンだ。

control — 1 func 1 control

control= はすべての func ファイルに必須だ。値は3つのうちのどれか:

control意味depth 制限
sequence順次実行2
selection分岐(switch)2
iteration繰り返し(loop)dimension + 1

Böhm-Jacopini 定理(1966)に基づく。すべてのプログラムは sequence、selection、iteration の3つの制御構造の組み合わせだ。filefuncはこれを関数単位で強制する — 1つの関数は1つの制御フローのみを持つ。

control=iteration の関数には dimension= も必須だ。走査するデータの次元を明示する。dimension=1 はフラットなリスト(depth ≤ 2)、dimension ≥ 2 は名前付き型(struct/interface)のネストが必要だ。

filefuncは control と実際のコードの整合性も検証する。control=selection なのに switch がない、あるいは control=sequence なのに switch や loop があれば ERROR だ。


LLM探索パイプライン

アノテーションが検索インデックスと同じ役割を果たす。ベクター埋め込みのような重いインフラなしで動作する。

1. 構造的絞り込み(LLM不要、grep)
   コードブックベースでgrepクエリを構成
   → 候補ファイル20〜30個を抽出

2. メタ判定(LLM不要または超小型)
   各ファイル先頭のアノテーションのみread
   → name/input/output/whatで5〜10個に絞り込み

3. 精密作業(大型LLM、最小コンテキスト)
   5〜10個のファイルのみfull read
   → コード修正/生成

ステップが進むほどコンテキストが減る。大型LLMが投入される時点では、本当に必要なファイルだけが残っている。


CLI

validate — コード構造ルール検証

filefunc validate                    # カレントディレクトリ
filefunc validate /path/to/project   # プロジェクトルートを明示
filefunc validate --format json

プロジェクトルートに go.modcodebook.yaml が必要だ。読み取り専用。違反時は exit code 1。

chain — 呼び出し関係の追跡

filefunc chain func RunAll              # 1親等(デフォルト)
filefunc chain func RunAll --chon 2     # 2親等(共に呼ばれる関数を含む)
filefunc chain func RunAll --chon 3     # 3親等(最大)
filefunc chain func RunAll --child-depth 3   # 下位呼び出しのみ
filefunc chain func RunAll --parent-depth 3  # 上位呼び出し元のみ
filefunc chain feature validate         # feature全体

リアルタイムAST解析。--chon は関係の距離だ。1親等は直接呼び出し/被呼び出し、2親等は共に呼ばれる関数まで含む。

既存の go callgraph はすべての呼び出しを静的解析して数千ノードが出る。chain は同じfeature内でのみ追跡する。コードブックのfeatureがズームレベルそのものだ。

llmc — LLM検証

filefunc llmc                           # カレントディレクトリ
filefunc llmc --model qwen3:8b
filefunc llmc --threshold 0.9

//ff:what が func body と一致するかをローカルLLM(ollama)で検証する。0.0〜1.0のスコア、デフォルト閾値0.8。通過すれば //ff:checked llm=モデル名 hash=ハッシュ を自動記録する。bodyが変更されるとhashが変わるため、再検証が必要になる。

アノテーションドリフトの核心的な問題 — 自然言語の //ff:what は機械的に検証できない — を小型LLMで解決したものだ。1 file 1 func なのでファイル単位の1対1対応が保証されているため、このアプローチが可能だ。


ルール

ファイル構造ルール

ルール違反時
1ファイルに func 1つ(ファイル名 = 関数名)ERROR
1ファイルに type 1つ(ファイル名 = 型名)ERROR
メソッド: 1 file 1 methodERROR
init() は単独不可(var または func と共に)ERROR
_test.go は複数 func 許可例外
意味的に1まとまりの const は同じファイル許可例外

コード品質ルール

ルール違反時
nesting depth: sequence=2、selection=2、iteration=dimension+1ERROR
func 最大1000行ERROR
func 推奨: sequence/iteration 100行、selection 300行WARNING

nesting depth は control の種類によって異なる。sequence と selection は depth 2。iteration は dimension + 1 — dimension=1(フラットなリスト)なら depth 2、dimension=2(ネスト構造)なら depth 3。Goの early return パターンと組み合わせると、ほとんどのコードがこの制限に収まる。

selection(switch)は case が長くなる傾向があるため、推奨行数が300と広めだ。


.ffignore

プロジェクトルートに .ffignore を置くと、すべての filefunc コマンドで該当パスを除外する。.gitignore と同じ文法。

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

生成されたコード(protobuf、コードジェン出力)や外部ベンダーコードのように、filefuncのルールを強制できないコードを除外するためのものだ。


whysoとの連携

func = file なので、関数単位の変更履歴がファイル単位で正確に対応する。

whyso history check_ssac_openapi.go   # CheckSSaCOpenAPI 関数の変更履歴

1つのファイルに複数の関数があると、どの関数が変更されたかをdiffで調べなければならない。filefuncならファイルの変更 = 関数の変更。追跡コストはゼロ。

暗黙的なカップリングの検出

whyso coupling check_ssac_openapi.go

同じリクエストで共に修正された関数:
  check_response_fields.go  8回
  check_err_status.go       5回
  types.go                  4回

明示的な関係がないのにcouplingの統計に繰り返し現れるなら、それは隠れた依存性のシグナルだ。同じビジネスルールを異なる角度で実装している関数、interfaceなしで暗黙的にformatを合わせているもの、バグが常にセットで起きるもの。


Goに限定する理由

Go以外ではfilefuncの構造化が容易ではない。gofmtがコードフォーマットを強制し、early returnが慣例であり、例外がなく、パッケージ = ディレクトリ。他の言語に拡張するには gofmt レベルの構造強制戦略が必要だ。これはfilefuncの範囲外だ。

適用対象も明確だ。バックエンドサービス、CLIツール、コードジェネレーター、SSOT検証ツール。アルゴリズムライブラリ、低レベルシステムプログラミング、パフォーマンスクリティカルなホットパスは対象外だ。


まとめ

LLM時代のコード構造は、人間の探索利便性ではなくAIの探索効率に合わせるべきだ。filefuncはその転換の第一歩だ。

1ファイル1コンセプト。コードブックで語彙を正規化し、アノテーションでメタを付与し、grep1回で正確なファイルを見つける。readの1回で不要なコードが付いてこない。コンテキスト汚染の防止はファイル構造そのものが解決する。

コード: github.com/park-jun-woo/filefunc