# 发布流程 ## 一次发版,三处版本同步 AgentForge 支持 Claude Code、Codex 和 DeepSeek Harness。`release.mjs` 同步更新三个清单中的版本号: | 文件 | 谁读它 | 作用 | |---|---|---| | `.claude-plugin/plugin.json` | Claude Code | **发版开关** —— 不 bump,CC 用户收不到更新 | | `.codex-plugin/plugin.json` | Codex | 安装副本的插件版本;本地与 Git marketplace 的刷新还取决于安装来源 | | `package.json` | DSH / npm | 装法决定它的作用:`link:` 读磁盘、git 安装钉 commit(**版本号都不参与**);只有 registry 安装才按 semver 解析,那时它才是开关。详见下方「DSH 侧的机制」 | > ⚠️ **三处漂移会被 `doctor` 判为 error**,发版脚本第 3 步就会中止。 > > **当前状态**:本插件没有发布到 npm(曾发过 0.4.2,已下架),所以 `package.json` > 的版本号眼下**不参与任何 DSH 用户的升级判断**——它只是为了一致性,以及将来 > 若恢复 registry 分发时能立刻生效。 ## CC 侧的核心机制:`version` 就是发版开关 Claude Code 用 `.claude-plugin/plugin.json` 的 `version` 字段判断用户是否需要更新: - **不 bump `version`** → 用户永远停留在旧版本,push 代码**没有任何影响** - **bump `version`** → 用户执行 `/plugin update` 或自动更新时拿到新版本 所以日常开发可以随便 push,发版是一个**显式动作**。 > ⚠️ **不要同时在 `plugin.json` 和 `marketplace.json` 里设 `version`。** > Claude Code 只读 `plugin.json` 的值,**且不报警告** —— marketplace 里写的会被静默忽略, > 造成"明明改了版本号用户却没更新"的假象。 ## Codex 侧的安装与更新 仓库根的 `.agents/plugins/marketplace.json` 将 `./` 注册为插件源;`.codex-plugin/plugin.json` 指向共用的 `skills/`。本地开发使用 `codex plugin marketplace add /path/to/AgentForge`,安装 `agentforge@agentforge` 后在新任务中验证。发布到 GitHub 默认分支后,用户可用 `codex plugin marketplace add Lixiuxiu559/AgentForge` 添加来源。 已安装插件从 Codex 的缓存副本加载。本地改动后重新安装插件;Git 来源先运行 `codex plugin marketplace upgrade agentforge`,再检查安装副本,必要时重装。开启新任务验证新内容。市场清单不设置版本;发布时同步 bump Codex 插件清单的版本。 ## DSH 侧的机制:版本开关取决于装法 `dsh plugin --profile ` 是对 **pnpm 的透传**:在 profile 目录里跑 `pnpm `, 然后按**已安装状态**(不是依赖 diff)reconcile `dsh.profile.bundles`。 | 安装方式 | lockfile 里钉的是什么 | 更新语义 | |---|---|---| | `dsh plugin --profile web add /abs/path` → `link:` | 路径 | **直接读磁盘**,改动立即生效,版本号不参与 | | `dsh plugin --profile web add github:owner/repo` | **具体 commit** | 版本号**不参与**;`pnpm update` 会重解析到分支最新 commit | | `dsh plugin --profile web add github:owner/repo#v0.4.0` | 该 tag 的 commit | 钉死;`pnpm update` 报 `Already up to date`,不受分支变动影响 | | ~~`dsh plugin --profile web add dsh-agentforge`(npm)~~ | semver 范围 | **当前不可用** —— 本插件未发布到 npm(0.4.2 已下架)。恢复后才是「按 `version` 解析」的唯一途径 | > ⚠️ **git 安装没有版本门控。** 实测(2026-09-26):`pnpm add github:Lixiuxiu559/AgentForge` > 在 lockfile 里写的是 `…/tar.gz/`,`pnpm update agentforge` 会把它重解析到 main 的 > 新 commit(`7c35415…` → `5d143c6…`),**装完版本号仍显示 `0.4.0`**。 > 也就是说 **git 安装下「push 到 main」就等于发布**——这跟下面「日常开发 push 不 bump」 > 的纪律直接冲突。同理,`awesome-dsh-plugin` 的站点是按 > `dsh plugin --profile web add github:` 生成安装命令的, > 所以**公开用户默认就是跟 main 的**。 **这不是理论。** `dshmarket` 判断「有没有更新」的逻辑(`dshmarket/lib/updates.js`)是: ```js // github 安装:拿钉住的 commit 和 git HEAD 比 const current = githubCommitOfTarget(spec) ?? lockCommits.get(`github.com/${repo}`) ?? null; const latest = await resolveHeadCommit(repo, ...); updateAvailable: current !== null && latest !== null && current !== latest ``` 即 **`updateAvailable = 钉住的 commit !== HEAD`**。所以 main 每推一次, 用户的市场页就显示一次「有更新」,点一下就拿到新代码——**不需要你发版**。 | 装法 | 市场能否检测到更新 | 更新语义 | |---|---|---| | `github:owner/repo` | ✅ 比 commit 与 HEAD | 跟 main,push 即发布 | | npm | ✅ 查 npm `latest` | **版本门控** | | Release tarball | ❌ 查 npm 查不到 → 永远 false | 只能手动升级 | (tarball 那行是 `repoFromTarget()` 的刻意设计:它**明确不识别 Release 资产 URL**, 因为同一个 URL 在不同时间可能返回不同字节。代码注释原话: "This is FOR DISPLAY/LOOKUP PURPOSES ONLY … MUST NOT be used for any decision that affects installation, rollback, duplicate detection"。) 所以**本地开发用 link 安装时,DSH 侧不需要发版就能拿到最新代码**; 分发给他人时,只有 **registry(npm)** 安装才让版本号真正成为开关。 **不发 npm 的代价就是「main 即发布」**,因此分支策略需要相应考虑—— 见下方「分支策略」。 DSH 桥接代码更新后需重启 profile;技能在 provider 下次读取时重扫,宿主可能另有缓存。详见 `docs/architecture.md`。 --- ## 分支策略:main 是发布通道,dev 是开发通道 **为什么需要 dev**:DSH 用户走 `github:` 安装,而市场判断更新的依据是 `钉住的 commit !== HEAD`(见上)。也就是说 **main 就是发布通道**—— 在 main 上推什么,用户点一次「更新」就拿到什么。 所以「攒两三个功能再发一次版本」这个诉求,只能靠分支实现: | 分支 | 角色 | 谁能拿到 | |---|---|---| | `dev` | 日常开发 | 只有你自己(CI 会跑校验) | | `main` | 已发布状态 | 所有用户 | ### 日常开发 ```bash git switch dev git add -A git commit -m "feat: ..." git push ``` **用户侧无感知。** 不需要打标签,不需要改版本号。CI 在 `dev` 上照样跑校验 (`validate.yml` 的 push 触发包含 `dev`),写坏了立刻能看到。 ### 发版(攒够了才做) ```bash git switch main git merge --no-ff dev # 把攒的功能带过来 node tools/release.mjs patch --push # 同步三个版本号 + 提交 + 打标签 + 推送 git switch dev git merge main # 把 bump 提交同步回 dev ``` > **忘了最后一步基本无害**:main 上的 bump 提交 dev 没碰过,下次 merge 不会冲突, > 只是 dev 的版本号落后——开发时无所谓。但养成习惯更省心。 > `release.mjs` 目前**强制要求当前分支是 main**(`if (branch !== 'main') die(...)`)。 > 如果你觉得「同步回 dev」这步烦,可以给它加 `--branch` 参数,改成在 dev 上发版 > 再快进 main。 ### 注意:默认分支仍是 main GitHub 的默认分支保持 `main`——它是仓库的门面,也是 awesome 列表指向的地方。 开发时手动 `git switch dev` 即可。 ### 分支保护(可选,未启用) 如果想从机制上防止误推 main,可以在仓库设置里给 `main` 加保护规则 (Settings → Branches)。但对单人项目来说,强制 PR 会显著增加每次发版的步骤, 所以目前只靠约定,没有启用。 --- ## 发版:一条命令 ```bash node tools/release.mjs patch git push --follow-tags ``` `release.mjs` 按顺序执行: 1. 检查工作区是否干净 2. 检查当前分支是否为 `main` 3. 跑 `tools/doctor.mjs` 校验(不通过则中止,**含三处版本一致性**) 4. 计算新版本号(版本漂移会在 doctor 阶段中止,先修复再发版) 5. 检查目标 tag 是否已存在 6. **同步更新 `.claude-plugin/plugin.json`、`.codex-plugin/plugin.json` 与 `package.json` 的 `version`** 7. 更新 `CHANGELOG.md`(把 `Unreleased` 转成正式版本 + 日期) 8. 提交 `chore(release): vX.Y.Z` 9. 打标签 `vX.Y.Z` 加 `--push` 可以连推送一起做: ```bash node tools/release.mjs patch --push ``` **`release.mjs` 不发布 npm。** 它只同步三个版本号、提交、打标签并推送。 推送 tag 触发 [`.github/workflows/release.yml`](../.github/workflows/release.yml) 创建 GitHub Release;npm 发布工作流当前仅手动触发,详见 [`npm-publishing.md`](npm-publishing.md)。 加 `--dry-run` 只预览不落盘: ```bash node tools/release.mjs patch --dry-run ``` --- ## 版本号怎么定 语义化版本,但判据是**「对使用者是否破坏」**,不是代码改了多少。 | 类型 | 触发条件 | 示例 | |---|---|---| | **PATCH** | 措辞修正、脚本 bug 修复、文档、`references/` 内容补充 | `0.1.0` → `0.1.1` | | **MINOR** | 新增技能、新增子 agent、新增语言支持、新增能力 | `0.1.1` → `0.2.0` | | **MAJOR** | 重命名或删除技能/子 agent、改变现有行为契约 | `0.2.0` → `1.0.0` | > **0.x 期间例外:破坏性变更用 MINOR 承载。** semver 对 `0.y.z` 的定义是 > 「初始开发阶段,任何东西都可能随时变化,公开 API 不应被视为稳定」, > 所以 `MAJOR` 从 `1.0.0` 起才启用。上表列的是 **1.0 之后的严格语义**。 > 0.5.0 就是这么发的:删除了 5 个子 agent,走的是 MINOR。 ### 重命名是破坏性变更 使用者按名字调用能力: ```text /agentforge:complexity-audit ``` 技能或子 agent **一旦改名,旧名字立即失效**,用户的习惯、脚本、文档全部断裂。 所以: - 重命名技能或 agent → **破坏性** - 删除技能或 agent → **破坏性** - 只改正文内容、不改名字 → PATCH 其中「破坏性」在 **`1.0.0` 之前用 MINOR 承载**,从 `1.0.0` 起升为 **MAJOR**。 > 插件自身的 `name`(`agentforge`)改名更严重,需要 marketplace 的 `renames` 字段做迁移。 > 当前不计划改动。 --- ## 发版前检查清单 `release.mjs` 会自动做前五项,后两项需要人工确认: - [x] 工作区干净 - [x] 在 `main` 分支 - [x] `tools/doctor.mjs` 通过(含三处版本一致性) - [x] 目标 tag 不存在 - [x] 三处版本号会被同步 bump - [ ] `CHANGELOG.md` 的 `Unreleased` 段写清了本次改动 - [ ] 本地实测过关键技能能正常加载(Codex:本地 marketplace 安装;CC:`/plugin marketplace add ./`;DSH:`--dump-config` 看装配) --- ## CI:push 时校验,不发版 `.github/workflows/validate.yml` 在 push 和 PR 时运行: - `node tools/doctor.mjs` —— 三端结构与资产校验 - JSON 清单合法性 - 脚本语法 - 版本号与 tag 一致性提示 **CI 不碰版本号,不发版。** 它只保证 push 进去的东西结构是好的。 --- ## 官方目录同步(若已提交到 claude-plugins-official) 通过官方目录审核后,**push 到 GitHub 会被 CI 自动同步并重新扫描,不需要重新提交表单**。 但同步的可见版本仍受 `version` 约束 —— 不 bump,用户还是拿不到。 --- ## 常见错误 | 现象 | 原因 | |---|---| | 改了代码,用户说没更新 | 忘了 bump `version` | | bump 了 `marketplace.json` 里的 version 但无效 | `plugin.json` 的值优先,且不报警告 | | 发版后用户仍拿旧版 | 用户没执行 `/plugin update`;或自动更新被关闭 | | tag 已存在导致发版失败 | 该版本已发过,换一个版本号 | | 新增了技能但用户看不到 | 未发版;或技能 frontmatter 不合法(CI 应已拦住) | | `doctor` 报「三处版本号不一致」 | 两份插件清单与 `package.json` 漂移,先统一现有版本再运行 `release.mjs` | | Codex 安装后看不到新技能 | 刷新 marketplace/重装插件,并在新任务中检查已安装副本 | | 更新后仍出现旧的 `agentforge` 派发工具 | 当前版本已移除该工具;检查加载路径并重启 profile 清除旧注册 | | DSH 侧改了技能没生效 | 技能每次重扫,通常应立即生效;若未生效检查 profile 是否真的挂载了 agentforge bundle |