@aipper/aiws-spec 0.0.19 → 0.0.21

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 (32) hide show
  1. package/docs/cli-interface.md +32 -1
  2. package/package.json +1 -1
  3. package/templates/workspace/.agents/skills/ws-commit/SKILL.md +14 -5
  4. package/templates/workspace/.agents/skills/ws-deliver/SKILL.md +34 -4
  5. package/templates/workspace/.agents/skills/ws-dev/SKILL.md +4 -3
  6. package/templates/workspace/.agents/skills/ws-finish/SKILL.md +41 -1
  7. package/templates/workspace/.agents/skills/ws-review/SKILL.md +7 -5
  8. package/templates/workspace/.claude/commands/ws-analyze.md +1 -1
  9. package/templates/workspace/.claude/commands/ws-bugfix.md +1 -1
  10. package/templates/workspace/.claude/commands/ws-commit.md +6 -5
  11. package/templates/workspace/.claude/commands/ws-deliver.md +11 -5
  12. package/templates/workspace/.claude/commands/ws-dev.md +7 -5
  13. package/templates/workspace/.claude/commands/ws-finish.md +10 -1
  14. package/templates/workspace/.claude/commands/ws-plan.md +2 -2
  15. package/templates/workspace/.claude/commands/ws-preflight.md +4 -4
  16. package/templates/workspace/.claude/commands/ws-req-review.md +2 -2
  17. package/templates/workspace/.claude/commands/ws-review.md +7 -5
  18. package/templates/workspace/.codex/prompts/ws-dev.md +8 -5
  19. package/templates/workspace/.codex/prompts/ws-review.md +7 -5
  20. package/templates/workspace/.iflow/commands/ws-commit.toml +5 -4
  21. package/templates/workspace/.iflow/commands/ws-deliver.toml +7 -1
  22. package/templates/workspace/.iflow/commands/ws-finish.toml +10 -1
  23. package/templates/workspace/.iflow/commands/ws-review.toml +5 -4
  24. package/templates/workspace/.opencode/command/ws-commit.md +6 -5
  25. package/templates/workspace/.opencode/command/ws-deliver.md +7 -1
  26. package/templates/workspace/.opencode/command/ws-dev.md +7 -5
  27. package/templates/workspace/.opencode/command/ws-finish.md +10 -1
  28. package/templates/workspace/.opencode/command/ws-review.md +7 -5
  29. package/templates/workspace/AGENTS.md +1 -1
  30. package/templates/workspace/AI_PROJECT.md +2 -0
  31. package/templates/workspace/changes/templates/proposal.md +35 -5
  32. package/templates/workspace/tools/ws_change_check.py +198 -0
@@ -274,9 +274,31 @@
274
274
  - 调用 `python3 tools/ws_change_check.py` 校验单个 change 工件目录。
275
275
  - `--strict`:强门禁(例如 WS:TODO、占位符、缺归因、truth drift 等都失败)。
276
276
  - `--allow-truth-drift`:仅用于紧急场景跳过 truth drift gating(仍会输出 warnings;不建议常用)。
277
+ - `--check-evidence`:证据存在性门禁(可选):校验 `Evidence_Path` 声明的路径必须是工作区相对路径且文件存在;并要求至少包含 1 条持久证据路径(`changes/<id>/evidence/...` 或 `changes/<id>/review/...`)。
278
+ - `--check-scope`:scope 门禁(可选,best-effort):要求 proposal/plan 中包含 “In Scope” 与 “Out of Scope” 标记(用于减少 scope creep)。
277
279
 
278
280
  接口(仅定义):
279
- - `aiws change validate [<change-id>] [--strict] [--allow-truth-drift]`
281
+ - `aiws change validate [<change-id>] [--strict] [--allow-truth-drift] [--check-evidence] [--check-scope]`
282
+
283
+ 说明:
284
+ - `--check-evidence/--check-scope` 默认不启用,避免对历史 change 造成行为破坏;交付阶段建议启用 `--check-evidence`。
285
+
286
+ ### `aiws change evidence`
287
+
288
+ 语义:
289
+ - 为交付阶段生成“可入库的持久证据”,并自动回填 `Evidence_Path`:
290
+ - 写入 `changes/<change-id>/evidence/`(例如:严格门禁结果快照、状态快照、validate stamp 复制等)
291
+ - 将生成的证据路径追加回填到:
292
+ - `changes/<change-id>/proposal.md` 的 `Evidence_Path`
293
+ - `Plan_File` 指向的计划文件中的 `Evidence_Path`(若存在)
294
+ - 若存在 `.agentdocs/tmp/review/codex-review.md` 且 `changes/<id>/review/codex-review.md` 不存在:复制为持久 review 证据。
295
+
296
+ 接口(仅定义):
297
+ - `aiws change evidence [<change-id>] [--no-validate] [--allow-fail]`
298
+
299
+ 选项:
300
+ - `--no-validate`:不执行严格门禁校验;仅生成/回填证据(适用于早期阶段先固化证据结构)。
301
+ - `--allow-fail`:即使严格门禁失败也继续生成证据并回填(默认会失败退出,避免“带病交付”)。
280
302
 
281
303
  ### `aiws change archive`
282
304
 
@@ -301,6 +323,7 @@
301
323
  - `aiws change list`
302
324
  - `aiws change status [<change-id>]`
303
325
  - `aiws change next [<change-id>]`
326
+ - `aiws change state [<change-id>] [--write]`(状态快照;`--write` 写入 `changes/<id>/STATE.md`)
304
327
 
305
328
  ### `aiws change templates which/init`
306
329
 
@@ -310,3 +333,11 @@
310
333
  接口(仅定义):
311
334
  - `aiws change templates which`
312
335
  - `aiws change templates init`
336
+
337
+ ## `aiws metrics`
338
+
339
+ 语义:
340
+ - 读取 `changes/*/metrics.json` 与 `changes/archive/*/metrics.json`,输出 best-effort 流程指标汇总(不修改任何文件)。
341
+
342
+ 接口(仅定义):
343
+ - `aiws metrics summary [--since YYYY-MM-DD]`
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@aipper/aiws-spec",
3
- "version": "0.0.19",
3
+ "version": "0.0.21",
4
4
  "description": "AIWS spec and templates (single source of truth).",
5
5
  "type": "module",
6
6
  "files": [
@@ -21,7 +21,7 @@ description: 提交(当前分支可直提;submodule 感知;先审计/门
21
21
 
22
22
  执行步骤(建议):
23
23
  1) 运行 `$ws-preflight`(确保真值文件就绪)。
24
- 2) 运行 `$ws-review`(生成审计证据:`.agentdocs/tmp/review/codex-review.md`)。
24
+ 2) 运行 `$ws-review`(优先生成审计证据:`changes/<change-id>/review/codex-review.md`;无 `change-id` 时回退 `.agentdocs/tmp/review/codex-review.md`)。
25
25
  3) 运行门禁校验并写 stamp:
26
26
  ```bash
27
27
  if [[ -x "./node_modules/.bin/aiws" ]]; then
@@ -55,7 +55,16 @@ while read -r _ sub_path; do
55
55
  done < <(git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null || true)
56
56
  ```
57
57
  判定规则(强制):
58
- - 任一 submodule `git status --porcelain` 非空:停止 superproject commit,先在对应 submodule 完成 commit(必要时先切回可提交分支),再回到 superproject 更新并提交 gitlink。
58
+ - 任一 submodule `git status --porcelain` 非空:停止 superproject commit,先在对应 submodule 完成 commit,再回到 superproject 更新并提交 gitlink。
59
+ - 若该 submodule 当前为 detached HEAD:先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target_branch>`;不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”。
60
+ 处理指引(detached submodule):
61
+ ```bash
62
+ sub_name="<submodule-name>"
63
+ base_branch="$(git branch --show-current)"
64
+ cfg_branch="$(git config --file .gitmodules --get "submodule.${sub_name}.branch" 2>/dev/null || true)"
65
+ if [[ "${cfg_branch:-}" == "." ]]; then cfg_branch="$base_branch"; fi
66
+ git -C "${sub_path}" checkout -B "aiws/pin/${cfg_branch}" HEAD
67
+ ```
59
68
  7) 检查当前 staging 内容(必须输出给用户确认):
60
69
  ```bash
61
70
  git status --porcelain
