Release Project
指导项目版本发布的完整流程,从版本规划到 Git 标签创建。
前置要求
- 当前仓库使用 Git 进行版本控制
- 项目配置了
package.json(Node.js 项目)或相应的版本管理文件 - 分支模型可识别(第 1 步调用
recognize-codebase-branch-flow技能自动判定 trunk-based 或 release-branch)
运行模式
| 调用方式 | 行为 |
|---|---|
| 默认(无参数) | 完整发版流程(下述 1-6 步) |
--changelog-only |
仅维护 ## [Unreleased] 区块(遵循 Keep a Changelog 分类、正交合并、[internal] 标记),自动确认、不升版本号、不打 tag、不推送。供自动化流程(如 flow-north-star-loop)增量沉淀 changelog;完整发版由不带参数的调用完成 |
--sync-to <target-branch> |
仅执行分支同步(如 dev → release 触发 CI/CD),不执行任何发版操作。完整流程见 branch-sync。与 --changelog-only 互斥 |
工作流程
- Preflight
- 分支检查
- 版本规划
- Changelog
- 版本号升级
- Git 提交& 标签
- 首次发布检测&准备
- Postflight (--post 校验)
0. Preflight 机械检查
进入流程前先跑检查脚本,把可机械判定的检查项一次跑完(脚本位于本技能目录):
node scripts/preflight.mjs # 发版前检查(cwd 默认为项目根目录,可用 --cwd 指定)
node scripts/preflight.mjs --post # 发布后校验(第 7 步)
node scripts/preflight.mjs --json # JSON 输出,供自动化流程消费
脚本覆盖:git 仓库、工作区干净、当前分支与远程同步、长期分支与最新 tag 探测、发布目标识别(npm 包 / VSCode 扩展 / CLI / Claude Code skill 包)、CHANGELOG 结构、release 脚本与钩子链、按发布目标分流的 registry/首发判定(monorepo 自动展开 workspace 逐包检查:publish registry 凭证与逐包首发判定)、版本 tag 冲突。fail 项必须先处理再继续;info 项(发布目标、分支/tag/首发状态)直接作为后续步骤的输入——发布目标决定走 npm 路径、VSCode 扩展(vsce)路径还是 skill 路径。
1. 分支检查
目标:识别项目分支模型,据此选择发版路径,并确保发版分支干净且与远程同步。
1.1 识别分支模型
自动调用 recognize-codebase-branch-flow 技能分析当前项目分支模型,获取其 branch_model 判定:
明确映射(直接走对应路径,无需询问):
| recognize 输出 | 发版路径 |
|---|---|
github flow |
Trunk-based(main 直接打 tag) |
gitflow |
Release-branch(develop → release → main) |
模糊映射(gitlab flow / 无显著模型 / 自建模式)或 recognize 调用失败:基于 preflight 输出的长期分支与最新 tag 位置探测——
- 是否存在长期
release/develop分支 - 最近 tag 落在哪个分支(tag 位置优先于分支名——tag 反映真实发版行为,分支名可能误导)
- tag 应位于长期分支,而不是 feat/hotfix 等短期分支
探测有定论则自动选路径;探测仍无定论才用 Ask 询问用户("检测到分支模型为 X,应走 [Trunk-based/Release-branch] 路径,是否正确?")。
1.2 Trunk-based 路径(github flow 等)
主分支(main/master)直接打 tag 发版:
- 确认在主分支:
git branch --show-current,不在主分支则git checkout main - 拉取最新(仅快进):
git pull --ff-only origin main - 验证工作区干净:
git status,确保无未提交变更
1.3 Release-branch 路径(gitflow 等)
有长期 release 分支用于发版准备:
- 检查当前分支:
git branch --show-current,不在 release 则git checkout release - 确保 release 分支最新:
- 开发在 develop/feature 分支:
git fetch origin && git merge origin/develop --no-ff -m "chore: merge develop into release" - 已在 release 分支开发:
git pull origin release
- 开发在 develop/feature 分支:
- 验证工作区干净:
git status,确保无未提交变更
1.4 Alpha / Prerelease 分支策略
当发布的版本是 prerelease(版本号含 -,如 0.4.0-alpha.2)时:
- 如果当前分支是 feature / alpha 专用分支(非
main/master/release),直接在该分支上打 tag 发版,不合并回 main。 - 版本号升级、Changelog、Git 提交与标签都在当前分支完成。
- 推送时使用当前分支:
git push origin <当前分支> --tags。
只有在发 stable 版本时,才需要把变更合并到 main(trunk-based)或 release(gitflow)后再打 tag。
1.5 test 分支合并(flow-north-star-loop 产物)
当项目存在长期 test 分支作为自动化开发循环(flow-north-star-loop)的集成分支时,发版前需先把 test 上已通过 e2e 验证的变更合并到发版分支——这填补 nsl-loop 产出(test)与发版(main 打 tag)之间的缝隙:
- 检测 test 分支:
git branch --list test - 检测待合并变更:
git log --oneline <发版分支>..test(若含feat/nsl-epic/*章鱼合并痕迹,确认是 nsl 产物) - 合并:
- trunk-based:
git checkout main && git merge --no-ff test -m "chore: merge test into main for release" - gitflow:合并到
release分支
- trunk-based:
- 无 test 分支或无差异:跳过本步,按常规流程发版
2. 版本规划
确定版本类型(遵循 Semantic Versioning):
| 版本类型 | 适用场景 | 版本变化示例 |
|---|---|---|
patch |
Bug 修复、小幅改动 | 1.0.0 → 1.0.1 |
minor |
新功能(向后兼容) | 1.0.0 → 1.1.0 |
major |
破坏性变更 | 1.0.0 → 2.0.0 |
显式版本号:当用户通过 /release-project <x.y.z> 传入具体语义化版本号(如 0.1.0、1.0.0)时,直接设为该版本号,不走 patch/minor/major 相对升级——npm version minor 等是相对当前版本推算,可能与用户指定的目标版本不一致(如当前 0.0.1 + 用户要 1.0.0 时,npm version minor 只会给 0.1.0)。具体版本号直接改 package.json 的 version 字段或 npm version <x.y.z> --no-git-tag-version。
3. Changelog 整理
遵循 Keep a Changelog 规范。格式模板、变动类型表、Unreleased 与 YANKED 约定见 changelog-format。
核心原则
- 更新日志是写给人而非机器的
- 每个版本都应该有独立的入口
- 同类改动应该分组放置
- 新版本在前,旧版本在后
- 使用 ISO 8601 日期格式:
YYYY-MM-DD - 文档最上方维护
## [Unreleased]区块记录即将发布的变更;发布时移至新版本区块,空区块保留标题
检查清单与用户确认
更新 Changelog 后,必须等待用户确认再继续下一步(--changelog-only 模式下跳过此确认,自动结束流程,不执行后续版本号升级与 Git 提交):
- 向用户展示 Changelog 变更内容
- 询问用户是否需要修改
- 只有在用户确认后才继续版本号升级和 Git 提交
检查清单:
- 所有变更按正确类型分类(Added / Changed / Deprecated / Removed / Fixed / Security)
- 日期格式为 ISO 8601(
YYYY-MM-DD) - 包含
[Unreleased]区块 - 空类别已移除(无内容时不保留标题)
- 版本号链接到对比页面(可选)
- 所有内容正交:同一功能的新增与后续修复应合并为一条面向价值的条目,避免同一改动拆成多条
- 反例:
- 增加交互式 pager与- 修复 pager footer 渲染异常应合并为- 为 supports/list 增加交互式 pager
- 反例:
- 非终端用户可见的变更已标记
[internal]:维护脚本、CI、内部工具、模式生成等不向最终用户暴露的条目,前缀[internal]- 示例:
- [internal] watch-patterns 重启时通过持久化 hash 缓存避免全量重新上传
- 示例:
- 从未发布过的历史版本段已收敛合并:按发布目标判定首发——npm 包看
npm view <pkg> version是否 404,VSCode 扩展看是否有v*git tag。未发布时其 0.x 历史段对终端用户不可见,应合并为首发段(用户只看最终形态,"相对旧实现的优化""修复了未发布版本的 bug"一类描述无意义) - 用户已确认 Changelog 内容
4. 版本号升级
根据项目类型选择升级方式:
单包项目:
# 使用 npm version
npm version [patch|minor|major]
# 或使用 standard-version
npx standard-version --release-as [patch|minor|major]
Monorepo 项目:
- 使用 changesets:
npx changeset version - 使用 lerna:
npx lerna version [patch|minor|major] - 或使用包管理器的 workspaces 命令
版本号同步:
- 代码中的版本号(如 CLI 工具、Server 配置)
- 文档中的版本引用
- lockfile 更新
Prerelease 版本(版本号含 -,如 alpha/beta/rc):
- 升级同系列用
npm version prerelease --no-git-tag-version(如 alpha.2 → alpha.3);误用npm version patch会跨出该系列直奔 stable --no-git-tag-version让版本号与 CHANGELOG 进同一个release:commit,避免 npm version 自动产生只含 package.json 的孤点 commit
5. Git 提交与标签
标准发布提交:
# 提交所有变更
git add .
git commit -m "release: v<版本号>"
# 创建标签
git tag -a "v<版本号>" -m "Release v<版本号>"
# 推送到远程(stable 版本通常推 main;alpha/prerelease 直接推当前 feature 分支)
git push origin main --tags
# 或
git push origin <当前分支> --tags
6. 首次发布检测 & 发布准备
触发时机:完成 "Git 提交 & 标签" 后。按 preflight 识别的发布目标分流。
npm 包(发布目标 = npm-package / cli)
以 registry 为准——git tag 数只反映 git 历史,monorepo 改造 / 包改名场景会误判(项目可能留有旧包时代的 tag,但新包名在 npm 从未上架)。preflight 脚本已执行该检测(npm view <pkg> version --registry https://registry.npmjs.org,404 = 首发);若未跑脚本则手工执行。
如果 npm 查询 404(意味着这是第一次发布),使用 Ask 工具询问用户:
"检测到这是您第一次发布此项目。是否需要我协助进行 npm 发布准备工作?包括:
- 从 GitHub 读取您的个人信息(author、repository 等)
- 完善 package.json 字段(keywords、license、bugs、homepage 等)"
用户确认后,按 first-publish 执行准备流程(含 package.json 字段模板与首发检查清单)。
publishConfig 不在 Ask 可选项之列,它是发布前置条件——由 preflight 机械强制检查(publish registry 项):全局 registry 为镜像源(npmmirror 等)而包缺 publishConfig.registry 时,publish 必然 ENEEDAUTH 或发到错误源。镜像源只读,npm view 等读查询在镜像上照样成功,给出"一切正常"的假信号,问题只在写操作(publish)时爆出。因此该项以 fail 阻断,补齐后直接发版,无需等用户确认。
VSCode 扩展(发布目标 = vscode-extension)
首发判定用 git tag 历史(无 v* tag = 首发),不用 npm view(扩展不上 npm)。首发准备走 vscode-extension-release 的「首次发布准备」清单:publisher、engines.vscode、main、contributes 等 package.json 必备字段,.vscodeignore 卫生,README/LICENSE 强制项,以及(若上架 Marketplace)Azure DevOps PAT 配置。本地分发形态无需 PAT,vsce package 生成 vsix 即可。
Claude Code skill 包(发布目标 = claude-skill)
不通过 npm 发布,安装方使用 npx skills 直接从 GitHub 仓库拉取 SKILL.md:
# 仓库简写
npx skills add <github-username>/<repo>
# 或指向仓库中的 skill 子目录
npx skills add https://github.com/<username>/<repo>/tree/main/path/to/skill
因此 skill 包发版只需要:
- 升级
package.json的version(仍用于版本号管理与 tag) - 维护
CHANGELOG.md git commit+git tag+git push --tags
无需 scripts.release、publishConfig、npm registry 检查或 first-publish 准备。
发布脚本约定(pnpm release,必备)
如果项目没有 pnpm script release,应当补充——发布入口必须收敛为一条命令,不允许依赖操作者记忆多步顺序。
例外:Claude Code skill 包(
main: SKILL.md)不通过 npm 发布,无需release脚本;其发版止于 Git tag 推送。
生命周期门禁链(固定约定)
pnpm release
└─ prerelease → pnpm build(+ 多包时 build:packages)
└─ prebuild → pnpm test
prerelease自动 build:发版产物必须是当前工作区的最新构建,禁止手工先 build 再发版prebuild自动 test:任何构建都必须先过测试门禁(dev watch 类脚本独立命名,不走build,不受影响)prepublishOnly若已存在,改为直调构建二进制(如vite build)而非pnpm build:否则 publish 生命周期会经prebuild再跑一遍全量测试(prerelease链已覆盖,重复跑是纯浪费)
单包项目
{
"scripts": {
"prebuild": "pnpm test",
"prerelease": "pnpm run build",
"release": "pnpm publish --config.registry=https://registry.npmjs.org --no-git-checks"
}
}
多包(workspace)项目
用 Node 脚本按依赖序发布:直接复制 templates/release.mjs 为项目 scripts/release.mjs,按依赖序填写 PACKAGES 即可。模板背后的完整要点见 release-script:依赖序硬编码、dist-tag 自动推导、registry 显式锁官方源、必须用 pnpm publish(workspace:* 转换)、参数透传、2FA/OTP 交互边界、npm 首发 latest 平台行为等。
Monorepo 使用 changesets
release 为 changeset publish(版本管理走 changeset version)时,publishConfig 是唯一的 registry 锁定手段——changeset publish 的 CLI 只有 --otp / --tag,没有 registry 参数,发布目标按 npm 配置优先级(publishConfig.registry > @scope:registry > 全局 registry)解析。每个可发布包的 package.json 必须声明(preflight 机械强制检查):
"publishConfig": {
"access": "public",
"registry": "https://registry.npmjs.org/"
}
Why 不能指望"全局 registry 正好是官方源":开发者常把全局 registry 配成镜像(npmmirror 等)加速 install,install 与 publish 的意图冲突——publishConfig 包级声明、随包走任何环境/CI,两者互不干扰。
VSCode 扩展(vsce)
发布目标为 vscode-extension(package.json 含 engines.vscode)时,release 脚本用 vsce package/vsce publish 而非 pnpm publish。门禁链相同(prerelease→build→prebuild→test),但有一个关键差异:vscode:prepublish 必须直调 tsc -p ./ 而非 pnpm run build,否则经 prebuild 重复跑 test。完整 scripts 模板、.vscodeignore 卫生、首发判定与 dry-run 替代方案见 vscode-extension-release。
Dry-run 验证
脚本落地后必须跑通一次验证,按 release 脚本内容选方式:
- release 含
publish(npm 包):pnpm release -- --dry-run(完整链路:test → build → 各包 publish dry-run) - release 含
vsce package(VSCode 扩展):vsce package无--dry-run选项,且 pnpm 透传双--会让 vsce 报Invalid version --dry-run。改为实际执行pnpm release后,核对 vsce 输出的 "Files included in the VSIX" 清单 vs.vscodeignore预期(无 src/test/docs/node_modules 泄漏)
常见工具选择
| 场景 | 推荐工具 |
|---|---|
| 简单项目 | npm version |
| 需要自动生成 Changelog | standard-version / semantic-release |
| Monorepo | changesets / lerna |
| 严格流程控制 | 自定义脚本 |
发布检查清单
机械检查项由脚本覆盖(发版前 scripts/preflight.mjs、发布后 --post),fail 项必须清零。以下为脚本无法替代的人工判定项:
人工确认
- 分支模型已识别,发版路径已确认(trunk-based / release-branch)
- 版本类型已确定(patch/minor/major)
- 用户已确认 Changelog 内容
- prerelease 分支策略已遵守(1.4):alpha 在当前分支发版,stable 才合并发版分支后打 tag
- Git commit / tag 信息符合约定(
release: v<版本号>)
发布后人工判读(基于 preflight.mjs --post 输出)
- registry 可查新版本(
--post已显式锁官方源;若用镜像源手工查,同步延迟会导致"新版本查不到"等假阴性) - dist-tag 符合预期:prerelease 不应顶 latest(npm 首发除外,见 release-script.md)
- 入口字段完整:
npm view <pkg> main module types exports与 package.json 一致 - 首次发布时已按 first-publish.md 完成 package.json 字段准备并通过
npm publish --dry-run验证
References
- branch-sync.md —
--sync-to纯分支同步模式的完整流程与同步路径决策 - changelog-format.md — Keep a Changelog 格式模板、变动类型表、Unreleased 与 YANKED 约定
- first-publish.md — npm 首发准备流程、package.json 字段模板与首发检查清单
- release-script.md — 多包(workspace)发布要点说明,配套可复制模板 templates/release.mjs
- vscode-extension-release.md — VSCode 扩展(vsce)发布:门禁链、
vscode:prepublish直调 tsc、.vscodeignore卫生、首发判定与 dry-run 替代 - cc-plugin-version.md — 处理 package.json、plugin.json、marketplace.json 与文档之间的版本号一致性问题