0%

Context 构建——给 AI 的代码探索加一个方向清单

上一篇文章讨论了知识沉淀的第一步——用 CONTEXT.md 统一术语,用 ADR 记录不可逆决策。这两样东西构成了 AI 在项目中的知识底座

文章里提到一个关键动作:grilling。你运行 /grill-with-docs,AI 一次一个问题地追问你,同时在代码里搜索它能找到的事实。对话结束,术语更新了,决策记录了,共享理解也形成了

当时我写了一句话:「一次 grilling 跑完,AI 也已经在代码中走了一遍它能找到的相关路径——这个过程本身就是 Context 构建」

这句话对了一半。AI 确实走了一遍——但它走全了吗?


会查 ≠ 查全了

grilling 的提示词里那条规则写得很清楚:

If a fact can be found by exploring the environment, look it up rather than asking me.

翻译过来:事实如果能从代码里找到,别来问我,直接去查

这个设计方向是对的——人能少回答一个问题是一个。但它有一个隐含的前提:Agent 知道该查什么。 而实际上,Agent 查什么、查到什么程度、在哪停下来,取决于它自己的判断。判断不准的时候,有些东西就漏了

更具体地说,Agent 的搜索有三个常见盲区:

盲区一:信息是扁平的。 Agent 搜到了五个文件和三个方法,但它不会把这些信息按影响面分层——哪个是核心路径必须改,哪个是边缘分支碰了会出事,哪个只是碰巧名字里有同一个关键词。一份扁平的信息列表不是 Context——Context 需要层次

盲区二:信息是静态的。 Agent 读到了当前代码的样子,但它不会主动去查这段代码的修改历史——上次是谁改的、为什么改、有没有关联的事故修复。而这些历史信息往往比当前代码本身更能告诉你「什么地方不能乱碰」

盲区三:Agent 不知道自己漏了什么。 它搜到的东西就是它的全部视野。它不会标注「这个模块可能还有一个 MQ 消费者在其他仓库里,我搜不到」——因为搜索工具返回的结果里没有这一条

这三个盲区不是 Agent 能力的问题——是引导的问题。提示词说了「去查」,但没说「按什么方向查、查到什么程度、怎么标注不知道的部分」

这就是这篇文章要解决的问题


五个方向:给探索加一张地图

拿一个具体需求为例。假设你接到一个任务:VIP 用户退款时免扣运费

当前系统里,所有用户退款都会以原支付渠道退回款项,同时从退款金额里扣一笔运费。现在要加一个逻辑:如果用户是 VIP,不扣这笔运费

就是这个需求。grilling 阶段,AI 会问你在「为什么」层面的问题——VIP 的判断标准是什么?免运费是跳过扣除还是事后返还?是全部免还是只免一部分?这些是决策型问题,必须人回答

但代码层面,有一套信息必须被搞清楚——而这些信息,Agent 如果没人引导,不一定全都能查到。下面用五个方向来组织这些信息


方向一:入口——这个功能有几个入口?

一个看似简单的功能,往往不只有一个调用入口

退款流程的入口至少有三个:

  • Web 接口:用户在订单页面点「申请退款」,走 RefundController.apply()
  • 后台接口:客服在管理后台帮用户操作退款,走 AdminRefundController.batchRefund()
  • MQ 回调:支付渠道的退款结果回调,走 RefundCallbackListener.onMessage()

Agent 搜索 refund() 方法的引用可以找到前两个。但 MQ 回调不会直接调用 refund()——它走的是消息监听器,入口在配置文件或注解里。如果 Agent 只按方法引用搜,第三个入口会漏

入口没找全的后果是:你改了 Web 接口和后台接口的退款逻辑,加了 VIP 判断,但 MQ 回调那条路径还是老逻辑——VIP 走回调退款的时候照常扣了运费。测试环境不一定会测到这条路径(回调依赖支付渠道的异步通知),于是上了线才发现

怎么查:不只是搜方法引用,还要搜事件绑定——注解、配置文件、消息队列的 topic 声明。每个入口都要确认一遍


方向二:路径——从入口往下,经过哪些关键节点?

找到所有入口之后,顺着每个入口往下追

退款流程从入口往下走,大致经过这些节点:

1
2
3
4
5
6
7
8
入口 → RefundService.refund()
→ validateOrder() // 校验订单状态
→ PaymentUtil.hasRefunded() // 幂等检查
→ RefundProcessor.process()
→ PaymentGateway.refund() // 调支付渠道
→ ShippingFeeService.calcAndDeduct() // 计算并扣除运费 ← 要改的地方
→ PointsService.deduct() // 积分扣回
→ NotificationGateway.send() // 通知用户

有两个节点值得单独拉出来看:

幂等检查节点hasRefunded()calcAndDeduct() 之前执行。如果 VIP 免运费逻辑错误地跳过了这个检查——比如有人在判断 VIP 之后直接 return——那整条链路后面全部不走,退款本身也不会执行。不是改运费的问题,是改出了更大的问题。ADR 里应该有这条幂等检查的记录——如果没有,grilling 阶段就该补一篇

运费计算节点ShippingFeeService.calcAndDeduct() 这个方法的签名长什么样?它接受什么参数?返回值是什么?VIP 的「跳过」应该在哪个位置插入——是在 calcAndDeduct() 里判断 VIP 然后跳过,还是在 refund() 主流程里判断 VIP 然后完全不调这个方法?

Agent 顺着调用链往下读,这些节点会被自然发现。但光读代码不够——还需要判断每个节点的风险等级。引用路径上的核心节点(如幂等检查)是高风险的,不能绕过。路径末端的工具方法(如日志输出、格式化转换)是低风险的,改了也没事


方向三:影响面——改了这里,谁会受影响?

这是五个方向里最容易漏的一步,也是最难的一步。影响面分两类

引用影响——通过代码引用可以直接找到的。改了 ShippingFeeService

  • RefundEventConsumer 监听了 refund.completed 事件,读取运费字段来做对账。改了运费的扣除逻辑,对账逻辑也要跟着改,否则对账会不平
  • RetryCompensationJob 是定时补偿任务,处理那些 MQ 消息投递失败的退款。它也调用了运费计算
  • 运营报表里的 SQL 引用了运费字段——「近 30 天退款运费汇总」。改了扣除规则(VIP 的不扣),这个报表的口径也要说明

这些是 Agent 通过引用搜索能直接找到的——搜索 ShippingFeeServiceshippingFee 的引用,一条一条过

假设影响——没有显式引用,但依赖了当前行为

这个更隐蔽。比如支付模块里有一个 RefundAmountValidator

1
2
3
if (refundAmount + shippingFee != order.getTotalPaid()) {
throw new ValidationException("退款金额校验失败");
}

这行代码不引用 ShippingFeeService。它只是用一个算术公式做了一个隐式假设:退款金额 + 运费 = 订单实付总额。 如果 VIP 免运费——退款金额不变但运费为 0——这个公式就不成立了。校验会报错,VIP 退款全部被拦截

Agent 搜 shippingFee 的引用找不到这个文件——这里用的只是一个局部变量,名字可能叫 feechargededuction,甚至直接内联在表达式里。怎么查?需要搜使用模式而不是引用——搜索运费字段可能的名字变体、搜索涉及金额计算的校验逻辑、搜索 getTotalPaid() 的调用方看它们假设了什么

引用影响靠搜索引用找,假设影响靠搜索使用模式找。两样都要查。查完之后列一个影响面清单,每个标注是「必须同步改」「确认不需要改」「不确定,需要验证」


方向四:历史——这段代码被改过吗?

代码是时间堆积出来的。一段逻辑今天长这样,可能是经历了三次迭代、一次回滚、一次事故修复之后的结果。只读当前代码等于只读了最后一页

Agent 可以查几样东西:

git logShippingFeeService 最近半年被谁改过?有没有关联的 issue 编号?如果半年前有一次提交的信息是「修复退款时运费重复扣除的问题」,那这次改动就得特别小心——上次已经踩过坑了

ADR。运费计算相关的决策有没有记录?比如为什么选了「先扣后退」而不是「事后结算」?这个决策如果还在生效,VIP 免运费的处理方式就得和它保持一致

关联的 issue。git log 里引用的 issue 编号点进去看——当初是什么场景触发了改动?是业务规则变了,还是线上出了问题?

查这些不需要人参与——Agent 自己就能跑 git log、读 ADR、拉 issue。但需要人去引导它去做。grilling 没说「查一下历史」,Agent 可能就不会主动查。把「历史」作为一个显式的探索方向列出来,就是为了让这件事不会被跳过去


