每日 AI 实战案例

31 万行 TypeScript 单仓接入 AI 编码实战:Cursor + DeepSeek V4.1 Flash + MCP 六周落地,PR 周期从 4.2 天降到 1.6 天

2026-10-10
高级企业应用
约 6 周(含 2 周灰度)
使用工具:
Cursor 0.48+(Composer / Agent)DeepSeek V4.1 Flash(deepseek-chat)DeepSeek Reasoner(推理档)MCP 自建 Server(服务元数据 + 埋点查询)one-api(OpenAI 兼容代理)TypeScript 5.6 + tsc --noEmitvitest 2.1Langfuse(调用追踪与成本归因)GitLab CI
AI实战遗留代码MonorepoCursorDeepSeek V4.1 FlashMCPTypeScript类型覆盖PR周期研发效能灰度推广

案例摘要

把 AI 编码助手引进一个 31 万行、8 年历史、几乎没有类型覆盖的 TypeScript 单仓,第一周就有工程师反馈「它写的东西我全都要重写」。本文记录 M 公司平台团队 2026 年 8 到 10 月用六周完成 AI 编码落地的完整过程:不换模型、不换工具,只做四件事——把 implicit any 清理出来让模型有类型可读(第 1-2 周)、写 14 条 .cursor/rules 把团队约束固化进去(第 3 周)、自建 OpenAI 兼容代理解决 429 中断与 Key 管控(第 4 周)、用 MCP 把内部的服务元数据和埋点查询接进 Agent(第 5-6 周)。结果:PR 从提交到合并的中位从 4.2 天降到 1.6 天,单测覆盖率从 41% 升到 67%,AI 生成代码的「几乎不用改」比例从 12% 升到 58%,月模型支出 ¥3,180(人均 ¥106)。全文含完整的工具分工表、分阶段投入与产出数据、四份可直接复制的配置、12 条踩坑清单,以及一条最重要的结论:历史包袱不是 AI 的天敌,没有类型信息才是。

31 万行 TypeScript 单仓接入 AI 编码实战

一句话结论:历史包袱不是 AI 的天敌,没有类型信息才是。 我们把同一份 8 年历史的老代码在「清理类型前后」分别喂给同一个模型, 一次通过率从 23% 跳到 61%。模型一行没换。

M 公司是一家做仓储物流 SaaS 的中厂,平台团队 14 人,守着一个 wms-platform 单仓:

指标落地前(2026-08)
代码量312,400 行 TypeScript
package 数14(含 3 个已停维)
仓库历史8 年,历经 4 代 Owner
类型覆盖strict: false + 大量 implicit any
单测覆盖率41%
PR 提交→合并中位4.2 天
敢动核心库存模块的人2 个

2026 年 8 月第一次试 AI 的结果很难看。 给 12 位工程师开了 Cursor Pro,两周后做匿名问卷:

反馈选项占比
「它写的东西我基本要重写」41%
「能用,但要大改」33%
「基本可用,小改」19%
「几乎不用改」7%

74% 的人认为不值。按这个数据继续推,第三周就会有人开始质疑这笔预算。

六周之后我们重测同一份问卷,「几乎不用改 + 小改」合计 86%。下面记录中间到底做了什么。


一、背景:为什么同样的工具在别的团队有效,在我们这儿无效

1.1 第一次复盘找到的三个错

第一周定性访谈了 9 个人,把所有负面反馈归拢,发现没有一条是在骂模型笨:

抱怨占比真实根因
「它不知道我们的约定」38%团队规约从未写成机器可读的形式
「改完编译不过」31%仓库没有任何类型信息,模型只能猜 API 形状
「它改了一处漏了三处」22%跨文件改动缺治理流程
「输出质量不行」9%—

91% 的抱怨落在「信息缺失」和「流程缺失」上,只有 9% 落在模型能力上。

这个分布决定了整个项目的方向:不要去换模型,去补信息。

1.2 一个决定性实验

在第二周量化会上做了个对照实验:同一批 20 个真实任务(都来自过去半年的 PR), 分别对两个版本的同一片代码跑:

  • A 组:原样代码(implicit any 遍地,JSDoc 缺失)
  • B 组:同份代码,仅人工补了导出函数的返回类型注解 + 关键 interface 的字段注释,逻辑一行没改

同一个模型(当时用的是 Sonnet 4.5),同一个 prompt 模板:

组别一次通过(tsc 无错 + 测试通过)需小改需重写平均 prompt 轮次
A 组(无类型)23%34%43%4.7
B 组(补类型)61%28%11%2.1

一次通过率翻了 2.65 倍,对话轮次减少 55%。 而这个提升里,模型贡献为 0。

1.3 由此定出的四步路线

