context-map:进仓库的第一件事,是先画一张地图
这是逐份拆 skill 系列的一篇,拆的是 context-map——通用组的一份,不挂在五个阶段的任何一个上,管的是进仓库的第一件事。母篇是《从能跑到可交付:一套前端 AI 协作系统的设计笔记
》;这个系列逐份拆开,每篇只讲一份:它为什么存在,具体怎么干。我用的系统是开源的 frontend-harness-cli
,文中规则都能在仓库的 templates/skills/context-map/SKILL.md 里找到原文。
二十万行仓库里,grep 到了错误的那一个
先看一个具体的场景。
一个上下文全新的 Agent 被放进一个二十万行的仓库,任务是给订单列表加一个日期格式化。它的开局动作是 grep:搜 “formatDate”,出来三个长得很像的工具函数——utils/date.ts 里一个、lib/format.ts 里一个、shared/dates/index.ts 里还有一个。它挑了名字最顺眼的那个,写了四十分钟代码,全建在这个函数上。
那个函数所在的模块,半年前就被标成待废弃了。团队现在的标准做法是第三个。更糟的是它顺手改了一个共享组件的 props——那个组件另外三个功能也在用。它每一步都不笨:搜索、比对、选型、实现,动作全对。它只是不知道这个仓库的地形:什么在哪、哪个是正的、哪些地方不能随便动。
SKILL.md 的开头把这件事说得很直白:被空降进陌生仓库的 Agent,出问题不是因为没本事,是因为没地图。 它盲目 grep、重复造一个已经存在的工具、改了一个被另外三个功能依赖的文件。context-map 这份 skill 的产出,就是那张地图——建一次,之后的每个会话开局就有方向。
这份 skill 干什么:把地形画成一张图
这份 skill 的用法列了四种触发时机:第一次在一个仓库里干活;仓库的 AGENTS.md / CLAUDE.md 缺失、过期,或者是"两千行没人能用的大杂烩";Agent 老在问仓库自己能回答的问题——测试怎么跑、组件在哪、什么不能碰;一次目录、命令或约定层面的重组之后。
它的产出是一份放在仓库根目录的 AGENTS.md(或仓库的等价物),内容固定五块:
- 结构:顶层目录清单,每个目录一句话说清它管什么;
- 命令:安装、运行、构建、lint、类型检查、测试——一张从真实配置抄来、逐条验证过的命令表;
- 事实源头:每类事实(API 契约、设计 Token、路由规则、状态约定、错误码)分别指明哪个文件或文档是权威;
- 高危区:粗心改动会放大爆炸半径的文件和目录,每条带理由;
- 硬规则:仓库少数不可商量的约定,写成祈使句。
注意这五块没有一块是"代码讲解"。地图不解释任何一个函数怎么实现,它只回答:东西在哪、命令怎么跑、哪句话说了算、哪里危险、规矩是什么。
勘察:先看结构,再看内容
建图的第一步叫勘察地形:列出顶层目录,每个用一句话说清用途。原文对产出的要求是"每条都有用途,不只是名字"。
顺序是关键:先结构,后内容。二十万行代码读不完,也不需要读完——先知道 src/features/ 管业务功能、src/shared/ 管跨功能共享,写日期格式化时该去哪一层找,心里就有数了。这一步防的正是开场那一幕:三个同名工具里哪个是正牌,不靠名字猜,靠它所在目录的定位和下面的事实源头去定。
勘察也不许凭印象。每个目录"管什么"要对着它真实的内容写,写错一句,后面每个会话都被这一句带偏——地图上的错误比没有地图更贵,因为读图的人不会再去核对。
命令与事实源头:只收录核验过的
第二步收录命令:从 package scripts、Makefile 或 CI 配置里找齐那六类命令,并"验证每条真实存在"。产出的要求写得很死:命令表"从真实配置抄来,绝不凭记忆"。
反模式里专门点名一条:命令表从 README 抄而不对真实 scripts 核验——过期命令比没有命令更糟,因为 Agent 会信它。 没有命令表,Agent 会自己去找;有一条错的,它照着错的跑,失败了还先怀疑代码。这和语言层那条"说谎的签名比没签名更危险"是同一条道理:带错的导航比没导航更能把人带沟里。
第三步指向事实源头:每类事实指明权威文件,产出是"每条一行的指针——地图只链接真相,不复制真相"。
这一句是整份 skill 的骨架。API 契约的真相在契约文件里,状态约定的真相在状态规范里;地图只负责说"这类问题去哪查"。把真相抄进地图,等于同一份事实存了两个地方,从此必然有一份过期——这正是状态层讲过的"一个事实,只许有一个事实源",用在了文档上。
高危区与硬规则:地图上的警示牌
第四步标出高危区:列出共享组件、全局样式、配置、生成代码、迁移脚本这类地方,并写明在那里"小心"具体意味着什么。产出要求是"一份短清单,每条带理由"。
带理由这条不能省。只写"不要动 src/shared/",Agent 遇到非动不可的场景只会二选一:硬着头皮乱动,或者绕远路另造一个。只有写清"这个目录的组件被全部功能共用,改 props 等于同时改所有调用方",它才知道小心的做法是加新 props 而不是改旧的——开场那一幕里被顺手改掉的共享组件,就是这么死的。
第五步记下硬规则:把仓库少数不可商量的约定写成祈使句,例如原文的例子——“用共享 Button,不要新建一个”。产出要求:最多几条,每条可检验。
反模式在这里点名了写法的分界:约定写成愿望(“我们偏好干净的代码”)不算规则,要写成可检验的(“文件保持在 300 行以内;要加先拆”)。愿望没法核对,读的人点点头就过去了;可检验的规则才进得了任务门禁和评测条目。和硬规则配对的还有数量纪律:最多几条。规则一多,每条都在贬值,最后和那两千行的大杂烩殊途同归。
设计一:地图是文件,不是记忆
为什么非要落成文件?原文的说法是:这份 skill"把地图建一次,让之后的每个会话开局就有方向"。
“每个会话"四个字是关键。在这套系统里,上下文是一次性的:澄清、实现、评测各是全新上下文,没人能把上一个会话的所见转述全。一个 Agent 花二十分钟摸清的地形,写在它的上下文里,会话一结束就蒸发;下一个 Agent 进来,从零再摸一遍,还不保证摸对同一个地方。
文件不蒸发。地图建在仓库根目录,谁进来都先读它,二十分钟的勘察成本只付一次。这也是它和前面那些 skill 的分工方式:clarify-requirements 访谈出来的项目认知、write-tech-design 要参考的既有模式,前提都是得先有一张可信的地图垫底。地图是所有后续会话共享的那一份地形事实。
设计二:记的是「这里怎么做事」,不是文件清单
第六步给整张图定了形态:保持索引形态——哪一节需要超过几行,就链接到文档而不是粘贴进来;产出标准是"整张地图两分钟内能扫完”。完成判据里再重复一遍:地图是索引,指向事实而不是复述事实,几分钟能读完。
这回答了一个很容易做反的选择:地图记的到底是"仓库里有什么",还是"这个仓库怎么做事"?
文件清单记的是前者,而且是最没用的记法:二十万行仓库的文件清单本身就有几千行,写进地图,地图立刻变成另一份没人读的东西——反模式第一条就是把整个 wiki 倒进 AGENTS.md,“什么都装的地图没人读,而且恰恰在最重要的地方腐烂得最快”。
真正要记的是后者:结构一节每个目录的"用途",讲的是模块怎么分工;事实源头讲的是每类问题去哪问;硬规则讲的是这里的规矩。这三样加起来就是"这里怎么做事"。文件会天天增删,做事的方式稳定得多——地图记稳定的那一层,才配叫长期资产。
第七步是收尾的验证:走一遍验证——随机挑三个指针打开,错的、死的当场修;完成判据要求"每条路径和命令都对着当前仓库核验过"。注意它抽查的是指针,不是内容:地图的承诺只是"指的地方对",指对了,内容以被指的文件为准。
它管不了什么
把边界说清。
第一,地图只保证指针是对的,不保证被指的东西是对的。它说状态约定以某个文件为准,那个文件本身写错了、写旧了,地图不会发现——地图不审真相,只登记真相住哪。
第二,地图会过期,而且过期的姿势很安静。反模式最后一条就是警告这个:仓库重组了,旧路径还留在地图里,“下个会话照着一张不存在的城市的地图导航”。这份 skill 能做的只是把"重组之后重建地图"写进触发时机,把抽查写进流程;它没有办法自己发现城市已经变了——发现变化靠的是用图的人撞了墙,或者重组的人顺手更新。
第三,它记下的是"这里怎么做事",不评判这套做法好不好。仓库的既有模式本身是烂的,地图会如实登记,照图干活的 Agent 会整齐地把烂模式复制下去。模式该不该改,是规划阶段拿着地图做的判断,不是地图的职责。
所以这份 skill 在整套系统里的位置是地基里的一块:它保证每个新上下文不再盲目开局。下一篇讲 state-schema-first:地图里"状态约定"那一行指针指向的地方,具体怎么把一份数据的形状、归属和事实源先定义死,再开始写代码。