publish
GitHub自动化 Python 包的完整发布流程,包括从 develop 切分支、版本号递增、生成中文 CHANGELOG、创建 PR 至 main 以及触发 CI/CD 打 Tag 并发布到 PyPI/Docker Hub。
Trigger Scenarios
Install
npx skills add TencentCloud/Octop --skill publish -g -y
SKILL.md
Frontmatter
{
"name": "publish",
"description": "Publish the Octop Python package: cut a release branch from develop, bump version, update CHANGELOG, open a PR to main; after merge, Actions tag on main (PyPI \/ Docker Hub + GHCR) and sync main into develop. Use when the user asks to publish, release, bump version, cut a release, or run \/publish.",
"disable-model-invocation": true
}
Publish
自动化 Octop Python 包的完整发布流程。
开始时宣告: "正在使用 publish 技能发布版本 {VERSION}。"
配置项
以下配置有默认值,可在项目的 .cursor/skills/publish/SKILL.md 中覆盖。
| 配置项 | 默认值 | 说明 |
|---|---|---|
CHANGELOG_FILE |
CHANGELOG.md |
相对于仓库根目录的路径,文件不存在则跳过 |
VERSION_FILE |
pyproject.toml |
包含版本号的文件 |
VERSION_PATTERN |
^\s*version\s*=\s*"[^"]+" |
匹配版本行的正则表达式 |
README_GLOB |
README*.md |
含 shields.io 版本徽标的 README(含多语言变体,如 README.md、README_CN.md);全部同步升级 |
INIT_VERSION_FILE |
src/octop/__init__.py |
含运行时常量 __version__ 的文件(缺失则跳过并提示) |
TAG_PREFIX |
v |
Git tag 前缀;工作流监听 v*,生成 v0.1.14 风格标签 |
REMOTE |
origin |
Git 远程仓库名 |
INTEGRATION_BRANCH |
develop |
日常集成分支;release 必须从最新 tip 切出 |
TARGET_BRANCH |
main |
合并请求的目标分支(生产真源) |
RELEASE_BRANCH_PREFIX |
release/ |
release 分支名前缀 |
调用方式
/publish 0.1.14
目标版本号是唯一必填参数,其余均从配置或自动检测获取。
发布流程
顺序硬约束: 先 PR 合入
main,再在 main tip 打并推送v*tag。
禁止在 release 分支未合入main前推送生产 tag。
步骤 1 — 读取配置并确认版本
- 获取仓库根目录:
git rev-parse --show-toplevel - 记录当前分支为
{original_branch}。 git fetch {REMOTE} {INTEGRATION_BRANCH} {TARGET_BRANCH}- 从
VERSION_FILE读取当前版本(在即将基于的 integration tip 上):git show {REMOTE}/{INTEGRATION_BRANCH}:pyproject.toml | grep -E '^\s*version\s*=\s*"[^"]+"' - 检查未提交的更改:
若工作树不干净:中止并要求用户先提交或 stash。发布不得夹带无关脏文件。git status --short - 查找最近的 git tag:
如果没有 tag,视为首次发布(在步骤 3 中使用从仓库初始到 HEAD 的所有提交)。git tag --sort=-creatordate | head -1 - 展示确认信息:
当前版本 (pyproject.toml @ develop): X.Y.Z
目标版本: A.B.C
上次发布 tag: vX.Y.Z (YYYY-MM-DD)
Release 分支: release/A.B.C
集成起点: develop
合入目标: main
Tag 时机: main 合并之后(不会在 release 上先打 tag)
确认发布 X.Y.Z → A.B.C?[y/N]
如果用户未输入 y 确认,立即中止。
步骤 2 — 从 develop 创建 release 分支
git checkout -B {RELEASE_BRANCH_PREFIX}{version} {REMOTE}/{INTEGRATION_BRANCH}
如果本地或远程已存在同名 release 分支,中止:
✗ 分支 {RELEASE_BRANCH_PREFIX}{version} 已存在。
请手动删除后再运行 /publish。
步骤 3 — 分析变更并生成 CHANGELOG 草稿
-
获取上次 tag 以来的提交(相对当前 release HEAD,即 develop tip):
git log {last_tag}..HEAD --oneline # 首次发布时: git log --oneline -
如果没有找到提交:
⚠ 自上次发布 tag ({last_tag}) 以来没有新提交。 是否继续?[y/N]用户未确认则中止。
-
按提交前缀分类,生成 Keep a Changelog 格式的条目。
CHANGELOG 内容必须用中文书写。 将每个提交总结为简洁的中文要点 — 不要逐字翻译提交信息。适当合并相关提交。
分类规则:
- 以
feat:或feat(开头 → 新增 - 以
fix:或fix(开头 → 修复 - 以
refactor:或perf:开头 → 变更 - 包含
!:或提交正文含BREAKING CHANGE:→ 变更,加**Breaking:**前缀 - 以
docs:开头 → 变更 - 以
chore:、test:、ci:开头 → 忽略(基础设施噪音) - 其他所有提交 → 变更
- 移除的功能 → 移除
- 安全修复 → 安全
输出格式:
## [A.B.C] - YYYY-MM-DD ### 新增 - 中文描述新增功能 ### 修复 - 中文描述修复内容 ### 变更 - 中文描述行为变更 ### 移除 - 中文描述移除内容 ### 安全 - 中文描述安全修复省略空的分类。日期使用 ISO 8601 格式(当天日期)。日期行与第一个分类之间、各分类之间保留空行。
- 以
-
向用户展示草稿并请求确认:
CHANGELOG 草稿: {draft} 添加到 CHANGELOG.md?[y/N/edit]y→ 继续n→ 中止edit或其他反馈 → 询问用户:"需要什么修改?" — 等待回复后重新生成,再次展示确认。循环直到y或n。
-
如果
CHANGELOG_FILE不存在,跳过此步骤(无需警告)。
步骤 4 — 更新文件、提交并推送 release 分支
按顺序执行:
4a. 更新 CHANGELOG:
在 CHANGELOG_FILE 中找到 ## [Unreleased] 标题,在其后插入新版本条目(保持 [Unreleased] 为空):
## [Unreleased]
## [A.B.C] - YYYY-MM-DD
### 新增
- ...
如果 ## [Unreleased] 标题不存在,在 # Changelog 标题行之后插入新条目(若无标题则插入到文件顶部)。
4b. 升级版本号(把旧号 X.Y.Z 全部换成当前号 A.B.C):
表示「当前发布版本」的钉死号必须一致,不得只改 pyproject.toml。
把旧版本 X.Y.Z 换成目标版本 A.B.C。缺文件则提示并继续(不中止)。
必改清单(按顺序,用 Edit 工具精确替换,勿用会误伤 CHANGELOG 历史条目的全局 replace):
VERSION_FILE(pyproject.toml)— wheel / PyPI 的唯一版本源:grep -n '^\s*version\s*=\s*"[^"]+"' pyproject.toml # "X.Y.Z" → "A.B.C"- 所有匹配
README_GLOB且含 shields.io 版本徽标的 README(含多语言):
当前仓库至少包括grep -l 'shields.io/badge/version-' README.md README_*.md 2>/dev/null grep -n 'shields.io/badge/version-' {file} # `version-X.Y.Z-orange` → `version-A.B.C-orange`README.md与README_CN.md。全部未命中则提示并继续。 INIT_VERSION_FILE(src/octop/__init__.py)— 运行时常量__version__:grep -n '__version__' src/octop/__init__.py # `__version__ = "X.Y.Z"` → `"A.B.C"`- 飞牛 FnOS 两个 manifest 的
version=(仓库源文件与 pyproject 保持同一 PEP 440 字符串。scripts/build-fpk.sh打包时会改写成商店可比版本,例如1.0.2b8→1.0.208,不要把源文件改成这个编码):grep -n '^version=' fnos/docker/manifest fnos/native/manifest # `version=X.Y.Z` → `version=A.B.C`(两个文件都要改,仍是 PEP 440) - 飞牛 Docker compose 镜像标签(与本包版本相同,供 FPK 拉取 GHCR):
grep -n 'ghcr.io/tencentcloud/octop:' fnos/docker/app/docker/docker-compose.yaml # `ghcr.io/tencentcloud/octop:X.Y.Z` → `:A.B.C` # 不得改成 `:latest`(测试禁止 latest) uv.lock里可编辑包octop的版本(漏改会导致 lock 与 pyproject 不一致):grep -n -A2 'name = "octop"' uv.lock | head -5 # 将该包的 `version = "X.Y.Z"` 改为 `"A.B.C"` # 或在改完 pyproject.toml 后执行 `uv lock`,只接受 octop 版本行变化
扫尾(必做): 全库搜索旧号,把漏网的「当前版本钉死」一并改掉:
git grep -n --fixed-strings "X.Y.Z"
对每一处命中:
| 类型 | 处理 |
|---|---|
当前版本钉死(compose / badge / manifest / lock / __version__ / 文档里「当前版本」) |
换成 A.B.C |
| 测试里断言「等于当前发布号」的硬编码 | 改成读 _pyproject_version()(或同步为 A.B.C) |
CHANGELOG.md 已发布章节(## [X.Y.Z] 及正文) |
保留,不要改历史 |
测试夹具里拿旧号做比较(如 1.0.2b4 vs 1.0.2b5、解析示例) |
保留 |
workflow / 注释里的示例旧号(如 0.9.28) |
保留(不是当前钉死) |
提交前再扫必改清单,必须已经没有旧号:
git grep -n --fixed-strings "X.Y.Z" -- \
pyproject.toml src/octop/__init__.py \
README.md README_CN.md \
fnos/docker/manifest fnos/native/manifest \
fnos/docker/app/docker/docker-compose.yaml \
uv.lock
任一文件仍命中旧号:补改后再提交,不得带着半套版本号推 release。
4c. 提交:
git status --short
- 如果有更改:暂存并提交:
git add -A git commit -m "chore: release {version}" - 如果工作树已干净:无需提交,跳过。
4d. 推送 release 分支:
git push -u {REMOTE} {RELEASE_BRANCH_PREFIX}{version}
推送失败则中止。
步骤 5 — 创建合入 main 的 Pull Request(先合,后自动 tag)
使用 gh CLI:
gh pr create \
--base {TARGET_BRANCH} \
--head {RELEASE_BRANCH_PREFIX}{version} \
--title "chore: release {version}" \
--body "$(cat <<'EOF'
{步骤 3 生成的 CHANGELOG 条目}
## Release checklist
- [ ] CI green
- [ ] Merge this PR into main
- [ ] After merge, GitHub Action auto-pushes v{version} tag on main tip
EOF
)"
- 成功时展示 PR URL,并明确告知:
- 合并前不要手动打 tag;
- 合并后会自动发版(自动打 tag)。
- 若
gh失败:中止(此时尚未发版),提示手动创建 PR:{RELEASE_BRANCH_PREFIX}{version}→{TARGET_BRANCH}
步骤 6 — 等待合入后,由 Action 在 main tip 打 tag
-
询问用户 PR 是否已合并,或轮询:
gh pr view {pr_url} --json state,mergedAt未合并则等待;无需本地执行打 tag。
-
合并后:
auto-tag-on-release.yml会读取合并后main的pyproject.toml版本并推送{TAG_PREFIX}{version}。- 若 tag 已存在,Action 会跳过并输出日志。
-
提示用户到 Actions 确认:
Auto Tag On Release Merge成功;Release与Docker Publish随v*tag 触发并通过;Sync Main Into Develop在 GitHub Release 发布后把main同步回develop。
步骤 7 — 删除 release 分支;develop 由 Action 同步
-
删除远程与本地 release 分支:
git push {REMOTE} --delete {RELEASE_BRANCH_PREFIX}{version} git branch -D {RELEASE_BRANCH_PREFIX}{version}删除失败则警告(非致命),提示手动删除。
-
develop 同步:GitHub Release 发布成功后,
sync-main-to-develop.yml会自动开main → developPR,并在无冲突时用 merge commit 合入。- 若已快进无差异,Action 会跳过。
- 若有冲突或分支保护拦截,Action 留下 PR 并告警,需人工处理。
- 技能侧无需再手动创建 sync PR(除非 Action 失败)。
步骤 8 — 切回原分支
git checkout {original_branch}
确保流程结束后用户不会停留在 release / 临时检出上。
错误处理参考
| 场景 | 行为 |
|---|---|
VERSION_FILE 未找到 |
中止:"找不到 VERSION_FILE:{path}" |
| 文件中未匹配到版本号 | 中止:"在 {VERSION_FILE} 中找不到匹配 {VERSION_PATTERN} 的版本行" |
| 工作树不干净 | 中止:先清理再发布 |
| 没有 git tag(首次发布) | 使用完整历史;提示"首次发布" |
| 上次 tag 以来无提交 | 警告并询问是否继续 |
| Release 分支已存在 | 中止并给出删除指令 |
| 步骤 4 推送失败 | 中止:文件已在本地更新但未推送 |
| 步骤 5 PR 创建失败 | 中止(尚未打 tag / 未发版) |
| 步骤 6 在未合入时手动打 tag | 禁止 — 硬红线 |
| 步骤 6 tag 已存在 | 中止并给出删除指令 |
| 步骤 6 tag 推送成功但 Action 失败 | 非致命:提示到 Actions Re-run |
| 步骤 7 删分支或 sync PR 失败 | 警告并给出手动命令 |
红线规则
绝不:
- 在 release / feature 分支上、于合入
main之前推送生产v*tag - 在推送 tag 前直接上传 PyPI(发布由 GitHub Action 负责)
- 将
develop直接 push / merge 进main(必须走 PR) - 跳过步骤 1 的用户确认
- 跳过步骤 3 的 CHANGELOG 确认
- 在任何步骤失败后继续执行(步骤 7 的清理/同步警告除外)
- 流程结束后让用户留在 release 分支
- 保留已发完的
release/*作为长期分支
始终:
- 从最新
{REMOTE}/{INTEGRATION_BRANCH}切 release - 先合入
{TARGET_BRANCH},再由 Action 在 main tip 打 tag - 发版后删除
release/*;main → develop由sync-main-to-develop.yml自动同步(失败时再手动补) - 中止前展示完整错误输出
- 插入新版本条目后保持
[Unreleased]为空 - 把旧版本号
X.Y.Z换成A.B.C:pyproject.toml、__version__、多语言 README 徽标、fnos/*/manifest、fnos/docker/app/docker/docker-compose.yaml镜像标签、uv.lock的 octop 包版本;提交前git grep必改清单确认无旧号 - 勿改 CHANGELOG 历史章节、测试夹具里的旧版本比较、workflow 示例号
- 推送 tag 后提示用户关注 GitHub Actions 的发布结果
Version History
- 4f443ba Current 2026-10-18 16:12
- 757fd12 2026-09-21 23:50