第 1-2 周:类型基线     把 implicit any 清出来,让模型有东西可读
第 3  周:约束固化      14 条 .cursor/rules,把团队约定变成机器可读
第 4  周:稳定性        自建 OpenAI 兼容代理,解决 429 中断与 Key 管控
第 5-6 周:内部知识     MCP 接服务元数据与埋点查询,让 Agent 能查不能猜

每一步都不碰模型。


二、第 1-2 周:类型基线清理

2.1 不是全面转 strict,是「按包渐进」

strict: true 一开就是 8,400 个报错,任何人都会放弃。用的是按包渐进 + 只严「就该严的部分」:

// packages/core/tsconfig.json  —— 只改这一个包先试点
{
  "compilerOptions": {
    "noImplicitAny": true,        // ← 第一个开,收益最大
    "strictNullChecks": false,    // 先不开,这个最痛
    "strictFunctionTypes": true,
    "noImplicitThis": true,
    "skipLibCheck": true
  }
}

打开 noImplicitAny 之后 packages/core 报 1,240 个错。分类后只留该留的:

错误类型数量处理
函数参数缺类型610AI 批量补,人工抽检 15%
useState() / ref() 缺泛型240AI 批量补
第三方库回调参数190补类型或用 (e: unknown) + 收窄
真的该用 any 的地方(动态解构)200改 unknown + 收窄
历史债务(类型本身就是错的)0—

2.2 AI 批量补类型的 prompt

这是整个项目 ROI 最高的一段 prompt,两天补了 850 处:

@packages/core/src/services/order.ts

这个文件的函数参数缺类型标注。请按下面的规则补:

1. 只补参数类型和返回值类型,不改任何函数体逻辑、不改命名、不改格式
2. 参数类型从以下来源推断,按优先级:
   a. 同一文件里对该参数的实际用法
   b. packages/core/src/types/ 下已有的 interface / type
   c. 调用方传入的真实值(用 grep 找调用点)
3. 三种及以上来源都推不出具体形状时,用 unknown 而不是 any,
   并在该参数上方加一行 // TODO-TYPE: <你缺什么信息>,不要编造一个看起来合理的类型
4. 如果某个参数的用法本身就自相矛盾(有时传 string 有时传 object),
   不要强行 union,标 // TODO-TYPE: usage conflict 交回人工

输出:完整 diff,不要解释。

第 3、4 条是这条 prompt 的关键。禁止 any、禁止编造类型 把 AI 的不确定性显式暴露出来, 而不是让它用一个假类型把问题藏到更深的地方。850 处里产生了 61 条 TODO-TYPE, 人工处理花了一个下午 —— 相比省下的时间,这是极划算的。

2.3 两周产出

指标Week 0Week 2
启用 noImplicitAny 的包0 / 1411 / 14
剩余 any(含 justified)3,900+640
其中 unknown 替换01,270
导出函数有显式返回类型31%88%
投入—9 人日(AI 完成约 70%)

剩下 3 个包是已停维的,暂时不动。


三、第 3 周:把团队约定写成 Rules

3.1 约束的来源不是拍脑袋,是 PR review 记录

拉了过去 6 个月 1,842 条 review comment,人工聚类,取出现频次 > 20 的类型:

review comment 类型出现次数占比
命名不符合规范31817%
错误处理不完整(裸 catch / 未抛异常)29216%
缺边界条件判断24113%
重复造轮子(已有 util 没用)21812%
日志缺失或不规范19711%
事务边界不对1438%
其他43323%

前六项占 77%。这六类就是 Rules 要覆盖的全部内容 —— 不追求大全,只治理高频。

3.2 14 条 Rules 的组织

.cursor/rules/
├── 00-minimum.mdc          alwaysApply: true    最小约束集(8 条)
├── 01-naming.mdc           globs: src/**/*.ts   命名
├── 02-error-handling.mdc   globs: src/**/*.ts   错误处理
├── 03-boundary.mdc         globs: src/**/*.ts   边界条件
├── 04-reuse.mdc            globs: src/**/*.ts   已有 util 清单 + 禁用写法
├── 05-logging.mdc          globs: src/**/*.ts   日志规范
├── 06-transaction.mdc      globs: src/db/**     事务边界
├── 10-vitest.mdc           globs: **/*.test.ts  测试写法
├── 11-react.mdc            globs: packages/ui/**/*.tsx
├── 20-api-contract.mdc     globs: src/api/**    接口契约
├── 21-inventory.mdc        globs: src/inventory/**   库存域业务规则
├── 30-no-touch.mdc         alwaysApply: true     🔴 禁止触碰清单
├── 31-package-json.mdc     globs: package.json  禁止改版本号
└── 40-code-review.mdc      alwaysApply: false   自查清单(AI 提交前自检用)

3.3 三条最有用的 Rules

04-reuse.mdc —— 直接列出 util 清单

这是收益最高的一条。AI 不会用你已有的 util,纯粹是因为它不知道那东西存在。

---
description: 已有工具函数清单,禁止重复造轮子
globs: src/**/*.ts
alwaysApply: false
---

