GitHub Copilot 上下文工程:让 AI 编程更接近真实项目

前言

我用 Copilot 最深的一个感受是,同一个模型、同一个工具,在不同项目里的表现差异会非常大。

有时候它能很快补出符合项目风格的代码,函数命名、错误处理、测试写法都接近现有实现。有时候它又会写出一段看起来能跑,但和项目结构完全不搭的通用代码。这个差异一开始很容易被归因到模型不稳定,后来我发现,很多问题其实出在上下文。

Copilot 不是在真空里写代码。它会根据当前文件、打开的文件、工作区索引、你显式引用的文件、聊天历史、项目自定义指令、工具执行结果这些信息生成回答。上下文给得准确,它更容易沿着项目原有方式写;上下文混乱或者缺失,它就会回到通用模式。

我现在会把这件事称为上下文工程。它不是多写几句提示词,也不是把整个项目都丢给 AI,而是有意识地管理 Copilot 能看到什么、应该遵守什么规则、当前任务的边界在哪里、哪些文件才是判断依据。

会写 prompt 只是第一步。真正用好 Copilot,需要把上下文当成工程材料来整理。

一、先知道 Copilot 看到了什么

很多人刚开始用 Copilot 时,会默认它理解整个项目。这个假设很危险。Copilot 在不同模式下获取上下文的方式不完全一样。补全、Chat、Edits、Agent 模式都会使用上下文,但它们不会无条件理解整个仓库里每个文件。

在 VS Code 里,一次 Copilot 请求通常会组合多层信息。系统会带上基础规则,你的工作区可能带有自定义指令,当前聊天里有用户输入和历史对话,编辑器会提供当前文件、选中代码、打开文件、工作区索引、显式引用的文件或符号,工具调用也可能把搜索结果、终端输出、测试结果带回来。

这就解释了一个现象:你问 Copilot 一个业务问题,它不一定知道你指的是哪个模块;你只写一句帮我实现保存逻辑,它可能会自己猜数据库结构、错误处理和返回格式。不是它故意乱写,而是它没有拿到足够明确的项目约束。

我会把上下文分成三类。

上下文类型 来源 适合解决的问题
隐式上下文 当前文件、选中代码、打开的编辑器、工作区索引 补全当前函数、解释当前代码、做小范围修改
显式上下文 #file#folder#codebase、符号引用、拖拽文件 分析指定文件、跨模块改造、排查调用链
规则上下文 .github/copilot-instructions.md、prompt files、workspace settings 固定项目规范、测试要求、提交风格、架构边界

这三类上下文要配合使用。只靠隐式上下文,Copilot 容易猜;只靠显式引用,使用成本又太高;只写自定义规则,不提供当前任务文件,它仍然不知道要改哪里。比较好的方式是让项目长期规则沉淀在指令文件里,让当前任务通过显式引用补足文件和目标。

我现在提问前会先确认三件事:当前文件是不是任务入口,相关类型和测试有没有进入上下文,项目规则有没有被 Copilot 读取。这个检查比直接写长 prompt 更重要。

二、打开文件只是开始,显式引用更可靠

以前我经常靠打开文件来给 Copilot 补上下文。比如写 OrderService.ts 时,同时打开 Order.tsOrderRepository.tsOrderService.test.ts。这样 Copilot 在补全时有机会参考相邻文件里的类型、测试和调用方式。

这个习惯仍然有用,尤其对代码补全很有帮助。你在写一个新方法时,旁边打开的测试文件可能会影响它生成的函数签名和返回结构;打开同类 service 文件,它也更容易模仿项目已有写法。

但 Chat 场景里,我不会只依赖打开文件。更稳的方式是显式引用。

比如要让 Copilot 分析订单保存逻辑,我会这样写:

text 复制代码
请基于 #file:src/services/OrderService.ts 和 #file:src/repositories/OrderRepository.ts 分析订单保存流程。

重点看三件事:
1. 保存前做了哪些校验。
2. 数据库写入失败时怎么返回错误。
3. 有没有可能重复创建订单。

先分析,不要修改代码。

这里我没有让 Copilot 猜我要看哪些文件,而是直接把关键文件放进问题里。它回答时就会更接近当前项目,而不是给一段通用订单逻辑。

如果不确定文件在哪,可以用 #codebase 或工作区搜索能力先让它定位。

