首页
/ MindsHub(Minds Platform)多模块开发工作流实战:子模块钉选、dev.env 分支选择与 Make 目标全解

MindsHub(Minds Platform)多模块开发工作流实战:子模块钉选、dev.env 分支选择与 Make 目标全解

2026-09-05 18:42:52作者:戚魁泉Nursing

本篇基于仓库根目录的 CLAUDE.md 展开,系统讲解 MindsHub 平台超项目(superproject)的子模块管理模型、多开发者模块分支协作流程,以及 make 各目标的实际行为与源码级实现。读完你可以独立完成从源码克隆、模块分支切换、本地 Web 模式运行到环境重置的完整开发链路。

项目结构:四个子模块组成的平台超项目

MindsHub 的当前仓库是平台超项目:它本身不存放业务代码,而是通过 Git 子模块把桌面/Web 应用、Agent 后端与数据引擎拼装在一起,让开发者可以从源码构建并运行整条技术栈(README.md)。

CLAUDE.md 中定义的子模块结构如下,且每一条都能在 .gitmodules 中得到印证:

路径 对应仓库 跟踪方式
frontend mindsdb/cowork tag 钉选(tag-pinned)
backend/core_api mindsdb/cowork-server tag 钉选(tag-pinned)
backend/core_agent mindsdb/anton tag 钉选(tag-pinned)
backend/data-vault mindsdb/data-vault main 分支

.gitmodules 中还有一条对日常开发影响重大的配置——每个子模块都设置了 ignore = all

[submodule "frontend"]
	path = frontend
	url = https://github.com/mindsdb/cowork
	ignore = all
[submodule "backend/core_api"]
	path = backend/core_api
	url = https://github.com/mindsdb/cowork-server
	ignore = all
# backend/core_agent、backend/data-vault 同样 ignore = all

这条配置是整个分支协作工作流的地基(下文详述)。

克隆与初始化

# 全新克隆(带子模块)
git clone --recurse-submodules https://github.com/mindsdb/minds-platform

# 克隆时未带子模块,事后补初始化
git submodule update --init --recursive

两种 Pull 语义

  • 默认:同步到父仓库钉选的位置(推荐日常使用):
git submodule update --recursive
  • 让所有子模块推进到各自 main 最新提交:仅当你打算跨所有仓库开发时才这样做,之后需要在父仓库提交更新后的指针:
git submodule foreach 'git checkout main && git pull origin main'

核心心智模型:父仓库只存 SHA 指针

子模块工作流最容易踩的坑是不理解指针机制。CLAUDE.md 对此有明确强调:

父仓库保存的是指向每个子模块的提交 SHA 指针,而不是代码本身。始终先从子模块内部推送,再到父仓库更新指针。

标准的「在子模块中修改并推送」三步流程:

  1. 进入子模块,先切到真实分支(子模块初始处于 detached HEAD 状态):
cd backend/data-vault   # 或任意子模块
git checkout main       # 或者: git checkout -b your-feature-branch
  1. 修改、提交、推送(在子模块内部执行):
git add .
git commit -m "your message"
git push origin main
  1. 回到父仓库,暂存并提交更新后的子模块指针:
cd ../..
git add backend/data-vault
git commit -m "bump data-vault to latest"
git push

注意第 3 步在多人分支工作流下会被 make pin 替代(见下文),但其底层逻辑完全一致:父仓库只记录一个 SHA。

多开发者模块分支工作流(核心章节)

多个开发者各自在自己的模块分支上开发时,会遇到两个典型痛点:子模块钉选互相打架、git status 噪音淹没真实改动。仓库给出的解法由五条规则构成,全部可以在 Makefile 中找到对应实现。

规则 1:.gitmodules 设置 ignore = all

如上所示,.gitmodules 对每个子模块设置了 ignore = all。效果是:子模块内的日常分支工作永远不会显示为超项目变更,无论在哪个子模块上待了多久,父仓库的 git status 都保持干净。文档特别指出:这是唯一能消除 "new commits in <module>" 提示的设置,ignore = dirty 做不到这一点。

规则 2:在 dev.env 中选择你的分支

dev.env 被 gitignore,不会提交——每位开发者维护自己的副本,互不干扰。从 dev.env.example 复制后编辑:

cp dev.env.example dev.env
# REF=feat/my-thing            # 所有模块统一切到某个分支……
# API_REF=feat/server-thing    # ……或按模块单独覆盖

完整可用的变量(对照 dev.env.example 原文注释):

