每日 AI 实战案例

AI 辅助大型重构:把 180 个文件的 CommonJS 迁到 ESM

2026-08-26
高级开发
3 天
使用工具:
Muse CodeMemory MCPClaude Code
AI实战重构ESMMuse Code长任务Agent

案例摘要

把一个 180 个文件、跑了 4 年的 Node.js 后端从 CommonJS 迁到 ESM,传统人工迁移预估 3-4 人周。我们用 Muse Code 的 /plan + /grill + /goal 三件套 + Memory MCP 记忆项目约定 + 检查点协议 Prompt 保证可恢复,把整个迁移压到 3 天。本文复盘工具组合、关键 Prompt、踩过的坑(Agent 改坏循环依赖 / 动态 require 漏改)和效果数据,给同类大型重构一套可复用的 Agent 工作流。

案例背景

我们的后端是一个跑了 4 年的 Node.js 单体,180 个 .js 文件,纯 CommonJS(require / module.exports),依赖 230 个 npm 包。随着 Node 22 把 ESM 作为推荐模块系统、越来越多新包只发 ESM 版本(如 nanoid v5、execa v8),继续留在 CJS 已经在拖后腿:

  • 新版依赖装不上(nanoid v5 是 pure ESM,CJS 无法 require)
  • Tree-shaking 失效,冷启动加载 280 个文件
  • 调试时 source map 在 CJS 下表现差

人工迁移的预估是 3-4 人周:改 180 个文件的 import/export、处理 __dirname / __filename / require 动态加载、改 jest 配置、修 230 个依赖里的 CJS-ESM 互操作。我们决定用 Muse Code(刚发布的长任务编码 Agent)+ Memory MCP 来压这个工期。

工具组合

工具角色为什么选它
Muse Code主执行 Agentappend-only 事件日志保证长任务可恢复;/plan+/grill+/goal 三件套适合大重构
Memory MCP跨 session 记忆存项目约定(哪些模块禁止动、__dirname 用什么替代),避免每个 session 复述
Claude Code二次审核Muse Code 跑完后用 Claude Code 独立审一遍 diff,交叉验证
检查点协议 Prompt轨迹可回放让 Agent 每步写 checkpoint,失败可从最近 checkpoint 恢复

实施步骤

第 1 步:Memory MCP 沉淀项目约定(半小时)

迁移前先把"铁律"存进 Memory,避免 Agent 跑偏:

claude
> 记住这个迁移项目的约定:
> 1. src/legacy/ 目录禁止改动,它是历史遗留,保留 require
> 2. __dirname 用 fileURLToPath(import.meta.url) 替代
> 3. 动态 require 用 createRequire(import.meta.url) 替代
> 4. jest.config.js 改成 jest.config.mjs
> 5. package.json 加 "type": "module" 放在最后一步,否则中途全崩

Memory MCP 存了 5 个约定实体。后续每个 Muse Code session 启动都 search_memories 召回,写代码时自动遵守。

第 2 步:Muse Code 生成迁移 plan(1 小时)

muse
> /plan 把整个项目从 CommonJS 迁移到 ESM,分 5 个阶段:
> 阶段1: 扫描所有 require/module.exports,列影响清单
> 阶段2: 处理 __dirname/__filename/动态 require 的替代
> 阶段3: 逐文件改 import/export(按依赖顺序,utils 层先改)
> 阶段4: 改 jest 配置和 babel 配置
> 阶段5: 加 "type":"module" 并跑全量测试验证
> 每阶段生成独立 commit,可单独回滚

Muse Code 生成 plan 文件,列出 180 个文件的影响清单和 5 阶段顺序。关键是每阶段独立 commit——这是回滚的基础。

第 3 步:/grill 压力测试 plan(半小时)

> /grill .muse/plans/esm-migration.md

/grill 挑出了 3 个 plan 漏掉的边界:

  1. 循环依赖:userRepo.js 和 orderRepo.js 互相 require,改成 ESM import 后循环依赖会变成 undefined(ESM 的 live binding 在循环时还未初始化)
  2. 动态 require 路径:configLoader.js 里有 require(path.join(__dirname, env + '.js')),createRequire 替代后路径解析行为不同
  3. 第三方包互操作:230 个包里有 12 个是 pure ESM,从 CJS require 它们会报错,迁移中途(还没加 type:module 时)需要用 dynamic import 兜底

这三个点被 /grill 挑出来后,我们让 Muse Code 修订 plan,给每个边界加专项处理步骤。

第 4 步:/goal 执行迁移(2 天)

修订后的 plan 喂给 /goal 长跑:

> /goal 按 .muse/plans/esm-migration-v2.md 执行,开 subagent worktree 隔离,
> 每阶段完成跑一次 npm test,失败就从该阶段开头重试

Muse Code 用了 4 个并发 subagent(每个跑在一个 git worktree),分头改 utils 层、api 层、config 层、test 层。每步写 checkpoint,我们用检查点协议的轨迹回放器监控进度。