text 复制代码
#codebase 帮我找到项目里处理订单状态流转的入口文件。只列文件路径和主要职责,先不要写实现方案。

找到入口后,再把具体文件加入下一轮对话。这样比一上来就让它全项目分析更容易控制结果。

显式上下文还有一个好处:审查时更容易追溯。你知道它参考了哪些文件,也知道它没有参考哪些文件。AI 写错时,可以回头判断是规则没说清,还是关键文件没给到。

三、项目规则要写进仓库

临时 prompt 适合描述当前任务,项目长期规则不适合每次重复输入。比如 TypeScript 项目不能用 any,测试统一用 Vitest,API 返回结构必须包含 codemessagedata,错误处理统一走 AppError,这些规则每次都写一遍很浪费,也容易漏。

我会把这类规则放进 .github/copilot-instructions.md

markdown 复制代码
# Copilot Instructions

## 技术栈

- 使用 TypeScript 严格模式。
- 前端使用 React 函数组件。
- 测试使用 Vitest。
- API 请求统一走 `src/lib/apiClient.ts`。

## 代码约束

- 不要生成 `any`。
- 不要引入新的状态管理库。
- 不要绕过现有权限校验。
- 新增工具函数要放在 `src/utils` 或对应模块目录下。

## 错误处理

- 业务错误使用 `AppError`。
- API 返回结构必须包含 `code`、`message` 和 `data`。
- 数据库写入失败时不能直接返回原始异常。

## 验证方式

- 修改业务逻辑后补充或更新测试。
- 优先运行和当前模块相关的测试。

这个文件的作用不是让 Copilot 一次性变得完美,而是减少它偏离项目风格的概率。每次 Chat、Edits 或 Agent 任务都能参考这些规则,很多重复沟通会被省掉。

我会把自定义指令控制在清晰、稳定、长期有效的范围里。不要把临时需求写进去,比如今天要改登录页、这次要加导出按钮,这些应该放在当前 prompt 里。指令文件更适合写项目级约束。

一个好的 .github/copilot-instructions.md 通常包含这些内容。

内容 是否适合写进指令文件 原因
技术栈和框架版本 适合 长期稳定,影响生成代码
命名规范和目录结构 适合 能减少错误路径和风格漂移
测试框架和运行命令 适合 能让 Copilot 生成更可验证的代码
错误处理约定 适合 项目差异很大,必须明确
当前 sprint 的临时需求 不适合 容易过期
某个 bug 的临时排查结论 不适合 应该放在当前会话或 issue 里
特定文件的修改要求 不适合 应该在 prompt 里显式引用

如果项目比较大,我还会把更具体的任务规则拆成 prompt files 或 skills。简单规则放在自定义指令里,复杂任务用专门文件承接。比如代码审查、单元测试生成、数据库迁移检查,都可以各自有一份更详细的说明。

四、注释不是装饰,是局部上下文

很多时候 Copilot 生成代码不准,是因为当前文件里缺少意图说明。类型能告诉它数据长什么样,函数名能告诉它大概做什么,但业务规则和边界条件通常藏在人的脑子里。

这时候注释很有用。这里说的注释不是写一堆废话,而是在即将生成代码的位置,把函数目标、输入、输出和边界写清楚。

比如下面这个注释就比简单写一个函数名有效。

ts 复制代码
/**
 * 根据订单金额、优惠券和会员等级计算最终支付金额。
 *
 * 规则:
 * 1. 订单金额必须大于 0。
 * 2. 优惠券只能抵扣商品金额,不能抵扣运费。
 * 3. 会员折扣在优惠券之后计算。
 * 4. 返回值保留两位小数。
 * 5. 任何校验失败都返回 AppError,不直接抛原始异常。
 */
function calculatePayableAmount(input: CheckoutPriceInput): Result<number> {
  // ...
}

Copilot 看到这种注释时,生成结果会更接近业务规则。它不需要猜优惠券和会员折扣谁先执行,也不会随手把错误直接 throw 出去。

我会把注释分成三类使用。

第一类是规则注释,放在函数或复杂逻辑前面,说明业务处理顺序。

第二类是边界注释,放在容易出错的位置,说明为什么不能用更简单的写法。

第三类是迁移注释,放在临时兼容逻辑旁边,说明这个逻辑什么时候可以删除。

比如:

ts 复制代码
// 这里保留旧字段读取,是为了兼容 1.4.0 以前导出的 JSON。
// 新版本保存时只写 `displayName`,不再写 `name`。
const displayName = raw.displayName ?? raw.name ?? '';

这种注释对人有用,对 Copilot 也有用。下次让它改导入逻辑时,它能知道这段兼容代码不能随手删。

不要把注释写成命令清单,也不要写成过度抽象的设计口号。注释越贴近当前业务,越能帮 Copilot 生成符合项目预期的代码。

五、Chat 里要把任务边界说清楚

Copilot Chat 最容易出问题的地方,是用户只写目标,不写边界。

比如:

text 复制代码
帮我重构这个模块。

这个提示词太宽。Copilot 可能会改目录、抽函数、换依赖、补测试,也可能顺手调整一堆无关代码。最后你看 diff 时,会发现自己不知道哪些改动应该保留。

我更愿意这样写:

text 复制代码
请基于 #file:src/auth/tokenService.ts 重构 token refresh 逻辑。

目标:
- 把重复的过期时间判断抽成独立函数。
- 保留现有接口返回结构。
- 不修改数据库字段。
- 不引入新依赖。

验证:
- 改完后说明需要运行哪些测试。
- 先输出修改计划,不要直接改文件。

这个提示词没有追求复杂,但它给了 Copilot 需要的边界。目标是什么,不能动什么,验证方式是什么,先做计划还是直接修改,都说清楚了。

我会把 Chat 提问固定成一个结构。

text 复制代码
上下文:
我正在处理哪个模块,相关文件是什么。

目标:
这次要完成什么。

约束:
哪些内容不能修改,哪些风格必须保留。

输出:
先分析、先计划、直接给代码、还是生成测试。

验证:
需要运行哪些测试,或者如何确认结果。

这个结构适合绝大多数工程任务。写熟以后,提问反而会更快,因为不需要每次临时组织语言。

如果任务很复杂,我会先让 Copilot 做 plan,再让它改代码。先计划的好处很直接:方向不对时,可以在最小成本下纠正。

text 复制代码
请先给出三步以内的修改计划。确认计划前不要改文件。

这句话能避免 Copilot 一上来就进入执行。尤其是多文件修改,先计划再执行更稳。

六、Copilot Edits 要小步使用

Copilot Edits 很适合处理多文件修改。比如把一个工具函数抽出去,给多个调用点补错误处理,把组件文案迁移到 i18n,把几个相似测试统一成同一种写法。

但 Edits 也容易让 diff 变大。你给它一个很宽的目标,它可能一次修改十几个文件。看起来效率很高,实际上审查成本也会马上上来。

我会给 Edits 设置一个很明确的范围。

text 复制代码
把当前组件里的日期格式化逻辑抽到 `src/utils/date.ts`。

只修改:
- 当前组件
- 新增的 date 工具文件
- 当前组件对应的测试

不要修改样式,不要调整组件结构。

这种写法能让它集中在一个小任务里。改完以后先看 diff,再决定是否继续下一步。

我一般会把一个大改动拆成几轮 Edits。

大任务 拆分后的 Edits 任务
重构登录模块 先抽 token 工具函数,再改调用点,再补测试
国际化页面文案 先迁移一个组件,再迁移同类组件,最后整理缺失 key
优化列表性能 先减少重复请求,再拆分页状态,再补 loading 和空状态
调整错误处理 先统一 service 返回,再改 UI 展示,再补异常测试

这种拆法和 PR 拆分很像。AI 可以帮助改代码,但人仍然要控制变更颗粒度。越是大项目,越要避免一次性让 Edits 改太多文件。

Edits 生成结果后,我会重点看三类问题。

第一,是否改了任务范围外的文件。

第二,是否引入了新的依赖或新的架构选择。

第三,是否把异常路径写得过于理想化。

AI 生成的正常路径通常还可以,边界条件更需要人工检查。

七、上下文工程要覆盖测试和验证

很多人给 Copilot 上下文时,只给实现文件,不给测试文件。这样生成出来的代码可能能跑,但不一定符合项目验证方式。

如果我正在改业务逻辑,会主动把测试文件放进上下文。比如:

text 复制代码
请基于 #file:src/services/orderService.ts 和 #file:src/services/orderService.test.ts 修改订单取消逻辑。

