我最近整理一個用了好幾年的 side project,想讓 AI agent 幫手做重構。開頭幾次,我發現它一時讀完 root prompt,一時在不同 folder 之間跳來跳去,token 花得多,回應又常常偏離重點。後來我重新檢視整個 repo 的結構,才明白問題不只出在 prompt,而是檔案怎樣擺、資訊怎樣揭露。
這篇文章記下我整理後的想法:怎樣設計一個 AI agent 友善的程式碼結構,讓它用最少 context 做出正確判斷,而不是被迫吞一堆無關的內容。
問題不在檔案數量,而在「要讀多少才能動手」
AI agent 的表現,很大程度上取決於它能否快速定位相關程式碼。如果每次改一個功能,都要從 root 讀起、再追蹤十幾個 import,那 token 自然會膨脹。最理想的狀態是:agent 讀完幾份文件,就能掌握一個小範圍的責任與邊界。
我現在會用的 repo 結構
我傾向把規則分層擺放:global 的東西放 root,專屬的東西放在各 module 內。下面是一個我覺得平衡的例子。
repo/
├── AGENTS.md # 全域規則與導航
├── INDEX.md # repo 的高層地圖
├── docs/
│ ├── architecture.md # 系統邊界與資料流
│ └── decisions/ # 小型架構決策記錄
├── src/
│ ├── billing/
│ │ ├── AGENTS.md # billing 專屬指令
│ │ ├── README.md # 用途、API、依賴
│ │ ├── index.ts # 公開介面
│ │ └── ...
│ └── users/
│ ├── AGENTS.md
│ ├── README.md
│ └── ...
├── tests/ # 與 src 結構對應
└── generated/ # 明確隔離,通常排除
這個結構的關鍵是「漸進式揭露」:root 只放通用規則,進入某個 subsystem 才讀它的 AGENTS.md 與 README。agent 不用一口氣讀完整個 repo。
幾個我覺得最重要的原則
- 模組小且內聚:按功能或領域分類,而不是按檔案類型。把相關的程式、測試、schema、文件放近一點,減少 agent 需要翻閱的檔案數。
- 公開介面要清楚:每個 module 都該有明顯入口,例如
index.ts、api.py或interface.go。寫清楚輸入、輸出、副作用與依賴,agent 不用讀完內部就能懂。 - 一份簡短的 repo 地圖:
INDEX.md只放 module 用途、入口、依賴、相關測試、穩定度。不要塞長篇大論或整份程式碼。 - 命名直接:用
invoice_calculator.py這種看得懂的名字,避免utils.py、misc/、common/、helpers/這種模糊命名。好名字能減少搜尋與試探。 - 把低價值內容隔離:generated code、build output、vendor、log、fixture、snapshot 都應該放在明確路徑,並標示為排除,避免 agent 浪費 token。
- 事實只寫一次:每條規則或事實放在一個權威位置,其他地方用連結引用。重複會浪費 token,也會造成衝突 context。
檔案大小:一個大檔 vs 一堆小檔?
關於原始碼檔案,我現在的答案是:兩個極端都不好。比較理想的是「一個責任一個檔案,大小足以提供 local context」。
下面是我參考的經驗範圍:
| 檔案大小 | 效果 |
|---|---|
| 50 行以下 | 通常太碎,除非是簡單的 type、schema 或 interface |
| 100–400 行 | 通常是好範圍 |
| 400–800 行 | 對於單一內聚的實作可接受 |
| 800–1000 行以上 | 應考慮按責任拆分 |
| 數千行 | 會增加檢索成本、無關 context、修改風險 |
不過行數只是參考,內聚性更重要。
為什麼不該一個超大檔?
大檔雖然減少 navigation,但 agent 會被迫處理不相關的程式碼,也更難定位邊界與依賴,容易一次改錯。要把整段相關程式碼塞進 context window 也變得更困難。
為什麼不該一堆超小檔?
過度碎片化則會讓 agent 不斷 search、開很多檔案才能理解一個操作,還要重建跨檔案的控制流程。重複的 import、boilerplate 和工具輸出也會燒 token。
例如把 create user 的工作拆成:
create_user.ts
validate_user.ts
normalize_user.ts
save_user.ts
send_user_email.ts
user_errors.ts
user_types.ts
如果這些小片段總是一起改,不如合併成:
users/
├── create-user.ts # 工作流程與 private helpers
├── user-repository.ts # 持久化邊界
├── user-types.ts # 共享公開類型
└── create-user.test.ts
會讓 agent 表現變差的結構
以下這些 pattern 通常會同時增加 token 用量與錯誤率:
- 一份巨大的系統 prompt 或 repo 指令檔
- 一個大檔塞進不相關的責任
- 過深的巢狀目錄,意義又不清楚
- 同樣的指令重複在很多檔案裡
- 文件與實作互相矛盾
- 用
util、core、base、manager這種含糊命名 - 測試離實作很遠,又沒有地圖
- generated code 與手寫 code 混在一起
- barrel file 一次 export 全部
- 只存在於人腦的隱藏慣例
- 強迫 agent 讀完整份 log、lockfile、schema 或 snapshot
- 過度微檔案,理解一個操作要打開幾十個檔案
真正的目標:讓正確決策所需 context 最小化
最後我學到,重點不是把 context 壓到最小,而是壓到「剛剛好」。一個只追求極簡或極度碎片的結構,也許 token 少,但表現會掉。好的結構讓 agent 能從以下幾項就理解任務:
- root 指令
- 一份 subsystem guide
- 相關的公開介面
- 少數幾個實作檔
- 對應的測試
這組合通常能在準確度、導航成本與 token 用量之間取得最好平衡。
一個可以馬上行動的小建議
如果你現在就想動手,試試做這一件事:為你正在整理的 repo 寫一份 INDEX.md,列出每個 module 的用途、入口檔、依賴與對應測試。然後把每個 module 的 README 與入口檔整理好。你會發現 agent 的對話變短,回應也變準。
結構對了,AI 才能幫你專心做正確的事。