@@ -73,6 +82,6 @@ git show --stat --oneline -1
73
82
  ```
74
83
 
75
84
  输出要求:
76
- - `Evidence:` `.agentdocs/tmp/review/codex-review.md` + `.agentdocs/tmp/aiws-validate/*.json`
77
- - `Context:` 当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
78
- - `Commit:` 最终使用的 commit message(仅当用户确认后)
85
+ - `证据(Evidence):` `changes/<change-id>/review/codex-review.md`(无 `change-id` 时回退 `.agentdocs/tmp/review/codex-review.md`) + `.agentdocs/tmp/aiws-validate/*.json`
86
+ - `上下文(Context):` 当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
87
+ - `提交信息(Commit):` 最终使用的 commit message(仅当用户确认后)
@@ -64,11 +64,22 @@ git -C "$sub_path" status --porcelain
64
64
  ```
65
65
  2) 若 submodule 处于 detached HEAD(`branch --show-current` 为空):
66
66
  - 说明:这通常是因为 superproject 的 gitlink checkout(例如 `git submodule update`)导致 detached。
67
- - 若你要在该 submodule 里提交:在 submodule 内创建并切到同名 change 分支(与 superproject 对齐),例如 `change/<change-id>`:
67
+ - 不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”。
68
+ - 若你要在该 submodule 里提交:先按 `.gitmodules` 的目标分支挂到 pin 分支 `aiws/pin/<target-branch>`,再在其上提交:
68
69
  ```bash
69
- git -C "$sub_path" switch -c "change/<change-id>"
70
+ sub_name="<submodule-name>"
71
+ base_branch="$(git branch --show-current)"
72
+ cfg_branch="$(git config --file .gitmodules --get "submodule.${sub_name}.branch" 2>/dev/null || true)"
73
+ if [[ "${cfg_branch:-}" == "." ]]; then cfg_branch="$base_branch"; fi
74
+ if [[ -z "${cfg_branch:-}" ]]; then
75
+ echo "error: missing .gitmodules submodule.${sub_name}.branch (path=${sub_path})"
76
+ exit 2
77
+ fi
78
+ git -C "$sub_path" fetch origin --prune
79
+ git -C "$sub_path" checkout -B "aiws/pin/${cfg_branch}" HEAD
80
+ git -C "$sub_path" branch --set-upstream-to "origin/${cfg_branch}" "aiws/pin/${cfg_branch}" >/dev/null 2>&1 || true
70
81
  ```
71
- - 若用户明确不想建分支:停止,解释风险(提交可能不可追溯/难以推送)。
82
+ - 若 `origin/<target-branch>` 不存在,或用户明确不想使用 pin 分支:停止,解释风险(提交可能不可追溯/难以推送)。
72
83
  3) 选择性 staging(默认用 `-p` 更安全):
73
84
  ```bash
74
85
  git -C "$sub_path" add -p
@@ -115,8 +126,26 @@ else
115
126
  fi
116
127
  ```
117
128
 
129
+ ## D2) 生成持久证据并回填 Evidence_Path(强烈建议)
130
+ > 说明:`.agentdocs/tmp/...` 默认 gitignored;交付前建议把关键结果落到 `changes/<change-id>/evidence/...` 并回填 `proposal.md`/`plan` 的 `Evidence_Path`,避免后续评审/二次会话读不到证据。
131
+ ```bash
132
+ change_id="<change-id>"
133
+ if [[ -x "./node_modules/.bin/aiws" ]]; then
134
+ ./node_modules/.bin/aiws change evidence "${change_id}"
135
+ elif command -v aiws >/dev/null 2>&1; then
136
+ aiws change evidence "${change_id}"
137
+ else
138
+ npx @aipper/aiws change evidence "${change_id}"
139
+ fi
140
+ ```
141
+
142
+ ## D3) 生成状态快照(可选,建议)
143
+ ```bash
144
+ aiws change state "${change_id}" --write
145
+ ```
146
+
118
147
  ## E) 安全合并回目标分支(fast-forward)
119
- 优先使用 `$ws-finish`(底层调用 `aiws change finish`)。
148
+ 优先使用 `$ws-finish`(底层调用 `aiws change finish`,并在 push 成功后清理对应 change worktree)。
120
149
 
121
150
  若需要显式指定目标分支:
122
151
  ```bash
@@ -132,4 +161,5 @@ aiws change archive <change-id>
132
161
  - `Submodules:` 每个 submodule 的分支/提交摘要(repo → commit sha → message)
133
162
  - `Superproject:` 提交摘要
134
163
  - `Merge:` `aiws change finish` 的输出(into/from)
164
+ - `Worktree cleanup:` 若存在独立 change worktree,输出清理结果(removed/skipped + reason)
135
165
  - `Evidence:` `.agentdocs/tmp/aiws-validate/*.json`(若使用 --stamp)
@@ -14,6 +14,7 @@ description: 开发(按需求实现并验证;适用于任何需要修改代
14
14
  2) 建立变更归因(推荐):
15
15
  - 推荐一键:`aiws change start <change-id> --hooks`(切分支 + 初始化变更工件 + 启用 hooks)
16
16
  - superproject + submodule(推荐):`aiws change start <change-id> --hooks --worktree --submodules`(创建独立 worktree;当前目录分支保持不变)
17
+ - 若后续需要在 detached submodule 内提交:先挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`
17
18
  - 若你明确要在 superproject 直接切分支:`aiws change start <change-id> --hooks --switch`(仅在存在 `.gitmodules` 时有意义;会尝试让 submodules 工作区跟随 superproject 指针)
18
19
  - 或手工:`git switch -c change/<change-id>`,并创建 `changes/<change-id>/proposal.md` 与 `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
19
20
  3) 如涉及需求调整:先做需求评审(可用 `$ws-req-review`)→ 用户确认后再做需求落盘(可用 `$ws-req-change`)(避免需求漂移)。
@@ -33,6 +34,6 @@ fi
33
34
  8) 交付收尾(推荐,减少手动 merge 出错):运行 `$ws-finish`(底层调用 `aiws change finish`,默认 fast-forward 安全合并回目标分支)。
34
35
 
35
36
  输出要求:
36
- - `Changed:` 文件清单
37
- - `Verify:` 实际运行的命令 + 期望结果
38
- - `Evidence:` 证据路径(例如 `plan/...`、`.agentdocs/tmp/aiws-validate/...` 或 `changes/<change-id>/...`)
37
+ - `变更文件(Changed):` 文件清单
38
+ - `验证(Verify):` 实际运行的命令 + 期望结果
39
+ - `证据(Evidence):` 证据路径(例如 `changes/<change-id>/review/...`、`plan/...`、`.agentdocs/tmp/aiws-validate/...` 或 `changes/<change-id>/...`)
@@ -46,6 +46,21 @@ else
46
46
  npx @aipper/aiws validate . --stamp
47
47
  fi
48
48
  ```
49
+ 1.1) (强烈建议)收敛持久证据并回填 `Evidence_Path`:
50
+ ```bash
51
+ change_id="<change-id>"
52
+ if [[ -x "./node_modules/.bin/aiws" ]]; then
53
+ ./node_modules/.bin/aiws change evidence "${change_id}"
54
+ elif command -v aiws >/dev/null 2>&1; then
55
+ aiws change evidence "${change_id}"
56
+ else
57
+ npx @aipper/aiws change evidence "${change_id}"
58
+ fi
59
+ ```
60
+ 1.2) (可选)生成状态快照(建议):
61
+ ```bash
62
+ aiws change state "${change_id}" --write
63
+ ```
49
64
  2) 安全合并(默认 fast-forward;并会在需要时切到目标分支):
50
65
  ```bash
51
66
  # 若当前就在 change/<change-id> 分支上,可省略 <change-id>
@@ -71,6 +86,7 @@ git config --file .gitmodules --get-regexp '^submodule\..*\.path$' 2>/dev/null |
71
86
  # 说明:`git submodule update` 会把 submodule checkout 到固定 gitlink commit,导致 detached HEAD。
72
87
  # 为减少“游离状态”的协作摩擦,本步骤采用“pin 分支”策略:
73
88
  # - 仅在 `.gitmodules` 明确配置了 `submodule.<name>.branch` 时执行(避免 origin 多分支导致误判)
89
+ # - 不要直接切 `change/<change-id>` / `main` / `master` 等业务分支来“解 detached”
74
90
  # - 不改动 submodule 现有分支指针(例如不强行移动 main/master)
75
91
  # - 创建/更新本地 pin 分支:`aiws/pin/<target_branch>` 指向 gitlink commit,并将其 upstream 设为 `origin/<target_branch>`
76
92
  sub_sha="$(git rev-parse "HEAD:<sub_path>")"
@@ -112,7 +128,31 @@ git branch --show-current
112
128
  git status -sb
113
129
  git push
114
130
  ```
115
- 6) (可选)归档变更工件(完成交付后推荐):
131
+ 6) push 成功后,清理 `change/<change-id>` 对应 worktree(若存在且不是当前 worktree):
132
+ ```bash
133
+ 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
149
+ fi
150
+ ```
151
+ 规则:
152
+ - 清理前先把 `change_wt` 输出给用户确认,避免误删。
153
+ - 仅使用 `git worktree remove`(不带 `--force`)。
154
+
155
+ 7) (可选)归档变更工件(完成交付后推荐):
116
156
  ```bash
117
157
  change_id="<change-id>"
118
158
  if [[ -x "./node_modules/.bin/aiws" ]]; then
@@ -5,7 +5,7 @@ description: 评审(提交前审计与证据落盘)
5
5
 
6
6
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
7
7
 
8
- 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据落盘到 `.agentdocs/tmp/review/`。
8
+ 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据优先落盘到 `changes/<change-id>/review/`(若无法确定 `change-id` 再回退 `.agentdocs/tmp/review/`)。
9
9
 
10
10
  步骤(建议):
11
11
  1) 先做 preflight:定位项目根目录,读取 `AI_PROJECT.md` / `REQUIREMENTS.md` / `AI_WORKSPACE.md`,输出约束摘要。
@@ -13,11 +13,13 @@ description: 评审(提交前审计与证据落盘)
13
13
  - 是否存在越界目录改动/危险操作
14
14
  - 是否有可复现验证命令与证据
15
15
  - 是否维护了 `changes/<change-id>/` 或相关 `issues/*.csv`
16
- 3) 将审计落盘到:`.agentdocs/tmp/review/codex-review.md`(目录不存在则创建)。
16
+ 3) 将审计落盘到(目录不存在则创建):
17
+ - 默认:`changes/<change-id>/review/codex-review.md`
18
+ - 回退:`.agentdocs/tmp/review/codex-review.md`(仅在无法确定 `change-id` 时使用)
17
19
  4) 回复中输出:
18
- - `Evidence:` 证据文件路径
19
- - `Top risks:` 3–8 条(高→低)
20
- - `Next:` 最小修复清单 + 最小验证命令
20
+ - `证据(Evidence):` 证据文件路径
21
+ - `主要风险(Top risks):` 3–8 条(高→低)
22
+ - `下一步(Next):` 最小修复清单 + 最小验证命令
21
23
 
22
24
  安全:
23
25
  - 不打印 secrets。
@@ -17,7 +17,7 @@
17
17
  - 风险与回滚(3–8 条)
18
18
  - 最小验证命令(可复现)
19
19
  3) 将分析落盘到:`.agentdocs/tmp/analyze/claude-analysis.md`(目录不存在则创建)。
20
- 4) 回复中必须包含:`Evidence:` 证据文件路径。
20
+ 4) 回复中必须包含:`证据(Evidence):` 证据文件路径。
21
21
 
22
22
  安全:
23
23
  - 不打印 secrets(尤其 `secrets/test-accounts.json`)。
@@ -15,7 +15,7 @@
15
15
  - `zentao-bug-<bug-id>.md`
16
16
  - `images/<bug-id>/...`
17
17
  5) upsert `issues/fix_bus_issues.csv`(主键 `Bug_ID`)。
18
- 6) 进入 `/ws-dev` 修复并回填 `Fix_Status/Verify_Command/Updated_At`。
18
+ 6) 进入 `/ws-dev` 修复并回填状态字段 `Fix_Status/Verify_Command/Updated_At`。
19
19
  7) 质量门:`aiws change validate <change-id> --strict` + `aiws validate . --stamp`。
20
20
 
21
21
  强制约束:
@@ -14,7 +14,7 @@
14
14
 
15
15
  步骤(建议):
16
16
  1) 先运行 `/ws-preflight`。
17
- 2) 运行 `/ws-review`(落盘审计证据)。
17
+ 2) 运行 `/ws-review`(优先落盘审计证据到 `changes/<change-id>/review/`)。
18
18
  3) 运行门禁校验并写 stamp:
19
19
  ```bash
20
20
  if [[ -x "./node_modules/.bin/aiws" ]]; then
@@ -43,7 +43,8 @@ while read -r _ sub_path; do
43
43
  done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
44
44
  ```
45
45
  判定规则(强制):
46
- - 任一 submodule `git status --porcelain` 非空:停止提交 superproject;先在对应 submodule 完成 commit(必要时先切回可提交分支),再回到 superproject 更新并提交 gitlink。
46
+ - 任一 submodule `git status --porcelain` 非空:停止提交 superproject;先在对应 submodule 完成 commit,再回到 superproject 更新并提交 gitlink。
47
+ - 若该 submodule 当前为 detached HEAD:先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`。
47
48
  6) 检查 staging(必须输出给用户确认):
48
49
  ```bash
49
50
  git status --porcelain
@@ -57,9 +58,9 @@ git commit -m "<message>"
57
58
  ```
58
59
 
59
60
  输出必须包含:
60
- - `Evidence:` `.agentdocs/tmp/review/*` + `.agentdocs/tmp/aiws-validate/*`
61
- - `Context:` 当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
62
- - `Next:` 若存在 submodule 改动,先提示用户进入 submodule 提交
61
+ - `证据(Evidence):` `changes/<change-id>/review/*`(无 change-id 时回退 `.agentdocs/tmp/review/*`) + `.agentdocs/tmp/aiws-validate/*`
62
+ - `上下文(Context):` 当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
63
+ - `下一步(Next):` 若存在 submodule 改动,先提示用户进入 submodule 提交
63
64
  <!-- AIWS_MANAGED_END:claude:ws-commit -->
64
65
 
65
66
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
@@ -20,6 +20,7 @@
20
20
  - `git submodule status --recursive`
21
21
  3) 逐个提交 submodules(按上一步顺序):
22
22
  - `git -C "<sub_path>" status --porcelain`
23
+ - 若当前为 detached HEAD:不要直接切 `change/<change-id>` / `main` / `master`;先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target-branch>`
23
24
  - `git -C "<sub_path>" add -p`
24
25
  - `git -C "<sub_path>" diff --staged --stat`
25
26
  - 生成并让用户确认该 submodule 的 commit message(每个 repo 单独确认)
@@ -32,14 +33,19 @@
32
33
  - `git commit -m "<message>"`
33
34
  5) (推荐)门禁 + 证据:
34
35
  - `aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)
36
+ 5.1) (强烈建议)生成持久证据并回填 `Evidence_Path`:
37
+ - `aiws change evidence <change-id>`(未安装全局 aiws 时可用 `npx @aipper/aiws change evidence <change-id>`)
38
+ 5.2) (可选)生成状态快照(建议):
39
+ - `aiws change state <change-id> --write`
35
40
  6) 安全合并回目标分支:
36
- - 优先运行 `/ws-finish`(底层调用 `aiws change finish`,默认 `--ff-only`)
41
+ - 优先运行 `/ws-finish`(底层调用 `aiws change finish`,默认 `--ff-only`;push 成功后会清理对应 change worktree)
37
42
 
38
43
  输出要求:
39
- - `Submodules:` 每个 submodule 的 commit 摘要(repo/path → sha → message)
40
- - `Superproject:` commit 摘要
41
- - `Merge:` `/ws-finish` 输出(into/from)
42
- - `Evidence:` `.agentdocs/tmp/aiws-validate/*.json`(若使用 --stamp
44
+ - `子模块(Submodules):` 每个 submodule 的 commit 摘要(repo/path → sha → message)
45
+ - `主仓库(Superproject):` commit 摘要
46
+ - `合并信息(Merge):` `/ws-finish` 输出(into/from)
47
+ - `工作区清理(Worktree cleanup):` 若存在独立 change worktree,输出清理结果(removed/skipped + reason
48
+ - `证据(Evidence):` `.agentdocs/tmp/aiws-validate/*.json`(若使用 --stamp)
43
49
  <!-- AIWS_MANAGED_END:claude:ws-deliver -->
44
50
 
45
51
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
@@ -8,8 +8,10 @@
8
8
  建议流程:
9
9
  1) 先运行 `/ws-preflight`(读真值文件并输出约束摘要)。
10
10
  2) 建立变更归因(推荐):
11
- - 切分支:`git switch -c change/<change-id>`
12
- - 初始化变更工件:创建 `changes/<change-id>/proposal.md` `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
11
+ - 推荐一键:`aiws change start <change-id> --hooks`
12
+ - superproject + submodule(推荐):`aiws change start <change-id> --hooks --worktree --submodules`
13
+ - 若后续需要在 detached submodule 内提交:先挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`
14
+ - 或手工:`git switch -c change/<change-id>`,并创建 `changes/<change-id>/proposal.md` 与 `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
13
15
  3) 如涉及需求调整:先 `/ws-req-review` → 用户确认后再 `/ws-req-change`(避免需求漂移)。
14
16
  4) 实施最小改动:任何改动都要能归因到 `REQUIREMENTS.md`(验收)或 `issues/problem-issues.csv`(问题)。
15
17
  5) 运行 `AI_WORKSPACE.md` 里声明的验证命令;未运行不声称已运行。
@@ -17,9 +19,9 @@
17
19
  7) 交付收尾(推荐,减少手动 merge 出错):运行 `/ws-finish`(底层调用 `aiws change finish`,默认 fast-forward 安全合并回目标分支)。
18
20
 
19
21
  输出要求:
20
- - `Changed:` 文件清单
21
- - `Verify:` 实际运行的命令 + 期望结果
22
- - `Evidence:` 证据路径(例如 `.agentdocs/tmp/...` 或 `changes/<change-id>/...`)
22
+ - `变更文件(Changed):` 文件清单
23
+ - `验证(Verify):` 实际运行的命令 + 期望结果
24
+ - `证据(Evidence):` 证据路径(例如 `changes/<change-id>/review/...`、`changes/<change-id>/...` 或 `.agentdocs/tmp/...`)
23
25
  <!-- AIWS_MANAGED_END:claude:ws-dev -->
24
26
 
25
27
  可在下方追加本项目对 Claude Code 的额外说明(托管块外内容会被保留)。
@@ -32,6 +32,8 @@ fi
32
32
  ```
33
33
  1) 先运行 `/ws-preflight`(确保真值文件齐全)。
34
34
  2) (推荐)门禁校验并落盘证据:`aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)。
35
+ 2.1) (强烈建议)收敛持久证据并回填 `Evidence_Path`:`aiws change evidence <change-id>`(未安装全局 aiws 时可用 `npx @aipper/aiws change evidence <change-id>`)。
36
+ 2.2) (可选)生成状态快照(建议):`aiws change state <change-id> --write`。
35
37
  3) 安全合并并切回目标分支:
36
38
  - 若当前就在 `change/<change-id>` 分支上,可直接执行:`aiws change finish`
37
39
  - 否则执行:`aiws change finish <change-id>`
@@ -41,6 +43,7 @@ fi
41
43
  - 对每个 `<sub_path>`:
42
44
  - 读取 superproject 当前 gitlink:`git rev-parse "HEAD:<sub_path>"`
43
45
  - 目标分支:必须在 `.gitmodules` 配置 `submodule.<name>.branch`(若为 `.` 则用当前主仓库分支;避免 origin 多分支时误判)
46
+ - 不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”
44
47
  - 用 pin 分支挂回(不改动现有 main/master 指针):`git -C "<sub_path>" checkout -B "aiws/pin/<target-branch>" <gitlink-sha>`
45
48
  - 仅当 `<gitlink-sha>` 属于 `origin/<target-branch>` 历史时才允许 push;否则停止并人工处理分叉
46
49
  - push(只允许 fast-forward):`git -C "<sub_path>" push origin "<gitlink-sha>:refs/heads/<target-branch>"`
@@ -49,7 +52,13 @@ fi
49
52
  - `git branch --show-current`
50
53
  - `git status -sb`
51
54
  - `git push`
52
- 8) (可选)交付完成后归档变更工件:`aiws change archive <change-id>`。
55
+ 8) push 成功后,清理 `change/<change-id>` 对应 worktree(若存在且不是当前 worktree):
56
+ - `change_ref="refs/heads/change/<change-id>"`
57
+ - `main_wt="$(git rev-parse --show-toplevel)"`
58
+ - `change_wt="$(git worktree list --porcelain | awk -v ref="$change_ref" '$1=="worktree"{wt=substr($0,10)} $1=="branch"&&$2==ref{print wt; exit}')"`
59
+ - 若 `change_wt` 非空且不等于 `main_wt`:先输出并让用户确认,再执行 `git worktree remove "$change_wt"`(不带 `--force`),最后 `git worktree prune`
60
+ - 若 `git -C "$change_wt" status --porcelain` 非空:停止并提示先清理该 worktree
61
+ 9) (可选)交付完成后归档变更工件:`aiws change archive <change-id>`。
53
62
 
54
63
  安全:
55
64
  - push 前先输出状态并请用户确认远端/分支。
@@ -9,8 +9,8 @@
9
9
  执行建议:
10
10
  1) 先运行 `/ws-preflight`(对齐 `AI_PROJECT.md` / `REQUIREMENTS.md` / `AI_WORKSPACE.md`)。
11
11
  2) 生成或更新计划文件:`plan/YYYY-MM-DD_HH-MM-SS-<slug>.md`。
12
- 3) 计划至少包含:`Bindings`、`Goal`、`Non-goals`、`Scope`、`Plan`、`Verify`、`Risks & Rollback`、`Evidence`。
13
- 4) 若已有 `changes/<change-id>/proposal.md`,对齐 `Plan_File` / `Contract_Row` / `Evidence_Path`。
12
+ 3) 计划至少包含:`绑定信息(Bindings)`、`目标(Goal)`、`非目标(Non-goals)`、`范围(Scope)`、`执行计划(Plan)`、`验证(Verify)`、`风险与回滚(Risks & Rollback)`、`证据(Evidence)`。
13
+ 4) 若已有 `changes/<change-id>/proposal.md`,对齐 `计划文件(Plan_File)` / `合同行(Contract_Row)` / `证据路径(Evidence_Path)`。
14
14
  5) 完成后先运行 `/ws-plan-verify`,通过再进入 `/ws-dev`。
15
15
  <!-- AIWS_MANAGED_END:claude:ws-plan -->
16
16
 
@@ -14,10 +14,10 @@
14
14
  - `REQUIREMENTS.md`
15
15
  - `AI_WORKSPACE.md`
16
16
  3) 输出:
17
- - `Root:` <项目根路径>
18
- - `Found:` <实际读取到的文件列表>
19
- - `Missing:` <缺失文件列表>
20
- - `Key rules:` 3–8 条 bullet(范围/禁止项/必须产物/必须验证命令)
17
+ - `根目录(Root):` <项目根路径>
18
+ - `已读取(Found):` <实际读取到的文件列表>
19
+ - `缺失(Missing):` <缺失文件列表>
20
+ - `关键规则(Key rules):` 3–8 条 bullet(范围/禁止项/必须产物/必须验证命令)
21
21
 
22
22
  安全:
23
23
  - 不打印 secrets;遇到疑似敏感值只提示“存在风险”但不要复述原文。
@@ -15,11 +15,11 @@
15
15
  - `requirements/requirements-issues.csv`(若存在)
16
16
  3) 输出固定结构的报告:
17
17
 
18
- ## Requirements QA
18
+ ## 需求 QA(Requirements QA
19
19
  - 结论:是否可进入实现(是/否/有条件)
20
20
  - 漂移风险:最容易导致不一致的点
21
21
  - 可验收性缺口:缺少输入/输出/错误码/边界/示例的条目
22
- - 完整性:Non-goals/兼容性/鉴权/重试/并发/观测性/性能
22
+ - 完整性:非目标(Non-goals)/兼容性/鉴权/重试/并发/观测性/性能
23
23
  - 一致性:与 `AI_PROJECT.md` 约束冲突点
24
24
  - 可测试性与证据:最小验证命令 + 期望结果 + 证据路径
25
25
  - 风险清单:3–8 条
@@ -3,7 +3,7 @@
3
3
 
4
4
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
5
5
 
6
- 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据落盘到 `.agentdocs/tmp/review/`。
6
+ 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据优先落盘到 `changes/<change-id>/review/`(若无法确定 `change-id` 再回退 `.agentdocs/tmp/review/`)。
7
7
 
8
8
  步骤(建议):
9
9
  1) 先运行 `/ws-preflight`。
@@ -11,11 +11,13 @@
11
11
  - 是否存在越界目录改动/危险操作
12
12
  - 是否有可复现验证命令与证据
13
13
  - 是否维护了 `changes/<change-id>/` 或相关 `issues/*.csv`
14
- 3) 将审计落盘到:`.agentdocs/tmp/review/claude-review.md`(目录不存在则创建)。
14
+ 3) 将审计落盘到(目录不存在则创建):
15
+ - 默认:`changes/<change-id>/review/claude-review.md`
16
+ - 回退:`.agentdocs/tmp/review/claude-review.md`(仅在无法确定 `change-id` 时使用)
15
17
  4) 回复中输出:
16
- - `Evidence:` 证据文件路径
17
- - `Top risks:` 3–8 条(高→低)
18
- - `Next:` 最小修复清单 + 最小验证命令
18
+ - `证据(Evidence):` 证据文件路径
19
+ - `主要风险(Top risks):` 3–8 条(高→低)
20
+ - `下一步(Next):` 最小修复清单 + 最小验证命令
19
21
 
20
22
  安全:
21
23
  - 不打印 secrets。
@@ -13,17 +13,20 @@ argument-hint: ""
13
13
  建议流程:
14
14
  1) 先运行 `/ws-preflight`(读真值文件并输出约束摘要)。
15
15
  2) 建立变更归因(推荐):
16
- - 切分支:`git switch -c change/<change-id>`
17
- - 初始化变更工件:创建 `changes/<change-id>/proposal.md` `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
16
+ - 推荐一键:`aiws change start <change-id> --hooks`
17
+ - superproject + submodule(推荐):`aiws change start <change-id> --hooks --worktree --submodules`
18
+ - 若后续需要在 detached submodule 内提交:先挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`
19
+ - 或手工:`git switch -c change/<change-id>`,并创建 `changes/<change-id>/proposal.md` 与 `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
18
20
  3) 如涉及需求调整:先 `/ws-req-review` → 用户确认后再 `/ws-req-change`(避免需求漂移)。
19
21
  4) 实施最小改动:任何改动都要能归因到 `REQUIREMENTS.md`(验收)或 `issues/problem-issues.csv`(问题)。
20
22
  5) 运行 `AI_WORKSPACE.md` 里声明的验证命令;未运行不声称已运行。
21
23
  6) 提交前强制:`aiws validate .`(commit/push hooks 也会阻断)。
24
+ 7) 交付收尾(推荐,减少手动 merge 出错):运行 `/ws-finish`(底层调用 `aiws change finish`,默认 fast-forward 安全合并回目标分支)。
22
25
 
23
26
  输出要求:
24
- - `Changed:` 文件清单
25
- - `Verify:` 实际运行的命令 + 期望结果
26
- - `Evidence:` 证据路径(例如 `.agentdocs/tmp/...` 或 `changes/<change-id>/...`)
27
+ - `变更文件(Changed):` 文件清单
28
+ - `验证(Verify):` 实际运行的命令 + 期望结果
29
+ - `证据(Evidence):` 证据路径(例如 `changes/<change-id>/review/...`、`changes/<change-id>/...` 或 `.agentdocs/tmp/...`)
27
30
  <!-- AIWS_MANAGED_END:codex:ws-dev -->
28
31
 
29
32
  可在下方追加本项目对 Codex 的额外说明(托管块外内容会被保留)。
@@ -8,7 +8,7 @@ argument-hint: ""
8
8
 
9
9
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
10
 
11
- 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据落盘到 `.agentdocs/tmp/review/`。
11
+ 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据优先落盘到 `changes/<change-id>/review/`(若无法确定 `change-id` 再回退 `.agentdocs/tmp/review/`)。
12
12
 
13
13
  步骤(建议):
14
14
  1) 先运行 `/ws-preflight`。
@@ -16,11 +16,13 @@ argument-hint: ""
16
16
  - 是否存在越界目录改动/危险操作
17
17
  - 是否有可复现验证命令与证据
18
18
  - 是否维护了 `changes/<change-id>/` 或相关 `issues/*.csv`
19
- 3) 将审计落盘到:`.agentdocs/tmp/review/codex-review.md`(目录不存在则创建)。
19
+ 3) 将审计落盘到(目录不存在则创建):
20
+ - 默认:`changes/<change-id>/review/codex-review.md`
21
+ - 回退:`.agentdocs/tmp/review/codex-review.md`(仅在无法确定 `change-id` 时使用)
20
22
  4) 回复中输出:
21
- - `Evidence:` 证据文件路径
22
- - `Top risks:` 3–8 条(高→低)
23
- - `Next:` 最小修复清单 + 最小验证命令
23
+ - `证据(Evidence):` 证据文件路径
24
+ - `主要风险(Top risks):` 3–8 条(高→低)
25
+ - `下一步(Next):` 最小修复清单 + 最小验证命令
24
26
 
25
27
  安全:
26
28
  - 不打印 secrets。
@@ -20,7 +20,7 @@ prompt = """
20
20
  强制步骤:
21
21
  1) 读取真值文件:`AI_PROJECT.md`、`REQUIREMENTS.md`、`AI_WORKSPACE.md`(缺失则先运行 `/aiws-init` 或 `aiws init .`)。
22
22
  2) 若 iFlow 拦截写入:先执行 `/ws-contract-check`,再继续。
23
- 3) 先运行 `/ws-review`(落盘审计证据到 `.agentdocs/tmp/review/`)。
23
+ 3) 先运行 `/ws-review`(优先落盘审计证据到 `changes/<change-id>/review/`)。
24
24
  4) 运行门禁并写 stamp:`aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)。
25
25
  5) 输出提交上下文(必须输出给用户确认):
26
26
  - `git branch --show-current`
@@ -30,7 +30,8 @@ prompt = """
30
30
  - 对每个 `<sub_path>` 输出:
31
31
  - `git -C "<sub_path>" rev-parse --abbrev-ref HEAD`
32
32
  - `git -C "<sub_path>" status --porcelain`
33
- - 判定规则:任一 submodule `git status --porcelain` 非空 → **停止提交 superproject**,先在该 submodule 完成 commit(必要时切回可提交分支),再回到 superproject 更新并提交 gitlink。
33
+ - 判定规则:任一 submodule `git status --porcelain` 非空 → **停止提交 superproject**,先在该 submodule 完成 commit,再回到 superproject 更新并提交 gitlink。
34
+ - 若该 submodule 当前为 detached HEAD:先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`。
34
35
  7) 检查 staging(必须输出给用户确认):
35
36
  - `git status --porcelain`
36
37
  - `git diff --staged --submodule=short`
@@ -39,6 +40,6 @@ prompt = """
39
40
  10) 执行提交(不带 `--no-verify`):`git commit -m "<message>"`。
40
41
 
41
42
  输出必须包含:
42
- - Evidence:`.agentdocs/tmp/review/*` + `.agentdocs/tmp/aiws-validate/*`
43
- - Context:当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
43
+ - 证据(Evidence):`changes/<change-id>/review/*`(无 change-id 时回退 `.agentdocs/tmp/review/*`) + `.agentdocs/tmp/aiws-validate/*`
44
+ - 上下文(Context):当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
44
45
  """
@@ -25,6 +25,7 @@ prompt = """
25
25
  - `git submodule status --recursive`
26
26
  3) 逐个提交 submodules(按上一步顺序):
27
27
  - `git -C "<sub_path>" status --porcelain`
28
+ - 若当前为 detached HEAD:不要直接切 `change/<change-id>` / `main` / `master`;先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target-branch>`
28
29
  - `git -C "<sub_path>" add -p`
29
30
  - `git -C "<sub_path>" diff --staged --stat`
30
31
  - 生成并让用户确认该 submodule 的 commit message(每个 repo 单独确认)
@@ -37,12 +38,17 @@ prompt = """
37
38
  - `git commit -m "<message>"`
38
39
  5) (推荐)门禁 + 证据:
39
40
  - `aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)
41
+ 5.1) (强烈建议)生成持久证据并回填 `Evidence_Path`:
42
+ - `aiws change evidence <change-id>`(未安装全局 aiws 时可用 `npx @aipper/aiws change evidence <change-id>`)
43
+ 5.2) (可选)生成状态快照(建议):
44
+ - `aiws change state <change-id> --write`
40
45
  6) 安全合并回目标分支:
41
- - 优先运行 `ws:finish`(底层调用 `aiws change finish`,默认 `--ff-only`)
46
+ - 优先运行 `ws:finish`(底层调用 `aiws change finish`,默认 `--ff-only`;push 成功后会清理对应 change worktree)
42
47
 
43
48
  输出要求:
44
49
  - `Submodules:` 每个 submodule 的 commit 摘要(repo/path → sha → message)
45
50
  - `Superproject:` commit 摘要
46
51
  - `Merge:` `ws:finish`/`aiws change finish` 输出(into/from)
52
+ - `Worktree cleanup:` 若存在独立 change worktree,输出清理结果(removed/skipped + reason)
47
53
  - `Evidence:` `.agentdocs/tmp/aiws-validate/*.json`(若使用 --stamp)
48
54
  """
@@ -20,6 +20,8 @@ prompt = """
20
20
  4) 若工作区不干净:停止,并要求先 commit 或 stash(不要尝试自动处理)。
21
21
  4.1) 若存在 `.gitmodules`:必须为每个 submodule 配置 `submodule.<name>.branch`(否则先运行 `ws:submodule-setup` 并提交 `.gitmodules`)。
22
22
  5) (推荐)门禁校验并落盘证据:`aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)。
23
+ 5.1) (强烈建议)收敛持久证据并回填 `Evidence_Path`:`aiws change evidence <change-id>`(未安装全局 aiws 时可用 `npx @aipper/aiws change evidence <change-id>`)。
24
+ 5.2) (可选)生成状态快照(建议):`aiws change state <change-id> --write`。
23
25
  6) 安全合并(默认 fast-forward):
24
26
  - 在 `change/<change-id>` 分支上:`aiws change finish`(会尝试读 `changes/<change-id>/.ws-change.json` 的 `base_branch` 并切回目标分支)
25
27
  - 否则:`aiws change finish <change-id>`
@@ -29,6 +31,7 @@ prompt = """
29
31
  - 对每个 `<sub_path>`:
30
32
  - 读取 superproject 当前 gitlink:`git rev-parse "HEAD:<sub_path>"`
31
33
  - 目标分支:必须在 `.gitmodules` 配置 `submodule.<name>.branch`(若为 `.` 则用当前主仓库分支;避免 origin 多分支时误判)
34
+ - 不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”
32
35
  - 用 pin 分支挂回(不改动现有 main/master 指针):`git -C "<sub_path>" checkout -B "aiws/pin/<target-branch>" <gitlink-sha>`
33
36
  - 仅当 `<gitlink-sha>` 属于 `origin/<target-branch>` 历史时才允许 push;否则停止并人工处理分叉
34
37
  - push(只允许 fast-forward):`git -C "<sub_path>" push origin "<gitlink-sha>:refs/heads/<target-branch>"`
@@ -37,7 +40,13 @@ prompt = """
37
40
  - `git branch --show-current`
38
41
  - `git status -sb`
39
42
  - `git push`
40
- 11) (可选)交付完成后归档变更工件:`aiws change archive <change-id>`。
43
+ 11) push 成功后,清理 `change/<change-id>` 对应 worktree(若存在且不是当前 worktree):
44
+ - `change_ref="refs/heads/change/<change-id>"`
45
+ - `main_wt="$(git rev-parse --show-toplevel)"`
46
+ - `change_wt="$(git worktree list --porcelain | awk -v ref="$change_ref" '$1=="worktree"{wt=substr($0,10)} $1=="branch"&&$2==ref{print wt; exit}')"`
47
+ - 若 `change_wt` 非空且不等于 `main_wt`:先输出并让用户确认,再执行 `git worktree remove "$change_wt"`(不带 `--force`),最后 `git worktree prune`
48
+ - 若 `git -C "$change_wt" status --porcelain` 非空:停止并提示先清理该 worktree
49
+ 12) (可选)交付完成后归档变更工件:`aiws change archive <change-id>`。
41
50
 
42
51
  边界:
43
52
  - push 前先输出状态并请用户确认远端/分支。
@@ -1,14 +1,14 @@
1
1
  # Command: ws:review
2
- # Description: AI Workspace 交叉审计交付(Claude + Codex + Gemini),证据落盘到 .agentdocs/tmp/review/
2
+ # Description: AI Workspace 交叉审计交付(Claude + Codex + Gemini),证据优先落盘到 changes/<change-id>/review/
3
3
  # Category: workspace
4
4
  # Version: 1
5
5
 
6
- description = "AI Workspace 交叉审计交付(Claude + Codex + Gemini),证据落盘到 .agentdocs/tmp/review/"
6
+ description = "AI Workspace 交叉审计交付(Claude + Codex + Gemini),证据优先落盘到 changes/<change-id>/review/"
7
7
 
8
8
  prompt = """
9
9
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
10
10
 
11
- 目标:在实现/提交前,对当前工作区改动进行交叉审计,并把审计结论落盘到 `.agentdocs/tmp/review/`,便于回放与追责。
11
+ 目标:在实现/提交前,对当前工作区改动进行交叉审计,并把审计结论优先落盘到 `changes/<change-id>/review/`(若无法确定 `change-id` 再回退 `.agentdocs/tmp/review/`),便于回放与追责。
12
12
 
13
13
  强制步骤:
14
14
  1) 读取真值文件:`AI_PROJECT.md`、`REQUIREMENTS.md`、`AI_WORKSPACE.md`。
@@ -22,7 +22,8 @@ prompt = """
22
22
  - Risks:3–8 条
23
23
  - Next:最小修复清单 + 最小验证命令
24
24
  5) 证据落盘(必须):
25
- - `.agentdocs/tmp/review/` 写入一份 `review.<timestamp>.md`
25
+ - 默认:在 `changes/<change-id>/review/` 写入一份 `iflow-review.<timestamp>.md`
26
+ - 回退:在 `.agentdocs/tmp/review/` 写入同名文件(仅在无法确定 `change-id` 时使用)
26
27
  6) 如果目标是提交前门禁:
27
28
  - 建议运行:`aiws validate .`(或 `npx @aipper/aiws validate .`)
28
29
 
@@ -17,7 +17,7 @@ description: Commit:门禁/审计后提交(submodule 感知)
17
17
 
18
18
  步骤(建议):
19
19
  1) 先运行 `/ws-preflight`。
20
- 2) 运行 `/ws-review`(落盘审计证据)。
20
+ 2) 运行 `/ws-review`(优先落盘审计证据到 `changes/<change-id>/review/`)。
21
21
  3) 运行门禁校验并写 stamp:
22
22
  ```bash
23
23
  if [[ -x "./node_modules/.bin/aiws" ]]; then
@@ -46,7 +46,8 @@ while read -r _ sub_path; do
46
46
  done < <(git config --file .gitmodules --get-regexp '^submodule\\..*\\.path$' 2>/dev/null || true)
47
47
  ```
48
48
  判定规则(强制):
49
- - 任一 submodule `git status --porcelain` 非空:停止提交 superproject;先在对应 submodule 完成 commit(必要时先切回可提交分支),再回到 superproject 更新并提交 gitlink。
49
+ - 任一 submodule `git status --porcelain` 非空:停止提交 superproject;先在对应 submodule 完成 commit,再回到 superproject 更新并提交 gitlink。
50
+ - 若该 submodule 当前为 detached HEAD:先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`。
50
51
  6) 检查 staging(必须输出给用户确认):
51
52
  ```bash
52
53
  git status --porcelain
@@ -60,9 +61,9 @@ git commit -m "<message>"
60
61
  ```
61
62
 
62
63
  输出必须包含:
63
- - `Evidence:` `.agentdocs/tmp/review/*` + `.agentdocs/tmp/aiws-validate/*`
64
- - `Context:` 当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
65
- - `Next:` 若存在 submodule 改动,先提示用户进入 submodule 提交
64
+ - `证据(Evidence):` `changes/<change-id>/review/*`(无 change-id 时回退 `.agentdocs/tmp/review/*`) + `.agentdocs/tmp/aiws-validate/*`
65
+ - `上下文(Context):` 当前分支 + 是否检测到 submodule + 若阻断则给出阻断原因
66
+ - `下一步(Next):` 若存在 submodule 改动,先提示用户进入 submodule 提交
66
67
  <!-- AIWS_MANAGED_END:opencode:ws-commit -->
67
68
 
68
69
  可在下方追加本项目对 OpenCode 的额外说明(托管块外内容会被保留)。
@@ -23,6 +23,7 @@ description: 交付:submodules+superproject 分步提交并安全合并回 bas
23
23
  - `git submodule status --recursive`
24
24
  3) 逐个提交 submodules(按上一步顺序):
25
25
  - `git -C "<sub_path>" status --porcelain`
26
+ - 若当前为 detached HEAD:不要直接切 `change/<change-id>` / `main` / `master`;先按 `.gitmodules` 的目标分支挂到 `aiws/pin/<target-branch>`
26
27
  - `git -C "<sub_path>" add -p`
27
28
  - `git -C "<sub_path>" diff --staged --stat`
28
29
  - 生成并让用户确认该 submodule 的 commit message(每个 repo 单独确认)
@@ -35,13 +36,18 @@ description: 交付:submodules+superproject 分步提交并安全合并回 bas
35
36
  - `git commit -m "<message>"`
36
37
  5) (推荐)门禁 + 证据:
37
38
  - `aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)
39
+ 5.1) (强烈建议)生成持久证据并回填 `Evidence_Path`:
40
+ - `aiws change evidence <change-id>`(未安装全局 aiws 时可用 `npx @aipper/aiws change evidence <change-id>`)
41
+ 5.2) (可选)生成状态快照(建议):
42
+ - `aiws change state <change-id> --write`
38
43
  6) 安全合并回目标分支:
39
- - 优先运行 `/ws-finish`(底层调用 `aiws change finish`,默认 `--ff-only`)
44
+ - 优先运行 `/ws-finish`(底层调用 `aiws change finish`,默认 `--ff-only`;push 成功后会清理对应 change worktree)
40
45
 
41
46
  输出要求:
42
47
  - `Submodules:` 每个 submodule 的 commit 摘要(repo/path → sha → message)
43
48
  - `Superproject:` commit 摘要
44
49
  - `Merge:` `/ws-finish` 输出(into/from)
50
+ - `Worktree cleanup:` 若存在独立 change worktree,输出清理结果(removed/skipped + reason)
45
51
  - `Evidence:` `.agentdocs/tmp/aiws-validate/*.json`(若使用 --stamp)
46
52
  <!-- AIWS_MANAGED_END:opencode:ws-deliver -->
47
53
 
@@ -11,8 +11,10 @@ description: 开发:在 AIWS 约束下完成小步交付
11
11
  建议流程:
12
12
  1) 先运行 `/ws-preflight`(读真值文件并输出约束摘要)。
13
13
  2) 建立变更归因(推荐):
14
- - 切分支:`git switch -c change/<change-id>`
15
- - 初始化变更工件:创建 `changes/<change-id>/proposal.md` `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
14
+ - 推荐一键:`aiws change start <change-id> --hooks`
15
+ - superproject + submodule(推荐):`aiws change start <change-id> --hooks --worktree --submodules`
16
+ - 若后续需要在 detached submodule 内提交:先挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master`
17
+ - 或手工:`git switch -c change/<change-id>`,并创建 `changes/<change-id>/proposal.md` 与 `changes/<change-id>/tasks.md`(参考 `changes/README.md`)
16
18
  3) 如涉及需求调整:先 `/ws-req-review` → 用户确认后再 `/ws-req-change`(避免需求漂移)。
17
19
  4) 实施最小改动:任何改动都要能归因到 `REQUIREMENTS.md`(验收)或 `issues/problem-issues.csv`(问题)。
18
20
  5) 运行 `AI_WORKSPACE.md` 里声明的验证命令;未运行不声称已运行。
@@ -20,9 +22,9 @@ description: 开发:在 AIWS 约束下完成小步交付
20
22
  7) 交付收尾(推荐,减少手动 merge 出错):运行 `/ws-finish`(底层调用 `aiws change finish`,默认 fast-forward 安全合并回目标分支)。
21
23
 
22
24
  输出要求:
23
- - `Changed:` 文件清单
24
- - `Verify:` 实际运行的命令 + 期望结果
25
- - `Evidence:` 证据路径(例如 `.agentdocs/tmp/...` 或 `changes/<change-id>/...`)
25
+ - `变更文件(Changed):` 文件清单
26
+ - `验证(Verify):` 实际运行的命令 + 期望结果
27
+ - `证据(Evidence):` 证据路径(例如 `changes/<change-id>/review/...`、`changes/<change-id>/...` 或 `.agentdocs/tmp/...`)
26
28
  <!-- AIWS_MANAGED_END:opencode:ws-dev -->
27
29
 
28
30
  可在下方追加本项目对 OpenCode 的额外说明(托管块外内容会被保留)。
@@ -35,6 +35,8 @@ fi
35
35
  ```
36
36
  1) 先运行 `/ws-preflight`(确保真值文件齐全)。
37
37
  2) (推荐)门禁校验并落盘证据:`aiws validate . --stamp`(未安装全局 aiws 时可用 `npx @aipper/aiws validate . --stamp`)。
38
+ 2.1) (强烈建议)收敛持久证据并回填 `Evidence_Path`:`aiws change evidence <change-id>`(未安装全局 aiws 时可用 `npx @aipper/aiws change evidence <change-id>`)。
39
+ 2.2) (可选)生成状态快照(建议):`aiws change state <change-id> --write`。
38
40
  3) 安全合并并切回目标分支:
39
41
  - 若当前就在 `change/<change-id>` 分支上,可直接执行:`aiws change finish`
40
42
  - 否则执行:`aiws change finish <change-id>`
@@ -44,6 +46,7 @@ fi
44
46
  - 对每个 `<sub_path>`:
45
47
  - 读取 superproject 当前 gitlink:`git rev-parse "HEAD:<sub_path>"`
46
48
  - 目标分支:必须在 `.gitmodules` 配置 `submodule.<name>.branch`(若为 `.` 则用当前主仓库分支;避免 origin 多分支时误判)
49
+ - 不要直接切 `change/<change-id>` / `main` / `master` 来“解 detached”
47
50
  - 用 pin 分支挂回(不改动现有 main/master 指针):`git -C "<sub_path>" checkout -B "aiws/pin/<target-branch>" <gitlink-sha>`
48
51
  - 仅当 `<gitlink-sha>` 属于 `origin/<target-branch>` 历史时才允许 push;否则停止并人工处理分叉
49
52
  - push(只允许 fast-forward):`git -C "<sub_path>" push origin "<gitlink-sha>:refs/heads/<target-branch>"`
@@ -52,7 +55,13 @@ fi
52
55
  - `git branch --show-current`
53
56
  - `git status -sb`
54
57
  - `git push`
55
- 8) (可选)交付完成后归档变更工件:`aiws change archive <change-id>`。
58
+ 8) push 成功后,清理 `change/<change-id>` 对应 worktree(若存在且不是当前 worktree):
59
+ - `change_ref="refs/heads/change/<change-id>"`
60
+ - `main_wt="$(git rev-parse --show-toplevel)"`
61
+ - `change_wt="$(git worktree list --porcelain | awk -v ref="$change_ref" '$1=="worktree"{wt=substr($0,10)} $1=="branch"&&$2==ref{print wt; exit}')"`
62
+ - 若 `change_wt` 非空且不等于 `main_wt`:先输出并让用户确认,再执行 `git worktree remove "$change_wt"`(不带 `--force`),最后 `git worktree prune`
63
+ - 若 `git -C "$change_wt" status --porcelain` 非空:停止并提示先清理该 worktree
64
+ 9) (可选)交付完成后归档变更工件:`aiws change archive <change-id>`。
56
65
 
57
66
  安全:
58
67
  - push 前先输出状态并请用户确认远端/分支。
@@ -6,7 +6,7 @@ description: Review:提交前审计改动并落盘证据
6
6
 
7
7
  用中文输出(命令/路径/代码标识符保持原样不翻译)。
8
8
 
9
- 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据落盘到 `.agentdocs/tmp/review/`。
9
+ 目标:在提交/交付前审计当前改动,对照真值文件检查是否越界,并把审计证据优先落盘到 `changes/<change-id>/review/`(若无法确定 `change-id` 再回退 `.agentdocs/tmp/review/`)。
10
10
 
11
11
  步骤(建议):
12
12
  1) 先运行 `/ws-preflight`。
@@ -14,11 +14,13 @@ description: Review:提交前审计改动并落盘证据
14
14
  - 是否存在越界目录改动/危险操作
15
15
  - 是否有可复现验证命令与证据
16
16
  - 是否维护了 `changes/<change-id>/` 或相关 `issues/*.csv`
17
- 3) 将审计落盘到:`.agentdocs/tmp/review/opencode-review.md`(目录不存在则创建)。
17
+ 3) 将审计落盘到(目录不存在则创建):
18
+ - 默认:`changes/<change-id>/review/opencode-review.md`
19
+ - 回退:`.agentdocs/tmp/review/opencode-review.md`(仅在无法确定 `change-id` 时使用)
18
20
  4) 回复中输出:
19
- - `Evidence:` 证据文件路径
20
- - `Top risks:` 3–8 条(高→低)
21
- - `Next:` 最小修复清单 + 最小验证命令
21
+ - `证据(Evidence):` 证据文件路径
22
+ - `主要风险(Top risks):` 3–8 条(高→低)
23
+ - `下一步(Next):` 最小修复清单 + 最小验证命令
22
24
 
23
25
  安全:
24
26
  - 不打印 secrets。
@@ -19,7 +19,7 @@
19
19
  - `$ws-bugfix`:缺陷修复(禅道 MCP 拉单 + 图片证据落盘 + `issues/fix_bus_issues.csv` 汇总)
20
20
  - `$ws-pull`:拉取并对齐 submodules(尽量避免 detached;减少人为差异)
21
21
  - `$ws-push`:推送(submodule 感知:先 submodules 后 superproject;fast-forward 安全)
22
- - `$ws-review`:评审(提交前审计与证据落盘)
22
+ - `$ws-review`:评审(提交前审计;证据优先落盘到 `changes/<change-id>/review/`)
23
23
  - `$ws-commit`:提交(先审计/门禁再 commit;submodule 感知)
24
24
  - `$aiws-init`:初始化工作区(生成真值文件与门禁)
25
25
  - `$aiws-validate`:校验工作区(漂移检测 + 门禁)
@@ -38,6 +38,8 @@
38
38
  推荐(防规则/范围漂移):
39
39
  - 创建工件:补齐 `changes/<change-id>/proposal.md`、`tasks.md`(可选 `design.md`)
40
40
  - 声明 active change(团队共享):切到分支 `change/<change-id>`(也支持 `changes/`、`ws/`、`ws-change/`)
41
+ - 若仓库存在 `.gitmodules`:优先使用 `aiws change start <change-id> --worktree`(或至少 `--no-switch`);不要在当前 superproject worktree 里直接手工切分支。
42
+ - 若 submodule 因 gitlink checkout 处于 detached HEAD:只允许挂到 `aiws/pin/<target-branch>`;不要直接切 `change/<change-id>` / `main` / `master` 等业务分支来“解 detached”。
41
43
  - 严格校验:`aiws validate .`(包含:漂移检测 + `ws_change_check` + `requirements_contract`)
42
44
  - 启用 hooks(本地生效):`git config core.hooksPath .githooks`(提交/推送时自动跑 `aiws validate .`)
43
45
  - CI 建议追加:`aiws validate .`
@@ -19,7 +19,17 @@
19
19
  - 问题修复:`Problem_ID` = <!-- WS:TODO (问题修复可填;例如 PROB-001) -->
20
20
  - `Contract_Row` = <!-- WS:TODO 绑定执行合同中的行 ID,可多项(逗号分隔);例如 Req_ID=TOOLING-001B -->
21
21
  - `Plan_File` = <!-- WS:TODO 例如 plan/2026-02-08_15-30-00-xxx.md -->
22
- - `Evidence_Path` = <!-- WS:TODO 证据路径,可多项(逗号分隔);例如 .agentdocs/tmp/review/codex-review.md -->
22
+ - `Evidence_Path` = <!-- WS:TODO 证据路径,可多项(逗号分隔);优先写持久证据 changes/<change-id>/evidence/...;也可附带 changes/<change-id>/review/... 或 .agentdocs/tmp/... -->
23
+
24
+ ## 依赖关系(可选)
25
+
26
+ - `Depends_On` = <!-- WS:TODO 本 change 依赖的前置 change ID(逗号分隔);例如 feature-auth -->
27
+ - `Blocks` = <!-- WS:TODO 本 change 完成后才能开始的后续 change ID(逗号分隔);例如 feature-dashboard -->
28
+
29
+ > 规则:
30
+ > - `Depends_On` 的 change 应已完成(archived);未完成时 `aiws change start` 会输出警告。
31
+ > - `Blocks` 用于记录依赖关系,便于后续 change 读取交接文档。
32
+ > - 依赖字段为可选,不强制填写。
23
33
 
24
34
  > 规则:
25
35
  > - `Req_ID` 与 `Problem_ID` 至少填写一项。
@@ -37,10 +47,28 @@
37
47
 
38
48
  ## 影响范围(Scope)
39
49
 
40
- - 影响的服务/模块/目录:
41
- - <!-- WS:TODO -->
50
+ ### In Scope(本次改动范围)
51
+
52
+ > 列出允许修改的文件/目录(支持 glob 模式);用于 `aiws change validate --check-scope` 检查越界改动。
53
+
54
+ - <!-- WS:TODO 例如:`packages/cli/src/commands/change.ts` - 修改 change 命令逻辑 -->
55
+ - <!-- WS:TODO 例如:`packages/cli/tests/**/*.test.ts` - 相关测试文件 -->
56
+
57
+ ### Out of Scope(明确不改动)
58
+
59
+ > 列出明确不在本次改动范围内的模块/目录;防止"顺手重构"。
60
+
61
+ - <!-- WS:TODO 例如:`packages/spec/` - 不修改规范定义 -->
62
+ - <!-- WS:TODO 例如:配置文件 - 不修改 .aiws/config.yaml -->
63
+
64
+ ### 外部影响
65
+
42
66
  - 可能影响的外部接口/使用方:
43
- - <!-- WS:TODO -->
67
+ - <!-- WS:TODO 例如:CLI 命令行接口、API 端点、配置格式等 -->
68
+
69
+ > 规则:
70
+ > - 实际改动超出 In Scope 时,需在 delivery 时解释原因并更新本章节。
71
+ > - 可使用 `aiws change validate --check-scope` 检查越界文件。
44
72
 
45
73
  ## 风险与回滚
46
74
 
@@ -64,4 +92,6 @@
64
92
  - `requirements/CHANGELOG.md`:<!-- WS:TODO 需要/不需要 -->
65
93
  - `requirements/requirements-issues.csv`:<!-- WS:TODO 需要/不需要 -->
66
94
  - `issues/problem-issues.csv`:<!-- WS:TODO 需要/不需要 -->
67
- - 证据落盘(`.agentdocs/tmp/...`):<!-- WS:TODO 计划输出哪些报告/日志 -->
95
+ - 证据落盘(推荐双层):
96
+ - 持久(建议入库):`changes/<change-id>/evidence/...`(例如 validate 总结、review 总结、关键日志摘要)
97
+ - 临时(可忽略入库):`.agentdocs/tmp/...`(例如 `aiws validate . --stamp` 的 JSON)
@@ -325,6 +325,8 @@ def validate_change(
325
325
  change_id: str,
326
326
  strict: bool,
327
327
  allow_truth_drift: bool,
328
+ check_evidence: bool,
329
+ check_scope: bool,
328
330
  ) -> int:
329
331
  change_dir = root / "changes" / change_id
330
332
  required_files = ["proposal.md", "tasks.md"]
@@ -389,6 +391,185 @@ def validate_change(
389
391
  if "WS:TODO" in text:
390
392
  (errors if strict else warnings).append(f"WS:TODO markers remain in {rel}")
391
393
 
394
+ def evidence_is_persistent(p: str) -> bool:
395
+ p2 = p.replace("\\", "/")
396
+ return p2.startswith(f"changes/{change_id}/evidence/") or p2.startswith(f"changes/{change_id}/review/")
397
+
398
+ def parse_scope_patterns_from_plan(plan_text: str) -> List[str]:
399
+ # Extract bullet list items under "## Scope" (until next heading).
400
+ lines = (plan_text or "").splitlines()
401
+ patterns: List[str] = []
402
+ in_scope = False
403
+ for raw in lines:
404
+ line = raw.rstrip("\n")
405
+ if line.startswith("## "):
406
+ in_scope = line.strip() in ("## Scope", "## 影响范围(Scope)", "## 影响范围 (Scope)")
407
+ continue
408
+ if not in_scope:
409
+ continue
410
+ if line.startswith("## "):
411
+ break
412
+ s = line.strip()
413
+ if not s:
414
+ continue
415
+ if s.startswith("- "):
416
+ v = s[2:].strip()
417
+ if v.startswith("`") and v.endswith("`") and len(v) >= 2:
418
+ v = v[1:-1].strip()
419
+ if v.upper() == "TBD":
420
+ continue
421
+ # Normalize common "path (note)" into "path"
422
+ v = v.split("(", 1)[0].split("(", 1)[0].strip()
423
+ if v:
424
+ patterns.append(v)
425
+ return patterns
426
+
427
+ def normalize_scope_pattern(pat: str) -> str:
428
+ p = (pat or "").strip().replace("\\", "/")
429
+ if p.startswith("./"):
430
+ p = p[2:]
431
+ # Treat trailing slash as directory prefix match.
432
+ if p.endswith("/") and not p.endswith("/**"):
433
+ p = p + "**"
434
+ return p
435
+
436
+ def match_scope_pattern(relpath: str, pat: str) -> bool:
437
+ import fnmatch
438
+
439
+ rp = (relpath or "").replace("\\", "/")
440
+ p = normalize_scope_pattern(pat)
441
+ if not p:
442
+ return False
443
+ # If the pattern contains glob syntax, fnmatch it.
444
+ if any(ch in p for ch in ["*", "?", "[", "]"]):
445
+ return fnmatch.fnmatch(rp, p)
446
+ # Otherwise treat as prefix (file or directory).
447
+ if rp == p:
448
+ return True
449
+ return rp.startswith(p.rstrip("/") + "/")
450
+
451
+ def check_declared_paths(rel: str, decl: str) -> None:
452
+ paths = split_declared_values(decl)
453
+ if not paths:
454
+ return
455
+ has_persistent = any(evidence_is_persistent(p) for p in paths)
456
+ if not has_persistent:
457
+ (errors if strict else warnings).append(
458
+ f"{rel} Evidence_Path should include at least one persistent path under changes/<id>/evidence or changes/<id>/review"
459
+ )
460
+ for raw in paths:
461
+ p = raw.strip()
462
+ if not p:
463
+ continue
464
+ if p.startswith("./"):
465
+ p = p[2:]
466
+ if p.startswith("~") or os.path.isabs(p):
467
+ errors.append(f"{rel} Evidence_Path must be workspace-relative, got absolute path: {raw}")
468
+ continue
469
+ if ".." in Path(p).parts:
470
+ errors.append(f"{rel} Evidence_Path must not contain '..': {raw}")
471
+ continue
472
+ abs_p = (root / p).resolve()
473
+ try:
474
+ abs_p.relative_to(root)
475
+ except Exception:
476
+ errors.append(f"{rel} Evidence_Path points outside workspace: {raw}")
477
+ continue
478
+ if not abs_p.exists():
479
+ errors.append(f"{rel} Evidence_Path missing file: {raw}")
480
+
481
+ def scope_check_from_plan(plan_rel: str, plan_text: str) -> None:
482
+ if not check_scope:
483
+ return
484
+ patterns = [normalize_scope_pattern(p) for p in parse_scope_patterns_from_plan(plan_text)]
485
+ patterns = [p for p in patterns if p]
486
+ if not patterns:
487
+ (errors if strict else warnings).append(f"{plan_rel} scope check enabled but no patterns found under '## Scope'")
488
+ return
489
+
490
+ meta_path = change_dir / ".ws-change.json"
491
+ base_branch = ""
492
+ if meta_path.exists():
493
+ try:
494
+ meta = json.loads(read_text(meta_path))
495
+ base_branch = str((meta or {}).get("base_branch") or "").strip()
496
+ except Exception:
497
+ base_branch = ""
498
+ if not base_branch:
499
+ (errors if strict else warnings).append(
500
+ "scope check requires base_branch recorded in changes/<id>/.ws-change.json (run `aiws change start <id>` to record it)"
501
+ )
502
+ return
503
+
504
+ # Compute changed files vs merge-base with base_branch (include staged + unstaged).
505
+ try:
506
+ mb = subprocess.run(
507
+ ["git", "-C", str(root), "merge-base", base_branch, "HEAD"],
508
+ check=False,
509
+ stdout=subprocess.PIPE,
510
+ stderr=subprocess.PIPE,
511
+ text=True,
512
+ )
513
+ except Exception as e:
514
+ (errors if strict else warnings).append(f"scope check failed to run git merge-base: {e}")
515
+ return
516
+ if mb.returncode != 0:
517
+ (errors if strict else warnings).append(f"scope check failed to compute merge-base vs {base_branch}: {mb.stderr.strip() or mb.stdout.strip()}")
518
+ return
519
+ base = (mb.stdout or "").strip()
520
+ if not base:
521
+ (errors if strict else warnings).append(f"scope check failed to compute merge-base vs {base_branch}: empty output")
522
+ return
523
+
524
+ def diff_names(args: List[str]) -> List[str]:
525
+ try:
526
+ res = subprocess.run(
527
+ ["git", "-C", str(root), "diff", "--name-only", "--diff-filter=ACMR", *args],
528
+ check=False,
529
+ stdout=subprocess.PIPE,
530
+ stderr=subprocess.PIPE,
531
+ text=True,
532
+ )
533
+ except Exception:
534
+ return []
535
+ if res.returncode != 0:
536
+ return []
537
+ return [ln.strip() for ln in (res.stdout or "").splitlines() if ln.strip()]
538
+
539
+ # Include untracked files as "changed" too (new files are common in dev/deliver stages).
540
+ untracked: List[str] = []
541
+ try:
542
+ ls = subprocess.run(
543
+ ["git", "-C", str(root), "ls-files", "--others", "--exclude-standard"],
544
+ check=False,
545
+ stdout=subprocess.PIPE,
546
+ stderr=subprocess.PIPE,
547
+ text=True,
548
+ )
549
+ if ls.returncode == 0:
550
+ untracked = [ln.strip() for ln in (ls.stdout or "").splitlines() if ln.strip()]
551
+ except Exception:
552
+ untracked = []
553
+
554
+ changed = sorted(set(diff_names([base]) + diff_names(["--cached", base]) + untracked))
555
+ if not changed:
556
+ return
557
+
558
+ # Always allow core workflow artifacts.
559
+ always_allow = [
560
+ f"changes/{change_id}/**",
561
+ "plan/**",
562
+ "REQUIREMENTS.md",
563
+ "requirements/requirements-issues.csv",
564
+ "issues/problem-issues.csv",
565
+ ]
566
+ all_patterns = patterns + always_allow
567
+
568
+ out_of_scope = [p for p in changed if not any(match_scope_pattern(p, pat) for pat in all_patterns)]
569
+ if out_of_scope:
570
+ msg = "out-of-scope files detected (update plan Scope or explain): " + ", ".join(out_of_scope[:20]) + (" ..." if len(out_of_scope) > 20 else "")
571
+ (errors if strict else warnings).append(f"{plan_rel} {msg}")
572
+
392
573
  req_id = ""
393
574
  prob_id = ""
394
575
  change_id_decl = ""
@@ -425,6 +606,8 @@ def validate_change(
425
606
  errors.append("proposal.md must include non-empty Plan_File")
426
607
  if strict and not evidence_path_decl:
427
608
  errors.append("proposal.md must include non-empty Evidence_Path")
609
+ if check_evidence and evidence_path_decl:
610
+ check_declared_paths("proposal.md", evidence_path_decl)
428
611
 
429
612
  if req_id and req_csv.exists():
430
613
  ok = False
@@ -532,6 +715,9 @@ def validate_change(
532
715
  errors.append(f"{plan_rel} must include non-empty Plan_File")
533
716
  if strict and not plan_evidence_path:
534
717
  errors.append(f"{plan_rel} must include non-empty Evidence_Path")
718
+ if check_evidence and plan_evidence_path:
719
+ check_declared_paths(plan_rel, plan_evidence_path)
720
+ scope_check_from_plan(plan_rel, plan_text)
535
721
 
536
722
  if plan_file_from_plan:
537
723
  plan_file_refs = split_declared_values(plan_file_from_plan)
@@ -602,6 +788,16 @@ def main(argv: Optional[List[str]] = None) -> int:
602
788
  action="store_true",
603
789
  help="Do not fail strict validation on truth drift (use only for emergencies).",
604
790
  )
791
+ parser.add_argument(
792
+ "--check-evidence",
793
+ action="store_true",
794
+ help="Validate Evidence_Path points to workspace-relative existing files; require at least one persistent evidence path.",
795
+ )
796
+ parser.add_argument(
797
+ "--check-scope",
798
+ action="store_true",
799
+ help="Scope gate: compare git diff vs base_branch with plan '## Scope' patterns and warn/error on out-of-scope files.",
800
+ )
605
801
  parser.add_argument(
606
802
  "--allow-branches",
607
803
  default="main,master",
@@ -663,6 +859,8 @@ def main(argv: Optional[List[str]] = None) -> int:
663
859
  change_id=change_id,
664
860
  strict=args.strict,
665
861
  allow_truth_drift=args.allow_truth_drift,
862
+ check_evidence=args.check_evidence,
863
+ check_scope=args.check_scope,
666
864
  )
667
865
 
668
866