@zhouhao4221/devflow-skills 0.2.0 → 0.3.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (83) hide show
  1. package/README.md +57 -235
  2. package/install.js +406 -116
  3. package/package.json +2 -1
  4. package/plugins/api/skills/api/SKILL.md +102 -0
  5. package/plugins/api/skills/api-field-mapper/SKILL.md +95 -0
  6. package/plugins/api/skills/config/SKILL.md +140 -0
  7. package/plugins/api/skills/gen/SKILL.md +345 -0
  8. package/plugins/api/skills/help/SKILL.md +121 -0
  9. package/plugins/api/skills/import/SKILL.md +95 -0
  10. package/plugins/api/skills/map/SKILL.md +152 -0
  11. package/plugins/api/skills/search/SKILL.md +95 -0
  12. package/plugins/diag/skills/audit/SKILL.md +103 -0
  13. package/plugins/diag/skills/diag/SKILL.md +41 -0
  14. package/plugins/diag/skills/diagnose/SKILL.md +167 -0
  15. package/plugins/diag/skills/init/SKILL.md +142 -0
  16. package/plugins/diag/skills/stack-analyzer/SKILL.md +150 -0
  17. package/plugins/pm/skills/ask/SKILL.md +89 -0
  18. package/plugins/pm/skills/brief/SKILL.md +95 -0
  19. package/plugins/pm/skills/export/SKILL.md +93 -0
  20. package/plugins/pm/skills/help/SKILL.md +257 -0
  21. package/plugins/pm/skills/milestone/SKILL.md +102 -0
  22. package/plugins/pm/skills/monthly/SKILL.md +111 -0
  23. package/plugins/pm/skills/plan/SKILL.md +96 -0
  24. package/plugins/pm/skills/pm/SKILL.md +174 -0
  25. package/plugins/pm/skills/progress/SKILL.md +113 -0
  26. package/plugins/pm/skills/report-generator/SKILL.md +104 -0
  27. package/plugins/pm/skills/risk/SKILL.md +223 -0
  28. package/plugins/pm/skills/standup/SKILL.md +96 -0
  29. package/plugins/pm/skills/stats/SKILL.md +158 -0
  30. package/plugins/pm/skills/weekly/SKILL.md +157 -0
  31. package/plugins/req/skills/branch/SKILL.md +447 -0
  32. package/plugins/req/skills/cache/SKILL.md +232 -0
  33. package/plugins/req/skills/changelog/SKILL.md +187 -0
  34. package/plugins/req/skills/changelog-generator/SKILL.md +106 -0
  35. package/plugins/req/skills/code-impact-analyzer/SKILL.md +48 -0
  36. package/plugins/req/skills/commit/SKILL.md +308 -0
  37. package/plugins/req/skills/dev/SKILL.md +229 -0
  38. package/plugins/req/skills/dev-guide/SKILL.md +530 -0
  39. package/plugins/req/skills/do/SKILL.md +191 -0
  40. package/plugins/req/skills/done/SKILL.md +95 -0
  41. package/plugins/req/skills/edit/SKILL.md +187 -0
  42. package/plugins/req/skills/fix/SKILL.md +300 -0
  43. package/plugins/req/skills/help/SKILL.md +136 -0
  44. package/plugins/req/skills/init/SKILL.md +505 -0
  45. package/plugins/req/skills/issue/SKILL.md +237 -0
  46. package/plugins/req/skills/issue-guide/SKILL.md +125 -0
  47. package/plugins/req/skills/migrate/SKILL.md +128 -0
  48. package/plugins/req/skills/modules/SKILL.md +195 -0
  49. package/plugins/req/skills/natural-language-dispatcher/SKILL.md +545 -0
  50. package/plugins/req/skills/new/SKILL.md +172 -0
  51. package/plugins/req/skills/new-quick/SKILL.md +246 -0
  52. package/plugins/req/skills/pr/SKILL.md +157 -0
  53. package/plugins/req/skills/prd/SKILL.md +187 -0
  54. package/plugins/req/skills/prd-analyzer/SKILL.md +131 -0
  55. package/plugins/req/skills/prd-edit/SKILL.md +201 -0
  56. package/plugins/req/skills/projects/SKILL.md +115 -0
  57. package/plugins/req/skills/quick-fix-guide/SKILL.md +51 -0
  58. package/plugins/req/skills/release/SKILL.md +300 -0
  59. package/plugins/req/skills/release-rationale/SKILL.md +213 -0
  60. package/plugins/req/skills/req/SKILL.md +173 -0
  61. package/plugins/req/skills/requirement-analyzer/SKILL.md +274 -0
  62. package/plugins/req/skills/review/SKILL.md +201 -0
  63. package/plugins/req/skills/review-pr/SKILL.md +699 -0
  64. package/plugins/req/skills/show/SKILL.md +302 -0
  65. package/plugins/req/skills/specs/SKILL.md +99 -0
  66. package/plugins/req/skills/split/SKILL.md +164 -0
  67. package/plugins/req/skills/status/SKILL.md +184 -0
  68. package/plugins/req/skills/test/SKILL.md +431 -0
  69. package/plugins/req/skills/test-guide/SKILL.md +304 -0
  70. package/plugins/req/skills/test_new/SKILL.md +417 -0
  71. package/plugins/req/skills/test_regression/SKILL.md +298 -0
  72. package/plugins/req/skills/update/SKILL.md +131 -0
  73. package/plugins/req/skills/update-template/SKILL.md +203 -0
  74. package/plugins/req/skills/upgrade/SKILL.md +178 -0
  75. package/plugins/req/skills/use/SKILL.md +158 -0
  76. package/plugins/req/skills/version-bumper/SKILL.md +113 -0
  77. package/plugins/uat/skills/bug/SKILL.md +153 -0
  78. package/plugins/uat/skills/init/SKILL.md +88 -0
  79. package/plugins/uat/skills/new/SKILL.md +131 -0
  80. package/plugins/uat/skills/report/SKILL.md +48 -0
  81. package/plugins/uat/skills/run/SKILL.md +78 -0
  82. package/plugins/uat/skills/uat/SKILL.md +64 -0
  83. package/plugins/uat/skills/uat-executor/SKILL.md +299 -0
