create-dwy 0.21.0 → 0.23.0

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 (35) hide show
  1. package/CHANGELOG.md +24 -0
  2. package/bin/index.js +6 -3
  3. package/package.json +1 -1
  4. package/src/cli-help.js +29 -13
  5. package/src/self-upgrade.js +7 -0
  6. package/src/skill-store.js +197 -0
  7. package/src/skills-install.js +47 -11
  8. package/src/sync-all.js +216 -605
  9. package/src/sync-codex.js +48 -25
  10. package/src/sync-global-skills.js +64 -14
  11. package/src/sync-opencode.js +9 -5
  12. package/src/sync-plan.js +278 -0
  13. package/src/sync.js +33 -18
  14. package/src/tab-multiselect.js +7 -5
  15. package/templates/ai-tools/AGENTS.md +1 -1
  16. package/templates/ai-tools/CHANGELOG.md +6 -0
  17. package/templates/ai-tools/VERSION +1 -1
  18. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/official/progr/sql/createExternalTable.md +2 -2
  19. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/official/tools/superset.md +1 -1
  20. package/templates/ai-tools/skills/DolphinDB/dwy-dolphindb/references/official/tutorials/Migrate_data_from_InfluxDB_to_DolphinDB.md +1 -1
  21. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/SKILL.md +18 -25
  22. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/android.md +3 -3
  23. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/backend-deploy.md +4 -4
  24. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/changelog.md +6 -70
  25. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/frontend-deploy.md +3 -3
  26. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/generic-release.md +5 -5
  27. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/git-tag.md +4 -79
  28. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/harmony.md +2 -2
  29. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/ios.md +3 -3
  30. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/publish-config.md +4 -4
  31. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-publish/references/version-sync.md +7 -7
  32. package/templates/ai-tools/skills//345/217/221/345/270/203/345/217/221/347/211/210/dwy-semver/SKILL.md +3 -7
  33. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/SKILL.md +58 -0
  34. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/references/delegation-guide.md +46 -0
  35. package/templates/ai-tools/skills//351/200/232/347/224/250/dwy-development-review/references/review-guide.md +57 -0
package/src/sync.js CHANGED
@@ -3,6 +3,7 @@ import path from 'path'
3
3
  import { isCancel } from '@clack/prompts'
4
4
  import { tabMultiselect } from './tab-multiselect.js'
5
5
  import { chalk, PACKAGE_ROOT } from './utils.js'
6
+ import { linkSkill, ensureSharedSkills, removeSkillEntry } from './skill-store.js'
6
7
 
