版本管理
版本策略
项目遵循语义化版本 2.0.0,当前处于 0.x 开发阶段。
版本递增规则
遵循 semver 2.0.0,0.x 开发阶段同样适用:
| 提交类型 | 递增位 | 示例 |
|---|---|---|
feat(新功能) | MINOR | 0.7.0 → 0.8.0 |
fix / perf(修复/优化) | PATCH | 0.7.0 → 0.7.1 |
| 其余(docs/ci/chore/refactor/test/build/style) | 不触发发版 | — |
| 大版本 | 由发布者手动决定 | 1.0.0 及以后 |
升级到 1.0.0 后的配置变更
| 配置项 | 当前值 | 1.x 值 |
|---|---|---|
major_on_zero | false | true |
版本管理自动化
python-semantic-release
采用 python-semantic-release v10 实现全自动发版:
- 触发:push 到 main
- 解析:Conventional Commits(
feat:/fix:/perf:/…) - 自动执行:bump 版本 → 更新 CHANGELOG → 打 tag → 创建 GitHub Release → 打包上传安装包
版本源
| 文件 | 作用 | 谁维护 |
|---|---|---|
core/version.py | 唯一版本源 __version__ = "0.7.0" | python-semantic-release 自动改写 |
pyproject.toml [project].version | 包元数据版本 | python-semantic-release 自动改写 |
uv.lock 本项目条目 | 锁文件里的项目版本(package = false,仅记录) | uv lock / uv sync 同步 |
CHANGELOG.md | 更新日志(Keep-a-Changelog 格式) | python-semantic-release 自动插入 |
git tag v{version} | 发版标记 | python-semantic-release 自动创建 |
⚠️
version_toml必须写点号路径:pyproject.toml:project.version([project] version在 dotty 里是project.version)。 2026-09-26 前这里写的是pyproject.toml:version—— PSR 的TomlVersionDeclaration.replace()在 key 不存在时直接返回原文(其源码里标着# TODO: v11 raise KeyError),于是静默跳过: pyproject / uv.lock 长期停在 0.24.2,而core/version.py与 CHANGELOG 已到 0.25.0, 期间没有任何检查发现(check_version.py当时只比对后两者)。 现已修正路径,并让check_version.py把 pyproject 一并纳入校验。
一致性校验
scripts/check_version.py 在 CI 中阻断合并:
core/version.py.__version__ == CHANGELOG.md 最新版本段
== pyproject.toml [project].version
== git tag(发版态)- 开发态(HEAD 无 tag):校验 version.py == CHANGELOG == pyproject.toml
- 发版态(HEAD 有 tag):四源必须完全一致
uv.lock不纳入硬校验:PSR 只改写version_variables/version_toml列出的文件, 锁文件的项目版本要等下一次uv lock/uv sync才跟上。它只是记录([tool.uv] package = false, 本项目不打包成 wheel),所以「锁文件版本滞后」不影响安装结果,不值得为它增加发布流程步骤。
发版流程
关键:semantic-release 使用
GITHUB_TOKENpush,不会再次触发 workflow(防止死循环)。
「手改版本号的小版本」这条回退路径的说明从哪来
PSR 判定「无需发版」(只有 docs/ci/chore 之类提交,或版本号已手动改好并打上 tag)时, release.yml 回退用 core/version.py 的版本打包,Release 由 action-gh-release 创建。 ⚠️ 这个 action 没有 body 时会把提交信息当 Release 说明 —— 2026-10-06 实测 v0.26.0 页面被贴上整段提交正文(异动榜那份长文),所以回退分支现在显式跑 scripts/release_notes.py <版本> release_notes.md,从 CHANGELOG.md 抽出该版本段当说明; PSR 正常发版那条路径 body_path 留空,保留 PSR 自己写好的说明(实测不会被清掉)。
本地一步发版(不依赖 CI)
CI 那条路(push main → release.yml)是常规路径。需要在本地一次做完(改版本 → CHANGELOG → commit → tag → push → 建 GitHub Release)时:
# 中文 Windows 必须带 PYTHONUTF8:PSR 按 GBK 解码,读 UTF-8 的提交信息时会崩
# ('gbk' codec can't decode byte 0x80 in position ...,2026-10-01 实测)
$env:PYTHONUTF8 = "1"
# token 只放进**当前会话**的环境变量,用完随 shell 消失。
# 不要写进 $PROFILE / .bashrc / .env —— 那才是落盘。
$env:GH_TOKEN = (gh auth token)
.\.venv\Scripts\semantic-release.exe version # bump + CHANGELOG + commit + tag + push
.\.venv\Scripts\semantic-release.exe publish # 用 CHANGELOG 当 notes 建 GitHub Releaseversion自己会 push(含 tag),不需要再git push --tags- ⚠️
uv.lock不会被改写(它不在version_variables/version_toml里)→ 发版后补一次:uv lock,或手工改「本项目条目」的version,再单独提交一次chore(release): uv.lock 同步到 x.y.z - ⚠️ 不要加
-vv:详细日志可能把请求头里的 token 打进终端 - 自检(只算版本、不改任何文件,安全):
semantic-release version --print - 没有
GH_TOKEN时的退路:version --no-push→git push origin main --tags→gh release create v{version} --title "v{version}" --notes-file <CHANGELOG 里那一段>(gh用系统凭据库里的登录态,与本条等价,只是多一次手动调用)
手动发版 Checklist
如需手动发版(不通过 semantic-release):
- 修改
core/version.py的__version__ - 同步
pyproject.toml的[project].version(别再靠 PSR 之外的路径补) - 更新
CHANGELOG.md(在<!-- version list -->下方添加新版本段) - 执行
uv lock让uv.lock的项目版本跟上(uv sync也会做) - 执行
python scripts/check_version.py确认一致 python build_release.py打包git commit+git tag v{version}+git push --tags