我最近一直在折腾一件事:怎么让 Claude Code 在动手改代码之前,先用最少的 token 把项目结构看明白。起因其实很朴素——我把一个 3 万行的 Python 后端丢给 Claude Code 做跨模块重构,它上来就把整个文件树连同大量源文件反复读了几遍,跑完一看账单,token 消耗高到让我怀疑人生。后来我在 GitHub 上挖到一个 30K Star 的开源神器,核心功能一句话讲完:给 Claude Code 装一张"代码地图"。装上之后,同一批任务的 token 中位数省了 65 倍左右。这篇文章就是我从踩坑到落地、从怀疑到真香的完整记录,适合那些被 token 烧到肉疼、又不想放弃 Claude Code 效率的人。
代码地图这个词听起来玄,其实就是一份经过压缩、按重要程度排序的项目结构索引。Claude Code 看到这份地图,就像司机拿到导航地图,不需要一条路一条路亲自开过去看;只有地图上没覆盖到的新文件,它才会精准读取。适合谁?一是整天抱着大仓库写业务的开发者,二是想控制 AI 编程成本的团队,三是刚接触 Claude Code、对 token 机制一头雾水的新手——后者的额外收获是,折腾完这套方案,你会顺手把 token 怎么计费、什么时候会烧 token 这些事彻底搞清楚。
1. 为什么 Claude Code 是个 Token 大户
1.1 Claude Code 默认是怎么读你项目的
很多人第一次用 Claude Code 都会有个错觉:它好像对我的项目了如指掌。实际上它在默认配置下,理解项目的方式非常"笨"——先看目录树,再用 Read 工具挨个读文件。
我做个类比你就明白了:让一个拆迁评估师去评估整条街。聪明的做法是先拿到街道地图,圈出重点建筑,再走近量尺寸。Claude Code 的默认行为是挨家挨户敲门,进去把每个房间拍一遍照片,最后才汇总出"这条街大概长什么样"。结果就是:它的上下文窗口里塞满了低信息密度的文件正文,真正有用的结构信息反而被稀释了。
这里面有个容易被忽略的机制成本:Claude Code 不是一次性把项目读完再回答你,它是"对话式"工作的。每一次模型调用,系统都要把当前会话里所有历史消息重新发送一遍。所以文件读得越多,后续每轮对话的输入 token 都在跟着涨。这就导致一个恶性循环——项目一大,Claude 为了找到目标代码,只能继续读文件;读得越多,每轮对话越贵;越贵,你就越舍不得让它多读几轮。
1.2 代码地图到底是什么
代码地图的核心思路,就是先给 Claude Code 一份"项目结构导航",而不是让它自己从头摸索。
具体来说,代码地图是一份 Markdown 格式的结构性索引,里面通常包含:
- 项目的目录层级和文件清单
- 每个源文件的核心职责概述(一两句话)
- 关键类、函数、模块的符号表
- 模块之间的依赖关系摘要
- 按"最近改动频率"和"与当前任务相关度"排序的文件优先级列表
这份索引不是人工维护的,而是通过静态分析工具自动生成。它的体积可以被压得非常小——一个几万行的仓库,地图往往只需要 2KB 到 4KB,换算成 token 也就一两千。而如果让 Claude 自己读一遍项目,动辄就是十几万 token。
这里有个很关键的设计点:代码地图给 Claude 的是"去哪里找",而不是"答案是什么"。Claude 拿到地图后,仍然会去读它判定相关的源文件,但读取数量会从"几十个"降到"两三个",这就是 token 大幅下降的根本原因。
1.3 为什么统计口径是"中位数省 65 倍"
我用这个工具跑了一批真实任务,整理了数据。当时第一反应是想算个平均值,但算完发现平均值参考价值不大——因为个别任务太极端了。
举个例子,有一个任务是从日志里定位一个偶发的竞态条件,无地图的情况下 Claude 反复扫描了大半个仓库,烧了 26 万 token;有地图之后它直接锁定了三个相关文件,只用了 1200 token,省了 200 多倍。而另一个任务只是让我改一个函数的参数名,脚本地图前后差距可能只有 3 倍。这种情况下"平均省多少倍"会被极端值严重干扰。
所以我把 37 个任务的省 token 倍数排了个序,取中间的数值,得到了"中位数省 65 倍"这个结论。中位数的好处是它代表"你的典型任务大概能省多少",比平均数稳健得多。换句话说,65 倍不是一个承诺,而是一个基于真实分布的实测经验值。你在自己的项目里跑出来的数字可能更高或者略低,但量级上不会差太多。
2. 方案选型:为什么是代码地图而不是其他方式
2.1 对比一下最常见的几种上下文方案
在决定用代码地图之前,我其实试过好几种给 Claude Code 补充项目上下文的方式,各有各的问题。
我整理了一个对比表,可以直观看出各自的取舍:
| 方案 | 维护成本 | 上下文体积 | 对 Claude 的友好度 | 动态性 |
|---|---|---|---|---|
| 手写 CLAUDE.md / AGENTS.md | 高,文档一不更新就误导 | 小 | 高 | 静态 |
| MCP 上下文工具 | 中,需要搭服务 | 小 | 中(多一次工具调用) | 较好 |
| 全量代码打包成 prompt | 无 | 极大,一次就炸 | 低 | 无 |
| 自动代码地图 | 低,生成器维护 | 极小 | 高 | 随代码更新 |
手动维护文档这条路,小项目还能撑,大项目基本走不通。我见过很多团队写了非常漂亮的 CLAUDE.md,但代码重构三次文档就废了,最后 Claude 拿着过期的架构说明去改代码,比没文档还糟糕。
MCP 方案在技术上更"正统",但有个成本陷阱:MCP 的工作方式是 Claude 先决定"我需要了解一下项目结构",然后发起一次工具调用,等结果返回,再继续推理。每一次工具调用都会产生额外的输入 token 和输出 token,而且在等待工具返回的过程中,Claude 的推理链会拉长,cost 反而更高。代码地图则是把知识直接挂在对话开始前,零轮询、零等待、零额外调用。
2.2 代码地图生成工具的工作原理拆解
我用的这个 30K Star 项目,内部原理其实并不神秘,底层是静态分析领域比较成熟的技术栈。
第一步是语法解析。它内置了 tree-sitter 解析器,能覆盖 JavaScript、TypeScript、Python、Go、Java、Rust 等主流语言。tree-sitter 比正则表达式强在它真正理解了代码的语法结构,能把"这是一个类""这是一个函数声明""这个函数调用了另一个模块的接口"这些关系准确抽出来。如果某种语言解析失败,它还会降级到基于正则和符号表的模式,保证大多数项目都能用。
第二步是相关性排序。生成地图时会综合两个维度:文件被引用的密集度,以及文件的 git 提交频率。这里的思想和主流 AI 编程工具的 repo map 理念同源——一个被很多文件引用、又经常被修改的文件,往往就是接下来任务最可能涉及的"热点区域",应该排在地图前面。
第三步是格式化成 Claude Code 友好的 Markdown。输出不是一堆原始符号清单,而是带上下文的描述,比如"controllers/user.ts —— 用户注册、登录、资料更新接口,依赖 services/user.ts 和 models/user.ts"。这样 Claude 读完地图,脑子里能直接拼出一张"这座城市的交通图"。
2.3 地图应该放在哪里:三种接入姿势
工具生成完地图之后,接不接得进 Claude Code 的工作流,