上下文工程是使用 AI 编程智能体时最重要的能力。本指南介绍如何组织项目上下文、CLAUDE.md 文件和任务描述,让 Claude Code 尽可能一次就写出高质量代码。
作者:Claude Directory
发布于:2026-04-02
原文:https://www.claudedirectory.org/blog/context-engineering-claude-code
有些开发者使用 Claude Code 时,只能得到平庸的结果;另一些人却能让它一次就写出足以投入生产的代码。两者之间的差距,几乎总能归结为同一件事:上下文工程。
所谓上下文工程,就是有意识地组织并提供信息,让 AI 能够发挥出最佳水平。它并不等同于提示词工程,而是比后者深入得多。提示词工程关注的是如何措辞一条具体请求;上下文工程关注的则是完整的信息环境,包括项目文件、CLAUDE.md、目录结构、提交历史,以及你在一段时间内组织和描述任务的方式。
本指南将介绍一套经过实践检验、能够持续改善 Claude Code 输出质量的方法。
为什么上下文工程比提示词工程更重要
当你让 Claude Code“添加支付流程”时,最终结果的质量取决于 Claude 能否在内部回答下面这些问题:
- 这个项目使用什么框架?
- 现有代码遵循哪些模式?
- API 路由放在哪里?组件又放在哪里?
- 项目如何处理错误、身份验证和状态?
- 测试遵循什么约定?
- 以前尝试过哪些方案,又否决过哪些方案?
如果 Claude 只能猜测这些问题的答案,你得到的往往是千篇一律的通用代码。如果答案已经包含在项目上下文中,它写出的代码就会像是出自团队成员之手,自然地融入现有项目。
提示词工程是在问一个更好的问题;上下文工程则是让 Claude 掌握回答好这个问题所需的知识。
第一层:以 CLAUDE.md 为基础
在所有能提供给 Claude 的上下文中,CLAUDE.md 是投入产出比最高的一项。每次会话开始时,它都会被自动加载,并影响 Claude 接下来所做的一切。
大多数 CLAUDE.md 的问题出在哪里
许多开发者会把 CLAUDE.md 写成 README,也就是对项目做一份静态介绍。这当然有用,却没有抓住重点。Claude 读过几个文件后,就能大致判断项目是做什么的。它真正需要的,是那些无法仅靠阅读代码推断出来的信息:
- 不仅告诉它做了什么决策,还要说明为什么这样决策
- 代码本身无法体现的约束条件
- 应该遵循的模式,以及需要避开的模式
- 构建、测试、代码检查和部署所需的命令
一份高信息密度的 CLAUDE.md 模板
# CLAUDE.md
## 命令
`npm run dev` — 在 3000 端口启动开发服务器
`npm run test` — 运行 Jest 测试(修改代码后执行)
`npm run test:e2e` — 运行 Playwright 端到端测试
`npm run lint` — 执行 ESLint 和 Prettier 检查
`npm run typecheck` — 执行 TypeScript 严格模式检查
## 架构决策
- **状态管理**:使用 Zustand,不要使用 Redux。我们已在第一季度完成迁移,禁止重新引入 Redux。
- **API 层**:内部 API 使用 tRPC,对外集成使用 REST。
- **数据库**:PostgreSQL + Drizzle ORM,迁移文件位于 /drizzle/。
- **身份验证**:使用 NextAuth v5 的凭据提供程序,会话策略为 JWT。
## 代码约定
- 除非组件需要交互,否则所有新组件都必须采用 React Server Components。
- 条件 className 使用 lib/utils 中的 `cn()`,禁止使用三元表达式拼接字符串。
- 错误边界应包裹每个路由段,而不是单个组件。
- API 错误响应采用以下结构:`{ error: string, code: string, details?: unknown }`
## 禁止事项
- 禁止使用 `any` 类型。使用 `unknown`,然后进行类型收窄。
- 禁止添加桶式导出(通过 index.ts 重新导出)。直接从源文件导入。
- 除页面和布局外,禁止使用默认导出。
- 未经用户确认,不得安装新依赖。
## 测试
- 单元测试与被测文件放在一起:`foo.ts` → `foo.test.ts`
- 模块模拟使用 `vi.mock()`,禁止使用 jest.mock(项目使用 Vitest)
- 测试行为,不测试实现细节。测试 API 路由时,优先编写集成测试而不是单元测试。
注意这份模板的规律:每个部分都在回答一个 Claude 原本只能靠猜测的问题。其中,“禁止事项”尤其有价值——它可以阻止 Claude 套用那些虽然常见、却并不适合当前项目的做法。
CLAUDE.md 的层级:用户、项目与目录
Claude Code 会按照层级加载多份 CLAUDE.md 文件:
~/.claude/CLAUDE.md:个人偏好,适用于所有项目./CLAUDE.md:项目根目录规则,适用于当前仓库./src/CLAUDE.md:目录专属规则,在该目录中工作时生效
你可以有策略地利用这套层级:
- 个人 CLAUDE.md:个人代码风格、偏好的库、回答格式偏好
- 项目 CLAUDE.md:架构、约定、命令和约束条件
- 目录 CLAUDE.md:特定模块的模式和局部约定
# src/components/CLAUDE.md
## 组件模式
- 每个组件使用独立目录:ComponentName/index.tsx + ComponentName.test.tsx
- Props 接口定义在同一个文件中,不从外部导入
- 所有会渲染 DOM 元素的组件都使用 forwardRef
- Storybook stories 不是硬性要求,但复杂组件建议添加
这样一来,Claude 在组件目录和 API 目录中工作时,就能分别遵守不同的约定。上下文会随着当前代码位置自动调整。
第二层:让项目结构本身提供上下文
文件组织方式也是一种隐含的上下文。Claude 会通过目录树判断各类代码应该放在哪里。结构清晰的项目能显著提升 Claude 的表现,因为目录结构本身已经替它回答了一部分问题。
有助于 Claude 理解项目的结构模式
src/
app/ # Next.js 路由——Claude 知道页面应该放在这里
api/ # API 路由——Claude 会遵循 REST 约定
(auth)/ # 路由组——Claude 能理解这种分组方式
components/
ui/ # 基础组件(Button、Input、Card)
features/ # 面向具体功能的组合组件
layouts/ # 布局组件
lib/
db/ # 数据库客户端、Schema 和迁移
auth/ # 身份验证配置
utils/ # 共享工具函数
hooks/ # 自定义 React Hooks
types/ # 共享 TypeScript 类型
项目遵循常见结构时,Claude 需要的额外说明会更少。它可以自行推断:新的 API 路由应该放进 src/app/api/,新的 Hook 应该放进 src/hooks/,新的工具函数则应该放进 src/lib/utils/。
会让 Claude 困惑的反模式
- 一个扁平目录中堆放数百个文件:Claude 无法推断它们的分组关系
- 命名方式前后不一致:文件名同时混用 camelCase 和 kebab-case,会向 Claude 传达规则混乱的信号
- 遗留死代码和未使用文件:Claude 可能引用甚至扩展已经废弃的模式
- 多套模式彼此竞争:同一件事存在两种不同写法,会让 Claude 在它们之间反复摇摆
如果项目确实存在结构问题,可以在 CLAUDE.md 中用一句话覆盖旧模式:“新组件统一放在 src/components/features/ 中,忽略 src/old-components/ 中的遗留模式。”
第三层:正确描述任务
你如何描述任务,将直接决定 Claude 如何着手解决它。优秀的任务描述通常具有三个共同点:说明目标、列出约束条件,并明确什么才算完成。
“目标—约束—完成标准”框架
较弱的提示词:
添加邮件通知
更强的提示词:
为订单状态变更添加邮件通知。
目标:当订单状态变为“已发货”或“已送达”时,向客户发送邮件。
约束条件:
- 使用 package.json 中已经安装的 Resend,以及 src/lib/email/ 中的现有邮件模板
- 通过现有的 Bull 任务队列发送邮件,不要在请求流程中直接发送
- 只有在用户偏好设置中启用了邮件通知时才发送
以下条件全部满足才算完成:
- 订单状态变为已发货或已送达时,会创建一项邮件任务
- 邮件使用现有的品牌模板
- 测试覆盖正常流程和“用户已关闭通知”两种情况
- 其他状态变更不会触发邮件
写出后一种提示词只多花 30 秒,却能省下 15 分钟的来回沟通和反复修改。
引用现有实现,不要重复描述
与其在提示词里从头解释某种模式,不如直接让 Claude 参考现有代码:
为订单添加一个 PATCH 端点,沿用
src/app/api/users/[id]/route.ts 的实现模式——使用相同的校验方式、
错误处理方式和响应格式。
Claude 会读取你指定的文件,并准确复用其中的模式。这通常比用自然语言重新描述模式更可靠。
用渐进式披露处理复杂任务
面对大型功能时,不要把全部要求一次性塞进一个提示词。应该分层补充上下文:
提示词 1:“我们要开发一项实时协作功能。
先不要写代码,请阅读 src/lib/ws/ 中现有的 WebSocket 实现,
以及 src/lib/db/schema.ts 中的文档模型。
然后告诉我你准备采用什么方案。”
提示词 2:“方案不错。先实现服务端事件处理——
包括文档内容变更和光标位置,暂时不用考虑 UI。”
提示词 3:“现在添加实时协作 UI 所需的 React Hooks 和组件,
使用上一步实现的光标数据。”
每一轮提示词都会逐步扩充 Claude 的上下文。等到开始实现 UI 时,它已经深入理解了自己刚刚构建的数据层。
第四层:记忆与连续性
Claude Code 的记忆系统可以跨会话保留上下文。使用得当,你就不必在每次开始新对话时,反复解释相同的信息。
哪些内容适合写入记忆
记忆适合保存那些跨会话长期有效,同时又无法从代码中推断出来的信息:
- 你的角色和技术水平,例如“我是资深后端工程师,但刚开始接触这个前端代码库”
- Claude 曾经理解错误、经你纠正的偏好,例如“始终使用命名导出,不要使用默认导出”
- 代码中没有体现的项目背景,例如“我们正在从 REST 迁移到 tRPC,因此新的端点应该使用 tRPC”
- 外部系统信息,例如“缺陷跟踪位于 Linear 的 PLATFORM 项目中”
哪些内容不应该写入记忆
- 代码模式,因为代码本身已经记录了它们
- 文件路径,因为路径会发生变化
- 当前任务状态,应该使用任务功能来管理
- 已经写在
CLAUDE.md里的任何内容
可以这样理解:记忆应该保存一位新团队成员必须知道、但目前没有记录在任何地方的信息。
第五层:把 Git 历史变成上下文
提交历史也是 Claude 能够读取的上下文。清晰、规范的提交记录,可以帮助 Claude 更准确地理解项目是如何演进的。
Claude 如何使用 Git 信息
探索代码库时,Claude 可以读取:
- 最近的提交消息,用来了解改了什么、为什么要改
- Diff 历史,用来观察代码如何演进
- 分支名称,用来判断你当前正在做什么
这意味着,提交消息实际上就是 Claude 用来理解项目的“训练资料”:
# 不好的写法——Claude 什么也学不到
git commit -m "fix stuff"
git commit -m "updates"
# 好的写法——Claude 能理解修改意图和背景
git commit -m "fix: prevent duplicate emails when order status webhook fires twice"
git commit -m "feat: add rate limiting to public API endpoints (100 req/min per key)"
用分支名称补充上下文
在名称清晰的功能分支上工作,可以让 Claude 立刻理解当前重点:
git checkout -b feat/real-time-collaboration
# Claude 现在知道你正在开发实时协作功能
# 因而能够提出与此相关的建议
第六层:通过 MCP 服务器获取运行时上下文
MCP(Model Context Protocol,模型上下文协议)服务器让 Claude 能够访问外部系统中的实时数据。这是基础设施层面的上下文工程。
| MCP 服务器 | 提供的上下文 |
|---|---|
| 数据库 MCP | 真实的 Schema、示例数据和数据关系 |
| Figma MCP | 实际设计规范,而不是口头描述 |
| GitHub MCP | Issue、PR 和代码审查评论 |
| Sentry MCP | 真实错误轨迹、堆栈信息和出现频率 |
| 浏览器 MCP | 页面实际呈现的效果和控制台错误 |
每接入一类服务器,就能消除一类猜测。没有数据库 MCP 时,你只能用语言描述 Schema;接入之后,Claude 可以直接读取真实的 Schema,并写出能够在实际数据上运行的查询。
{
"mcpServers": {
"postgres": {
"command": "npx",
"args": ["-y", "@modelcontextprotocol/server-postgres"],
"env": {
"DATABASE_URL": "postgresql://localhost:5432/mydb"
}
}
}
}
汇总:上下文工程检查清单
每次开始一项重要的编码工作前,都可以快速过一遍下面这份清单。
项目基础
- 已创建
CLAUDE.md,其中包含命令、架构决策和代码约定 - 目录结构遵循一致且常见的模式
- 已删除遗留死代码和废弃模式,或者对它们进行了说明
- Git 历史中的提交消息足够清晰具体
会话准备
- 已为外部数据源配置相关的 MCP 服务器
- 记忆中包含长期有效的个人偏好和项目背景
- 当前分支名称能够说明正在进行的工作
任务描述
- 目标表达清楚,说明“做什么”而不是“怎么做”
- 约束条件明确,包括库、模式和边界
- 完成标准具体,覆盖测试、行为和范围
- 已指出可供模仿现有模式的参考文件
迭代过程
- 复杂任务已拆成渐进式提示词
- 纠正 Claude 时说明了“为什么”,让它理解背后的原则
- Claude 给出好结果后进行了确认,让它知道哪些做法有效
复利效应
上下文工程并不是一次性的配置工作,它带来的收益会随着时间不断累积:
- 第一周:写一份基础的
CLAUDE.md,并开始更有条理地描述任务。Claude 的输出会出现明显改善。 - 第一个月:根据真实会话持续完善
CLAUDE.md,用记忆记录你的偏好。Claude 开始像真正了解这个项目一样工作。 - 第三个月:用目录级
CLAUDE.md指导它在代码库不同区域中的行为,同时用 MCP 服务器提供实时数据。大多数时候,Claude 第一次交付的代码就能通过你的审查。
那些最能发挥 Claude Code 价值的开发者,未必写出了更高明的提示词;他们真正做对的,是构建了更好的上下文环境。这项投入主要发生在前期,但回报会越来越快地增长。
不妨先从 CLAUDE.md 开始。在本指南介绍的所有方法中,仅这一份文件,就可能对你的 Claude Code 使用体验产生最大的改变。