@rpamis/comet 0.2.0 → 0.2.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.
|
@@ -1,253 +1,255 @@
|
|
|
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
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
red "
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
local
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
actual_phase
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
script_dir
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
check "
|
|
141
|
-
check "
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
check "
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
check "
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
check "
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
bash "$state_sh" set "$CHANGE"
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
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
|
+
if [[ "${3:-}" == "--apply" ]]; then
|
|
41
|
+
APPLY=1
|
|
42
|
+
fi
|
|
43
|
+
CHANGE_DIR="openspec/changes/$CHANGE"
|
|
44
|
+
|
|
45
|
+
BLOCK=0
|
|
46
|
+
check() {
|
|
47
|
+
local desc="$1"
|
|
48
|
+
shift
|
|
49
|
+
if "$@" 2>/dev/null; then
|
|
50
|
+
green " [PASS] $desc"
|
|
51
|
+
else
|
|
52
|
+
red " [FAIL] $desc"
|
|
53
|
+
BLOCK=1
|
|
54
|
+
fi
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
# --- Helper functions ---
|
|
58
|
+
|
|
59
|
+
tasks_all_done() {
|
|
60
|
+
local tasks="$CHANGE_DIR/tasks.md"
|
|
61
|
+
[ -f "$tasks" ] || return 1
|
|
62
|
+
grep -q '\- \[x\]' "$tasks" || return 1
|
|
63
|
+
! grep -q '\- \[ \]' "$tasks"
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
tasks_has_any() {
|
|
67
|
+
local tasks="$CHANGE_DIR/tasks.md"
|
|
68
|
+
[ -f "$tasks" ] && grep -q '\- \[' "$tasks"
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
yaml_field_value() {
|
|
72
|
+
local field="$1"
|
|
73
|
+
local yaml="$CHANGE_DIR/.comet.yaml"
|
|
74
|
+
if [ -f "$yaml" ]; then
|
|
75
|
+
grep "^${field}:" "$yaml" | sed "s/^${field}: *//" | tr -d '"' | tr -d "'"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
file_nonempty() {
|
|
80
|
+
[ -f "$1" ] && [ -s "$1" ]
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
preflight() {
|
|
84
|
+
local expected_phase="$1"
|
|
85
|
+
|
|
86
|
+
if [ ! -d "$CHANGE_DIR" ]; then
|
|
87
|
+
red "FATAL: change directory not found: $CHANGE_DIR"
|
|
88
|
+
exit 1
|
|
89
|
+
fi
|
|
90
|
+
if [ ! -f "$CHANGE_DIR/.comet.yaml" ]; then
|
|
91
|
+
red "FATAL: .comet.yaml not found in $CHANGE_DIR"
|
|
92
|
+
exit 1
|
|
93
|
+
fi
|
|
94
|
+
|
|
95
|
+
local actual_phase
|
|
96
|
+
actual_phase=$(yaml_field_value "phase" 2>/dev/null || true)
|
|
97
|
+
if [ "$actual_phase" != "$expected_phase" ]; then
|
|
98
|
+
red "FATAL: .comet.yaml phase is '$actual_phase', expected '$expected_phase'"
|
|
99
|
+
exit 1
|
|
100
|
+
fi
|
|
101
|
+
|
|
102
|
+
# Schema validation
|
|
103
|
+
local script_dir validate_script
|
|
104
|
+
script_dir="$(dirname "$(readlink -f "$0" 2>/dev/null || echo "$0")" 2>/dev/null || dirname "$0")"
|
|
105
|
+
validate_script="$script_dir/comet-yaml-validate.sh"
|
|
106
|
+
if [ -f "$validate_script" ]; then
|
|
107
|
+
if ! bash "$validate_script" "$CHANGE" 2>/dev/null; then
|
|
108
|
+
bash "$validate_script" "$CHANGE"
|
|
109
|
+
red "FATAL: .comet.yaml schema validation failed"
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
fi
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
build_passes() {
|
|
116
|
+
if [ "${COMET_SKIP_BUILD:-0}" = "1" ]; then
|
|
117
|
+
return 0
|
|
118
|
+
fi
|
|
119
|
+
# Attempt common build commands; succeeds if any pass
|
|
120
|
+
(npm run build 2>/dev/null) || (mvn compile -q 2>/dev/null) || (cargo build 2>/dev/null) || true
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
verify_result_is_pass() {
|
|
124
|
+
local result
|
|
125
|
+
result=$(yaml_field_value "verify_result" 2>/dev/null || true)
|
|
126
|
+
[ "$result" = "pass" ]
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
archived_is_true() {
|
|
130
|
+
local val
|
|
131
|
+
val=$(yaml_field_value "archived" 2>/dev/null || true)
|
|
132
|
+
[ "$val" = "true" ]
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
# --- Phase-specific checks ---
|
|
136
|
+
|
|
137
|
+
guard_open() {
|
|
138
|
+
echo "=== Guard: open → design ===" >&2
|
|
139
|
+
|
|
140
|
+
check "proposal.md exists and non-empty" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
141
|
+
check "design.md exists and non-empty" file_nonempty "$CHANGE_DIR/design.md"
|
|
142
|
+
check "tasks.md exists and non-empty" file_nonempty "$CHANGE_DIR/tasks.md"
|
|
143
|
+
check "tasks.md has at least one task" tasks_has_any
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
guard_design() {
|
|
147
|
+
echo "=== Guard: design → build ===" >&2
|
|
148
|
+
|
|
149
|
+
local design_doc
|
|
150
|
+
design_doc=$(yaml_field_value "design_doc" 2>/dev/null || true)
|
|
151
|
+
|
|
152
|
+
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
153
|
+
check "tasks.md exists" file_nonempty "$CHANGE_DIR/tasks.md"
|
|
154
|
+
|
|
155
|
+
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ]; then
|
|
156
|
+
check "Design Doc ($design_doc) exists" file_nonempty "$design_doc"
|
|
157
|
+
else
|
|
158
|
+
warn " [WARN] No design_doc recorded in .comet.yaml"
|
|
159
|
+
fi
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
guard_build() {
|
|
163
|
+
echo "=== Guard: build → verify ===" >&2
|
|
164
|
+
|
|
165
|
+
check "tasks.md all tasks checked" tasks_all_done
|
|
166
|
+
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
167
|
+
check "Build passes" build_passes
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
guard_verify() {
|
|
171
|
+
echo "=== Guard: verify → archive ===" >&2
|
|
172
|
+
|
|
173
|
+
check "verify_result is pass" verify_result_is_pass
|
|
174
|
+
check "tasks.md all tasks checked" tasks_all_done
|
|
175
|
+
check "Build passes" build_passes
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
guard_archive() {
|
|
179
|
+
echo "=== Guard: archive completeness ===" >&2
|
|
180
|
+
|
|
181
|
+
check "archived is true" archived_is_true
|
|
182
|
+
check "proposal.md exists" file_nonempty "$CHANGE_DIR/proposal.md"
|
|
183
|
+
check "tasks.md all tasks checked" tasks_all_done
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
apply_state_update() {
|
|
187
|
+
local state_sh="$script_dir/comet-state.sh"
|
|
188
|
+
local p="$1"
|
|
189
|
+
|
|
190
|
+
if [ -f "$state_sh" ]; then
|
|
191
|
+
case "$p" in
|
|
192
|
+
open) bash "$state_sh" set "$CHANGE" phase design ;;
|
|
193
|
+
design) bash "$state_sh" set "$CHANGE" phase build ;;
|
|
194
|
+
build)
|
|
195
|
+
bash "$state_sh" set "$CHANGE" phase verify
|
|
196
|
+
bash "$state_sh" set "$CHANGE" verify_result pending
|
|
197
|
+
;;
|
|
198
|
+
verify)
|
|
199
|
+
bash "$state_sh" set "$CHANGE" phase archive
|
|
200
|
+
bash "$state_sh" set "$CHANGE" verify_result pass
|
|
201
|
+
bash "$state_sh" set "$CHANGE" verified_at "$(date +%Y-%m-%d)"
|
|
202
|
+
;;
|
|
203
|
+
esac
|
|
204
|
+
else
|
|
205
|
+
local yaml="$CHANGE_DIR/.comet.yaml"
|
|
206
|
+
case "$p" in
|
|
207
|
+
open) sed -i 's/^phase:.*/phase: design/' "$yaml" ;;
|
|
208
|
+
design) sed -i 's/^phase:.*/phase: build/' "$yaml" ;;
|
|
209
|
+
build) sed -i 's/^phase:.*/phase: verify/' "$yaml"; sed -i 's/^verify_result:.*/verify_result: pending/' "$yaml" ;;
|
|
210
|
+
verify)
|
|
211
|
+
sed -i 's/^phase:.*/phase: archive/' "$yaml"
|
|
212
|
+
sed -i 's/^verify_result:.*/verify_result: pass/' "$yaml"
|
|
213
|
+
if ! grep -q '^verified_at:' "$yaml" 2>/dev/null; then
|
|
214
|
+
echo "verified_at: $(date +%Y-%m-%d)" >> "$yaml"
|
|
215
|
+
else
|
|
216
|
+
sed -i "s/^verified_at:.*/verified_at: $(date +%Y-%m-%d)/" "$yaml"
|
|
217
|
+
fi
|
|
218
|
+
;;
|
|
219
|
+
esac
|
|
220
|
+
fi
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
# --- Main ---
|
|
224
|
+
|
|
225
|
+
case "$PHASE" in
|
|
226
|
+
open) preflight "design" ; guard_open ;;
|
|
227
|
+
design) preflight "build" ; guard_design ;;
|
|
228
|
+
build) preflight "verify" ; guard_build ;;
|
|
229
|
+
verify) preflight "archive" ; guard_verify ;;
|
|
230
|
+
archive) preflight "archive" ; guard_archive ;;
|
|
231
|
+
*)
|
|
232
|
+
red "Unknown phase: $PHASE"
|
|
233
|
+
echo "Valid phases: open, design, build, verify, archive" >&2
|
|
234
|
+
exit 1
|
|
235
|
+
;;
|
|
236
|
+
esac
|
|
237
|
+
|
|
238
|
+
if [ "$BLOCK" -eq 1 ]; then
|
|
239
|
+
echo "" >&2
|
|
240
|
+
red "BLOCKED — fix failing checks before proceeding to next phase"
|
|
241
|
+
exit 1
|
|
242
|
+
else
|
|
243
|
+
echo "" >&2
|
|
244
|
+
green "ALL CHECKS PASSED — ready for next phase"
|
|
245
|
+
if [ "$APPLY" -eq 1 ]; then
|
|
246
|
+
apply_state_update "$PHASE"
|
|
247
|
+
case "$PHASE" in
|
|
248
|
+
open) green " [APPLY] .comet.yaml updated: phase=design" ;;
|
|
249
|
+
design) green " [APPLY] .comet.yaml updated: phase=build" ;;
|
|
250
|
+
build) green " [APPLY] .comet.yaml updated: phase=verify, verify_result=pending" ;;
|
|
251
|
+
verify) green " [APPLY] .comet.yaml updated: phase=archive, verify_result=pass" ;;
|
|
252
|
+
esac
|
|
253
|
+
fi
|
|
254
|
+
exit 0
|
|
255
|
+
fi
|
|
@@ -22,6 +22,7 @@ yellow() { echo -e "\033[33m$1\033[0m" >&2; }
|
|
|
22
22
|
|
|
23
23
|
# --- Script location ---
|
|
24
24
|
|
|
25
|
+
# shellcheck disable=SC2034
|
|
25
26
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
26
27
|
|
|
27
28
|
# --- Input validation ---
|
|
@@ -211,8 +212,8 @@ cmd_set() {
|
|
|
211
212
|
|
|
212
213
|
# Write or update the field
|
|
213
214
|
if grep -q "^${field}:" "$yaml_file"; then
|
|
214
|
-
# Field exists, replace it
|
|
215
|
-
sed -i "s
|
|
215
|
+
# Field exists, replace it (use | delimiter to avoid / conflicts in paths)
|
|
216
|
+
sed -i "s|^${field}:.*|${field}: ${value}|" "$yaml_file"
|
|
216
217
|
else
|
|
217
218
|
# Field doesn't exist, append it
|
|
218
219
|
echo "${field}: ${value}" >> "$yaml_file"
|
|
@@ -238,7 +239,7 @@ check_fail() {
|
|
|
238
239
|
|
|
239
240
|
check_nonempty() {
|
|
240
241
|
local desc="$1"
|
|
241
|
-
local path="$
|
|
242
|
+
local path="$2"
|
|
242
243
|
if file_nonempty "$path"; then
|
|
243
244
|
check_pass "$desc non-empty"
|
|
244
245
|
else
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@rpamis/comet",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"description": "OpenSpec + Superpowers dual-star development workflow",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"comet",
|
|
@@ -41,8 +41,9 @@
|
|
|
41
41
|
"prepublishOnly": "node scripts/prepublish-check.js && pnpm run build",
|
|
42
42
|
"postinstall": "node scripts/postinstall.js"
|
|
43
43
|
},
|
|
44
|
+
"packageManager": "pnpm@10.18.3",
|
|
44
45
|
"engines": {
|
|
45
|
-
"node": ">=
|
|
46
|
+
"node": ">=20"
|
|
46
47
|
},
|
|
47
48
|
"devDependencies": {
|
|
48
49
|
"@eslint/js": "^10.0.1",
|