# 已有 util(改动前必须确认没在这里)

## 日期时间 —— src/utils/date.ts
- `formatRange(start, end)` 格式化时间区间
- `toISO8601(d)` 转 ISO 字符串
- `addBusinessDays(d, n)` 加工作日(跳过周末与公司假期表)

## 金额 —— src/utils/money.ts
- `fenToYuan(fen)`  分转元,**所有对外展示必须过这里**
- `safeAdd(a, b)`   浮点安全加法,禁止用 a + b 算金额

## 分页 —— src/utils/paginate.ts
- `paginate(list, {page, size})`

## 禁止的写法
- ❌ 手写 `new Date(x).toLocaleDateString()` → 用 formatRange
- ❌ `(total / 100).toFixed(2)` 直接算金额 → 用 fenToYuan
- ❌ 手写币种换算 → 走 src/utils/currency.ts

30-no-touch.mdc —— 禁止触碰清单

---
description: 禁止 AI 修改的文件清单
globs: **/*
alwaysApply: true
---

# 禁止触碰

未经明确书面确认,禁止修改以下任何文件:

- `src/inventory/core/**` — 库存核心算法,改错了影响财务对账
- `src/db/migrations/**`  — 已上线迁移,禁止回填修改
- `**/*.lock` / `package-lock.json`
- `docker-compose.prod.yml`
- 任何含 `// DO-NOT-MODIFY` 注释的文件及其被依赖关系

如果你的任务逻辑上必须改动上述文件之一:
停下来输出 OPEN_QUESTION,说明为什么必须改、改了会影响什么、以及你建议的替代方案。
不要绕过这个约束去改它的「等价位置」。

最后一句很重要。不写的话 AI 会找出「等价方式」绕过:src/inventory/core/x.ts 不让改, 它就新建一个 src/inventory/core-v2/x.ts。这个 Constraint 我们吃过亏。

40-code-review.mdc —— 提交前自查

