@aipper/aiws-spec 0.0.21 → 0.0.24

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 (89) hide show
  1. package/docs/spec-contract.md +5 -4
  2. package/package.json +1 -1
  3. package/templates/workspace/.agents/skills/{aiws-change-archive → p-aiws-change-archive}/SKILL.md +3 -2
  4. package/templates/workspace/.agents/skills/{aiws-change-finish → p-aiws-change-finish}/SKILL.md +2 -2
  5. package/templates/workspace/.agents/skills/{aiws-change-list → p-aiws-change-list}/SKILL.md +2 -2
  6. package/templates/workspace/.agents/skills/{aiws-change-new → p-aiws-change-new}/SKILL.md +2 -2
  7. package/templates/workspace/.agents/skills/{aiws-change-next → p-aiws-change-next}/SKILL.md +2 -2
  8. package/templates/workspace/.agents/skills/{aiws-change-start → p-aiws-change-start}/SKILL.md +2 -2
  9. package/templates/workspace/.agents/skills/{aiws-change-status → p-aiws-change-status}/SKILL.md +2 -2
  10. package/templates/workspace/.agents/skills/{aiws-change-sync → p-aiws-change-sync}/SKILL.md +2 -2
  11. package/templates/workspace/.agents/skills/{aiws-change-templates-init → p-aiws-change-templates-init}/SKILL.md +2 -2
  12. package/templates/workspace/.agents/skills/{aiws-change-templates-which → p-aiws-change-templates-which}/SKILL.md +2 -2
  13. package/templates/workspace/.agents/skills/{aiws-change-validate → p-aiws-change-validate}/SKILL.md +2 -2
  14. package/templates/workspace/.agents/skills/{aiws-hooks-install → p-aiws-hooks-install}/SKILL.md +2 -2
  15. package/templates/workspace/.agents/skills/{aiws-hooks-status → p-aiws-hooks-status}/SKILL.md +2 -2
  16. package/templates/workspace/.agents/skills/{aiws-init → p-aiws-init}/SKILL.md +2 -2
  17. package/templates/workspace/.agents/skills/{aiws-rollback → p-aiws-rollback}/SKILL.md +2 -2
  18. package/templates/workspace/.agents/skills/{aiws-update → p-aiws-update}/SKILL.md +2 -2
  19. package/templates/workspace/.agents/skills/{aiws-validate → p-aiws-validate}/SKILL.md +2 -2
  20. package/templates/workspace/.agents/skills/p-tasks-plan/SKILL.md +37 -0
  21. package/templates/workspace/.agents/skills/ws-bugfix/SKILL.md +30 -7
  22. package/templates/workspace/.agents/skills/ws-commit/SKILL.md +23 -6
  23. package/templates/workspace/.agents/skills/ws-deliver/SKILL.md +61 -17
  24. package/templates/workspace/.agents/skills/ws-dev/SKILL.md +102 -2
  25. package/templates/workspace/.agents/skills/ws-finish/SKILL.md +96 -65
  26. package/templates/workspace/.agents/skills/ws-handoff/SKILL.md +31 -0
  27. package/templates/workspace/.agents/skills/ws-plan/SKILL.md +68 -1
  28. package/templates/workspace/.agents/skills/ws-push/SKILL.md +17 -12
  29. package/templates/workspace/.claude/commands/{aiws-change-archive.md → p-aiws-change-archive.md} +3 -1
  30. package/templates/workspace/.claude/commands/{aiws-change-finish.md → p-aiws-change-finish.md} +2 -0
  31. package/templates/workspace/.claude/commands/{aiws-change-list.md → p-aiws-change-list.md} +2 -0
  32. package/templates/workspace/.claude/commands/{aiws-change-new.md → p-aiws-change-new.md} +2 -0
  33. package/templates/workspace/.claude/commands/{aiws-change-next.md → p-aiws-change-next.md} +2 -0
  34. package/templates/workspace/.claude/commands/{aiws-change-start.md → p-aiws-change-start.md} +2 -0
  35. package/templates/workspace/.claude/commands/{aiws-change-status.md → p-aiws-change-status.md} +2 -0
  36. package/templates/workspace/.claude/commands/{aiws-change-sync.md → p-aiws-change-sync.md} +2 -0
  37. package/templates/workspace/.claude/commands/{aiws-change-templates-init.md → p-aiws-change-templates-init.md} +2 -0
  38. package/templates/workspace/.claude/commands/{aiws-change-templates-which.md → p-aiws-change-templates-which.md} +2 -0
  39. package/templates/workspace/.claude/commands/{aiws-change-validate.md → p-aiws-change-validate.md} +2 -0
  40. package/templates/workspace/.claude/commands/{aiws-hooks-install.md → p-aiws-hooks-install.md} +2 -0
  41. package/templates/workspace/.claude/commands/{aiws-hooks-status.md → p-aiws-hooks-status.md} +2 -0
  42. package/templates/workspace/.claude/commands/{aiws-init.md → p-aiws-init.md} +2 -1
  43. package/templates/workspace/.claude/commands/{aiws-rollback.md → p-aiws-rollback.md} +2 -1
  44. package/templates/workspace/.claude/commands/{aiws-update.md → p-aiws-update.md} +2 -1
  45. package/templates/workspace/.claude/commands/{aiws-validate.md → p-aiws-validate.md} +2 -1
  46. package/templates/workspace/.claude/commands/ws-bugfix.md +11 -6
  47. package/templates/workspace/.claude/commands/ws-commit.md +2 -1
  48. package/templates/workspace/.claude/commands/ws-deliver.md +6 -2
  49. package/templates/workspace/.claude/commands/ws-dev.md +7 -1
  50. package/templates/workspace/.claude/commands/ws-finish.md +19 -19
  51. package/templates/workspace/.claude/commands/ws-handoff.md +22 -0
  52. package/templates/workspace/.claude/commands/ws-plan.md +9 -5
  53. package/templates/workspace/.codex/prompts/ws-dev.md +5 -1
  54. package/templates/workspace/.githooks/commit-msg +109 -0
  55. package/templates/workspace/.iflow/commands/ws-commit.toml +2 -1
  56. package/templates/workspace/.iflow/commands/ws-deliver.toml +6 -2
  57. package/templates/workspace/.iflow/commands/ws-finish.toml +19 -19
  58. package/templates/workspace/.opencode/command/{aiws-change-archive.md → p-aiws-change-archive.md} +4 -2
  59. package/templates/workspace/.opencode/command/{aiws-change-finish.md → p-aiws-change-finish.md} +3 -1
  60. package/templates/workspace/.opencode/command/{aiws-change-list.md → p-aiws-change-list.md} +3 -1
  61. package/templates/workspace/.opencode/command/{aiws-change-new.md → p-aiws-change-new.md} +3 -1
  62. package/templates/workspace/.opencode/command/{aiws-change-next.md → p-aiws-change-next.md} +3 -1
  63. package/templates/workspace/.opencode/command/{aiws-change-start.md → p-aiws-change-start.md} +3 -1
  64. package/templates/workspace/.opencode/command/{aiws-change-status.md → p-aiws-change-status.md} +3 -1
  65. package/templates/workspace/.opencode/command/{aiws-change-sync.md → p-aiws-change-sync.md} +3 -1
  66. package/templates/workspace/.opencode/command/{aiws-change-templates-init.md → p-aiws-change-templates-init.md} +3 -1
  67. package/templates/workspace/.opencode/command/{aiws-change-templates-which.md → p-aiws-change-templates-which.md} +3 -1
  68. package/templates/workspace/.opencode/command/{aiws-change-validate.md → p-aiws-change-validate.md} +3 -1
  69. package/templates/workspace/.opencode/command/{aiws-hooks-install.md → p-aiws-hooks-install.md} +3 -1
  70. package/templates/workspace/.opencode/command/{aiws-hooks-status.md → p-aiws-hooks-status.md} +3 -1
  71. package/templates/workspace/.opencode/command/{aiws-init.md → p-aiws-init.md} +3 -1
  72. package/templates/workspace/.opencode/command/{aiws-rollback.md → p-aiws-rollback.md} +3 -1
  73. package/templates/workspace/.opencode/command/{aiws-update.md → p-aiws-update.md} +3 -1
  74. package/templates/workspace/.opencode/command/{aiws-validate.md → p-aiws-validate.md} +3 -1
  75. package/templates/workspace/.opencode/command/ws-bugfix.md +11 -6
  76. package/templates/workspace/.opencode/command/ws-commit.md +3 -2
  77. package/templates/workspace/.opencode/command/ws-deliver.md +6 -2
  78. package/templates/workspace/.opencode/command/ws-dev.md +7 -1
  79. package/templates/workspace/.opencode/command/ws-finish.md +19 -19
  80. package/templates/workspace/.opencode/command/ws-handoff.md +25 -0
  81. package/templates/workspace/.opencode/command/ws-plan.md +9 -5
  82. package/templates/workspace/.opencode/command/ws-review.md +1 -1
  83. package/templates/workspace/AGENTS.md +8 -4
  84. package/templates/workspace/AI_WORKSPACE.md +4 -4
  85. package/templates/workspace/changes/README.md +8 -2
  86. package/templates/workspace/manifest.json +173 -103
  87. package/templates/workspace/tools/ws_change_check.py +99 -0
  88. package/templates/workspace/tools/ws_resolve_sub_target.sh +47 -0
  89. package/templates/workspace/tools/ws_tasks_plan.py +137 -0
