@pzy560117/opensuper 0.2.6

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 (74) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +362 -0
  3. package/assets/manifest.json +21 -0
  4. package/assets/skills/opensuper/SKILL.md +268 -0
  5. package/assets/skills/opensuper/scripts/opensuper-archive.sh +255 -0
  6. package/assets/skills/opensuper/scripts/opensuper-guard.sh +407 -0
  7. package/assets/skills/opensuper/scripts/opensuper-state.sh +680 -0
  8. package/assets/skills/opensuper/scripts/opensuper-yaml-validate.sh +157 -0
  9. package/assets/skills/opensuper-archive/SKILL.md +69 -0
  10. package/assets/skills/opensuper-build/SKILL.md +194 -0
  11. package/assets/skills/opensuper-design/SKILL.md +84 -0
  12. package/assets/skills/opensuper-hotfix/SKILL.md +146 -0
  13. package/assets/skills/opensuper-open/SKILL.md +82 -0
  14. package/assets/skills/opensuper-tweak/SKILL.md +133 -0
  15. package/assets/skills/opensuper-verify/SKILL.md +139 -0
  16. package/assets/skills-zh/opensuper/SKILL.md +271 -0
  17. package/assets/skills-zh/opensuper-archive/SKILL.md +69 -0
  18. package/assets/skills-zh/opensuper-build/SKILL.md +194 -0
  19. package/assets/skills-zh/opensuper-design/SKILL.md +84 -0
  20. package/assets/skills-zh/opensuper-hotfix/SKILL.md +152 -0
  21. package/assets/skills-zh/opensuper-open/SKILL.md +84 -0
  22. package/assets/skills-zh/opensuper-tweak/SKILL.md +139 -0
  23. package/assets/skills-zh/opensuper-verify/SKILL.md +154 -0
  24. package/bin/opensuper.js +3 -0
  25. package/dist/cli/index.d.ts +2 -0
  26. package/dist/cli/index.d.ts.map +1 -0
  27. package/dist/cli/index.js +63 -0
  28. package/dist/cli/index.js.map +1 -0
  29. package/dist/commands/doctor.d.ts +9 -0
  30. package/dist/commands/doctor.d.ts.map +1 -0
  31. package/dist/commands/doctor.js +191 -0
  32. package/dist/commands/doctor.js.map +1 -0
  33. package/dist/commands/init.d.ts +17 -0
  34. package/dist/commands/init.d.ts.map +1 -0
  35. package/dist/commands/init.js +242 -0
  36. package/dist/commands/init.js.map +1 -0
  37. package/dist/commands/status.d.ts +6 -0
  38. package/dist/commands/status.d.ts.map +1 -0
  39. package/dist/commands/status.js +108 -0
  40. package/dist/commands/status.js.map +1 -0
  41. package/dist/commands/update.d.ts +28 -0
  42. package/dist/commands/update.d.ts.map +1 -0
  43. package/dist/commands/update.js +193 -0
  44. package/dist/commands/update.js.map +1 -0
  45. package/dist/core/detect.d.ts +13 -0
  46. package/dist/core/detect.d.ts.map +1 -0
  47. package/dist/core/detect.js +101 -0
  48. package/dist/core/detect.js.map +1 -0
  49. package/dist/core/openspec.d.ts +5 -0
  50. package/dist/core/openspec.d.ts.map +1 -0
  51. package/dist/core/openspec.js +58 -0
  52. package/dist/core/openspec.js.map +1 -0
  53. package/dist/core/platforms.d.ts +15 -0
  54. package/dist/core/platforms.d.ts.map +1 -0
  55. package/dist/core/platforms.js +48 -0
  56. package/dist/core/platforms.js.map +1 -0
  57. package/dist/core/skills.d.ts +22 -0
  58. package/dist/core/skills.d.ts.map +1 -0
  59. package/dist/core/skills.js +59 -0
  60. package/dist/core/skills.js.map +1 -0
  61. package/dist/core/superpowers.d.ts +5 -0
  62. package/dist/core/superpowers.d.ts.map +1 -0
  63. package/dist/core/superpowers.js +60 -0
  64. package/dist/core/superpowers.js.map +1 -0
  65. package/dist/core/types.d.ts +2 -0
  66. package/dist/core/types.d.ts.map +1 -0
  67. package/dist/core/types.js +2 -0
  68. package/dist/core/types.js.map +1 -0
  69. package/dist/utils/file-system.d.ts +25 -0
  70. package/dist/utils/file-system.d.ts.map +1 -0
  71. package/dist/utils/file-system.js +53 -0
  72. package/dist/utils/file-system.js.map +1 -0
  73. package/package.json +60 -0
  74. package/scripts/postinstall.js +44 -0