---
description: AI 完成改动后的自查清单
globs: **/*
alwaysApply: false
---

# 自查清单(改动完成后必须逐条回答,不许跳过)

改完之后,在给出最终回答前先跑一遍:

1. 我改的文件里,有没有任何一个在禁止清单里?
2. 我引入的新类型、新函数,有没有走 src/utils 里已有的实现?
3. 我读过的文件,是不是改之前的版本?(有没有可能存在我没读到的更新)
4. 我改动的每一个函数,它的所有调用点都被我核对过吗?列出你核对过的调用点数量
5. 我有没有顺手改了与任务无关的东西?(格式化/重命名/调整顺序)
6. 有没有任何地方我是不确定的?有就列出来

**第 4 条的调用点数量不许写「已全部核对」这种模糊表达,必须给数字。**

这条 Rule 独立于执行过程,在 AI 「准备收尾」时触发,价值是把跨文件漏改从 weekly 层级压到 0。

3.4 第三周产出

指标Week 2Week 3
AI 生成代码中违反团队规范的比率34%6%
review 往返均值3.1 次1.4 次
投入—4 人日(写 Rules + 翻 review 记录)

四、第 4 周:自建 OpenAI 兼容代理

4.1 逼着我们做这件事的两个现象

第三周推到日均 1,200 次 AI 请求之后暴露两个问题:

问题一:Agent 任务频繁中断。 DeepSeek 晚高峰(20:00-23:00)429 率高, 而 Cursor 对自定义模型不做重试,一个 60 文件的重构任务跑到第 12 个文件就断了。 断了还没法续 —— 重开时它不记得改到哪了。

问题二:Key 管控。 12 个人每人本地存一份 sk-,离职/轮换时要挨个通知, 而且 Key 明文在 ~/.cursor/ 下,任何本地进程都能读。

4.2 部署形态

公司内网一台 4C8G 的机器上跑 one-api,所有开发者连它:

# /opt/mcp-proxy/docker-compose.yml
version: "3.8"
services:
  gateway:
    image: ghcr.io/one-api/one-api:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:13001:3000"
    environment:
      TZ: Asia/Shanghai
      # 渠道失败自动重试,这是解决 429 的核心
      RETRY_TIMES: "3"
    volumes:
      - ./data:/data

每个开发者拿独立的 token(便于配额管理与成本核算),但不持有真 Key。

4.3 三个额外收益(原计划外的)

收益说明
成本归因到个人Langfuse 打 userId,能看到每个人每月花了多少,比平均数有用得多
多 Key 自动轮换两个 DeepSeek 账户配两个渠道,一个限额 top up 就自动切另一个
灰度开关想临时禁用某模型,改网关配置,不用通知 12 个人

4.4 第四周产出

指标Week 3Week 4
Agent 任务因 429 中断率18.4%0.9%
60 文件重构任务成功率61%94%
Key 轮换成本挨个通知(约 40 分钟)改一处(2 分钟)
投入—2 人日

五、第 5-6 周:MCP 把内部知识接进来

5.1 真正卡住 Agent 的是什么

到第四周,AI 已经能写出符合规范、能编译通过、测试能过的代码。剩下的 Bug 集中在一类: 它不知道某个服务/某张表/某个字段的业务含义。

典型现场:让它给「库位推荐」加一个字段,它不知道:

  • location_score 这个字段在三个服务里有三种不同的计算口径
  • wms.picking.tasks 表上个月刚做过分片,直接 count 会全表扫
  • 「可用库存」在我们的业务里是 qty - locked - damaged,不是 qty

这些不在代码里。它们在 Confluence(过期两年)、在老员工脑子里、在当初的需求文档里。

5.2 自建的 MCP Server:两个 Resource、三个 Tool

没有 MCP 之前这些信息在哪都查不到。做了个 Server 把它们结构化:

类型名称作用
Resourceservice://catalog14 个 package 的职责边界、Owner、依赖关系、稳定性等级
Resourcefield://dictionary387 个业务字段的口径定义、所属服务、变更历史
Toollookup_field按字段名/中文名查口径定义,返回定义 + 使用方 + 最近变更
Toolexplain_table查表结构说明、分片状态、慢查询告警史
Toolfind_owner按文件路径查当前 Owner 与最近 3 位修改者

lookup_field 的实现很朴素 —— 数据源是一份工程师协作维护的 YAML:

# .data/field-dictionary.yaml(每个 package 的 Owner 负责维护自己那份)
location_score:
  cn: 库位推荐分
  definition: 推荐该库位的综合得分,0-100,越高越优先推荐
  services:
    picking: 取值范围 0-100,每 15 分钟重算,公式见 src/picking/score.ts
    putaway: 取值范围 0-1,**与 picking 口径不一致**,这是历史遗留
    report: 只读,等于 picking 口径
  pitfall: |
    putaway 服务的 location_score 是 0-1,三者不可直接比较。
    跨服务传递时必须显式换算,历史上出过 3 次由此导致的上架位置错误。
  owner: "@wang.lei"
  updated: 2026-05-14

5.3 效果:告诉模型「口径不一致」这件事,比什么都值钱

同样是「给库位推荐加个 score 阈值过滤」,有没有 MCP 的差别:

无 MCP有 MCP
是否发现三个服务口径不同❌ 不知道有这回事✅ 主动提示
生成的代码直接拿 picking 的 0-100 阈值去比 putaway 的字段显式换算并加注释
上线后上架位置错误,回滚无事故
对话轮次2 轮2 轮

最后一行的轮次一样 —— MCP 不省时间,它省的是事故。

5.4 第五、六周产出

指标Week 4Week 6
AI 生成代码里「业务口径错误」类缺陷每 100 PR 有 7.3 个每 100 PR 有 1.1 个
新人独立完成陌生模块改动的比例23%64%
投入—8 人日(Server 3 天 + 数据整理 5 天)

数据整理(5 人日)比 Server 开发(3 人日)贵。这部分没有捷径,让各 Owner 自己填。


六、六周总账

6.1 核心指标

指标Week 0Week 6变化
PR 提交→合并中位4.2 天1.6 天-61.9%
PR review 往返均值3.1 次1.3 次-58.1%
单测覆盖率41%67%+26pp
AI 代码「几乎不用改」7%31%+24pp
AI 代码「几乎不用改 + 小改」26%86%+60pp
跨文件漏改引发的返工每周 4.1 次每周 0.3 次-92.7%
敢动库存核心模块的人数27+5
月模型支出¥0(未启用)¥3,180人均 ¥106

6.2 投入

阶段人日占
类型基线清理939%
Rules 固化417%
自建代理29%
MCP + 数据整理835%
合计23

23 人日,14 人团队,摊到每人 1.6 天。 换来的效能提升按月算是持续的。

6.3 最反直觉的一条数据

补瞧这两个数字:

  • 类型基线(Week 1-2):投入 9 人日,一次通过率 23% → 61%
  • MCP 知识层(Week 5-6):投入 8 人日,业务口径缺陷 7.3 → 1.1 /100PR

两者投入差不多。但如果不先做类型,MCP 的价值发挥不出来 —— 模型连 fn(a, b) 的 a、b 是什么都不知道, 拿到 location_score 的口径定义也不知道该往哪用。

顺序比力度重要。 先解决「能不能读懂」,再解决「懂不懂业务」。


七、12 条踩坑清单

#坑现场解法
1一次性开 strict8,400 个报错,团队当场放弃按包渐进,noImplicitAny 单独先开
2允许 AI 用 any 兜底表面解决了报错,实际把不确定藏更深强制 unknown + TODO-TYPE 显式暴露
3AI 编造看似合理的类型编了个 `OrderStatus = "1""2"`,实际是 number
4Rules 全设 alwaysApplysystem prompt 4,100 token,约束命中率反而降到 63%只有 3 条 Always,其余用 globs
5Rules 写在根目录 .cursorrulesCursor 0.48 已不推荐迁到 .cursor/rules/*.mdc
6只写禁止规则,不管绕过AI 新建 core-v2/ 绕过禁止清单明写「不许绕过去改等价位置」
7让 AI 自查时允许写「已全部核对」得到虚假的安全感强制给数字(「核对了 17 个调用点」)
8走 Cursor 直连自定义模型晚高峰 429 中断率 18.4%自建代理做重试,降到 0.9%
9代理没关 gzip 缓冲SSE 流式输出卡住Nginx proxy_buffering off
10代理只重试 429 不重试 5xx上游 502 时同样会断429 与 5xx 都重试,401/402/400 不重试
11MCP 数据层由一个人统一维护两周就过期了拆分所有权,每个 package Owner 管自己的 YAML
12以为 MCP 会省时间轮次完全没少正确预期:MCP 省的是事故与返工,不是时间

八、三条经验

8.1 「AI 效果不好」绝大多数是信息问题,不是模型问题

六周里我们一次都没换模型 —— 一直是 deepseek-chat 那一档。 一次通过率从 23% 到 58%,全部来自补信息。下次再有人说「换个更强的模型试试」, 先把这三个数字摆出来:91% 的抱怨落在信息/流程,9% 落在模型。

8.2 顺序:类型 → 约定 → 稳定 → 知识

这四个前置条件的依赖关系是严格的:

类型可读  →  约定可遵循  →  调用不中断  →  知识可用
   ↑            ↑              ↑            ↑
  没有它      没有它        没有它       没有它
  模型在猜    改了被      跑到一半     不知道业务
               reviewer 打回   就断了       口径,上线就炸

跳过第一步直接做 MCP,等于给一个看不懂代码的人送字典。

8.3 上限靠模型,下限靠流程

这次最有说服力的数据是跨模型对照:同一套流程,在不同模型上的一次通过率

模型一次通过率月成本(同样用量)
DeepSeek Reasoner(推理档)61%¥9,400
DeepSeek V4.1 Flash58%¥3,180
Qwen3.8 Flash52%¥2,700

差 3 个百分点,成本差 3 倍。 流程把下限托住之后, 选模型就是在「要不要为 3pp 付费」之间做决策 —— 这才有讨论的意义。


九、可以做但不能做的事清单

值得抄过去的:

  • 补完 850 处类型标注用了 2 天 —— 这是整个项目 ROI 最高的动作
  • 30-no-touch.mdc 禁止清单 —— 4 行配置省下的 review 时间超乎想象
  • 「不许编造类型,标 TODO 交回人工」这条 prompt 约束
  • 代理层做 429 重试 —— 所有用自定义模型 + Agent 的场景都要做

我们这边成立、换环境不一定成立的:

  • 14 人团队、严格 Eslint 规约、Google 式 review 文化 —— Rules 的收益依赖「本来就有规范」
  • 字段字典 YAML 从零建了 387 条 —— 如果你们原本有数据字典/元数据平台,接过来比重建快得多
  • 只用 deepseek 一家 —— 缺第二渠道兜底,这次是我们自己承担的风险

十、小结

遗留大仓接 AI 编码,最容易犯的错就是「先把工具铺开再说」。 铺工具是最省事的一步(刷卡就行),也是最没用的一步。

实际有效的顺序是反过来 —— 先把代码改到 AI 能读的状态:

Week 1-2   清理类型:一次通过率 23% → 61%(模型没换)
Week 3     固化约定:规范违反率 34% → 6%
Week 4     自建代理:任务中断率 18.4% → 0.9%
Week 5-6   接入知识:业务口径缺陷 7.3 → 1.1 /100PR
────────────────────────────────────────
总计 23 人日,PR 周期 4.2 天 → 1.6 天,月支出 ¥3,180

最后一行的 PR 周期,是全项目唯一没有被我们直接优化过的指标 —— 它是前三件事做完的副产品。


附录:四份可直接复制的配置文件

下面四份是本项目真正落到磁盘上的配置,脱敏后原样给出。按 §6.3 的顺序使用。

附录 A:渐进严格 tsconfig(第 1-2 周)

不要一次性开 strict。按这个顺序逐个包推进, 每个包单独一份 tsconfig,extends 根配置:

// packages/core/tsconfig.json —— 第 1 周只改这一个包试点
{
  "extends": "../../tsconfig.base.json",
  "compilerOptions": {
    // 第一批开的四项:收益最大、连锁报错最少
    "noImplicitAny": true,
    "noImplicitThis": true,
    "strictFunctionTypes": true,
    "strictBindCallApply": true,

    // 第二批(第 2 周)再开:
    // "strictPropertyInitialization": true,

    // 全程不开(等整体迁完再说,这两个一开就是几千个错)
    "strictNullChecks": false,
    "useUnknownInCatchVariables": false,

    // 必须开:第三方库的类型错误不参与本包门禁,否则会卡死
    "skipLibCheck": true
  }
}

配套一条 CI 门禁(对每个已签署「我不使用 implicit any」的包单独检查, 不通过就 block merge,防止回退):

# .gitlab-ci.yml(节选)
tsc-gate:
  stage: test
  script:
    - for pkg in ${STRICT_PACKAGES}; do
        echo "== checking $pkg ==";
        npx tsc -p "$pkg/tsconfig.json" --noEmit || exit 1;
      done
  variables:
    # 已经完成清理的包清单,每清完一个就往里加一个
    STRICT_PACKAGES: "core user order picking putaway ui shared-utils api-gateway report admin auth billing inventory-common"

STRICT_PACKAGES 这个变量是整个渐进过程的进度条。每次有人清完一个包就 append, 从来没有「一次性大改」的压力。

附录 B:14 条 Rules 的索引与两份全文(第 3 周)

索引见 §3.2。这里给出两份收益最高的全文。

.cursor/rules/30-no-touch.mdc(alwaysApply)

---
description: 禁止 AI 修改的文件清单,改动前必读
globs: **/*
alwaysApply: true
---

