指令佈局
代理在讀任何程式碼之前就會先讀它的指令檔案,所以裡面的每個位元組在每個任務上都要付費。放著不管,它們就會長大。在一個真實的 monorepo 上,經過大約十天的代理工作,一個典型的 web 任務在碰到任何檔案之前就載入了約 120 000 個 token 的指令,光是一份工作區地圖就有 91 645 個 token,其中 227 KB 是表格的填充空白。
成因是結構性的。地圖變成變更日誌,每次改動一個帶日期的段落。同一個功能在三個檔案裡各描述一次。硬性規則被複製到每個根檔案裡,於是付了三次費。Coograph 現在給這些檔案一個形狀,以它為準衡量它們,並在它們偏離時告訴你。
三個層級,加上 gotcha
| 層級 | 內容 | 預設預算 | 載入時機 |
|---|---|---|---|
| 永遠載入 | 根目錄的 CLAUDE.md、AGENTS.md、.github/copilot-instructions.md,加上它們用 @path 拉進來的每個檔案 | 合計 9 000 token | 每次工作階段 |
| 路由 | <workspace>/AGENTS.md(以及巢狀的 CLAUDE.md):命令、gotcha 指標、區域索引 | 每份 2 000 | 代理進入該資料夾時 |
| 文件 | docs/features/<area>.md,每個產品區域一份,只寫現況,帶 paths: frontmatter | 每份 8 000 | 按需 |
| Gotcha | GOTCHAS.md 條目 | 每個檔案 8 000,每個條目 800 位元組 | 只在條目的路徑或命令符合時 |
每條規則只住在一個檔案裡。硬性規則和工作流程在 AGENTS.md,程式碼慣例在 copilot-instructions.md,CLAUDE.md 用 @ 匯入這兩者,只補上 Claude Code 特有的部分。Coograph 自己的範本在 9 000 個永遠載入 token 中量得 5 510 個。
內容規則很短:不要帶日期的段落(歷史留在 OpenSpec 和 git 裡),用項目清單取代寬表格,每條規則只有一個歸屬。
設定
.github/layout/layout.json 屬於專案。它從隨附的種子檔建立一次,之後 sync 永遠不會覆寫;新的種子鍵會被加進來,不動你的值。
{
"always_loaded": ["CLAUDE.md", ".claude/CLAUDE.md", "AGENTS.md", ".github/copilot-instructions.md"],
"routers": ["*/AGENTS.md", "*/*/AGENTS.md", "*/CLAUDE.md", "*/*/CLAUDE.md"],
"docs": ["docs/features/*.md"],
"gotchas": ["GOTCHAS.md", "*/GOTCHAS.md", "*/*/GOTCHAS.md"],
"budgets": {"always_loaded": 9000, "router": 2000, "doc": 8000, "gotchas": 8000},
"guard": {"ignore": ["**/*.lock", "openspec/**", "**/*.md"], "skip_marker": "[skip docs]"},
"structural_factor": 1.25
}
單一套件的儲存庫把 routers 設為 [];monorepo 列出它的工作區。連結的 git worktree 永遠不會被衡量:它們的檔案和主要簽出重複。
Gotchas
GOTCHAS.md 收錄從教訓中學到的陷阱,每個一條:
## expo-export-ignores-shell-env
- **Symptom:** `expo export` builds with the old API URL after `export API_URL=...`
- **Cause:** `.env` wins over the shell for `expo export`.
- **Fix / rule:** change `.env.production`, never the shell.
- **paths:** `apps/mobile/**`, `app.config.ts`
- **commands:** `expo export`
- **confirmed:** 2026-10-01
Gotcha 永遠不會預先載入。在 Claude Code 中,gotcha-surface.py hook 會在代理編輯條目路徑底下的檔案、或執行包含其某個命令的命令的那一刻,把符合的條目放到代理面前,每個工作階段一次。其他工具則透過 AGENTS.md 裡的路由表找到這個檔案。當根本原因是程式碼本身看不出來的陷阱時,/coograph-debug 會提議新增一個條目;Retro 則會提議刪除路徑已不符合任何檔案的條目。
檢查
python3 .github/layout/layout.py --budget # exit 1 when anything is over
python3 .github/layout/layout.py --guard BASE HEAD # exit 1 when covered code changed but its doc did not
python3 .github/layout/layout.py --json # the full measurement, which Retro reads
--budget 遇到指向不存在檔案的 @ 匯入(Claude Code 會一聲不吭地略過它)以及無效的 gotcha 時也會失敗,並在小幅修改無法補足差距時印出 STRUCTURAL 判定:永遠載入層超出預算 25% 以上(structural_factor)、某個路由或文件超過預算的兩倍,或某個檔案有三個以上帶日期的標題。
--guard 把每個變更的原始碼路徑對應到 paths: 涵蓋它的功能文件,若同一個 diff 裡這些文件都沒變就失敗。沒有任何文件涵蓋的路徑永遠不會失敗。在 PR 標題裡放 [skip docs] 可略過一次。
/coograph-init 把兩者都作為選用項目提供:一個 CI 工作流程和一個 pre-commit hook。當專案把 coograph 工具排除在 git 之外時,工作流程會自行取得檢查器。
當檔案長得太過頭
每一次 Claude Code 工作階段開始時都會這麼說:
[layout] STRUCTURAL: apps/web/AGENTS.md over twice its budget (+5 more), run /coograph-docs-restructure
/coograph-docs-restructure 會先盤點,找出專案的形狀(工作區、使用中的工具、產品區域、被陳述兩次的規則、埋在地圖裡的陷阱),然後寫出一份 OpenSpec,每個區域附一份事實清單。在你核可之前什麼都不會被編輯。核可之後,它每個區域交給一個代理遷移,並把每項事實拿去對照程式碼:確認過的事實連同其 file:line 保留;程式碼無法確認也無法反駁的事實(部署步驟、主控台設定)保留並加上標記;只有被程式碼反駁的事實、歷史紀錄和重複內容會被刪除,每一項都附上理由。在上面那個 monorepo 上,它把永遠載入的集合從 30 745 降到 7 914 個 token,91 645 token 的 web 地圖降到 591;現在沒有任何工作區地圖超過 700。