docs/backend-migration/plans/2026-05-07-m1-monorepo-skeleton.md
给执行 agent:本计划自包含。只读本文件和下方列出的两份参考文档。 不要读其他 Mx 计划 —— 它们依赖 M1 先完成。
目标:把整个 src/ 目录迁到 packages/desktop/src/,建立 bun
workspaces,把 electron-builder.yml 和 electron.vite.config.ts 迁进
packages/desktop/,并同步更新所有硬编码 src/ 路径的配置文件 —— 保证
bun run dev / bun run webui / bun run build 一律不回退。
架构:纯结构性重构。除了因迁移必然的 import 路径变更外,不改任何运行期 逻辑。单 PR、单 revert 即可回退。
技术栈:bun workspaces、TypeScript path alias、vite / electron-vite / electron-builder / vitest。
你正在执行 9 个里程碑重构中的第 1 个(M1),目标是把 AionUi 的 WebUI 从
Electron 解耦。完整设计在
docs/backend-migration/plans/2026-05-07-webui-decouple-electron-design.md。
团队协作契约在
docs/backend-migration/plans/2026-05-07-webui-decouple-team-playbook.md。
M1 的交付物:仓库变为 bun workspaces monorepo。所有现有功能
(bun run dev / webui / build)照常工作。src/ 目录消失,内容迁到
packages/desktop/src/。electron-builder.yml 和 electron.vite.config.ts
迁入 packages/desktop/。后续里程碑(M3 起)会在这个骨架上增加
packages/web-host/ 和 packages/web-cli/。
M1 不做的事:不改任何业务逻辑;不新增 packages/desktop/ 以外的包;
暂不引入 @aionui/web-host;不清理 aionrs 遗留(那是 M2 的事)。
开始前的前置条件:
git status 干净bun install 成功bun run dev 能启动分支:基于 origin/feat/backend-migration 创建 feat/m1-monorepo-skeleton
(不是基于 main)。整个 9 里程碑重构都发生在 feat/backend-migration
这条长期分支上。
git fetch origin
git checkout -b feat/m1-monorepo-skeleton origin/feat/backend-migration
git rev-parse --abbrev-ref HEAD # 应为 feat/m1-monorepo-skeleton
PR 的 base 分支是 feat/backend-migration,不是 main。
除本计划外,只读这两份:
docs/backend-migration/plans/2026-05-07-webui-decouple-electron-design.md
—— "目标形态"、"仓库组织"、"M1 的隐蔽风险" 三节docs/backend-migration/plans/2026-05-07-webui-decouple-team-playbook.md
—— "M1 checkpoint" 一节不要读其他里程碑计划。
迁移(git mv):
src/ → packages/desktop/src/electron-builder.yml → packages/desktop/electron-builder.ymlelectron.vite.config.ts → packages/desktop/electron.vite.config.ts新建:
packages/desktop/package.json修改(12 大类配置文件):
package.json(根)tsconfig.jsonvitest.config.tsuno.config.tscodecov.yml.oxlintrc.json.pre-commit-config.yamlscripts/build-with-builder.jsscripts/postinstall.jsAGENTS.mddocs/conventions/file-structure.md.claude/skills/architecture/SKILL.md + references/process.md + references/renderer.md.github/workflows/README.md.github/workflows/gpt-review.ymltests/vitest.setup.ts 以及 20+ 个测试文件(相对路径改 alias)验证不回退(只跑,不改):
bun run devbun run webuibun run buildbun testbun run lintbunx tsc --noEmitcd /Users/zhoukai/Documents/github/AionUi
# 当前测试通过数量
bun test 2>&1 | tail -20 > /tmp/m1-baseline-test.log
# 当前 src/ 文件数
find src -type f | wc -l > /tmp/m1-baseline-file-count.txt
# 当前 package.json 脚本名单
bun run 2>&1 | head -50 > /tmp/m1-baseline-scripts.log
预期:/tmp/m1-baseline-test.log 中有测试通过数。这是基线,不要 commit。
git fetch origin
git checkout -b feat/m1-monorepo-skeleton origin/feat/backend-migration
git status
预期:干净,已切换到新分支。验证基线:
git merge-base --is-ancestor origin/feat/backend-migration HEAD && echo "base OK"
packages/ 目录骨架mkdir -p packages/desktop
src/ 迁到 packages/desktop/git mv src packages/desktop/src
预期:packages/desktop/src/process/… 等存在;根 src/ 不再存在。
electron.vite.config.tsgit mv electron.vite.config.ts packages/desktop/electron.vite.config.ts
electron-builder.ymlgit mv electron-builder.yml packages/desktop/electron-builder.yml
git add -A
git status # 验证只有迁移,没有文件内容修改
git commit -m "refactor(m1): move src/ and electron configs to packages/desktop/
Raw file moves only. Config updates in follow-up commits."
这个 commit 会让构建暂时坏掉,后续阶段依次修复。
packages/desktop/package.jsonpackages/desktop/package.json内容:
{
"name": "@aionui/desktop",
"version": "0.0.0",
"private": true,
"description": "AionUi desktop Electron application",
"main": "../../out/main/index.js"
}
main 指向 ../../out/main/index.js,因为 electron-vite 输出仍在仓库根
out/ 目录下(阶段 3 会保持这个约定)。
git add packages/desktop/package.json
git commit -m "refactor(m1): add packages/desktop/package.json"
package.json编辑根 package.json。
新增顶层字段(插入到合适位置,例如 keywords 之后):
"workspaces": [
"packages/*"
],
修改 scripts.test:bun,从:
"test:bun": "bun test src/process/services/database/drivers/*.bun.test.ts"
改为:
"test:bun": "bun test packages/desktop/src/process/services/database/drivers/*.bun.test.ts"
其余 scripts 暂不改(阶段 4 会加 --config 参数)。
bun install 能识别 workspacerm -rf node_modules bun.lock
bun install
预期:成功;bun.lock 内有 workspace 记录。
git add package.json bun.lock
git commit -m "refactor(m1): declare bun workspaces in root package.json"
scripts/build-with-builder.js 里有 4 处硬编码 electron-vite /
electron-builder 相关路径,逐一修复。
package.json scripts 中 10 个 electron-vite 调用在 package.json:12-67 的 scripts 字段里,用 Edit 工具对每一项做精确替换。
涉及的 10 个 script 和完整替换(严格按此,不要简写):
"start": "electron-vite dev"
→ "electron-vite dev --config packages/desktop/electron.vite.config.ts"
"start:multi": "cross-env AIONUI_MULTI_INSTANCE=1 electron-vite dev"
→ "cross-env AIONUI_MULTI_INSTANCE=1 electron-vite dev --config packages/desktop/electron.vite.config.ts"
"cli": "electron-vite dev"
→ "electron-vite dev --config packages/desktop/electron.vite.config.ts"
"webui": "rm -rf out/renderer && electron-vite dev -- --webui"
→ "rm -rf out/renderer && electron-vite dev --config packages/desktop/electron.vite.config.ts -- --webui"
"webui:remote": "rm -rf out/renderer && electron-vite dev -- --webui --remote"
→ "rm -rf out/renderer && electron-vite dev --config packages/desktop/electron.vite.config.ts -- --webui --remote"
"webui:prod": "cross-env NODE_ENV=production electron-vite dev -- --webui"
→ "cross-env NODE_ENV=production electron-vite dev --config packages/desktop/electron.vite.config.ts -- --webui"
"webui:prod:remote": "cross-env NODE_ENV=production electron-vite dev -- --webui --remote"
→ "cross-env NODE_ENV=production electron-vite dev --config packages/desktop/electron.vite.config.ts -- --webui --remote"
"resetpass": "electron-vite dev -- --resetpass"
→ "electron-vite dev --config packages/desktop/electron.vite.config.ts -- --resetpass"
"package": "electron-vite build"
→ "electron-vite build --config packages/desktop/electron.vite.config.ts"
"make": "electron-vite build"
→ "electron-vite build --config packages/desktop/electron.vite.config.ts"
scripts/build-with-builder.js 第 54-55 行的增量构建 hash 清单computeSourceHash 函数(L46-87)用这几个文件做增量构建缓存判断。迁移后
路径全变了,缓存判断会失效(一直触发全量重建或一直命中旧缓存)。
Edit 原文件 L49-57:
const filesToHash = [
'package.json',
'package-lock.json',
'bun.lock',
'tsconfig.json',
'electron.vite.config.ts',
'electron-builder.yml',
'justfile',
];
改为:
const filesToHash = [
'package.json',
'package-lock.json',
'bun.lock',
'tsconfig.json',
'packages/desktop/electron.vite.config.ts',
'packages/desktop/electron-builder.yml',
'justfile',
];
再改 L68:
const hashDirs = ['src', 'public', 'scripts'];
改为:
const hashDirs = ['packages/desktop/src', 'packages', 'public', 'scripts'];
(加了 packages 本身,以便将来新增的 packages/web-host/ 等子包也能
触发增量缓存失效。)
scripts/build-with-builder.js L322 的 config 读取路径getTargetArchFromConfig 函数(L320-341)读取 electron-builder.yml 解析
目标架构。Edit L322:
const configPath = path.resolve(__dirname, '../electron-builder.yml');
改为:
const configPath = path.resolve(__dirname, '../packages/desktop/electron-builder.yml');
electron-builder 命令补 --config 参数scripts/build-with-builder.js:544 的:
const builderCommand = `bunx electron-builder ${builderArgs} ${archFlag} ${nsisInclude} ${publishArg}`;
改为:
const builderCommand = `bunx electron-builder --config packages/desktop/electron-builder.yml ${builderArgs} ${archFlag} ${nsisInclude} ${publishArg}`;
同时 L226 的 DMG 重试路径:
execSync(`bunx electron-builder --mac dmg --${targetArch} --prepackaged "${appPath}" --publish=never`, {
改为:
execSync(`bunx electron-builder --config packages/desktop/electron-builder.yml --mac dmg --${targetArch} --prepackaged "${appPath}" --publish=never`, {
package.json.main 的自动覆盖scripts/build-with-builder.js:386-394 会自动把根 package.json.main
改回 ./out/main/index.js:
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
if (packageJson.main !== './out/main/index.js') {
packageJson.main = './out/main/index.js';
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2) + '\n');
}
不要改这段。M1 的约定是 electron-vite 输出仍放仓库根 out/,所以根
package.json.main 保持 ./out/main/index.js 是正确的;
packages/desktop/package.json.main 单独指向 ../../out/main/index.js
(阶段 2 已完成)。两者并存不冲突。
git add package.json scripts/build-with-builder.js
git commit -m "refactor(m1): point build scripts at packages/desktop/ configs"
packages/desktop/electron.vite.config.ts文件在阶段 1 已迁移。resolve(...) 是从 electron-vite import 的 path
模块别名(L3),相对 process.cwd() 解析。因为构建命令从仓库根执行
(bun run dev 等),所以 resolve('src/...') 会解析到仓库根的 src/,
迁移后这个目录不存在了,必须改成 packages/desktop/src/...。
共有 12 处 src/ 引用需要改,另外 1 处 Sentry 的正则也要改。
mainAliases(L48-55)const mainAliases = {
'@': resolve('src'),
'@common': resolve('src/common'),
'@renderer': resolve('src/renderer'),
'@process': resolve('src/process'),
'@worker': resolve('src/process/worker'),
'@xterm/headless': resolve('src/common/utils/shims/xterm-headless.ts'),
};
改为:
const mainAliases = {
'@': resolve('packages/desktop/src'),
'@common': resolve('packages/desktop/src/common'),
'@renderer': resolve('packages/desktop/src/renderer'),
'@process': resolve('packages/desktop/src/process'),
'@worker': resolve('packages/desktop/src/process/worker'),
'@xterm/headless': resolve('packages/desktop/src/common/utils/shims/xterm-headless.ts'),
};
rewriteSources 正则(L66-71) rewriteSources: (source: string) => {
// Normalize Windows backslashes and strip leading relative prefixes
// so Sentry paths match the GitHub repo structure (e.g. src/process/...)
return source.replace(/\\/g, '/').replace(/^(\.\.\/)+(src\/)/, '$2');
},
改为:
rewriteSources: (source: string) => {
// Normalize Windows backslashes and strip leading relative prefixes
// so Sentry paths match the GitHub repo structure (e.g.
// packages/desktop/src/process/...)
return source.replace(/\\/g, '/').replace(/^(\.\.\/)+(packages\/desktop\/src\/)/, '$2');
},
{ src: 'src/renderer/assets/logos/*', dest: 'static/images' },
改为:
{ src: 'packages/desktop/src/renderer/assets/logos/*', dest: 'static/images' },
index: resolve('src/index.ts'),
改为:
index: resolve('packages/desktop/src/index.ts'),
L142:
alias: { '@': resolve('src'), '@common': resolve('src/common') },
改为:
alias: { '@': resolve('packages/desktop/src'), '@common': resolve('packages/desktop/src/common') },
L150-153:
index: resolve('src/preload/main.ts'),
petPreload: resolve('src/preload/petPreload.ts'),
petHitPreload: resolve('src/preload/petHitPreload.ts'),
petConfirmPreload: resolve('src/preload/petConfirmPreload.ts'),
改为:
index: resolve('packages/desktop/src/preload/main.ts'),
petPreload: resolve('packages/desktop/src/preload/petPreload.ts'),
petHitPreload: resolve('packages/desktop/src/preload/petHitPreload.ts'),
petConfirmPreload: resolve('packages/desktop/src/preload/petConfirmPreload.ts'),
alias: {
'@': resolve('src'),
'@common': resolve('src/common'),
'@renderer': resolve('src/renderer'),
'@process': resolve('src/process'),
'@worker': resolve('src/process/worker'),
// Force ESM version of streamdown
streamdown: resolve('node_modules/streamdown/dist/index.js'),
},
改为:
alias: {
'@': resolve('packages/desktop/src'),
'@common': resolve('packages/desktop/src/common'),
'@renderer': resolve('packages/desktop/src/renderer'),
'@process': resolve('packages/desktop/src/process'),
'@worker': resolve('packages/desktop/src/process/worker'),
// Force ESM version of streamdown
streamdown: resolve('node_modules/streamdown/dist/index.js'),
},
index: resolve('src/renderer/index.html'),
pet: resolve('src/renderer/pet/pet.html'),
'pet-hit': resolve('src/renderer/pet/pet-hit.html'),
'pet-confirm': resolve('src/renderer/pet/pet-confirm.html'),
改为:
index: resolve('packages/desktop/src/renderer/index.html'),
pet: resolve('packages/desktop/src/renderer/pet/pet.html'),
'pet-hit': resolve('packages/desktop/src/renderer/pet/pet-hit.html'),
'pet-confirm': resolve('packages/desktop/src/renderer/pet/pet-confirm.html'),
# 先 tsc 快速扫一下,只看语法性错误
bunx tsc --noEmit packages/desktop/electron.vite.config.ts 2>&1 | head -20
# dev 启动冒烟
timeout 20s bun run dev > /tmp/m1-phase5-dev.log 2>&1 &
DEV_PID=$!
sleep 18
grep -qE "(built in|ready in|DevTools)" /tmp/m1-phase5-dev.log && echo "DEV_READY" || (echo "DEV_NOT_READY"; cat /tmp/m1-phase5-dev.log)
kill -TERM $DEV_PID 2>/dev/null || true
wait $DEV_PID 2>/dev/null || true
# webui 启动冒烟
timeout 25s bun run webui > /tmp/m1-phase5-webui.log 2>&1 &
WEBUI_PID=$!
sleep 20
PORT=$(grep -oE "http://(127.0.0.1|localhost):[0-9]+" /tmp/m1-phase5-webui.log | head -1 | grep -oE "[0-9]+$")
echo "WEBUI_PORT=$PORT"
[ -n "$PORT" ] && curl -fsS -o /dev/null -w "HTTP_STATUS=%{http_code}\n" "http://127.0.0.1:$PORT/" || echo "no port parsed"
kill -TERM $WEBUI_PID 2>/dev/null || true
wait $WEBUI_PID 2>/dev/null || true
预期:DEV_READY、WEBUI_PORT 非空、HTTP_STATUS=200。
如果 electron-vite dev 报"Cannot find entry: src/index.ts",说明某处
遗漏改了;grep -n "'src/" packages/desktop/electron.vite.config.ts
应无输出(注意带单引号,排除注释里的 src/process/... 这种描述)。
git add packages/desktop/electron.vite.config.ts
git commit -m "refactor(m1): update all 12 src/ path references in electron.vite.config.ts"
packages/desktop/electron-builder.ymlelectron-builder 的路径有两种解析规则:
directories / extraResources[].from / afterPack / afterSign / icon 等
相对 yml 本身所在目录解析,迁到 packages/desktop/ 后需要 ../../ 前缀files[] glob 默认相对 appDir(通过 directories.app 控制),也需要
../../ 前缀本阶段改动多但规则统一:凡是指向仓库根或 out/ / node_modules/ 的路径,
前面都加 ../../。
directories.output,新增 directories.app原 L14-16:
directories:
output: out
buildResources: resources
改为:
directories:
app: ../..
output: ../../out
buildResources: ../../resources
directories.app: ../.. 告诉 electron-builder 应用根目录是仓库根
(package.json 在那、out/ 在那、node_modules/ 在那)。这样 files[]
里的 out/** 和 node_modules/** 都能直接命中,不用加前缀。
注意:因为设了 directories.app: ../..,阶段 6 里所有 files[] 相关
路径都不需要加 ../../ 前缀(appDir 已经指向仓库根)。需要加前缀的是
extraResources[].from / afterPack / afterSign / icon 这些路径。
files[] 保持不变由于上一步设了 directories.app: ../..,files[] 里的 out/main/** /
node_modules/better-sqlite3/** 等都继续相对仓库根解析,L18-99 整段
不需要修改。
但是补充防御性排除,阻止未来的 web-host / web-cli 被扫进 asar。在
L66('!**/node_modules/*.d.ts' 之前)新增:
# Defensive: exclude sibling packages from desktop bundle
- '!packages/web-host/**'
- '!packages/web-cli/**'
- '!packages/shared-scripts/**'
- '!packages/desktop/src/**'
(最后一行 !packages/desktop/src/** 是因为源码已经被 electron-vite
编译到 out/ 里,asar 不需要再装一份源码。)
extraResources[] 全部加 ../../ 前缀原 L101-115:
extraResources:
- from: public
to: .
- from: resources/app.png
to: app.png
- from: resources/bundled-bun
to: bundled-bun
# aionrs binary (pre-compiled per platform/arch)
- from: resources/bundled-aionrs
to: bundled-aionrs
# aionui-backend binary (pre-compiled per platform/arch)
- from: resources/bundled-aionui-backend
to: bundled-aionui-backend
- from: resources/hub
to: hub
改为:
extraResources:
- from: ../../public
to: .
- from: ../../resources/app.png
to: app.png
- from: ../../resources/bundled-bun
to: bundled-bun
# aionrs binary (pre-compiled per platform/arch)
- from: ../../resources/bundled-aionrs
to: bundled-aionrs
# aionui-backend binary (pre-compiled per platform/arch)
- from: ../../resources/bundled-aionui-backend
to: bundled-aionui-backend
- from: ../../resources/hub
to: hub
bundled-aionrs 留在 M1,M2 会整条删除。
win.icon 加前缀(L121)icon: resources/app.ico # Use the checked-in Windows icon resource for executable metadata/icon patching
改为:
icon: ../../resources/app.ico # Use the checked-in Windows icon resource for executable metadata/icon patching
mac.icon 和 entitlements 加前缀(L138、L143-144)icon: resources/app.icns
→
icon: ../../resources/app.icns
entitlements: entitlements.plist
entitlementsInherit: entitlements.plist
→
entitlements: ../../entitlements.plist
entitlementsInherit: ../../entitlements.plist
afterPack / afterSign 加前缀(L165-166)afterPack: scripts/afterPack.js
afterSign: scripts/afterSign.js
改为:
afterPack: ../../scripts/afterPack.js
afterSign: ../../scripts/afterSign.js
linux.icon 加前缀(L171)icon: resources/app.png
→
icon: ../../resources/app.png
git add packages/desktop/electron-builder.yml
git commit -m "refactor(m1): update all electron-builder paths for packages/desktop/ location"
阶段 7-9 修完剩余配置后才跑 bun run build,因为 build 流程内部还会跑
tsc / oxlint。
快速验证 electron-builder 能读懂新路径,不实际打包:
# 干跑 electron-builder,只解析 config,不执行打包
bunx electron-builder --config packages/desktop/electron-builder.yml --help 2>&1 | head -5
预期:命令输出 help 信息,无 "cannot resolve" / "file not found" 之类错误。
如果想验证 directories.app: ../.. 解析正确,检查它能否找到根 package.json:
# 先检查引用路径都存在
ls -la entitlements.plist resources/app.icns resources/app.png scripts/afterPack.js scripts/afterSign.js 2>&1 | head
应全部显示文件存在。
tsconfig.jsonpaths把:
"paths": {
"@/*": ["./src/*"],
"@process/*": ["./src/process/*"],
"@renderer/*": ["./src/renderer/*"],
"@worker/*": ["./src/process/worker/*"]
}
改为:
"paths": {
"@/*": ["./packages/desktop/src/*"],
"@process/*": ["./packages/desktop/src/process/*"],
"@renderer/*": ["./packages/desktop/src/renderer/*"],
"@worker/*": ["./packages/desktop/src/process/worker/*"]
}
include"include": [
"packages/desktop/src/**/*",
"uno.config.ts",
"packages/desktop/electron.vite.config.ts",
"playwright.config.ts",
"packages/desktop/src/renderer/types.d.ts"
]
exclude"exclude": [
"packages/desktop/src/process/services/database/drivers/BunSqliteDriver.ts",
"packages/desktop/src/process/services/database/drivers/BunSqliteDriver.bun.test.ts"
]
bunx tsc --noEmit 2>&1 | tee /tmp/m1-phase7-tsc.log | tail -30
预期:退出码 0。若有 Cannot find module '../../src/...' 类错误,是测试
文件的相对路径问题,阶段 10 会统一修复,暂时可接受。
git add tsconfig.json
git commit -m "refactor(m1): update tsconfig paths for packages/desktop/"
vitest.config.tsconst aliases = {
'@/': path.resolve(__dirname, './packages/desktop/src') + '/',
'@process/': path.resolve(__dirname, './packages/desktop/src/process') + '/',
'@renderer/': path.resolve(__dirname, './packages/desktop/src/renderer') + '/',
'@worker/': path.resolve(__dirname, './packages/desktop/src/process/worker') + '/',
'@mcp/models/': path.resolve(__dirname, './packages/desktop/src/common/models') + '/',
'@mcp/types/': path.resolve(__dirname, './packages/desktop/src/common') + '/',
'@mcp/': path.resolve(__dirname, './packages/desktop/src/common') + '/',
};
include:
include: ['packages/desktop/src/**/*.{ts,tsx}', 'packages/**/src/**/*.{ts,tsx}', 'scripts/prepareBundledBun.js'],
exclude:
exclude: [
'packages/**/src/**/*.d.ts',
'packages/desktop/src/index.ts',
'packages/desktop/src/preload.ts',
'packages/desktop/src/common/utils/shims/**',
'packages/desktop/src/common/types/**',
'packages/desktop/src/renderer/**/*.json',
'packages/desktop/src/renderer/**/*.svg',
'packages/desktop/src/renderer/**/*.css',
'packages/desktop/src/common/config/i18n-config.json',
],
bun test 2>&1 | tee /tmp/m1-phase8-test.log | tail -20
预期:通过数与 /tmp/m1-baseline-test.log 一致。
如果有 Cannot resolve '../../src/...' 这种失败,都是测试里硬编码相对路径
的问题,阶段 10 会统一修复。
git add vitest.config.ts
git commit -m "refactor(m1): update vitest aliases and coverage globs"
uno.config.tsgrep -n "src/" uno.config.ts
把 content 扫描 glob 从 src/**/*.tsx 改成 packages/desktop/src/**/*.tsx。
git add uno.config.ts
git commit -m "refactor(m1): point unocss content scan at packages/desktop/src"
codecov.yml把 ignore: 段的所有 src/ 替换为 packages/desktop/src/。
grep -n "src/" codecov.yml
预期:除 packages/desktop/src/ 外无其他 src/ 引用。
git add codecov.yml
git commit -m "refactor(m1): update codecov ignore paths for packages/desktop"
.oxlintrc.jsongrep -n "src/" .oxlintrc.json
把引用到 src/agent/gemini/cli/ 之类旧路径的 ignore 项改为
packages/desktop/src/agent/gemini/cli/。如果该目录在 packages/desktop/src/
下也不存在(表示已失效的陈旧引用),直接删除该条。
git add .oxlintrc.json
git commit -m "refactor(m1): update oxlint ignore paths"
.pre-commit-config.yaml把 files: ^src/renderer/services/i18n/locales/(约 L79)改为:
files: ^packages/desktop/src/renderer/services/i18n/locales/
git add .pre-commit-config.yaml
git commit -m "refactor(m1): update pre-commit hook file patterns"
scripts/postinstall.jsgrep -n "src/" scripts/postinstall.js
若有引用就修,大概率没有。若有,commit:
git add scripts/postinstall.js
git commit -m "refactor(m1): update postinstall paths"
../../src/ importsrc/ import 的测试文件grep -rln "from '\.\./\.\./src/\|from '\.\./src/\|from '\.\./\.\./\.\./src/\|vi.mock('\.\./\.\./src/" tests/
约有 20+ 个文件。
⚠️ 下面的 sed -i '' 是 macOS 语法,Linux 环境用 sed -i:
# ../../src/process/... → @process/...
grep -rln "from '\.\./\.\./src/process/" tests/ | xargs -I{} sh -c "sed -i '' \"s|from '../../src/process/|from '@process/|g\" {}"
# ../../src/renderer/... → @renderer/...
grep -rln "from '\.\./\.\./src/renderer/" tests/ | xargs -I{} sh -c "sed -i '' \"s|from '../../src/renderer/|from '@renderer/|g\" {}"
# ../../src/common/... → @mcp/...(跟 vitest alias 一致)
grep -rln "from '\.\./\.\./src/common/" tests/ | xargs -I{} sh -c "sed -i '' \"s|from '../../src/common/|from '@mcp/|g\" {}"
# ../../src/worker/... → @worker/...
grep -rln "from '\.\./\.\./src/worker/" tests/ | xargs -I{} sh -c "sed -i '' \"s|from '../../src/worker/|from '@worker/|g\" {}"
# 剩下的 catch-all:../../src/… → @/…
grep -rln "from '\.\./\.\./src/" tests/ | xargs -I{} sh -c "sed -i '' \"s|from '../../src/|from '@/|g\" {}"
# vi.mock 同样处理
grep -rln "vi.mock('\.\./\.\./src/process/" tests/ | xargs -I{} sh -c "sed -i '' \"s|vi.mock('../../src/process/|vi.mock('@process/|g\" {}"
grep -rln "vi.mock('\.\./\.\./src/renderer/" tests/ | xargs -I{} sh -c "sed -i '' \"s|vi.mock('../../src/renderer/|vi.mock('@renderer/|g\" {}"
grep -rln "vi.mock('\.\./\.\./src/common/" tests/ | xargs -I{} sh -c "sed -i '' \"s|vi.mock('../../src/common/|vi.mock('@mcp/|g\" {}"
grep -rln "vi.mock('\.\./\.\./src/worker/" tests/ | xargs -I{} sh -c "sed -i '' \"s|vi.mock('../../src/worker/|vi.mock('@worker/|g\" {}"
grep -rln "vi.mock('\.\./\.\./src/" tests/ | xargs -I{} sh -c "sed -i '' \"s|vi.mock('../../src/|vi.mock('@/|g\" {}"
grep -rln "from '\.\./\.\./src/\|vi.mock('\.\./\.\./src/\|from '\.\./src/" tests/
预期:无输出。
bun test 2>&1 | tee /tmp/m1-phase10-test.log | tail -20
预期:通过数与基线一致,无 Cannot resolve 错误。
git add tests/
git commit -m "refactor(m1): replace relative src/ imports with aliases in tests"
这些文档不影响 build/test,但对后续 AI agent 找对路径至关重要。
AGENTS.md把所有 src/process/ / src/renderer/ / src/common/ 改成
packages/desktop/src/process/ 等。
grep -n "src/" AGENTS.md
预期:只剩 packages/desktop/src/ 引用。
git add AGENTS.md
git commit -m "docs(m1): update AGENTS.md paths for monorepo layout"
docs/conventions/file-structure.md在文档顶部加一节:
## Monorepo 布局(M1 之后)
本项目采用 bun workspaces monorepo 结构:
- `packages/desktop/` —— Electron 桌面应用(原根 `src/`)
- `packages/web-host/` —— (M3 添加)共享的 WebUI 核心
- `packages/web-cli/` —— (M8 添加)独立的 Node CLI
以下所有路径都以 `packages/desktop/src/` 作为桌面包根;之前的 `src/` 前缀
已废弃。
然后把正文里所有 src/process/ / src/renderer/ / src/common/ 改成
packages/desktop/src/...。
git add docs/conventions/file-structure.md
git commit -m "docs(m1): update file-structure doc for monorepo layout"
.claude/skills/architecture/替换以下三个文件里的 src/process/ / src/renderer/ / src/common/ /
src/process/worker/ 为 packages/desktop/src/...:
.claude/skills/architecture/SKILL.md.claude/skills/architecture/references/process.md.claude/skills/architecture/references/renderer.mdgrep -rn "src/" .claude/skills/architecture/
预期:只剩 packages/desktop/src/ 引用。
git add .claude/skills/architecture/
git commit -m "docs(m1): update architecture skill for monorepo layout"
.github/workflows/gpt-review.yml 和 README.mdgrep -n "src/" .github/workflows/gpt-review.yml .github/workflows/README.md
把引用改成 packages/desktop/src/...。
git add .github/workflows/
git commit -m "docs(m1): update workflow docs for monorepo layout"
rm -rf node_modules bun.lock out dist
bun install
预期:成功,workspace 符号链接建立。
bunx tsc --noEmit 2>&1 | tee /tmp/m1-final-tsc.log | tail -20
预期:退出码 0,无错误。
bun run lint 2>&1 | tee /tmp/m1-final-lint.log | tail -20
预期:退出码 0。
bun test 2>&1 | tee /tmp/m1-final-test.log | tail -20
预期:通过数与 /tmp/m1-baseline-test.log 一致。
timeout 20s bun run dev > /tmp/m1-final-dev.log 2>&1 &
DEV_PID=$!
sleep 18
ps -p $DEV_PID > /dev/null && echo "DEV_RUNNING" || echo "DEV_DEAD"
grep -qE "(built in|ready in|DevTools)" /tmp/m1-final-dev.log && echo "DEV_READY" || echo "DEV_NOT_READY"
kill -TERM $DEV_PID 2>/dev/null || true
wait $DEV_PID 2>/dev/null || true
预期:打印 DEV_RUNNING 和 DEV_READY。
timeout 25s bun run webui > /tmp/m1-final-webui.log 2>&1 &
WEBUI_PID=$!
sleep 20
PORT=$(grep -oE "http://(127.0.0.1|localhost):[0-9]+" /tmp/m1-final-webui.log | head -1 | grep -oE "[0-9]+$")
echo "WEBUI_PORT=$PORT"
[ -n "$PORT" ] && curl -fsS -o /dev/null -w "HTTP_STATUS=%{http_code}\n" "http://127.0.0.1:$PORT/" || echo "no port parsed"
kill -TERM $WEBUI_PID 2>/dev/null || true
wait $WEBUI_PID 2>/dev/null || true
预期:WEBUI_PORT 非空,HTTP_STATUS=200。
bun run build-mac:arm64
(或本机对应平台命令。)
预期:退出码 0;ls dist/*.dmg 至少一个文件。
# 找到实际 app.asar 路径
APP_ASAR=$(find dist -name "app.asar" -type f | head -1)
echo "APP_ASAR=$APP_ASAR"
# 检查没有 web-cli / web-host 残留(当前还没这些包,值应为 0)
bunx @electron/asar list "$APP_ASAR" | grep -cE "packages/(web-cli|web-host)"
预期:APP_ASAR 非空,grep count 为 0。
prek run --from-ref origin/feat/backend-migration --to-ref HEAD 2>&1 | tail -30
预期:全部通过。
按 playbook 的模板创建 docs/backend-migration/handoffs/M1-outcome.md,
字数控制在 500 字以内,内容包括:
packages/desktop/packages/desktop/electron-builder.yml,不是已删除的根目录那个)本方案不创建 PR。teammate 只把 feature 分支 push 到 origin,让下游 teammate 能基于此分支起步;PR 由人类在整条 9 里程碑链完成后统一处理。
在 push 之前,必须先把 origin/feat/backend-migration 合入本分支,
避免分支偏离基准太远。见 playbook 的"基线同步规范"。
# 拉最新基线
git fetch origin feat/backend-migration
# 查看基线是否有新 commit
git log --oneline HEAD..origin/feat/backend-migration | head -10
git merge origin/feat/backend-migration --no-ff \
-m "chore(m1): sync with feat/backend-migration"
禁止用 rebase,必须用 merge(保留 commit 历史,便于回滚和下游
teammate 拉分支时的 SHA 稳定性)。
冲突处理:
无冲突 → git status 看到 "All conflicts fixed" → 继续
有冲突且简单(不同文件或同文件不同段落)→ 手动解决,git add 后
git commit(commit message 沿用 merge 自动生成的)
冲突复杂(同一段代码两方都改)→ 不要硬改,直接进入步骤 13.4 向 team-lead escalate,本 teammate 终止
步骤 13.2:合入后重跑自动化验证
合入基线后代码变了,必须重跑阶段 12 的核心验证:
bunx tsc --noEmit 2>&1 | tail -10
bun run lint 2>&1 | tail -10
bun test 2>&1 | tail -10
# 关键冒烟(阶段 12.5 / 12.6 / 12.7)
预期:全部 PASS。
如果失败:
是基线引入的破坏性变更 → 不要尝试修,进入步骤 13.4 escalate
是本里程碑和基线的隐性冲突(文件无冲突但语义冲突)→ 同样 escalate
步骤 13.3:push feature 分支到 origin
git push -u origin feat/m1-monorepo-skeleton
git rev-parse HEAD > /tmp/m1-final-sha.txt
cat /tmp/m1-final-sha.txt
预期:push 成功,git branch -vv 显示 tracking
origin/feat/m1-monorepo-skeleton;SHA 保存到 /tmp/m1-final-sha.txt。
这个 SHA 会写进 handoff,作为 M2 teammate 的起点。
如果作为 team teammate 运行,用 SendMessage 工具向主会话报告。
正常完成:
SendMessage({
to: "team-lead",
message: "M1 完成。
- 分支:feat/m1-monorepo-skeleton
- SHA:<从 /tmp/m1-final-sha.txt 读>
- 基线同步:origin/feat/backend-migration @ <基线 SHA> 已合入
- Handoff:docs/backend-migration/handoffs/M1-outcome.md
- 偏离计划:<无 / 列出>
请启动 M2。"
})
合入基线失败或验证失败(escalate):
SendMessage({
to: "team-lead",
message: "M1 完成实施但基线同步/验证失败,需要人类决策。
- 分支:feat/m1-monorepo-skeleton(本地,尚未 push)
- 当前 SHA:<git rev-parse HEAD>
- 问题:<合并冲突文件 / 验证失败原因>
- 已尝试:<列出具体尝试>
- Handoff 已写,详情见 docs/backend-migration/handoffs/M1-outcome.md 的
Deviations 节。
请决定如何处理。"
})
如果是独立执行(非 team 模式),直接在会话末尾打印上述信息即可。
不要做:
gh pr create 禁用)feat/backend-migrationfeat/m1-monorepo-skeleton(M2 会基于它)如果本里程碑完成后才发现问题:
git reset --hard origin/feat/backend-migration 重来git push origin --delete feat/m1-monorepo-skeleton
git pull 拿到修复electron-vite dev 模式可能不支持 --config 标志
— 补救:在仓库根创建一个桩 electron.vite.config.ts re-export
packages/desktop/electron.vite.config.ts,仅在 --config 失效时启用。
Sentry source maps 注释(原 electron.vite.config.ts L69)
— 注释里写 src/process/...,改为 packages/desktop/src/process/...,
保证 Sentry 上报的路径能在 GitHub 上定位。
bun test vs bunx vitest
— test:bun 脚本用的是 bun 自带 test runner,不是 vitest。本计划阶段 3
更新的路径只涉及此脚本对应的 .bun.test.ts 文件。
husky / .husky/_/ 自动生成
— bun install 的 postinstall 会跑 husky,出问题时重跑 bun install,
不要手工改 .husky/_/ 目录。
@electron/asar 工具
— 步骤 12.8 用 bunx @electron/asar list <path>。运行前先验证工具可用:
bunx @electron/asar --version。
文档替换时不要误改本方案自身
— 设计文档、playbook、本 M1 计划里本来就引用 src/ 描述迁移前的状态。
步骤 11 的 grep-and-replace 必须精确到目标文件,不要批量对整个
docs/backend-migration/ 目录操作。