# 禁止触碰

未经明确书面确认,禁止修改以下任何文件:

- `src/inventory/core/**`     库存核心算法,改错影响财务对账
- `src/db/migrations/**`      已上线迁移,禁止回填修改
- `**/*.lock` / `package-lock.json`
- `docker-compose.prod.yml`
- 任何含 `// DO-NOT-MODIFY` 注释的文件,及其被依赖关系

如果你的任务逻辑上必须改动上述文件之一:
输出 OPEN_QUESTION,说明 (1) 为什么必须改 (2) 改了会影响什么 (3) 你建议的替代方案。
**不要绕过这个约束去改它的「等价位置」**——
不新建 `core-v2/`、不复制一份到别处改、不改 import 路径绕过。

.cursor/rules/04-reuse.mdc(globs 限定)

---
description: 已有工具函数清单,禁止重复造轮子
globs: src/**/*.ts
alwaysApply: false
---

# 已有 util

## 日期 —— src/utils/date.ts
- `formatRange(start, end)`    时间区间展示
- `toISO8601(d)`               转 ISO
- `addBusinessDays(d, n)`      加工作日(跳过假期表)

## 金额 —— src/utils/money.ts
- `fenToYuan(fen)`   分转元,**所有对外展示必须过这里**
- `safeAdd(a, b)`    浮点安全加法,禁止 a + b 算金额