# 所有模块使用同一分支:
# REF=feat/my-thing

# ……或按模块单独覆盖(优先级高于 REF):
# FRONTEND_REF=feat/ui-thing
# API_REF=feat/server-thing
# AGENT_REF=feat/agent-thing

Makefile 顶部的解析逻辑印证了优先级:-include dev.env 之后,REF 默认 main,而 API_REF/AGENT_REF/FRONTEND_REF 在未显式设置时回退到 REFMakefile):

-include dev.env

REF          ?= main
API_REF      ?= $(REF)
AGENT_REF    ?= $(REF)
FRONTEND_REF ?= $(REF)

规则 3:make 是 refs 的唯一事实来源,两条运行路径共用同一套 refs

CLAUDE.md 给出的完整目标清单如下(每项行为均已对照 Makefile 源码核实):

命令 作用
make refs 打印下一次运行将使用的 refs
make use 在所有子模块中 checkout 这些 refs
make dev / make dev-web 运行本地子模块源码(带 --reload)——跟随当前 checkout 的分支
make server API_REF/AGENT_REF 安装 Electron 桌面服务器
make server-local 本地未提交源码安装 Electron 桌面服务器(无需 push)
make app 运行 Electron 桌面应用(关闭自动更新,防止分支被回滚)
make app-local 让 Electron 桌面应用直接跑本地未提交源码(先执行 server-local
make pack-local 从本地未提交源码构建 macOS .app,iCloud 友好(不产 DMG,构建在 /tmp
make watch 实时热重载——Electron 应用 + Python --reloadmake dev 的别名)
make baseline 把所有子模块回拨到超项目钉选的提交
make pin 把子模块当前提交记录为超项目钉选(一次有意为之的提交)

源码级细节补充:

  • make refs 仅打印三个值,输出格式 frontend=… core_api=… core_agent=…Makefile)。
  • make usefrontendbackend/core_apibackend/core_agent 三个子模块依次执行 git fetch -q origin <ref> + git checkout <ref>Makefile)。
  • make dev 的进程模型:用 uv run --directory backend/core_api uvicorn cowork.server:app --reload 启动 FastAPI 后端,--reload-dir 同时覆盖 backend/core_api/coworkbackend/core_agent/anton 两个目录,再前台启动 npm --prefix frontend run dev;通过 trap 'kill 0' 保证 Ctrl-C 时前后端一起退出(Makefile)。
  • make dev-webmake dev 的差异在后端完全相同,前端改为 BUILD_TARGET=web npm run dev:renderer -- --open,即在浏览器中打开 Web 渲染器(Makefile)。
  • make serveruv tool installAPI_REF 指定的 git 位置安装 cowork-server,并锁定 Python 版本区间 >=3.12,<3.14;当 AGENT_REF 不是 main 时,还会追加 --with 参数让 anton-agent 也装自对应分支(MakefileMakefile)。
  • make app 通过 COWORK_SERVER_DISABLE_AUTOUPDATE=1 环境变量关闭自动更新(Makefile),目的就是文档强调的「不能让桌面应用把你正在开发的分支覆盖回去」。

两条运行路径,同一套 refsmake dev/dev-web 直接执行本地源码,跟随 make use checkout 的分支;而桌面应用运行的是一个 uv tool 安装的服务器,其分支键是环境变量 COWORK_SERVER_REF/ANTON_REF(前端侧逻辑见 frontend/src/main/server-source.ts,按 CLAUDE.md 描述)——这两个变量正是在 Makefile 中从 API_REF/AGENT_REF 导出并 exportmake server/make app 的。

保持两者 ref 一致:桌面端与本地开发共享 ~/.cowork/cowork.db,一边执行的数据库迁移必须同样存在于另一边,否则应用启动时会因 Can't locate revision … 崩溃。漂移时用 make flush 重置。

规则 4:钉选推进是有意为之(deliberate)

因为子模块被 ignore,指针变化的唯一途径是 make pin——在子模块 PR 合并并推送后执行。make pin 的实现在 Makefilegit add 四个子模块路径,若 staged 无变化则打印「Nothing to pin」,否则自动提交 chore: bump submodule pins,提示推送以共享新钉选。这把原本容易失控的指针变更收敛为一次可评审的提交。

规则 5:make baseline 回拨基线

当你不开发某个模块时,用 make baseline(即 git submodule update --init --recursiveMakefile)把所有子模块对齐到超项目钉选。由于 ignore = all 不会提醒你有队友的钉选推进把某个模块落在了后面,拉取超项目之后主动跑一次 make baseline 是对齐的可靠做法。