@@ -8,13 +8,13 @@ description: 收尾(门禁 + 安全合并 + submodule→主仓库顺序 push
8
8
  目标:
9
9
  - 在结束一次变更交付时,用 fast-forward 安全合并 `change/<change-id>` 回目标分支,减少手输分支名导致的错误
10
10
  - 合并成功后,按 `submodule -> superproject` 顺序 push,避免遗漏导致其它仓库拉取异常
11
- - 不自动删分支;push 前必须让用户确认
12
- - 若团队希望减少 submodule detached 的人为差异:建议在 `.gitmodules` 配置 `submodule.<name>.branch`,并在日常拉取时使用 `$ws-pull`
11
+ - 不自动删分支;AI 入口执行前应先向用户说明将要 push 的 remote/branch,显式传入 `--push` 即视为确认
12
+ - 若团队希望减少 submodule detached 的人为差异:建议在 `.gitmodules` 配置 `submodule.<name>.branch`,并在日常拉取时使用 `$ws-pull`
13
13
 
14
14
  前置(必须):
15
15
  - 工作区是干净的:`git status --porcelain` 无输出(若有未提交改动:先 commit 或 stash)
16
16
  - change 分支已存在:`change/<change-id>`(也支持 `changes/`、`ws/`、`ws-change/`)
17
- - 若使用 worktree:在“目标分支所在 worktree”执行(`aiws change finish` 会提示正确的 worktree)
17
+ - 若使用 worktree:在目标分支所在 worktree 执行(`aiws change finish` 会提示正确的 worktree)
18
18
  - 若存在 `.gitmodules`:必须为每个 submodule 配置 `submodule.<name>.branch`(否则无法确定性减少 detached;先运行 `$ws-submodule-setup` 并提交 `.gitmodules`)
19
19
 
20
20
  建议步骤:
@@ -61,96 +61,127 @@ fi
61
61
  ```bash
62
62
  aiws change state "${change_id}" --write
63
63
  ```
64
- 2) 安全合并(默认 fast-forward;并会在需要时切到目标分支):
64
+ 2) 若不存在 `.gitmodules`,或 submodules 已按顺序处理完成,优先直接使用最小收尾闭环:
65
65
  ```bash
66
66
  # 若当前就在 change/<change-id> 分支上,可省略 <change-id>
67
67
  change_id="<change-id>"
68
68
  if [[ -x "./node_modules/.bin/aiws" ]]; then
69
- ./node_modules/.bin/aiws change finish "${change_id}"
69
+ ./node_modules/.bin/aiws change finish "${change_id}" --push
70
70
  elif command -v aiws >/dev/null 2>&1; then
71
- aiws change finish "${change_id}"
71
+ aiws change finish "${change_id}" --push
72
72
  else
73
- npx @aipper/aiws change finish "${change_id}"
73
+ npx @aipper/aiws change finish "${change_id}" --push
74
74
  fi
75
75
  ```
