跳到正文
coograph 17

指令布局

代理在读任何代码之前先读它的指令文件,所以里面的每个字节在每个任务上都要付费。放着不管,它们就会膨胀。在一个真实的 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按需
GotchaGOTCHAS.md 条目每个文件 8 000,每个条目 800 字节只在某个条目的路径或命令匹配时

每条规则只住在一个文件里。硬性规则和工作流在 AGENTS.md,代码约定在 copilot-instructions.md,CLAUDE.md 用 @ 导入这两者,只补充 Claude Code 特有的内容。Coograph 自己的模板在 9 000 的始终加载预算中实测占 5 510 个 token。

内容规则很简短:不要带日期的小节(历史留在 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 永远不被衡量:它们的文件是主检出的副本。

Gotcha

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 个 token 降到 7 914,把 91 645 token 的 Web 地图降到 591;现在没有任何工作区地图超过 700。

下一步