@@ -0,0 +1,255 @@
1
+ #!/bin/bash
2
+ # OpenSuper Archive — automates the archive phase in one command
3
+ # Usage: opensuper-archive.sh <change-name> [--dry-run]
4
+ # Exit 0 = archive complete, exit 1 = fatal error
5
+
6
+ set -euo pipefail
7
+
8
+ red() { echo -e "\033[31m$1\033[0m" >&2; }
9
+ green() { echo -e "\033[32m$1\033[0m" >&2; }
10
+ yellow() { echo -e "\033[33m$1\033[0m" >&2; }
11
+
12
+ DRY_RUN=0
13
+ if [[ "${2:-}" == "--dry-run" ]]; then
14
+ DRY_RUN=1
15
+ fi
16
+
17
+ # Input validation
18
+ validate_change_name() {
19
+ local name="$1"
20
+ if [ -z "$name" ]; then
21
+ red "FATAL: Change name cannot be empty"
22
+ exit 1
23
+ fi
24
+ if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
25
+ red "FATAL: Invalid change name: '$name'"
26
+ red "Valid characters: a-z, A-Z, 0-9, -, _"
27
+ exit 1
28
+ fi
29
+ if [[ "$name" =~ \.\. ]]; then
30
+ red "FATAL: Change name cannot contain '..'"
31
+ exit 1
32
+ fi
33
+ }
34
+
35
+ CHANGE="$1"
36
+ validate_change_name "$CHANGE"
37
+
38
+ CHANGE_DIR="openspec/changes/$CHANGE"
39
+ YAML="$CHANGE_DIR/.opensuper.yaml"
40
+ SCRIPT_DIR="$(cd "$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")" && pwd)"
41
+ STATE_SH="$SCRIPT_DIR/opensuper-state.sh"
42
+ TODAY=$(date +%Y-%m-%d)
43
+ ARCHIVE_NAME="${TODAY}-${CHANGE}"
44
+ ARCHIVE_DIR="openspec/changes/archive/${ARCHIVE_NAME}"
45
+
46
+ STEPS_OK=0
47
+ STEPS_TOTAL=0
48
+
49
+ step_ok() {
50
+ green " [OK] $1"
51
+ STEPS_OK=$((STEPS_OK + 1))
52
+ STEPS_TOTAL=$((STEPS_TOTAL + 1))
53
+ }
54
+
55
+ step_fail() {
56
+ red " [FAIL] $1"
57
+ STEPS_TOTAL=$((STEPS_TOTAL + 1))
58
+ }
59
+
60
+ step_dry_run() {
61
+ yellow " [DRY-RUN] $1"
62
+ STEPS_OK=$((STEPS_OK + 1))
63
+ STEPS_TOTAL=$((STEPS_TOTAL + 1))
64
+ }
65
+
66
+ echo "=== OpenSuper Archive: $CHANGE ===" >&2
67
+
68
+ # --- Step 1: Read .opensuper.yaml, extract paths ---
69
+
70
+ yaml_field() {
71
+ local field="$1"
72
+ if [ -f "$STATE_SH" ]; then
73
+ bash "$STATE_SH" get "$CHANGE" "$field" 2>/dev/null
74
+ else
75
+ if [ -f "$YAML" ]; then
76
+ grep "^${field}:" "$YAML" | sed "s/^${field}: *//" | tr -d '"' | tr -d "'"
77
+ fi
78
+ fi
79
+ }
80
+
81
+ if [ ! -f "$YAML" ]; then
82
+ red "FATAL: .opensuper.yaml not found in $CHANGE_DIR/"
83
+ exit 1
84
+ fi
85
+
86
+ DESIGN_DOC=$(yaml_field "design_doc")
87
+ PLAN_PATH=$(yaml_field "plan")
88
+
89
+ # --- Step 2: Validate entry state ---
90
+
91
+ PHASE_VAL=$(yaml_field "phase")
92
+ VERIFY_VAL=$(yaml_field "verify_result")
93
+ ARCHIVED_VAL=$(yaml_field "archived")
94
+
95
+ if [ "$PHASE_VAL" != "archive" ]; then
96
+ red "FATAL: phase is '$PHASE_VAL', expected 'archive'"
97
+ exit 1
98
+ fi
99
+
100
+ if [ "$VERIFY_VAL" != "pass" ]; then
101
+ red "FATAL: verify_result is '$VERIFY_VAL', expected 'pass'. Run opensuper-verify first."
102
+ exit 1
103
+ fi
104
+
105
+ if [ "$ARCHIVED_VAL" = "true" ]; then
106
+ red "FATAL: change already archived"
107
+ exit 1
108
+ fi
109
+
110
+ step_ok "Entry state verified"
111
+
112
+ # --- Step 3: Check archive target ---
113
+
114
+ if [ -d "$ARCHIVE_DIR" ]; then
115
+ red "FATAL: archive target already exists: $ARCHIVE_DIR"
116
+ exit 1
117
+ fi
118
+
119
+ step_ok "Archive target available"
120
+
121
+ # --- Step 4: Sync delta specs → main specs ---
122
+
123
+ sync_delta_specs() {
124
+ local delta_root="$CHANGE_DIR/specs"
125
+ if [ ! -d "$delta_root" ]; then
126
+ return 0
127
+ fi
128
+
129
+ for delta_spec_dir in "$delta_root"/*/; do
130
+ [ -d "$delta_spec_dir" ] || continue
131
+ local capability
132
+ capability=$(basename "$delta_spec_dir")
133
+ local delta_spec="$delta_spec_dir/spec.md"
134
+ local main_spec="openspec/specs/$capability/spec.md"
135
+
136
+ if [ ! -f "$delta_spec" ]; then
137
+ continue
138
+ fi
139
+
140
+ if [ "$DRY_RUN" -eq 1 ]; then
141
+ step_dry_run "Would sync: $capability → $main_spec"
142
+ continue
143
+ fi
144
+
145
+ if [ ! -f "$main_spec" ]; then
146
+ mkdir -p "openspec/specs/$capability"
147
+ elif ! cmp -s "$main_spec" "$delta_spec"; then
148
+ yellow " [DIFF] Delta spec differs from main spec before sync: $capability"
149
+ diff -u "$main_spec" "$delta_spec" >&2 || true
150
+ fi
151
+ cp "$delta_spec" "$main_spec"
152
+
153
+ step_ok "Delta spec synced: $capability → openspec/specs/$capability/spec.md"
154
+ done
155
+ }
156
+
157
+ sync_delta_specs
158
+
159
+ # --- Step 5: Annotate design doc frontmatter ---
160
+
161
+ annotate_frontmatter() {
162
+ local file="$1"
163
+ local extra_fields="$2"
164
+
165
+ if [ ! -f "$file" ]; then
166
+ return 0
167
+ fi
168
+
169
+ if [ "$DRY_RUN" -eq 1 ]; then
170
+ step_dry_run "Would annotate: $file"
171
+ return 0
172
+ fi
173
+
174
+ if head -1 "$file" | grep -q '^---'; then
175
+ local tmp_file
176
+ tmp_file=$(mktemp)
177
+ awk -v archive="$ARCHIVE_NAME" -v extra="$extra_fields" '
178
+ /^archived-with:/ { next }
179
+ NR==1 && /^---/ { print; next }
180
+ /^---/ && NR>1 {
181
+ print "archived-with: " archive
182
+ if (extra != "") print extra
183
+ print; next
184
+ }
185
+ { print }
186
+ ' "$file" > "$tmp_file"
187
+ mv "$tmp_file" "$file"
188
+ else
189
+ local tmp_file
190
+ tmp_file=$(mktemp)
191
+ {
192
+ echo "---"
193
+ echo "archived-with: $ARCHIVE_NAME"
194
+ if [ -n "$extra_fields" ]; then
195
+ echo "$extra_fields"
196
+ fi
197
+ echo "status: final"
198
+ echo "---"
199
+ cat "$file"
200
+ } > "$tmp_file"
201
+ mv "$tmp_file" "$file"
202
+ fi
203
+
204
+ step_ok "Annotated: $file"
205
+ }
206
+
207
+ if [ -n "$DESIGN_DOC" ] && [ "$DESIGN_DOC" != "null" ]; then
208
+ annotate_frontmatter "$DESIGN_DOC" "status: final"
209
+ fi
210
+
211
+ # --- Step 6: Annotate plan frontmatter ---
212
+
213
+ if [ -n "$PLAN_PATH" ] && [ "$PLAN_PATH" != "null" ]; then
214
+ annotate_frontmatter "$PLAN_PATH" ""
215
+ fi
216
+
217
+ # --- Step 7: Move change to archive ---
218
+
219
+ if [ "$DRY_RUN" -eq 1 ]; then
220
+ step_dry_run "Would move: $CHANGE_DIR → $ARCHIVE_DIR"
221
+ else
222
+ mkdir -p "openspec/changes/archive"
223
+ mv "$CHANGE_DIR" "$ARCHIVE_DIR"
224
+ step_ok "Moved to: $ARCHIVE_DIR"
225
+ fi
226
+
227
+ # --- Step 8: Mark archived via opensuper-state transition ---
228
+
229
+ ARCHIVE_YAML="$ARCHIVE_DIR/.opensuper.yaml"
230
+
231
+ if [ "$DRY_RUN" -eq 1 ]; then
232
+ step_dry_run "Would set archived: true in $ARCHIVE_YAML"
233
+ else
234
+ if [ -f "$ARCHIVE_YAML" ]; then
235
+ bash "$STATE_SH" transition "$ARCHIVE_NAME" archived >/dev/null
236
+ step_ok "archived: true"
237
+ else
238
+ step_fail "archived: true (.opensuper.yaml not found after move)"
239
+ fi
240
+ fi
241
+
242
+ # --- Step 9: Print summary ---
243
+
244
+ echo "" >&2
245
+ if [ "$DRY_RUN" -eq 1 ]; then
246
+ yellow "Dry run complete. $STEPS_OK/$STEPS_TOTAL steps would succeed."
247
+ else
248
+ green "Archive complete. $STEPS_OK/$STEPS_TOTAL steps succeeded."
249
+ fi
250
+
251
+ if [ "$STEPS_OK" -lt "$STEPS_TOTAL" ]; then
252
+ exit 1
253
+ fi
254
+
255
+ exit 0
@@ -0,0 +1,407 @@
1
+ #!/bin/bash
2
+ # OpenSuper Phase Guard — validates exit conditions before phase transitions
3
+ # Usage: opensuper-guard.sh <change-name> <current-phase> [--apply]
4
+ # Phases: open, design, build, verify, archive
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
+ red() { echo -e "\033[31m$1\033[0m" >&2; }
11
+ green() { echo -e "\033[32m$1\033[0m" >&2; }
12
+ warn() { echo -e "\033[33m$1\033[0m" >&2; }
13
+
14
+ # Input validation - prevent path traversal
15
+ validate_change_name() {
16
+ local name="$1"
17
+ # Reject empty names
18
+ if [ -z "$name" ]; then
19
+ red "ERROR: Change name cannot be empty" >&2
20
+ exit 1
21
+ fi
22
+ # Only allow alphanumeric, hyphens, and underscores
23
+ if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
24
+ red "ERROR: Invalid change name: '$name'" >&2
25
+ red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
26
+ exit 1
27
+ fi
28
+ # Reject path traversal attempts
29
+ if [[ "$name" =~ \.\. ]]; then
30
+ red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
31
+ exit 1
32
+ fi
33
+ }
34
+
35
+ validate_change_name "$1"
36
+
37
+ CHANGE="$1"
38
+ PHASE="$2"
39
+ APPLY=0
40
+ SCRIPT_DIR="$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")"
41
+ if [[ "${3:-}" == "--apply" ]]; then
42
+ APPLY=1
43
+ fi
44
+ CHANGE_DIR="openspec/changes/$CHANGE"
45
+ if [ "$PHASE" = "archive" ] && [ ! -d "$CHANGE_DIR" ] && [ -d "openspec/changes/archive/$CHANGE" ]; then
46
+ CHANGE_DIR="openspec/changes/archive/$CHANGE"
47
+ fi
48
+
49
+ BLOCK=0
50
+ check() {
51
+ local desc="$1"
52
+ shift
53
+ local output
54
+ if output=$("$@" 2>&1); then
55
+ green " [PASS] $desc"
56
+ else
57
+ red " [FAIL] $desc"
58
+ if [ -n "$output" ]; then
59
+ while IFS= read -r line; do
60
+ red " $line"
61
+ done <<< "$output"
62
+ fi
63
+ BLOCK=1
64
+ fi
65
+ }
66
+
67
+ # --- Helper functions ---
68
+
69
+ tasks_all_done() {
70
+ local tasks="$CHANGE_DIR/tasks.md"
71
+ if [ ! -f "$tasks" ]; then
72
+ echo "tasks.md is missing at $tasks" >&2
73
+ echo "Next: restore or create tasks.md for this change before leaving build." >&2
74
+ return 1
75
+ fi
76
+ if ! grep -q '\- \[x\]' "$tasks"; then
77
+ echo "tasks.md has no completed tasks." >&2
78
+ echo "Next: complete implementation tasks and mark them with '- [x]'." >&2
79
+ return 1
80
+ fi
81
+ if grep -q '\- \[ \]' "$tasks"; then
82
+ echo "Unfinished tasks:" >&2
83
+ grep -n '\- \[ \]' "$tasks" >&2 || true
84
+ echo "Next: complete or explicitly remove unfinished tasks, then mark tasks.md with '- [x]'." >&2
85
+ return 1
86
+ fi
87
+ return 0
88
+ }
89
+
90
+ tasks_has_any() {
91
+ local tasks="$CHANGE_DIR/tasks.md"
92
+ [ -f "$tasks" ] && grep -q '\- \[' "$tasks"
93
+ }
94
+
95
+ yaml_field_value() {
96
+ local field="$1"
97
+ local yaml="$CHANGE_DIR/.opensuper.yaml"
98
+ if [ -f "$yaml" ]; then
99
+ local value
100
+ value=$(grep "^${field}:" "$yaml" 2>/dev/null | sed "s/^${field}: *//" || true)
101
+ strip_wrapping_quotes "$value"
102
+ fi
103
+ }
104
+
105
+ strip_wrapping_quotes() {
106
+ local value="$1"
107
+ case "$value" in
108
+ \"*\")
109
+ printf '%s\n' "${value:1:${#value}-2}"
110
+ ;;
111
+ \'*\')
112
+ printf '%s\n' "${value:1:${#value}-2}"
113
+ ;;
114
+ *)
115
+ printf '%s\n' "$value"
116
+ ;;
117
+ esac
118
+ }
119
+
120
+ project_config_value() {
121
+ local field="$1"
122
+ local value
123
+
124
+ value=$(yaml_field_value "$field" 2>/dev/null || true)
125
+ if [ -n "$value" ] && [ "$value" != "null" ]; then
126
+ echo "$value"
127
+ return 0
128
+ fi
129
+
130
+ for config in ".opensuper.yaml" "opensuper.yaml" ".opensuper.yml" "opensuper.yml"; do
131
+ if [ -f "$config" ]; then
132
+ value=$(grep "^${field}:" "$config" 2>/dev/null | sed "s/^${field}: *//" || true)
133
+ value=$(strip_wrapping_quotes "$value")
134
+ if [ -n "$value" ] && [ "$value" != "null" ]; then
135
+ echo "$value"
136
+ return 0
137
+ fi
138
+ fi
139
+ done
140
+ }
141
+
142
+ file_nonempty() {
143
+ [ -f "$1" ] && [ -s "$1" ]
144
+ }
145
+
146
+ is_windows_bash() {
147
+ case "$(uname -s 2>/dev/null || true)" in
148
+ MINGW*|MSYS*|CYGWIN*) return 0 ;;
149
+ *) return 1 ;;
150
+ esac
151
+ }
152
+
153
+ run_command_string() {
154
+ local command="$1"
155
+ echo "+ $command" >&2
156
+ bash -lc "$command"
157
+ }
158
+
159
+ preflight() {
160
+
161
+ if [ ! -d "$CHANGE_DIR" ]; then
162
+ red "FATAL: change directory not found: $CHANGE_DIR"
163
+ exit 1
164
+ fi
165
+ if [ ! -f "$CHANGE_DIR/.opensuper.yaml" ]; then
166
+ red "FATAL: .opensuper.yaml not found in $CHANGE_DIR"
167
+ exit 1
168
+ fi
169
+
170
+ # Schema validation
171
+ local validate_script
172
+ validate_script="$SCRIPT_DIR/opensuper-yaml-validate.sh"
173
+ if [ -f "$validate_script" ]; then
174
+ if ! bash "$validate_script" "$CHANGE" 2>/dev/null; then
175
+ bash "$validate_script" "$CHANGE"
176
+ red "FATAL: .opensuper.yaml schema validation failed"
177
+ exit 1
178
+ fi
179
+ fi
180
+ }
181
+
182
+ build_passes() {
183
+ if [ "${OPENSUPER_SKIP_BUILD:-0}" = "1" ]; then
184
+ return 0
185
+ fi
186
+ local configured_build
187
+ configured_build=$(project_config_value "build_command" 2>/dev/null || true)
188
+ if [ -n "$configured_build" ]; then
189
+ run_command_string "$configured_build"
190
+ return $?
191
+ fi
192
+ if [ -f "package.json" ] && grep -q '"build"' "package.json"; then
193
+ npm run build
194
+ return $?
195
+ fi
196
+ if [ -f "pom.xml" ]; then
197
+ if [ -x "./mvnw" ]; then
198
+ ./mvnw compile -q
199
+ elif is_windows_bash && command -v mvn.cmd >/dev/null 2>&1; then
200
+ mvn.cmd compile -q
201
+ else
202
+ mvn compile -q
203
+ fi
204
+ return $?
205
+ fi
206
+ if [ -f "Cargo.toml" ]; then
207
+ cargo build
208
+ return $?
209
+ fi
210
+ return 1
211
+ }
212
+
213
+ verification_command_passes() {
214
+ if [ "${OPENSUPER_SKIP_BUILD:-0}" = "1" ]; then
215
+ return 0
216
+ fi
217
+ local configured_verify
218
+ configured_verify=$(project_config_value "verify_command" 2>/dev/null || true)
219
+ if [ -n "$configured_verify" ]; then
220
+ run_command_string "$configured_verify"
221
+ return $?
222
+ fi
223
+ build_passes
224
+ }
225
+
226
+ isolation_selected() {
227
+ local isolation
228
+ isolation=$(yaml_field_value "isolation" 2>/dev/null || true)
229
+ case "$isolation" in
230
+ branch|worktree) return 0 ;;
231
+ *)
232
+ echo "isolation must be branch or worktree, got '${isolation:-null}'" >&2
233
+ echo "Next: ask the user to choose branch or worktree, create the chosen isolation, then run:" >&2
234
+ echo " bash \"\$OPENSUPER_STATE\" set $CHANGE isolation <branch|worktree>" >&2
235
+ return 1
236
+ ;;
237
+ esac
238
+ }
239
+
240
+ build_mode_selected() {
241
+ local build_mode
242
+ build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
243
+ case "$build_mode" in
244
+ subagent-driven-development|executing-plans|direct) return 0 ;;
245
+ *)
246
+ echo "build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2
247
+ echo "Next: ask the user to choose an implementation mode, then run:" >&2
248
+ echo " bash \"\$OPENSUPER_STATE\" set $CHANGE build_mode <subagent-driven-development|executing-plans>" >&2
249
+ return 1
250
+ ;;
251
+ esac
252
+ }
253
+
254
+ build_mode_allowed_for_workflow() {
255
+ local workflow build_mode direct_override
256
+ workflow=$(yaml_field_value "workflow" 2>/dev/null || true)
257
+ build_mode=$(yaml_field_value "build_mode" 2>/dev/null || true)
258
+ direct_override=$(yaml_field_value "direct_override" 2>/dev/null || true)
259
+
260
+ if [ "$build_mode" != "direct" ]; then
261
+ return 0
262
+ fi
263
+ case "$workflow" in
264
+ hotfix|tweak) return 0 ;;
265
+ *)
266
+ if [ "$direct_override" = "true" ]; then
267
+ return 0
268
+ fi
269
+ echo "build_mode=direct is only allowed for hotfix/tweak unless direct_override: true is recorded" >&2
270
+ echo "Next: switch build_mode to executing-plans or subagent-driven-development, or stop and ask the user for an explicit direct override." >&2
271
+ return 1
272
+ ;;
273
+ esac
274
+ }
275
+
276
+ verify_result_is_pass() {
277
+ local result
278
+ result=$(yaml_field_value "verify_result" 2>/dev/null || true)
279
+ [ "$result" = "pass" ]
280
+ }
281
+
282
+ verification_report_exists() {
283
+ local report
284
+ report=$(yaml_field_value "verification_report" 2>/dev/null || true)
285
+ [ -n "$report" ] && [ "$report" != "null" ] && [ -f "$report" ]
286
+ }
287
+
288
+ branch_status_handled() {
289
+ local status
290
+ status=$(yaml_field_value "branch_status" 2>/dev/null || true)
291
+ [ "$status" = "handled" ]
292
+ }
293
+
294
+ archived_is_true() {
295
+ local val
296
+ val=$(yaml_field_value "archived" 2>/dev/null || true)
297
+ [ "$val" = "true" ]
298
+ }
299
+
300
+ # --- Phase-specific checks ---
301
+
302
+ guard_open() {
303
+ echo "=== Guard: open → next ===" >&2
304
+
305
+ check "proposal.md exists and non-empty" file_nonempty "$CHANGE_DIR/proposal.md"
306
+ check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md"
307
+ check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md"
308
+ check "tasks.md has at least one task" tasks_has_any
309
+ }
310
+
311
+ guard_design() {
312
+ echo "=== Guard: design → build ===" >&2
313
+
314
+ local design_doc
315
+ design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
316
+
317
+ check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
318
+ check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
319
+
320
+ if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
321
+ check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
322
+ else
323
+ warn " [WARN] No design_doc recorded in .opensuper.yaml"
324
+ fi
325
+ }
326
+
327
+ guard_build() {
328
+ echo "=== Guard: build → verify ===" >&2
329
+
330
+ check "isolation selected" isolation_selected
331
+ check "build_mode selected" build_mode_selected
332
+ check "build_mode allowed for workflow" build_mode_allowed_for_workflow
333
+ check "tasks.md all tasks checked" tasks_all_done
334
+ check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
335
+ check "Build passes" build_passes
336
+ }
337
+
338
+ guard_verify() {
339
+ echo "=== Guard: verify → archive ===" >&2
340
+
341
+ check "tasks.md all tasks checked" tasks_all_done
342
+ check "Build passes" verification_command_passes
343
+ check "verification_report exists" verification_report_exists
344
+ check "branch_status=handled" branch_status_handled
345
+ }
346
+
347
+ guard_archive() {
348
+ echo "=== Guard: archive completeness ===" >&2
349
+
350
+ check "archived is true" archived_is_true
351
+ check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
352
+ check "tasks.md all tasks checked" tasks_all_done
353
+ }
354
+
355
+ apply_state_update() {
356
+ local state_sh="$SCRIPT_DIR/opensuper-state.sh"
357
+ local p="$1"
358
+
359
+ if [ -f "$state_sh" ]; then
360
+ case "$p" in
361
+ open) bash "$state_sh" transition "$CHANGE" open-complete ;;
362
+ design) bash "$state_sh" transition "$CHANGE" design-complete ;;
363
+ build) bash "$state_sh" transition "$CHANGE" build-complete ;;
364
+ verify) bash "$state_sh" transition "$CHANGE" verify-pass ;;
365
+ esac
366
+ else
367
+ red "FATAL: opensuper-state.sh not found; cannot apply state transition"
368
+ exit 1
369
+ fi
370
+ }
371
+
372
+ # --- Main ---
373
+
374
+ case "$PHASE" in
375
+ open) preflight ; guard_open ;;
376
+ design) preflight ; guard_design ;;
377
+ build) preflight ; guard_build ;;
378
+ verify) preflight ; guard_verify ;;
379
+ archive) preflight ; guard_archive ;;
380
+ *)
381
+ red "Unknown phase: $PHASE"
382
+ echo "Valid phases: open, design, build, verify, archive" >&2
383
+ exit 1
384
+ ;;
385
+ esac
386
+
387
+ if [ "$BLOCK" -eq 1 ]; then
388
+ echo "" >&2
389
+ red "BLOCKED — fix failing checks before proceeding to next phase"
390
+ exit 1
391
+ else
392
+ echo "" >&2
393
+ green "ALL CHECKS PASSED — ready for next phase"
394
+ if [ "$APPLY" -eq 1 ]; then
395
+ apply_state_update "$PHASE"
396
+ case "$PHASE" in
397
+ open)
398
+ new_phase=$(grep "^phase:" "$CHANGE_DIR/.opensuper.yaml" | sed 's/^phase: *//' | tr -d '"' | tr -d "'")
399
+ green " [APPLY] .opensuper.yaml updated: phase=$new_phase"
400
+ ;;
401
+ design) green " [APPLY] .opensuper.yaml updated: phase=build" ;;
402
+ build) green " [APPLY] .opensuper.yaml updated: phase=verify, verify_result=pending" ;;
403
+ verify) green " [APPLY] .opensuper.yaml updated: phase=archive, verify_result=pass" ;;
404
+ esac
405
+ fi
406
+ exit 0
407
+ fi