@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.
- package/LICENSE +21 -0
- package/README.md +362 -0
- package/assets/manifest.json +21 -0
- package/assets/skills/opensuper/SKILL.md +268 -0
- package/assets/skills/opensuper/scripts/opensuper-archive.sh +255 -0
- package/assets/skills/opensuper/scripts/opensuper-guard.sh +407 -0
- package/assets/skills/opensuper/scripts/opensuper-state.sh +680 -0
- package/assets/skills/opensuper/scripts/opensuper-yaml-validate.sh +157 -0
- package/assets/skills/opensuper-archive/SKILL.md +69 -0
- package/assets/skills/opensuper-build/SKILL.md +194 -0
- package/assets/skills/opensuper-design/SKILL.md +84 -0
- package/assets/skills/opensuper-hotfix/SKILL.md +146 -0
- package/assets/skills/opensuper-open/SKILL.md +82 -0
- package/assets/skills/opensuper-tweak/SKILL.md +133 -0
- package/assets/skills/opensuper-verify/SKILL.md +139 -0
- package/assets/skills-zh/opensuper/SKILL.md +271 -0
- package/assets/skills-zh/opensuper-archive/SKILL.md +69 -0
- package/assets/skills-zh/opensuper-build/SKILL.md +194 -0
- package/assets/skills-zh/opensuper-design/SKILL.md +84 -0
- package/assets/skills-zh/opensuper-hotfix/SKILL.md +152 -0
- package/assets/skills-zh/opensuper-open/SKILL.md +84 -0
- package/assets/skills-zh/opensuper-tweak/SKILL.md +139 -0
- package/assets/skills-zh/opensuper-verify/SKILL.md +154 -0
- package/bin/opensuper.js +3 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.d.ts.map +1 -0
- package/dist/cli/index.js +63 -0
- package/dist/cli/index.js.map +1 -0
- package/dist/commands/doctor.d.ts +9 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +191 -0
- package/dist/commands/doctor.js.map +1 -0
- package/dist/commands/init.d.ts +17 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +242 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/status.d.ts +6 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +108 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/update.d.ts +28 -0
- package/dist/commands/update.d.ts.map +1 -0
- package/dist/commands/update.js +193 -0
- package/dist/commands/update.js.map +1 -0
- package/dist/core/detect.d.ts +13 -0
- package/dist/core/detect.d.ts.map +1 -0
- package/dist/core/detect.js +101 -0
- package/dist/core/detect.js.map +1 -0
- package/dist/core/openspec.d.ts +5 -0
- package/dist/core/openspec.d.ts.map +1 -0
- package/dist/core/openspec.js +58 -0
- package/dist/core/openspec.js.map +1 -0
- package/dist/core/platforms.d.ts +15 -0
- package/dist/core/platforms.d.ts.map +1 -0
- package/dist/core/platforms.js +48 -0
- package/dist/core/platforms.js.map +1 -0
- package/dist/core/skills.d.ts +22 -0
- package/dist/core/skills.d.ts.map +1 -0
- package/dist/core/skills.js +59 -0
- package/dist/core/skills.js.map +1 -0
- package/dist/core/superpowers.d.ts +5 -0
- package/dist/core/superpowers.d.ts.map +1 -0
- package/dist/core/superpowers.js +60 -0
- package/dist/core/superpowers.js.map +1 -0
- package/dist/core/types.d.ts +2 -0
- package/dist/core/types.d.ts.map +1 -0
- package/dist/core/types.js +2 -0
- package/dist/core/types.js.map +1 -0
- package/dist/utils/file-system.d.ts +25 -0
- package/dist/utils/file-system.d.ts.map +1 -0
- package/dist/utils/file-system.js +53 -0
- package/dist/utils/file-system.js.map +1 -0
- package/package.json +60 -0
- 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
|