76
- 3) 若 fast-forward 失败(提示需要 rebase):先在 change 分支(或对应 worktree)里 `git rebase <target-branch>`,再重试 `aiws change finish`。
77
- 4) 合并成功后,先把每个 submodule gitlink commit 合并回其目标分支(解决 detached HEAD),并按顺序 push:
76
+ 说明:
77
+ - 该命令会在 fast-forward 合并成功后 push 目标分支。
78
+ - 默认会优先使用当前分支的 upstream 配置(`branch.<name>.remote` + `branch.<name>.merge`);只有在你明确知道要推向别的 remote 时,才追加 `--remote <name>`。
79
+ - 若 `change/<change-id>` 位于独立 worktree,且该 worktree 干净,则会在 push 成功后自动执行 `git worktree remove` + `git worktree prune`。
80
+ - AI 工具内执行前,应先向用户说明将要 push 的 remote/branch;CLI 本身只有在显式传入 `--push` 时才会真正执行 push。
81
+
82
+ 3) 若你需要先处理 submodules,则不要依赖当前分支名推断目标分支,先显式解析 `base_branch`,再执行 submodule 步骤,最后回到主仓库执行 `aiws change finish --push`:
83
+ ```bash
84
+ change_id="<change-id>"
85
+ base_branch="$(python3 - <<'PY'
86
+ import json, pathlib
87
+ change_id = "<change-id>"
88
+ meta = pathlib.Path("changes") / change_id / ".ws-change.json"
89
+ data = json.loads(meta.read_text(encoding="utf-8"))
90
+ print((data.get("base_branch") or "").strip())
91
+ PY
92
+ )"
93
+ test -n "$base_branch"
94
+ ```
95
+ 4) 若 fast-forward 失败(提示需要 rebase):先在 change 分支(或对应 worktree)里 `git rebase <target-branch>`,再重试 `aiws change finish --push`。
96
+ 5) 若存在 `.gitmodules`,先把每个 submodule 的 gitlink commit 合并回其目标分支(解决 detached HEAD),并按顺序 push:
78
97
  ```bash
79
- # superproject 当前分支(finish 后通常是 base 分支)
80
- base_branch="$(git branch --show-current)"
98
+ # 不要用当前分支名代替目标分支;这里显式使用 .ws-change.json base_branch
99
+ change_id="<change-id>"
100
+ targets="changes/${change_id}/submodules.targets"
81
101
 
82
102
  # 子模块清单(没有则跳过)
83
103
  git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null || true
84
104
 
85
105
  # 对每个 submodule.<name>.path <sub_path>:
86
106
  # 说明:`git submodule update` 会把 submodule checkout 到固定 gitlink commit,导致 detached HEAD。
87
- # 为减少“游离状态”的协作摩擦,本步骤采用“pin 分支”策略:
88
- # - 仅在 `.gitmodules` 明确配置了 `submodule.<name>.branch` 时执行(避免 origin 多分支导致误判)
89
- # - 不要直接切 `change/<change-id>` / `main` / `master` 等业务分支来“解 detached”
107
+ # 为减少游离状态的协作摩擦,本步骤采用 pin 分支策略:
108
+ # - `changes/<change-id>/submodules.targets` 是本次 finish/push 的真值;每个 submodule path 都必须在该文件中声明目标分支(可选 remote)
109
+ # - `.gitmodules` `submodule.<name>.branch` 仍建议保留,用于团队默认配置与校验,但不再作为 finish/push fallback
110
+ # - 生成/检查 `submodules.targets` 时可显式说明默认来源:
111
+ # - detached HEAD:默认建议取 `.gitmodules` 的 `submodule.<name>.branch`
112
+ # - 已附着在某个本地分支:默认建议取当前分支
113
+ # - 以上仅为预填建议;真正执行 finish/push 时仍只认 `submodules.targets`
114
+ # - 不要直接切 `change/<change-id>` / `main` / `master` 等业务分支来解 detached
90
115
  # - 不改动 submodule 现有分支指针(例如不强行移动 main/master)
91
116
  # - 创建/更新本地 pin 分支:`aiws/pin/<target_branch>` 指向 gitlink commit,并将其 upstream 设为 `origin/<target_branch>`
92
- sub_sha="$(git rev-parse "HEAD:<sub_path>")"
93
- cfg_branch="$(git config --file .gitmodules --get "submodule.<name>.branch" 2>/dev/null || true)"
94
- if [[ "${cfg_branch:-}" == "." ]]; then cfg_branch="$base_branch"; fi
95
- if [[ -z "${cfg_branch:-}" ]]; then
96
- echo "[warn] <sub_path>: missing .gitmodules submodule.<name>.branch; keep detached and skip auto-push"
97
- continue
98
- fi
99
- target_branch="$cfg_branch"
100
- pin_branch="aiws/pin/${target_branch}"
117
+ if git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' >/dev/null 2>&1; then
118
+ if [[ ! -f "${targets}" ]]; then
119
+ echo "error: missing ${targets} (required when .gitmodules declares submodules)"
120
+ exit 2
121
+ fi
101
122
 
102
- git -C "<sub_path>" fetch origin --prune
103
- if ! git -C "<sub_path>" show-ref --verify --quiet "refs/remotes/origin/${target_branch}"; then
104
- echo "[warn] <sub_path>: origin/${target_branch} not found; keep detached and skip auto-push"
105
- continue
106
- fi
123
+ source tools/ws_resolve_sub_target.sh
107
124
 
108
- # 仅当 gitlink commit 属于 origin/<target_branch> 的历史时才“挂回分支”
109
- if ! git -C "<sub_path>" merge-base --is-ancestor "${sub_sha}" "origin/${target_branch}"; then
110
- echo "[warn] <sub_path>: ${sub_sha} is not in origin/${target_branch}; keep detached and stop (need manual reconcile)"
111
- exit 1
112
- fi
125
+ while read -r key sub_path; do
126
+ name="${key#submodule.}"; name="${name%.path}"
127
+ [[ -z "${sub_path:-}" ]] && continue
128
+ echo "== submodule: ${sub_path} (${name}) =="
129
+
130
+ current_branch="$(git -C "${sub_path}" branch --show-current 2>/dev/null || true)"
131
+ declared_branch="$(git config --file .gitmodules --get "submodule.${name}.branch" 2>/dev/null || true)"
132
+ if [[ -n "${current_branch}" ]]; then
133
+ echo "info: ${sub_path} default suggestion would be current branch: ${current_branch}"
134
+ elif [[ -n "${declared_branch}" ]]; then
135
+ echo "info: ${sub_path} default suggestion would be .gitmodules branch: ${declared_branch}"
136
+ else
137
+ echo "warn: ${sub_path} has no current branch and no .gitmodules branch; submodules.targets must be filled manually"
138
+ fi
113
139
 
114
- git -C "<sub_path>" checkout -B "${pin_branch}" "${sub_sha}"
115
- git -C "<sub_path>" branch --set-upstream-to "origin/${target_branch}" "${pin_branch}" >/dev/null 2>&1 || true
116
- git -C "<sub_path>" status -sb
140
+ sub_sha="$(git rev-parse "HEAD:${sub_path}")"
117
141
 
118
- # push:只允许 fast-forward(若远端分叉会被拒绝;此时必须人工处理)
119
- git -C "<sub_path>" push origin "${sub_sha}:refs/heads/${target_branch}"
142
+ ws_resolve_sub_target "${sub_path}" "${name}" "${targets}" "${base_branch}" || exit 2
143
+ target_branch="${_resolved_branch}"
144
+ remote="${_resolved_remote}"
145
+ pin_branch="aiws/pin/${target_branch}"
146
+
147
+ git -C "${sub_path}" fetch "${remote}" --prune
148
+ if ! git -C "${sub_path}" show-ref --verify --quiet "refs/remotes/${remote}/${target_branch}"; then
149
+ echo "error: ${sub_path}: missing ${remote}/${target_branch}; refusing to push superproject (would break gitlink fetchability)"
150
+ exit 2
151
+ fi
152
+
153
+ # 仅当 gitlink commit 属于 <remote>/<target_branch> 的历史时才挂回分支
154
+ if ! git -C "${sub_path}" merge-base --is-ancestor "${sub_sha}" "${remote}/${target_branch}"; then
155
+ echo "[warn] ${sub_path}: ${sub_sha} is not in ${remote}/${target_branch}; keep detached and stop (need manual reconcile)"
156
+ exit 1
157
+ fi
158
+
159
+ git -C "${sub_path}" checkout -B "${pin_branch}" "${sub_sha}"
160
+ git -C "${sub_path}" branch --set-upstream-to "${remote}/${target_branch}" "${pin_branch}" >/dev/null 2>&1 || true
161
+ git -C "${sub_path}" status -sb
162
+
163
+ # push:只允许 fast-forward(若远端分叉会被拒绝;此时必须人工处理)
164
+ git -C "${sub_path}" push "${remote}" "${sub_sha}:refs/heads/${target_branch}"
165
+ done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
166
+ fi
120
167
  ```