方向五:模式——有没有类似的改法可以参考?

大部分业务系统的改动不是首创——同一类需求在不同模块里可能有类似的实现

查一下系统里有没有类似的功能:

  • 「优惠券免运费」——用优惠券的时候也免运费,逻辑可能和 VIP 免运费高度相似。它怎么实现的?在哪一层做的判断?有什么边界条件?
  • 「满减活动免运费」——满减也免运费,它是走 ShippingFeeService 还是绕过了它?

Agent 搜索「免运费」「free shipping」或者 ShippingFee 的绕过方式,能找到这些现有实现。它们不仅是参考——它们是系统里已经验证过的正确做法。抄一个已经跑通、已经测过的模式,比从零设计一个新方案风险低得多

如果找不到类似模式怎么办?那本身就是一条重要信息——这个改法是首创,意味着你需要比平时更仔细地评估影响面,因为没有人走过这条路


AI 天然擅长的那件事

五个方向都需要人引导 Agent 去查。但有一个方向不需要——基础知识

AI 在训练过程中已经学会了大量通用的工程知识:@Transactional 在同类方法自调用时不生效、MQ 的 at-least-once 语义要求消费端幂等、锁的获取和释放应该在同一层级

探索代码的时候,AI 看到 ShippingFeeService.calcAndDeduct() 上有 @Transactional,而调用它的 refund() 方法上也有 @Transactional——它知道这两个事务会合并,知道传播行为可能带来什么后果。它不需要你告诉它「注意事务传播」,它已经知道了

它缺的不是知识,是把通用知识绑定到具体代码上的能力。而五个方向的代码探索,恰好提供了这个绑定的机会——AI 在查入口、查路径、查影响面的过程中,不断把代码和基础知识做匹配。@Transactional 自调用是基础知识,但只有读到调用链的具体代码,才能发现「这个项目的 refund() 方法确实在同类里自调用了 calcAndDeduct()

这件事 AI 能做,人做不了——人不可能在几十个方法上同时做这类检查,但 AI 可以。在 Context 构建中,基础知识的检查不需要一个新的步骤。它搭在五个方向的探索过程中自动完成


五个方向之外的补强

CONTEXT.md 和 ADR 覆盖了术语和决策。五个方向覆盖了单次任务的工程 Context。但还有几样知识,介于两者之间——不属于术语,也不是决策,但对 AI 的探索效率影响很大。不用一开始就建,遇到了再补

SYSTEM.md——五秒钟看懂全局结构。 CONTEXT.md 说术语,SYSTEM.md 说结构。一个项目有哪些模块、各管什么、怎么通信——写几行就行。Agent 打开仓库之后不需要再花时间从目录结构猜出模块边界

1
2
3
4
5
6
7
8
9
10
11
## 模块

- ordering/ — 订单生命周期
- billing/ — 计费和发票
- notification/ — 消息推送
- points/ — 积分管理

## 通信方式

ordering → billing:同步 RPC
ordering → notification:异步 MQ(at-least-once)

pitfalls.md——敏感代码标签。 ADR 记录的是设计方案,但有些知识不是设计——是「这个地方改过,炸过」。一段话就能防止一个人或 AI 踩同一个坑:

1
2
3
4
## RefundService.idempotencyCheck()

2024 年线上重复扣款事故后加的。删除或绕过会导致重复退款。
上次有人想「优化」它,被 code review 拦了,原因见 #234

postmortem——出过什么事。 pitfalls 是标签,postmortem 是来由。什么时候发生、根因是什么、修了哪里、以后要注意什么。三行就够

这三样不是必选项。CONTEXT.md + ADR 是基础设施,五个方向是每次需求的探索方法——先让这两层跑顺。等哪一天觉得「AI 老找错入口」,再写 SYSTEM.md。等哪一天有个代码段让你犹豫「能不能改」,再补一笔 pitfalls。知识沉淀不需要一步到位


走完完整流程

回到「VIP 免运费」这个需求。用这套东西,完整的开发前流程长这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
接到需求


① grill-with-docs(提取人的知识)
AI 追问我:VIP 的判断标准?免运费是跳过还是事后返还?
我回答,术语进 CONTEXT.md,不可逆决策进 ADR
AI 也在搜代码——但搜到什么程度,看它的判断


