【AI Native开发-03】上下文工程:让 AI 真正理解你的项目
这是“AI Native开发”系列的第 3 篇。前两篇建立了 SDD 的四阶段与四道闸门,并用一次完整交付演示了错误如何被拦在闸门之外。但那次演示有一个隐含前提:AI 开工前就理解了这个项目。本篇专门讨论这个前提——如果它不成立,再好的 Spec 也会被错误实现。
摘要
本篇回答一个问题:为什么同一个模型,在 A 项目里像资深同事,在 B 项目里像第一天入职的外包? 结论是模型不是变量,上下文才是。文章给出上下文失效的四种模式(缺失 / 过时 / 淹没 / 冲突)、上下文分层模型(L0–L4)、四条组织原则(就近、单一事实来源、新鲜度、按需加载),以及可以直接落地的入口文件骨架、踩坑笔记模板和上下文健康度自检表。
先给结论:模型不是变量,上下文才是
很多人评估 AI Coding 效果时的思路是错的:他们换模型、调温度、改提示词,试图让“AI 更聪明”。但在固定模型、固定任务复杂度的前提下,决定产出质量的最大变量是上下文——AI 在动手那一刻,能看到、且能注意到的关于这个项目的全部信息。
同一段需求:“给文章加删除功能”
在上下文完备的项目里:
AI 知道文章是否公开由 frontmatter 的 hide 字段决定
AI 知道权限走 CMS 身份,不是自建用户表
AI 知道本项目没有测试脚本,验证靠 pnpm build 加人工确认
→ 一次产出可审查的实现
在上下文缺失的项目里:
AI 假设状态存在 article.status 字段
AI 自建一套 user/role 权限表
AI 猜了一个 npm test 命令,实际不存在
→ 结构完整、测试“通过”、但全盘皆错上一篇教学案例里的事故——Spec 写“已发布不可删除”,实现去读 article.status,而项目实际用 hide: true 布尔字段——本质不是模型能力问题,而是上下文里缺了一条“这个项目怎么表达状态”的事实。
SDD 的四道闸门能拦住错误,但闸门的输入质量取决于上下文。上下文工程,就是在闸门之前保证 AI“看得见事实、注意得到约束”。
上下文失效的四种模式
上下文出问题,不是“有没有文档”这么简单。归纳为四种失效模式,每一种的症状和修法都不同。
C1:缺失——约束不存在,AI 就会发明
AI 面对空白时不会停下来问“这个项目怎么规定”,它会用训练数据里最常见的做法补全。缺上下文时,AI 的默认行为是按最流行的开源项目套路实现,而你的项目几乎必然不是那个套路。
- 症状:AI 引入了项目里不存在的抽象(自建状态字段、自建权限层、引入没装过的依赖)。
- 对应上一篇的失效模式:F1(状态假设)、F2(权限默认放行)大多源于 C1。
- 修法:把“这个项目怎么做 X”写成显式事实,放到 AI 必读的位置。
C2:过时——文档说 A,代码是 B
这是最危险的一种。过时上下文比没有上下文更糟:没有时 AI 还会去读代码确认;过时时,AI 会信任文档而跳过代码核实。
- 典型场景:重构后状态字段从
status改成了hide,但文档里的示例还在写status。 - 症状:AI 严格按照过时文档实现,代码评审时人一眼看出不对,但 AI “有据可依”。
- 修法:上下文文件必须和代码在同一次变更里更新;评审时把上下文 diff 当作代码 diff 的一部分审。
C3:淹没——关键信息被无关内容稀释
上下文窗口很大,但注意力是稀缺资源。把整个仓库、所有历史聊天记录一股脑塞给 AI,结果不是“它知道得更多”,而是关键约束淹没在噪声里,被忽略的概率显著上升。
- 症状:你明明在 README 里写了规则,AI 还是违反了——但那条规则在第 800 行,夹在两段无关的安装说明中间。
- 经验规律:入口文件超过一屏,AI 对入口文件后半段的遵从度明显下降。
- 修法:入口要薄,细节下沉到专门文件,用“按需读取”的索引代替“一次性全给”。
C4:冲突——两个来源互相矛盾
同一条规则在两个地方写法不同,AI 会自己挑一个——而它挑的那个往往是表述更自信、出现得更近的那个,不一定是对的。
- 典型场景:README 说“用 pnpm”,某个旧脚本注释里写“用 npm”;A 文档说删除走软删除,B 文档说直接物理删除。
- 症状:同一条规则,不同任务里 AI 的实现互相矛盾。
- 修法:单一事实来源(见下文原则二),冲突必须消灭而不是并存。
失效模式速查:
C1 缺失 → AI 发明约束 修:显式写出来
C2 过时 → AI 信任旧文档 修:上下文随代码一起改
C3 淹没 → 关键约束被稀释 修:入口做薄、按需加载
C4 冲突 → AI 随机选一个 修:单一事实来源上下文分层模型:L0 到 L4
不是所有信息都该放在同一个地方。按生命周期和使用者分五层:
| 层 | 内容 | 生命周期 | 谁主要维护 | 典型载体 |
|---|---|---|---|---|
| L0 隐式上下文 | 代码本身:类型、命名、测试 | 随代码实时变化 | 全体开发者 | 仓库源码 |
| L1 项目入口 | 项目是什么、怎么跑、铁律、索引 | 长期稳定,低频更新 | 技术负责人 | AGENTS.md / CLAUDE.md |
| L2 架构与约定 | 架构决策、模块边界、编码规范 | 随架构演进,按决策更新 | 架构决策人 | ARCHITECTURE.md、docs/decisions/ |
| L3 活的记忆 | 踩坑、当前 Spec/Plan、已知技术债 | 高频更新,随任务沉淀 | 每个任务的执行者 | docs/pitfalls.md、specs/、plans/ |
| L4 会话上下文 | 本轮任务的临时说明 | 单次任务 | 当前操作者 | 对话、任务指令 |
几个关键判断:
- L0 永远是最终事实来源。 文档(L1–L3)和代码冲突时,以代码为准——然后立刻修文档,消灭 C2/C4。
- L1 是 AI 的“第一印象”,决定它会不会读 L2/L3。 入口文件里最重要的内容不是规则本身,而是索引:告诉 AI“遇到 X 类问题,去读 Y 文件”。
- L3 是 SDD 的天然副产品。 Spec、Plan、Verify 报告、踩坑笔记本来就在仓库里(第 2 篇强调过“Spec 是仓库里的文件,不是聊天记录”),它们同时就是上下文资产。
- L4 最不可靠,最不该承载长期规则。 只在对话里说过的约束,换个会话就丢了——这正是要把规则下沉到 L1–L3 的原因。
四条组织原则
原则一:就近——规则放在它约束的东西旁边
规则离被约束的代码越近,被读到的概率越高,也越不容易在重构时被漏掉。
差:所有规则集中在根目录一个 3000 行的 KNOWLEDGE.md 里
好:
根目录 AGENTS.md → 只放全局铁律 + 索引
src/content/ 旁的说明 → 放内容模型的规则
specs/article-delete/ → 放这个功能自己的 Spec/Plan/Verify
pitfalls.md 里的条目 → 每条标注涉及的文件路径“删除草稿文章”那个状态字段事故,最该沉淀的位置不是通用文档,而是内容模型旁边——因为下一个动文章状态的人(或 AI)一定会出现在那里。
原则二:单一事实来源(SSOT)
同一条规则,全仓库只允许有一个权威定义处。其他地方需要引用时,引用而不是复制。
反例(C4 冲突温床):
README 里写了一遍测试命令
CONTRIBUTING 里又写了一遍
CI 配置里再写一遍
→ 三处迟早不一致
正例:
AGENTS.md 写:“测试与构建命令以 package.json scripts 为准”
package.json 是唯一定义处
CI 和文档都引用它判断方法:如果你要改一条规则,需要改超过一个文件,就违反了 SSOT。
原则三:新鲜度——上下文随代码一起变更
把上下文文件当成代码对待:它在同一个 PR 里变更,接受同样的评审。
- 改了数据模型 → 同一次提交更新架构说明和相关 pitfalls 条目。
- 发现一个新坑 → 任务收尾时写进 pitfalls.md,而不是记在聊天里。
- 评审清单里加一条:“这次变更有没有让某条现有上下文变假?”
这条原则对应 C2 的根治。过时上下文不是“文档质量问题”,是变更流程没有把文档纳入。
原则四:按需加载——别把窗口当垃圾桶
给 AI 的上下文要像给新同事的 onboarding:先给一页纸总览,告诉他“需要细节时去哪查”,而不是第一天把所有档案柜钥匙塞给他。
- 入口文件控制在一屏到两屏,超过就拆分下沉。
- 在入口里写明路由规则:“动权限相关代码前,先读
docs/decisions/0007-auth-model.md”。 - 让 AI 在 Plan 阶段显式列出“我读了哪些上下文文件”,这既是检查也是留痕。
一个真实的上下文沉淀:pitfalls.md 如何救回下一次实现
第 2 篇的事故根因是:Spec 假设了 status 字段,实际项目用 hide 布尔。那次归因结束后,结论没有停在聊天记录里,而是写进了踩坑笔记:
# docs/pitfalls.md
## 内容模型的状态表达
本项目文章是否公开用 frontmatter 的 hide 布尔字段表达,
不存在 status 字符串字段。
- hide: true → 不在列表/RSS 中出现(可删除)
- hide: false 或缺省 → 已公开(不可直接删除)
历史事故:曾因 Spec 写「已发布不可删除」而实现读取
article.status,值为 undefined 导致校验被绕过,
TC-004 期望 409 实际返回 204。
给 AI 的指令:任何涉及文章公开状态的判断,都基于 hide 布尔;
不要引入 status、state、draft、is_published 等新字段,除非
Spec 明确要求并同步修改 content.config.ts 的 schema。这条笔记的价值在下一个任务兑现:后来做“文章批量归档”时,AI 在 Plan 阶段主动读到了这条,直接基于 hide 字段设计,并在 Plan 里注明“遵循 pitfalls.md 的状态表达约定”。一次代价不小的事故,变成了之后所有相关任务的免费免疫力。
这就是上下文工程和“写文档”的本质区别:文档是写给人看的归档,上下文是写给 AI(和下一个人)执行的约束,它必须包含事实、事故、以及“下次该怎么做”的明确指令。
入口文件长什么样:AGENTS.md 最小骨架
L1 入口不需要面面俱到,它只需要回答四件事:这是什么、怎么跑、铁律是什么、细节去哪找。下面按本博客项目的真实情况写一份:
# AGENTS.md
## 这个项目是什么
Astro 5 静态博客。文章是 src/content/blog/ 下的 Markdown 文件,
按 年/月 分目录。内容后台目前 Keystatic 与 Decap 两套并存
(@keystatic/astro + decap-server),没有自建服务端和用户表。
## 怎么跑
- 安装依赖:pnpm install
- 本地开发:pnpm dev(默认 4321 端口)
- 带 CMS 开发:pnpm dev:cms(并行起 astro dev 和 decap-server)
- 构建:pnpm build
- 新建文章:pnpm newpost,不要手写目录和 frontmatter
- 本项目尚未引入测试框架,没有 pnpm test;
验证手段是 pnpm build 通过 + 人工确认页面
- 包管理器统一用 pnpm,不要用 npm/yarn
## 结构地图
- src/content/blog/ 文章正文(Markdown + frontmatter)
- src/content.config.ts 内容集合与 frontmatter schema(zod)
- src/pages/ 路由与页面
- src/plugins/ Markdown 自定义插件
- specs/ 各功能的 Spec / Plan / Verify 记录(待建)
- docs/pitfalls.md 踩坑笔记,动相关代码前必读对应条目(待建)
## 铁律(不可违反)
1. 文章是否公开用 frontmatter 的 hide 布尔,没有 status/draft 字段
2. frontmatter 字段必须在 content.config.ts 的 schema 里声明
3. 不要引入服务端框架:本站是静态构建,写操作走 CMS/Git
4. 删除任何文件前必须确认;不要改动 dist/(构建产物)
## 上下文路由
- 改内容模型 / frontmatter → 先读 src/content.config.ts 和 pitfalls「内容模型的状态表达」
- 改文章相关功能 → 先读 specs/ 下对应功能目录
- 不确定命令是否存在 → 先看 package.json 的 scripts,不要猜注意最后两节。铁律只放违反了会直接导致返工或事故的少量规则(对应 C1 最高频的坑);上下文路由是入口文件真正的杠杆——它把 L1 从“规则堆积地”变成“按需加载的目录页”,治 C3。
怎么知道上下文够不够:几个可观察信号
上下文质量没有分数,但有可观察的行为信号:
| 信号 | 说明 | 指向的问题 |
|---|---|---|
| AI 反复问同一个已经“写过”的问题 | 写了但放错位置或没被索引 | C3 淹没 / 索引缺失 |
| AI 频繁引入项目里不存在的字段、依赖、抽象 | 该有的事实没有显式化 | C1 缺失 |
| 实现和文档描述不一致但各自“都对” | 文档没跟上代码 | C2 过时 |
| 不同任务里 AI 对同一件事做法矛盾 | 规则有多个出处 | C4 冲突 |
| Plan 阶段列不出“读了哪些文件” | AI 没建立先读上下文的习惯 | L1 路由失效 |
这些信号同时也是第 2 篇闸门度量指标的补充:如果“计划外文件修改数”长期偏高,先别怀疑模型,先查上下文。
附录 A:上下文文件清单
下面是一份完整形态的参照,不是本博客仓库的现状——本站目前这些文件一个都没建,本系列里出现的 AGENTS.md、docs/pitfalls.md、specs/ 都属于方法论示意。列出来是为了说明每一层该放什么,以及从哪两个文件开始最划算。
AGENTS.md / CLAUDE.md L1 入口:是什么、怎么跑、铁律、路由索引
ARCHITECTURE.md L2 架构:模块边界、数据模型、关键决策概览
docs/decisions/ L2 决策记录(ADR):每个重要决策一个文件,含背景与取舍
docs/pitfalls.md L3 踩坑笔记:事故 → 根因 → 下次怎么做
specs/<feature>/ L3 功能档案:spec.md / plan.md / tests.md / verify.md
docs/glossary.md L2 业务术语表:消除同名歧义(对应第 0 篇术语约定)落地建议:不要一次性建齐。从 AGENTS.md + pitfalls.md 两个文件开始,其余在任务中自然长出来——上下文工程本身也该小步迭代,而不是先花两周写一份没人维护的“完整文档”(那只会制造 C2)。
附录 B:上下文健康度自检表
[ ] 新人(或新会话的 AI)只看 AGENTS.md 能跑起项目
[ ] 每条铁律都能对应一次真实事故或高频错误,没有“正确的废话”
[ ] 同一条规则在全仓库只有一个权威定义处
[ ] 最近三次重构,相关上下文都在同一次变更里更新了
[ ] pitfalls.md 每条都包含:事实、事故、给 AI 的明确指令
[ ] 入口文件不超过两屏,细节都有索引可查
[ ] AI 的 Plan 里会显式列出“本次读取了哪些上下文”
[ ] 没有任何长期规则只存在于聊天记录里一条使用经验:这份表自检时最容易打勾、实际最容易破的是第 4 条(变更时更新上下文)。它靠的不是自觉,是把“上下文 diff”加进评审清单。
上下文解决的是“AI 理解现状”的问题。但理解现状之后,AI 还需要一样东西:一份它能无歧义执行的需求。下一篇,我们进入 SDD 的第一个正式产物——怎么写一份 AI 能执行的 Spec:它和需求文档有什么本质区别,十项结构分别解决什么问题,以及为什么“非目标”是整篇 Spec 里最被低估的一节。