121
168
  规则:
122
- - 每个 submodule 必须先执行“pin 分支挂回 + fast-forward push”,再 push 主仓库。
123
- - 若任一 submodule 的 gitlink commit 不在 `origin/<target_branch>` 历史中:立即停止,先人工处理分叉,再继续。
169
+ - 每个 submodule 必须先执行 pin 分支挂回 + fast-forward push,再 push 主仓库。
170
+ - 若任一 submodule 的 gitlink commit 不在 `<remote>/<target_branch>` 历史中:立即停止,先人工处理分叉,再继续。
124
171
 
125
- 5) 仅当 submodules 全部成功后,再 push superproject 当前分支:
126
- ```bash
127
- git branch --show-current
128
- git status -sb
129
- git push
130
- ```
131
- 6) push 成功后,清理 `change/<change-id>` 对应 worktree(若存在且不是当前 worktree):
172
+ 6) 仅当 submodules 全部成功后,再在 superproject 当前分支执行最小收尾闭环:
132
173
  ```bash
133
174
  change_id="<change-id>"
134
- change_ref="refs/heads/change/${change_id}"
135
- main_wt="$(git rev-parse --show-toplevel)"
136
- change_wt="$(git worktree list --porcelain | awk -v ref="$change_ref" '
137
- $1=="worktree" { wt=substr($0,10) }
138
- $1=="branch" && $2==ref { print wt; exit }
139
- ')"
140
-
141
- if [[ -n "${change_wt:-}" && "$change_wt" != "$main_wt" ]]; then
142
- if [[ -n "$(git -C "$change_wt" status --porcelain 2>/dev/null)" ]]; then
143
- echo "[warn] worktree not clean, skip remove: $change_wt"
144
- echo "hint: clean it first, then run: git worktree remove \"$change_wt\""
145
- else
146
- git worktree remove "$change_wt"
147
- git worktree prune
148
- fi
175
+ if [[ -x "./node_modules/.bin/aiws" ]]; then
176
+ ./node_modules/.bin/aiws change finish "${change_id}" --push
177
+ elif command -v aiws >/dev/null 2>&1; then
178
+ aiws change finish "${change_id}" --push
179
+ else
180
+ npx @aipper/aiws change finish "${change_id}" --push
149
181
  fi
150
182
  ```