目标:
- 已支付订单不能直接取消。
- 未支付订单取消后释放库存。
- 保持现有测试风格。

先说明需要新增哪些测试用例。

这里测试文件的作用很大。它告诉 Copilot 当前项目怎么 mock、怎么断言、怎么命名 case。相比让它自己发明测试结构,参考现有测试更稳。

我还会让 Copilot 先生成测试清单,而不是直接写代码。

text 复制代码
先列出这个改动需要覆盖的测试场景,不要写实现。

这样可以先检查它有没有理解业务边界。测试场景都不对,代码大概率也会偏。

上下文工程不应该只围绕生成代码,也要围绕验证代码。一个好的 prompt 应该包含验证方式,比如运行哪条测试命令、检查哪个页面、看哪个接口返回。否则 Copilot 很容易停在代码能写出来这一层。

八、团队里要把上下文沉淀成规范

个人使用 Copilot 时,很多上下文可以放在脑子里。团队使用时就不能这样。每个人给 Copilot 的规则不一样,生成出来的代码风格也会飘。

我会建议团队至少沉淀三类文件。

第一类是仓库级 Copilot 指令。放在 .github/copilot-instructions.md,写技术栈、目录结构、测试要求、错误处理和代码风格。

第二类是任务级 prompt files。比如代码审查、生成测试、排查构建失败、写发布说明,都可以各有一份固定提示。这样团队成员不需要每次重新写。

第三类是专项 skills 或 agent 说明。比如安全审查、数据库迁移检查、前端组件规范、接口兼容性检查,这些任务可以比普通自定义指令更详细。

团队上下文文件不要写成很长的制度文档。它们要让 Copilot 能用,也要让开发者愿意维护。太长、太抽象、太过期的规则,会比没有规则更麻烦。

我会定一个简单的维护原则:每次发现 Copilot 重复犯同一种错误,就考虑把规则写进上下文文件。比如它总是引入 any,就把 TypeScript 约束写进去;它总是忘记补测试,就把测试要求写进去;它总是改错目录,就把目录规则写进去。

上下文工程最后会变成团队工程的一部分。它不是某个人写 prompt 的技巧,而是项目如何向 AI 表达自己的结构和规则。

总结

GitHub Copilot 的使用效果,很大程度上取决于上下文质量。模型能力当然重要,但在真实项目里,Copilot 更常见的问题是缺少项目规则、缺少关键文件、缺少测试约束、缺少明确的任务边界。

我现在会按这几个层次整理上下文:

  • 当前任务用显式引用锁定文件和目录。
  • 项目长期规则放进 .github/copilot-instructions.md
  • 复杂任务先让 Copilot 输出计划。
  • 多文件修改用 Edits 小步推进。
  • 测试文件和验证命令一起进入上下文。
  • 团队高频任务沉淀成 prompt files、skills 或 agents。

把这些习惯建立起来以后,Copilot 的表现会更接近真实项目需要。它生成的代码不会突然变得百分百正确,但会少很多通用代码,少很多风格漂移,也少很多不该出现的改动。

上下文工程的目标不是让 AI 替你理解项目,而是让项目用更清楚的方式告诉 AI:这里怎么写代码,哪里不能乱动,改完以后怎么验证。这个能力会越来越重要。

相关推荐
KIO no way1 小时前
AI内容编排是什么_聊聊CSDN_AI数字营销背后的分发逻辑
android·人工智能
环球科讯1 小时前
广东省茂名市:普惠金融畅流通,建行助力商贸兴
大数据·人工智能
一切皆是因缘际会1 小时前
神经符号融合智能体
大数据·数据结构·人工智能·ai
武子康1 小时前
调查研究-173 MOSS-TTS 调查:开源 TTS 正在从“朗读器“走向声音生成系统
人工智能·ai·chatgpt·claude·tts·minimax
2401_840759761 小时前
2026年前端框架生态与AI开发新趋势
前端·人工智能·科技
诺***帝1 小时前
GPT-Image-2构图逻辑解析:2026年五层提示词公式实测
人工智能·gpt
qdprobot1 小时前
AIcam智能ESP32视觉摄像头体识万物
人工智能·esp32s3·图形化编程·mcp·mixly小智ai
林三的日常1 小时前
一周AI核弹级热点
人工智能·搜索引擎