Home
avatar

.Sam

【AI Native开发-03】同一个 AI,为什么换个项目就“变笨”?

这是“AI Native开发”系列的第 3 篇。前两篇建立了 SDD 的四阶段与四道闸门,并用一次完整交付演示了错误如何被拦在闸门之外。但那次演示有一个隐含前提:AI 开工前就理解了这个项目。本篇专门讨论这个前提——如果它不成立,再好的 Spec 也会被错误实现。

30 秒看懂这篇

答案通常不只在模型,也在它动手那一刻能看到什么、注意到什么。在模型和任务固定时,上下文是影响产出质量的关键变量之一。

flowchart TD
    A["同一句需求"] --> B["上下文缺失 / 过时 / 淹没 / 冲突"]
    A --> C["代码事实 + 架构约定 + 当前 Spec + 按需索引"]
    B --> D["猜字段 · 猜命令 · 自建权限 · 引入错误依赖"]
    C --> E["先核实现状,再形成受约束 Plan"]
    D --> F["看起来完整,实际偏离项目"]
    E --> G["实现更可审查,验证更可复现"]

本篇给出上下文失效的四种模式、上下文分层模型(L0–L4)、四条组织原则,以及可以直接落地的入口文件骨架和踩坑笔记模板。

上一篇假想案例里,Spec 已规定使用 hide,实现却读取 article.status。直接根因是 Plan/Implement 的字段映射错误;上下文工程的改进点,则是让“项目用 hide 表达状态”这一事实在规划时更容易被找到和引用,降低同类错误发生的概率。

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 行,夹在两段无关的安装说明中间。
  • 经验性提醒:入口越长、无关内容越多,关键规则越难稳定被注意;“一屏到两屏”是本系列采用的组织建议,不是适用于所有模型的固定阈值。
  • 修法:入口要薄,细节下沉到专门文件,用“按需读取”的索引代替“一次性全给”。

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;但代码不能推翻已批准 Spec 对“业务应该是什么”的定义——实现偏离契约时应修实现,而不是把现状当成正确行为。
  • 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 篇假想事故的直接根因是 Plan/Implement 错用了 status 字段。若把教训沉淀下来,可以写成下面这种既给人看、也给 AI 读的约束(这是模板示例,不代表当前仓库已有该文件):

# docs/pitfalls.md

## 内容模型的状态表达

本项目文章是否公开用 frontmatter 的 hide 布尔字段表达,
不存在 status 字符串字段。
- hide: true         → 不在列表/RSS 中出现
- hide: false 或缺省 → 已公开

教学事故:实现曾把 Spec 的 hide 错映射为 article.status,
值为 undefined 导致校验被绕过,TC-004 期望 409 实际返回 204。

给 AI 的指令:任何涉及文章公开状态的判断,都先核对
content.config.ts 与查询代码,并基于 hide 布尔;
不要引入 status、state、draft、is_published 等新字段,除非
Spec 明确要求并同步修改 schema。

这条笔记的价值要在下一个任务兑现。例如做“文章批量归档”时,AI 应在 Plan 中引用这条事实,并再次用代码确认它仍成立。注意:踩坑笔记能降低复发概率,但不能代替代码核查与测试。

这就是上下文工程和“只把文档归档”的区别:好的上下文既包含事实与事故,也包含“下次怎么行动”的明确指令,并且始终允许代码现实推翻过时记录。

入口文件长什么样: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 里最被低估的一节。

AI Native SDD 上下文工程 软件工程