151
- 规则:
152
- - 清理前先把 `change_wt` 输出给用户确认,避免误删。
153
- - 仅使用 `git worktree remove`(不带 `--force`)。
183
+ 说明:
184
+ - 该命令内部已经包含主仓库 push 成功后安全清理独立 change worktree 的逻辑。
154
185
 
155
186
  7) (可选)归档变更工件(完成交付后推荐):
156
187
  ```bash
@@ -0,0 +1,31 @@
1
+ ---
2
+ name: ws-handoff
3
+ description: 交接(归档后生成/查看 changes/archive/.../handoff.md)
4
+ ---
5
+
6
+ 用中文输出(命令/路径/代码标识符保持原样不翻译)。
7
+
8
+ 目标:
9
+ - 让 change 的交接信息可回放:`handoff.md`(包含:本次完成/改动文件/关键决策/下一步建议/绑定)
10
+ - 指引在归档与依赖场景下如何使用 handoff
11
+
12
+ 说明:
13
+ - `handoff.md` 由 `aiws change archive` 自动生成:`changes/archive/<date>-<change-id>/handoff.md`
14
+ - `Depends_On` 依赖若已归档,`aiws change start` 会尝试读取依赖的 `handoff.md` 并输出摘要
15
+
16
+ 执行建议:
17
+ 1) 先运行 `$ws-preflight`。
18
+ 2) 若本 change 已完成并准备归档:运行 `aiws change archive <change-id>`(会先做严格校验,并移动目录到 `changes/archive/`,同时生成 `handoff.md`)。
19
+ 3) 查看 handoff:
20
+ ```bash
21
+ change_id="<change-id>"
22
+ ls -1 changes/archive/*-"${change_id}"/handoff.md
23
+ sed -n '1,160p' changes/archive/*-"${change_id}"/handoff.md
24
+ ```
25
+ 4) 若要让后续 change 可接力:在 `proposal.md` 里声明 `Blocks`(下一步建议会据此生成)。
26
+
27
+ 输出要求:
28
+ - `Change_ID:` <change-id>
29
+ - `Handoff:` `changes/archive/.../handoff.md`
30
+ - `Next:` 若还有后续工作,建议创建新 change 并在其 `Depends_On` 引用本 change
31
+
@@ -7,6 +7,7 @@ description: 规划(生成可落盘 plan/ 工件;供 ws-dev 执行)
7
7
 
8
8
  目标:
9
9
  - 对齐真值文件(`AI_PROJECT.md` / `REQUIREMENTS.md` / `AI_WORKSPACE.md`)
10
+ - 若尚未进入本次 change 的工作上下文:先建立 `change/<change-id>` 分支 / worktree,再生成计划
10
11
  - 为当前任务生成一份可追踪的执行计划文件:`plan/<timestamp>-<slug>.md`
11
12
  - 计划必须包含可复现验证命令(优先引用 `AI_WORKSPACE.md`)
12
13
  - 计划必须包含“主索引绑定”:`Change_ID` / (`Req_ID` or `Problem_ID`) / `Contract_Row` / `Plan_File` / `Evidence_Path`
@@ -21,19 +22,53 @@ description: 规划(生成可落盘 plan/ 工件;供 ws-dev 执行)
21
22
  1) 先运行 `$ws-preflight`(读取真值文件并输出约束摘要)。
22
23
  2) 若用户任务描述不清:先问 1-3 个关键澄清问题(不要猜)。
23
24
  3) 判断复杂度:`simple / medium / complex`(给出一句理由),并估算步骤数。
24
- 4) 识别主索引上下文(若存在):
25
+ 4) 识别或建立主索引 / change 上下文:
25
26
  - 若存在 `changes/<change-id>/proposal.md`:读取其中 `Change_ID` / `Req_ID` / `Problem_ID` / `Contract_Row` / `Evidence_Path`
26
27
  - 若缺失关键绑定:先补齐 proposal(至少 `Change_ID`、`Req_ID|Problem_ID`、`Contract_Row`)再继续生成计划
28
+ - 若当前不在 `change/<change-id>` 分支 / worktree,且本次任务需要新建 change:先调用 `aiws change start` 建立上下文,再继续写 plan
29
+ - 推荐顺序:
30
+ - 工作区已存在未提交改动:不要先写 `plan/...`;先停下来说明原因,并要求用户先 commit/stash,或改用已有 change 上下文
31
+ - 仓库已有提交:优先创建独立 worktree;若仓库声明了 submodules,加上 `--submodules`
32
+ - 仓库尚无提交 / 不满足 worktree 前置条件:回退为 `--no-switch`
33
+ ```bash
34
+ change_id="<change-id>"
35
+ if [[ -n "$(git status --porcelain)" ]]; then
36
+ echo "error: working tree dirty before ws-plan creates change context"
37
+ echo "hint: commit/stash first, or continue inside an existing change/<change-id> context"
38
+ exit 2
39
+ fi
40
+
41
+ has_commits=0
42
+ git rev-parse --verify HEAD >/dev/null 2>&1 && has_commits=1
43
+
44
+ has_submodules=0
45
+ if [[ -f .gitmodules ]] && git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' >/dev/null 2>&1; then
46
+ has_submodules=1
47
+ fi
48
+
49
+ if [[ "${has_commits}" -eq 1 ]]; then
50
+ if [[ "${has_submodules}" -eq 1 ]]; then
51
+ aiws change start "${change_id}" --hooks --worktree --submodules
52
+ else
53
+ aiws change start "${change_id}" --hooks --worktree
54
+ fi
55
+ else
56
+ aiws change start "${change_id}" --hooks --no-switch
57
+ fi
58
+ ```
59
+ - 若上一步创建了 worktree:后续所有读取/写入都必须切到 `aiws change start` 输出的 `worktree:` 路径中进行;不要把 `plan/...` 写回原工作区
27
60
  5) 生成计划文件:
28
61
  - 文件名:`plan/YYYY-MM-DD_HH-MM-SS-<slug>.md`(`<slug>` 用 kebab-case;同一任务调整计划时尽量复用同一文件)
29
62
  - 若 `plan/` 不存在先创建
30
63
  - 必须实际写入到磁盘(不要只在对话里输出);如因权限/策略无法写盘,必须明确说明原因并输出可复制的完整内容
64
+ - 计划必须写在当前 active change 上下文内:若当前已进入 `change/<change-id>` worktree,则 `plan/...`、`proposal.md`、`tasks.md` 都应写在该 worktree 中
31
65
  6) 计划内容至少包含(不要留空):
32
66
  - `Bindings`:`Change_ID` / `Req_ID` / `Problem_ID` / `Contract_Row` / `Plan_File` / `Evidence_Path`
33
67
  - `Goal`:要达成什么
34
68
  - `Non-goals`:明确不做什么(避免 scope creep)
35
69
  - `Scope`:将改动的文件/目录清单(不确定就写 `TBD` 并说明如何确定)
36
70
  - `Plan`:分步执行(每步尽量落到具体文件/命令;必要时拆 Phase)
71
+ - `Submodules`(当存在 `.gitmodules` 且声明了 submodule 条目时,强制):声明“本次 change 的 submodule 目标分支真值”(用于同一 superproject 分支内的多渠道交付;也避免仅靠 `.gitmodules` 默认分支导致交付推送到错误分支)
37
72
  - `Verify`:可复现命令 + 期望结果(优先引用 `AI_WORKSPACE.md` 的入口;必要时补充 e2e)
38
73
  - `Risks & Rollback`:风险点 + 回滚方案(例如 git 回滚、`aiws rollback`、恢复备份等)
39
74
  - `Evidence`:计划文件路径;若创建了变更工件则附 `changes/<change-id>/...`
@@ -42,6 +77,38 @@ description: 规划(生成可落盘 plan/ 工件;供 ws-dev 执行)
42
77
  9) 若计划涉及“需求/验收”变更:先用 `$ws-req-review` 评审 → 用户确认后再 `$ws-req-change` 落盘(避免需求漂移)。
43
78
  10) 多步任务(≥2 步):后续进入实现时,使用 `update_plan` 工具跟踪 `pending → in_progress → completed`。
44
79
 
80
+ 补充:submodule 目标分支真值(强约束;同一 superproject 分支内可多渠道)
81
+ - 背景:`.gitmodules submodule.<name>.branch` 适合作为“团队默认分支真值”,但当同一 superproject 分支需要在不同交付中选择不同 submodule 目标分支(多渠道)时,仅靠 `.gitmodules` 不足。
82
+ - 强约束:当 `.gitmodules` 声明了 submodule 条目时,门禁会要求本次 change 存在该文件且覆盖所有 submodule path(否则 `aiws validate .` / `aiws change validate --strict` 阻断)。
83
+ - 约定:为本次 change 落盘一个“交付目标分支映射”文件,并在后续 `$ws-dev`/`$ws-deliver`/`$ws-finish` 优先使用它:
84
+ - 文件:`changes/<change-id>/submodules.targets`
85
+ - 格式:每行一个 submodule(忽略空行与 `#` 注释),字段用空白分隔(推荐 `TAB`):
86
+ - 第 1 列:submodule path(例如 `vendor/foo`)
87
+ - 第 2 列:target branch(例如 `release/channel-a`)
88
+ - 第 3 列(可选):remote 名(默认 `origin`)
89
+ - 生成模板(建议在确认 `Change_ID` 后执行;如文件已存在先备份再覆盖):
90
+ ```bash
91
+ change_id="<change-id>"
92
+ targets="changes/${change_id}/submodules.targets"
93
+ mkdir -p "changes/${change_id}"
94
+ if [[ -f "${targets}" ]]; then
95
+ bak="${targets}.bak.$(date -u +%Y%m%d-%H%M%SZ)"
96
+ cp "${targets}" "${bak}"
97
+ echo "info: backup: ${bak}"
98
+ fi
99
+ : > "${targets}"
100
+ echo "# path<TAB>target_branch<TAB>remote(optional, default=origin)" >> "${targets}"
101
+ while read -r key sub_path; do
102
+ name="${key#submodule.}"; name="${name%.path}"
103
+ b="$(git config --file .gitmodules --get "submodule.${name}.branch" 2>/dev/null || true)"
104
+ [[ "${b:-}" == "." ]] && b="$(git branch --show-current)" # '.' means "follow superproject branch"
105
+ printf "%s\t%s\t%s\n" "${sub_path}" "${b:-<fill-me>}" "origin" >> "${targets}"
106
+ done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
107
+ echo "ok: wrote ${targets}"
108
+ ```
109
+ - 计划里必须写清:本次交付选择的 `targets` 内容,以及后续在 `$ws-dev` 进入编码前会把 submodules 挂到 `aiws/pin/<target_branch>`(必要时先 `fetch`)。
110
+
45
111
  输出要求:
46
112
  - `Plan file:` <实际写入的路径>
113
+ - `Change context:` <当前 change 分支或 worktree 路径;若新建了 worktree 需明确写出>
47
114
  - `Next:` 推荐下一步(先 `$ws-plan-verify`,通过后再 `$ws-dev`;或 `aiws change start <change-id> --hooks`,superproject + submodule 可用 `--worktree`)
@@ -60,6 +60,12 @@ fi
60
60
  4) 逐个 push submodules(fast-forward only),再 push superproject:
61
61
  ```bash
62
62
  base_branch="$(git branch --show-current)"
63
+ change_id="$(echo "${base_branch}" | sed -n 's|^change/||p')"
64
+ targets="changes/${change_id}/submodules.targets"
65
+ # Note: if not on a change/ branch, change_id is empty and targets file won't exist;
66
+ # resolution falls back to .gitmodules submodule.<name>.branch via ws_resolve_sub_target.
67
+
68
+ source tools/ws_resolve_sub_target.sh
63
69
 
64
70
  git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null \
65
71
  | while read -r key sub_path; do
@@ -72,25 +78,25 @@ git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null
72
78
  exit 2
73
79
  fi
74
80
 
75
- cfg_branch="$(git config --file .gitmodules --get "submodule.${name}.branch" 2>/dev/null || true)"
76
- if [[ "${cfg_branch:-}" == "." ]]; then cfg_branch="$base_branch"; fi
77
- target_branch="${cfg_branch}"
81
+ ws_resolve_sub_target "${sub_path}" "${name}" "${targets}" "${base_branch}" || exit 2
82
+ target_branch="${_resolved_branch}"
83
+ remote="${_resolved_remote}"
78
84
 
79
- git -C "${sub_path}" fetch origin --prune
80
- if ! git -C "${sub_path}" show-ref --verify --quiet "refs/remotes/origin/${target_branch}"; then
81
- echo "error: missing origin/${target_branch} for ${sub_path}"
85
+ git -C "${sub_path}" fetch "${remote}" --prune
86
+ if ! git -C "${sub_path}" show-ref --verify --quiet "refs/remotes/${remote}/${target_branch}"; then
87
+ echo "error: missing ${remote}/${target_branch} for ${sub_path}"
82
88
  exit 2
83
89
  fi
84
90
 
85
- # fast-forward only: origin/<branch> 必须是 HEAD 的祖先
86
- if ! git -C "${sub_path}" merge-base --is-ancestor "origin/${target_branch}" HEAD; then
87
- echo "error: non-fast-forward (submodule=${sub_path}, branch=${target_branch})"
91
+ # fast-forward only: <remote>/<branch> 必须是 HEAD 的祖先
92
+ if ! git -C "${sub_path}" merge-base --is-ancestor "${remote}/${target_branch}" HEAD; then
93
+ echo "error: non-fast-forward (submodule=${sub_path}, remote=${remote}, branch=${target_branch})"
88
94
  echo "hint: rebase/merge in submodule, then retry"
89
95
  exit 2
90
96
  fi
91
97
 
92
- # push HEAD -> origin/<branch>(不 force)
93
- git -C "${sub_path}" push origin "HEAD:refs/heads/${target_branch}"
98
+ # push HEAD -> <remote>/<branch>(不 force)
99
+ git -C "${sub_path}" push "${remote}" "HEAD:refs/heads/${target_branch}"
94
100
  done
95
101
 
96
102
  # 最后 push superproject(仍需用户确认远端/分支)
@@ -102,4 +108,3 @@ git push
102
108
  - `Context:` 当前分支 + 是否有 submodules
103
109
  - `Submodules:` 每个 submodule push 的目标分支与结果(成功/阻断原因)
104
110
  - `Superproject:` push 结果
105
-
@@ -3,8 +3,10 @@
3
3
 
4
4
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
5
 
6
+ (私有原子入口;日常优先用 /ws-* 链路。)
7
+
6
8
  目标:
7
- - 归档已完成 change 工件
9
+ - 归档已完成 change 工件,并生成交接文档:`changes/archive/.../handoff.md`
8
10
 
9
11
  执行(在仓库根目录):
10
12
  ```bash
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-finish -->
2
2
  # aiws change finish
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-list -->
2
2
  # aiws change list
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-new -->
2
2
  # aiws change new
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-next -->
2
2
  # aiws change next
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-start -->
2
2
  # aiws change start
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-status -->
2
2
  # aiws change status
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-sync -->
2
2
  # aiws change sync
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-templates-init -->
2
2
  # aiws change templates init
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-templates-which -->
2
2
  # aiws change templates which
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-change-validate -->
2
2
  # aiws change validate
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-hooks-install -->
2
2
  # aiws hooks install
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-hooks-status -->
2
2
  # aiws hooks status
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
7
 
6
8
  目标:
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-init -->
2
2
  # aiws init
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  目标:
5
7
  - 生成/补齐真值文件与门禁文件(以 `AI_PROJECT.md` / `REQUIREMENTS.md` / `AI_WORKSPACE.md` 为准)
6
8
  - 写入/更新 `.gitignore` 的 aiws 托管块
@@ -16,4 +18,3 @@
16
18
  <!-- AIWS_MANAGED_END:claude:aiws-init -->
17
19
 
18
20
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
19
-
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-rollback -->
2
2
  # aiws rollback
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  目标:
5
7
  - 从 `.aiws/backups/` 恢复到某次备份快照
6
8
 
@@ -9,4 +11,3 @@
9
11
  <!-- AIWS_MANAGED_END:claude:aiws-rollback -->
10
12
 
11
13
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
12
-
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-update -->
2
2
  # aiws update
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  目标:
5
7
  - 基于当前 `@aipper/aiws-spec` 刷新模板与 tool-native 文件
6
8
  - 更新前备份到 `.aiws/backups/<timestamp>/`
@@ -15,4 +17,3 @@
15
17
  <!-- AIWS_MANAGED_END:claude:aiws-update -->
16
18
 
17
19
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
18
-
@@ -1,6 +1,8 @@
1
1
  <!-- AIWS_MANAGED_BEGIN:claude:aiws-validate -->
2
2
  # aiws validate
3
3
 
4
+ (私有原子入口;日常优先用 /ws-* 链路。)
5
+
4
6
  目标:
5
7
  - 作为 CI/本地门禁:校验 required 文件结构、托管块、`.aiws/manifest.json` 漂移
6
8
  - 强门禁:缺 `python3`/缺 required 脚本也应失败
@@ -10,4 +12,3 @@
10
12
  <!-- AIWS_MANAGED_END:claude:aiws-validate -->
11
13
 
12
14
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
13
-
@@ -8,15 +8,20 @@
8
8
 
9
9
  建议流程:
10
10
  1) 先运行 `/ws-preflight`。
11
- 2) 建立变更工件(推荐):`aiws change start <change-id> --hooks`(superproject+submodule 可用 `--worktree --submodules`)。
12
- 3) 通过已配置 zentao MCP 拉取 bug 字段与附件列表。
13
- 4) 落盘证据到 `changes/<change-id>/bug/`:
11
+ 2) 若当前不在 `change/<change-id>` 分支 / worktree,先建立 change 上下文:
12
+ - 工作区先保持干净
13
+ - 仓库已有提交:优先 `aiws change start <change-id> --hooks --worktree`
14
+ - superproject + submodule:优先 `aiws change start <change-id> --hooks --worktree --submodules`
15
+ - 仓库尚无提交 / 不满足 worktree 前置条件:回退 `aiws change start <change-id> --hooks --no-switch`
16
+ 3) 若上一步创建了 worktree:后续 bug 证据、CSV 更新、`/ws-dev` 修复都必须在该 worktree 中继续。
17
+ 4) 通过已配置 zentao MCP 拉取 bug 字段与附件列表。
18
+ 5) 落盘证据到当前 active change 上下文的 `changes/<change-id>/bug/`:
14
19
  - `zentao-bug-<bug-id>.json`
15
20
  - `zentao-bug-<bug-id>.md`
16
21
  - `images/<bug-id>/...`
17
- 5) upsert `issues/fix_bus_issues.csv`(主键 `Bug_ID`)。
18
- 6) 进入 `/ws-dev` 修复并回填状态字段 `Fix_Status/Verify_Command/Updated_At`。
19
- 7) 质量门:`aiws change validate <change-id> --strict` + `aiws validate . --stamp`。
22
+ 6) upsert 当前 active change 上下文中的 `issues/fix_bus_issues.csv`(主键 `Bug_ID`)。
23
+ 7) 进入 `/ws-dev` 修复并回填状态字段 `Fix_Status/Verify_Command/Updated_At`。
24
+ 8) 质量门:`aiws change validate <change-id> --strict` + `aiws validate . --stamp`。
20
25
 
21
26
  强制约束:
22
27
  - 不自动 commit / push。