MindsHub(Minds Platform)多模块开发工作流实战:子模块钉选、dev.env 分支选择与 Make 目标全解
本篇基于仓库根目录的 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 指针,而不是代码本身。始终先从子模块内部推送,再到父仓库更新指针。
标准的「在子模块中修改并推送」三步流程:
- 进入子模块,先切到真实分支(子模块初始处于 detached HEAD 状态):
cd backend/data-vault # 或任意子模块
git checkout main # 或者: git checkout -b your-feature-branch
- 修改、提交、推送(在子模块内部执行):
git add .
git commit -m "your message"
git push origin main
- 回到父仓库,暂存并提交更新后的子模块指针:
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 在未显式设置时回退到 REF(Makefile):
-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 --reload(make dev 的别名) |
make baseline |
把所有子模块回拨到超项目钉选的提交 |
make pin |
把子模块当前提交记录为超项目钉选(一次有意为之的提交) |
源码级细节补充:
make refs仅打印三个值,输出格式frontend=… core_api=… core_agent=…(Makefile)。make use对frontend、backend/core_api、backend/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/cowork和backend/core_agent/anton两个目录,再前台启动npm --prefix frontend run dev;通过trap 'kill 0'保证 Ctrl-C 时前后端一起退出(Makefile)。make dev-web与make dev的差异在后端完全相同,前端改为BUILD_TARGET=web npm run dev:renderer -- --open,即在浏览器中打开 Web 渲染器(Makefile)。make server用uv tool install从API_REF指定的 git 位置安装 cowork-server,并锁定 Python 版本区间>=3.12,<3.14;当AGENT_REF不是main时,还会追加--with参数让 anton-agent 也装自对应分支(Makefile 与 Makefile)。make app通过COWORK_SERVER_DISABLE_AUTOUPDATE=1环境变量关闭自动更新(Makefile),目的就是文档强调的「不能让桌面应用把你正在开发的分支覆盖回去」。
两条运行路径,同一套 refs:
make dev/dev-web直接执行本地源码,跟随make usecheckout 的分支;而桌面应用运行的是一个uvtool 安装的服务器,其分支键是环境变量COWORK_SERVER_REF/ANTON_REF(前端侧逻辑见frontend/src/main/server-source.ts,按 CLAUDE.md 描述)——这两个变量正是在 Makefile 中从API_REF/AGENT_REF导出并export给make server/make app的。保持两者 ref 一致:桌面端与本地开发共享
~/.cowork/cowork.db,一边执行的数据库迁移必须同样存在于另一边,否则应用启动时会因Can't locate revision …崩溃。漂移时用make flush重置。
规则 4:钉选推进是有意为之(deliberate)
因为子模块被 ignore,指针变化的唯一途径是 make pin——在子模块 PR 合并并推送后执行。make pin 的实现在 Makefile:git add 四个子模块路径,若 staged 无变化则打印「Nothing to pin」,否则自动提交 chore: bump submodule pins,提示推送以共享新钉选。这把原本容易失控的指针变更收敛为一次可评审的提交。
规则 5:make baseline 回拨基线
当你不开发某个模块时,用 make baseline(即 git submodule update --init --recursive,Makefile)把所有子模块对齐到超项目钉选。由于 ignore = all 不会提醒你有队友的钉选推进把某个模块落在了后面,拉取超项目之后主动跑一次 make baseline 是对齐的可靠做法。
进阶:带着未提交改动开发 Electron 应用
make dev/dev-web天然能拾取本地未提交的源码改动(--reload)。- Electron 桌面端对应
make app-local:其实现是先$(MAKE) server-local(uv 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-local,npx 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.yml 中 api 服务对外映射的 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-agent(Makefile 的 flush 目标注释同样提到 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/.venv、backend/core_agent/.venv |
Makefile 开发流程通过 uv sync 构建的 venv |
~/.anton |
provider keys / .env |
~/.cowork |
数据库、hermes、projects |
两点注意:
- ⚠️ 破坏性操作——会删除会话和已保存的 provider key,不可逆。flush 之后下一次
make setup(或重新打开应用)会从零重装一切。make setup在实现上是依次完成frontend的npm ci与两个后端子模块的uv sync(Makefile)。 - 它刻意保留
uv本身和 uv 管理的 Python 运行时——这些是共享基础设施,不属于 anton/cowork 的安装范畴(Makefile 注释与 CLAUDE.md 一致)。
小结
这套工作流的设计核心可以概括为三句话:
- 指针与开发解耦:父仓库只保存 SHA 指针,
ignore = all让分支开发静默进行,指针变更唯一入口是make pin; - refs 单一事实来源:个人分支选择收敛到 gitignored 的
dev.env,make refs/use/baseline/pin构成完整的切换—执行—回拨—固化闭环; - 两条运行路径同 ref:本地源码热重载(
make dev/dev-web)与桌面端uv tool安装(make server/app)共享~/.cowork/cowork.db,必须保持 ref 一致,漂移时make flush兜底。
对照仓库中的 Makefile、dev.env.example 与 .gitmodules 逐行核对,即可把文档中的每一条规则映射到具体实现。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00