@aipper/aiws-spec 0.0.21 → 0.0.23
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.
- package/docs/spec-contract.md +5 -4
- package/package.json +1 -1
- package/templates/workspace/.agents/skills/{aiws-change-archive → p-aiws-change-archive}/SKILL.md +3 -2
- package/templates/workspace/.agents/skills/{aiws-change-finish → p-aiws-change-finish}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-list → p-aiws-change-list}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-new → p-aiws-change-new}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-next → p-aiws-change-next}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-start → p-aiws-change-start}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-status → p-aiws-change-status}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-sync → p-aiws-change-sync}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-templates-init → p-aiws-change-templates-init}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-templates-which → p-aiws-change-templates-which}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-change-validate → p-aiws-change-validate}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-hooks-install → p-aiws-hooks-install}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-hooks-status → p-aiws-hooks-status}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-init → p-aiws-init}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-rollback → p-aiws-rollback}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-update → p-aiws-update}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/{aiws-validate → p-aiws-validate}/SKILL.md +2 -2
- package/templates/workspace/.agents/skills/p-tasks-plan/SKILL.md +37 -0
- package/templates/workspace/.agents/skills/ws-commit/SKILL.md +21 -6
- package/templates/workspace/.agents/skills/ws-deliver/SKILL.md +38 -9
- package/templates/workspace/.agents/skills/ws-dev/SKILL.md +61 -2
- package/templates/workspace/.agents/skills/ws-finish/SKILL.md +39 -25
- package/templates/workspace/.agents/skills/ws-handoff/SKILL.md +31 -0
- package/templates/workspace/.agents/skills/ws-plan/SKILL.md +32 -0
- package/templates/workspace/.agents/skills/ws-push/SKILL.md +17 -12
- package/templates/workspace/.claude/commands/{aiws-change-archive.md → p-aiws-change-archive.md} +3 -1
- package/templates/workspace/.claude/commands/{aiws-change-finish.md → p-aiws-change-finish.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-list.md → p-aiws-change-list.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-new.md → p-aiws-change-new.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-next.md → p-aiws-change-next.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-start.md → p-aiws-change-start.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-status.md → p-aiws-change-status.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-sync.md → p-aiws-change-sync.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-templates-init.md → p-aiws-change-templates-init.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-templates-which.md → p-aiws-change-templates-which.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-change-validate.md → p-aiws-change-validate.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-hooks-install.md → p-aiws-hooks-install.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-hooks-status.md → p-aiws-hooks-status.md} +2 -0
- package/templates/workspace/.claude/commands/{aiws-init.md → p-aiws-init.md} +2 -1
- package/templates/workspace/.claude/commands/{aiws-rollback.md → p-aiws-rollback.md} +2 -1
- package/templates/workspace/.claude/commands/{aiws-update.md → p-aiws-update.md} +2 -1
- package/templates/workspace/.claude/commands/{aiws-validate.md → p-aiws-validate.md} +2 -1
- package/templates/workspace/.claude/commands/ws-dev.md +4 -1
- package/templates/workspace/.claude/commands/ws-handoff.md +22 -0
- package/templates/workspace/.opencode/command/{aiws-change-archive.md → p-aiws-change-archive.md} +4 -2
- package/templates/workspace/.opencode/command/{aiws-change-finish.md → p-aiws-change-finish.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-list.md → p-aiws-change-list.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-new.md → p-aiws-change-new.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-next.md → p-aiws-change-next.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-start.md → p-aiws-change-start.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-status.md → p-aiws-change-status.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-sync.md → p-aiws-change-sync.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-templates-init.md → p-aiws-change-templates-init.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-templates-which.md → p-aiws-change-templates-which.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-change-validate.md → p-aiws-change-validate.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-hooks-install.md → p-aiws-hooks-install.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-hooks-status.md → p-aiws-hooks-status.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-init.md → p-aiws-init.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-rollback.md → p-aiws-rollback.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-update.md → p-aiws-update.md} +3 -1
- package/templates/workspace/.opencode/command/{aiws-validate.md → p-aiws-validate.md} +3 -1
- package/templates/workspace/.opencode/command/ws-commit.md +1 -1
- package/templates/workspace/.opencode/command/ws-dev.md +4 -1
- package/templates/workspace/.opencode/command/ws-handoff.md +25 -0
- package/templates/workspace/.opencode/command/ws-review.md +1 -1
- package/templates/workspace/AGENTS.md +7 -4
- package/templates/workspace/manifest.json +171 -103
- package/templates/workspace/tools/ws_change_check.py +99 -0
- package/templates/workspace/tools/ws_resolve_sub_target.sh +46 -0
- package/templates/workspace/tools/ws_tasks_plan.py +137 -0
package/docs/spec-contract.md
CHANGED
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
- managed surface(可托管面):
|
|
19
19
|
- `replace_file`:该文件由 aiws 全量托管(更新可整文件覆盖)。
|
|
20
20
|
- `managed_blocks`:只更新文件内 `AIWS_MANAGED_BEGIN/END` 标记的托管块;块外内容保留给用户自定义。
|
|
21
|
+
- `remove`:该文件在本版本已废弃;`update` 会先备份再删除(可通过 `aiws rollback` 恢复)。仅用于跨版本改名/废弃场景。
|
|
21
22
|
- unmanaged required(必需但不托管):
|
|
22
23
|
- 模板 `manifest.json` 里的 `required` 仅表示“init/validate 必须存在”。
|
|
23
24
|
- 若某个 required 文件不在 `update.replace_file` 且不在 `update.managed_blocks` 中:视为用户自主管理内容;`aiws update` 不应修改其内容(最多只补齐缺失文件)。
|
|
@@ -127,8 +128,8 @@ hash 规则(用于跨平台稳定性):
|
|
|
127
128
|
- 默认覆盖:Claude Code / OpenCode / Codex / iFlow。
|
|
128
129
|
|
|
129
130
|
约定路径(示例;最终以模板 manifest 为准):
|
|
130
|
-
- Claude Code:`.claude/commands/aiws-*.md`(commands
|
|
131
|
-
- OpenCode:`.opencode/command/aiws-*.md`(command
|
|
131
|
+
- Claude Code:`.claude/commands/p-aiws-*.md`(commands;私有原子入口)
|
|
132
|
+
- OpenCode:`.opencode/command/p-aiws-*.md`(command;私有原子入口)
|
|
132
133
|
- Codex:`.agents/skills/*/SKILL.md`(skills)
|
|
133
134
|
- iFlow:`.iflow/commands/aiws-*.toml`(commands)
|
|
134
135
|
|
|
@@ -160,13 +161,13 @@ hash 规则(用于跨平台稳定性):
|
|
|
160
161
|
|
|
161
162
|
建议的默认取值(与 `workspace` 模板一致):
|
|
162
163
|
- Claude Code
|
|
163
|
-
- `path_pattern`:`.claude/commands/aiws-*.md`
|
|
164
|
+
- `path_pattern`:`.claude/commands/p-aiws-*.md`
|
|
164
165
|
- `purpose`:提供“在 Claude 内可见的命令入口/说明”,引导用户运行 `npx @aipper/aiws ...`
|
|
165
166
|
- `autodiscovery`:不作为契约要求(由工具决定;aiws 不做保证)
|
|
166
167
|
- `reload_needed`:不作为契约要求
|
|
167
168
|
- `fallback`:在 shell 直接运行 `npx @aipper/aiws init|update|validate|rollback`
|
|
168
169
|
- OpenCode
|
|
169
|
-
- `path_pattern`:`.opencode/command/aiws-*.md`
|
|
170
|
+
- `path_pattern`:`.opencode/command/p-aiws-*.md`
|
|
170
171
|
- `purpose`:提供命令入口/说明(同上)
|
|
171
172
|
- `autodiscovery`:不作为契约要求
|
|
172
173
|
- `reload_needed`:不作为契约要求
|
package/package.json
CHANGED
package/templates/workspace/.agents/skills/{aiws-change-archive → p-aiws-change-archive}/SKILL.md
RENAMED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
|
-
name: aiws-change-archive
|
|
3
|
-
description:
|
|
2
|
+
name: p-aiws-change-archive
|
|
3
|
+
description: 私有:归档变更工件(会先做严格校验并生成 handoff.md)
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
目标:
|
|
@@ -21,3 +21,4 @@ fi
|
|
|
21
21
|
说明:
|
|
22
22
|
- `archive` 默认会先跑严格校验并要求 tasks 全部勾选
|
|
23
23
|
- `--force` 会绕过部分门禁(不推荐)
|
|
24
|
+
- 归档后会生成交接文档:`changes/archive/<date>-<change-id>/handoff.md`
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: p-tasks-plan
|
|
3
|
+
description: 原子:tasks 同步(从 changes/<id>/tasks.md 生成 update_plan payload)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
用中文输出(命令/路径/代码标识符保持原样不翻译)。
|
|
7
|
+
|
|
8
|
+
目标:
|
|
9
|
+
- 用 `changes/<change-id>/tasks.md` 的 checkbox 任务作为“步骤真值”
|
|
10
|
+
- 生成可直接用于 `update_plan` 的 JSON payload(`pending/in_progress/completed`)
|
|
11
|
+
- 输出当前进度摘要(done/total + 当前 in_progress)
|
|
12
|
+
|
|
13
|
+
约束:
|
|
14
|
+
- 不写入任何 secrets(token、账号、内网端点等不得进入 git)
|
|
15
|
+
- 只读 `changes/<id>/tasks.md`(不自动改写 tasks 文件)
|
|
16
|
+
- 未运行不声称已运行
|
|
17
|
+
|
|
18
|
+
执行步骤(建议):
|
|
19
|
+
1) 先运行 `$ws-preflight`。
|
|
20
|
+
2) 推断 `change-id`:
|
|
21
|
+
- 优先从当前分支名读取:`git rev-parse --abbrev-ref HEAD`(期望形如 `change/<change-id>`)
|
|
22
|
+
- 若无法推断:让用户明确本次要同步的 `<change-id>`
|
|
23
|
+
3) 检查 tasks 文件存在:`test -f changes/<change-id>/tasks.md`
|
|
24
|
+
4) 输出进度摘要:
|
|
25
|
+
```bash
|
|
26
|
+
python3 tools/ws_tasks_plan.py status --file changes/<change-id>/tasks.md
|
|
27
|
+
```
|
|
28
|
+
5) 生成 `update_plan` payload(JSON 输出到 stdout):
|
|
29
|
+
```bash
|
|
30
|
+
python3 tools/ws_tasks_plan.py plan --file changes/<change-id>/tasks.md --explanation "sync tasks.md -> update_plan"
|
|
31
|
+
```
|
|
32
|
+
6) 调用 `update_plan`,将上一步 JSON 原样作为入参(确保任意时刻最多 1 条 `in_progress`)。
|
|
33
|
+
|
|
34
|
+
输出要求:
|
|
35
|
+
- `Change_ID:` <change-id>
|
|
36
|
+
- `Tasks:` `changes/<change-id>/tasks.md`
|
|
37
|
+
- `Next:` 推荐下一步(通常为继续完善 tasks 或进入 `$ws-dev`)
|
|
@@ -18,6 +18,7 @@ description: 提交(当前分支可直提;submodule 感知;先审计/门
|
|
|
18
18
|
- 不自动 push
|
|
19
19
|
- 不写入任何 secrets
|
|
20
20
|
- 检测到 submodule 有未提交改动时,不允许直接提交 superproject(先处理 submodule)
|
|
21
|
+
- commit message 默认使用中文(命令/路径/代码标识符保持原样不翻译);格式建议:`<类型>: <简述>`(例如 `修复: 登录页空指针`、`功能: 新增 submodule targets 校验`、`重构: 提取共享脚本`)
|
|
21
22
|
|
|
22
23
|
执行步骤(建议):
|
|
23
24
|
1) 运行 `$ws-preflight`(确保真值文件就绪)。
|
|
@@ -59,11 +60,22 @@ done < <(git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/d
|
|
|
59
60
|
- 若该 submodule 当前为 detached HEAD:先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target_branch>`;不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”。
|
|
60
61
|
处理指引(detached submodule):
|
|
61
62
|
```bash
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
63
|
+
cur_branch="$(git branch --show-current)"
|
|
64
|
+
change_id="$(echo "${cur_branch}" | sed -n 's|^change/||p')"
|
|
65
|
+
targets="changes/${change_id}/submodules.targets"
|
|
66
|
+
|
|
67
|
+
source tools/ws_resolve_sub_target.sh
|
|
68
|
+
ws_resolve_sub_target "${sub_path}" "${sub_name}" "${targets}" "${cur_branch}" || exit 2
|
|
69
|
+
target_branch="${_resolved_branch}"
|
|
70
|
+
remote="${_resolved_remote}"
|
|
71
|
+
|
|
72
|
+
git -C "${sub_path}" fetch "${remote}" --prune
|
|
73
|
+
if ! git -C "${sub_path}" show-ref --verify --quiet "refs/remotes/${remote}/${target_branch}"; then
|
|
74
|
+
echo "error: missing ${remote}/${target_branch} for submodule path=${sub_path}"
|
|
75
|
+
exit 2
|
|
76
|
+
fi
|
|
77
|
+
git -C "${sub_path}" checkout -B "aiws/pin/${target_branch}" HEAD
|
|
78
|
+
git -C "${sub_path}" branch --set-upstream-to "${remote}/${target_branch}" "aiws/pin/${target_branch}" >/dev/null 2>&1 || true
|
|
67
79
|
```
|
|
68
80
|
7) 检查当前 staging 内容(必须输出给用户确认):
|
|
69
81
|
```bash
|
|
@@ -71,7 +83,10 @@ git status --porcelain
|
|
|
71
83
|
git diff --staged --submodule=short
|
|
72
84
|
```
|
|
73
85
|
8) 若没有 staged changes:停止并提示用户先明确要提交哪些文件(例如 `git add -p` 或 `git add <path>`)。
|
|
74
|
-
9)
|
|
86
|
+
9) 生成中文 commit message 草案(格式:`<类型>: <简述>`),输出给用户确认后再执行。
|
|
87
|
+
- 类型参考:`功能` / `修复` / `重构` / `文档` / `测试` / `构建` / `杂项`
|
|
88
|
+
- 简述用一句话概括本次改动的"为什么"而非"改了什么"
|
|
89
|
+
- 命令/路径/代码标识符保持原样不翻译
|
|
75
90
|
10) 执行提交(不带 `--no-verify`):
|
|
76
91
|
```bash
|
|
77
92
|
git commit -m "<message>"
|
|
@@ -29,6 +29,7 @@ description: 交付(submodules + superproject 分步提交,并安全合并
|
|
|
29
29
|
|
|
30
30
|
## 0) submodule branch 真值检查(减少 detached 与人为差异)
|
|
31
31
|
如果存在 `.gitmodules` 但缺少 `submodule.<name>.branch`,先运行 `$ws-submodule-setup` 并提交 `.gitmodules`,否则后续 `aiws validate .` 会失败,且 `ws-pull/ws-finish` 无法确定性工作。
|
|
32
|
+
> 说明:若同一 superproject 分支内存在“多渠道 submodule 目标分支”的交付需求,可在 `changes/<change-id>/submodules.targets` 额外声明本次 change 的目标分支;交付时会优先使用该文件(不改变 `.gitmodules` 的团队默认真值)。
|
|
32
33
|
```bash
|
|
33
34
|
if [[ -f .gitmodules ]]; then
|
|
34
35
|
missing=0
|
|
@@ -44,6 +45,28 @@ if [[ -f .gitmodules ]]; then
|
|
|
44
45
|
echo "hint: run $ws-submodule-setup (and commit .gitmodules), then retry"
|
|
45
46
|
exit 2
|
|
46
47
|
fi
|
|
48
|
+
|
|
49
|
+
# 强约束:当 .gitmodules 声明 submodules 时,要求本次 change 存在 submodules.targets 且覆盖所有 submodule path
|
|
50
|
+
if git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' >/dev/null 2>&1; then
|
|
51
|
+
change_id="$(git branch --show-current | sed -n 's|^change/||p')"
|
|
52
|
+
targets="changes/${change_id}/submodules.targets"
|
|
53
|
+
if [[ ! -f "${targets}" ]]; then
|
|
54
|
+
echo "error: missing ${targets} (required when .gitmodules declares submodules)"
|
|
55
|
+
exit 2
|
|
56
|
+
fi
|
|
57
|
+
t_missing=0
|
|
58
|
+
while read -r _ sub_path; do
|
|
59
|
+
[[ -z "${sub_path:-}" ]] && continue
|
|
60
|
+
if ! awk -v p="${sub_path}" '$1==p && $0 !~ /^[[:space:]]*#/ && $2!="" { found=1 } END { exit(found?0:1) }' "${targets}" 2>/dev/null; then
|
|
61
|
+
echo "error: ${targets} missing entry for submodule path=${sub_path}"
|
|
62
|
+
t_missing=1
|
|
63
|
+
fi
|
|
64
|
+
done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
|
|
65
|
+
if [[ "${t_missing}" -ne 0 ]]; then
|
|
66
|
+
echo "hint: fill ${targets} as: <path> <target_branch> [remote]"
|
|
67
|
+
exit 2
|
|
68
|
+
fi
|
|
69
|
+
fi
|
|
47
70
|
fi
|
|
48
71
|
```
|
|
49
72
|
|
|
@@ -65,19 +88,25 @@ git -C "$sub_path" status --porcelain
|
|
|
65
88
|
2) 若 submodule 处于 detached HEAD(`branch --show-current` 为空):
|
|
66
89
|
- 说明:这通常是因为 superproject 的 gitlink checkout(例如 `git submodule update`)导致 detached。
|
|
67
90
|
- 不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”。
|
|
68
|
-
- 若你要在该 submodule
|
|
91
|
+
- 若你要在该 submodule 里提交:先按目标分支挂到 pin 分支 `aiws/pin/<target-branch>`,再在其上提交:
|
|
92
|
+
- 目标分支真值优先级:`changes/<change-id>/submodules.targets`(若存在)> `.gitmodules submodule.<name>.branch`
|
|
69
93
|
```bash
|
|
70
|
-
|
|
94
|
+
change_id="<change-id>"
|
|
95
|
+
targets="changes/${change_id}/submodules.targets"
|
|
71
96
|
base_branch="$(git branch --show-current)"
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
97
|
+
|
|
98
|
+
source tools/ws_resolve_sub_target.sh
|
|
99
|
+
ws_resolve_sub_target "${sub_path}" "${sub_name}" "${targets}" "${base_branch}" || exit 2
|
|
100
|
+
target_branch="${_resolved_branch}"
|
|
101
|
+
remote="${_resolved_remote}"
|
|
102
|
+
|
|
103
|
+
git -C "$sub_path" fetch "${remote}" --prune
|
|
104
|
+
if ! git -C "$sub_path" show-ref --verify --quiet "refs/remotes/${remote}/${target_branch}"; then
|
|
105
|
+
echo "error: missing ${remote}/${target_branch} for submodule path=${sub_path}"
|
|
76
106
|
exit 2
|
|
77
107
|
fi
|
|
78
|
-
git -C "$sub_path"
|
|
79
|
-
git -C "$sub_path"
|
|
80
|
-
git -C "$sub_path" branch --set-upstream-to "origin/${cfg_branch}" "aiws/pin/${cfg_branch}" >/dev/null 2>&1 || true
|
|
108
|
+
git -C "$sub_path" checkout -B "aiws/pin/${target_branch}" HEAD
|
|
109
|
+
git -C "$sub_path" branch --set-upstream-to "${remote}/${target_branch}" "aiws/pin/${target_branch}" >/dev/null 2>&1 || true
|
|
81
110
|
```
|
|
82
111
|
- 若 `origin/<target-branch>` 不存在,或用户明确不想使用 pin 分支:停止,解释风险(提交可能不可追溯/难以推送)。
|
|
83
112
|
3) 选择性 staging(默认用 `-p` 更安全):
|
|
@@ -12,9 +12,68 @@ description: 开发(按需求实现并验证;适用于任何需要修改代
|
|
|
12
12
|
- 若是中大型任务:建议先用 `$ws-plan` 生成 `plan/` 工件,再进入实现(便于可回放与对齐验证入口)。
|
|
13
13
|
- 若已有 `plan/` 工件:先执行 `$ws-plan-verify`;通过后再进入实现(防止计划过长/跑偏)。
|
|
14
14
|
2) 建立变更归因(推荐):
|
|
15
|
-
-
|
|
16
|
-
-
|
|
15
|
+
- ⚠️ 开始前先确认工作区干净:`git status --porcelain` 为空;否则切分支/创建 worktree 后,未提交改动可能“看起来丢了”(worktree 只从 `HEAD` checkout,未提交内容会留在原目录)。
|
|
16
|
+
- 强制(当你准备执行 `aiws change start ... --worktree/--switch` 创建新 change 时):先同步线上代码(含 submodules),避免基线过旧导致的 rebase/冲突与“别人已更新但本地没拉”的协作摩擦。
|
|
17
|
+
- 若你已经在 `change/<change-id>` 上继续开发:不要在此处强制 pull(避免把远端变动拉进变更分支);改为按需 `git fetch`/rebase,并保持门禁与验证可复现。
|
|
18
|
+
- 在 AI 工具中运行:`$ws-pull`(推荐;会在工作区不干净时阻断)
|
|
19
|
+
- 或等价手工(必须工作区干净;失败则停止并人工处理):
|
|
20
|
+
```bash
|
|
21
|
+
cur="$(git branch --show-current)"
|
|
22
|
+
if [[ "${cur}" =~ ^(change|changes|ws|ws-change)/ ]]; then
|
|
23
|
+
echo "info: already on change branch (${cur}); skip pull here"
|
|
24
|
+
else
|
|
25
|
+
git status --porcelain
|
|
26
|
+
git pull --ff-only
|
|
27
|
+
if [[ -f .gitmodules ]]; then
|
|
28
|
+
git submodule sync --recursive
|
|
29
|
+
git submodule update --init --recursive
|
|
30
|
+
fi
|
|
31
|
+
fi
|
|
32
|
+
```
|
|
33
|
+
- 推荐更安全(默认):`aiws change start <change-id> --hooks --no-switch`(只创建分支/工件 + 启用 hooks;不切分支)
|
|
34
|
+
- 准备进入实现(且工作区干净)后再切换:`git switch change/<change-id>`
|
|
35
|
+
- 若你明确要“一键切分支”(不推荐,且 dirty 会被拦截):`aiws change start <change-id> --hooks --switch`
|
|
36
|
+
- superproject + submodule(推荐):`aiws change start <change-id> --hooks --worktree --submodules`(创建独立 worktree;当前目录分支保持不变;会在新 worktree 内初始化 submodules;若忘了 `--submodules` 也会强制初始化)
|
|
17
37
|
- 若后续需要在 detached submodule 内提交:先挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`
|
|
38
|
+
- 若仓库存在 submodule(`.gitmodules` 声明了 submodule 条目):进入编码前必须准备好 `changes/<change-id>/submodules.targets`,并把每个 submodule 挂到对应的 `aiws/pin/<target_branch>`(必要时切到 `<remote>/<target_branch>`;这可能会改变 superproject 的 gitlink 指针,属于预期的“选渠道”行为;缺失该文件会被门禁阻断)
|
|
39
|
+
```bash
|
|
40
|
+
change_id="<change-id>"
|
|
41
|
+
targets="changes/${change_id}/submodules.targets"
|
|
42
|
+
has_submodules=0
|
|
43
|
+
if [[ -f .gitmodules ]]; then
|
|
44
|
+
if git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' >/dev/null 2>&1; then
|
|
45
|
+
has_submodules=1
|
|
46
|
+
fi
|
|
47
|
+
fi
|
|
48
|
+
if [[ "${has_submodules}" -eq 1 && ! -f "${targets}" ]]; then
|
|
49
|
+
echo "error: missing ${targets} (required when .gitmodules declares submodules)"
|
|
50
|
+
exit 2
|
|
51
|
+
fi
|
|
52
|
+
|
|
53
|
+
if [[ -f "${targets}" ]]; then
|
|
54
|
+
echo "info: applying submodule targets: ${targets}"
|
|
55
|
+
while read -r sub_path target_branch remote; do
|
|
56
|
+
[[ -z "${sub_path:-}" ]] && continue
|
|
57
|
+
[[ "${sub_path}" == \#* ]] && continue
|
|
58
|
+
[[ -z "${target_branch:-}" ]] && { echo "error: missing target_branch for path=${sub_path}"; exit 2; }
|
|
59
|
+
remote="${remote:-origin}"
|
|
60
|
+
|
|
61
|
+
echo "== submodule: ${sub_path} target=${remote}/${target_branch} =="
|
|
62
|
+
if [[ -n "$(git -C "${sub_path}" status --porcelain 2>/dev/null)" ]]; then
|
|
63
|
+
echo "error: submodule dirty: ${sub_path} (commit/stash first)"
|
|
64
|
+
exit 2
|
|
65
|
+
fi
|
|
66
|
+
git -C "${sub_path}" fetch "${remote}" --prune
|
|
67
|
+
# 选渠道:默认切到远端目标分支,并用 pin 分支承载本地提交(避免 detached)
|
|
68
|
+
git -C "${sub_path}" checkout -B "aiws/pin/${target_branch}" "${remote}/${target_branch}"
|
|
69
|
+
git -C "${sub_path}" branch --set-upstream-to "${remote}/${target_branch}" "aiws/pin/${target_branch}" >/dev/null 2>&1 || true
|
|
70
|
+
git -C "${sub_path}" status -sb
|
|
71
|
+
done < "${targets}"
|
|
72
|
+
|
|
73
|
+
# 检查 superproject 的 gitlink 是否发生变化(预期:若切了不同渠道,会看到差异)
|
|
74
|
+
git diff --submodule
|
|
75
|
+
fi
|
|
76
|
+
```
|
|
18
77
|
- 若你明确要在 superproject 直接切分支:`aiws change start <change-id> --hooks --switch`(仅在存在 `.gitmodules` 时有意义;会尝试让 submodules 工作区跟随 superproject 指针)
|
|
19
78
|
- 或手工:`git switch -c change/<change-id>`,并创建 `changes/<change-id>/proposal.md` 与 `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
|
|
20
79
|
3) 如涉及需求调整:先做需求评审(可用 `$ws-req-review`)→ 用户确认后再做需求落盘(可用 `$ws-req-change`)(避免需求漂移)。
|
|
@@ -78,6 +78,8 @@ fi
|
|
|
78
78
|
```bash
|
|
79
79
|
# superproject 当前分支(finish 后通常是 base 分支)
|
|
80
80
|
base_branch="$(git branch --show-current)"
|
|
81
|
+
change_id="<change-id>"
|
|
82
|
+
targets="changes/${change_id}/submodules.targets"
|
|
81
83
|
|
|
82
84
|
# 子模块清单(没有则跳过)
|
|
83
85
|
git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null || true
|
|
@@ -89,38 +91,50 @@ git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null |
|
|
|
89
91
|
# - 不要直接切 `change/<change-id>` / `main` / `master` 等业务分支来“解 detached”
|
|
90
92
|
# - 不改动 submodule 现有分支指针(例如不强行移动 main/master)
|
|
91
93
|
# - 创建/更新本地 pin 分支:`aiws/pin/<target_branch>` 指向 gitlink commit,并将其 upstream 设为 `origin/<target_branch>`
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
continue
|
|
98
|
-
fi
|
|
99
|
-
target_branch="$cfg_branch"
|
|
100
|
-
pin_branch="aiws/pin/${target_branch}"
|
|
94
|
+
if git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' >/dev/null 2>&1; then
|
|
95
|
+
if [[ ! -f “${targets}” ]]; then
|
|
96
|
+
echo “error: missing ${targets} (required when .gitmodules declares submodules)”
|
|
97
|
+
exit 2
|
|
98
|
+
fi
|
|
101
99
|
|
|
102
|
-
|
|
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
|
|
100
|
+
source tools/ws_resolve_sub_target.sh
|
|
107
101
|
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
102
|
+
while read -r key sub_path; do
|
|
103
|
+
name=”${key#submodule.}”; name=”${name%.path}”
|
|
104
|
+
[[ -z “${sub_path:-}” ]] && continue
|
|
105
|
+
echo “== submodule: ${sub_path} (${name}) ==”
|
|
106
|
+
|
|
107
|
+
sub_sha=”$(git rev-parse “HEAD:${sub_path}”)”
|
|
108
|
+
|
|
109
|
+
ws_resolve_sub_target “${sub_path}” “${name}” “${targets}” “${base_branch}” || exit 2
|
|
110
|
+
target_branch=”${_resolved_branch}”
|
|
111
|
+
remote=”${_resolved_remote}”
|
|
112
|
+
pin_branch=”aiws/pin/${target_branch}”
|
|
113
113
|
|
|
114
|
-
git -C
|
|
115
|
-
git -C
|
|
116
|
-
|
|
114
|
+
git -C “${sub_path}” fetch “${remote}” --prune
|
|
115
|
+
if ! git -C “${sub_path}” show-ref --verify --quiet “refs/remotes/${remote}/${target_branch}”; then
|
|
116
|
+
echo “error: ${sub_path}: missing ${remote}/${target_branch}; refusing to push superproject (would break gitlink fetchability)”
|
|
117
|
+
exit 2
|
|
118
|
+
fi
|
|
117
119
|
|
|
118
|
-
#
|
|
119
|
-
git -C
|
|
120
|
+
# 仅当 gitlink commit 属于 <remote>/<target_branch> 的历史时才”挂回分支”
|
|
121
|
+
if ! git -C “${sub_path}” merge-base --is-ancestor “${sub_sha}” “${remote}/${target_branch}”; then
|
|
122
|
+
echo “[warn] ${sub_path}: ${sub_sha} is not in ${remote}/${target_branch}; keep detached and stop (need manual reconcile)”
|
|
123
|
+
exit 1
|
|
124
|
+
fi
|
|
125
|
+
|
|
126
|
+
git -C “${sub_path}” checkout -B “${pin_branch}” “${sub_sha}”
|
|
127
|
+
git -C “${sub_path}” branch --set-upstream-to “${remote}/${target_branch}” “${pin_branch}” >/dev/null 2>&1 || true
|
|
128
|
+
git -C “${sub_path}” status -sb
|
|
129
|
+
|
|
130
|
+
# push:只允许 fast-forward(若远端分叉会被拒绝;此时必须人工处理)
|
|
131
|
+
git -C “${sub_path}” push “${remote}” “${sub_sha}:refs/heads/${target_branch}”
|
|
132
|
+
done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
|
|
133
|
+
fi
|
|
120
134
|
```
|
|
121
135
|
规则:
|
|
122
136
|
- 每个 submodule 必须先执行“pin 分支挂回 + fast-forward push”,再 push 主仓库。
|
|
123
|
-
- 若任一 submodule 的 gitlink commit 不在
|
|
137
|
+
- 若任一 submodule 的 gitlink commit 不在 `<remote>/<target_branch>` 历史中:立即停止,先人工处理分叉,再继续。
|
|
124
138
|
|
|
125
139
|
5) 仅当 submodules 全部成功后,再 push superproject 当前分支:
|
|
126
140
|
```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
|
+
|
|
@@ -34,6 +34,7 @@ description: 规划(生成可落盘 plan/ 工件;供 ws-dev 执行)
|
|
|
34
34
|
- `Non-goals`:明确不做什么(避免 scope creep)
|
|
35
35
|
- `Scope`:将改动的文件/目录清单(不确定就写 `TBD` 并说明如何确定)
|
|
36
36
|
- `Plan`:分步执行(每步尽量落到具体文件/命令;必要时拆 Phase)
|
|
37
|
+
- `Submodules`(当存在 `.gitmodules` 且声明了 submodule 条目时,强制):声明“本次 change 的 submodule 目标分支真值”(用于同一 superproject 分支内的多渠道交付;也避免仅靠 `.gitmodules` 默认分支导致交付推送到错误分支)
|
|
37
38
|
- `Verify`:可复现命令 + 期望结果(优先引用 `AI_WORKSPACE.md` 的入口;必要时补充 e2e)
|
|
38
39
|
- `Risks & Rollback`:风险点 + 回滚方案(例如 git 回滚、`aiws rollback`、恢复备份等)
|
|
39
40
|
- `Evidence`:计划文件路径;若创建了变更工件则附 `changes/<change-id>/...`
|
|
@@ -42,6 +43,37 @@ description: 规划(生成可落盘 plan/ 工件;供 ws-dev 执行)
|
|
|
42
43
|
9) 若计划涉及“需求/验收”变更:先用 `$ws-req-review` 评审 → 用户确认后再 `$ws-req-change` 落盘(避免需求漂移)。
|
|
43
44
|
10) 多步任务(≥2 步):后续进入实现时,使用 `update_plan` 工具跟踪 `pending → in_progress → completed`。
|
|
44
45
|
|
|
46
|
+
补充:submodule 目标分支真值(强约束;同一 superproject 分支内可多渠道)
|
|
47
|
+
- 背景:`.gitmodules submodule.<name>.branch` 适合作为“团队默认分支真值”,但当同一 superproject 分支需要在不同交付中选择不同 submodule 目标分支(多渠道)时,仅靠 `.gitmodules` 不足。
|
|
48
|
+
- 强约束:当 `.gitmodules` 声明了 submodule 条目时,门禁会要求本次 change 存在该文件且覆盖所有 submodule path(否则 `aiws validate .` / `aiws change validate --strict` 阻断)。
|
|
49
|
+
- 约定:为本次 change 落盘一个“交付目标分支映射”文件,并在后续 `$ws-dev`/`$ws-deliver`/`$ws-finish` 优先使用它:
|
|
50
|
+
- 文件:`changes/<change-id>/submodules.targets`
|
|
51
|
+
- 格式:每行一个 submodule(忽略空行与 `#` 注释),字段用空白分隔(推荐 `TAB`):
|
|
52
|
+
- 第 1 列:submodule path(例如 `vendor/foo`)
|
|
53
|
+
- 第 2 列:target branch(例如 `release/channel-a`)
|
|
54
|
+
- 第 3 列(可选):remote 名(默认 `origin`)
|
|
55
|
+
- 生成模板(建议在确认 `Change_ID` 后执行;如文件已存在先备份再覆盖):
|
|
56
|
+
```bash
|
|
57
|
+
change_id="<change-id>"
|
|
58
|
+
targets="changes/${change_id}/submodules.targets"
|
|
59
|
+
mkdir -p "changes/${change_id}"
|
|
60
|
+
if [[ -f "${targets}" ]]; then
|
|
61
|
+
bak="${targets}.bak.$(date -u +%Y%m%d-%H%M%SZ)"
|
|
62
|
+
cp "${targets}" "${bak}"
|
|
63
|
+
echo "info: backup: ${bak}"
|
|
64
|
+
fi
|
|
65
|
+
: > "${targets}"
|
|
66
|
+
echo "# path<TAB>target_branch<TAB>remote(optional, default=origin)" >> "${targets}"
|
|
67
|
+
while read -r key sub_path; do
|
|
68
|
+
name="${key#submodule.}"; name="${name%.path}"
|
|
69
|
+
b="$(git config --file .gitmodules --get "submodule.${name}.branch" 2>/dev/null || true)"
|
|
70
|
+
[[ "${b:-}" == "." ]] && b="$(git branch --show-current)" # '.' means "follow superproject branch"
|
|
71
|
+
printf "%s\t%s\t%s\n" "${sub_path}" "${b:-<fill-me>}" "origin" >> "${targets}"
|
|
72
|
+
done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
|
|
73
|
+
echo "ok: wrote ${targets}"
|
|
74
|
+
```
|
|
75
|
+
- 计划里必须写清:本次交付选择的 `targets` 内容,以及后续在 `$ws-dev` 进入编码前会把 submodules 挂到 `aiws/pin/<target_branch>`(必要时先 `fetch`)。
|
|
76
|
+
|
|
45
77
|
输出要求:
|
|
46
78
|
- `Plan file:` <实际写入的路径>
|
|
47
79
|
- `Next:` 推荐下一步(先 `$ws-plan-verify`,通过后再 `$ws-dev`;或 `aiws change start <change-id> --hooks`,superproject + submodule 可用 `--worktree`)
|