进阶:带着未提交改动开发 Electron 应用

  • make dev/dev-web 天然能拾取本地未提交的源码改动(--reload)。
  • Electron 桌面端对应 make app-local:其实现是先 $(MAKE) server-localuv tool install "$(CURDIR)/backend/core_api" 直接从本地路径安装,Makefile),再以 COWORK_SERVER_DISABLE_AUTOUPDATE=1 COWORK_SERVER_PACKAGE="$(CURDIR)/backend/core_api" npm run dev 启动(Makefile),全程无需提交。
  • 若还要免提交开发 core_agent:把 backend/core_api/pyproject.toml 中的 [tool.uv.sources] anton-agent 改为 { path = "../../core_agent" },再重跑 make app-local(推送前记得恢复 git source)。make server-local 的提示输出也印证了这一步(Makefile)。
  • make pack-local 则用于从本地未提交源码产出 macOS .app:先 server-localnpx electron-builder --mac --arm64 --dir 输出到 /tmp/minds-build 再拷回 frontend/release/,不产生 DMG(Makefile)。

本地运行:Web 浏览器模式

初始化子模块后,完整技术栈的浏览器模式启动方式(CLAUDE.md):

cd frontend
npm install
npm run dev:web
  • 前端:http://localhost:5173/
  • 后端:FastAPI 自动在 http://127.0.0.1:26866 启动

这与 docker-compose.ymlapi 服务对外映射的 26866 端口、健康检查 /health 路径一致,可以互为印证。

跳过 Keycloak 认证(本地开发必需)

Web 应用默认在首次加载时重定向到 MindsHub SSO。本地绕开的方式是创建 frontend/src/renderer/.env(注意 Vite 的根目录是 src/renderer,不是 frontend/):

echo "VITE_SKIP_AUTH=true" > frontend/src/renderer/.env

然后重启 npm run dev:web。Keycloak 重定向被跳过,应用直接加载 onboarding 页面,可在其中输入 BYOK API key。

首次运行的符号链接修复

dev:web 脚本期望 uv 安装的工具位于 ~/.local/share/uv/tools/anton/,但实际可能装成了 anton-agentMakefileflush 目标注释同样提到 legacy anton-agent 工具)。若看到 "Anton Python interpreter not found":

ln -s ~/.local/share/uv/tools/anton-agent ~/.local/share/uv/tools/anton

环境重置:make flush(全新开始)

make flush 会清除所有本地安装和全部应用状态,把机器恢复到安装前状态——用于测试从零安装的完整流程,或从损坏/半成品环境中恢复(CLAUDE.md)。

make flush          # 删除前交互式确认
make flush FORCE=1  # 跳过确认(CI / 脚本)

它会删除的内容(对照 Makefile 实现逐条核实):

被删除项 说明
cowork-server uv 工具(含 legacy anton-agent Electron 应用通过 uv tool install 安装的运行时
backend/core_api/.venvbackend/core_agent/.venv Makefile 开发流程通过 uv sync 构建的 venv
~/.anton provider keys / .env
~/.cowork 数据库、hermesprojects

两点注意:

  • ⚠️ 破坏性操作——会删除会话和已保存的 provider key,不可逆。flush 之后下一次 make setup(或重新打开应用)会从零重装一切。make setup 在实现上是依次完成 frontendnpm ci 与两个后端子模块的 uv syncMakefile)。
  • 刻意保留 uv 本身和 uv 管理的 Python 运行时——这些是共享基础设施,不属于 anton/cowork 的安装范畴(Makefile 注释与 CLAUDE.md 一致)。

小结

这套工作流的设计核心可以概括为三句话:

  1. 指针与开发解耦:父仓库只保存 SHA 指针,ignore = all 让分支开发静默进行,指针变更唯一入口是 make pin
  2. refs 单一事实来源:个人分支选择收敛到 gitignored 的 dev.envmake refs/use/baseline/pin 构成完整的切换—执行—回拨—固化闭环;
  3. 两条运行路径同 ref:本地源码热重载(make dev/dev-web)与桌面端 uv tool 安装(make server/app)共享 ~/.cowork/cowork.db,必须保持 ref 一致,漂移时 make flush 兜底。

对照仓库中的 Makefiledev.env.example.gitmodules 逐行核对,即可把文档中的每一条规则映射到具体实现。

登录后查看全文
热门项目推荐
相关项目推荐