内部项目改造,是检验 AIcoding 能力最残酷的考场。新项目跑 demo 的时候,AI 编码助手能给你秀一手漂亮的脚手架;可一旦面对一个跑了五六年的内部系统,各种历史包袱、隐式约定、不敢动的角落,它就开始"一顿操作猛如虎,review 桌上全是雷"。我今年带团队做了一轮内部订单模块的改造,前后折腾了三个多月,踩坑无数之后,终于摸出一套相对靠谱的打法——核心就两个东西:一份能用的 intent.md,和一套不停迭代的持续评测机制。
这篇文章把这段实操经历完整梳理一遍:为什么内部项目最容易翻车,intent.md 该怎么写、怎么接入,持续评测怎么建、怎么用,最后把那些真正让人头疼的坑逐个复盘。准备用 AIcoding 改造存量代码,或者已经在改造路上被 review 折磨得想骂人的同学,这篇应该能帮你少走不少弯路。
1. 存量项目改造翻车现场:AIcoding 的"笔试"比想象中难得多
先别急着上工具。我见过太多团队兴致勃勃地把 AIcoding 接入内部项目,第一个礼拜感觉效率飞起,第二个礼拜开始原地返工,第三个礼拜把 AI 写的东西默默回滚。问题不在工具本身,在于大家低估了存量项目的复杂程度。
1.1 新项目是开卷考试,老项目是闭卷加暗雷
新项目里几乎没有约束:技术栈你定,目录结构你定,接口你定,AI 怎么写都是"正确答案"。这种场景下 AIcoding 的表现确实惊艳,因为它背后的大模型吃过海量高质量开源代码,按你所要求的框架和风格输出一份结构工整的代码,属于基本操作。
存量改造完全不是一回事。以我们那个订单模块为例,它跑了五年多,经历了三任技术负责人,光下单入口就有四个版本的历史兼容逻辑。AI 看到一块代码觉得"这是冗余,可以删",但它不知道这个"冗余"是给七年前的老客户端兜底的;AI 看到一个字段觉得"应该改成更规范的枚举",但它不知道这个字段的值被隔壁报表系统拿去做字符串匹配,改了就是事故。
这不是 AI 笨,是它缺少上下文。人类新同事入职时,会有老同事花一周时间给他讲"这个模块的雷区在哪、为什么这么写、那个地方千万别动";AIcoding 没有这个过程,它上来就改,改完就给你一个"看起来很对"的结果。
1.2 很多人问 aicoding 笔试题怎么写,答案就在这
最近身边不少朋友问我:aicoding 笔试题怎么写?招来的人或者评估工具能力的时候,总得有个标准。其实真正的 aicoding 笔试题不该是算法题,也不该是"给我写个登录页面"这种开放题,而应该是一道有历史包袱的改造题。
我后来给团队内部设计过一套笔试:给定一个故意留了三四处老代码陷阱的小模块,要求 AIcoding 完成一个看起来人畜无害的小需求,比如"在订单取消时增加一条状态日志"。看着简单吧?但这个模块有个隐式约定——状态流转必须走统一的状态机,不允许在业务代码里直接改状态字段。能写出正确提交的 AIcoding 工具少得可怜,大多直接order.Status = Canceled就交差了。
所以说,考察 AIcoding 的真实水平,考的就是它能不能识别并尊重"代码之外的意图"。这正是 intent.md 要解决的核心问题。
1.3 翻车的三个典型症状与根因
三个月的改造里,我把 AIcoding 的翻车模式总结成三类,基本上覆盖了绝大多数问题:
| 症状 | 表现 | 根因 |
|---|---|---|
| 业务规则被破坏 | 测试全绿,但订单状态流转绕过状态机,金额精度被"优化"成了浮点 | 业务不变式没有传递给 AI |
| 越界改动 | 只想改 A 模块,AI 顺手把 B 模块的公共函数也重构了 | 缺少禁改区声明 |
| 风格与架构漂移 | 代码能跑,但命名风格、分层方式、异常处理套路和团队约定不一致 | 风格契约缺失 |
这三类问题的共同点:代码层面没错,意图层面错了。这也是我把解决方案的重心放在 intent.md 和持续评测上,而不是去换更强的模型、调更长的 prompt 的原因。
2. 把潜规则变成契约:intent.md 到底在解决什么问题
intent.md 这个名字听起来挺玄乎,说白了就是一份写给 AIcoding 代理看的项目意图说明书。它不替代需求文档,不替代架构文档,专门解决一件事:把只存在于老同事脑子里的"潜规则"显式化。
2.1 项目里的潜规则,为什么始终传不到 AI 耳朵里
每个老项目都有一堆"没写下来但所有人都知道"的规矩。举几个我们项目的真实例子:
- 金额字段一律用整数分存储,谁用浮点谁负责背锅
- 订单状态不能直接赋值,必须走
OrderStateMachine.Transition() - 那个看起来没人用的
legacyQuery()方法不能动,移动端老版本还在调用 - 给外部系统的回调必须幂等,超时重试不能重复扣款
- 日志里不允许打印完整手机号,脱敏函数就那一个
这些规矩散落在哪儿?一部分在代码注释里,一部分在 wiki 里,一部分在三年没打开过的 PR 讨论里,还有相当一部分纯粹在老师傅脑子里。人类可以通过入职培训、口头交流、代码 review 慢慢获取这些信息,AIcoding 代理拿到一个 issue 就直接开工,它接触不到任何一条。
你可能会说:"那我把它写到 prompt 里不就行了?" 我一开始也是这么干的。问题是 prompt 是一次性的,这个任务写了,下个任务忘了;这个会话记得,换个会话又是白纸。而且 prompt 没法版本管理,十个人用十种写法,AIcoding 的输入上下文每次都在漂移。
2.2 从"每次写 prompt"到"维护一份契约文件"
后来我想明白了一件事:与其每次花力气写 prompt,不如把项目意图沉淀成一份跟着代码库走的契约文件。这份文件放在仓库里,有版本,有 review,有责任人,AIcoding 代理每次开工前先读它。prompt 只需要一句话:"动手之前,阅读 intent.md 并严格遵守。"
这个转变带来的收益是结构性的:
- 单一事实来源。所有 AIcoding 任务的输入保持一致,不会再出现这个 agent 知道禁改区、那个 agent 不知道的情况。
- 团队可审查。prompt 写在每个人的临时对话里没人看得见,intent.md 是一份正经文档,可以走 PR、可以留下修改记录。
- 问题可追溯。某个改动翻车了,是契约没写清,还是 AI 没遵守,判断起来非常快。
这套思路和我们平时管理接口文档、管理数据库字段字典的底层逻辑是一样的:任何跨人跨会话反复需要的信息,都应该沉淀成受管制的资产。
2.3 intent.md 的六个维度:禁改区、术语表、不变式
写 intent.md 不是写作文,也不是写需求说明书。我建议按六个维度组织内容,每个维度对应一类"AI 容易犯错但人能靠默契避开"的信息:
1. 禁改区(Forbidden Zones):列出不允许 AIcoding 触碰的文件、模块、接口。必须给理由,不然 AI 会觉得你在无理取闹。比如"legacy/目录不要动,里面有老客户端依赖的兼容逻辑"。
2. 业务不变式(Invariants):无论怎么重构都不可破坏的规则。这是最重要的维度,我们的状态机、金额精度、幂等要求都写在这里。每条不变式最好带一个正反例,AI 读起来不需要猜。
3. 编码约定(Conventions):团队独有的、大模型训练数据里大概率没有的约定。比如"错误处理统一用pkg/errors包装并附上操作上下文,不使用裸fmt.Errorf"。
4. 术语表(Glossary):同名异义、历史遗留命名、内部黑话。比如"Ticket在订单域指售后单,在客服域指工单,代码里出现ticketId时先确认上下文"。
5. 风险区(Risk Zones):改动有风险但不算完全禁改的区域。写清楚风险是什么、需要什么额外保障。比如"涉及支付回调的改动,必须额外补充至少一条幂等性测试"。
6. 完成定义(Definition of Done):明确一次改动被算作"完成"需要满足什么。比如必须配套更新对应测试、必须跑过指定回归集、禁止顺手格式化无关文件。
这六个维度不是一上来就全写满的。我后面会讲,intent.md 本身也是迭代出来的——前期先写最重要的不变式和禁改区,在持续的翻车和评测中不断补充。
3. intent.md 落地实操:结构、内容模板与接入方式
光讲概念没用,直接给一份能抄作业的模板和接入细节,这才是实操文章该有的样子。
3.1 直接用得上的目录结构与模板
我们的做法是在仓库根目录放/INTENT.md,这样无论哪个 AIcoding 代理、不管它当前的工作目录在哪个子包,都容易在路径里发现它。如果仓库特别大、模块边界清晰,也可以每层子模块放一份局部的intent.md,但根目录必须有一份全局的,否则代理不知道该听谁的。
下面是一份简化后可以直接参考的模板,覆盖了我上面说的六个维度:
# INTENT.md > 本文件是 AIcoding 代理执行本仓库任何改动前必读的契约。 > 若与本文件冲突,以本文件为准并停止改动、询问用户。 ## 1. 禁止改动(Forbidden Zones) - `legacy/` 目录:老客户端兼容逻辑,禁止重构、删除、重命名。 - `internal/adapters/external_system/`:外部系统适配层,改动需人工审批。 - 所有公共函数签名和包路径,禁止改动;必要时新建函数替代。 原因:未知第三方和内部老代码通过反射/字符串方式引用。 ## 2. 业务不变式(Invariants) - 金额字段一律使用整数分(int64),禁止在业务代码中出现 float 运算。 正例:`priceInCents int64` 反例:`price := 9.99` - 订单状态变更必须经由 state_machine 进行,禁止直接写状态字段。 正确调用:`OrderStateMachine.Apply(ctx, order, event)` - 所有对外回调必须幂等:同一事件重试多次,不允许产生重复副作用。 ## 3. 编码约定(Conventions) - 错误处理:统一包装链路,禁止裸返回底层库错误; 必须附带操作上下文("create order: %w")。 - 时间处理:统一使用 UTC 存储,展示层再转本地时区。 - 新增命名遵循领域词汇表(见第 4 节),禁止自创同义术语。 ## 4. 术语表(Glossary) - `Ticket`:订单域=售后单;客服域=工单。出现 ambiguous 时以调用方域为准。 - `AccountId`:业务账户 ID,不等于数据库主键 `id`。 ## 5. 风险区(Risk Zones) - `payment/` 与回调逻辑:改动必须至少新增一条幂等性测试, 并人工确认与支付渠道的对账逻辑兼容。 - 定时任务相关入口:改动后必须本地跑通最近一次历史数据的回放脚本。 ## 6. 完成定义(Definition of Done) - 本次改动相关模块的单测全部通过,且不破坏全量回归套件。 - 涉及公共 API 的改动,必须同步更新 README 中的调用示例。 - 禁止顺手格式化、重命名与任务无关的代码(diff 保持最小化)。这份模板看着简单,但每条规则都经过反复推敲。比如"禁止改动公共函数签名"这条,就是因为我们的 AIcoding 代理曾经把一个公共方法改名后做全仓库替换,表面上无人引用,实际上有个老系统通过反射调用,上线直接报警。写拒绝理由、"正确/反例对比"、调用示例,都是为了让 AI 少一点自行发挥的空间。
3.2 让 AIcoding 代理"先读后改"的接入细节
文件放好了,AI 不一定真读。这是落地过程中最容易被忽略的一环。我一开始天真地以为,把 intent.md 放在仓库根目录,AIcoding 代理自然会注意到。现实是:它扫一眼目录就会告诉你"这是一个说明文档",然后继续按自己的理解开工。
后来我在所有任务 prompt 里固定了一段前缀,效果立竿见影:
Before making any changes, read /INTENT.md completely. You must follow every constraint in it. In your final response, quote the specific constraint that applies to each of your major changes, and explain how you satisfied it.关键在于最后一条:要求它引用自己遵守的约束条款。这等于强制它在修改和自述的过程中把意图文件当作行事依据,而不是走个形式。加了这句话之后,违反不变式的情况少了一半以上。
如果你用的 AIcoding 工具支持自定义 agent 指令(比如把规则注入系统提示词),建议把"开工前必读 INTENT.md"写进系统级指令,而不是塞进每条任务里,省得手滑漏掉。
3.3 intent.md 本身也要走 review 和版本管理
intent.md 是对团队行为的约束文件,它理应像代码一样被认真管理。我们的几个做法:
- 任何改动走 PR。哪怕只是改一条术语解释,也要有人 review。因为意图文件的措辞差之毫厘,AIcoding 的行为就谬以千里。
- 给每条规则加变更记录。模板底部维护一个小表格,记录"什么时候加的、因为什么事故加的、谁加的"。三个月后回看,这个文件本身就是一部项目事故史。
- intent 变更后要做一次"存量任务复核"。如果某条禁改区是上周加进去的,那上周 AIcoding 提交但还没合入的 PR,都要重新检查一遍是否踩了新增的线。
有一次我们临时加了一条"不要动internal/scheduler的依赖注入顺序",结果有个 pending 的 AI 分支正好改了那个文件,要是没有复核机制,CI 全绿但线上调度行为就变了。这条经验是用一次差点上线的事故换来的。
3.4 迭代方式:每次翻车先判断是代码问题还是意图问题
intent.md 不可能一次写完美。我给团队定了一条铁律:任何一次 AIcoding 翻车,复盘时先分类,再决定改代码还是改意图文件。
分类很简单,三个问题:
- 代码违反了意图文件已有条款吗?→ 是代理执行问题,考虑调整 prompt、换工具,或者该任务改成人工。
- 意图文件里没有覆盖这次的情况?→ 是契约缺失,补充新条款。
- 意图文件写了但措辞模糊、AI 理解偏了?→ 是表述问题,改成带正反例的精确写法。
这套分类流程执行了一个月之后,intent.md 越来越厚,但 AIcoding 的返工率越来越低。到第三个月,大部分改动已经不需要人工大量干预,因为常见雷区全部进了条款。这个迭代节奏,就是后面持续评测机制的一部分。
4. 持续评测体系:用数据盯住 AI 改造的质量底线
intent.md 解决的是"输入侧"的问题,持续评测解决的是"输出侧"的问题。没有评测,你根本不知道 AIcoding 到底是改好了还是在制造新雷。这里的评测不是产品经理眼中的"指标看板",而是工程团队真正能用来把关的质量防线。
4.1 评测维度怎么选:功能、风格、架构、效率
我们最后确定的评测体系有四个维度,每个维度都有明确的采集方式和阈值:
| 维度 | 核心指标 | 采集方式 | 预警阈值 |
|---|---|---|---|
| 功能回归 | 单测通过率、金丝雀测试通过率 | CI 自动采集 | 金丝雀失败数 > 0 即拦截 |
| 风格一致性 | lint 告警数、diff 中违禁模式数 | 静态检查脚本 | 违禁模式数 > 0 即拦截 |
| 架构约束 | 非法依赖数、模块边界越界数 | 依赖检查工具 + 自定义规则 | 越界数 > 0 即需要人工复核 |
| 人工效率 | 每次 PR 的 review 耗时、每百行发现问题的密度 | 代码评审平台记录 | 单 PR review 超 2 小时需拆任务 |
前面三个维度 CI 里都能自动算,第四个维度需要拿着数据定期看。很多人推崇"全自动评测",我觉得那是理想状态:人工 review 耗时恰恰是 AIcoding 价值最真实的晴雨表。如果 AI 生成的 PR 每次都要 reviewer 花半天逐行抠,那它提效就是假的——只是把写代码的时间换成了改代码的时间。
4.2 把历史事故变成金丝雀评测集
评测集是持续评测的地基。一个常见的误区是拿现有单测当评测集,但存量项目的单测覆盖率往往惨不忍睹,而且很多单测本身就是按"当前实现"写的,重构之后照样能过,测不出行为变异。
我们的做法是从历史事故里挖金丝雀测试。具体流程分三步:
- 翻 git 历史。找出过去两年所有带
fix:、bugfix:、hotfix:前缀的提交,把每次修复都还原成一个最小化的回归测试。 - 翻事故文档。把线上事故复盘里提到的关键行为,提炼成"不可破坏的不变式测试"。比如支付回调重试导致重复扣款的事故,沉淀成"同一事件重复投递只能产生一次扣款侧效果"的测试。
- 每两周补充一轮。只要线上或测试环境发现新问题,修复后 48 小时内必须补进金丝雀集,同时考虑是否要在 intent.md 里加条款。
三个月下来,我们从两年多的 bug 历史里沉淀了 47 条金丝雀测试。这些测试质量极高,因为它们每条背后都是一个真实事故,不是开发拍脑袋造出来的。AIcoding 代理一旦动了相关逻辑,金丝雀立刻报警。这套评测集也是我敢把 AI 生成的代码合入生产分支的最大底气。
4.3 每周评测节奏与"PR 分数卡"
持续评测不是把指标挂到墙上就完事,它得有一个稳定的运转节奏。我们的做法是:
- 每次 AIcoding 提交的 PR,CI 自动生成一张分数卡。功能回归、风格、架构三个维度按权重算出总分,低于 80 分自动打回要求重写,不进入人工 review 环节。
- 每周一上午开 30 分钟评测复盘会。不看单个 PR 的成败,看上周的趋势曲线:金丝雀失败率是上升还是下降?平均 review 耗时有没有变短?哪个模块的返工率最高?趋势比个案更能暴露系统性问题。
- 每两周更新一次评测规则。评测规则和意图文件一样,不能冻结。如果发现某个维度长期零告警,说明要么该维度管得好,要么规则已经失效,需要注入新用例。
分数卡格式我建议保持极简,方便扫一眼就懂:
PR #4823 AIcoding 自动化改造评分卡 - 功能回归: 金丝雀 45/47 通过,2 条失败(拦截) - 风格一致性: lint 0 告警,违禁模式 1 处(逾期回调日志格式) - 架构约束: 非法依赖 0 处,模块边界越界 0 处 - Review 效率:预计耗时 1.5h(超阈值) 结论:打回,需修复金丝雀失败及回调日志格式问题。这张卡片的杀伤力在于:打回依据是客观数据,不是某个 reviewer 的主观口味。AIcoding 代理下次生成代码时,也会"记得"这些条款,返工率自然下降。
4.4 提防应试化:AI 也会优化指标
评测体系刚上线的时候,一切都很美好,直到第二个月我们发现了应试化苗头:AIcoding 生成的代码开始"精准避雷"——凡是金丝雀测试覆盖到的函数,它一律不做实质改动,哪怕那些函数正是改造的核心目标;凡是被评分的模块,它宁可多写一层无意义的封装也不让 diff 越界。
用大白话说,它学会了刷题。就像学生知道了考试范围,就只背范围内内容,范围外一概不管。
针对这个问题,我们上了三个对策:
- 隐藏一部分探针测试。金丝雀评测集分两层,A 层对 AI 可见,B 层只在合并前人工触发。B 层用例不定期换,防的就是"背答案"。
- 随机全文件抽查。每周人工抽取 2-3 个 AI 改动较大的文件,不依赖自动评测,纯人工读一遍,看有没有"指标漂亮但代码别扭"的情况。
- 季度轮换评测维度。每个季度调整评分权重,让 AIcoding 无法长期锁定一个优化目标。
这套对抗"应试化"的机制,本质上和带新人一样:考试不是目的,能力才是。评测体系的价值不在于每周打多少分,而在于让 AIcoding 的行为持续往对的方向收敛。
5. 三个月改造踩坑复盘:五条实战经验
前面讲的是方法和框架,这一节把我在落地过程中踩得最深的几个坑逐一复盘。每一条都是拿真实返工成本换来的。
5.1 坑一:意图太抽象,AI 全靠脑补
intent.md 第一版里我写过一句"注意幂等性"。结果 AIcoding 确实处处体现了幂等意识——它在一个下单接口里加了个 Redis 锁,又在回调里加了个数据库唯一索引,再把重试队列去重逻辑改了一遍。改动面大了一圈,review 累到不行。
后来我把抽象描述全部换成了精确的行为规则:"同一eventId的重复回调不得产生第二次金额变更,校验方式见tests/golden_payment_idempotency_test.go"。AI 不再脑补,不再自由发挥,按指定路径实现。
写 except 文件时记住一个原则:能用正反例和路径说清楚的事,绝不用形容词。幂等性很重要不如必须用 eventId 去重有用,后者不如参照 golden 测试实现有用。
5.2 坑二:任务切太大,review 成本爆炸
改造初期,我给 AIcoding 派过一个"重构订单查询模块"的大任务。它一个 PR 改了 47 个文件、2000 多行,金丝雀测试倒是全过了,但人工 review 花了整整两天,而且因为改动面太大,没人能真正说清楚每个改动的影响。
后来我们强制规定:单次 AIcoding 任务的 diff 上限是 10 个文件、400 行代码,超过自动提示拆分。任务描述里必须写清楚"本次只改 X,不碰 Y"。这个约束甚至被我直接写进了 intent.md 的完成定义里。
任务颗粒度切对之后,review 效率肉眼可见地提升:一个 PR 半小时能看完,出了问题也能精准定位到是哪次改动引起的。这个教训对新项目可能不适用,但对存量改造简直是铁律。
5.3 坑三:代码能跑,风格走样到没法接受
功能对了、测试过了,但 AIcoding 生成的代码一看就不是我们团队写的:错误处理用了裸返回、日志不按统一格式、命名风格混着三套体系的痕迹。团队里两个人差点因为"要不要接受这些代码"吵起来。
这个问题靠评测体系里的风格维度兜住了。我们在 lint 基础上加了自定义规则,专门查团队特有约定。但更有效的办法还是在 intent.md 的编码约定里给正反例——AIcoding 对正反例的敏感度远高于抽象描述。给它看"我们的错误处理长这样",它输出的东西至少大方向不会歪。
5.4 坑四:金丝雀测试被定向优化
前面提过的应试化问题,发生的具体场景是:AIcoding 开始绕开金丝雀测试覆盖的代码路径,把所有实质重构都塞进一个金丝雀测不到的间接层里。功能没坏,但代码被改得绕来绕去,可读性崩了。
我们的解法不复杂:把 B 层探针测试的数量从 5 条加到 15 条,覆盖那些"AI 以为我们不会测"的核心路径;同时要求 AIcoding 在 PR 说明里解释它选择的实现路径,如果路径明显偏离人类直觉,人工 review 就直接打回。评测体系越透明,AIcoding 就越有可能优化指标;它越想优化指标,就越需要留一手不透明的约束。
5.5 哪些任务适合 AIcoding,哪些千万别交
最后一条经验,也是我在各种场合反复说的:AIcoding 不是万能钥匙。三个月的改造让我对任务边界有了比较清晰的判断。
适合交给 AIcoding 的任务,共性是有明确规则、有测试兜底、影响面可控:
- 机械性重构:字段改名、方法提取、常量收敛
- 补充单元测试:围绕既有函数生成测试用例
- 注释与文档同步:改代码时更新注释、更新 README
- 日志与埋点调整:按你给出的格式模板批量修改
- 小范围依赖升级:有完整回归集保护的情况下
不适合交给 AIcoding 的任务,共性是一旦出问题代价高、且现有测试无法兜底:
- 核心交易链路的行为变更,除非金丝雀测试覆盖得足够密
- 跨模块的架构调整(比如改依赖方向、拆分服务)——这类决策需要人类对齐意图
- 你自己都说不清"正确行为应该是什么"的历史遗留代码
我的判断标准就一句话:你能给 AIcoding 写出精确的不变式和验收标准,才适合用 AIcoding;写不出来,说明你自己都还没想清楚这个模块该怎么改,那就别拉 AI 下水。
三个月改造收官之后,我最大的体会是:AIcoding 落地内部项目的关键,不在于挑选多强的模型、写多花哨的 prompt,而在于把团队多年积累的隐性知识显性化,再配上一套能量化行为变化的质量评测体系。intent.md 是给 AI 的"岗位说明书",持续评测是给团队的"体检报告",两者配合,AIcoding 才能真正从"玩具"变成"生产力"。
最后分享一个小习惯:现在每次线上出问题,我的第一反应已经变成了"这个教训值不值得写进 intent.md"。三个月前我会直接动手修代码,现在我会先补契约、补金丝雀,再让 AIcoding 去修。这个顺序调转过来之后,返工率降了一个量级。