7
8
  const CATEGORIES = [
8
9
  { key: 'skills', label: 'Skills' },
@@ -194,8 +195,9 @@ export function buildCategoryTabs(items, type) {
194
195
  if (!groups.has(category)) groups.set(category, [])
195
196
  groups.get(category).push({
196
197
  value: item.name,
197
- label: item.name,
198
+ label: item.label || item.name,
198
199
  description: item.description,
200
+ disabled: item.disabled,
199
201
  type,
200
202
  })
201
203
  }
@@ -209,21 +211,21 @@ export function buildCategoryTabs(items, type) {
209
211
  }
210
212
 
211
213
  /**
212
- * 顺序选择 Skills、Rules;Enter 进入下一类,不再提供回退或重选菜单。
213
- * 完整同步默认启用全部模板 Hooks;仅 Skills 模式不得写入其他配置。
214
+ * 只展开调用方点名的内容,Enter 进入下一类,不提供回退菜单。
214
215
  * 取消返回 null,调用方必须终止同步。
215
216
  *
216
217
  * @param {object} scans
217
218
  * @param {{ skills: Iterable<string>, rules: Iterable<string> }} existing
218
- * @param {{ skillsOnly?: boolean }} [opts]
219
+ * @param {{ skillsOnly?: boolean, types?: string[] }} [opts]
219
220
  */
220
- export async function interactiveSelect(scans, existing, { skillsOnly = false } = {}) {
221
- const selected = { skills: [], rules: [], commands: [], hooks: skillsOnly ? [] : scans.hooks }
222
- for (const { key, label } of skillsOnly ? CATEGORIES.slice(0, 1) : CATEGORIES) {
221
+ export async function interactiveSelect(scans, existing, { skillsOnly = false, types } = {}) {
222
+ const activeTypes = types || (skillsOnly ? ['skills'] : ['skills', 'rules', 'hooks'])
223
+ const selected = { skills: [], rules: [], commands: [], hooks: activeTypes.includes('hooks') ? scans.hooks : [] }
224
+ for (const { key, label } of CATEGORIES.filter(item => activeTypes.includes(item.key))) {
223
225
  const tabs = buildCategoryTabs(scans[key], key)
224
- const availableNames = new Set(scans[key].map(item => item.name))
226
+ const availableNames = new Set(scans[key].filter(item => !item.disabled).map(item => item.name))
225
227
  const names = await tabMultiselect({
226
- message: `选择 ${label}`,
228
+ message: `选择 ${label}(取消已有勾选将移除当前范围的托管项)`,
227
229
  tabs,
228
230
  initialValues: [...existing[key]].filter(name => availableNames.has(name)),
229
231
  required: false,
@@ -243,8 +245,7 @@ export function logAction(label, color = 'green', prefix = '✓') {
243
245
  async function copyItems(items, targetDir) {
244
246
  for (const item of items) {
245
247
  const dest = path.join(targetDir, item.type + 's', item.name)
246
- await fs.ensureDir(path.dirname(dest))
247
- await fs.copy(item.sourcePath, dest, { overwrite: true, filter: copyFilter(item.sourcePath) })
248
+ await linkSkill({ skill: item, dest, backupRoot: path.join(targetDir, '..', '.dwy', 'skill-backups') })
248
249
  logAction(`${item.type}s/${item.name}`)
249
250
  }
250
251
  return items.length
@@ -278,7 +279,11 @@ async function removeUnselected(typePlural, existingNames, selectedNames, templa
278
279
  // 项目独有(模板未提供过)的条目永不删除,保留用户自定义
279
280
  if (!templateNames.has(name)) continue
280
281
  const target = path.join(targetDir, typePlural, name)
281
- await fs.remove(target)
282
+ if (typePlural === 'skills') {
283
+ await removeSkillEntry({ dest: target, backupRoot: path.join(targetDir, '..', '.dwy', 'skill-backups') })
284
+ } else {
285
+ await fs.remove(target)
286
+ }
282
287
  logAction(`${typePlural}/${name}`, 'red', '×')
283
288
  count++
284
289
  }
@@ -400,6 +405,9 @@ export async function syncClaude({
400
405
  projectDir,
401
406
  global = false,
402
407
  selected,
408
+ homeDir,
409
+ sharedSkills,
410
+ syncTypes = ['skills', 'rules', 'hooks'],
403
411
  staleRemovals = {},
404
412
  } = {}) {
405
413
  const sourceDir = sourceDirOverride || await resolveSourceDir()
@@ -455,16 +463,23 @@ export async function syncClaude({
455
463
  let syncedCount = 0
456
464
  let removedCount = 0
457
465
 
458
- syncedCount += await copyItems(syncedSelection.skills, projectTargetDir)
459
- syncedCount += await syncSettings(sourceDir, projectTargetDir, selectedHookNames, global)
460
- syncedCount += await copyHooks(syncedSelection.hooks, projectTargetDir)
466
+ if (syncTypes.includes('skills')) {
467
+ const skills = sharedSkills
468
+ ? sharedSkills.filter(skill => selectedNames('skills').has(skill.name))
469
+ : await ensureSharedSkills({ skills: syncedSelection.skills, homeDir })
470
+ syncedCount += await copyItems(skills, projectTargetDir)
471
+ }
472
+ if (syncTypes.includes('hooks')) {
473
+ syncedCount += await syncSettings(sourceDir, projectTargetDir, selectedHookNames, global)
474
+ syncedCount += await copyHooks(syncedSelection.hooks, projectTargetDir)
475
+ }
461
476
 
462
477
  const skillTemplateNames = new Set([...scans.skills.map(s => s.name), ...approvedStaleSkills])
463
478
  const ruleTemplateNames = new Set([...scans.rules.map(r => r.name), ...approvedStaleRules])
464
479
  const hookTemplateNames = new Set([...scans.hooks.map(h => h.name), ...approvedStaleHooks])
465
- removedCount += await removeUnselected('skills', existing.skills, new Set(syncedSelection.skills.map(s => s.name)), skillTemplateNames, projectTargetDir)
466
- removedCount += await removeUnselected('rules', existing.rules, new Set(), ruleTemplateNames, projectTargetDir)
467
- removedCount += await removeUnselected('hooks', existing.hooks, selectedHookNames, hookTemplateNames, projectTargetDir)
480
+ if (syncTypes.includes('skills')) removedCount += await removeUnselected('skills', existing.skills, new Set(syncedSelection.skills.map(s => s.name)), skillTemplateNames, projectTargetDir)
481
+ if (syncTypes.includes('rules')) removedCount += await removeUnselected('rules', existing.rules, new Set(), ruleTemplateNames, projectTargetDir)
482
+ if (syncTypes.includes('hooks')) removedCount += await removeUnselected('hooks', existing.hooks, selectedHookNames, hookTemplateNames, projectTargetDir)
468
483
 
469
484
  console.log(chalk.blue(`\nDone! ${syncedCount} synced${removedCount > 0 ? `, ${removedCount} removed` : ''}.`))
470
485
  console.log(chalk.gray(` Project config → ${projectTargetDir}`))
@@ -72,8 +72,8 @@ function symbol(state) {
72
72
  */
73
73
  export function tabSelectionStats(tab, selectedValues) {
74
74
  const set = selectedValues instanceof Set ? selectedValues : new Set(selectedValues)
75
- const total = tab.options.length
76
- const selected = tab.options.filter(option => set.has(option.value)).length
75
+ const total = tab.options.filter(option => !option.disabled).length
76
+ const selected = tab.options.filter(option => !option.disabled && set.has(option.value)).length
77
77
  return { selected, total }
78
78
  }
79
79
 
@@ -211,7 +211,8 @@ class TabMultiSelectPrompt extends Prompt {
211
211
  constructor(opts) {
212
212
  super(opts, false)
213
213
  this.tabs = opts.tabs
214
- this.value = [...(opts.initialValues ?? [])]
214
+ const selectable = new Set(this.tabs.flatMap(tab => tab.options.filter(option => !option.disabled).map(option => option.value)))
215
+ this.value = (opts.initialValues ?? []).filter(value => selectable.has(value))
215
216
  this.cursors = this.tabs.map(() => 0)
216
217
  this.tabIndex = firstTabWithSelection(this.tabs, this.value)
217
218
  this.on('cursor', (action) => this.#onCursor(action))
@@ -248,7 +249,7 @@ class TabMultiSelectPrompt extends Prompt {
248
249
  /** Space:只动光标那一条。 */
249
250
  toggleValue() {
250
251
  const option = this.currentOptions[this.cursor]
251
- if (!option) return
252
+ if (!option || option.disabled) return
252
253
  const selected = this.value.includes(option.value)
253
254
  this.value = selected
254
255
  ? this.value.filter(value => value !== option.value)
@@ -257,7 +258,7 @@ class TabMultiSelectPrompt extends Prompt {
257
258
 
258
259
  /** a:当前 Tab 全选或全不选。 */
259
260
  toggleAllCurrentTab() {
260
- const tabValues = this.currentOptions.map(option => option.value)
261
+ const tabValues = this.currentOptions.filter(option => !option.disabled).map(option => option.value)
261
262
  this.value = toggleTabValues(this.value, tabValues)
262
263
  }
263
264
  }
@@ -384,6 +385,7 @@ export function tabMultiselect(opts) {
384
385
  label: opt.label ?? String(opt.value ?? ''),
385
386
  selected: selected.includes(opt.value),
386
387
  active,
388
+ disabled: opt.disabled,
387
389
  }),
388
390
  })
389
391
 
@@ -2,7 +2,7 @@
2
2
 
3
3
  ## 输出完整性要求
4
4
 
5
- - **文件路径**:写出从仓库根开始的完整相对路径(如 `frontend/eui/src/components/input/EInput.vue`)。不要只写文件名。不要把本机绝对路径写进规则、代码、文档或提交说明。
5
+ - **文件路径**:对话回复中使用完整绝对路径,并提供可点击的文件链接;不要只写文件名或相对路径。写入仓库的规则、代码、文档和提交说明仍使用从仓库根开始的完整相对路径,不写本机绝对路径。
6
6
  - **服务地址**:写出完整 URL,包含协议、主机、端口和路径(如 `http://localhost:8000/api/users`)。
7
7
  - **数据库连接串**:说明它来自哪个环境变量或配置项。回复、日志、示例里不写密码和 token。需要示意时用占位符,如 `postgresql+asyncpg://USER:PASSWORD@localhost:5432/DBNAME`。
8
8
 
@@ -52,6 +52,12 @@
52
52
 
53
53
  ---
54
54
 
55
+ ## 0.32.0 — 2026-10-29
56
+
57
+ ### Changed
58
+ - `skills/发布发版/dwy-publish`:按工程识别变更并维护版本、changelog 和 tag;部署新代码前自动补齐并校验三者与部署提交,按项目上下文判断发布方式。
59
+ - `skills/发布发版/dwy-semver`:按版本和发布用途判断正式版或预发布版,不根据分支判断。
60
+
55
61
  ## 0.31.0 — 2026-10-10
56
62
 
57
63
  ### Changed
@@ -1 +1 @@
1
- 0.31.0
1
+ 0.32.0
@@ -108,8 +108,8 @@ select t.name from t where t.id > 200 limit 50
108
108
 
109
109
  ```dolphindb
110
110
  loadPlugin("aws")
111
- aws_cfg = dict(["filePath", "region", "bucket", "accessId", "secretKey"], ["demo.csv.gz", 'cn-north-1', 'tests3func', 'AKIAXTOOQXTLU44HF75E',
112
- 'S0OzvoQnlmFRSn1RzhK5b0yY4IpbrvYTyF5bwtyw'])
111
+ aws_cfg = dict(["filePath", "region", "bucket", "accessId", "secretKey"], ["demo.csv.gz", 'cn-north-1', 'tests3func', 'YOUR_AWS_ACCESS_KEY_ID',
112
+ 'YOUR_AWS_SECRET_ACCESS_KEY'])
113
113
  t = createExternalTable("test","s3", aws_cfg,`Open`High`OpenInt, [DOUBLE, DOUBLE, INT]);
114
114
  select count(*) from t where Open > 0.5 and High < 0.6 limit 1;
115
115
  ```
@@ -60,7 +60,7 @@ Superset 进行可视化数据分析。
60
60
  `rand(33..126, 32).concat("").base64Encode()`。
61
61
 
62
62
  ```dolphindb
63
- echo "SECRET_KEY = 'cIgiAjJyZoxb4FfPzxQ8CdLJa+afcA5m+r3ezwe7XGzJ/Qj1VQWnGHRU'" > superset_config.py
63
+ echo "SECRET_KEY = 'YOUR_RANDOM_SUPERSET_SECRET_KEY'" > superset_config.py
64
64
  ```
65
65
 
66
66
  配置环境变量并初始化 Superset。
@@ -111,7 +111,7 @@ InfluxDB 2.0 中存储时间类型数据,采用的是带有时区信息的 RFC
111
111
  "org": "zhiyu"
112
112
  }
113
113
  ],
114
- "token": "GLiPjQFQIxzVO0-atASJHH4b075sTlyEZGrqW20XURkelUT5pOlfhi_Yuo2fjcSKVZvyuO00kdXunWPrpJd_kg==",
114
+ "token": "YOUR_INFLUXDB_TOKEN",
115
115
  "range": [
116
116
  "2007-08-09"
117
117
  ]
@@ -1,35 +1,28 @@
1
1
  ---
2
2
  name: dwy-publish
3
- description: "发版/部署编排器。触发条件:用户说\"发版\"\"部署\"\"发布上线\"\"release\"\"publish\"\"走发版流程\"时。按应用目录确认发布方式并缓存,串起定版本→changelog→安全检查→打 tag→build/发布→监控→通知。工厂里有多个项目时,先查出哪些项目的前端或后端有变更,再分别写 changelog。每次更新版本必须打 git tag,tag 版本串与该份 changelog 新版本标题完全一致。版本号决策单独走 dwy-semver,GA 打包走 dwy-github-action-publish,发布前安全检查走 dwy-sdk-spec——本 skill 负责编排,按路径主动调它们。"
3
+ description: "Use when 用户要求整理工程版本、更新 changelog、打版本 tag、发版、发布或部署上线。"
4
4
  ---
5
5
 
6
- # 发版编排器
6
+ # 工程版本管理与发布编排
7
7
 
8
- 一条发版链的编排执行。进入前测试已完成。
8
+ 先识别工程及变更,再为需要更新的工程维护版本、changelog 和 tag;是否发布及发布方式按用户意图和项目上下文判断。
9
9
 
10
- **先**读仓库上下文,判目标平台(前端/后端/Android/iOS/鸿蒙/通用)+ 定位应用目录(相对项目根的相对路径)。**再**按下表执行 9 步。
10
+ ## 流程
11
11
 
12
- | # | 步骤 | 类型 | 调用 |
13
- |---|------|------|------|
14
- | 1 | 确认发布方式 + 探测 build/publish 命令 | 项目步 | `scripts/release_config.sh` + `references/publish-config.md` |
15
- | 2 | 写/校验发布配置(GA workflow / TestFlight 就绪) | 项目步 | `references/publish-config.md` |
16
- | 3 | 定版本号 + 多文件同步 | 通用步 | `../dwy-semver` + `references/version-sync.md` |
17
- | 4 | 写 changelog(先扫工厂内要发的项目,再按每个项目的前端/后端分别写) | 通用步 | `references/changelog.md` |
18
- | 5 | 安全检查(闸门,未过不发) | 通用步 | `../dwy-sdk-spec` |
19
- | 6 | commit 版本变更 + 打 tag 并推送 | 通用步 | `references/git-tag.md` |
20
- | 7 | build + 触发发布 | 项目步 | `references/<平台>.md`;走 GA 引 `../dwy-github-action-publish` |
21
- | 8 | 监控发布进程 | 项目步 | `references/monitor-notify.md` |
22
- | 9 | 通知结果(输出结果文本) | — | `references/monitor-notify.md` |
12
+ 1. 读取仓库指令、结构、构建配置、版本来源、changelog、Git 状态和历史 tag,识别独立工程及共享版本边界。依据实际依赖判断共享代码影响;不要把每个子目录当成独立工程。优先遵循用户指定范围,分不清边界时询问。
13
+ 2. 逐工程列出变更、版本来源、changelog 路径、tag 命名、版本类型、是否需要发布和 tag 触发器。检查上次对应 tag 后的提交及本次相关的未提交变更;不要把不属于本次的改动纳入提交。
14
+ 3. 对本次需要更新的工程,按 `../dwy-semver` 确定版本级别、beta/rc 或正式版,按 `references/version-sync.md` 同步版本;按 `references/changelog.md` 更新该工程 changelog,每个版本条目必须包含明确版本号和日期,禁止使用 `Unreleased` 或“未发布”等占位标题,与是否发布无关;再按 `references/git-tag.md` 提交和打 tag。无变更工程不创建空版本。已覆盖本次代码的现有版本可复用,不重复升版或打 tag。
15
+ 4. 确定是否发布:仅版本管理时结束;需要发布或部署时,读取项目约定及现有 CI、构建和部署配置,判断渠道和命令。遵守仓库的发布和授权规则,不套用固定发布方式;证据不足时询问。不根据分支判断版本类型或发布流程。
16
+ 5. **部署上线闸门**:有新代码要上线,AI 自动检查并补齐对应工程的版本更新、changelog 和 tag,不逐项要求用户确认。自动校验 tag 指向包含全部拟部署代码、版本和 changelog 的提交,构建或部署使用该提交或其已验证产物。补齐并校验通过后继续部署;无法补齐或校验失败时停止部署并说明原因,不能以赶时间或已有构建替代。先完成项目要求的测试和适用的安全检查;SDK 对外发布前读取 `../dwy-sdk-spec`。
17
+ 6. 推送 tag 前检查远端工作流及其他自动触发器:若推送会发布,先完成所有发布闸门并确认本次已获发布授权。仅要求版本管理而推送会触发未经授权的发布时,保留本地 tag 并报告,不能推送或擅自更改 workflow。
18
+ 7. 已获授权且满足闸门时,按项目实际配置执行发布;需要 GitHub Actions 打包时读取 `../dwy-github-action-publish` 和相应平台参考文件。按 `references/monitor-notify.md` 监控并验证实际结果。
23
19
 
24
- 平台流程文件:`frontend-deploy.md` / `backend-deploy.md` / `ios.md` / `android.md` / `harmony.md` / `generic-release.md`。
20
+ 仅重发、重跑或回滚**完全相同的已记录版本和产物**,可复用对应 changelog、版本及 tag;必须核实代码和产物身份。有新代码则不是重发,必须完成新版本的全部闸门。仅环境配置变更按项目约定记录和验证,不得借此夹带新代码。
25
21
 
26
- ## 编排约束
22
+ ## 参考文件
27
23
 
28
- - **通用步固化**(定版本/changelog/安全检查/打 tag 跨项目一致,走子 skill/脚本);**项目步读上下文+缓存**(build/deploy/监控命令因项目而异,**绝不写死**,探测不到问用户后缓存)。
29
- - 安全检查(第 5 步)是闸门,未过不进 tag / 发布。
30
- - **每次更新版本必须打 tag**(第 6 步):tag 版本串与本次 changelog 新版本标题(`## x.y.z`)完全一致;细则见 `references/git-tag.md`。无 bump 的纯重发可不打新 tag。
31
- - 若 GA workflow 由 tag 触发,第 6 步 push tag 即触发第 7 步发布。
32
- - **正式环境分支**:只有 `develop` 和 `preview`。`develop` 发测试环境,版本只能是 `beta` 或 `rc`。正式版只从 `preview` 发。仓库没有这两条分支就先提醒,不自行建分支。用 `develop` 发正式版时,先按 `rules/开发流程/dwy-prod-branch.md` 问用户,未确认不发。
33
- - 全程 AI 自动判断、自动跑,仅搞不定时才问用户(版本级别拿不准、命令探测不到、配置缺失、构建/发布失败、需删远端 tag)。用 `develop` 部署正式环境时除外,须先问用户。
34
- - 编排时按文件路径主动加载子 skill(`../dwy-semver`、`../dwy-sdk-spec`、`../dwy-github-action-publish`);这些 skill 也可被用户独立触发。
35
- - `.dwy/` 缓存提醒用户加进 `.gitignore`。
24
+ - 版本和记录:`references/version-sync.md`、`references/changelog.md`、`references/git-tag.md`。
25
+ - 发布配置:仅需要发布时读取 `references/publish-config.md`,必要时使用 `bash scripts/release_config.sh`。
26
+ - 平台:`references/frontend-deploy.md`、`references/backend-deploy.md`、`references/ios.md`、`references/android.md`、`references/harmony.md`、`references/generic-release.md`。
27
+
28
+ 版本、tag 或发布目标存在歧义时先核实;不可识别时询问。删除或覆盖远端 tag / release 前必须获得用户明确授权。输出各工程的版本、changelog、tag、是否推送和实际发布验证结果。
@@ -10,7 +10,7 @@
10
10
  ## 版本(通用步,固化)
11
11
 
12
12
  - `versionName` = 语义版本号,走 `../dwy-semver` 决策 + bump.py。
13
- - `versionCode` = 单调递增整数,每次发布 +1。
13
+ - 新上传构建的 `versionCode` 遵循渠道及项目的递增要求。
14
14
  - 多文件同步见 `version-sync.md`。
15
15
 
16
16
  ## build / publish(项目步,不写死命令)
@@ -26,7 +26,7 @@
26
26
 
27
27
  ## changelog / tag / 安全检查 / 监控
28
28
 
29
- - changelog → `changelog.md`(写该项目的 `android/CHANGELOG.md`;工厂里多个项目时每个项目各一份)
30
- - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
29
+ - changelog → `changelog.md`(使用该工程既有路径和格式)
30
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog 标题一致)
31
31
  - 安全检查 → `../dwy-sdk-spec`
32
32
  - 监控 + 结果 → `monitor-notify.md`
@@ -4,8 +4,8 @@
4
4
 
5
5
  ## 版本(通用步,固化)
6
6
 
7
- - 走 `../dwy-semver` 决策 + bump.py;多文件同步见 `version-sync.md`(Python 须同步 `pyproject.toml` 的 `version` 和包 `__init__.py` 的 `__version__`)。
8
- - 纯重发、仅重跑 workflow、仅补环境配置、仅回滚已有构建时可不 bump。
7
+ - 走 `../dwy-semver` 决策 + bump.py;按 `version-sync.md` 同步工程实际维护的版本来源。
8
+ - 部署新代码必须先完成入口规定的版本、changelog 和 tag 闸门;仅重发或回滚相同已记录产物可复用原版本。
9
9
 
10
10
  ## build / deploy(项目步,不写死命令)
11
11
 
@@ -19,6 +19,6 @@
19
19
 
20
20
  ## changelog / tag / 监控
21
21
 
22
- - changelog → `changelog.md`(写该项目的 `backend/CHANGELOG.md`;工厂里多个项目时每个项目各一份)
23
- - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
22
+ - changelog → `changelog.md`(使用该工程既有路径和格式)
23
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog 标题一致)
24
24
  - 监控 + verify → `monitor-notify.md`(后端信号:健康检查 / 版本接口 / 运行日志 / 镜像 tag)
@@ -1,75 +1,11 @@
1
- # 发版时按项目写 CHANGELOG
1
+ # 按工程维护 CHANGELOG
2
2
 
3
- 只在发版写。不发版不写、不升版本。
3
+ 先识别版本边界:根据构建配置、版本来源、已有 changelog、tag 和项目约定确定独立工程;共享版本的模块遵循仓库既有规则。用户指定范围优先,不将无关改动纳入本次版本。
4
4
 
5
- 工厂是这次发版的 git 仓库根。工厂下可以有多个项目。每个项目可以各自有前端和后端。changelog 写在那一端自己的目录里。
5
+ 逐工程检查上次对应 tag 后的提交和本次相关未提交变更,分析共享代码对各工程的影响。列出工程、变更、版本来源、changelog 路径、tag 和是否发布。无变更的工程不写空版本;已记录的内容不要重复添加。
6
6
 
7
- ## 先列出要写的端
7
+ 需要更新的工程各自维护 changelog:优先使用现有路径、分类和语言;没有时在工程目录创建 `CHANGELOG.md`,遵循仓库约定。记录该工程本次实际变更,不混入其他工程。用户要求仅管理版本时也要记录,不以是否发布为条件。
8
8
 
9
- 1. 找出项目。一个项目是含有可独立发版的前端和/或后端的目录。常见是 `<项目>/frontend` 与 `<项目>/backend`。仓库根下直接就是这两端时,根就是这一个项目。目录名按仓库实际结构认,不套死 `frontend` / `backend` 这两个词。
10
- 2. 一端算存在:该目录有自己的版本来源(前端看 `package.json` 的 `version`,后端看 `pyproject.toml` 的 `version`),或有该端的应用入口。没有这一端就不写。
11
- 3. 决定哪些端要写:
12
- - 用户点名了项目或端:只写点名的。
13
- - 没点名:该端目录自上次 release tag 以来有提交才写。没有上次 tag 时,看这次准备发布的提交是否改过该目录。
9
+ 每个版本条目的标题必须同时包含明确版本号和 `YYYY-MM-DD` 日期,例如 `## [1.2.3] - 2026-11-02`;版本串与该工程版本来源及 tag 中的版本串一致。日期表示本次版本记录日期,不以实际发布为前提,也不等到发布时再补。禁止使用 `Unreleased`、`[Unreleased]` 或“未发布”等占位标题;即使仓库原格式缺少版本号或日期,本次新增或更新的条目也必须补齐。已有占位条目中属于本次的变更,归入明确版本号和日期的条目,不继续保留为占位记录。按现有分类和语言写清用户可理解的变更;仓库已有可靠生成工具时可复用并检查结果。
14
10
 
15
- ```bash
16
- git log <该端上次 tag>..HEAD --oneline -- <项目>/<端>/
17
- ```
18
-
19
- 4. 没有提交的端不写、不升版本、不打 tag。
20
- 5. 写之前先列出清单:项目、端、changelog 路径、有没有变更。清单里该写的还没写完,不进入 tag。
21
-
22
- 该项目下若还有 android / ios / harmony,同样各算一端,不写进前后端的 changelog。
23
-
24
- ## 落点
25
-
26
- - `<项目>/frontend/CHANGELOG.md`
27
- - `<项目>/backend/CHANGELOG.md`
28
-
29
- 只有一端的项目只写存在的那一端。不要在工厂根再写一份总表。
30
-
31
- ## 版本标题格式(与 git tag 对齐)
32
-
33
- 新版本必须用二级标题,版本串单独成标题,供打 tag 原样取用:
34
-
35
- ```markdown
36
- # <包标识> ← 作为 tag 的包前缀(如 shop-frontend、@acme/api)
37
-
38
- ## 0.16.1 ← 新版本标题;这一份 changelog 的 tag 版本串必须与此完全一致
39
-
40
- ### Patch Changes
41
-
42
- - ...
43
- ```
44
-
45
- 约束:
46
-
47
- - 标题里只写 SemVer 版本串,不要写 `v0.16.1`、日期或其它后缀
48
- - 从 `develop` 发:标题是 `x.y.z-beta.N` 或 `x.y.z-rc.N`。从 `preview` 发正式版:标题是 `x.y.z`,不带预发布
49
- - 该版本串 = 这一端版本来源文件里的 version = 这一份 changelog 即将打的 tag 中的版本部分(见 `git-tag.md`)
50
- - 各端版本号各自维护。前端升了,不表示后端也要升
51
-
52
- ## 写法
53
-
54
- 1. 找这一端的上一个 release tag
55
- 2. 只取这一端目录里的 commit(命令见上)
56
- 3. 按 `feat` / `fix` / `refactor` / `chore` 分组
57
- 4. 写入这一端的 `CHANGELOG.md`,用 `## <新版本号>` 标注
58
-
59
- 仓库已有自动生成命令(changelogen 等)时优先使用。仍然只覆盖这一端的目录,版本标题仍要满足上面的格式。
60
-
61
- 写了几份 changelog,就按 `git-tag.md` 各打一个 tag。不要合成一个工厂级 tag。
62
-
63
- ## 自动执行
64
-
65
- changelog 由 AI 生成并写入,不逐步确认。只有 commit 无法归类,或分不清项目边界时才问用户。
66
-
67
- ## 禁止
68
-
69
- - 禁止在工厂根写一份总 CHANGELOG,把多个项目或前端后端混在一起
70
- - 禁止只写了其中一个项目就进入 tag
71
- - 禁止把 A 项目的 commit 写进 B 项目
72
- - 禁止把前端的 commit 写进后端,或反过来
73
- - 禁止给没有变更的端写一份空版本
74
- - 禁止 changelog 未写完就进入 tag / 部署
75
- - 禁止版本标题与这一份即将打的 tag 版本串不一致
11
+ 部署新代码前 AI 自动检查目标工程的 changelog 是否覆盖拟部署变更,并与版本和 tag 指向的提交一致;缺失时自动补齐,不逐项要求用户确认。补齐和校验完成前禁止部署。
@@ -5,7 +5,7 @@
5
5
  ## 版本(通用步,固化)
6
6
 
7
7
  - 走 `../dwy-semver` 决策 + bump.py;多文件同步见 `version-sync.md`(前端改 `package.json` 的 `version`)。
8
- - 纯重发、仅重跑 workflow、仅回滚已有构建时可不 bump。
8
+ - 部署新代码必须先完成入口规定的版本、changelog 和 tag 闸门;仅重发或回滚相同已记录产物可复用原版本。
9
9
 
10
10
  ## build / deploy(项目步,不写死命令)
11
11
 
@@ -19,6 +19,6 @@
19
19
 
20
20
  ## changelog / tag / 监控
21
21
 
22
- - changelog → `changelog.md`(写该项目的 `frontend/CHANGELOG.md`;工厂里多个项目时每个项目各一份)
23
- - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
22
+ - changelog → `changelog.md`(使用该工程既有路径和格式)
23
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog 标题一致)
24
24
  - 监控 + verify → `monitor-notify.md`(前端信号:页面访问 / 版本接口 / 产物 hash / CDN 刷新)
@@ -4,8 +4,8 @@
4
4
 
5
5
  ## 版本(通用步,固化)
6
6
 
7
- - 走 `../dwy-semver` 决策 + bump.py;多文件同步见 `version-sync.md`(Python 同步 `pyproject.toml` + `__init__.py`,Node 同步 `package.json`)。
8
- - 纯重发、仅重跑 workflow、仅回滚已有构建时可不 bump。
7
+ - 走 `../dwy-semver` 决策 + bump.py;按 `version-sync.md` 同步工程实际维护的版本来源。
8
+ - 发布新代码必须先完成入口规定的版本、changelog 和 tag 闸门;仅重发或回滚相同已记录产物可复用原版本。
9
9
 
10
10
  ## build / publish(项目步,不写死命令)
11
11
 
@@ -16,13 +16,13 @@
16
16
  按缓存 method 分流:
17
17
  - `github-action-oidc` → **PyPI/npm 首选**,引 `../dwy-github-action-publish`(OIDC + Trusted Publisher)
18
18
  - `github-action` → Cython wheel / Electron / 镜像,引 `../dwy-github-action-publish`
19
- - `local` → 缓存的本地发包命令(`uv publish` / `pnpm publish` 等)
19
+ - 其他渠道 → 遵循项目已有发布配置和授权;PyPI / npm 发布必须走 GitHub Actions OIDC。
20
20
 
21
21
  **PyPI/npm 未配置 GA 时按 `publish-config.md` 主动推荐 OIDC。**
22
22
 
23
23
  ## changelog / tag / 安全检查 / 监控
24
24
 
25
- - changelog → `changelog.md`(单包仓库根目录一份;应用工厂按每个项目的前端/后端分写)
26
- - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致;monorepo 例:`@dwydev/eui@2.2.3`、`create-dwy@0.16.1`)
25
+ - changelog → `changelog.md`(使用该工程既有路径和格式)
26
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog 标题一致;monorepo 例:`@dwydev/eui@2.2.3`、`create-dwy@0.16.1`)
27
27
  - 安全检查 → `../dwy-sdk-spec`(对外包必查泄露/产物)
28
28
  - 监控 + verify → `monitor-notify.md`(包仓库版本查询 / GitHub Release)
@@ -1,82 +1,7 @@
1
- # 发版 git tag(强制)
1
+ # 版本 tag
2
2
 
3
- **每次更新版本号都必须打 tag 并推远端。** 无版本 bump 的纯重发 / 仅重跑 workflow / 仅回滚,不打新 tag。
3
+ 每个需要更新版本的工程,在版本来源和 changelog 完成后提交对应变更并打 tag。tag 的版本串必须与版本来源和 changelog 标题一致;tag 指向包含本次完整代码、版本和 changelog 的提交。遵循仓库既有命名,多个独立工程使用各自不冲突的 tag,已有约定优先。
4
4
 
5
- ## 时机
5
+ 打 tag 和推送前检查 tag 是否已存在、指向是否正确,以及推送是否触发 CI、发布或部署。推送由用户授权范围和项目要求决定;推送可能触发发布时,先完成测试、适用的安全检查及部署闸门,且仅在发布已获授权时推送。仅版本管理且推送会触发未经授权发布时,保留本地 tag 并报告。
6
6
 
7
- 在第 3 步版本同步 + 第 4 步 changelog 写完、第 5 步安全检查通过之后:
8
-
9
- 1. 将版本来源文件 + CHANGELOG 一并 commit(若尚未提交)
10
- 2. 按下方规则算出 tag 名
11
- 3. 本地打 tag → push tag 到 origin
12
- 4. 再进入 build / 发布(若 workflow 由 tag 触发,push tag 即触发发布)
13
-
14
- ```bash
15
- # 版本与 changelog 已落盘并 commit 后
16
- git tag <tag>
17
- git push origin <tag>
18
- ```
19
-
20
- ## tag 与 changelog 版本对齐(硬规则)
21
-
22
- tag 中的**版本串**必须与这一份 CHANGELOG **新版本标题**完全一致,禁止改写。第 4 步写了几份,就各打一个 tag,版本串对应该份标题。
23
-
24
- | CHANGELOG 写法 | 取出的版本串 |
25
- |----------------|-------------|
26
- | `## 0.16.1` | `0.16.1` |
27
- | `## 2.2.3` | `2.2.3` |
28
- | `## 1.0.0-rc.1` | `1.0.0-rc.1` |
29
-
30
- 禁止:
31
-
32
- - changelog 写 `## 0.16.1`,tag 打成 `v0.16.1` / `0.16.1.0` / `0.16`(格式不一致)
33
- - tag 版本与 `package.json` / `pyproject.toml` 等来源文件不一致
34
- - 未写 changelog 新版本标题就打 tag
35
-
36
- ## tag 全名拼法
37
-
38
- 先取 changelog 版本串 `VER`,再按仓库惯例拼全名:
39
-
40
- | 场景 | 规则 | 例 |
41
- |------|------|-----|
42
- | monorepo 多包 / 多平台 | `{包标识}@{VER}`;包标识优先取该 CHANGELOG 顶部 H1(去掉 `# `),否则取 `package.json` 的 `name` | CHANGELOG H1 `# create-dwy` + `## 0.16.1` → `create-dwy@0.16.1`;H1 `# @dwydev/eui` + `## 2.2.3` → `@dwydev/eui@2.2.3` |
43
- | 单包仓库且历史 tag 已有固定前缀 | 沿用历史前缀 + `VER`(仅当前缀不篡改 VER 本身时) | 历史 `v1.2.0` 且 changelog 为 `## 1.3.0` 时,若项目约定 `v` 前缀且 VER 仍完整出现 → 须在本仓库已有 tag 惯例中确认;**优先保证 VER 与 changelog 一致** |
44
- | 单包仓库无历史惯例 | 直接用 `VER` | changelog `## 1.0.0` → tag `1.0.0` |
45
-
46
- **冲突裁决**:若「历史 `v` 前缀」与「changelog 无 `v`」冲突,以 **changelog 标题里的版本串** 为准拼 tag(monorepo 用 `包@VER`,单包用 `VER`),不要为了凑 `v` 前缀改掉 VER。仓库若已有 workflow 写死 `v*` 触发,同步改 workflow 或 changelog 标题约定,二者必须一致。
47
-
48
- 打 tag 前自检:
49
-
50
- ```bash
51
- # VER 必须等于 changelog 最新 ## 标题
52
- # 包来源文件 version 字段必须等于 VER
53
- git tag -l | head # 对照历史命名是否 monorepo 包前缀
54
- ```
55
-
56
- ## 推送与触发
57
-
58
- - 必须 `git push origin <tag>`,只打本地 tag 不算完成
59
- - method 为 `github-action` / `github-action-oidc` 且 workflow `on.push.tags` 时:push tag 即触发 CI,不必再另起发布命令
60
- - method 为 local / testflight / maven 等:先 push tag,再跑缓存的 `publish_cmd`(或按项目既有顺序,但 tag 不可省)
61
-
62
- ## 重发 / 删 tag
63
-
64
- 远端已有错误 tag 需要覆盖时:
65
-
66
- 1. **先确认用户允许**删除远端 tag / release
67
- 2. 再删远端 + 本地,重打同名 tag 推送
68
-
69
- ```bash
70
- git push origin :refs/tags/<tag>
71
- git tag -d <tag>
72
- git tag <tag>
73
- git push origin <tag>
74
- ```
75
-
76
- ## 禁止
77
-
78
- - 禁止版本号已 bump 却不打 tag
79
- - 禁止 tag 版本串与 changelog 新版本标题不一致
80
- - 禁止 tag 与版本来源文件不一致
81
- - 禁止未 push 远端就当发版完成
82
- - 禁止未确认用户就删除远端已有 tag / release
7
+ 部署新代码必须使用包含该代码的版本 tag 对应提交或其已验证产物;不能用旧 tag 掩盖新增代码。重发或回滚完全相同的已记录版本和产物,可复用原 tag,无须重复打 tag。不得未经用户明确授权删除或覆盖远端 tag / release。
@@ -20,7 +20,7 @@
20
20
 
21
21
  ## changelog / tag / 安全检查 / 监控
22
22
 
23
- - changelog → `changelog.md`(写该项目的 `harmony/CHANGELOG.md`;工厂里多个项目时每个项目各一份)
24
- - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
23
+ - changelog → `changelog.md`(使用该工程既有路径和格式)
24
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog 标题一致)
25
25
  - 安全检查 → `../dwy-sdk-spec`
26
26
  - 监控 + 结果 → `monitor-notify.md`
@@ -10,7 +10,7 @@
10
10
  ## 版本(通用步,固化)
11
11
 
12
12
  - 语义版本号 `CFBundleShortVersionString`,走 `../dwy-semver` 决策 + bump.py。
13
- - **build 号 `CFBundleVersion` = 时间戳 `yyyyMMddHHmm` 12 位**(如 `202606231430`,`date +%Y%m%d%H%M`),每次发布重新生成,单调递增。app 和 SDK 都适用。
13
+ - 构建号 `CFBundleVersion` 遵循项目及发布渠道约定;重新上传构建时满足渠道的递增要求。
14
14
  - 多文件同步见 `version-sync.md`。
15
15
 
16
16
  ## build / publish(项目步,不写死命令)
@@ -27,7 +27,7 @@
27
27
 
28
28
  ## changelog / tag / 安全检查 / 监控
29
29
 
30
- - changelog → `changelog.md`(写该项目的 `ios/CHANGELOG.md`;工厂里多个项目时每个项目各一份)
31
- - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog `##` 标题一致)
30
+ - changelog → `changelog.md`(使用该工程既有路径和格式)
31
+ - tag → `git-tag.md`(每次 bump 必打;版本串与 changelog 标题一致)
32
32
  - 安全检查 → `../dwy-sdk-spec`(SDK 发布尤其要查泄露)
33
33
  - 监控 + 结果 → `monitor-notify.md`
@@ -1,6 +1,6 @@
1
1
  # 发布方式缓存 + 命令探测
2
2
 
3
- 发布方式与 build/publish 命令**因项目而异,不写死**。按应用目录探测 → 问 → 缓存复用。
3
+ 仅在需要发布时探测项目已有发布方式和 build/publish 命令;根据项目上下文及授权判断,存在歧义时询问。缓存仅作为线索,执行前核实现有配置和权限。
4
4
 
5
5
  ## 缓存机制
6
6
 
@@ -17,7 +17,7 @@
17
17
  - 未命中(首次进入该目录)→ 走第 3 步
18
18
  3. 首次配置:
19
19
  - 探测真实命令(见下方探测线索)
20
- - AskUserQuestion 问发布方式(见下方各平台候选)
20
+ - 按项目现有配置判断发布方式;无法确定时询问
21
21
  - 检查对应配置就绪(GA workflow 是否存在 / TestFlight 凭据线索 / maven·pod publish 配置)
22
22
  - `release_config.sh set <root> <rel_dir> <platform> <method> <channel> <configured> [build_cmd] [publish_cmd] [verify_cmd] [cmd_source]`
23
23
  - 提醒把 `.dwy/` 加进 `.gitignore`
@@ -51,7 +51,7 @@
51
51
  | harmony(har / har app) | `ohpm`(推荐) / `github-action` / `local` |
52
52
  | frontend | `github-action`(CI,推荐 OIDC) / `local` |
53
53
  | backend | `github-action`(CI,推荐 OIDC) / `local` |
54
- | generic — PyPI/npm | `github-action-oidc`(**推荐**,无静态凭据、符合团队准则) / `local` |
54
+ | generic — PyPI/npm | `github-action-oidc`(必须,无长期静态发布凭据) |
55
55
  | generic — Cython wheel | `github-action`(cibuildwheel) / `local` |
56
56
  | generic — Electron | `github-action`(多平台 runner) / `local` |
57
57
  | generic — Docker/二进制 | `github-action` / `local` |
@@ -62,4 +62,4 @@ PyPI / npm 且未配置 GA 时,**主动推荐 `github-action-oidc`**(OIDC Tr
62
62
 
63
63
  - 给 workflow 模板:见 `../dwy-github-action-publish`(Python wheel / 纯包 OIDC)。
64
64
  - 给 Trusted Publisher 网页配置步骤:PyPI/npmjs 后台绑定 repo + workflow 文件名 + environment。
65
- - 用户仍可选 `local`(`uv publish` / `pnpm publish` 等),按其选择缓存。
65
+ - 按仓库要求通过 GitHub Actions OIDC 发布,不使用本地命令或长期静态发布凭据。