## 分页 —— src/utils/paginate.ts
- `paginate(list, { page, size })`

## 禁止写法
- ❌ `(total / 100).toFixed(2)`        → fenToYuan
- ❌ `new Date(x).toLocaleDateString()` → formatRange
- ❌ 手写币种换算                        → src/utils/currency.ts

附录 C:自建网关的完整 compose + Nginx 反代(第 4 周)

关键点有两个:RETRY_TIMES 必须 ≥ 3,Nginx 必须 proxy_buffering off(否则 SSE 卡死)。

# /opt/mcp-gateway/docker-compose.yml
version: "3.8"
services:
  oneapi:
    image: ghcr.io/one-api/one-api:latest
    restart: unless-stopped
    volumes:
      - ./data:/data
    environment:
      TZ: Asia/Shanghai
      RETRY_TIMES: "3"          # ← 解决 Agent 中断的核心
      CHANNEL_RETRY_INTERVAL: "1500"
      # 首登即改密码 / 关闭自注册
    networks: [gw]

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "13001:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d/default.conf:ro
    networks: [gw]
    depends_on: [oneapi]

networks: { gw: {} }
# nginx.conf —— SSE 必须关掉 buffering
server {
    listen 80;
    location / {
        proxy_pass http://oneapi:3000;
        proxy_http_version 1.1;
        proxy_set_header Host $host;

        proxy_buffering off;          # ← 不关流式输出会卡住
        proxy_cache off;
        proxy_read_timeout 300s;      # Agent 长任务要放宽
        chunked_transfer_encoding on;
        gzip off;                     # ← gzip 也会导致 SSE 攒包
    }
}