② 结构化探索(补充代码里的知识)
AI 按五个方向系统查:
· 入口:Web + 后台 + MQ 回调,三个入口
· 路径:经过幂等检查 → 运费计算 → 积分扣回 → 通知
· 影响面:RefundEventConsumer 对账 + 定时补偿 + 报表 SQL
+ RefundAmountValidator 的隐式假设
· 历史:半年前改过运费计算,关联 #234
· 模式:优惠券免运费逻辑可以抄
盲区:跨仓库依赖未探索


③ to-spec(合成可执行的 spec)
AI 综合 grilling 的理解 + 结构化探索的 Context
+ CONTEXT.md + ADR,生成一份精确的 spec
发布到 issue tracker,标注 ready-for-agent


to-tickets → implement → code-review

三步的分工很清晰:

步骤 做什么 谁主导
grilling 提取人的隐性知识——术语、决策、偏好 AI 问,人回答
结构化探索 补全代码中的事实——入口、路径、影响面、历史、模式 AI 查,人确认
to-spec 把前两步的产出合成为可执行的 spec AI 写,人签字

grilling 吃掉的是人脑子里的东西,结构化探索吃掉的是代码里藏的东西。两样都喂给 to-spec,它产出的 spec 才不会漏


落地:一套提示词模板

如果你不想每次都在脑子里过五个方向,可以直接用这套提示词。在 grilling 结束后,或者在 grilling 过程中感到「AI 好像没查全」的时候,把这段话丢给它:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
你现在帮我构建这个需求的工程上下文。先读 CONTEXT.md 了解项目术语,检查 docs/adr/ 确认相关历史决策。

然后逐项回答以下问题。每项的答案基于代码搜索——能查到的事实直接汇报,不要问我。只有在代码里找不到答案时,才向我确认。

## 入口
搜索这个功能的所有入口——不只是方法引用,还包括注解、配置文件、
MQ 绑定里声明的入口。有没有 Web 接口之外的入口(后台接口、
定时任务、消息回调)?

## 路径
从每个入口顺调用链往下走,标注关键节点。哪些节点是核心路径不能
绕过的(如幂等检查),哪些是末端工具方法可以灵活处理的?

## 影响面
改了关键节点之后,谁会受影响?
- 引用影响:搜索所有引用方——MQ 消费者、定时任务、Event 监听者、
报表 SQL、跨服务调用
- 假设影响:搜索字段的使用模式——不引用你但依赖了你当前行为的代码

## 历史
用 git log 查这块代码的修改历史。有没有关联的 issue 编号?
有没有事故修复或回滚?检查 ADR 里有没有相关的约束记录。

## 模式
系统里有没有类似的实现?搜索相似的功能、相似的校验、相似的
跨模块协作。如果有,它是怎么做的?能不能参考?

这套提示词不是 skill——它不需要安装,不需要配置。复制粘贴,换掉需求描述,就能用

当然,你也可以把它做成一个 skill。那就更方便了——每次 grilling 跑完,自动触发一次结构化探索


这套东西有保质期

最后说一个需要诚实面对的问题:这套方向清单是给现在的 AI 用的

grilling 的提示词已经让 AI 去探索了——只是没说按什么方向探索。随着模型越来越强,AI 自己就会追调用链、查 git log、搜使用模式——不是因为它被五个方向引导了,而是因为它「知道这些事该做」

到那时候,结构化探索就不再是一个独立步骤了——它会融回 grilling 过程本身。grilling 跑完就等于 Context 构建完成。我们这套提示词也会从「单独的 skill 补充」退化成「grilling 内部的隐性习惯」——最终消失

但在那之前——在 AI 还会漏掉 MQ 回调入口、还会忽略隐式假设、还会忘记查 git log 之前——这张方向清单能帮你把 Context 补全

它不是标准流程。它是一个过渡工具。好用就用,不需要了就扔


这是 AI Native 软件开发系列的第八篇。前七篇:软件开发管理的对象是知识知识的六个层次从知识到上下文的筛选工程决策——在不确定中做最好的选择Specification——把决策翻译给 AIExecution 与闭环——从执行到知识的回流知识沉淀的第一步——统一语言与 ADR。这一篇继续落地:如何在 grilling 中系统性地构建工程 Context。如果你有不同的理解或者实践经验,欢迎一起讨论。