中途遇到一次 OOM(subagent 把整个 node_modules 塞进 context 了),muse resume 从最近 checkpoint 恢复,config.toml 把 max_tokens 从 500K 降到 200K 后稳定跑完。

关键 Prompt 复盘

循环依赖处理 Prompt

/grill 挑出循环依赖后,我们用这个 Prompt 让 Agent 专项处理:

你是 ESM 迁移专家。src/repo/userRepo.js 和 src/repo/orderRepo.js 存在循环依赖:
- userRepo.js require orderRepo.js(查用户订单)
- orderRepo.js require userRepo.js(查订单所属用户)

CJS 下 require 缓存能容忍循环,ESM 的 live binding 在循环时返回 undefined。

## 处理要求
1. 不要改业务逻辑
2. 把循环依赖打破:把共同依赖的 userId→user 解析抽到 src/repo/userRef.js,两个 repo 都依赖它
3. 改完后用 npm test 验证 user/order 相关用例全过

## 输出
- 改动文件清单
- 每个文件的 import 前后对比
- 测试结果

Agent 把 userRef.js 抽出来后,循环依赖打破,ESM live binding 问题解决。

动态 require 替代 Prompt

src/config/configLoader.js 里有:
  const config = require(path.join(__dirname, process.env.NODE_ENV + '.js'))

改成 ESM 时用 createRequire:
  import { createRequire } from 'module';
  const require = createRequire(import.meta.url);
  const config = require(path.join(new URL('.', import.meta.url).pathname, process.env.NODE_ENV + '.js'));

但 createRequire 的路径解析和 __dirname 行略有差异(Windows 下 path.join 和 URL.pathname 分隔符不同)。

## 处理要求
1. 跨平台验证:macOS/Linux/Windows 都跑通
2. 给出三种方案的 diff:createRequire / 动态 import() / 静态 import 映射表
3. 推荐其中一种并说明理由

Agent 最终选了静态 import 映射表(const configs = { dev: () => import('./dev.js'), prod: () => import('./prod.js') }),因为静态映射可被 bundler 分析,且跨平台无路径解析差异。

效果数据

指标人工预估Agent 实际节省
工期3-4 人周3 天(含 0.5 天人工审核)~85%
改动文件180187(多 7 个是抽公共模块打破循环依赖)-
测试通过率-阶段 1-4 全过,阶段 5 加 type:module 后 2 个用例失败(已修)98.9%
Token 成本-Muse Code Standard 档,$1.25/MTok,总 3.2M token = $4-
回滚次数-1 次(循环依赖那步,从 checkpoint 恢复)-

踩过的坑

  1. subagent 改同一文件:第一次跑没开 worktree = true,两个 subagent 同时改 package.json 互相覆盖。开启 worktree 隔离后解决。详见 Muse Code 接入教程 的故障排查表。
  2. Agent 把 node_modules 塞进 context:Muse Code 默认会把项目根目录递归塞 context,node_modules 直接撑爆 1M token。config.toml 加 [context] exclude = ["node_modules", ".git", "dist"] 解决。
  3. 记忆污染:Agent 把临时讨论的"可能要加 logging 模块"当成永久决策存进 Memory。后来 read_graph 发现后 delete_entities 清掉。重要决策存 Memory 前最好人工确认。

经验总结

  1. plan → grill → goal 是大重构标配:/plan 出方案,/grill 挑边界,/goal 长跑。跳过 /grill 直接 /goal,边界问题会在执行中爆雷,回滚成本更高。
  2. 每阶段独立 commit:迁移类任务最大的风险是"改到一半崩了无法回退"。每阶段独立 commit + checkpoint,出问题能精确回退到某个阶段,不用整体回滚。
  3. Memory MCP 先行:迁移前先把项目铁律存进 Memory,比每个 session 复述约定高效得多。Agent 自动 recall 比人工提醒更可靠。
  4. 交叉审核:Muse Code 跑完后用 Claude Code 独立审 diff,两个不同模型交叉验证,比单模型自审更能发现盲点。
  5. 控 context 成本:长任务最大的成本是 token,排除 node_modules/dist/.git 这类无关目录能把 context 从 500K 压到 200K,省 60% token 费用。

可复用的工作流

这套工作流不只适用于 ESM 迁移,任何"多文件批量改动 + 有明确阶段划分 + 需要可回滚"的重构都能套用:

  • 框架升级(React 17→18、Vue 2→3、Express→Fastify)
  • 类型化迁移(JS→TS 流式加类型注解)
  • 依赖统一(moment→dayjs、axios→fetch、jest→vitest)
  • 批量重构(命名规范统一、错误处理模式统一)

核心三件套不变:Muse Code 的 /plan+/grill+/goal + Memory MCP 记忆约定 + 检查点协议保证可恢复。换的是 plan 内容和每阶段的验证脚本。

相关推荐

查看更多 →
案例企业应用

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实战#代码评审
案例企业应用

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

把 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 的天敌,没有类型信息才是。

2026-10-10
#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实战#多模型路由