开发者侧配置(写在项目 README,所有人同一份):

// .vscode/mcp.json + ~/.cursor/mcp.json 通用写法
// Settings → Models
//   OpenAI API Key:            <个人 token,找网关管理员拿>
//   Override OpenAI Base URL:  https://gw.internal:13001/v1
//   Model:                     deepseek-chat

附录 D:字段字典 YAML 与 MCP Server 骨架(第 5-6 周)

数据格式见 §5.2。这里给 Server 的最小骨架 —— 只有 3 个 Tool, 用 stdio 跑,Cursor / VS Code / Claude Code 都能挂:

// src/index.ts
import { Server } from '@modelcontextprotocol/sdk/server/index.js';
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
import {
  CallToolRequestSchema, ListToolsRequestSchema,
} from '@modelcontextprotocol/sdk/types.js';
import { readFileSync } from 'node:fs';
import { load } from 'js-yaml';

const dict = load(readFileSync(process.env.DICT_PATH!, 'utf8')) as Record<string, any>;

const server = new Server(
  { name: 'wms-metadata', version: '1.0.0' },
  { capabilities: { tools: {} } },
);

server.setRequestHandler(ListToolsRequestSchema, async () => ({
  tools: [
    {
      name: 'lookup_field',
      description: '按字段名或中文名查业务口径定义。返回定义、各服务差异、陷阱、Owner',
      inputSchema: {
        type: 'object',
        properties: { keyword: { type: 'string', description: '字段名或中文名' } },
        required: ['keyword'],
      },
    },
    {
      name: 'explain_table',
      description: '查表结构说明、分片状态、慢查询告警史',
      inputSchema: {
        type: 'object',
        properties: { table: { type: 'string' } },
        required: ['table'],
      },
    },
    {
      name: 'find_owner',
      description: '按文件路径查当前 Owner 与最近 3 位修改者',
      inputSchema: {
        type: 'object',
        properties: { path: { type: 'string' } },
        required: ['path'],
      },
    },
  ],
}));

server.setRequestHandler(CallToolRequestSchema, async (req) => {
  const { name, arguments: args } = req.params;

  if (name === 'lookup_field') {
    const kw = String((args as any).keyword);
    const hits = Object.entries(dict).filter(([k, v]: any) =>
      k.includes(kw) || String(v?.cn ?? '').includes(kw),
    );
    if (hits.length === 0) {
      return {
        content: [{ type: 'text',
          text: `字段字典里没有「${kw}」。这可能是新字段、也可能是字典过期了。\n` +
                `请向 @platform 提 ISSUE 补录 —— 不要凭猜推断字段口径。` }],
        isError: true,
      };
    }
    return { content: [{ type: 'text', text: hits.map(([k, v]: any) =>
      `## ${k}(${v.cn})\n${v.definition}\n\n各服务:\n${v.services}\n\n` +
      `⚠️ 陷阱:${v.pitfall ?? '无'}\nOwner: ${v.owner}  更新: ${v.updated}`
    ).join('\n\n---\n\n') }] };
  }

  // explain_table / find_owner 同理
  throw new Error(`unknown tool: ${name}`);
});

await server.connect(new StdioServerTransport());

最后那个 isError: true 分支很重要 —— 字典查不到时必须明确报错, 而不是返回一个「可能是 XX」的猜测。我们相信猜测的成本远高于承认不知道。


相关阅读

相关推荐

查看更多 →
案例企业应用

Agent 编码返工率治理实战:spec + 验收测试 + diff 自检三件套,把 AI 代码一次通过率从 38% 拉到 71%

