@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,680 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# OpenSuper State — unified interface for .opensuper.yaml state management
|
|
3
|
+
# Usage: opensuper-state.sh <subcommand> <change-name> [args...]
|
|
4
|
+
#
|
|
5
|
+
# Subcommands:
|
|
6
|
+
# init <change-name> <workflow> — Initialize .opensuper.yaml with workflow defaults
|
|
7
|
+
# get <change-name> <field> — Read a field value from .opensuper.yaml
|
|
8
|
+
# set <change-name> <field> <val> — Update a field value
|
|
9
|
+
# transition <change-name> <event> — Apply a validated state transition
|
|
10
|
+
# check <change-name> <phase> — Verify entry requirements for a phase
|
|
11
|
+
# scale <change-name> — Assess and set verification mode based on metrics
|
|
12
|
+
#
|
|
13
|
+
# Workflows: full, hotfix, tweak
|
|
14
|
+
# Phases for check: open, design, build, verify, archive
|
|
15
|
+
|
|
16
|
+
set -euo pipefail
|
|
17
|
+
|
|
18
|
+
# --- Color output helpers ---
|
|
19
|
+
|
|
20
|
+
red() { echo -e "\033[31m$1\033[0m" >&2; }
|
|
21
|
+
green() { echo -e "\033[32m$1\033[0m" >&2; }
|
|
22
|
+
yellow() { echo -e "\033[33m$1\033[0m" >&2; }
|
|
23
|
+
|
|
24
|
+
# --- Script location ---
|
|
25
|
+
|
|
26
|
+
# shellcheck disable=SC2034
|
|
27
|
+
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
28
|
+
|
|
29
|
+
# --- Input validation ---
|
|
30
|
+
|
|
31
|
+
validate_change_name() {
|
|
32
|
+
local name="$1"
|
|
33
|
+
# Reject empty names
|
|
34
|
+
if [ -z "$name" ]; then
|
|
35
|
+
red "ERROR: Change name cannot be empty" >&2
|
|
36
|
+
exit 1
|
|
37
|
+
fi
|
|
38
|
+
# Only allow alphanumeric, hyphens, and underscores
|
|
39
|
+
if [[ ! "$name" =~ ^[a-zA-Z0-9_-]+$ ]]; then
|
|
40
|
+
red "ERROR: Invalid change name: '$name'" >&2
|
|
41
|
+
red "Valid characters: a-z, A-Z, 0-9, -, _" >&2
|
|
42
|
+
exit 1
|
|
43
|
+
fi
|
|
44
|
+
# Reject path traversal attempts
|
|
45
|
+
if [[ "$name" =~ \.\. ]]; then
|
|
46
|
+
red "ERROR: Change name cannot contain '..' (path traversal not allowed)" >&2
|
|
47
|
+
exit 1
|
|
48
|
+
fi
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
validate_enum() {
|
|
52
|
+
local value="$1"
|
|
53
|
+
shift
|
|
54
|
+
local valid_values=("$@")
|
|
55
|
+
|
|
56
|
+
for valid in "${valid_values[@]}"; do
|
|
57
|
+
if [ "$value" = "$valid" ]; then
|
|
58
|
+
return 0
|
|
59
|
+
fi
|
|
60
|
+
done
|
|
61
|
+
|
|
62
|
+
red "ERROR: Invalid value: '$value'" >&2
|
|
63
|
+
red "Valid values: ${valid_values[*]}" >&2
|
|
64
|
+
exit 1
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
# --- Helper functions ---
|
|
68
|
+
|
|
69
|
+
yaml_field() {
|
|
70
|
+
local field="$1"
|
|
71
|
+
local yaml_file="$2"
|
|
72
|
+
if [ -f "$yaml_file" ]; then
|
|
73
|
+
local value
|
|
74
|
+
value=$(grep "^${field}:" "$yaml_file" 2>/dev/null | sed "s/^${field}: *//" || true)
|
|
75
|
+
strip_wrapping_quotes "$value"
|
|
76
|
+
fi
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
strip_wrapping_quotes() {
|
|
80
|
+
local value="$1"
|
|
81
|
+
case "$value" in
|
|
82
|
+
\"*\")
|
|
83
|
+
printf '%s\n' "${value:1:${#value}-2}"
|
|
84
|
+
;;
|
|
85
|
+
\'*\')
|
|
86
|
+
printf '%s\n' "${value:1:${#value}-2}"
|
|
87
|
+
;;
|
|
88
|
+
*)
|
|
89
|
+
printf '%s\n' "$value"
|
|
90
|
+
;;
|
|
91
|
+
esac
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
replace_yaml_field() {
|
|
95
|
+
local yaml_file="$1"
|
|
96
|
+
local field="$2"
|
|
97
|
+
local value="$3"
|
|
98
|
+
local tmp_file
|
|
99
|
+
|
|
100
|
+
tmp_file=$(mktemp)
|
|
101
|
+
awk -v field="$field" -v value="$value" '
|
|
102
|
+
index($0, field ":") == 1 { print field ": " value; next }
|
|
103
|
+
{ print }
|
|
104
|
+
' "$yaml_file" > "$tmp_file"
|
|
105
|
+
mv "$tmp_file" "$yaml_file"
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
file_nonempty() {
|
|
109
|
+
[ -f "$1" ] && [ -s "$1" ]
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
change_dir_for() {
|
|
113
|
+
local change_name="$1"
|
|
114
|
+
if [ -d "openspec/changes/$change_name" ]; then
|
|
115
|
+
echo "openspec/changes/$change_name"
|
|
116
|
+
elif [ -d "openspec/changes/archive/$change_name" ]; then
|
|
117
|
+
echo "openspec/changes/archive/$change_name"
|
|
118
|
+
else
|
|
119
|
+
echo "openspec/changes/$change_name"
|
|
120
|
+
fi
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
yaml_file_for() {
|
|
124
|
+
local change_name="$1"
|
|
125
|
+
local change_dir
|
|
126
|
+
change_dir=$(change_dir_for "$change_name")
|
|
127
|
+
echo "$change_dir/.opensuper.yaml"
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
# --- Subcommands ---
|
|
131
|
+
|
|
132
|
+
cmd_init() {
|
|
133
|
+
local change_name="$1"
|
|
134
|
+
local workflow="$2"
|
|
135
|
+
|
|
136
|
+
validate_change_name "$change_name"
|
|
137
|
+
validate_enum "$workflow" "full" "hotfix" "tweak"
|
|
138
|
+
|
|
139
|
+
local change_dir yaml_file
|
|
140
|
+
change_dir=$(change_dir_for "$change_name")
|
|
141
|
+
yaml_file=$(yaml_file_for "$change_name")
|
|
142
|
+
|
|
143
|
+
# Check if .opensuper.yaml already exists
|
|
144
|
+
if [ -f "$yaml_file" ]; then
|
|
145
|
+
red "ERROR: .opensuper.yaml already exists at $yaml_file"
|
|
146
|
+
exit 1
|
|
147
|
+
fi
|
|
148
|
+
|
|
149
|
+
# Create change directory if it doesn't exist
|
|
150
|
+
mkdir -p "$change_dir"
|
|
151
|
+
|
|
152
|
+
# Set workflow-appropriate defaults
|
|
153
|
+
local phase build_mode isolation verify_mode
|
|
154
|
+
phase="open"
|
|
155
|
+
|
|
156
|
+
case "$workflow" in
|
|
157
|
+
full)
|
|
158
|
+
build_mode="null"
|
|
159
|
+
isolation="null"
|
|
160
|
+
verify_mode="null"
|
|
161
|
+
;;
|
|
162
|
+
hotfix|tweak)
|
|
163
|
+
build_mode="direct"
|
|
164
|
+
isolation="branch"
|
|
165
|
+
verify_mode="light"
|
|
166
|
+
;;
|
|
167
|
+
esac
|
|
168
|
+
|
|
169
|
+
# Write .opensuper.yaml
|
|
170
|
+
cat > "$yaml_file" <<EOF
|
|
171
|
+
workflow: $workflow
|
|
172
|
+
phase: $phase
|
|
173
|
+
build_mode: $build_mode
|
|
174
|
+
isolation: $isolation
|
|
175
|
+
verify_mode: $verify_mode
|
|
176
|
+
design_doc: null
|
|
177
|
+
plan: null
|
|
178
|
+
verify_result: pending
|
|
179
|
+
verification_report: null
|
|
180
|
+
branch_status: pending
|
|
181
|
+
verified_at: null
|
|
182
|
+
archived: false
|
|
183
|
+
EOF
|
|
184
|
+
|
|
185
|
+
green "Initialized: $yaml_file (workflow=$workflow)"
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
cmd_get() {
|
|
189
|
+
local change_name="$1"
|
|
190
|
+
local field="$2"
|
|
191
|
+
|
|
192
|
+
validate_change_name "$change_name"
|
|
193
|
+
|
|
194
|
+
local yaml_file
|
|
195
|
+
yaml_file=$(yaml_file_for "$change_name")
|
|
196
|
+
|
|
197
|
+
# Check if .opensuper.yaml exists
|
|
198
|
+
if [ ! -f "$yaml_file" ]; then
|
|
199
|
+
red "ERROR: .opensuper.yaml not found at $yaml_file"
|
|
200
|
+
exit 1
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
# Read and output the field value
|
|
204
|
+
local value
|
|
205
|
+
value=$(yaml_field "$field" "$yaml_file")
|
|
206
|
+
echo "${value:-}"
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
cmd_set() {
|
|
210
|
+
local change_name="$1"
|
|
211
|
+
local field="$2"
|
|
212
|
+
local value="$3"
|
|
213
|
+
|
|
214
|
+
validate_change_name "$change_name"
|
|
215
|
+
|
|
216
|
+
local yaml_file
|
|
217
|
+
yaml_file=$(yaml_file_for "$change_name")
|
|
218
|
+
|
|
219
|
+
# Check if .opensuper.yaml exists
|
|
220
|
+
if [ ! -f "$yaml_file" ]; then
|
|
221
|
+
red "ERROR: .opensuper.yaml not found at $yaml_file"
|
|
222
|
+
exit 1
|
|
223
|
+
fi
|
|
224
|
+
|
|
225
|
+
# Validate field name
|
|
226
|
+
case "$field" in
|
|
227
|
+
workflow|phase|build_mode|isolation|verify_mode|verify_result|verification_report|branch_status|archived|design_doc|plan|verified_at|direct_override|build_command|verify_command)
|
|
228
|
+
# Valid field
|
|
229
|
+
;;
|
|
230
|
+
*)
|
|
231
|
+
red "ERROR: Unknown field: '$field'" >&2
|
|
232
|
+
red "Valid fields: workflow, phase, design_doc, plan, build_mode, isolation, verify_mode, verify_result, verification_report, branch_status, verified_at, archived, direct_override, build_command, verify_command" >&2
|
|
233
|
+
exit 1
|
|
234
|
+
;;
|
|
235
|
+
esac
|
|
236
|
+
|
|
237
|
+
# Validate enum values
|
|
238
|
+
case "$field" in
|
|
239
|
+
workflow)
|
|
240
|
+
validate_enum "$value" "full" "hotfix" "tweak"
|
|
241
|
+
;;
|
|
242
|
+
phase)
|
|
243
|
+
validate_enum "$value" "open" "design" "build" "verify" "archive"
|
|
244
|
+
;;
|
|
245
|
+
build_mode)
|
|
246
|
+
validate_enum "$value" "subagent-driven-development" "executing-plans" "direct"
|
|
247
|
+
;;
|
|
248
|
+
isolation)
|
|
249
|
+
validate_enum "$value" "branch" "worktree"
|
|
250
|
+
;;
|
|
251
|
+
verify_mode)
|
|
252
|
+
validate_enum "$value" "light" "full"
|
|
253
|
+
;;
|
|
254
|
+
verify_result)
|
|
255
|
+
validate_enum "$value" "pending" "pass" "fail"
|
|
256
|
+
;;
|
|
257
|
+
branch_status)
|
|
258
|
+
validate_enum "$value" "pending" "handled"
|
|
259
|
+
;;
|
|
260
|
+
archived)
|
|
261
|
+
validate_enum "$value" "true" "false"
|
|
262
|
+
;;
|
|
263
|
+
direct_override)
|
|
264
|
+
validate_enum "$value" "true" "false"
|
|
265
|
+
;;
|
|
266
|
+
design_doc|plan|verification_report|verified_at|build_command|verify_command)
|
|
267
|
+
# No validation for path fields, date fields, or project command strings
|
|
268
|
+
;;
|
|
269
|
+
esac
|
|
270
|
+
|
|
271
|
+
# Write or update the field
|
|
272
|
+
if grep -q "^${field}:" "$yaml_file"; then
|
|
273
|
+
replace_yaml_field "$yaml_file" "$field" "$value"
|
|
274
|
+
else
|
|
275
|
+
# Field doesn't exist, append it
|
|
276
|
+
echo "${field}: ${value}" >> "$yaml_file"
|
|
277
|
+
fi
|
|
278
|
+
|
|
279
|
+
green "[SET] ${field}=${value}"
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
require_phase() {
|
|
283
|
+
local change_name="$1"
|
|
284
|
+
local expected="$2"
|
|
285
|
+
local actual
|
|
286
|
+
actual=$(cmd_get "$change_name" "phase")
|
|
287
|
+
if [ "$actual" != "$expected" ]; then
|
|
288
|
+
red "ERROR: Cannot transition '$change_name': expected phase ${expected}, got ${actual}" >&2
|
|
289
|
+
exit 1
|
|
290
|
+
fi
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
require_verification_evidence() {
|
|
294
|
+
local change_name="$1"
|
|
295
|
+
local report branch_status
|
|
296
|
+
report=$(cmd_get "$change_name" "verification_report")
|
|
297
|
+
branch_status=$(cmd_get "$change_name" "branch_status")
|
|
298
|
+
|
|
299
|
+
if [ -z "$report" ] || [ "$report" = "null" ] || [ ! -f "$report" ]; then
|
|
300
|
+
red "ERROR: Cannot transition '$change_name': verification_report must point to an existing report file" >&2
|
|
301
|
+
exit 1
|
|
302
|
+
fi
|
|
303
|
+
|
|
304
|
+
if [ "$branch_status" != "handled" ]; then
|
|
305
|
+
red "ERROR: Cannot transition '$change_name': branch_status must be handled" >&2
|
|
306
|
+
exit 1
|
|
307
|
+
fi
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
require_build_decisions() {
|
|
311
|
+
local change_name="$1"
|
|
312
|
+
local workflow build_mode isolation direct_override
|
|
313
|
+
workflow=$(cmd_get "$change_name" "workflow")
|
|
314
|
+
build_mode=$(cmd_get "$change_name" "build_mode")
|
|
315
|
+
isolation=$(cmd_get "$change_name" "isolation")
|
|
316
|
+
direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true)
|
|
317
|
+
|
|
318
|
+
case "$isolation" in
|
|
319
|
+
branch|worktree) ;;
|
|
320
|
+
*)
|
|
321
|
+
red "ERROR: Cannot transition '$change_name': isolation must be branch or worktree, got '${isolation:-null}'" >&2
|
|
322
|
+
exit 1
|
|
323
|
+
;;
|
|
324
|
+
esac
|
|
325
|
+
|
|
326
|
+
case "$build_mode" in
|
|
327
|
+
subagent-driven-development|executing-plans|direct) ;;
|
|
328
|
+
*)
|
|
329
|
+
red "ERROR: Cannot transition '$change_name': build_mode must be selected before leaving build, got '${build_mode:-null}'" >&2
|
|
330
|
+
exit 1
|
|
331
|
+
;;
|
|
332
|
+
esac
|
|
333
|
+
|
|
334
|
+
if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ] && [ "$direct_override" != "true" ]; then
|
|
335
|
+
red "ERROR: Cannot transition '$change_name': build_mode=direct is only allowed for hotfix/tweak unless direct_override=true" >&2
|
|
336
|
+
exit 1
|
|
337
|
+
fi
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
cmd_transition() {
|
|
341
|
+
local change_name="$1"
|
|
342
|
+
local event="$2"
|
|
343
|
+
|
|
344
|
+
validate_change_name "$change_name"
|
|
345
|
+
validate_enum "$event" "open-complete" "design-complete" "build-complete" "verify-pass" "verify-fail" "archived"
|
|
346
|
+
|
|
347
|
+
case "$event" in
|
|
348
|
+
open-complete)
|
|
349
|
+
require_phase "$change_name" "open"
|
|
350
|
+
local workflow
|
|
351
|
+
workflow=$(cmd_get "$change_name" "workflow")
|
|
352
|
+
if [ "$workflow" = "full" ]; then
|
|
353
|
+
cmd_set "$change_name" phase design
|
|
354
|
+
else
|
|
355
|
+
cmd_set "$change_name" phase build
|
|
356
|
+
fi
|
|
357
|
+
;;
|
|
358
|
+
design-complete)
|
|
359
|
+
require_phase "$change_name" "design"
|
|
360
|
+
cmd_set "$change_name" phase build
|
|
361
|
+
;;
|
|
362
|
+
build-complete)
|
|
363
|
+
require_phase "$change_name" "build"
|
|
364
|
+
require_build_decisions "$change_name"
|
|
365
|
+
cmd_set "$change_name" phase verify
|
|
366
|
+
cmd_set "$change_name" verify_result pending
|
|
367
|
+
cmd_set "$change_name" verification_report null
|
|
368
|
+
cmd_set "$change_name" branch_status pending
|
|
369
|
+
;;
|
|
370
|
+
verify-pass)
|
|
371
|
+
require_phase "$change_name" "verify"
|
|
372
|
+
require_verification_evidence "$change_name"
|
|
373
|
+
cmd_set "$change_name" verify_result pass
|
|
374
|
+
cmd_set "$change_name" phase archive
|
|
375
|
+
cmd_set "$change_name" verified_at "$(date +%Y-%m-%d)"
|
|
376
|
+
;;
|
|
377
|
+
verify-fail)
|
|
378
|
+
require_phase "$change_name" "verify"
|
|
379
|
+
cmd_set "$change_name" verify_result fail
|
|
380
|
+
cmd_set "$change_name" phase build
|
|
381
|
+
cmd_set "$change_name" branch_status pending
|
|
382
|
+
;;
|
|
383
|
+
archived)
|
|
384
|
+
require_phase "$change_name" "archive"
|
|
385
|
+
cmd_set "$change_name" archived true
|
|
386
|
+
;;
|
|
387
|
+
esac
|
|
388
|
+
|
|
389
|
+
green "[TRANSITION] ${event}"
|
|
390
|
+
}
|
|
391
|
+
|
|
392
|
+
# --- Check helpers for entry verification ---
|
|
393
|
+
|
|
394
|
+
CHECK_BLOCK=0
|
|
395
|
+
|
|
396
|
+
check_pass() {
|
|
397
|
+
local msg="$1"
|
|
398
|
+
echo " $(green "[PASS]") $msg"
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
check_fail() {
|
|
402
|
+
local msg="$1"
|
|
403
|
+
echo " $(red "[FAIL]") $msg"
|
|
404
|
+
CHECK_BLOCK=1
|
|
405
|
+
}
|
|
406
|
+
|
|
407
|
+
check_nonempty() {
|
|
408
|
+
local desc="$1"
|
|
409
|
+
local path="$2"
|
|
410
|
+
if file_nonempty "$path"; then
|
|
411
|
+
check_pass "$desc non-empty"
|
|
412
|
+
else
|
|
413
|
+
check_fail "$desc missing or empty"
|
|
414
|
+
fi
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
check_yaml_is() {
|
|
418
|
+
local field="$1"
|
|
419
|
+
local expected="$2"
|
|
420
|
+
local change_name="$3"
|
|
421
|
+
local actual
|
|
422
|
+
actual=$(cmd_get "$change_name" "$field")
|
|
423
|
+
if [ "$actual" = "$expected" ]; then
|
|
424
|
+
check_pass "${field}=${actual} (expected: ${expected})"
|
|
425
|
+
else
|
|
426
|
+
check_fail "${field}=${actual} (expected: ${expected})"
|
|
427
|
+
fi
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
check_yaml_empty() {
|
|
431
|
+
local field="$1"
|
|
432
|
+
local change_name="$2"
|
|
433
|
+
local value
|
|
434
|
+
value=$(cmd_get "$change_name" "$field")
|
|
435
|
+
if [ -z "$value" ] || [ "$value" = "null" ]; then
|
|
436
|
+
check_pass "${field} is empty/null"
|
|
437
|
+
else
|
|
438
|
+
check_fail "${field}=${value} (expected: empty/null)"
|
|
439
|
+
fi
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
check_file_not_exists() {
|
|
443
|
+
local desc="$1"
|
|
444
|
+
local path="$2"
|
|
445
|
+
if [ ! -f "$path" ]; then
|
|
446
|
+
check_pass "$desc does not exist"
|
|
447
|
+
else
|
|
448
|
+
check_fail "$desc exists (should not exist)"
|
|
449
|
+
fi
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
cmd_check() {
|
|
453
|
+
local change_name="$1"
|
|
454
|
+
local phase="$2"
|
|
455
|
+
|
|
456
|
+
validate_change_name "$change_name"
|
|
457
|
+
validate_enum "$phase" "open" "design" "build" "verify" "archive"
|
|
458
|
+
|
|
459
|
+
local change_dir="openspec/changes/$change_name"
|
|
460
|
+
local yaml_file="$change_dir/.opensuper.yaml"
|
|
461
|
+
local proposal_file="$change_dir/proposal.md"
|
|
462
|
+
local design_file="$change_dir/design.md"
|
|
463
|
+
local tasks_file="$change_dir/tasks.md"
|
|
464
|
+
|
|
465
|
+
echo "=== Entry Check: opensuper-${phase} ==="
|
|
466
|
+
|
|
467
|
+
# .opensuper.yaml must exist for all phases (state machine core)
|
|
468
|
+
if [ ! -f "$yaml_file" ]; then
|
|
469
|
+
red "ERROR: .opensuper.yaml not found at $yaml_file"
|
|
470
|
+
exit 1
|
|
471
|
+
fi
|
|
472
|
+
|
|
473
|
+
# Phase-specific checks
|
|
474
|
+
case "$phase" in
|
|
475
|
+
open)
|
|
476
|
+
check_pass ".opensuper.yaml exists"
|
|
477
|
+
check_yaml_is "phase" "open" "$change_name"
|
|
478
|
+
;;
|
|
479
|
+
design)
|
|
480
|
+
check_pass ".opensuper.yaml exists"
|
|
481
|
+
check_yaml_is "phase" "design" "$change_name"
|
|
482
|
+
check_yaml_is "workflow" "full" "$change_name"
|
|
483
|
+
check_yaml_empty "design_doc" "$change_name"
|
|
484
|
+
check_nonempty "proposal.md" "$proposal_file"
|
|
485
|
+
check_nonempty "design.md" "$design_file"
|
|
486
|
+
check_nonempty "tasks.md" "$tasks_file"
|
|
487
|
+
;;
|
|
488
|
+
build)
|
|
489
|
+
check_pass ".opensuper.yaml exists"
|
|
490
|
+
check_yaml_is "phase" "build" "$change_name"
|
|
491
|
+
# design_doc required for full workflow only
|
|
492
|
+
local workflow
|
|
493
|
+
workflow=$(cmd_get "$change_name" "workflow")
|
|
494
|
+
if [ "$workflow" = "full" ]; then
|
|
495
|
+
local design_doc
|
|
496
|
+
design_doc=$(cmd_get "$change_name" "design_doc")
|
|
497
|
+
if [ -n "$design_doc" ] && [ "$design_doc" != "null" ] && [ -f "$design_doc" ]; then
|
|
498
|
+
check_pass "design_doc=${design_doc} (file exists)"
|
|
499
|
+
else
|
|
500
|
+
check_fail "design_doc=${design_doc} (expected: non-null and file exists)"
|
|
501
|
+
fi
|
|
502
|
+
else
|
|
503
|
+
check_pass "workflow=${workflow} (design_doc not required)"
|
|
504
|
+
fi
|
|
505
|
+
check_nonempty "proposal.md" "$proposal_file"
|
|
506
|
+
check_nonempty "tasks.md" "$tasks_file"
|
|
507
|
+
;;
|
|
508
|
+
verify)
|
|
509
|
+
check_pass ".opensuper.yaml exists"
|
|
510
|
+
check_yaml_is "phase" "verify" "$change_name"
|
|
511
|
+
# Check verify_result is pending or null
|
|
512
|
+
local verify_result
|
|
513
|
+
verify_result=$(cmd_get "$change_name" "verify_result")
|
|
514
|
+
if [ "$verify_result" = "pending" ] || [ -z "$verify_result" ] || [ "$verify_result" = "null" ]; then
|
|
515
|
+
check_pass "verify_result=${verify_result} (expected: pending or null)"
|
|
516
|
+
else
|
|
517
|
+
check_fail "verify_result=${verify_result} (expected: pending or null)"
|
|
518
|
+
fi
|
|
519
|
+
;;
|
|
520
|
+
archive)
|
|
521
|
+
check_pass ".opensuper.yaml exists"
|
|
522
|
+
check_yaml_is "phase" "archive" "$change_name"
|
|
523
|
+
check_yaml_is "verify_result" "pass" "$change_name"
|
|
524
|
+
# Check archived is NOT true
|
|
525
|
+
local archived
|
|
526
|
+
archived=$(cmd_get "$change_name" "archived")
|
|
527
|
+
if [ "$archived" != "true" ]; then
|
|
528
|
+
check_pass "archived=${archived} (expected: not true)"
|
|
529
|
+
else
|
|
530
|
+
check_fail "archived=${archived} (expected: not true)"
|
|
531
|
+
fi
|
|
532
|
+
;;
|
|
533
|
+
*)
|
|
534
|
+
red "ERROR: Unknown phase for check: $phase"
|
|
535
|
+
exit 1
|
|
536
|
+
;;
|
|
537
|
+
esac
|
|
538
|
+
|
|
539
|
+
echo ""
|
|
540
|
+
if [ "$CHECK_BLOCK" -eq 1 ]; then
|
|
541
|
+
red "BLOCKED — fix failing checks before proceeding"
|
|
542
|
+
exit 1
|
|
543
|
+
else
|
|
544
|
+
green "ALL CHECKS PASSED — ready to proceed"
|
|
545
|
+
exit 0
|
|
546
|
+
fi
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
cmd_scale() {
|
|
550
|
+
local change_name="$1"
|
|
551
|
+
|
|
552
|
+
validate_change_name "$change_name"
|
|
553
|
+
|
|
554
|
+
local change_dir="openspec/changes/$change_name"
|
|
555
|
+
local yaml_file="$change_dir/.opensuper.yaml"
|
|
556
|
+
|
|
557
|
+
# Verify .opensuper.yaml exists
|
|
558
|
+
if [ ! -f "$yaml_file" ]; then
|
|
559
|
+
red "ERROR: .opensuper.yaml not found at $yaml_file"
|
|
560
|
+
exit 1
|
|
561
|
+
fi
|
|
562
|
+
|
|
563
|
+
# Read metrics
|
|
564
|
+
# 1. Task count: count lines matching `- [` in tasks.md
|
|
565
|
+
local tasks_file="$change_dir/tasks.md"
|
|
566
|
+
local task_count=0
|
|
567
|
+
if [ -f "$tasks_file" ]; then
|
|
568
|
+
task_count=$(grep -c '^\- \[' "$tasks_file" 2>/dev/null || echo "0")
|
|
569
|
+
fi
|
|
570
|
+
|
|
571
|
+
# 2. Delta spec count: count files named spec.md under specs/*/spec.md
|
|
572
|
+
local delta_spec_count=0
|
|
573
|
+
if [ -d "$change_dir/specs" ]; then
|
|
574
|
+
delta_spec_count=$(find "$change_dir/specs" -name "spec.md" -type f 2>/dev/null | wc -l | tr -d ' ')
|
|
575
|
+
fi
|
|
576
|
+
|
|
577
|
+
# 3. Changed files: prefer plan base-ref, fall back to worktree diff
|
|
578
|
+
local changed_files=0
|
|
579
|
+
if git rev-parse --git-dir > /dev/null 2>&1; then
|
|
580
|
+
local plan_file base_ref
|
|
581
|
+
plan_file=$(cmd_get "$change_name" "plan" 2>/dev/null || true)
|
|
582
|
+
if [ -n "$plan_file" ] && [ "$plan_file" != "null" ] && [ -f "$plan_file" ]; then
|
|
583
|
+
base_ref=$(grep '^base-ref:' "$plan_file" 2>/dev/null | head -1 | sed 's/^base-ref: *//')
|
|
584
|
+
fi
|
|
585
|
+
|
|
586
|
+
if [ -n "${base_ref:-}" ] && git rev-parse --verify "$base_ref" >/dev/null 2>&1; then
|
|
587
|
+
changed_files=$(git diff --name-only "$base_ref"...HEAD 2>/dev/null | wc -l | tr -d ' ')
|
|
588
|
+
else
|
|
589
|
+
changed_files=$(git diff --name-only HEAD 2>/dev/null | wc -l | tr -d ' ')
|
|
590
|
+
fi
|
|
591
|
+
fi
|
|
592
|
+
|
|
593
|
+
# Decision rules
|
|
594
|
+
local result="light"
|
|
595
|
+
if [ "$task_count" -gt 3 ] || [ "$delta_spec_count" -gt 1 ] || [ "$changed_files" -gt 5 ]; then
|
|
596
|
+
result="full"
|
|
597
|
+
fi
|
|
598
|
+
|
|
599
|
+
# Output assessment to stderr
|
|
600
|
+
echo "=== Scale Assessment: $change_name ===" >&2
|
|
601
|
+
echo " Tasks: $task_count (threshold: 3)" >&2
|
|
602
|
+
echo " Delta specs: $delta_spec_count capabilities (threshold: 1)" >&2
|
|
603
|
+
echo " Changed files: $changed_files (threshold: 5)" >&2
|
|
604
|
+
echo " → Result: $result" >&2
|
|
605
|
+
|
|
606
|
+
# Update verify_mode in .opensuper.yaml
|
|
607
|
+
replace_yaml_field "$yaml_file" "verify_mode" "$result"
|
|
608
|
+
|
|
609
|
+
green "[SCALE] verify_mode=$result"
|
|
610
|
+
}
|
|
611
|
+
|
|
612
|
+
# --- Main ---
|
|
613
|
+
|
|
614
|
+
SUBCOMMAND="${1:-}"
|
|
615
|
+
shift || true
|
|
616
|
+
|
|
617
|
+
case "$SUBCOMMAND" in
|
|
618
|
+
init)
|
|
619
|
+
if [ $# -lt 2 ]; then
|
|
620
|
+
red "Usage: opensuper-state.sh init <change-name> <workflow>" >&2
|
|
621
|
+
red "Workflows: full, hotfix, tweak" >&2
|
|
622
|
+
exit 1
|
|
623
|
+
fi
|
|
624
|
+
cmd_init "$@"
|
|
625
|
+
;;
|
|
626
|
+
get)
|
|
627
|
+
if [ $# -lt 2 ]; then
|
|
628
|
+
red "Usage: opensuper-state.sh get <change-name> <field>" >&2
|
|
629
|
+
exit 1
|
|
630
|
+
fi
|
|
631
|
+
cmd_get "$@"
|
|
632
|
+
;;
|
|
633
|
+
set)
|
|
634
|
+
if [ $# -lt 3 ]; then
|
|
635
|
+
red "Usage: opensuper-state.sh set <change-name> <field> <value>" >&2
|
|
636
|
+
exit 1
|
|
637
|
+
fi
|
|
638
|
+
cmd_set "$@"
|
|
639
|
+
;;
|
|
640
|
+
transition)
|
|
641
|
+
if [ $# -lt 2 ]; then
|
|
642
|
+
red "Usage: opensuper-state.sh transition <change-name> <event>" >&2
|
|
643
|
+
red "Events: open-complete, design-complete, build-complete, verify-pass, verify-fail, archived" >&2
|
|
644
|
+
exit 1
|
|
645
|
+
fi
|
|
646
|
+
cmd_transition "$@"
|
|
647
|
+
;;
|
|
648
|
+
check)
|
|
649
|
+
if [ $# -lt 2 ]; then
|
|
650
|
+
red "Usage: opensuper-state.sh check <change-name> <phase>" >&2
|
|
651
|
+
red "Phases: open, design, build, verify, archive" >&2
|
|
652
|
+
exit 1
|
|
653
|
+
fi
|
|
654
|
+
cmd_check "$@"
|
|
655
|
+
;;
|
|
656
|
+
scale)
|
|
657
|
+
if [ $# -lt 1 ]; then
|
|
658
|
+
red "Usage: opensuper-state.sh scale <change-name>" >&2
|
|
659
|
+
exit 1
|
|
660
|
+
fi
|
|
661
|
+
cmd_scale "$@"
|
|
662
|
+
;;
|
|
663
|
+
*)
|
|
664
|
+
red "Unknown subcommand: $SUBCOMMAND" >&2
|
|
665
|
+
echo "" >&2
|
|
666
|
+
echo "Usage: opensuper-state.sh <subcommand> <change-name> [args...]" >&2
|
|
667
|
+
echo "" >&2
|
|
668
|
+
echo "Subcommands:" >&2
|
|
669
|
+
echo " init <change-name> <workflow> — Initialize .opensuper.yaml with workflow defaults" >&2
|
|
670
|
+
echo " get <change-name> <field> — Read a field value from .opensuper.yaml" >&2
|
|
671
|
+
echo " set <change-name> <field> <val> — Update a field value in .opensuper.yaml" >&2
|
|
672
|
+
echo " transition <change-name> <event> — Apply a validated state transition" >&2
|
|
673
|
+
echo " check <change-name> <phase> — Verify entry requirements for a phase" >&2
|
|
674
|
+
echo " scale <change-name> — Assess and set verification mode based on metrics" >&2
|
|
675
|
+
echo "" >&2
|
|
676
|
+
echo "Workflows: full, hotfix, tweak" >&2
|
|
677
|
+
echo "Phases for check: open, design, build, verify, archive" >&2
|
|
678
|
+
exit 1
|
|
679
|
+
;;
|
|
680
|
+
esac
|