xwang 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (55) hide show
  1. package/assets/skills/xwang/SKILL.md +112 -0
  2. package/assets/skills/xwang/scripts/xwang-env.sh +48 -0
  3. package/assets/skills/xwang/scripts/xwang-guard.sh +530 -0
  4. package/assets/skills/xwang/scripts/xwang-state.sh +374 -0
  5. package/assets/skills/xwang-archive/SKILL.md +86 -0
  6. package/assets/skills/xwang-build/REFERENCE.md +94 -0
  7. package/assets/skills/xwang-build/SKILL.md +113 -0
  8. package/assets/skills/xwang-design/SKILL.md +104 -0
  9. package/assets/skills/xwang-hotfix/SKILL.md +102 -0
  10. package/assets/skills/xwang-open/SKILL.md +108 -0
  11. package/assets/skills/xwang-open/scripts/xwang-open-check.sh +197 -0
  12. package/assets/skills/xwang-tweak/SKILL.md +99 -0
  13. package/assets/skills/xwang-verify/SKILL.md +112 -0
  14. package/bin/xwang.js +3 -0
  15. package/dist/cli/index.d.ts +2 -0
  16. package/dist/cli/index.d.ts.map +1 -0
  17. package/dist/cli/index.js +30 -0
  18. package/dist/cli/index.js.map +1 -0
  19. package/dist/commands/init.d.ts +9 -0
  20. package/dist/commands/init.d.ts.map +1 -0
  21. package/dist/commands/init.js +238 -0
  22. package/dist/commands/init.js.map +1 -0
  23. package/dist/core/detect.d.ts +6 -0
  24. package/dist/core/detect.d.ts.map +1 -0
  25. package/dist/core/detect.js +74 -0
  26. package/dist/core/detect.js.map +1 -0
  27. package/dist/core/mattpocock.d.ts +6 -0
  28. package/dist/core/mattpocock.d.ts.map +1 -0
  29. package/dist/core/mattpocock.js +82 -0
  30. package/dist/core/mattpocock.js.map +1 -0
  31. package/dist/core/openspec.d.ts +3 -0
  32. package/dist/core/openspec.d.ts.map +1 -0
  33. package/dist/core/openspec.js +178 -0
  34. package/dist/core/openspec.js.map +1 -0
  35. package/dist/core/package-manager.d.ts +8 -0
  36. package/dist/core/package-manager.d.ts.map +1 -0
  37. package/dist/core/package-manager.js +62 -0
  38. package/dist/core/package-manager.js.map +1 -0
  39. package/dist/core/platforms.d.ts +22 -0
  40. package/dist/core/platforms.d.ts.map +1 -0
  41. package/dist/core/platforms.js +23 -0
  42. package/dist/core/platforms.js.map +1 -0
  43. package/dist/core/superpowers.d.ts +5 -0
  44. package/dist/core/superpowers.d.ts.map +1 -0
  45. package/dist/core/superpowers.js +75 -0
  46. package/dist/core/superpowers.js.map +1 -0
  47. package/dist/core/types.d.ts +2 -0
  48. package/dist/core/types.d.ts.map +1 -0
  49. package/dist/core/types.js +2 -0
  50. package/dist/core/types.js.map +1 -0
  51. package/dist/utils/file-system.d.ts +5 -0
  52. package/dist/utils/file-system.d.ts.map +1 -0
  53. package/dist/utils/file-system.js +41 -0
  54. package/dist/utils/file-system.js.map +1 -0
  55. package/package.json +50 -0
