上一篇文章的结尾,我提了一个问题:Execution 之后,代码自动进入了 Knowledge 空间,但其他几层知识不会自动更新。下一次任务还是会从同样不完整的 Context 出发
那么需要更新的到底是什么?怎么更新?
这不是一个理论问题。如果你读完前六篇,觉得「把知识外化」听起来有道理,但不知道具体怎么动手——这篇文章就是给你的
很长一段时间,我对「知识沉淀」这四个字也十分抗拒。它听起来像某种咨询公司术语,暗示着一套繁重的文档体系。直到看到 Matt Pocock 的 skills 仓库中的实践,我才意识到这件事可以做到极简
两样东西就够了:统一语言和不可逆决策
两个文件
在 Matt 的实践中,一个项目的知识基础设施只需要两个目录:
1 | / |
不是「知识库」「文档中心」「团队 Wiki」——就是两个东西。多一样都没有
这不是偷懒。这是一种精确的判断:AI 和人协作时,最少需要外化的知识,就这两类
一类解决「我们在说什么」(统一语言),一类解决「为什么这里长这样」(不可逆决策)
CONTEXT.md:统一语言的落地
先说统一语言。这个概念来自领域驱动设计(DDD)的 Ubiquitous Language,但在 AI 时代,它的价值不再只是「领域专家和开发者说同一种语言」——而是人和 AI 说同一种语言
一个很常见的场景:你在和 AI 讨论退款流程,一会儿说「订单取消」,一会儿说「退款回滚」,一会儿说「资金退回」。人知道你指的都是同一件事。AI 不知道。它可能认为这是三个不同的概念,分别搜索代码、分别评估影响面——浪费了 tokens,增加了出错概率
但如果你在仓库里有一个 CONTEXT.md:
1 | # 退款上下文 |
你可能会问:AI 怎么知道要读这个文件? Matt 的 setup-matt-pocock-skills skill 会在仓库的 CLAUDE.md 或 AGENTS.md 中写入一段指令,告诉 AI:每次探索代码前,先读 CONTEXT.md 获取项目术语,先查 docs/adr/ 了解历史决策。这样 CONTEXT.md 不需要你每次手动喂给 AI——它被纳入了 AI 的默认上下文
AI 读到这个文件之后,不再把「退款回滚」当成独立概念去搜索。它知道这就是「退款」
这看起来太简单了。但它的威力在持续的对话中累积: AI 每一次生成代码、每一次检索文件、每一次评估影响面,使用的都是同一套词汇。你不必在每次对话中重新定义「我们叫它退款而不是回滚」——CONTEXT.md 替你做了这件事
格式要点
严格来说 CONTEXT.md 就是一个纯 glossary,不包含任何实现细节。有几个规则值得说一下:
- 定义要短。 一两句话,说清楚它是什么,而不是它做了什么。定义写在「是什么」上比写在「做了什么」上更稳定——做的事情会变,但概念本身不容易变
- 别名要列入黑名单。
_Avoid_列表不是装饰。它告诉 AI:当你看到这些词,它们就等于这个标准术语。统一语言的核心不只是「定义了什么」,还是「排除了什么」 - 只放项目特有的术语。 通用的编程概念(timeout、error type、utility function)不要放进去。CONTEXT.md 不是编程词典——它是这个项目里容易产生歧义的词
- 分组按自然聚类。 如果项目里有清晰的子域(退款、积分、通知),可以按子域分组,用二级标题隔开。如果没有,一个平面列表完全够用
多上下文仓库(比如 monorepo 里有多个子系统),可以用一个 CONTEXT-MAP.md 列出每个上下文的 CONTEXT.md 位置和它们之间的关系。但大部分仓库不需要——一个 CONTEXT.md 就够
ADR:不可逆决策的记录
如果说 CONTEXT.md 解决的是「词不达意」,ADR 解决的是「代码不告诉你的事」
一个很典型的场景:你看到一段代码写得奇怪,你想改它,但你判断不了——是写得不好所以可以改,还是有一个你不知道的原因所以不能改
ADR 就是那个原因
什么时候写 ADR
Matt 对 ADR 有三个硬条件,缺一不可:
- Hard to reverse — 改了之后难以回头。修改 API 返回字段、更换数据库、调整消息队列的 Event schema——一旦做了,回滚意味着多个系统一起回滚
- Surprising without context — 后来的人看了代码会疑惑「为什么选了这么奇怪的做法」。如果决策是显而易见的,就没有解释的必要
- The result of a real trade-off — 真的有备选方案。如果只有一个选项,那不是决策,那是唯一路径
三个条件放在一起,形成了一个简单的决策规则:
如果决策容易逆转 → 不需要 ADR,你直接逆转它就行了
如果决策一看就懂 → 不需要 ADR,代码本身已经解释了
如果根本没有其他选择 → 不存在决策,不存在 ADR
这个规则的效果是:docs/adr/ 目录里没有废话。当你翻到一篇 ADR,读完第一段就知道——这是一个重要的、不明显的、不能随便推翻的决定
格式可以极端简单
1 | # 退款幂等使用 Redis 而不是分布式锁中间件 |
一篇 ADR 可以就是一个标题加一段话。不需要填写「状态」「背景」「影响」「相关方」——如果你不确定该写什么,标题加一段解释就够了
当然,如果需要,你可以补充:
- Status —— 决策后来被推翻了吗?被哪个新 ADR 替代了?
- Considered Options —— 只有被拒绝的选项本身也值得记住时才写。如果被拒绝的选项明显不合理,跳过
- Consequences —— 只有非显式的下游影响需要单独列出
关键是:不要为了填模板而编内容。 ADR 的格式是建议,不是考勤表
编号和位置
docs/adr/ 下的文件用四位序号:0001-slug.md。序号就是产生顺序,不承载其他含义。扫描目录取最大序号加一即可,不用纠结编号体系
什么值得写
- 架构形状:单仓库还是多仓库、事件溯源还是 CRUD
- 技术栈中难以替换的选择:数据库、消息队列、部署方式
- 跨上下文的集成模式:同步 RPC 还是异步事件
- 边界和范围决策:「用户数据归属于用户上下文,其他上下文只能引用 ID」
- 故意偏离常规的做法:大多数人会选择 ORM,但你选了手写 SQL——解释为什么
- 代码里看不到的约束:合规要求、性能 SLA、第三方接口限制
- 非显而易见的拒绝:考虑了 GraphQL 但选了 REST,而且理由不是「REST 更快」——记录下来,否则六个月后又会有人提议切到 GraphQL
你可能会发现,这个列表里的很多东西,和我们系列第四篇讨论的「不可逆决策」高度重叠。不是巧合——两类知识沉淀,对应两类决策属性:可逆的不需要记,不可逆的需要
这能覆盖多少知识?
回到系列第二篇的六层知识框架。用 CONTEXT.md + ADR 这个极简方案,各层的覆盖情况是:
| 知识层 | 覆盖情况 |
|---|---|
| 基础知识 | 不需要覆盖——它在 AI 的训练数据里 |
| 代码 | 自动覆盖——代码本身就是知识 |
| 需求 | 不全覆盖——CONTEXT.md 的术语定义部分触及需求层,但原始需求仍然需要从 PRD 或 issue 里找 |
| 业务逻辑 | 部分覆盖——术语定义和 ADR 中的决策理由可以帮助还原部分业务逻辑,但被打散在各处的完整逻辑链,glossary 拼不起来 |
| 架构设计 | 较好覆盖——ADR 天然适合记录架构决策 |
| 历史决策 | 较好覆盖——这就是 ADR 的设计目标 |
覆盖不全的地方有两类:一类是最稳定的(基础知识,不需要你记),另一类是最易变的(业务逻辑,记录成本太高,更依赖代码索引来自动发现)。中间的——历史决策和架构设计——被 ADR 覆盖得最好
这个覆盖模式,反过来也说明了 CONTEXT.md + ADR 为什么是合理的 第一步:它们在投入产出比最高的地方动手。先把不可逆的、代码里看不到的东西记下来。需求层和业务逻辑层的覆盖,可以等到 Context 构建半自动化的时候再解决
AI 在其中的角色
知识沉淀本身,AI 能做一部分。不是让它凭空写——而是在对话的过程中实时捕捉:
- 你在讨论改动方案,提到了一个模糊的术语 → AI 追问精确含义,确认后写入 CONTEXT.md
- 你做了一个不可逆的决策(选了方案 A 而不是 B)→ AI 草拟一篇 ADR,三句话解释为什么,你扫一眼确认
- 你在 review 代码时发现一段逻辑和 CONTEXT.md 中的定义有矛盾 → AI 指出矛盾,要求统一
Matt 的 domain-modeling skill 就是这样设计的——知识外化不是写完代码之后补文档,而是在对话中实时发生。具体的机制下面展开
这本质上是系列第五篇讨论的「AI 起草,人签字」模式——AI 生成草稿,人验证和决策。这样一来,知识沉淀不会成为额外的负担,因为它被编进了工作流,而不是一个独立的事后步骤
这些文件在一个完整工作流中的位置
说了这么多,CONTEXT.md 和 ADR 在实际的 AI 协作流程中,到底在什么环节被消费?
Matt 的 skill 体系里有一条完整的工作流链:
1 | grill-with-docs → to-spec → to-tickets → implement → code-review |
每一步都依赖知识沉淀的产出:
grill-with-docs(对话 + 文档化)。这是整个流程的入口。你和 AI 讨论一个需求,AI 一次一个问题的追问你——为什么这样做、这个术语具体指什么、这个边界在哪里。在问答过程中,确认的术语实时写入 CONTEXT.md,不可逆的决策实时写入 ADR。对话结束的时候,文档也更新完了。不是「讨论完了再去补文档」——讨论本身就是文档化
to-spec(生成 Specification)。AI 基于刚才的对话和已更新的 CONTEXT.md、ADR,将需求合成一份 Spec。因为 glossary 里的术语是精确的,Spec 不会出现「同一个概念用了三个名字」的问题。因为 ADR 里记载了历史决策,Spec 不会提出和被拒绝方案矛盾的实现路径。它读 ADR 和 CONTEXT.md 不是为了引用,而是为了不犯错
之后的 to-tickets(拆分 ticket)、implement(TDD 驱动实现)、code-review(双轴并行 review)是执行链条,不在本文展开。CONTEXT.md 和 ADR 在后续每一步都持续发挥作用:ticket 描述复用 glossary 词汇,实现时参考 ADR 避开历史雷区,review 时用 Spec + Standards 双轴保障质量
grill 这个机制值得展开说说。它和传统的需求评审不一样。需求评审是「你讲给我听,我点头」。grill 是「我一个一个问题追问,每个问题带着一个推荐答案」。它不是为了让你舒服——它就是为了从你脑子里逼出那些你以为是常识、AI 完全不知道的东西。而那些东西,就是知识沉淀的原材料
这和我们系列反复出现的一个观点一致:AI 是决策放大器,不是决策者。 关键节点上,人的判断不可替代
如果你想今天就开始
理论说到这。如果你想在自己的项目里试一试,从零到第一篇 ADR 只需要两步:
第一步:setup。 在项目根目录运行 /setup-matt-pocock-skills。 AI 会自动检查你的仓库——用了 GitHub Issues 还是其他 issue tracker、是单仓库还是 monorepo——然后在 CLAUDE.md 或 AGENTS.md 中写入一小段配置。配置的效果只有一个:告诉 AI,以后每次探索代码之前,先读 CONTEXT.md 和 docs/adr/
如果你不想安装整套 skills,也可以手动做到同样的事。在 CLAUDE.md 中加一句:「在开始任何代码修改前,先阅读 CONTEXT.md 了解项目术语,检查 docs/adr/ 确认相关历史决策」
第二步:grill。 下一次开始一个新需求时,不要直接写代码。运行 /grill-with-docs。AI 会一次一个问题地追问你——为什么这样做、这个术语具体指什么、如果出现冲突怎么办。在一个个问题中,模糊的概念变精确,隐含的假设被挑出来,确认的术语实时写入 CONTEXT.md,不可逆的决策实时写入 ADR
这和前面说的原则一样——知识沉淀被编进了工作流,不是事后的额外步骤
CONTEXT.md 和 ADR 不需要在项目第一天就建好。它们是在一次次有意义的对话中,慢慢长出来的。setup 是搭基础设施,grill 是日常使用。两件事做完,知识沉淀就从「想法」变成了「习惯」
从一个词开始
如果你读完想动手,从一件小事开始:打开仓库,新建一个 CONTEXT.md,写下一个词的定义
不需要把所有术语都梳理清楚。不需要补三年前的 ADR。只需要一个词——你最近一次讨论中最容易产生歧义的那个词——定义它,标注它的别名黑名单
然后下次和 AI 协作时,告诉它这个文件的存在。你会注意到一个微妙的变化:它不再用 20 个词说同一件事了
ADR 也不用急着补。等下一个不可逆的决策出现时——你会在说「那就这样吧」之后感到一丝不确定——那时候,花三分钟写一段话,放在 docs/adr/0001-some-slug.md 里。标题加三句话,就够了
不是因为你将来可能需要它(你可能确实会需要),而是因为写下来的过程中,你会发现自己是不是真的想清楚了
这是 AI Native 软件开发系列的第七篇。前六篇:软件开发管理的对象是知识 → 知识的六个层次 → 从知识到上下文的筛选 → 工程决策——在不确定中做最好的选择 → Specification——把决策翻译给 AI → Execution 与闭环——从执行到知识的回流。这一篇开始讨论落地的第一步:知识沉淀。如果你有不同的理解或者实践经验,欢迎一起讨论。