@@ -0,0 +1,300 @@
1
+ ---
2
+ name: release
3
+ description: |
4
+ 颁布版本 - 合并 SQL、生成回滚、打 tag、创建 Release
5
+ ---
6
+
7
+ # 颁布版本
8
+
9
+ 准备发版产物(SQL 合并、回滚脚本、changelog、commit、PR)并**默认创建 draft Release**。加 `--tag` 额外创建 annotated git tag;加 `--no-draft` 直接正式发布。
10
+
11
+ > **Audience:** Engineer
12
+ > readonly 仓库可用。不触发缓存同步。
13
+ > CLI 优先:GitHub → `gh`;Gitea → 检测 `tea`,不支持的接口回退 curl。详见 [`_gitea_cli.md`](./_gitea_cli.md)。
14
+ > 设计原理和边界情况详见 [`release-rationale.md`](./release-rationale.md)。
15
+
16
+ ## 参数
17
+
18
+ | 参数 | 说明 |
19
+ |------|------|
20
+ | `<version>` | 可选,如 `v1.2.0`。不传则自动推导(见步骤 2) |
21
+ | `--bump=major\|minor\|patch` | 显式 bump 等级,与 `<version>` 互斥 |
22
+ | `--from=<ref>` | 起始点,默认上一个 git tag |
23
+ | `--to=<ref>` | 结束点,默认 HEAD |
24
+ | `--tag` | **额外**创建并推送 annotated git tag(Release 始终创建) |
25
+ | `--no-draft` | 创建正式 Release(默认 draft) |
26
+ | `--no-release` | 跳过创建平台 Release,仅准备产物和 PR |
27
+ | `--main=<branch>` | 临时覆盖 `branchStrategy.mainBranch` |
28
+
29
+ 示例:
30
+ - `/req:release`(准备产物 + PR + draft Release,不打 tag)
31
+ - `/req:release --tag`(同上 + annotated tag)
32
+ - `/req:release --no-draft`(正式 Release,无 tag)
33
+ - `/req:release --tag --no-draft`(正式 Release + tag)
34
+ - `/req:release v1.2.0`(显式版本 + draft Release)
35
+
36
+ ## 起步分支速查
37
+
38
+ | 策略 | 必须从此分支运行 | 流程模式 | PR2 回流 |
39
+ |------|----------------|---------|---------|
40
+ | `git-flow`(develop 起步) | `developBranch` | cross-branch | ❌ 无需(develop 已有准备 commit) |
41
+ | `git-flow`(发布分支起步) | `release/*` / `chore/release-*` | release-branch | ✅ release → develop |
42
+ | `github-flow` / `trunk-based` | `mainBranch` | direct | ❌ 无需回流 |
43
+ | 未配置 | 按当前分支名判断 | 同上规则 | ❌ 无需回流 |
44
+
45
+ ---
46
+
47
+ ## 执行流程
48
+
49
+ ### 步骤 0:角色检查 + 目录配置 + 项目发版规则
50
+
51
+ **读取项目发版规则**:若 `docs/prompt/release.md` 存在,立即 Read 并提取三个变量(后续步骤使用):
52
+
53
+ - `PRE_RELEASE_CHECKS`:「发版前检查」章节的内容,步骤 0.5 执行
54
+ - `POST_RELEASE_NOTES`:「发版后步骤」章节的内容,步骤 16 追加到最终报告
55
+ - `EXTRA_ASSETS`:「额外附件」章节的 glob 列表,步骤 12 上传时合并进去
56
+
57
+ 文件不存在时三个变量均为空,跳过对应行为,不打印任何提示。
58
+
59
+ 读取 `.claude/settings.local.json` 中的 `requirementRole`:
60
+
61
+ - **readonly**:
62
+ - 从全局缓存 `~/.claude-requirements/projects/<requirementProject>/` 读取需求文档
63
+ - **禁止修改任何 `docs/requirements/` 下的文件**(包括状态更新、关联信息追加等)
64
+ - SQL 合并(`<MIGRATIONS_DIR>/released/`)和 changelog(`docs/changelogs/`)的写入**不受此限**——这些是版本产物,不是需求文档;目录不存在时自动创建
65
+ - 其余步骤(git commit、PR、tag)照常执行
66
+ - **primary / 未配置**:正常读写本地 `docs/requirements/`
67
+
68
+ **目录变量解析**:
69
+
70
+ - `CHANGELOG_DIR`:固定为 `docs/changelogs`,不需要配置
71
+ - `MIGRATIONS_DIR`:按优先级解析,后续步骤统一使用此变量
72
+
73
+ | 优先级 | 来源 | 方式 |
74
+ |--------|------|------|
75
+ | 1 | 项目内配置 | Read `.claude/skills/migration.md`,解析其中的 `MIGRATIONS_DIR` 行 |
76
+ | 2 | 自动检测 | 扫描 `db/migrations`、`database/migrations`、`migrations`、`src/migrations`,取第一个存在的 |
77
+ | 3 | 兜底默认 | `docs/migrations` |
78
+
79
+ **「后端项目 + 未配置优先级 1」时**,打印一次警告(非阻塞,继续执行):
80
+
81
+ > 后端项目判断依据:存在 `.sql` 文件或 migration 相关目录。
82
+
83
+ ```
84
+ ⚠️ 未找到 .claude/skills/migration.md,当前使用 MIGRATIONS_DIR=<auto-detected or default>
85
+ 如需固定路径,创建 .claude/skills/migration.md 并写入:
86
+ - **MIGRATIONS_DIR**: `<路径>`
87
+ ```
88
+
89
+ ### 步骤 0.5:发版前检查(仅 `PRE_RELEASE_CHECKS` 非空时执行)
90
+
91
+ 逐条执行检查命令,打印每条结果(✅ 通过 / ❌ 失败)。**任意一条失败则硬停止**,不进入后续步骤。
92
+
93
+ ```
94
+ 发版前检查:
95
+ ✅ npm test
96
+ ❌ npm run build(exit 1)
97
+
98
+ 发版中止。请修复后重新运行。
99
+ ```
100
+
101
+ ### 步骤 1:参数校验 + 分支判定
102
+
103
+ 1. `<version>` 与 `--bump` 互斥,否则报错退出
104
+ 2. `create_tag = bool(args.tag)`;`skip_release = bool(args.no_release)`
105
+ 3. 读 `branchStrategy`:`strategy_model / main_branch / develop_branch / repo_type`
106
+ 4. **策略合规检查**(无确认,直接阻止):
107
+ - `git-flow` + 当前在 `main_branch`:自动 `git checkout <develop_branch>`,提示重跑,exit
108
+ - `github-flow` / `trunk-based` + 当前不在 `main_branch`:硬阻止
109
+ 5. **流程模式**:当前 == `main_branch` → `direct`;`release/*` / `chore/release-*` → `release-branch`;`develop_branch` → `cross-branch`;其他 → 硬阻止
110
+ 6. **draft 初始化**(`skip_release=false` 时执行):
111
+ - `is_draft = not args.no_draft`
112
+ - `repoType=other` + draft:询问是否降级 `--no-draft`(**强制交互**)
113
+ - cross/release-branch + `--no-draft`:额外确认(**强制交互**)
114
+ - `repoType=gitea` + draft + `!create_tag`:Gitea draft Release 要求 tag 先存在,但未指定 `--tag`;询问(**强制交互**):
115
+ - [1] 改用 `--no-draft`:Gitea API 自动从 `target_commitish` 生成 lightweight tag,创建正式 Release
116
+ - [2] 加 `--tag`:额外创建 annotated tag,继续 draft Release
117
+ - [3] 取消
118
+
119
+ 打印:`当前分支 / 策略 / 流程模式 / create_tag / skip_release / is_draft`
120
+
121
+ ### 步骤 2:确定版本号和 git 范围
122
+
123
+ **基线版本来源**(按优先级,取第一个成功的):
124
+
125
+ 1. **平台 Release**:查询最新已发布 Release 的 tag
126
+ - GitHub:`gh release list --limit 1 --exclude-drafts --exclude-pre-releases`,取 `Tag` 列
127
+ - Gitea:调用 `/releases?limit=1&draft=false&pre-release=false`,取 `tag_name`
128
+ - 查询失败(网络、未初始化)→ 打印一行警告,降级到来源 2
129
+ 2. **git tag**:`git tag --sort=-v:refname` 中最新的 semver tag
130
+ 3. **兜底**:视为首次发版
131
+
132
+ **范围**:`FROM_REF`(基线 tag,无则仓库首次 commit)→ `TO_REF`(`--to` 或 HEAD)
133
+
134
+ **版本推导**(未传 `<version>` 时执行):
135
+ - 无基线 → 首发 `v0.1.0`
136
+ - 基线 tag 非 X.Y.Z semver → 阻断,提示显式传版本号
137
+ - 扫描 `FROM_REF..TO_REF` commits,按优先级 bump:`!:` / `BREAKING CHANGE` → major;`feat:` → minor;`fix:/perf:/refactor:` → patch;仅 chore/docs/style/test/ci → 阻断
138
+ - `--bump` 存在时直接用,跳过扫描
139
+ - 打印 `基线来源(Release/tag/首次)/ 基线版本 / 推导版本 / 推导依据`,**自动使用推导结果**(如需覆盖请显式传参)
140
+
141
+ ### 步骤 2.5:更新版本号文件
142
+
143
+ 读取 `docs/prompt/release.md` 中的「版本号文件」章节,按其规则更新对应文件并暂存(在步骤 10 的统一 commit 中一起提交)。章节不存在时跳过。
144
+
145
+ ### 步骤 3:扫描候选需求
146
+
147
+ 扫描 `$FROM_REF..$TO_REF` 范围内(不含 merge commit)的 commit subject + body,提取所有 `REQ-XXX` / `QUICK-XXX` 编号(去重)。读取每个需求文档,提取标题/类型/状态/关联 SQL 文件数。
148
+ - **primary**:从 `docs/requirements/` 读取
149
+ - **readonly**:从 `~/.claude-requirements/projects/<requirementProject>/` 读取;不存在则跳过该需求,继续纯 commit changelog 流程
150
+
151
+ ### 步骤 4:扫描 migration SQL
152
+
153
+ 扫描 `$MIGRATIONS_DIR`(不含 `released/` 子目录)下的 `.sql` 文件,文件名含 `REQ-XXX` / `QUICK-XXX` 即归属对应需求。
154
+
155
+ ### 步骤 5:自动选择需求
156
+
157
+ - `已完成` 需求:自动纳入,打印清单
158
+ - 其他状态:展示后询问一次 `[y/N]`(本步唯一交互点)
159
+ - 无需求:继续纯 commit changelog 流程
160
+
161
+ ### 步骤 6:打印产物预览,自动继续
162
+
163
+ 打印:`flow_mode / draft / create_tag / bump_reason / 将产出的文件 / 分支操作计划 / tag + Release 计划`
164
+
165
+ 自动继续(如需中止请按 Ctrl+C)。
166
+
167
+ ### 步骤 7:合并 SQL(有 SQL 时执行)
168
+
169
+ 输出 `$MIGRATIONS_DIR/released/<version>.sql`,文件头注释含 Release/Date/Range/Includes,每段前加来源注释,按选中顺序排列。
170
+
171
+ **写入成功后立即 `git rm` 所有已合并的源 SQL 文件**(详见 rationale §10),放入暂存区由后续 commit 统一提交。
172
+
173
+ ### 步骤 8:生成回滚 SQL
174
+
175
+ 输出 `$MIGRATIONS_DIR/released/<version>.rollback.sql`,按倒序排列(后建的先回滚)。对每条 DDL 生成语义相反的回滚语句;INSERT / UPDATE / DELETE / DROP / 复杂 ALTER 无法自动推导的,输出 `-- ⚠️ 需手动补充:<原语句首 80 字>`。记录待补充数量,最终报告中提示。
176
+
177
+ ### 步骤 9:生成 changelog
178
+
179
+ 执行 `/req:changelog` 核心逻辑,写入 `docs/changelogs/<version>.md`(已存在则覆盖)。
180
+
181
+ ### 步骤 10:提交产物 + 推送 + PR
182
+
183
+ **direct**:暂存所有产物 → commit(消息:`chore(release): prepare <version>`),进入步骤 11。
184
+
185
+ **cross-branch**:
186
+ 1. commit(同上)+ push `<develop_branch>`
187
+ 2. 创建 PR:`<develop_branch>` → `<main_branch>`(复用 `state=open` 的 PR,不复用 merged/closed,详见 rationale §7.3);`other` → 打印命令后终止
188
+ - Body:需求清单 + changelog 摘要
189
+ 3. **自动合并 PR**;合并失败(分支保护/CI 未通过)→ 打印 PR URL,等待用户手动合并后回复「继续」(**强制交互**)
190
+ 4. 切到 `<main_branch>` 并 fast-forward pull(验证 `docs/changelogs/<version>.md` 存在,异常见 rationale §7.4)
191
+ 5. 继续步骤 11(若 `create_tag`)和步骤 12(若 `!skip_release`)
192
+
193
+ **release-branch**:同 cross-branch,但 PR1 是 `<release_branch>` → `<main_branch>`,PR2(步骤 14)同样自动合并。
194
+
195
+ ### 步骤 10.9:主分支强制验证(步骤 11/12 前必须通过)
196
+
197
+ **无论 flow_mode 是 direct / cross-branch / release-branch,执行 tag 或 Release 前必须硬性确认当前在 `main_branch` 上。** 若不在,打印错误(当前分支 / 主分支名 / 手动切换命令),硬停止——不自动切换。
198
+
199
+ `target_commitish` 后续所有步骤统一使用 `main_branch`,**不使用 develop / release 分支**。
200
+
201
+ ### 步骤 11:创建 Git Tag(仅 `--tag`)
202
+
203
+ 确认当前在 `main_branch`(步骤 10.9 已保证)。`push_tag_first` 决策(详见 rationale §6):
204
+
205
+ | 组合 | push_tag_first | 行为 |
206
+ |------|---------------|------|
207
+ | draft + gitea | true | annotated tag + push(Gitea draft 要求先存在) |
208
+ | draft + github | false | 不创建,gh release 懒创建 |
209
+ | 正式 + gitea | false | 不创建,API 从 target_commitish 生成 |
210
+ | 正式 + github / other | true | annotated tag + push |
211
+
212
+ ### 步骤 12:创建平台 Release(`skip_release=false` 时执行)
213
+
214
+ Release notes 取 `docs/changelogs/<version>.md`。**`target_commitish` 固定为 `main_branch`(由步骤 10.9 保证),绝不使用 develop / release 分支。**
215
+
216
+ - **gitea**:解析 remote URL,读 `branchStrategy.giteaToken`,调用 Releases API;body 必须用 `jq --rawfile` 从文件构造(不手工拼接字符串,避免 emoji 编码损坏,详见 rationale §11);`target_commitish` 固定为 `main_branch`;成功后上传 SQL 资产
217
+ - **github**:`gh release create`,带 `--target <main_branch>` 和 changelog 文件,酌情加 `--draft` 和 SQL 附件
218
+ - **other**:打印手动命令
219
+
220
+ 已存在(HTTP 409)时打印链接,不重复创建。
221
+
222
+ ### 步骤 13:切回起始分支
223
+
224
+ 切回步骤 0 记录的起始分支。
225
+
226
+ ### 步骤 14:PR2 回流到 `developBranch`(仅 release-branch 模式)
227
+
228
+ **触发条件**:`flow_mode == "release-branch"`。
229
+
230
+ **PR2 方向**:`<release_branch>` → `<develop_branch>`
231
+
232
+ - 标题:`chore(release): backmerge <version> → <develop_branch>`
233
+ - Body:说明回流目的(使下次 release 不重复产物)+ tag 已落在 `<main_branch>`
234
+ - 等待用户确认(**非阻塞**,可跳过);跳过时最终报告标记
235
+
236
+ ### 步骤 15:清理 release 分支(仅 release-branch)
237
+
238
+ PR2 merged → 删除本地和远程 release 分支(remote ref 不存在视为成功)。PR2 pending 时保留分支。
239
+
240
+ ### 步骤 16:最终报告
241
+
242
+ **16a 正式 Release(`--no-draft`)**:
243
+ ```
244
+ ✅ 版本 <version> 已颁布!
245
+ 需求清单 / SQL 脚本 / 版本说明
246
+ <若 --tag:✅ annotated tag 已推送 | 否则:— 无本地 tag(平台自动生成 lightweight tag)>
247
+ <Release URL>
248
+ 检查回滚 SQL:cat $MIGRATIONS_DIR/released/<version>.rollback.sql
249
+ ```
250
+
251
+ **16b draft Release(默认)**:
252
+ ```
253
+ ⚠️ DRAFT:<version> 草稿已创建,需手工 Publish
254
+ //同上
255
+ <若 --tag:gitea: ✅ annotated tag 已推 | github: ⚠️ publish 时生成 | 否则:— 无本地 tag>
256
+ <Draft Release URL>(仅作者/管理员可见)
257
+ ⚠️ 未 publish 前:CI/CD 不触发,release 不可见
258
+ 放弃:gitea 需删 draft(若有 tag 一并删);github 只删 draft
259
+ ```
260
+
261
+ **16c 跳过 Release(`--no-release`)**:
262
+ ```
263
+ ✅ 版本 <version> 产物已就绪!
264
+ //同上
265
+ <若 --tag:✅ tag 已推送 | 否则:— 无 tag>
266
+ — 已跳过(--no-release)
267
+ PR: <PR URL>(等待合并到 <main_branch>)
268
+ ```
269
+
270
+ **发版后步骤**(`POST_RELEASE_NOTES` 非空,且非 draft 模式时追加):
271
+ ```
272
+ 发版后待办:
273
+ <POST_RELEASE_NOTES 内容逐条列出>
274
+ ```
275
+
276
+ ---
277
+
278
+ ## 边界情况
279
+
280
+ 完整速查见 [rationale §12](./release-rationale.md):
281
+
282
+ | 场景 | 处理 |
283
+ |------|------|
284
+ | feat/fix/hotfix/* 等分支 | 硬阻止 |
285
+ | git-flow + 在主分支 | 自动切 develop,提示重跑 |
286
+ | github-flow/trunk-based + 非主分支 | 硬阻止 |
287
+ | PR 未合并用户中止 | 保留已生成产物,不打 tag |
288
+ | 无 candidate 需求 / 全跳过未完成 | 继续纯 commit changelog |
289
+ | `--no-draft` 未指定 `--tag` | 正常执行,Release 公开,平台生成 lightweight tag |
290
+ | 未指定 `--tag` | 仅跳过步骤 11(annotated tag),Release 照常创建 |
291
+ | `--no-release` | 跳过步骤 12,仅准备产物和 PR |
292
+ | `repoType=gitea` + draft + 无 `--tag` | 步骤 1 强制交互,选择降级 --no-draft 或补 --tag |
293
+ | git-flow + release-branch | 步骤 14 自动创建 PR2:`release分支 → developBranch`,同步准备 commit |
294
+ | git-flow + cross-branch | 步骤 14 跳过,develop 已有准备 commit,无需回流(`main → develop` 会产生循环 merge) |
295
+ | Gitea draft 422(Release is has no Tag) | 详见 rationale §12 |
296
+ | `--draft`(老语法) | 接受但忽略(默认已是 draft) |
297
+
298
+ ## 用户输入
299
+
300
+ $ARGUMENTS
@@ -0,0 +1,213 @@
1
+ ---
2
+ name: release-rationale
3
+ description: |
4
+ release-rationale
5
+ ---
6
+
7
+ # /req:release 设计原理与边界情况
8
+
9
+ > 本文档是 `release.md` 的伴随文档。记录复杂决策的"为什么"和完整的边界情况查询表。
10
+ >
11
+ > **正常发版流程不需要读本文档**。AI 在以下情况按需查阅:
12
+ > - 用户追问"为什么这样设计"
13
+ > - 出错时根据 §6 边界情况速查表定位处理方式
14
+ > - 修改 release 命令前需要理解决策依据
15
+
16
+ ---
17
+
18
+ ## 1. 为什么 draft 是默认模式
19
+
20
+ 从 v2 开始 `/req:release` 默认创建 Gitea / GitHub draft release——对外不可见、CI/CD 不触发,需手工在平台点 Publish 才正式发版。
21
+
22
+ **设计原因**:
23
+ - 发布的大部分步骤不可逆(commit、push、tag、平台 Release),人工 gate 让用户最后有机会检查 release notes / 资产文件 / 版本范围
24
+ - 与 git-flow cross-branch 流程天然配合——一次 PR gate(merge 到主分支)+ 一次 draft gate(平台 publish),两道闸门都过了才真正对外发版
25
+
26
+ 不想 draft 就加 `--no-draft`。老的 `--draft` 作为冗余别名接受但无效果(向前兼容)。
27
+
28
+ ---
29
+
30
+ ## 2. 为什么不静默降级 `repoType=other` 的 draft
31
+
32
+ 旧版本会在 `repoType=other` 时静默把 `is_draft` 改成 false。这会让用户在"以为创建了 draft,实际已经 push 了 tag 并创建了正式 release"的状态下惊讶。
33
+
34
+ v3.0.0+ 改为强制交互确认,把"我要放弃 draft 闸门"这个决定变成明确的用户动作,避免发版事故。
35
+
36
+ ---
37
+
38
+ ## 3. 流程模式(cross-branch vs release-branch)选择
39
+
40
+ | 维度 | cross-branch | release-branch |
41
+ |------|-------------|---------------|
42
+ | 起步分支 | `developBranch` | `release/<version>` 或 `chore/release-*` |
43
+ | PR 数量 | 1(develop → main) | 2(release → main,release → develop 回流) |
44
+ | 适用场景 | develop 到 main 的 delta 就是本次要发的全部内容 | develop 上已积累未准备发布的 feature,需要隔离发布内容 |
45
+ | tag 落点 | 主分支 HEAD | 主分支 HEAD(PR1 合并后) |
46
+
47
+ **判断方法**:如果 develop 到 main 的 delta 没有未准备发布的 feature 堆积,cross-branch 更简单;否则必须用 release-branch 隔离要发的部分,否则 tag 会带上未准备发布的代码。
48
+
49
+ ---
50
+
51
+ ## 4. 为什么 git-flow 的主分支不允许 direct 模式
52
+
53
+ git-flow 的 `mainBranch` 通常设有保护规则。step 8.8 的 `chore(release): prepare` 提交会被直接 push 拒绝,用户被迫手动 reset → 切 develop → cherry-pick → 新 PR → 等合并 → 回主分支 → tag,绕一大圈。
54
+
55
+ 步骤 1(策略合规检查)的守门会把这种"误起步"在最早环节挡下,给出 cross-branch / release-branch 两种推荐路径。
56
+
57
+ ---
58
+
59
+ ## 5. draft 模式行为矩阵详解
60
+
61
+ | `is_draft` | 最终行为 | 典型触发 | 不可逆程度 |
62
+ |----|----|----|----|
63
+ | `false` | 本地创建 annotated tag → push tag → 平台正式 Release(对外可见) | `--no-draft` | **最强**,tag + 外部 release 双重不可逆 |
64
+ | `true` | 平台 **draft** Release(作者可见,需手工 publish);Gitea 需先 push tag,GitHub 懒创建 tag | **默认行为** | Gitea 中(需清理 tag + draft)/ GitHub 低(仅删 draft) |
65
+
66
+ ### 5.1 draft 模式下的 tag 行为按 `repoType` 分叉
67
+
68
+ - **gitea**:Release API 要求 tag 必须先存在(否则返回 `Release is has no Tag`),所以 **draft 模式也会先 push annotated tag**。放弃 draft 时需一并清理 tag:`git push --delete origin <tag> && git tag -d <tag>`
69
+ - **github**:`gh release create --draft --target <SHA>` 懒创建 tag(publish 时平台创建 lightweight tag),本地 + 远程都没有 tag,放弃仅需删 draft
70
+ - **other**:draft 模式在 step 1.5 已降级为 `--no-draft`,不进入该分叉
71
+
72
+ ### 5.2 危险组合:`is_draft=false` 在 developBranch / release 分支
73
+
74
+ 意味着你正在跳过 draft 闸门直接走 cross-branch / release-branch 发布。是合法但少见的组合,step 1.6 末尾的额外 y/n 就是为它准备的闸门。
75
+
76
+ 常见误操作:原本只是想 dry-check 发版流程却覆盖了 draft 默认。
77
+
78
+ ---
79
+
80
+ ## 6. `push_tag_first` 决策矩阵的四个为什么
81
+
82
+ 矩阵本身见 release.md §9。这里解释每个分支的设计依据。
83
+
84
+ ### 6.1 为什么非 draft + github 要先 push tag
85
+
86
+ GitHub Release API 在 tag 不存在时会用 `target_commitish` 现场创建 tag。如果 `target_commitish` 缺省为默认分支(可能是 develop),tag 就被打错。先推已存在的 annotated tag,API 直接引用,不再创建。
87
+
88
+ ### 6.2 为什么非 draft + gitea 不 push tag
89
+
90
+ Gitea Release API 在 `draft=false` 时会从 `target_commitish` 现场生成 lightweight tag,而步骤 12(创建平台 Release)显式把 `target_commitish` 设为 `tag_target`(主分支名),不会打错分支。
91
+
92
+ 本地再 push annotated tag 是**冗余**——会出现"本地 annotated、远程 lightweight"两种类型 tag 同名冲突(或者被 push 覆盖)。省掉这一步既简化流程、又与用户直觉一致(tag 在创建 Release 时一并出现)。
93
+
94
+ ### 6.3 为什么 Gitea 的 draft 模式反而要先 push tag
95
+
96
+ Gitea Release API 在 `draft=true` 时不会为你创建 tag——必须先存在,否则返回 `422 Release is has no Tag`。
97
+
98
+ 步骤 11(创建 Git Tag)的本地 `git push origin <tag>` 如果失败或未执行,就会触发此错误。排查:`git ls-remote --tags origin | grep <version>` 确认远程是否有 tag;检查 Gitea 对 tag 是否配了保护规则。
99
+
100
+ 这是 Gitea 与 GitHub 的关键差异(GitHub 的 draft release 可以引用未来的 tag,Gitea 不可以)。因此 Gitea 上的 draft 闸门价值比 GitHub 弱一些:放弃 draft 需同时清理 tag。
101
+
102
+ ### 6.4 为什么 GitHub 的 draft 模式不 push tag
103
+
104
+ 1. GitHub Release API / `gh --draft` 允许 tag 在 publish 时才创建——这是 draft 的核心价值
105
+ 2. draft 的 `target_commitish` 传主分支名(`tag_target`),publish 时 tag 打在该分支最新 HEAD 上。比 SHA 更直观方便,适合 draft 创建后很快 publish 的场景
106
+ 3. 本地如果先 `git tag -a` 会和平台最终创建的 lightweight tag 类型冲突,push 时还会被拦或产生 divergent 状态——干脆完全不碰本地 tag
107
+
108
+ ---
109
+
110
+ ## 7. 跨分支 / 发布分支流程的设计要点
111
+
112
+ ### 7.1 cross-branch:提交顺序的关键性
113
+
114
+ 必须先完成"提交产物到 develop 并 push",再创建/复用 PR。这样 PR 的 head 就包含 changelog,合并后主分支就有 changelog,不会出现 step 8.5 步骤 4 的 "changelog 不存在" 错误。
115
+
116
+ ### 7.2 release-branch:为什么 PR1 在前,PR2 在后
117
+
118
+ 先合 PR1 保证 tag 落在干净的 main HEAD 上(只含本次发布内容);PR2 把 changelog/SQL/回滚脚本回流到 develop,让下次 release 不重复产出这些文件。
119
+
120
+ 顺序颠倒会导致 tag 被 develop 上未准备发布的 feature 污染。
121
+
122
+ ### 7.3 PR 复用的状态过滤
123
+
124
+ 查询 PR 必须过滤 `state=open` 才能复用。`merged`/`closed` 状态的 PR 编号 **绝不能复用**——会出现"复用了已合并 PR 编号但 head 指向旧 commit"的情况,导致主分支拉下来缺 changelog。
125
+
126
+ ### 7.4 PR 合并后主分支验证
127
+
128
+ `git pull --ff-only` 之后必须验证 `test -f docs/changelogs/<version>.md`。若文件不存在说明 PR 未真正合并或合并的是旧版本。此时:
129
+
130
+ - **不得**尝试 `git checkout develop -- docs/changelogs/<version>.md` 这种补丁式操作
131
+ - **不得**直接 push 主分支
132
+ - 警告用户 PR 状态异常,回到创建 PR 步骤重新创建新 PR
133
+
134
+ ---
135
+
136
+ ## 8. 版本号自动推导背景
137
+
138
+ v3.0.0 前版本号必须由用户手写,容易出错——尤其 minor vs patch 的判断、格式 `v` 前缀一致性。step 2.5 基于最近一个 git tag + 范围内 conventional commits 自动推导下一个 semver,同时保留 `<version>` / `--bump` 两级覆盖路径。
139
+
140
+ ### 8.1 v 前缀规范化规则
141
+
142
+ - 基线 `v3.0.0` + 用户输入 `3.1.0` → 自动补齐为 `v3.1.0`
143
+ - 基线 `3.0.0` + 用户输入 `v3.1.0` → 自动去除为 `3.1.0`
144
+ - 首发场景(`base_tag is None`,默认 `has_v_prefix=True`)→ 按 v 前缀规范化
145
+
146
+ 同一仓库不混用两种格式。
147
+
148
+ ### 8.2 仅识别严格 X.Y.Z core semver
149
+
150
+ 不识别 prerelease 后缀(如 `v1.2.0-rc.1`)。带 prerelease 后缀的基线 tag 会被拒绝,需要显式指定版本号或先打一个规范 tag 作为基线。
151
+
152
+ ### 8.3 chore-only 范围拒绝自动发版
153
+
154
+ 若 `base_tag..to_ref` 范围内 commits 只包含 chore/docs/style/test/ci 类型,拒绝自动发版以避免无意义的版本号累积。用户可显式指定 `<version>` 或 `--bump=patch` 强制发版。
155
+
156
+ ---
157
+
158
+ ## 9. 步骤 6 产物预览的存在意义
159
+
160
+ 发布的大部分动作都不可逆(提交、push、tag、平台 Release)。用户必须在实际执行前看清所有将要发生的事——尤其是在 `--no-draft`、`cross-branch` 这些会改变最终行为的条件下,用户的心智模型和命令默认行为常常不一致。明确的预览消除"以为会 X,实际做了 Y"的事故。
161
+
162
+ 预览**必须完整渲染**,但不等待用户 y/n 确认——自动继续执行 step 6+。用户如需中止请按 Ctrl+C。如需覆盖版本号请在命令中显式传参(`/req:release v1.4.0` 或 `--bump=minor`)。
163
+
164
+ ---
165
+
166
+ ## 10. SQL 文件删除的设计
167
+
168
+ step 6.5 在合并 SQL 后立即 `git rm` 源文件。设计原因:
169
+
170
+ - 已合并的 SQL 不应保留在 `docs/migrations/` 顶层,否则下次 release 会重复扫描到
171
+ - 用 `git rm` 而非 `rm` 是为了把删除放进暂存区,让步骤 10 各分支流程(direct/cross-branch/release-branch)一次性 commit 干净
172
+ - 仅删除**被选中并成功合并**的文件,未选中需求的 SQL 保留——给用户"分批发版"的灵活性
173
+ - 若 `released/<version>.sql` 写入失败则不得执行,避免源文件丢失
174
+
175
+ ---
176
+
177
+ ## 11. Gitea Release API 的 emoji 处理
178
+
179
+ body 必须用 `jq --rawfile body <path>` 从文件构造 JSON,**不要手工拼接 JSON 字符串**,否则 emoji(、、等 4 字节 UTF-8)会在 shell 双引号转义过程中退化成 `�` replacement char。
180
+
181
+ curl 用 `--data-binary @file` 上传,按二进制流,不做换行/编码转换。Header 显式声明 `Content-Type: application/json; charset=utf-8`。
182
+
183
+ ---
184
+
185
+ ## 12. 边界情况速查表
186
+
187
+ | 场景 | 处理方式 |
188
+ |------|---------|
189
+ | 当前在 feat/* / fix/* / hotfix/* 等 | **硬阻止**,提示切换到 `mainBranch` / `release/*` / `chore/release-*` / `developBranch` |
190
+ | 在 `release/*` / `chore/release-*` | 走 release-branch 流程,双 PR:release→main 先合+打 tag,release→develop 后合回流 |
191
+ | 在 `developBranch` | 走 cross-branch 流程,单 PR:develop→main,合并后在主分支打 tag |
192
+ | 跨分支流程中 PR 未合并用户中止 | 保留已生成的 SQL/changelog/PR,不打 tag |
193
+ | 跨分支流程中主分支 pull 后找不到合并提交 | 警告后重新等待用户确认 |
194
+ | release-branch 流程 PR1 未合并用户中止 | 保留已生成的 SQL/changelog/PR1,不打 tag,PR2 也不发 |
195
+ | release-branch 流程 PR2 用户选"跳过" | tag 和 Release 已完成,命令直接进入最终报告;PR2 保留等用户手动合并,报告中标记 待合并 |
196
+ | 没有 git tag | 从首次提交开始,显示警告 |
197
+ | 范围内无 commit | 终止操作 |
198
+ | 范围内无候选需求 | 提示后自动继续(仅打 tag + 纯 commit changelog) |
199
+ | git 范围内只有未完成需求 | 询问一次是否纳入;全部跳过则继续纯 commit changelog 流程 |
200
+ | 选中需求都无 SQL | 跳过 SQL 步骤,仅执行 changelog/tag/release |
201
+ | `docs/migrations/released/<version>.sql` 已存在 | Hook 弹确认 |
202
+ | `docs/changelogs/<version>.md` 已存在 | Hook 弹确认 |
203
+ | git tag 已存在 | 提示已存在,询问是否跳过 tag 步骤继续 |
204
+ | Gitea token 缺失 | 跳过 Release,保留 tag |
205
+ | gh CLI 缺失 | 输出命令让用户手动执行 |
206
+ | `repoType` 未配置 | 仅输出手动命令 |
207
+ | 默认 draft 模式 + `repoType == other` | 步骤 1(参数校验)强制交互确认降级为 `--no-draft`(不再静默降级),用户取消则中止 |
208
+ | draft 模式 draft 创建成功但 release notes 错误 | 在平台编辑 draft,或删除 draft 后重跑命令(gitea 场景需同时删 tag:`git push --delete origin <version> && git tag -d <version>`;github 场景 draft 一删即清) |
209
+ | draft 模式下 draft 创建后用户迟迟未 publish | 命令已终止,责任在用户。建议记在团队 checklist 里,或用 cron 巡检未 publish 的 draft |
210
+ | Gitea Release API 返回 `Release is has no Tag`(422) | 仅发生在 **draft + gitea** 场景(此时 `PUSH_TAG_FIRST=true`)。步骤 11(创建 Git Tag)的本地 `git push origin <tag>` 失败或未执行。排查:`git ls-remote --tags origin \| grep <version>` 确认远程是否有 tag;检查 Gitea 对 tag 是否配了保护规则拦截了 push。非 draft + gitea 不会触发此错(API 从 target_commitish 自己生成 tag) |
211
+ | `--no-draft` 在受保护主分支 + cross-branch/release-branch 流程 | **按 repoType 分叉**:<br>• **github**:步骤 11 会本地 `git tag -a` + `git push origin <tag>`;若 GitHub 对 tag 配了保护规则,push 会失败。改默认 draft 模式同样 push(draft+github 是 `PUSH_TAG_FIRST=false`,不 push)——**推荐回到默认 draft 以绕开 tag 保护**<br>• **gitea**:步骤 11 **不** push tag(`PUSH_TAG_FIRST=false`),API 在服务器侧创建 lightweight tag。若 Gitea 对 tag 有保护规则,API 会返回权限错误。改回默认 draft 模式**无效**(draft+gitea 反而要 push tag),需先解除 tag 保护或用其他路径<br>• **other**:步骤 11 本地 + push,同 github 处理 |
212
+ | 用户传 `--draft`(老语法) | 接受但不报错,冗余别名;`args.draft` 变量不参与逻辑,`is_draft` 只看 `args.no_draft` |
213
+ | 未指定 `--tag` | 仅跳过步骤 11(annotated tag),Release(步骤 12)照常创建;最终报告走 §16b(draft)或 §16a(--no-draft) |