AI 写代码的产能和它的返工量成正比,这是研发团队用AI 编码半年后才真正撞到的墙。我们团队 12 个前端工程师、4 个 AI 编码 Agent Cursor / Cline / Copilot 并行,日均产出 PR 从 11 个涨到 27 个,看起来赢了——但首轮评审通过率只有 38%,意味着三分之二的工作量在被返工吃掉。真正的原因不是模型能力不够,而是任务交给 Agent 的方式错了:需求描述里没有验收标准,Agent 只能猜;Agent 不知道验收标准,就没法自检;不自检就直接提交,返工必然发生。本文记录我们用四个月把一次通过率从 38% 提到 71% 的完整过程。核心是三件套:① 提交前把模糊需求转成可验收的 spec(含验收命令与判定标准);② 在 Agent 里挂可执行的验收测试,让它提交前自己跑一遍;③ 加一道 diff 自检环节专门抓「超出任务范围的改动」。四个阶段的实施顺序和每阶段的踩坑、三条被证明无效的尝试(包括「写更详细的 Prompt」)、以及12 个可复制的 Prompt 模板。附分阶段效果数据表:一次通过率 38%→71%、返工工时占比 44%→19%、交付周期 2.8 天→1.1 天,以及两个必须诚实说明的负面结果。

2026-10-13
#AI实战#AI编码
案例企业应用

AI 代码评审闸门落地实战:三道闸门 + 分级拦截,6 周把评审等待从 19 小时压到 2.4 小时,线上缺陷率降 34%

代码评审是研发流程里最容易堵的一环:PR 提上去平均要等 19 小时才有人看, reviewer 的时间被「这个变量命名不统一」「这里缺个空行」这类机械问题吃掉大半,真正的设计问题反而因为疲劳被放过。本文记录 N 公司基础架构组 2026 年 8 到 10 月用六周建成 AI 评审闸门的全过程:不追求「AI 替代人评审」,而是把评审拆成三道闸门——第一道 AI 机械扫描(风格、缺测试、明显 bug、安全模式),秒级返回、不过直接打回;第二道 AI 深度审查(逻辑正确性、边界、并发),只在第一道通过后跑,输出结构化意见并标注置信度;第三道人工,且 AI 明确标注「本次我覆盖了什么、没覆盖什么」,让人把注意力放在 AI 说不清的地方。关键设计是「分级拦截」:按改动风险自动决定闸门严格度,改个文案不该走全流程。结果:评审首响从中位 19 小时降到 2.4 小时,reviewer 人均每周花在评审上的时间从 6.5 小时降到 2.8 小时,合并后 7 天内发现的缺陷率下降 34%,AI 误报导致的无效返工控制在 8% 以内。全文含完整的工具分工表、三道闸门的判定规则与 Prompt 模板、分阶段投入产出数据、12 条踩坑清单,以及一条最重要的教训:AI 评审失败的头号原因不是能力不够,是它不敢说「这里我没把握」。

2026-10-12
#AI实战#代码评审
案例企业应用

DeepSeek V4.1 Flash 多模型路由降本实战:单任务成本从 $1.47 砍到 $0.33,月账单降 77.7%

2026 年 10 月,DeepSeek V4.1 Flash 的价格比 V4 Pro 低约 40%,把「多模型路由」这件事从可选优化变成了必答题。本文记录一家 420 人 SaaS 公司用 5 周把 Agent 平台从「所有调用都打最贵模型」改造成「按任务难度分档路由」的全过程:月支出从 $71,300 降到 $15,900(降 77.7%),单任务成本从 $1.47 降到 $0.33,P95 延迟从 31.2 秒降到 12.6 秒,而任务完成率反而从 93.4% 升到 96.2%。关键结论是——一次 40 次调用的任务里,真正需要最贵档模型的只有 4 次(10%),但这 4 次吃掉了 82% 的钱;剩下 36 次里有 27 次是「格式化、过滤、抽取」这类确定性活,用 Flash 完全够。全文含可直接复制的难度分级 Prompt 模板、TypeScript 路由判定器完整实现(规则前置 + 模型打分 + 三级 fallback + 预算闸门)、五周灰度切流的逐周数据、以及 12 个踩过的坑。

2026-10-08
#AI实战#多模型路由
案例开发

83 万行 TypeScript 重写成 Rust:GitHub Copilot 运行时迁移实战复盘

GitHub 官方博客在 2026 年 9 月公布了一次极端规模的迁移:用 Copilot 智能体把 Copilot 运行时从 TypeScript/Node.js 重写为 **832,378 行 Rust**,耗时约 **14.5 周**,通过 **128 个增量 PR** 合入 main 并持续发布。这是目前公开可查的、规模最大的「AI 主导的生产级跨语言迁移」案例。本文把它拆成可复用的方法论:为什么选择增量 PR 而不是一次性重写、5 个阶段的实施路径(接口冻结 / 边界层搭建 / 分模块迁移 / 双跑验证 / 切流下线)、每阶段配套的 Prompt 模板、效果数据(P99 延迟 -62%、内存 -71%、冷启动 -88%)、以及 15 个真实踩过的坑(生命周期地狱、异步运行时不兼容、FFI 边界开销、类型系统阻抗失配等)。附一份可直接套用的迁移检查清单。

2026-09-18
#AI实战#跨语言迁移