@@ -0,0 +1,112 @@
1
+ ---
2
+ name: xwang
3
+ description: xwang 项目主入口 skill,自动检测当前所处阶段并分发到 open、design、build、verify、archive 子流程;也支持快速路径 xwang-hotfix 与 xwang-tweak。当用户输入 /xwang 或提到启动 xwang 工作流时使用。
4
+ ---
5
+
6
+ # xwang
7
+
8
+ xwang 工作流包含 5 个阶段:open → design → build → verify → archive。另有快速路径:
9
+ - `xwang-hotfix`:bug 修复,open → build → verify → archive,跳过 design
10
+ - `xwang-tweak`:轻量小改动,open → lightweight build → light verify → archive,跳过 design
11
+
12
+ ## 快速开始
13
+
14
+ 当用户输入 `/xwang` 或想要启动 xwang 工作流时:
15
+
16
+ 1. **Preset 意图检测**:优先判断用户输入是否命中 `xwang-hotfix` 或 `xwang-tweak`;命中时直接调用对应快速路径,不再走普通阶段检测。
17
+ 2. 执行阶段检测(检查未完成任务与现有 change 状态)。
18
+ 3. 根据检测结果分发到对应子流程:
19
+ - 有未完成任务 → 进入 `xwang-open`
20
+ - 无未完成任务且无现有 change → **直接进入 `xwang-design`,无需用户确认**
21
+ - 有 design 产物但未完成 → 进入 `xwang-design` 继续
22
+ - design 已完成 → 进入 `xwang-build`
23
+ - build 已完成(`phase: verify`) → 进入 `xwang-verify`
24
+ - verify 已完成(`phase: archive`) → 进入 `xwang-archive` 执行归档(需用户确认)
25
+ 4. 如果无法明确判断,默认进入 `xwang-open` 阶段。
26
+
27
+ ## 阶段判定(待补充)
28
+
29
+ 按以下优先级判断当前阶段:
30
+
31
+ 1. **open 阶段 / Preset 入口**:项目存在未完成任务,或用户明确表示要先检查状态。`xwang-open` 自身也会先做 Preset 意图检测,命中 hotfix / tweak 时直接分发到对应快速路径。
32
+ 2. **design 阶段**:
33
+ - 没有未完成任务,且存在已创建、未完成的 OpenSpec / xwang design 产物;或
34
+ - 没有未完成任务,也没有现有 change(`start-fresh`),直接为当前需求新建 change 并进入 design。
35
+ 3. **build 阶段**:没有未完成任务,design 产物已就绪,且当前 phase 为 `build`,进入实施构建。
36
+ 4. **verify 阶段**:build 完成后,phase 为 `verify`,进入验证阶段。
37
+ 5. **archive 阶段**:verify 完成后,phase 为 `archive`,进入 `xwang-archive` 执行归档(需用户确认)。
38
+
39
+ 具体检测信号和规则后续补充。
40
+
41
+ ## 子流程分发
42
+
43
+ | 阶段 | 调用 |
44
+ |------|------|
45
+ | open | `xwang-open`(内部可分发到 `xwang-hotfix` / `xwang-tweak`) |
46
+ | design | `xwang-design` |
47
+ | build | `xwang-build` / `xwang-hotfix` / `xwang-tweak` |
48
+ | verify | `xwang-verify` |
49
+ | archive | `xwang-archive` |
50
+
51
+ 快速路径:
52
+ - `xwang-hotfix`:bug 修复,跳过 design,build 使用 direct 模式
53
+ - `xwang-tweak`:轻量小改动,跳过 design,使用轻量 build 与轻量 verify
54
+
55
+ 分发时向用户简要说明检测结论,然后调用对应 skill。
56
+
57
+ ## 报告格式
58
+
59
+ 返回简洁的状态块:
60
+
61
+ ```
62
+ xwang 阶段检测: <open | design | build | verify | archive>
63
+
64
+ 判定依据:
65
+ - <依据项>: <简要描述>
66
+
67
+ 分发动作:
68
+ - 调用 <xwang-open | xwang-design | xwang-build | xwang-verify | xwang-archive>
69
+
70
+ 说明:
71
+ - 若阶段无法明确判断,默认进入 open 阶段。
72
+ - 若当前没有未完成任务且没有现有 change,直接进入 design 阶段,无需用户确认。
73
+ - 若用户输入命中 hotfix / tweak preset,直接调用对应快速路径。
74
+ ```
75
+
76
+ ## 脚本
77
+
78
+ - `xwang-env.sh`(位于 `xwang/scripts/`):脚本定位器。source 后会动态设置 `XWANG_STATE`、`XWANG_OPEN_CHECK` 与 `XWANG_GUARD` 环境变量,避免在 skill 指令中硬编码 `.claude/skills/...` 路径。示例:
79
+
80
+ ```bash
81
+ XWANG_ENV="${XWANG_ENV:-$(find . "$HOME"/.*/skills "$HOME/.config" -path '*/xwang/scripts/xwang-env.sh' -type f -print -quit 2>/dev/null)}"
82
+ if [ -z "$XWANG_ENV" ]; then
83
+ echo "ERROR: xwang-env.sh not found. Ensure the xwang skill is installed." >&2
84
+ return 1
85
+ fi
86
+ . "$XWANG_ENV"
87
+ # 后续使用 "$XWANG_STATE" 调用状态脚本,使用 "$XWANG_OPEN_CHECK" 调用检测脚本,使用 "$XWANG_GUARD" 调用阶段守护脚本
88
+ ```
89
+
90
+ - `xwang-open-check.sh`(位于 `xwang-open/scripts/`):未完成任务检测脚本。扫描活跃 xwang change、Git dirty worktree、TODO/FIXME/XXX 注释,输出 `READY` 或 `HAS-UNFINISHED-WORK` 报告。支持 `--text`(默认)和 `--json` 两种输出格式。
91
+
92
+ - `xwang-guard.sh`(位于 `xwang/scripts/`):阶段出口守护者。在修改 `.xwang.yaml` 的 `phase` 前运行,校验当前阶段所有退出条件是否满足;支持 `--apply` 自动调用 `xwang-state.sh` 推进到下一阶段。用法示例:
93
+
94
+ ```bash
95
+ "$XWANG_GUARD" <change-name> design --apply # design → build
96
+ "$XWANG_GUARD" <change-name> build --apply # build → verify
97
+ "$XWANG_GUARD" <change-name> verify --apply # verify → archive
98
+ "$XWANG_GUARD" <change-name> archive # 仅检查归档完整性
99
+ ```
100
+
101
+ - `xwang-state.sh`(位于 `xwang/scripts/`):xwang 变更状态管理脚本。
102
+ - `init <change-name> <workflow>`:在 `openspec/changes/<change-name>/.xwang.yaml` 初始化状态文件。`full` 起始 phase 为 `design`;`quick`/`tweak` 跳过 design,起始 phase 为 `build`,并默认设置 `build_mode: direct`、`tdd_mode: direct`、`isolation: branch`。
103
+ - `get <change-name> <field>`:读取 `.xwang.yaml` 中指定字段的值。
104
+ - `set <change-name> <field> <value>`:更新 `.xwang.yaml` 中指定字段,并自动更新 `updated_at`。支持字段包括 `workflow`、`phase`(design / build / verify / archive)、`design_doc`、`plan`、`base_ref`、`isolation`、`build_mode`、`build_pause`、`tdd_mode`、`subagent_dispatch`、`direct_override`、`verify_mode`、`verify_result`、`verification_report`、`branch_status`、`archived`、`created_at`、`updated_at`。
105
+ - 后续可扩展 `transition` 等子命令。
106
+
107
+ ## 说明
108
+
109
+ - 当前本 skill 仅负责阶段判定和分发,不直接执行具体任务。
110
+ - 阶段判定逻辑将在 xwang 流程模型完善后补充。
111
+ - 子流程分别由 `xwang-open`、`xwang-design`、`xwang-build`、`xwang-verify`、`xwang-archive` skill 实现。
112
+ - 状态管理脚本 `xwang-state.sh` 由本 skill 维护,后续按需扩展。
@@ -0,0 +1,48 @@
1
+ #!/bin/bash
2
+ # xwang-env — locator for xwang management scripts
3
+ # Usage: source this file to set XWANG_STATE and XWANG_OPEN_CHECK
4
+
5
+ set -euo pipefail
6
+
7
+ # Resolve the directory of this script. Support both bash (BASH_SOURCE)
8
+ # and zsh/other shells where $0 points to the sourced file.
9
+ SCRIPT_SOURCE="${BASH_SOURCE[0]:-$0}"
10
+ XWANG_SCRIPT_DIR="$(cd "$(dirname "$SCRIPT_SOURCE")" && pwd)"
11
+
12
+ export XWANG_STATE="${XWANG_SCRIPT_DIR}/xwang-state.sh"
13
+
14
+ if [ ! -f "$XWANG_STATE" ]; then
15
+ echo "ERROR: xwang-state.sh not found at $XWANG_STATE" >&2
16
+ return 1
17
+ fi
18
+
19
+ # Locate xwang-open-check.sh dynamically without hardcoding a .claude path.
20
+ # Prefer the sibling relative location, then fall back to a platform-agnostic search.
21
+ if [ -z "${XWANG_OPEN_CHECK:-}" ]; then
22
+ XWANG_OPEN_CHECK_SIBLING="${XWANG_SCRIPT_DIR}/../xwang-open/scripts/xwang-open-check.sh"
23
+ if [ -f "$XWANG_OPEN_CHECK_SIBLING" ]; then
24
+ XWANG_OPEN_CHECK="$XWANG_OPEN_CHECK_SIBLING"
25
+ else
26
+ XWANG_OPEN_CHECK="$(find "$XWANG_SCRIPT_DIR" "$XWANG_SCRIPT_DIR/.." "$HOME" -path '*/xwang-open/scripts/xwang-open-check.sh' -type f -print -quit 2>/dev/null || true)"
27
+ fi
28
+ fi
29
+ export XWANG_OPEN_CHECK
30
+
31
+ # Locate xwang-guard.sh using the same approach.
32
+ if [ -z "${XWANG_GUARD:-}" ]; then
33
+ XWANG_GUARD_SIBLING="${XWANG_SCRIPT_DIR}/xwang-guard.sh"
34
+ if [ -f "$XWANG_GUARD_SIBLING" ]; then
35
+ XWANG_GUARD="$XWANG_GUARD_SIBLING"
36
+ else
37
+ XWANG_GUARD="$(find "$XWANG_SCRIPT_DIR" "$XWANG_SCRIPT_DIR/.." "$HOME" -path '*/xwang/scripts/xwang-guard.sh' -type f -print -quit 2>/dev/null || true)"
38
+ fi
39
+ fi
40
+ export XWANG_GUARD
41
+
42
+ if [ -z "$XWANG_OPEN_CHECK" ] || [ ! -f "$XWANG_OPEN_CHECK" ]; then
43
+ echo "WARNING: xwang-open-check.sh not found" >&2
44
+ fi
45
+
46
+ if [ -z "$XWANG_GUARD" ] || [ ! -f "$XWANG_GUARD" ]; then
47
+ echo "WARNING: xwang-guard.sh not found" >&2
48
+ fi
@@ -0,0 +1,530 @@
1
+ #!/bin/bash
2
+ # xwang Phase Guard — validates exit conditions before phase transitions
3
+ # Usage: xwang-guard.sh <change-name> <phase> [--apply]
4
+ # Phases: design, build, verify, archive (open is not recorded in .xwang.yaml)
5
+ # Exit 0 = all checks pass, exit 1 = blocked (reasons printed to stderr)
6
+ # shellcheck disable=SC2329 # Functions called indirectly via check() dispatch
7
+
8
+ set -euo pipefail
9
+
10
+ XWANG_BASH="${XWANG_BASH:-${BASH:-bash}}"
11
+
12
+ red() { echo -e "\033[31m$1\033[0m" >&2; }
13
+ green() { echo -e "\033[32m$1\033[0m" >&2; }
14
+ warn() { echo -e "\033[33m$1\033[0m" >&2; }
15
+
16
+ # --- Input validation ---
17
+
18
+ validate_change_name() {
19
+ local name="$1"
20
+ if [ -z "$name" ]; then
21
+ red "ERROR: Change name cannot be empty" >&2
22
+ exit 1
23
+ fi
24
+ if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
25
+ red "ERROR: Invalid change name: '$name'" >&2
26
+ red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
27
+ exit 1
28
+ fi
29
+ if [[ "$name" =~ \.\. ]]; then
30
+ red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
31
+ exit 1
32
+ fi
33
+ }
34
+
35
+ if [ "${XWANG_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
36
+ CHANGE="${CHANGE:-}"
37
+ PHASE="${PHASE:-}"
38
+ APPLY="${APPLY:-0}"
39
+ SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
40
+ CHANGE_DIR="${CHANGE_DIR:-}"
41
+ else
42
+ validate_change_name "$1"
43
+
44
+ CHANGE="$1"
45
+ PHASE="$2"
46
+ APPLY=0
47
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd -P)"
48
+ if [[ "${3:-}" == "--apply" ]]; then
49
+ APPLY=1
50
+ fi
51
+ CHANGE_DIR="openspec/changes/$CHANGE"
52
+ if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
53
+ CHANGE_DIR="openspec/changes/archive/$CHANGE"
54
+ fi
55
+ fi
56
+
57
+ BLOCK=0
58
+ check() {
59
+ local desc="$1"
60
+ shift
61
+ local output
62
+ if output=$("$@" 2>&1); then
63
+ green " [PASS] $desc"
64
+ else
65
+ red " [FAIL] $desc"
66
+ if [ -n "$output" ]; then
67
+ while IFS= read -r line; do
68
+ red " $line"
69
+ done <<< "$output"
70
+ fi
71
+ BLOCK=1
72
+ fi
73
+ }
74
+
75
+ # --- Helper functions ---
76
+
77
+ yaml_field_value() {
78
+ local field="$1"
79
+ local yaml="$CHANGE_DIR/.xwang.yaml"
80
+ if [ -f "$yaml" ]; then
81
+ local value
82
+ value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true)
83
+ value=$(strip_inline_comment "$value")
84
+ strip_wrapping_quotes "$value"
85
+ fi
86
+ }
87
+
88
+ strip_inline_comment() {
89
+ local value="$1"
90
+ printf '%s\n' "$value" | awk -v squote="'" '
91
+ {
92
+ out = ""
93
+ quote = ""
94
+ for (i = 1; i <= length($0); i++) {
95
+ c = substr($0, i, 1)
96
+ if (quote == "") {
97
+ if (c == "\"" || c == squote) {
98
+ quote = c
99
+ } else if (c == "#" && (i == 1 || substr($0, i - 1, 1) ~ /[[:space:]]/)) {
100
+ sub(/[[:space:]]+$/, "", out)
101
+ print out
102
+ next
103
+ }
104
+ } else if (c == quote) {
105
+ quote = ""
106
+ }
107
+ out = out c
108
+ }
109
+ print out
110
+ }
111
+ '
112
+ }
113
+
114
+ strip_wrapping_quotes() {
115
+ local value="$1"
116
+ case "$value" in
117
+ \"*\")
118
+ printf '%s\n' "${value:1:${#value}-2}"
119
+ ;;
120
+ \'*\')
121
+ printf '%s\n' "${value:1:${#value}-2}"
122
+ ;;
123
+ *)
124
+ printf '%s\n' "$value"
125
+ ;;
126
+ esac
127
+ }
128
+
129
+ project_config_value() {
130
+ local field="$1"
131
+ local value
132
+
133
+ value=$(yaml_field_value "$field" 2>/dev/null || true)
134
+ if [ -n "$value" ] && [ "$value" != "null" ]; then
135
+ echo "$value"
136
+ return 0
137
+ fi
138
+
139
+ for config in ".xwang.yaml" "xwang.yaml" ".xwang.yml" "xwang.yml"; do
140
+ if [ -f "$config" ]; then
141
+ value=$(grep "^${field}:" "$config" 2>/dev/null | sed "s/^${field}: *//" || true)
142
+ value=$(strip_inline_comment "$value")
143
+ value=$(strip_wrapping_quotes "$value")
144
+ if [ -n "$value" ] && [ "$value" != "null" ]; then
145
+ echo "$value"
146
+ return 0
147
+ fi
148
+ fi
149
+ done
150
+ }
151
+
152
+ file_nonempty() {
153
+ [ -f "$1" ] && [ -s "$1" ]
154
+ }
155
+
156
+ tasks_has_any() {
157
+ local tasks="$CHANGE_DIR/tasks.md"
158
+ [ -f "$tasks" ] && grep -q '\- \[' "$tasks"
159
+ }
160
+
161
+ tasks_all_done() {
162
+ local tasks="$CHANGE_DIR/tasks.md"
163
+ if [ ! -f "$tasks" ]; then
164
+ echo "tasks.md is missing at $tasks" >&2
165
+ echo "Next: restore or create tasks.md for this change before leaving build." >&2
166
+ return 1
167
+ fi
168
+ if ! grep -q '\- \[x\]' "$tasks"; then
169
+ echo "tasks.md has no completed tasks." >&2
170
+ echo "Next: complete implementation tasks and mark them with '- [x]'." >&2
171
+ return 1
172
+ fi
173
+ if grep -q '\- \[ \]' "$tasks"; then
174
+ echo "Unfinished tasks:" >&2
175
+ grep -n '\- \[ \]' "$tasks" >&2 || true
176
+ echo "Next: complete or explicitly remove unfinished tasks, then mark tasks.md with '- [x]'." >&2
177
+ return 1
178
+ fi
179
+ return 0
180
+ }
181
+
182
+ plan_tasks_all_done() {
183
+ local plan
184
+ plan=$(yaml_field_value "plan" 2>/dev/null || true)
185
+
186
+ if [ -z "$plan" ] || [ "$plan" = "null" ]; then
187
+ return 0
188
+ fi
189
+ if [ ! -f "$plan" ]; then
190
+ echo "plan file is missing at $plan" >&2
191
+ echo "Next: restore the plan file or update .xwang.yaml plan before leaving build." >&2
192
+ return 1
193
+ fi
194
+ if grep -q '^[[:space:]]*- \[ \]' "$plan"; then
195
+ echo "Unfinished plan tasks:" >&2
196
+ grep -n '^[[:space:]]*- \[ \]' "$plan" >&2 || true
197
+ echo "Next: check off corresponding completed plan tasks, then commit the plan update." >&2
198
+ return 1
199
+ fi
200
+ return 0
201
+ }
202
+
203
+ isolation_selected() {
204
+ local isolation
205
+ isolation=$(yaml_field_value "isolation" 2>/dev/null || true)
206
+ case "$isolation" in
207
+ branch|worktree) return 0 ;;
208
+ *)
209
+ echo "isolation must be branch or worktree, got '${isolation:-null}'" >&2
210
+ echo "Next: ask the user to choose branch or worktree, create the chosen isolation, then run:" >&2
211
+ echo " \"\$XWANG_BASH\" \"\$XWANG_STATE\" set $CHANGE isolation <branch|worktree>" >&2
212
+ return 1
213
+ ;;
214
+ esac
215
+ }
216
+
217
+ build_mode_selected() {
218
+ local build_mode
219
+ build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
220
+ case "$build_mode" in
221
+ subagent-driven-development|executing-plans|direct) return 0 ;;
222
+ *)
223
+ echo "build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2
224
+ echo "Next: ask the user to choose an execution mode, then run:" >&2
225
+ echo " \"\$XWANG_BASH\" \"\$XWANG_STATE\" set $CHANGE build_mode <subagent-driven-development|executing-plans|direct>" >&2
226
+ return 1
227
+ ;;
228
+ esac
229
+ }
230
+
231
+ build_mode_allowed_for_workflow() {
232
+ local workflow build_mode direct_override
233
+ workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
234
+ build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
235
+ direct_override=$(yaml_field_value "direct_override" 2>/dev/null || true)
236
+
237
+ if [ "$build_mode" != "direct" ]; then
238
+ return 0
239
+ fi
240
+ case "$workflow" in
241
+ quick|tweak) return 0 ;;
242
+ *)
243
+ if [ "$direct_override" = "true" ]; then
244
+ return 0
245
+ fi
246
+ echo "build_mode=direct is only allowed for quick/tweak unless direct_override: true is recorded" >&2
247
+ echo "Next: choose executing-plans or subagent-driven-development, or stop and ask the user for an explicit direct override." >&2
248
+ return 1
249
+ ;;
250
+ esac
251
+ }
252
+
253
+ subagent_dispatch_confirmed() {
254
+ local build_mode subagent_dispatch
255
+ build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
256
+ subagent_dispatch=$(yaml_field_value "subagent_dispatch" 2>/dev/null || true)
257
+
258
+ if [ "$build_mode" != "subagent-driven-development" ]; then
259
+ return 0
260
+ fi
261
+
262
+ if [ "$subagent_dispatch" = "confirmed" ]; then
263
+ return 0
264
+ fi
265
+
266
+ echo "subagent_dispatch must be confirmed before using build_mode=subagent-driven-development" >&2
267
+ echo "Next: confirm the current platform has a real background subagent/Task/multi-agent dispatcher, then run:" >&2
268
+ echo " \"\$XWANG_BASH\" \"\$XWANG_STATE\" set $CHANGE subagent_dispatch confirmed" >&2
269
+ echo "Or ask the user to switch to executing-plans and run:" >&2
270
+ echo " \"\$XWANG_BASH\" \"\$XWANG_STATE\" set $CHANGE build_mode executing-plans" >&2
271
+ return 1
272
+ }
273
+
274
+ tdd_mode_selected() {
275
+ local workflow tdd_mode
276
+ workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
277
+ tdd_mode=$(yaml_field_value "tdd_mode" 2>/dev/null || true)
278
+
279
+ case "$workflow" in
280
+ quick|tweak) return 0 ;;
281
+ esac
282
+
283
+ case "$tdd_mode" in
284
+ tdd|direct) return 0 ;;
285
+ *)
286
+ echo "tdd_mode must be tdd or direct for full workflow, got '${tdd_mode:-null}'" >&2
287
+ echo "Next: ask the user to choose TDD enforcement level, then run:" >&2
288
+ echo " \"\$XWANG_BASH\" \"\$XWANG_STATE\" set $CHANGE tdd_mode <tdd|direct>" >&2
289
+ return 1
290
+ ;;
291
+ esac
292
+ }
293
+
294
+ verification_report_exists() {
295
+ local report
296
+ report=$(yaml_field_value "verification_report" 2>/dev/null || true)
297
+ [ -n "$report" ] && [ "$report" != "null" ] && [ -f "$report" ]
298
+ }
299
+
300
+ branch_status_handled() {
301
+ local status
302
+ status=$(yaml_field_value "branch_status" 2>/dev/null || true)
303
+ [ "$status" = "handled" ]
304
+ }
305
+
306
+ design_doc_recorded() {
307
+ local design_doc
308
+ design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
309
+ if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
310
+ return 0
311
+ fi
312
+ echo "design_doc must point to an existing PRD for full workflow before leaving design." >&2
313
+ echo "Next: create the PRD and run: \"\$XWANG_BASH\" \"\$XWANG_STATE\" set $CHANGE design_doc <path>" >&2
314
+ return 1
315
+ }
316
+
317
+ archived_is_true() {
318
+ local val
319
+ val=$(yaml_field_value "archived" 2>/dev/null || true)
320
+ [ "$val" = "true" ]
321
+ }
322
+
323
+ run_command_string() {
324
+ local command="$1"
325
+ if [ -z "$command" ]; then
326
+ red "ERROR: build/verify command is empty" >&2
327
+ return 1
328
+ fi
329
+ # Basic command injection guard: reject dangerous shell metacharacters
330
+ if [[ "$command" =~ [\;\|\&\$\`] ]]; then
331
+ red "ERROR: build/verify command contains shell metacharacters: $command" >&2
332
+ red "Allowed: alphanumeric, spaces, hyphens, underscores, dots, colons, forward slashes, quotes" >&2
333
+ return 1
334
+ fi
335
+ echo "+ $command" >&2
336
+ "$XWANG_BASH" -lc "$command"
337
+ }
338
+
339
+ is_windows_bash() {
340
+ case "$(uname -s 2>/dev/null || true)" in
341
+ MINGW*|MSYS*|CYGWIN*) return 0 ;;
342
+ *) return 1 ;;
343
+ esac
344
+ }
345
+
346
+ build_passes() {
347
+ if [ "${XWANG_SKIP_BUILD:-0}" = "1" ]; then
348
+ return 0
349
+ fi
350
+ local configured_build
351
+ configured_build=$(project_config_value "build_command" 2>/dev/null || true)
352
+ if [ -n "$configured_build" ]; then
353
+ run_command_string "$configured_build"
354
+ return $?
355
+ fi
356
+ if [ -f "package.json" ] && grep -q '"build"' "package.json"; then
357
+ npm run build
358
+ return $?
359
+ fi
360
+ if [ -f "pom.xml" ]; then
361
+ if [ -x "./mvnw" ]; then
362
+ ./mvnw compile -q
363
+ elif is_windows_bash && command -v mvn.cmd >/dev/null 2>&1; then
364
+ mvn.cmd compile -q
365
+ else
366
+ mvn compile -q
367
+ fi
368
+ return $?
369
+ fi
370
+ if [ -f "Cargo.toml" ]; then
371
+ cargo build
372
+ return $?
373
+ fi
374
+ return 1
375
+ }
376
+
377
+ verification_command_passes() {
378
+ if [ "${XWANG_SKIP_BUILD:-0}" = "1" ]; then
379
+ return 0
380
+ fi
381
+ local configured_verify
382
+ configured_verify=$(project_config_value "verify_command" 2>/dev/null || true)
383
+ if [ -n "$configured_verify" ]; then
384
+ run_command_string "$configured_verify"
385
+ return $?
386
+ fi
387
+ build_passes
388
+ }
389
+
390
+ preflight() {
391
+ if [ ! -d "$CHANGE_DIR" ]; then
392
+ red "FATAL: change directory not found: $CHANGE_DIR"
393
+ exit 1
394
+ fi
395
+ if [ ! -f "$CHANGE_DIR/.xwang.yaml" ]; then
396
+ red "FATAL: .xwang.yaml not found in $CHANGE_DIR"
397
+ exit 1
398
+ fi
399
+ }
400
+
401
+ # --- Phase-specific guards ---
402
+
403
+ guard_design() {
404
+ echo "=== Guard: design → build ===" >&2
405
+
406
+ local workflow
407
+ workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
408
+
409
+ check "proposal.md exists and non-empty" file_nonempty "$CHANGE_DIR/proposal.md"
410
+ check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md"
411
+ check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md"
412
+ check "tasks.md has at least one task" tasks_has_any
413
+
414
+ if [ "$workflow" = "full" ]; then
415
+ check "design_doc is recorded for full workflow" design_doc_recorded
416
+ fi
417
+
418
+ local design_doc
419
+ design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
420
+ if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
421
+ check "PRD file ($design_doc) exists" file_nonempty "$design_doc"
422
+ elif [ "$workflow" != "full" ]; then
423
+ warn " [WARN] No design_doc recorded in .xwang.yaml (optional for quick/tweak)"
424
+ fi
425
+ }
426
+
427
+ guard_build() {
428
+ echo "=== Guard: build → verify ===" >&2
429
+
430
+ check "isolation selected" isolation_selected
431
+ check "build_mode selected" build_mode_selected
432
+ check "build_mode allowed for workflow" build_mode_allowed_for_workflow
433
+ check "subagent dispatch confirmed" subagent_dispatch_confirmed
434
+ check "tdd_mode selected" tdd_mode_selected
435
+ check "tasks.md all tasks checked" tasks_all_done
436
+ check "plan file all tasks checked" plan_tasks_all_done
437
+ check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
438
+ check "Build passes" build_passes
439
+ }
440
+
441
+ guard_verify() {
442
+ echo "=== Guard: verify → archive ===" >&2
443
+
444
+ check "tasks.md all tasks checked" tasks_all_done
445
+ check "Build/verify passes" verification_command_passes
446
+ check "verification_report exists" verification_report_exists
447
+ check "branch_status=handled" branch_status_handled
448
+ }
449
+
450
+ guard_archive() {
451
+ echo "=== Guard: archive completeness ===" >&2
452
+
453
+ check "archived is true" archived_is_true
454
+ check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
455
+ check "design.md exists" file_nonempty "$CHANGE_DIR/design.md"
456
+ check "tasks.md all tasks checked" tasks_all_done
457
+ }
458
+
459
+ locate_state_script() {
460
+ local state_sh="$SCRIPT_DIR/xwang-state.sh"
461
+ if [ -f "$state_sh" ]; then
462
+ echo "$state_sh"
463
+ return 0
464
+ fi
465
+ state_sh="$(find "$SCRIPT_DIR" "$SCRIPT_DIR/.." "$HOME" -path '*/xwang/scripts/xwang-state.sh' -type f -print -quit 2>/dev/null || true)"
466
+ if [ -n "$state_sh" ] && [ -f "$state_sh" ]; then
467
+ echo "$state_sh"
468
+ return 0
469
+ fi
470
+ return 1
471
+ }
472
+
473
+ apply_state_update() {
474
+ local state_sh
475
+ if ! state_sh=$(locate_state_script); then
476
+ red "FATAL: xwang-state.sh not found; cannot apply state transition"
477
+ exit 1
478
+ fi
479
+
480
+ local p="$1"
481
+ case "$p" in
482
+ design) "$XWANG_BASH" "$state_sh" set "$CHANGE" phase build ;;
483
+ build)
484
+ "$XWANG_BASH" "$state_sh" set "$CHANGE" phase verify
485
+ "$XWANG_BASH" "$state_sh" set "$CHANGE" verify_result pending
486
+ ;;
487
+ verify)
488
+ "$XWANG_BASH" "$state_sh" set "$CHANGE" phase archive
489
+ "$XWANG_BASH" "$state_sh" set "$CHANGE" verify_result pass
490
+ ;;
491
+ esac
492
+ }
493
+
494
+ # --- Main ---
495
+
496
+ if [ "${XWANG_GUARD_SOURCE_ONLY:-0}" = "1" ]; then
497
+ return 0 2>/dev/null
498
+ red "ERROR: XWANG_GUARD_SOURCE_ONLY=1 is only for sourcing, not direct execution" >&2
499
+ exit 1
500
+ fi
501
+
502
+ case "$PHASE" in
503
+ design) preflight ; guard_design ;;
504
+ build) preflight ; guard_build ;;
505
+ verify) preflight ; guard_verify ;;
506
+ archive) preflight ; guard_archive ;;
507
+ *)
508
+ red "Unknown phase: $PHASE"
509
+ echo "Valid phases: design, build, verify, archive" >&2
510
+ exit 1
511
+ ;;
512
+ esac
513
+
514
+ if [ "$BLOCK" -eq 1 ]; then
515
+ echo "" >&2
516
+ red "BLOCKED — fix failing checks before proceeding to next phase"
517
+ exit 1
518
+ else
519
+ echo "" >&2
520
+ green "ALL CHECKS PASSED — ready for next phase"
521
+ if [ "$APPLY" -eq 1 ]; then
522
+ apply_state_update "$PHASE"
523
+ case "$PHASE" in
524
+ design) green " [APPLY] .xwang.yaml updated: phase=build" ;;
525
+ build) green " [APPLY] .xwang.yaml updated: phase=verify, verify_result=pending" ;;
526
+ verify) green " [APPLY] .xwang.yaml updated: phase=archive, verify_result=pass" ;;
527
+ esac
528
+ fi
529
+ exit 0
530
+ fi