Agent2API 协作与发版规范
覆盖两类工作:日常协作(分支落脚点、PR 署名、Issue 落款,第 1–3 节)与发版(版本号、更新日志、tag、Release、验收,第 4–7 节),排错见第 8 节。
0. 两条人工红线(必须停下等确认)
下列两件事必须由人先审过、明确点头,代理才能继续;可以准备草稿、可以列依据,不能自己拍板往下走:
- 发版:进入发版流程(改版本号、建发布提交、推 main / beta、打 tag、挂 Release)之前,先把版本号与「这次要发什么」讲清楚,等用户确认。未经确认不得推任何分支或 tag —— tag 一旦推出去就会触发构建、产出公开的安装包与镜像,收不回来。
- 更新日志:写好的日志(发布提交的提交信息,同时也是 GitHub Release 正文)必须先给用户逐条过目。用户要求改措辞、增删条目、调整分类前缀时,按反馈改完要再确认一次,不能因为「改得不多」就直接提交。
一次发版里两条同时适用:既要有版本号的确认,也要有日志的确认;用户说「继续」之后才动手。
1. 分支落脚点:日常只碰 beta,发版才动 main
| 分支 | 角色 | 什么时候动它 |
|---|---|---|
beta | 集成分支(默认落脚点) | 日常修改、合并 PR、解冲突 —— 一律先到这里 |
main | 发版分支 | 只在构建发版时:切 main、把 beta 合进来、打 tag |
- 合并 PR 时 base 选
beta,不要选main。合进main等于把还没发布的改动提前塞进发版分支;之后还得反过来把 main 合回 beta,平白多一次合并提交、多一次冲突机会。已经把 PR 开成 base=main 的,先在 PR 页面把 base 改成beta再合。 - 本地改动也提交到
beta(写代码、改文档、解冲突都算),推origin/beta;合并结果的核对、构建自检都在 beta 上做完。 - 只有正式发版才切
main:git switch main→ 把 beta 合进来 → 写发布提交、推 main、打 tag;tag 必须落在main上。发完版 beta 再把 main 合回来,两边保持一致。 - 不要自作主张选落脚分支:用户没说要发版,就不要往
main提交 / 推送,也不要把 PR 的 base 改成main。 - beta 版(预发布)是唯一例外:不切
main,tag 直接落在beta分支自己身上(见第 6 节)。
2. 合并 PR:保留贡献者署名
合并贡献者的 PR 时,提交作者(author)必须还是贡献者本人 —— 这直接决定对方能不能进仓库首页 Contributors 名单、贡献图里有没有这一笔。署名是贡献者应得的东西,不该因为合并方式丢掉。
GitHub 的归属规则只有一条:提交头里的 author 邮箱出现在某个账号已验证的邮箱列表里,这次提交就算那个账号的;否则记成匿名条目,不进 Contributors。另有两条口径:统计只认默认分支(本仓库 main),合并提交与空提交不计入。
- 默认用 GitHub 网页上的合并按钮(
Merge pull request/Squash and merge/Rebase and merge):三种都会保留原作者为 author;squash 时 PR 里的多位作者会一并进 co-author 名单。 - 禁止「本地摘取重提」:把对方的改动拷过来自己提交(本地
git merge --squash后自己 commit、重抄一遍改动、或改了 author 再提交)都不行 —— 这样 author 是合并者,贡献者的署名在 GitHub 侧完全不可见。本仓库历史上出现过这种情况,别再来一次。 - 确实要在本地处理时(有冲突要解、要顺带调整),必须把署名带上:单个原作者用
git commit --author="原作者 <原作者邮箱>";一个提交里有多个人的工作,在提交信息末尾加 trailerCo-authored-by: 名字 <邮箱>。 - 摘除 AI 的 co-author trailer:贡献者提交信息末尾的
Co-Authored-By: Claude Code <…>这类 AI trailer,合并时一律删除 —— GitHub 会把它渲染成共同作者,进了main就出现在 Contributors 名单里,而 AI 不是贡献者。只删 trailer 行本身,author 与其余提交信息逐字保留;正文里对 AI 产品的技术性提及(「支持 Claude Code 客户端」「Claude 兼容端点」之类)与此无关,不动。摘除动的是已合入分支的历史,趁合并后、发版前尽早做(git filter-branch --msg-filter摘行后 force-push),拖到发版后就得重写main。 - 邮箱必须能对上账号:用对方 GitHub 账号里已验证的地址;对方想保密就用 GitHub 给的
ID+用户名@users.noreply.github.com(在对方账号的 Emails 设置里查)。内网地址、user@机器名这类邮箱永远关联不上账号,提交会被记成匿名。 - 合并后顺手核对一次:
git log --format='%an <%ae>' -1 <合并提交>,或在 PR 页面看 commit 旁的头像是否指向原作者。
3. 回复 Issue / PR:标明 AI 来源
由 AI 代理发出(含代笔)的每一条 Issue / PR 回复,末尾都要单独一行写明来源。 收件人有权知道自己在跟谁说话 —— 与第 2 节的署名是同一条道理:谁说的写谁。
固定落款,逐字照用(别改写成「机器人」「助手」这类含糊说法):
🤖 本条回复由 AI Agent 生成
- 位置:整条回复的最后一行,单独成段(前后各留一个空行);不要塞进正文段落、列表项、引用块或代码块里 —— 引用别人的话时,落款落在自己的话之后。
- 英文场合(外部贡献者的 PR / Issue)在中文那行下面再补一行
Generated by an AI agent.,说的是同一件事,两边都不省。 - 代理自己新建的 Issue / PR,正文末尾同样加。
- 代笔也算:人在对话里让代理转达(哪怕逐字)一段话,落款照加 —— 它描述的是「这条回复是谁发出去的」,不是「谁构思的」。
- 只约束 Issue / PR 回复(评论、review 意见、关闭 / 合并说明)。提交信息不追加这一行,署名口径见第 2 节。
4. 版本号
- 版本号形如
X.Y.Z(如2.7.2),日常小版本「递增 0.01」= 末位 +1(如2.7.8→2.7.9)。 - 递增幅度以用户要求为准:默认用上面的「递增 0.01」;用户另有要求时(如「这次递增 0.1」)一律听用户的,按用户指定的幅度算出新版本号,不要自作主张套用默认幅度。
- 版本号要同步改 5 处(缺一处会导致安装包与显示版本对不上):
package.json(根)desktop-tauri/package.jsondesktop-tauri/src-tauri/Cargo.tomldesktop-tauri/src-tauri/tauri.conf.jsondesktop-tauri/src-tauri/server/Cargo.toml
- 应用内「关于」与更新检查读的是
env!("CARGO_PKG_VERSION")(第 5 处),改完跑一次cargo check让Cargo.lock跟着刷新。 - 顺手过一遍安装文档:安装说明已经从 README 搬进
docs/install.md(README 只留速查表 + 一个链接),另有 5 份译文docs/install.{en,zh-Hant,ja,ko,pt-BR}.md,六份内容一一对应。凡是这次改动碰到了安装 / 升级 / 卸载步骤、产物体名、默认端口、环境变量、安装脚本参数,六份都要同步改;README 速查表受影响时也一起改。
5. 正式版发版流程
按顺序执行;第 0 节的两条红线贯穿全程。
# 1. 收干净工作区(tag 里的代码就是发出去的代码)
git status --short # 必须为空;有改动就先提交或清理
cargo check # 或前端有改动时做一次构建自检
# 2. 切到 main 并把 beta 合进来
git switch main && git pull
git merge beta
# 3. 五处版本号一起改(默认末位 +1,幅度听用户);改完刷新 Cargo.lock
cargo check
# 4. 写发布提交:提交信息 = 更新日志
git commit -am "…更新日志…"
# 5. 推 main、打 tag、推 tag(一条 v* tag 触发全部构建)
git push origin main
git tag vX.Y.Z
git push origin vX.Y.Z
# 6. 两个工作流跑完后挂 GitHub Release
gh run list --limit 4 # 手动跑 gh 前先设代理,见第 8 节
bash scripts/release.sh vX.Y.Z
发布提交的提交信息 = 更新日志,因为发版收尾脚本创建 / 更新 Release 时,正文取「tag 所指提交的提交信息」(git log -1 --format=%B)。
- 格式沿用历史版本的编号清单,每行一条,带分类前缀:
新增:/优化:/修复:/更新:/移除:,最后一条固定版本:X.Y.Z → X.Y.Z+1; - 内容取「上个 tag 以来的全部提交」综合整理(
git log vX.Y.Z..HEAD --oneline),纯 CI / 临时的调试提交归并成一条流程性描述即可; - 本次没有代码改动时用空提交承载:
git commit --allow-empty -F 更新日志.txt; - 更新日志草稿与历史发布说明统一写在
docs/release-notes/<版本号>.md(如docs/release-notes/2.9.2.md),不要在项目根目录新建.release-notes-*.md——docs/release-notes/已在忽略列表里(只忽略这个目录,docs/下其它文档正常入库),新增文件不必再加忽略规则。
推 v* tag 会同时触发两个工作流(.github/workflows/):
| 工作流 | 产出 | 说明 |
|---|---|---|
build.yml(build) | 桌面端三平台安装包 + Linux 服务端包 | 四个 job:macos(dmg)/ windows(NSIS exe)/ linux-desktop(deb + AppImage)/ linux-server(容器内更新用的 tar.gz)。macOS 与 Linux 桌面端的产物只能在各自平台上构建,无法从 Windows 交叉编译;所有产物只挂 artifact,GitHub Release 由本地脚本挂载 |
docker.yml(docker) | Docker Hub aimodcc/agent2api:<版本> + :latest(amd64 / arm64 双架构) | 手动 workflow_dispatch 触发时只出 :dev 测试 tag,不碰正式 tag;beta tag(版本号带 -)只出 <版本>,不移动 :latest |
跟踪进度:
gh run list --limit 4
gh run watch <run-id> --exit-status
发版收尾:GitHub Release 不由 CI 发布(Release 本来就不会自动创建),由本地脚本一步挂载:
bash scripts/release.sh vX.Y.Z # 自动找该 tag 的成功 build run
bash scripts/release.sh vX.Y.Z <run-id> # 或显式指定 run
脚本做三件事:下载 build 的安装包 artifact 到 dist/ → 按 tag 提交信息(= 更新日志)创建 / 更新 GitHub Release 并挂附件 → 打印验收提示。幂等可重跑(--clobber 覆盖附件)。
6. beta 版(预发布)
应用里有「设置 → 软件更新 → 更新设置 → 加入 beta 版」开关:打开的人才收到 beta 更新,没打开的人只看正式发布。
beta 版不是另一条流水线,而是第 5 节流程的一个变体 —— 唯一结构差别是「从 beta 分支打带预发布后缀的 tag」,提交、tag、Release、验收逐字照旧。第 0 节的两条红线同样适用。
# 1. 在 beta 分支上动手(beta 版不碰 main)
git switch beta && git pull
git status --short # 必须为空
# 2. 五处版本号一起改成「与 tag 逐字一致」的形态:
# tag 打 v3.1.0-beta.1 → 五处都写 3.1.0-beta.1(清单见第 4 节)
cargo check # 顺带刷新 Cargo.lock
# 3. 提交并推 beta 分支(提交信息 = 这次的 beta 更新日志,格式同第 5 节)
git commit -am "…beta 版日志…"
git push origin beta
# 4. 打 tag 并推:tag 落在 beta 分支的提交上
git tag v3.1.0-beta.1
git push origin v3.1.0-beta.1
# 5. 两个工作流跑完后挂 Release(脚本按 tag 里的 `-` 自动标 pre-release)
gh run list --limit 4
bash scripts/release.sh v3.1.0-beta.1
验收就三条:Release 显示 Prerelease、exe / dmg / deb / AppImage 附件齐全、Docker Hub 上 :latest 的 Pushed 时间没有跟着变(完整清单见第 7 节)。
三处不能错,每处错了都有明确症状:
- 五处版本号漏了后缀(写成
3.1.0):比较是按 semver 做的,运行版本3.1.0对线上 tag3.1.0-beta.1会判成「当前比线上新」,beta 用户永远收不到更新。五个文件都接受带后缀的版本号(Cargo / npm / tauri.conf.json 都认),照 tag 抄即可。 - Release 漏标 pre-release:标记就是开关的分界线 —— 正式通道走 GitHub 的
/releases/latest,它排除预发布,只有开了开关的人从/releases列表里看到 beta。漏标 = beta 被所有正式版用户当成正式更新收到。scripts/release.sh已按「tag 里带-」自动标记,重跑收尾时也会把标记纠正回来,走脚本就不用操心。 - tag 打在
main上:beta 是集成分支,beta 版要的是它的快照。打在main上要么不含 beta 分支的新改动(白测一场),要么等于把还没发布的改动提前推进main。
docker 的 :latest 不会被 beta tag 移动(.github/workflows/docker.yml),镜像仍出 3.1.0-beta.1 那个版本 tag,想尝鲜的按版本号拉。
后续常见三件事:
- 再发一个 beta:核心号不变、递增序号(
3.1.0-beta.2,五处一起改,上面那套命令照走)。预发布序号能分出先后(beta.2>beta.1),已经在 beta 上的人照样收到更新 —— 所以 beta 之间也要保持「tag 与版本号逐字一致」。 - 转正(发
3.1.0正式版):按第 5 节在main上走,五处版本号去掉后缀。beta 用户不会自动接回正式版:开关开着的人只看预发布版(pick_listed_release按「当前跑的是不是 beta 版」筛),转正的3.1.0对他们不算更新 —— 要留在 beta 频道的人等下一个 beta(3.2.0-beta.1一出现就收得到);想回正式版由用户自己关开关,关掉后只在版本号高于他手上那个 beta 的正式版发布时才收到更新(3.1.0>3.1.0-beta.1,但更早的3.0.9不会把他降级)。 - 「打开了却没有反应」:仓库里一个预发布 Release 都没有时,打开这个开关也只会看到正式版 —— 第一次 beta 发出去之后才有效果。
7. 验收清单
- GitHub Release:
gh release view vX.Y.Z(手动跑 gh 前先设代理,见第 8 节)—— 正文日志齐全,四个桌面端附件都在:WindowsAgent2API_<版本>_x64-setup.exe、macOSAgent2API_<版本>_universal.dmg、LinuxAgent2API_<版本>_amd64.deb与Agent2API_<版本>_amd64.AppImage(另有两个agent2api-server-linux-*.tar.gz+.sha256,那是容器内更新包,见第 5 节); - Linux 桌面端:装 deb(或
chmod +x后跑 AppImage)能启动,且面板「设置 → 软件更新」能检查到版本、给出对应包型的安装入口(deb → 系统安装器,AppImage → 换文件); - Docker Hub:
aimodcc/agent2api的 Tags 页出现<版本>与latest,Pushed 时间一致; - 安装包「关于」页版本号与 tag 一致;
- 安装文档对得上实际产物:
docs/install.md与 5 份译文的产物名、端口、命令、环境变量跟这次 Release 一致(README 里那几张速查表同理); - beta 版多一条:Release 显示 Prerelease,且 Docker Hub 上
latest没有跟着动。
8. 已知坑与排查
-
GHCR 新包默认私有:若以后镜像改推 GHCR,首次推送后需到包设置手动改 Public(当前推的是 Docker Hub,无此问题)。
-
gh 不读 git 的代理配置,跑 gh 前要先设代理环境变量:
gh是 Go 程序,不读~/.gitconfig里的http(s).proxy,也不读 Windows 系统代理(WinINET),只认HTTPS_PROXY/HTTP_PROXY环境变量。本机 GitHub 直连时通时不通,手动执行 gh 命令前先设:export HTTPS_PROXY=$(git config --get-urlmatch http.proxy https://github.com || git config --get https.proxy)只设
HTTPS_PROXY就够(gh 的请求全是 HTTPS,实测不读HTTP_PROXY)。scripts/release.sh已内置这一步(从 git 配置读取后透传给 gh),用脚本发版无需手动设。代理没开时 gh 会立刻报proxyconnect tcp: ... connection refused而不回退直连 —— 与git push的表现一致。