AI 不需要一张代码地图,它需要一个索引
这是一篇讲”范式”的文章,起点是我从 cartographer 转向自己写 codewise 的过程。
但结论不是”我的工具更好”。结论是:“代码地图”这个概念本身就是为人类设计的,把它交给 AI 去读,从一开始方向就偏了。
cartographer 是个好工具,但它解决的不是我的问题
先把话说在前面:cartographer 是我用过之后,才真正意识到自己想要的是另一样东西。它做得对的那些事里,很多其实是我后来写 codewise 时直接继承下来的——比如”Opus 编排、Sonnet 读”的多代理分工,比如”先扫描、再规划、再并行生成”的阶段划分。这些范式不是我想出来的,是 cartographer 先提的。
它的产出也是认真的:一张 docs/CODEBASE_MAP.md,里面有完整的目录树、Mermaid 架构图、模块指南、数据流图,甚至有一节 Navigation Guide,告诉你”要加一个 API 要改哪些文件”。从任何一个传统文档工具的评价标准来看,这份输出都是高质量的。
我用它映射了几个项目之后发现了一个不容易说清楚的问题:这张地图对人很有用,但对 AI 没那么有用。
我说不出它哪里”错”了。反过来,我看着每一节都觉得有道理。但当我后来在实际的编码会话里想让 AI 参考这些信息时,总感觉哪里不对——它读的东西很多,抓到的信息却没有那么精准。
花了一点时间我才意识到:cartographer 做得对的每一件事,几乎都是按”给人看”的前提设计的。而我真正的使用场景不是让人看,是让 AI 在写代码的时候顺手查一下。这两个场景看起来相近,其实差得挺远。
“代码地图”这个比喻本身就带偏了方向
这是全文最重要的一节,后面所有论点都从这里长出来。
地图是给人用的。人看地图的目的是形成对空间的整体理解——你看完之后就把它记住了,接下来走哪里都不用再看。地图的价值在于”被读一次,然后被内化成记忆”。
AI 读项目的目的完全不同。它不需要”形成理解然后记住”,它每次都在回答一个当下的具体任务:这个 bug 改哪里?这个函数谁在用?加一个新功能要碰哪些文件?它不需要全景,它需要精准。
把一张为”形成整体理解”而设计的文档塞给一个”只在乎当前任务”的读者,结果就是——它每次都得把整张地图读一遍,才能从里面提取出那一小段它真正需要的信息。这不是效率问题,是信息架构和使用场景不匹配。
类比一下:一个经常去图书馆的人不需要每次都重新研究图书馆地图,他记住了常去的那几个区域。但你让一个”每次都第一次进馆”的读者(而这正是 AI 的真实工作模式——没有跨会话记忆,每次对话都是新的)去用地图,最合理的设计一定不是”一张更大更全的地图”,而是一个让他能快速找到所需书架的索引系统。
“代码地图”这个比喻最不易察觉的危害,是它引导设计者把精力投在”怎么让地图更全、更好看、更清晰”上。而真正该问的问题是:AI 读这份文档时,能不能直接跳到它需要的那一小段?
整体加载 vs 按需索引:两种根本不同的信息架构
把上一节的观点收紧成一个技术判断:这是两种完全不同的信息架构范式。
- 地图范式把上下文窗口当成”RAM,尽量装满”——一次性注入项目的全部结构
- 索引范式把上下文窗口当成”工作台,只放当前要用的”——按任务动态加载相关条目
你可能会问:现在上下文窗口都 200k 甚至 1M 了,整个 CODEBASE_MAP 一次性塞进去也装得下,有什么区别?
这里有一个反直觉的点:上下文窗口越大,索引范式反而越重要,不是越不重要。原因不是”装不下”,而是信号稀释。
模型对上下文的注意力不是均匀分布的,相关内容占比越低,模型对每一 token 的注意力权重就越稀。一个 200k 上下文里,有 5k 和当前任务真正相关,另外 195k 是”背景信息”——模型在这种条件下的判断质量,明显低于把那 5k 单独注入的情况。大上下文不是免费的,它在背后悄悄地稀释每一个 token 的信号强度。
所以:让 AI 读完整张代码地图,不是浪费钱,是自毁注意力。
真正该追求的不是”塞更多进去”,是”塞得刚好”。这件事在设计上对应的不是一张大文档,而是一组可按需加载的小条目,加上一个能判断”哪些可以不读”的入口。
按结构切 vs 按语义切
如果”按需加载”是结论,那下一个问题是:按什么维度切分这些知识?
cartographer 按物理结构切——目录、模块、文件。这是地图的自然切法,因为地图本来就是按空间来的。但这个切法有一个隐藏假设:AI 的提问方式是以结构为单位的。而实际情况几乎相反。
codewise 走了另一条路,按语义功能切分,一共六类:
| 分类 | 语义 | 回答的是 |
|---|---|---|
domains/ | What | 业务做了什么,数据怎么流 |
decisions/ | Why | 为什么选这个方案而不是另一个 |
integrations/ | How | 第三方依赖怎么集成、什么限制 |
shared/ | With what | 用了哪些公共工具/类型 |
workflows/ | How to | 跨模块操作的完整步骤 |
pitfalls/ | Watch out | 别踩什么坑 |
这六类不是我拍脑袋想出来的,是AI 做事时真实的提问方式分类。想一下 AI 在一个项目里做任何事情时,它心里在问什么:
- “我现在要加一个认证方式” →
workflows/+decisions/+integrations/ - “这个 bug 可能和什么有关” →
pitfalls/+domains/ - “这个类型能不能改” →
shared/(看谁在用)+decisions/(看为什么这么定义)
按结构切的地图,要求 AI 自己把结构翻译成语义——它得先从”api/” 目录里猜测”这大概是认证相关的”,再从多个模块里拼凑”也许登录流程是这样的”。按语义切的知识库,直接回答它要问的那个问题。
这一个差异,其他所有差异都只是它的推论。
被代码地图丢掉的三样东西
上一节是理论,这一节是具体证据。当你按语义切的时候,你会立刻发现地图范式系统性地丢掉了三类信息——而这三类恰好是 AI 在具体任务里最需要的。
① Why(设计决策)
代码里看不出来”为什么这样做而不是那样做”。git log 偶尔有线索,但多数 commit message 只写 what,不写 why。一个稍微严肃一点的项目,永远有大量”我们曾经考虑过 A 方案,因为 B 原因放弃了”这种信息——而这些信息只存在于讨论记录、设计文档、或者某个人的记忆里。
cartographer 的 CODEBASE_MAP 里没有这个维度。不是它偷懒,是**“地图”这个范式本身就只能记录当前状态**——你画不出”没选的那条路”。
但这恰恰是 AI 最需要的信息之一。下一次它遇到类似决策,它需要知道上次为什么没选另一条路——否则它就会一次又一次地建议你”用方案 A 吧”,而你每次都得重新解释为什么不行。
codewise 把 decisions/ 作为一级分类,条目模板里直接要求写”背景 / 方案对比 / 最终选择”。Phase 1 还特意强调:“读文档获取意图,读代码验证事实,冲突时以代码为准”——因为意图这种东西代码里根本没有,只能从现有文档里读出来。这不是加一节的问题,是承认这类信息在项目知识里的一级地位。
② Pitfalls(踩坑记录)
cartographer 的 CODEBASE_MAP 里有一节叫 Gotchas,把各种非显而易见的行为合并在一起描述。从结构上看是有的,但从数据结构的意义上看,它没有给这类信息应有的位置。
codewise 把 pitfalls/ 作为独立分类,每条用严格的三段式:
**What**: [发生了什么]**Why**: [为什么会这样]**Action**: [怎么避免/解决]而且在”深度模式”下,pitfalls/ 是不可跳过的——必须至少产出一个条目。这是一个故意的设计压力,逼你在扫完代码之后回去找”有没有什么非显而易见的坑”,而不是让它被别的内容稀释掉。
这个选择背后的判断是:项目最宝贵的知识是踩过的坑。一个用了一年的项目,真正的智慧几乎全在 pitfalls 里——那些”当初我们以为 X 会生效,后来发现在 Y 情况下会静默失败”的经验。把它和模块描述混在一起写成一段 Gotchas,等于把黄金和石头放在同一个抽屉里。
给踩坑独立的数据结构,是告诉未来的读者(包括 AI):这里是你最应该先看的地方。
③ 横向关联
地图是树状的:目录→模块→文件,只有上下级关系。你能表达”API 模块下有 auth 子模块”,但你表达不了”auth 模块和数据库事务模块在某种情况下会互相影响”——因为它们不在同一个目录下。
知识库是网状的。codewise 里每个条目末尾都有一个 ## 关联条目 区,使用相对路径链接到其他条目,而且要求双向链接——A 链接了 B,B 也要链接回 A。
这听起来像是一个小细节,其实是两种数据结构的根本差异。树状只能表达上下级关联,网状才能表达跨模块的语义关联。而在真实的编码场景里,AI 要处理的关联几乎全是横向的——“认证模块和日志模块”、“缓存层和数据库层”、“一个组件和它间接依赖的某个 util”。
树状地图在结构上就表达不了这些,只能靠读者(AI)自己脑补。结果就是每一次脑补都是一次幻觉风险。
INDEX.md 才是这个范式真正的秘密
听到这里你可能觉得,这套做法的核心是把大文档拆成小条目。其实不是。
真正让这个范式成立的,是那份 INDEX。
没有 INDEX 的知识库等于没有知识库。为什么?因为 AI 不知道哪些条目和当前任务相关——如果它得”读完所有条目才能判断”,那结果和读一张大地图完全一样,你什么都没省。
INDEX 的真正作用,不是告诉 AI “有什么”,而是告诉它”哪些可以不看”。一个好的 INDEX 应该让 AI 在读具体条目之前,就能排除掉 80% 的不相关内容。
这是一个容易被低估的设计点:
节省上下文的不是小条目,是索引。
条目再小,AI 全读一遍也是灾难。30 个 50 行的条目加起来和一份 1500 行的大地图是一样的。真正的省法是让它先读 INDEX、然后只去读那 3-5 个相关条目。这才是”按需加载”这个词的真实含义。
所以 codewise 的 INDEX.md 做了几件很具体的事:
- 按六个分类列出所有条目,每条只有一句话描述
- 加了一个”数据流全景”的 ASCII 图,给 AI 一个整体锚定
- 空分类不出现——没有 pitfalls 就不要假装有一栏
INDEX 这份东西本身就是一张”微型地图”——它确实承担了一点地图的功能。但它的定位不是”被读完”,而是”被用来决定下一步读什么”。这和 CODEBASE_MAP 的定位完全不同。
承认:代码地图并不是没有用
写到这里,我得停一下说一句诚实的话,否则这篇文章会滑成”我做的工具更好”,那就无聊了。
代码地图有它合理的场景:让人快速上手一个陌生项目。
一个新加入团队的工程师,翻一张带 Mermaid 图的 CODEBASE_MAP,比他读二十个 markdown 条目快得多。人类大脑擅长从一张图里形成整体印象——看一眼就知道”哦,原来前端、API、数据库是这么连起来的”。地图在这个场景里干的活是给人脑做一次快速索引加载,然后这个加载出来的索引就留在那个人脑子里了。
这是地图的本职工作,也是它原本设计的场景。cartographer 做这件事做得很好。
问题只在于:这是 onboarding 场景,不是 AI 每天做任务的场景。AI 没有”记住整张地图然后每天用脑内版本”的能力——它的每次会话都是一次”第一次进馆”。这时候你给它的不应该是地图,应该是索引系统。
两个场景都合理,它们要的东西不一样。如果你的目标是给新人介绍项目,cartographer 是合适的。如果你的目标是让 AI 在写代码的每次会话里都能用上项目知识,你需要的不是地图。
结尾:信息架构是第一问题,生成速度是第二问题
几乎所有”让 AI 生成文档”的工具,都在优化两件事:生成得更快,生成得更全。这两件事本身没错,但它们都是第二问题。
第一问题是这个:
AI 读这份文档是为了回答什么问题?
这个问题决定了信息架构——按结构切还是按语义切。信息架构决定了颗粒度——一个条目管多少事。颗粒度决定了互链方向——树状还是网状。最后这一切汇总到 INDEX——它是否能让 AI 在读具体内容之前就缩小范围。
把第一问题想错了,后面全是白费力气。再聪明的 subagent,再大的并行度,也只是更快地产出一份错结构的文档。大模型的速度会继续涨,你省下来的时间会被”读得更多但抓不准”这个代价吃掉。
cartographer 教会我的不是”怎么生成一张地图”,是让我意识到”地图”从来就不是我真正想要的东西。这件事我花了几个项目才想清楚。写 codewise 的时候,我的注意力从”怎么生成得更好”转到了”AI 真正需要读到什么”——从那一刻起,它变成了另一个范式的问题,而不是同一个范式下的优化。
这也是我一直相信的一件事:工具的好坏多数时候不取决于实现,取决于它对使用场景的假设是不是对的。cartographer 的实现很好,假设不完全对(至少对我来说);codewise 的实现还在迭代,但我相信它的假设方向是对的。
希望你在做 AI 工具时,也能先想清楚第一问题。