@rpamis/comet 0.2.5 → 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.
@@ -1,257 +1,407 @@
1
- #!/bin/bash
2
- # Comet Phase Guard — validates exit conditions before phase transitions
3
- # Usage: comet-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
- if "$@" 2>/dev/null; then
54
- green " [PASS] $desc"
55
- else
56
- red " [FAIL] $desc"
57
- BLOCK=1
58
- fi
59
- }
60
-
61
- # --- Helper functions ---
62
-
63
- tasks_all_done() {
64
- local tasks="$CHANGE_DIR/tasks.md"
65
- [ -f "$tasks" ] || return 1
66
- grep -q '\- \[x\]' "$tasks" || return 1
67
- ! grep -q '\- \[ \]' "$tasks"
68
- }
69
-
70
- tasks_has_any() {
71
- local tasks="$CHANGE_DIR/tasks.md"
72
- [ -f "$tasks" ] && grep -q '\- \[' "$tasks"
73
- }
74
-
75
- yaml_field_value() {
76
- local field="$1"
77
- local yaml="$CHANGE_DIR/.comet.yaml"
78
- if [ -f "$yaml" ]; then
79
- grep "^${field}:" "$yaml" | sed "s/^${field}: *//" | tr -d '"' | tr -d "'"
80
- fi
81
- }
82
-
83
- file_nonempty() {
84
- [ -f "$1" ] && [ -s "$1" ]
85
- }
86
-
87
- preflight() {
88
-
89
- if [ ! -d "$CHANGE_DIR" ]; then
90
- red "FATAL: change directory not found: $CHANGE_DIR"
91
- exit 1
92
- fi
93
- if [ ! -f "$CHANGE_DIR/.comet.yaml" ]; then
94
- red "FATAL: .comet.yaml not found in $CHANGE_DIR"
95
- exit 1
96
- fi
97
-
98
- # Schema validation
99
- local validate_script
100
- validate_script="$SCRIPT_DIR/comet-yaml-validate.sh"
101
- if [ -f "$validate_script" ]; then
102
- if ! bash "$validate_script" "$CHANGE" 2>/dev/null; then
103
- bash "$validate_script" "$CHANGE"
104
- red "FATAL: .comet.yaml schema validation failed"
105
- exit 1
106
- fi
107
- fi
108
- }
109
-
110
- build_passes() {
111
- if [ "${COMET_SKIP_BUILD:-0}" = "1" ]; then
112
- return 0
113
- fi
114
- if [ -f "package.json" ] && grep -q '"build"' "package.json"; then
115
- npm run build >/dev/null
116
- return $?
117
- fi
118
- if [ -f "pom.xml" ]; then
119
- mvn compile -q >/dev/null
120
- return $?
121
- fi
122
- if [ -f "Cargo.toml" ]; then
123
- cargo build >/dev/null
124
- return $?
125
- fi
126
- return 1
127
- }
128
-
129
- verify_result_is_pass() {
130
- local result
131
- result=$(yaml_field_value "verify_result" 2>/dev/null || true)
132
- [ "$result" = "pass" ]
133
- }
134
-
135
- verification_report_exists() {
136
- local report
137
- report=$(yaml_field_value "verification_report" 2>/dev/null || true)
138
- [ -n "$report" ] && [ "$report" != "null" ] && [ -f "$report" ]
139
- }
140
-
141
- branch_status_handled() {
142
- local status
143
- status=$(yaml_field_value "branch_status" 2>/dev/null || true)
144
- [ "$status" = "handled" ]
145
- }
146
-
147
- archived_is_true() {
148
- local val
149
- val=$(yaml_field_value "archived" 2>/dev/null || true)
150
- [ "$val" = "true" ]
151
- }
152
-
153
- # --- Phase-specific checks ---
154
-
155
- guard_open() {
156
- echo "=== Guard: open → next ===" >&2
157
-
158
- check "proposal.md exists and non-empty" file_nonempty "$CHANGE_DIR/proposal.md"
159
- check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md"
160
- check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md"
161
- check "tasks.md has at least one task" tasks_has_any
162
- }
163
-
164
- guard_design() {
165
- echo "=== Guard: design build ===" >&2
166
-
167
- local design_doc
168
- design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
169
-
170
- check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
171
- check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
172
-
173
- if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
174
- check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
175
- else
176
- warn " [WARN] No design_doc recorded in .comet.yaml"
177
- fi
178
- }
179
-
180
- guard_build() {
181
- echo "=== Guard: build → verify ===" >&2
182
-
183
- check "tasks.md all tasks checked" tasks_all_done
184
- check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
185
- check "Build passes" build_passes
186
- }
187
-
188
- guard_verify() {
189
- echo "=== Guard: verify → archive ===" >&2
190
-
191
- check "tasks.md all tasks checked" tasks_all_done
192
- check "Build passes" build_passes
193
- check "verification_report exists" verification_report_exists
194
- check "branch_status=handled" branch_status_handled
195
- }
196
-
197
- guard_archive() {
198
- echo "=== Guard: archive completeness ===" >&2
199
-
200
- check "archived is true" archived_is_true
201
- check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
202
- check "tasks.md all tasks checked" tasks_all_done
203
- }
204
-
205
- apply_state_update() {
206
- local state_sh="$SCRIPT_DIR/comet-state.sh"
207
- local p="$1"
208
-
209
- if [ -f "$state_sh" ]; then
210
- case "$p" in
211
- open) bash "$state_sh" transition "$CHANGE" open-complete ;;
212
- design) bash "$state_sh" transition "$CHANGE" design-complete ;;
213
- build) bash "$state_sh" transition "$CHANGE" build-complete ;;
214
- verify) bash "$state_sh" transition "$CHANGE" verify-pass ;;
215
- esac
216
- else
217
- red "FATAL: comet-state.sh not found; cannot apply state transition"
218
- exit 1
219
- fi
220
- }
221
-
222
- # --- Main ---
223
-
224
- case "$PHASE" in
225
- open) preflight ; guard_open ;;
226
- design) preflight ; guard_design ;;
227
- build) preflight ; guard_build ;;
228
- verify) preflight ; guard_verify ;;
229
- archive) preflight ; guard_archive ;;
230
- *)
231
- red "Unknown phase: $PHASE"
232
- echo "Valid phases: open, design, build, verify, archive" >&2
233
- exit 1
234
- ;;
235
- esac
236
-
237
- if [ "$BLOCK" -eq 1 ]; then
238
- echo "" >&2
239
- red "BLOCKED — fix failing checks before proceeding to next phase"
240
- exit 1
241
- else
242
- echo "" >&2
243
- green "ALL CHECKS PASSED — ready for next phase"
244
- if [ "$APPLY" -eq 1 ]; then
245
- apply_state_update "$PHASE"
246
- case "$PHASE" in
247
- open)
248
- new_phase=$(grep "^phase:" "$CHANGE_DIR/.comet.yaml" | sed 's/^phase: *//' | tr -d '"' | tr -d "'")
249
- green " [APPLY] .comet.yaml updated: phase=$new_phase"
250
- ;;
251
- design) green " [APPLY] .comet.yaml updated: phase=build" ;;
252
- build) green " [APPLY] .comet.yaml updated: phase=verify, verify_result=pending" ;;
253
- verify) green " [APPLY] .comet.yaml updated: phase=archive, verify_result=pass" ;;
254
- esac
255
- fi
256
- exit 0
257
- fi
1
+ #!/bin/bash
2
+ # Comet Phase Guard — validates exit conditions before phase transitions
3
+ # Usage: comet-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/.comet.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 ".comet.yaml" "comet.yaml" ".comet.yml" "comet.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/.comet.yaml" ]; then
166
+ red "FATAL: .comet.yaml not found in $CHANGE_DIR"
167
+ exit 1
168
+ fi
169
+
170
+ # Schema validation
171
+ local validate_script
172
+ validate_script="$SCRIPT_DIR/comet-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: .comet.yaml schema validation failed"
177
+ exit 1
178
+ fi
179
+ fi
180
+ }
181
+
182
+ build_passes() {
183
+ if [ "${COMET_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 [ "${COMET_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 \"\$COMET_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 \"\$COMET_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 .comet.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/comet-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: comet-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/.comet.yaml" | sed 's/^phase: *//' | tr -d '"' | tr -d "'")
399
+ green " [APPLY] .comet.yaml updated: phase=$new_phase"
400
+ ;;
401
+ design) green " [APPLY] .comet.yaml updated: phase=build" ;;
402
+ build) green " [APPLY] .comet.yaml updated: phase=verify, verify_result=pending" ;;
403
+ verify) green " [APPLY] .comet.yaml updated: phase=archive, verify_result=pass" ;;
404
+ esac
405
+ fi
406
+ exit 0
407
+ fi