@kafka0102/onespec 0.1.2 → 0.2.2
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/README.md +45 -48
- package/assets/skills/onespec/SKILL.md +22 -14
- package/assets/skills/onespec/references/archive.md +214 -0
- package/assets/skills/{onespec-design/SKILL.md → onespec/references/design.md} +55 -51
- package/assets/skills/onespec/references/execute.md +291 -0
- package/assets/skills/onespec/references/fast.md +110 -0
- package/assets/skills/onespec/scripts/onespec-closeout.sh +238 -77
- package/assets/skills/onespec/scripts/onespec-commit.sh +191 -11
- package/assets/skills/onespec/scripts/onespec-handoff.sh +19 -6
- package/assets/skills/onespec/scripts/onespec-state.sh +157 -18
- package/assets/skills/onespec-fast/SKILL.md +22 -0
- package/assets/skills/onespec-fast/agents/openai.yaml +4 -0
- package/assets/skills-en/onespec/SKILL.md +22 -13
- package/assets/skills-en/onespec/references/archive.md +213 -0
- package/assets/skills-en/{onespec-design/SKILL.md → onespec/references/design.md} +58 -43
- package/assets/skills-en/onespec/references/execute.md +291 -0
- package/assets/skills-en/onespec/references/fast.md +110 -0
- package/assets/skills-en/onespec-fast/SKILL.md +22 -0
- package/package.json +10 -3
- package/scripts/postinstall.js +3 -3
- package/src/cli.js +120 -110
- package/src/doctor.js +46 -20
- package/src/init.js +24 -10
- package/src/platforms.js +88 -8
- package/src/setup.js +211 -0
- package/assets/skills/onespec-archive/SKILL.md +0 -202
- package/assets/skills/onespec-execute/SKILL.md +0 -219
- package/assets/skills-en/onespec-archive/SKILL.md +0 -199
- package/assets/skills-en/onespec-execute/SKILL.md +0 -219
|
@@ -26,7 +26,9 @@ change_dir() {
|
|
|
26
26
|
|
|
27
27
|
state_file() {
|
|
28
28
|
local change="$1"
|
|
29
|
-
|
|
29
|
+
local script_root
|
|
30
|
+
script_root="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
|
|
31
|
+
"${BASH:-bash}" "$script_root/onespec-state.sh" path "$change"
|
|
30
32
|
}
|
|
31
33
|
|
|
32
34
|
normalize_path() {
|
|
@@ -123,9 +125,12 @@ git_dirty_paths() {
|
|
|
123
125
|
|
|
124
126
|
dirty_change_artifact_paths() {
|
|
125
127
|
local change="$1"
|
|
126
|
-
local
|
|
127
|
-
|
|
128
|
-
|
|
128
|
+
local active_prefix archive_prefix
|
|
129
|
+
active_prefix="openspec/changes/$change/"
|
|
130
|
+
archive_prefix="openspec/changes/archive/$change/"
|
|
131
|
+
git_dirty_paths | awk -v active="$active_prefix" -v archived="$archive_prefix" '
|
|
132
|
+
index($0, active) == 1 || index($0, archived) == 1 { print }
|
|
133
|
+
'
|
|
129
134
|
}
|
|
130
135
|
|
|
131
136
|
repo_layout() {
|
|
@@ -152,7 +157,20 @@ find_policy_doc() {
|
|
|
152
157
|
local pattern='提交|commit message|commit messages|提交信息|提交规范|提交格式|conventional commit|conventional commits|git workflow|commitlint|commitizen'
|
|
153
158
|
local file
|
|
154
159
|
|
|
155
|
-
for file in
|
|
160
|
+
for file in \
|
|
161
|
+
AGENTS.md \
|
|
162
|
+
agents.md \
|
|
163
|
+
.agents.md \
|
|
164
|
+
CLAUDE.md \
|
|
165
|
+
claude.md \
|
|
166
|
+
.claude.md \
|
|
167
|
+
CONTRIBUTING.md \
|
|
168
|
+
CONTRIBUTING.zh-CN.md \
|
|
169
|
+
README.md \
|
|
170
|
+
README-zh.md \
|
|
171
|
+
README.en.md \
|
|
172
|
+
README.zh-CN.md
|
|
173
|
+
do
|
|
156
174
|
if [ -f "$file" ] && grep -Eiq "$pattern" "$file"; then
|
|
157
175
|
printf '%s\n' "$file"
|
|
158
176
|
return 0
|
|
@@ -222,13 +240,35 @@ detect_language_from_doc() {
|
|
|
222
240
|
detect_format_from_file() {
|
|
223
241
|
local file="$1"
|
|
224
242
|
|
|
225
|
-
if grep -Eiq '<type>\(<scope>\):|
|
|
243
|
+
if grep -Eiq '<type>\(<scope>\):|type\(scope\)' "$file"; then
|
|
244
|
+
echo "conventional-scope"
|
|
245
|
+
return 0
|
|
246
|
+
fi
|
|
247
|
+
if grep -Eiq '<type>:[[:space:]]*<|^```text$|^```$' "$file" && grep -Eiq '<type>:[[:space:]]*<' "$file"; then
|
|
248
|
+
echo "conventional"
|
|
249
|
+
return 0
|
|
250
|
+
fi
|
|
251
|
+
if grep -Eiq 'conventional commit|conventional commits|commitlint' "$file"; then
|
|
226
252
|
echo "conventional"
|
|
227
253
|
return 0
|
|
228
254
|
fi
|
|
229
255
|
echo "unknown"
|
|
230
256
|
}
|
|
231
257
|
|
|
258
|
+
scope_mode_for_format() {
|
|
259
|
+
case "$1" in
|
|
260
|
+
conventional-scope)
|
|
261
|
+
echo "required"
|
|
262
|
+
;;
|
|
263
|
+
conventional)
|
|
264
|
+
echo "optional"
|
|
265
|
+
;;
|
|
266
|
+
*)
|
|
267
|
+
echo "optional"
|
|
268
|
+
;;
|
|
269
|
+
esac
|
|
270
|
+
}
|
|
271
|
+
|
|
232
272
|
infer_scope() {
|
|
233
273
|
local change="${1:-}"
|
|
234
274
|
local tracked
|
|
@@ -281,6 +321,85 @@ infer_scope() {
|
|
|
281
321
|
rm -f "$tracked"
|
|
282
322
|
}
|
|
283
323
|
|
|
324
|
+
valid_commit_context() {
|
|
325
|
+
case "$1" in
|
|
326
|
+
closeout|archive|preserve-state) ;;
|
|
327
|
+
*)
|
|
328
|
+
die "unsupported commit context: $1"
|
|
329
|
+
;;
|
|
330
|
+
esac
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
policy_value() {
|
|
334
|
+
local policy="$1"
|
|
335
|
+
local key="$2"
|
|
336
|
+
printf '%s\n' "$policy" | awk -F ': ' -v key="$key" '$1 == key { print $2; exit }'
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
default_commit_summary() {
|
|
340
|
+
local change="$1"
|
|
341
|
+
local context="$2"
|
|
342
|
+
local language="$3"
|
|
343
|
+
|
|
344
|
+
case "$context" in
|
|
345
|
+
closeout)
|
|
346
|
+
if [ "$language" = "zh" ]; then
|
|
347
|
+
printf '提交 %s 收尾前改动\n' "$change"
|
|
348
|
+
else
|
|
349
|
+
printf 'record %s before closeout\n' "$change"
|
|
350
|
+
fi
|
|
351
|
+
;;
|
|
352
|
+
archive)
|
|
353
|
+
if [ "$language" = "zh" ]; then
|
|
354
|
+
printf '归档 %s\n' "$change"
|
|
355
|
+
else
|
|
356
|
+
printf 'archive %s\n' "$change"
|
|
357
|
+
fi
|
|
358
|
+
;;
|
|
359
|
+
preserve-state)
|
|
360
|
+
if [ "$language" = "zh" ]; then
|
|
361
|
+
printf '保存 %s 收尾状态\n' "$change"
|
|
362
|
+
else
|
|
363
|
+
printf 'preserve %s closeout state\n' "$change"
|
|
364
|
+
fi
|
|
365
|
+
;;
|
|
366
|
+
esac
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
build_commit_message() {
|
|
370
|
+
local change="$1"
|
|
371
|
+
local context="$2"
|
|
372
|
+
local policy language scope summary format scope_mode type
|
|
373
|
+
|
|
374
|
+
valid_commit_context "$context"
|
|
375
|
+
policy="$(cmd_detect_policy "$change")"
|
|
376
|
+
language="$(policy_value "$policy" message_language)"
|
|
377
|
+
scope="$(policy_value "$policy" scope_hint)"
|
|
378
|
+
format="$(policy_value "$policy" commit_format)"
|
|
379
|
+
scope_mode="$(policy_value "$policy" scope_mode)"
|
|
380
|
+
[ -n "$language" ] || language="en"
|
|
381
|
+
[ -n "$scope" ] || scope="repo"
|
|
382
|
+
[ -n "$format" ] || format="conventional-scope"
|
|
383
|
+
[ -n "$scope_mode" ] || scope_mode="optional"
|
|
384
|
+
type="chore"
|
|
385
|
+
case "$context" in
|
|
386
|
+
archive|preserve-state)
|
|
387
|
+
scope="docs"
|
|
388
|
+
;;
|
|
389
|
+
esac
|
|
390
|
+
summary="$(default_commit_summary "$change" "$context" "$language")"
|
|
391
|
+
|
|
392
|
+
if [ "$scope_mode" = "required" ]; then
|
|
393
|
+
printf '%s(%s): %s\n' "$type" "$scope" "$summary"
|
|
394
|
+
else
|
|
395
|
+
printf '%s: %s\n' "$type" "$summary"
|
|
396
|
+
fi
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
has_staged_changes() {
|
|
400
|
+
! git diff --cached --quiet --exit-code
|
|
401
|
+
}
|
|
402
|
+
|
|
284
403
|
cmd_track() {
|
|
285
404
|
local change="$1"
|
|
286
405
|
shift
|
|
@@ -363,22 +482,24 @@ cmd_stage_related() {
|
|
|
363
482
|
|
|
364
483
|
cmd_detect_policy() {
|
|
365
484
|
local change="${1:-}"
|
|
366
|
-
local layout source origin format language confidence scope
|
|
485
|
+
local layout source origin format language confidence scope scope_mode template
|
|
367
486
|
|
|
368
487
|
layout="$(repo_layout)"
|
|
369
488
|
scope="$(infer_scope "$change")"
|
|
370
489
|
confidence="default"
|
|
371
490
|
origin="default"
|
|
372
491
|
source="default"
|
|
373
|
-
format="conventional"
|
|
492
|
+
format="conventional-scope"
|
|
374
493
|
language="en"
|
|
494
|
+
scope_mode="optional"
|
|
495
|
+
template="<type>(<scope>): <summary>"
|
|
375
496
|
|
|
376
497
|
if source="$(find_policy_doc)"; then
|
|
377
498
|
origin="project-doc"
|
|
378
499
|
confidence="explicit"
|
|
379
500
|
format="$(detect_format_from_file "$source")"
|
|
380
501
|
language="$(detect_language_from_doc "$source")"
|
|
381
|
-
[ "$format" != "unknown" ] || format="conventional"
|
|
502
|
+
[ "$format" != "unknown" ] || format="conventional-scope"
|
|
382
503
|
[ "$language" != "unknown" ] || language="en"
|
|
383
504
|
else
|
|
384
505
|
local config_source
|
|
@@ -386,20 +507,74 @@ cmd_detect_policy() {
|
|
|
386
507
|
source="$config_source"
|
|
387
508
|
origin="project-config"
|
|
388
509
|
confidence="partial"
|
|
389
|
-
format="conventional"
|
|
510
|
+
format="conventional-scope"
|
|
390
511
|
language="en"
|
|
391
512
|
fi
|
|
392
513
|
fi
|
|
393
514
|
|
|
515
|
+
scope_mode="$(scope_mode_for_format "$format")"
|
|
516
|
+
case "$format" in
|
|
517
|
+
conventional-scope)
|
|
518
|
+
template="<type>(<scope>): <summary>"
|
|
519
|
+
;;
|
|
520
|
+
conventional)
|
|
521
|
+
template="<type>: <summary>"
|
|
522
|
+
;;
|
|
523
|
+
esac
|
|
524
|
+
|
|
394
525
|
cat <<EOF
|
|
395
526
|
policy_source: $source
|
|
396
527
|
policy_origin: $origin
|
|
397
528
|
policy_confidence: $confidence
|
|
398
529
|
commit_format: $format
|
|
530
|
+
scope_mode: $scope_mode
|
|
399
531
|
message_language: $language
|
|
400
532
|
repo_layout: $layout
|
|
401
533
|
scope_hint: $scope
|
|
402
|
-
template:
|
|
534
|
+
template: $template
|
|
535
|
+
EOF
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
cmd_commit_related() {
|
|
539
|
+
local change="$1"
|
|
540
|
+
local context="${2:-closeout}"
|
|
541
|
+
local related message sha
|
|
542
|
+
|
|
543
|
+
valid_change "$change"
|
|
544
|
+
valid_commit_context "$context"
|
|
545
|
+
ensure_git_repo
|
|
546
|
+
|
|
547
|
+
related="$(cmd_related_dirty "$change")"
|
|
548
|
+
if [ -z "$related" ]; then
|
|
549
|
+
cat <<EOF
|
|
550
|
+
commit_created: false
|
|
551
|
+
commit_context: $context
|
|
552
|
+
commit_sha: none
|
|
553
|
+
commit_message: none
|
|
554
|
+
EOF
|
|
555
|
+
return 0
|
|
556
|
+
fi
|
|
557
|
+
|
|
558
|
+
cmd_stage_related "$change" >/dev/null
|
|
559
|
+
if ! has_staged_changes; then
|
|
560
|
+
cat <<EOF
|
|
561
|
+
commit_created: false
|
|
562
|
+
commit_context: $context
|
|
563
|
+
commit_sha: none
|
|
564
|
+
commit_message: none
|
|
565
|
+
EOF
|
|
566
|
+
return 0
|
|
567
|
+
fi
|
|
568
|
+
|
|
569
|
+
message="$(build_commit_message "$change" "$context")"
|
|
570
|
+
git commit -m "$message" >/dev/null
|
|
571
|
+
sha="$(git rev-parse HEAD)"
|
|
572
|
+
|
|
573
|
+
cat <<EOF
|
|
574
|
+
commit_created: true
|
|
575
|
+
commit_context: $context
|
|
576
|
+
commit_sha: $sha
|
|
577
|
+
commit_message: $message
|
|
403
578
|
EOF
|
|
404
579
|
}
|
|
405
580
|
|
|
@@ -411,6 +586,7 @@ usage() {
|
|
|
411
586
|
onespec-commit.sh related-dirty <change>
|
|
412
587
|
onespec-commit.sh stage-related <change>
|
|
413
588
|
onespec-commit.sh detect-policy [change]
|
|
589
|
+
onespec-commit.sh commit-related <change> [closeout|archive|preserve-state]
|
|
414
590
|
EOF
|
|
415
591
|
}
|
|
416
592
|
|
|
@@ -437,6 +613,10 @@ case "$cmd" in
|
|
|
437
613
|
[ "$#" -le 2 ] || { usage; exit 2; }
|
|
438
614
|
cmd_detect_policy "${2:-}"
|
|
439
615
|
;;
|
|
616
|
+
commit-related)
|
|
617
|
+
[ "$#" -ge 2 ] && [ "$#" -le 3 ] || { usage; exit 2; }
|
|
618
|
+
cmd_commit_related "$2" "${3:-closeout}"
|
|
619
|
+
;;
|
|
440
620
|
*)
|
|
441
621
|
usage
|
|
442
622
|
exit 2
|
|
@@ -30,6 +30,18 @@ json_escape() {
|
|
|
30
30
|
sed 's/\\/\\\\/g; s/"/\\"/g' <<<"$1"
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
display_path() {
|
|
34
|
+
local file="$1"
|
|
35
|
+
case "$file" in
|
|
36
|
+
*/openspec/*)
|
|
37
|
+
printf 'openspec/%s\n' "${file##*/openspec/}"
|
|
38
|
+
;;
|
|
39
|
+
*)
|
|
40
|
+
printf '%s\n' "$file"
|
|
41
|
+
;;
|
|
42
|
+
esac
|
|
43
|
+
}
|
|
44
|
+
|
|
33
45
|
valid_change() {
|
|
34
46
|
local change="$1"
|
|
35
47
|
[[ -n "$change" ]] || die "change name is required"
|
|
@@ -57,7 +69,7 @@ summary_text() {
|
|
|
57
69
|
files="$(source_files)"
|
|
58
70
|
count="$(printf '%s\n' "$files" | sed '/^$/d' | wc -l | tr -d ' ')"
|
|
59
71
|
first="$(printf '%s\n' "$files" | sed -n '1p')"
|
|
60
|
-
printf '%s handoff from %s file(s); primary artifact: %s' "$purpose" "$count" "$first"
|
|
72
|
+
printf '%s handoff from %s file(s); primary artifact: %s' "$purpose" "$count" "$(display_path "$first")"
|
|
61
73
|
}
|
|
62
74
|
|
|
63
75
|
write_excerpt() {
|
|
@@ -95,9 +107,11 @@ case "$purpose" in proposal|plan|review|archive) ;; *) die "purpose must be prop
|
|
|
95
107
|
case "$full_flag" in "" ) mode="compact" ;; "--full" ) mode="full" ;; * ) die "unknown option: $full_flag" ;; esac
|
|
96
108
|
|
|
97
109
|
change_dir="openspec/changes/$change"
|
|
98
|
-
|
|
99
|
-
|
|
110
|
+
script_dir="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" && pwd -P)"
|
|
111
|
+
state="$("${BASH:-bash}" "$script_dir/onespec-state.sh" path "$change")"
|
|
100
112
|
[ -f "$state" ] || die "state file not found: $state"
|
|
113
|
+
change_dir="$(cd "$(dirname "$state")" && pwd -P)"
|
|
114
|
+
state_label="$(display_path "$state")"
|
|
101
115
|
|
|
102
116
|
if [ "$(source_files | wc -l | tr -d ' ')" -eq 0 ]; then
|
|
103
117
|
die "no OpenSpec artifacts found under $change_dir"
|
|
@@ -106,10 +120,9 @@ fi
|
|
|
106
120
|
hash="$(context_hash)"
|
|
107
121
|
summary="$(summary_text)"
|
|
108
122
|
|
|
109
|
-
|
|
110
|
-
"${BASH:-bash}" "$script_dir/onespec-state.sh" set "$change" handoff_context "$state"
|
|
123
|
+
"${BASH:-bash}" "$script_dir/onespec-state.sh" set "$change" handoff_context "$state_label"
|
|
111
124
|
"${BASH:-bash}" "$script_dir/onespec-state.sh" set "$change" handoff_purpose "$purpose"
|
|
112
125
|
"${BASH:-bash}" "$script_dir/onespec-state.sh" set "$change" handoff_summary "$summary"
|
|
113
126
|
"${BASH:-bash}" "$script_dir/onespec-state.sh" set "$change" handoff_hash "$hash"
|
|
114
127
|
|
|
115
|
-
echo "$
|
|
128
|
+
echo "$state_label"
|
|
@@ -29,6 +29,41 @@ state_file() {
|
|
|
29
29
|
printf '%s/.onespec.yaml\n' "$(change_dir "$change")"
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
+
current_workspace_path() {
|
|
33
|
+
pwd -P
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
in_git_repo() {
|
|
37
|
+
git rev-parse --show-toplevel >/dev/null 2>&1
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
canonicalize_dir() {
|
|
41
|
+
local dir="$1"
|
|
42
|
+
if [ -z "$dir" ] || [ "$dir" = "unknown" ] || [ ! -d "$dir" ]; then
|
|
43
|
+
printf '%s\n' "$dir"
|
|
44
|
+
return 0
|
|
45
|
+
fi
|
|
46
|
+
(
|
|
47
|
+
cd "$dir"
|
|
48
|
+
pwd -P
|
|
49
|
+
)
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
state_file_in_workspace() {
|
|
53
|
+
local workspace="$1"
|
|
54
|
+
local change="$2"
|
|
55
|
+
local active archived
|
|
56
|
+
active="$workspace/openspec/changes/$change/.onespec.yaml"
|
|
57
|
+
archived="$workspace/openspec/changes/archive/$change/.onespec.yaml"
|
|
58
|
+
if [ -f "$active" ]; then
|
|
59
|
+
printf '%s\n' "$active"
|
|
60
|
+
elif [ -f "$archived" ]; then
|
|
61
|
+
printf '%s\n' "$archived"
|
|
62
|
+
else
|
|
63
|
+
printf '%s\n' "$active"
|
|
64
|
+
fi
|
|
65
|
+
}
|
|
66
|
+
|
|
32
67
|
today() {
|
|
33
68
|
date +%Y-%m-%d
|
|
34
69
|
}
|
|
@@ -43,6 +78,96 @@ field_value() {
|
|
|
43
78
|
awk -F ': *' -v key="$key" '$1 == key { sub(/^[^:]+: */, ""); print; found=1; exit } END { if (!found) exit 0 }' "$file" 2>/dev/null
|
|
44
79
|
}
|
|
45
80
|
|
|
81
|
+
phase_rank() {
|
|
82
|
+
case "$1" in
|
|
83
|
+
intake) echo 0 ;;
|
|
84
|
+
proposal-ready) echo 1 ;;
|
|
85
|
+
approved) echo 2 ;;
|
|
86
|
+
plan-ready) echo 3 ;;
|
|
87
|
+
implementing) echo 4 ;;
|
|
88
|
+
review) echo 5 ;;
|
|
89
|
+
done) echo 6 ;;
|
|
90
|
+
archived) echo 7 ;;
|
|
91
|
+
*) echo -1 ;;
|
|
92
|
+
esac
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
preferred_state_file() {
|
|
96
|
+
local left="$1"
|
|
97
|
+
local right="$2"
|
|
98
|
+
local left_phase right_phase left_rank right_rank left_updated right_updated
|
|
99
|
+
|
|
100
|
+
if [ -z "$left" ] || [ ! -f "$left" ]; then
|
|
101
|
+
printf '%s\n' "$right"
|
|
102
|
+
return 0
|
|
103
|
+
fi
|
|
104
|
+
if [ -z "$right" ] || [ ! -f "$right" ]; then
|
|
105
|
+
printf '%s\n' "$left"
|
|
106
|
+
return 0
|
|
107
|
+
fi
|
|
108
|
+
|
|
109
|
+
left_phase="$(field_value "$left" phase)"
|
|
110
|
+
right_phase="$(field_value "$right" phase)"
|
|
111
|
+
left_rank="$(phase_rank "$left_phase")"
|
|
112
|
+
right_rank="$(phase_rank "$right_phase")"
|
|
113
|
+
if [ "$right_rank" -gt "$left_rank" ]; then
|
|
114
|
+
printf '%s\n' "$right"
|
|
115
|
+
return 0
|
|
116
|
+
fi
|
|
117
|
+
if [ "$left_rank" -gt "$right_rank" ]; then
|
|
118
|
+
printf '%s\n' "$left"
|
|
119
|
+
return 0
|
|
120
|
+
fi
|
|
121
|
+
|
|
122
|
+
left_updated="$(field_value "$left" updated_at)"
|
|
123
|
+
right_updated="$(field_value "$right" updated_at)"
|
|
124
|
+
if [ -n "$right_updated" ] && { [ -z "$left_updated" ] || [ "$right_updated" \> "$left_updated" ]; }; then
|
|
125
|
+
printf '%s\n' "$right"
|
|
126
|
+
return 0
|
|
127
|
+
fi
|
|
128
|
+
printf '%s\n' "$left"
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
resolve_state_file() {
|
|
132
|
+
local change="$1"
|
|
133
|
+
local current_workspace current_file best_file implementation_workspace implementation_file worktree_path candidate
|
|
134
|
+
|
|
135
|
+
current_workspace="$(current_workspace_path)"
|
|
136
|
+
current_file="$(state_file_in_workspace "$current_workspace" "$change")"
|
|
137
|
+
if [ ! -f "$current_file" ] && ! in_git_repo; then
|
|
138
|
+
printf '%s\n' "$current_file"
|
|
139
|
+
return 0
|
|
140
|
+
fi
|
|
141
|
+
|
|
142
|
+
best_file=""
|
|
143
|
+
if [ -f "$current_file" ]; then
|
|
144
|
+
best_file="$current_file"
|
|
145
|
+
implementation_workspace="$(canonicalize_dir "$(field_value "$current_file" implementation_workspace_path)")"
|
|
146
|
+
if [ -n "$implementation_workspace" ] && [ "$implementation_workspace" != "unknown" ]; then
|
|
147
|
+
implementation_file="$(state_file_in_workspace "$implementation_workspace" "$change")"
|
|
148
|
+
if [ -f "$implementation_file" ]; then
|
|
149
|
+
best_file="$(preferred_state_file "$best_file" "$implementation_file")"
|
|
150
|
+
fi
|
|
151
|
+
fi
|
|
152
|
+
fi
|
|
153
|
+
|
|
154
|
+
if in_git_repo; then
|
|
155
|
+
while IFS= read -r worktree_path; do
|
|
156
|
+
[ -n "$worktree_path" ] || continue
|
|
157
|
+
candidate="$(state_file_in_workspace "$worktree_path" "$change")"
|
|
158
|
+
if [ -f "$candidate" ]; then
|
|
159
|
+
best_file="$(preferred_state_file "$best_file" "$candidate")"
|
|
160
|
+
fi
|
|
161
|
+
done < <(git worktree list --porcelain 2>/dev/null | awk '/^worktree / { sub(/^worktree /, ""); print }')
|
|
162
|
+
fi
|
|
163
|
+
|
|
164
|
+
if [ -n "$best_file" ]; then
|
|
165
|
+
printf '%s\n' "$best_file"
|
|
166
|
+
else
|
|
167
|
+
printf '%s\n' "$current_file"
|
|
168
|
+
fi
|
|
169
|
+
}
|
|
170
|
+
|
|
46
171
|
enum_contains() {
|
|
47
172
|
local needle="$1"
|
|
48
173
|
shift
|
|
@@ -170,6 +295,7 @@ workspace: undecided
|
|
|
170
295
|
origin_branch: unknown
|
|
171
296
|
origin_workspace_path: unknown
|
|
172
297
|
origin_workspace_mode: unknown
|
|
298
|
+
implementation_workspace_path: unknown
|
|
173
299
|
plan: null
|
|
174
300
|
handoff_context: null
|
|
175
301
|
handoff_purpose: null
|
|
@@ -189,7 +315,7 @@ cmd_get() {
|
|
|
189
315
|
local key="$2"
|
|
190
316
|
valid_change "$change"
|
|
191
317
|
local file
|
|
192
|
-
file="$(
|
|
318
|
+
file="$(resolve_state_file "$change")"
|
|
193
319
|
[ -f "$file" ] || die "state not found: $file"
|
|
194
320
|
field_value "$file" "$key"
|
|
195
321
|
}
|
|
@@ -200,11 +326,11 @@ cmd_set() {
|
|
|
200
326
|
local value="$3"
|
|
201
327
|
valid_change "$change"
|
|
202
328
|
local file
|
|
203
|
-
file="$(
|
|
329
|
+
file="$(resolve_state_file "$change")"
|
|
204
330
|
[ -f "$file" ] || die "state not found: $file"
|
|
205
331
|
|
|
206
332
|
case "$key" in
|
|
207
|
-
phase|ambiguity|complexity|implementation_path|execution_method|workspace|origin_branch|origin_workspace_path|origin_workspace_mode|plan|handoff_context|handoff_purpose|handoff_summary|handoff_hash|touched_files_b64|review_result|archive|updated_at)
|
|
333
|
+
phase|ambiguity|complexity|implementation_path|execution_method|workspace|origin_branch|origin_workspace_path|origin_workspace_mode|implementation_workspace_path|plan|handoff_context|handoff_purpose|handoff_summary|handoff_hash|touched_files_b64|review_result|archive|updated_at)
|
|
208
334
|
;;
|
|
209
335
|
*)
|
|
210
336
|
die "unsupported field: $key"
|
|
@@ -227,62 +353,63 @@ cmd_recover() {
|
|
|
227
353
|
local change="$1"
|
|
228
354
|
valid_change "$change"
|
|
229
355
|
local file
|
|
230
|
-
file="$(
|
|
356
|
+
file="$(resolve_state_file "$change")"
|
|
231
357
|
[ -f "$file" ] || die "state not found: $file"
|
|
232
358
|
|
|
233
359
|
echo "OneSpec 恢复状态"
|
|
234
360
|
echo "change: $change"
|
|
235
|
-
for key in phase ambiguity complexity implementation_path execution_method workspace origin_branch origin_workspace_path origin_workspace_mode plan handoff_context handoff_purpose handoff_summary handoff_hash review_result archive updated_at; do
|
|
361
|
+
for key in phase ambiguity complexity implementation_path execution_method workspace origin_branch origin_workspace_path origin_workspace_mode implementation_workspace_path plan handoff_context handoff_purpose handoff_summary handoff_hash review_result archive updated_at; do
|
|
236
362
|
printf '%s: %s\n' "$key" "$(field_value "$file" "$key")"
|
|
237
363
|
done
|
|
238
|
-
local phase next_skill next_gate allowed_actions next_step
|
|
364
|
+
local phase next_skill next_reference next_gate allowed_actions next_step
|
|
239
365
|
phase="$(field_value "$file" phase)"
|
|
366
|
+
next_skill="onespec"
|
|
240
367
|
|
|
241
368
|
case "$phase" in
|
|
242
369
|
intake)
|
|
243
|
-
|
|
370
|
+
next_reference="references/design.md"
|
|
244
371
|
next_gate="ambiguity-scan"
|
|
245
372
|
allowed_actions="scan-ambiguity,draft-proposal"
|
|
246
373
|
next_step="执行歧义扫描,然后创建或恢复 OpenSpec 提案"
|
|
247
374
|
;;
|
|
248
375
|
proposal-ready)
|
|
249
|
-
|
|
376
|
+
next_reference="references/design.md"
|
|
250
377
|
next_gate="proposal-approval"
|
|
251
378
|
allowed_actions="show-approval-menu,revise-artifacts,pause-design"
|
|
252
379
|
next_step="汇总提案并展示编号批准选项,等待用户回复数字"
|
|
253
380
|
;;
|
|
254
381
|
approved)
|
|
255
|
-
|
|
382
|
+
next_reference="references/execute.md"
|
|
256
383
|
next_gate="implementation-route"
|
|
257
384
|
allowed_actions="confirm-route,create-plan,choose-workspace"
|
|
258
|
-
next_step="
|
|
385
|
+
next_step="读取 \`references/execute.md\`,创建或校验实现计划"
|
|
259
386
|
;;
|
|
260
387
|
plan-ready)
|
|
261
|
-
|
|
388
|
+
next_reference="references/execute.md"
|
|
262
389
|
next_gate="start-implementation"
|
|
263
390
|
allowed_actions="record-phase-implementing,start-work,track-files"
|
|
264
|
-
next_step="
|
|
391
|
+
next_step="读取 \`references/execute.md\`,确认执行方式后开始实现,并先写入 \`phase implementing\`"
|
|
265
392
|
;;
|
|
266
393
|
implementing)
|
|
267
|
-
|
|
394
|
+
next_reference="references/execute.md"
|
|
268
395
|
next_gate="implementation-in-progress"
|
|
269
396
|
allowed_actions="continue-implementation,update-tasks,run-tests"
|
|
270
397
|
next_step="继续未完成任务,然后更新 tasks.md"
|
|
271
398
|
;;
|
|
272
399
|
review)
|
|
273
|
-
|
|
400
|
+
next_reference="references/archive.md"
|
|
274
401
|
next_gate="user-review-closeout"
|
|
275
|
-
allowed_actions="request-changes,choose-
|
|
276
|
-
next_step="
|
|
402
|
+
allowed_actions="request-changes,choose-closeout-action,direct-instruction"
|
|
403
|
+
next_step="等待用户评审;若用户回复非编号内容则继续修改,若用户明确选择收尾动作,则读取 \`references/archive.md\` 直接执行对应 closeout"
|
|
277
404
|
;;
|
|
278
405
|
done|archived)
|
|
279
|
-
|
|
406
|
+
next_reference="references/archive.md"
|
|
280
407
|
next_gate="no-implementation-work"
|
|
281
408
|
allowed_actions="stop,archive-if-needed"
|
|
282
409
|
next_step="没有剩余实现工作"
|
|
283
410
|
;;
|
|
284
411
|
*)
|
|
285
|
-
|
|
412
|
+
next_reference="references/design.md"
|
|
286
413
|
next_gate="repair-state"
|
|
287
414
|
allowed_actions="inspect-artifacts,repair-state"
|
|
288
415
|
next_step="检查 OpenSpec 产物并修复状态"
|
|
@@ -290,11 +417,18 @@ cmd_recover() {
|
|
|
290
417
|
esac
|
|
291
418
|
|
|
292
419
|
printf 'next_skill: %s\n' "$next_skill"
|
|
420
|
+
printf 'next_reference: %s\n' "$next_reference"
|
|
293
421
|
printf 'next_gate: %s\n' "$next_gate"
|
|
294
422
|
printf 'allowed_actions: %s\n' "$allowed_actions"
|
|
295
423
|
printf '下一步: %s\n' "$next_step"
|
|
296
424
|
}
|
|
297
425
|
|
|
426
|
+
cmd_path() {
|
|
427
|
+
local change="$1"
|
|
428
|
+
valid_change "$change"
|
|
429
|
+
resolve_state_file "$change"
|
|
430
|
+
}
|
|
431
|
+
|
|
298
432
|
cmd_list() {
|
|
299
433
|
if [ ! -d openspec/changes ]; then
|
|
300
434
|
return 0
|
|
@@ -306,6 +440,7 @@ usage() {
|
|
|
306
440
|
cat <<'EOF'
|
|
307
441
|
用法:
|
|
308
442
|
onespec-state.sh init <change>
|
|
443
|
+
onespec-state.sh path <change>
|
|
309
444
|
onespec-state.sh get <change> <field>
|
|
310
445
|
onespec-state.sh set <change> <field> <value>
|
|
311
446
|
onespec-state.sh recover <change>
|
|
@@ -319,6 +454,10 @@ case "$cmd" in
|
|
|
319
454
|
[ "$#" -eq 2 ] || { usage; exit 2; }
|
|
320
455
|
cmd_init "$2"
|
|
321
456
|
;;
|
|
457
|
+
path)
|
|
458
|
+
[ "$#" -eq 2 ] || { usage; exit 2; }
|
|
459
|
+
cmd_path "$2"
|
|
460
|
+
;;
|
|
322
461
|
get)
|
|
323
462
|
[ "$#" -eq 3 ] || { usage; exit 2; }
|
|
324
463
|
cmd_get "$2" "$3"
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: onespec-fast
|
|
3
|
+
description: 当用户明确要求使用 OneSpec 快速路径、onespec-fast、fast apply、OpenSpec 自动 proposal/开发/归档或自动贯通时使用。该 skill 会复用 `onespec/references/fast.md`,全程走原生 OpenSpec apply,不做复杂度检查,不生成 Superpowers plan。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OneSpec Fast
|
|
7
|
+
|
|
8
|
+
这是 OneSpec 快速路径的独立入口。它不重复阶段规则;必须复用主 `onespec` skill 的 `references/fast.md`。
|
|
9
|
+
|
|
10
|
+
开始时说明:
|
|
11
|
+
|
|
12
|
+
> 我正在使用 `onespec-fast` 快速路径。
|
|
13
|
+
|
|
14
|
+
## 入口规则
|
|
15
|
+
|
|
16
|
+
- 只有用户明确要求 `onespec-fast`、快速路径、fast apply、OpenSpec 自动 proposal/开发/归档或自动贯通时使用。
|
|
17
|
+
- 先读取相邻安装的 `../onespec/SKILL.md`,遵守其中的恢复优先、共同约束和 reference 读取规则。
|
|
18
|
+
- 然后读取 `../onespec/references/fast.md` 并按其中步骤执行。
|
|
19
|
+
- 如果相邻路径不可用,先在当前项目、`$HOME/.codex`、`$HOME/.claude`、`$HOME/.cursor`、`$HOME/.gemini`、`$HOME/.copilot`、`$HOME/.agents`、`$HOME/.config` 下定位 `*/onespec/references/fast.md`;仍找不到时停止并要求重新运行 `onespec init --overwrite`。
|
|
20
|
+
- `references/fast.md` 可以复用 `design.md`、`execute.md` 和 `archive.md` 的过程段,但会覆盖普通 proposal approval、review pause 和 closeout menu gate。
|
|
21
|
+
|
|
22
|
+
不要在 `onespec-fast/SKILL.md` 内重写快速路径步骤;完整规则只维护在 `onespec/references/fast.md`。
|