@kafka0102/onespec 0.1.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 +84 -0
- package/assets/skills/onespec/SKILL.md +58 -0
- package/assets/skills/onespec/scripts/onespec-closeout.sh +404 -0
- package/assets/skills/onespec/scripts/onespec-commit.sh +444 -0
- package/assets/skills/onespec/scripts/onespec-env.sh +15 -0
- package/assets/skills/onespec/scripts/onespec-handoff.sh +115 -0
- package/assets/skills/onespec/scripts/onespec-state.sh +341 -0
- package/assets/skills/onespec-archive/SKILL.md +202 -0
- package/assets/skills/onespec-design/SKILL.md +226 -0
- package/assets/skills/onespec-execute/SKILL.md +219 -0
- package/assets/skills-en/onespec/SKILL.md +57 -0
- package/assets/skills-en/onespec-archive/SKILL.md +199 -0
- package/assets/skills-en/onespec-design/SKILL.md +226 -0
- package/assets/skills-en/onespec-execute/SKILL.md +219 -0
- package/bin/onespec.js +7 -0
- package/package.json +38 -0
- package/scripts/postinstall.js +28 -0
- package/src/cli.js +244 -0
- package/src/doctor.js +172 -0
- package/src/init.js +136 -0
- package/src/platforms.js +23 -0
|
@@ -0,0 +1,341 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
set -euo pipefail
|
|
3
|
+
|
|
4
|
+
die() {
|
|
5
|
+
echo "ERROR: $*" >&2
|
|
6
|
+
exit 1
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
valid_change() {
|
|
10
|
+
local change="$1"
|
|
11
|
+
[[ -n "$change" ]] || die "change name is required"
|
|
12
|
+
[[ "$change" =~ ^[A-Za-z0-9][A-Za-z0-9._-]*$ ]] || die "invalid change name: $change"
|
|
13
|
+
[[ "$change" != *".."* ]] || die "change name must not contain '..'"
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
change_dir() {
|
|
17
|
+
local change="$1"
|
|
18
|
+
if [ -d "openspec/changes/$change" ]; then
|
|
19
|
+
printf 'openspec/changes/%s\n' "$change"
|
|
20
|
+
elif [ -d "openspec/changes/archive/$change" ]; then
|
|
21
|
+
printf 'openspec/changes/archive/%s\n' "$change"
|
|
22
|
+
else
|
|
23
|
+
printf 'openspec/changes/%s\n' "$change"
|
|
24
|
+
fi
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
state_file() {
|
|
28
|
+
local change="$1"
|
|
29
|
+
printf '%s/.onespec.yaml\n' "$(change_dir "$change")"
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
today() {
|
|
33
|
+
date +%Y-%m-%d
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
now_utc() {
|
|
37
|
+
date -u +%Y-%m-%dT%H:%M:%SZ
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
field_value() {
|
|
41
|
+
local file="$1"
|
|
42
|
+
local key="$2"
|
|
43
|
+
awk -F ': *' -v key="$key" '$1 == key { sub(/^[^:]+: */, ""); print; found=1; exit } END { if (!found) exit 0 }' "$file" 2>/dev/null
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
enum_contains() {
|
|
47
|
+
local needle="$1"
|
|
48
|
+
shift
|
|
49
|
+
local candidate
|
|
50
|
+
for candidate in "$@"; do
|
|
51
|
+
if [ "$candidate" = "$needle" ]; then
|
|
52
|
+
return 0
|
|
53
|
+
fi
|
|
54
|
+
done
|
|
55
|
+
return 1
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
validate_enum_value() {
|
|
59
|
+
local key="$1"
|
|
60
|
+
local value="$2"
|
|
61
|
+
|
|
62
|
+
case "$key" in
|
|
63
|
+
phase)
|
|
64
|
+
enum_contains "$value" intake proposal-ready approved plan-ready implementing review done archived \
|
|
65
|
+
|| die "invalid value for $key: $value"
|
|
66
|
+
;;
|
|
67
|
+
ambiguity)
|
|
68
|
+
enum_contains "$value" unknown low high || die "invalid value for $key: $value"
|
|
69
|
+
;;
|
|
70
|
+
complexity)
|
|
71
|
+
enum_contains "$value" unknown low medium high || die "invalid value for $key: $value"
|
|
72
|
+
;;
|
|
73
|
+
implementation_path)
|
|
74
|
+
enum_contains "$value" undecided openspec-apply superpowers || die "invalid value for $key: $value"
|
|
75
|
+
;;
|
|
76
|
+
execution_method)
|
|
77
|
+
enum_contains "$value" undecided subagent local native || die "invalid value for $key: $value"
|
|
78
|
+
;;
|
|
79
|
+
workspace|origin_workspace_mode)
|
|
80
|
+
enum_contains "$value" unknown undecided worktree current-branch main-override \
|
|
81
|
+
|| die "invalid value for $key: $value"
|
|
82
|
+
;;
|
|
83
|
+
review_result)
|
|
84
|
+
enum_contains "$value" pending changes-requested approved || die "invalid value for $key: $value"
|
|
85
|
+
;;
|
|
86
|
+
archive)
|
|
87
|
+
enum_contains "$value" pending skipped archived || die "invalid value for $key: $value"
|
|
88
|
+
;;
|
|
89
|
+
esac
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
phase_transition_allowed() {
|
|
93
|
+
local old="$1"
|
|
94
|
+
local new="$2"
|
|
95
|
+
|
|
96
|
+
[ "$old" = "$new" ] && return 0
|
|
97
|
+
|
|
98
|
+
case "$old" in
|
|
99
|
+
intake)
|
|
100
|
+
enum_contains "$new" proposal-ready
|
|
101
|
+
;;
|
|
102
|
+
proposal-ready)
|
|
103
|
+
enum_contains "$new" intake approved
|
|
104
|
+
;;
|
|
105
|
+
approved)
|
|
106
|
+
enum_contains "$new" proposal-ready plan-ready implementing
|
|
107
|
+
;;
|
|
108
|
+
plan-ready)
|
|
109
|
+
enum_contains "$new" proposal-ready approved implementing
|
|
110
|
+
;;
|
|
111
|
+
implementing)
|
|
112
|
+
enum_contains "$new" proposal-ready approved plan-ready review
|
|
113
|
+
;;
|
|
114
|
+
review)
|
|
115
|
+
enum_contains "$new" implementing done archived
|
|
116
|
+
;;
|
|
117
|
+
done)
|
|
118
|
+
enum_contains "$new" review archived
|
|
119
|
+
;;
|
|
120
|
+
archived)
|
|
121
|
+
return 1
|
|
122
|
+
;;
|
|
123
|
+
*)
|
|
124
|
+
return 1
|
|
125
|
+
;;
|
|
126
|
+
esac
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
set_field() {
|
|
130
|
+
local file="$1"
|
|
131
|
+
local key="$2"
|
|
132
|
+
local value="$3"
|
|
133
|
+
local tmp
|
|
134
|
+
tmp="$(mktemp)"
|
|
135
|
+
if grep -q "^${key}:" "$file"; then
|
|
136
|
+
awk -v key="$key" -v value="$value" '
|
|
137
|
+
$0 ~ "^" key ":" { print key ": " value; next }
|
|
138
|
+
{ print }
|
|
139
|
+
' "$file" > "$tmp"
|
|
140
|
+
else
|
|
141
|
+
cat "$file" > "$tmp"
|
|
142
|
+
printf '%s: %s\n' "$key" "$value" >> "$tmp"
|
|
143
|
+
fi
|
|
144
|
+
mv "$tmp" "$file"
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
cmd_init() {
|
|
148
|
+
local change="$1"
|
|
149
|
+
valid_change "$change"
|
|
150
|
+
|
|
151
|
+
local dir file
|
|
152
|
+
dir="$(change_dir "$change")"
|
|
153
|
+
file="$(state_file "$change")"
|
|
154
|
+
mkdir -p "$dir"
|
|
155
|
+
|
|
156
|
+
if [ -f "$file" ]; then
|
|
157
|
+
echo "$file"
|
|
158
|
+
return 0
|
|
159
|
+
fi
|
|
160
|
+
|
|
161
|
+
cat > "$file" <<EOF
|
|
162
|
+
version: 1
|
|
163
|
+
change: $change
|
|
164
|
+
phase: intake
|
|
165
|
+
ambiguity: unknown
|
|
166
|
+
complexity: unknown
|
|
167
|
+
implementation_path: undecided
|
|
168
|
+
execution_method: undecided
|
|
169
|
+
workspace: undecided
|
|
170
|
+
origin_branch: unknown
|
|
171
|
+
origin_workspace_path: unknown
|
|
172
|
+
origin_workspace_mode: unknown
|
|
173
|
+
plan: null
|
|
174
|
+
handoff_context: null
|
|
175
|
+
handoff_purpose: null
|
|
176
|
+
handoff_summary: null
|
|
177
|
+
handoff_hash: null
|
|
178
|
+
touched_files_b64: null
|
|
179
|
+
review_result: pending
|
|
180
|
+
archive: pending
|
|
181
|
+
created_at: $(today)
|
|
182
|
+
updated_at: $(now_utc)
|
|
183
|
+
EOF
|
|
184
|
+
echo "$file"
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
cmd_get() {
|
|
188
|
+
local change="$1"
|
|
189
|
+
local key="$2"
|
|
190
|
+
valid_change "$change"
|
|
191
|
+
local file
|
|
192
|
+
file="$(state_file "$change")"
|
|
193
|
+
[ -f "$file" ] || die "state not found: $file"
|
|
194
|
+
field_value "$file" "$key"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
cmd_set() {
|
|
198
|
+
local change="$1"
|
|
199
|
+
local key="$2"
|
|
200
|
+
local value="$3"
|
|
201
|
+
valid_change "$change"
|
|
202
|
+
local file
|
|
203
|
+
file="$(state_file "$change")"
|
|
204
|
+
[ -f "$file" ] || die "state not found: $file"
|
|
205
|
+
|
|
206
|
+
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)
|
|
208
|
+
;;
|
|
209
|
+
*)
|
|
210
|
+
die "unsupported field: $key"
|
|
211
|
+
;;
|
|
212
|
+
esac
|
|
213
|
+
|
|
214
|
+
validate_enum_value "$key" "$value"
|
|
215
|
+
if [ "$key" = "phase" ]; then
|
|
216
|
+
local current_phase
|
|
217
|
+
current_phase="$(field_value "$file" phase)"
|
|
218
|
+
phase_transition_allowed "$current_phase" "$value" \
|
|
219
|
+
|| die "illegal phase transition: $current_phase -> $value"
|
|
220
|
+
fi
|
|
221
|
+
|
|
222
|
+
set_field "$file" "$key" "$value"
|
|
223
|
+
set_field "$file" "updated_at" "$(now_utc)"
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
cmd_recover() {
|
|
227
|
+
local change="$1"
|
|
228
|
+
valid_change "$change"
|
|
229
|
+
local file
|
|
230
|
+
file="$(state_file "$change")"
|
|
231
|
+
[ -f "$file" ] || die "state not found: $file"
|
|
232
|
+
|
|
233
|
+
echo "OneSpec 恢复状态"
|
|
234
|
+
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
|
|
236
|
+
printf '%s: %s\n' "$key" "$(field_value "$file" "$key")"
|
|
237
|
+
done
|
|
238
|
+
local phase next_skill next_gate allowed_actions next_step
|
|
239
|
+
phase="$(field_value "$file" phase)"
|
|
240
|
+
|
|
241
|
+
case "$phase" in
|
|
242
|
+
intake)
|
|
243
|
+
next_skill="onespec-design"
|
|
244
|
+
next_gate="ambiguity-scan"
|
|
245
|
+
allowed_actions="scan-ambiguity,draft-proposal"
|
|
246
|
+
next_step="执行歧义扫描,然后创建或恢复 OpenSpec 提案"
|
|
247
|
+
;;
|
|
248
|
+
proposal-ready)
|
|
249
|
+
next_skill="onespec-design"
|
|
250
|
+
next_gate="proposal-approval"
|
|
251
|
+
allowed_actions="show-approval-menu,revise-artifacts,pause-design"
|
|
252
|
+
next_step="汇总提案并展示编号批准选项,等待用户回复数字"
|
|
253
|
+
;;
|
|
254
|
+
approved)
|
|
255
|
+
next_skill="onespec-execute"
|
|
256
|
+
next_gate="implementation-route"
|
|
257
|
+
allowed_actions="confirm-route,create-plan,choose-workspace"
|
|
258
|
+
next_step="进入 \`onespec-execute\`,创建或校验实现计划"
|
|
259
|
+
;;
|
|
260
|
+
plan-ready)
|
|
261
|
+
next_skill="onespec-execute"
|
|
262
|
+
next_gate="start-implementation"
|
|
263
|
+
allowed_actions="record-phase-implementing,start-work,track-files"
|
|
264
|
+
next_step="进入 \`onespec-execute\`,确认执行方式后开始实现,并先写入 \`phase implementing\`"
|
|
265
|
+
;;
|
|
266
|
+
implementing)
|
|
267
|
+
next_skill="onespec-execute"
|
|
268
|
+
next_gate="implementation-in-progress"
|
|
269
|
+
allowed_actions="continue-implementation,update-tasks,run-tests"
|
|
270
|
+
next_step="继续未完成任务,然后更新 tasks.md"
|
|
271
|
+
;;
|
|
272
|
+
review)
|
|
273
|
+
next_skill="onespec-archive"
|
|
274
|
+
next_gate="user-review-closeout"
|
|
275
|
+
allowed_actions="request-changes,choose-archive-menu,direct-instruction"
|
|
276
|
+
next_step="等待用户评审;若用户回复非编号内容则继续修改,若选择归档菜单则进入 \`onespec-archive\` 处理删除 worktree / 归档组合选项"
|
|
277
|
+
;;
|
|
278
|
+
done|archived)
|
|
279
|
+
next_skill="onespec-archive"
|
|
280
|
+
next_gate="no-implementation-work"
|
|
281
|
+
allowed_actions="stop,archive-if-needed"
|
|
282
|
+
next_step="没有剩余实现工作"
|
|
283
|
+
;;
|
|
284
|
+
*)
|
|
285
|
+
next_skill="onespec"
|
|
286
|
+
next_gate="repair-state"
|
|
287
|
+
allowed_actions="inspect-artifacts,repair-state"
|
|
288
|
+
next_step="检查 OpenSpec 产物并修复状态"
|
|
289
|
+
;;
|
|
290
|
+
esac
|
|
291
|
+
|
|
292
|
+
printf 'next_skill: %s\n' "$next_skill"
|
|
293
|
+
printf 'next_gate: %s\n' "$next_gate"
|
|
294
|
+
printf 'allowed_actions: %s\n' "$allowed_actions"
|
|
295
|
+
printf '下一步: %s\n' "$next_step"
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
cmd_list() {
|
|
299
|
+
if [ ! -d openspec/changes ]; then
|
|
300
|
+
return 0
|
|
301
|
+
fi
|
|
302
|
+
find openspec/changes -name .onespec.yaml -type f | sort
|
|
303
|
+
}
|
|
304
|
+
|
|
305
|
+
usage() {
|
|
306
|
+
cat <<'EOF'
|
|
307
|
+
用法:
|
|
308
|
+
onespec-state.sh init <change>
|
|
309
|
+
onespec-state.sh get <change> <field>
|
|
310
|
+
onespec-state.sh set <change> <field> <value>
|
|
311
|
+
onespec-state.sh recover <change>
|
|
312
|
+
onespec-state.sh list
|
|
313
|
+
EOF
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
cmd="${1:-}"
|
|
317
|
+
case "$cmd" in
|
|
318
|
+
init)
|
|
319
|
+
[ "$#" -eq 2 ] || { usage; exit 2; }
|
|
320
|
+
cmd_init "$2"
|
|
321
|
+
;;
|
|
322
|
+
get)
|
|
323
|
+
[ "$#" -eq 3 ] || { usage; exit 2; }
|
|
324
|
+
cmd_get "$2" "$3"
|
|
325
|
+
;;
|
|
326
|
+
set)
|
|
327
|
+
[ "$#" -eq 4 ] || { usage; exit 2; }
|
|
328
|
+
cmd_set "$2" "$3" "$4"
|
|
329
|
+
;;
|
|
330
|
+
recover)
|
|
331
|
+
[ "$#" -eq 2 ] || { usage; exit 2; }
|
|
332
|
+
cmd_recover "$2"
|
|
333
|
+
;;
|
|
334
|
+
list)
|
|
335
|
+
cmd_list
|
|
336
|
+
;;
|
|
337
|
+
*)
|
|
338
|
+
usage
|
|
339
|
+
exit 2
|
|
340
|
+
;;
|
|
341
|
+
esac
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: onespec-archive
|
|
3
|
+
description: 当用户需要对 OneSpec change 做最终评审、处理反馈、删除 worktree 或执行 OpenSpec archive 时使用。
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# OneSpec Archive
|
|
7
|
+
|
|
8
|
+
用于 OneSpec 的评审、收尾与归档阶段。目标是在用户确认后执行删除 worktree 与 OpenSpec archive,不默认执行有后果的操作。
|
|
9
|
+
|
|
10
|
+
开始时说明:
|
|
11
|
+
|
|
12
|
+
> 我正在使用 `onespec-archive` 处理 review / closeout 阶段。
|
|
13
|
+
|
|
14
|
+
## 1. 评审入口
|
|
15
|
+
|
|
16
|
+
先恢复状态:
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
ONESPEC_ENV="${ONESPEC_ENV:-$(find . "$HOME"/.codex "$HOME"/.agents "$HOME"/.config -path '*/onespec/scripts/onespec-env.sh' -type f -print -quit 2>/dev/null)}"
|
|
20
|
+
. "$ONESPEC_ENV"
|
|
21
|
+
"$ONESPEC_BASH" "$ONESPEC_STATE" list
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
如果发现相关 change,必须继续执行:
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
"$ONESPEC_BASH" "$ONESPEC_STATE" recover <change-id>
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
`recover` 的输出是当前阶段合同,不是参考信息。至少先读取 `phase`、`next_skill`、`next_gate` 与 `allowed_actions`,再决定是否继续收尾阶段动作。
|
|
31
|
+
|
|
32
|
+
读取最少必要上下文:
|
|
33
|
+
|
|
34
|
+
- `openspec/changes/<change-id>/proposal.md`
|
|
35
|
+
- `openspec/changes/<change-id>/tasks.md`
|
|
36
|
+
- `openspec/changes/<change-id>/design.md`,如果存在
|
|
37
|
+
- 相关 `openspec/specs/**`
|
|
38
|
+
- 最新测试结果与 `openspec validate <change-id> --strict` 结果
|
|
39
|
+
- 当前分支、worktree 和工作区状态
|
|
40
|
+
- `origin_branch`、`origin_workspace_path`、`origin_workspace_mode`
|
|
41
|
+
|
|
42
|
+
如果状态尚未到 `review`,先说明缺少什么:未实现、未验证、未回填 `tasks.md`,或 proposal 尚未批准。
|
|
43
|
+
|
|
44
|
+
入口校验:如果 phase 已到 `review`,但 `.onespec.yaml` 里的 `handoff_purpose` 不是 `review`,或 `handoff_hash` 为空,说明 execute gate 可能未正常完成。此时必须告诉用户:"执行阶段的 review handoff 状态未写回,建议先回到执行阶段补充汇报(回复 `补充汇报` 或重新触发 execute gate)。" 不允许静默跳过。
|
|
45
|
+
|
|
46
|
+
## 2. 用户评审
|
|
47
|
+
|
|
48
|
+
实现完成后让用户评审。若用户指出问题,继续修改并重新验证。
|
|
49
|
+
|
|
50
|
+
开发完成后不需要再次让用户确认是否 review,也不需要展示常规“继续评审 / 保留分支”类选项。只需询问是否进行归档;如果用户回复任意非编号内容,默认视为“继续修改当前实现”,直接回到代码处理环节。
|
|
51
|
+
|
|
52
|
+
不要让用户自己猜“下一步该输入什么”。进入 `onespec-archive` 时,必须给出可直接回复的编号选项;如果支持多动作组合,允许用户回复逗号分隔的数字,例如 `1,3`。
|
|
53
|
+
|
|
54
|
+
进入收尾选择前,必须显式向用户汇报:
|
|
55
|
+
|
|
56
|
+
- 当前分支名
|
|
57
|
+
- 当前工作区路径
|
|
58
|
+
- 最初开始这次 change 时记录的 `origin_branch` 与 `origin_workspace_path`
|
|
59
|
+
- 当前是否仍在原始分支/原始工作区
|
|
60
|
+
|
|
61
|
+
如果当前分支或工作区不同于 `origin_*`,必须明确说明“你当前看到的是临时实现分支或临时 worktree”。此时默认展示删除 worktree / 归档组合选项;如果用户改为输入任意非编号内容,则表示当前功能还有问题,需要继续修改。
|
|
62
|
+
|
|
63
|
+
可选收尾路径只围绕两件事展开:
|
|
64
|
+
|
|
65
|
+
- 删除 worktree
|
|
66
|
+
- 执行归档
|
|
67
|
+
|
|
68
|
+
不要默认自动删除 worktree。删除与归档都是有后果的操作,必须来自用户选择。
|
|
69
|
+
|
|
70
|
+
## 2.1 Superpowers Worktree 优先规则
|
|
71
|
+
|
|
72
|
+
如果 `origin_workspace_mode=worktree`,或当前路径是实现期新建的临时 worktree,收尾时必须把“回收到原始分支/工作区”的动作提到最前面说明。
|
|
73
|
+
|
|
74
|
+
必须显式告诉用户:
|
|
75
|
+
|
|
76
|
+
- 当前实现位于临时 worktree
|
|
77
|
+
- 原始分支是 `origin_branch`
|
|
78
|
+
- 原始工作区是 `origin_workspace_path`
|
|
79
|
+
- 收尾后是否会删除本地临时 branch 与 worktree
|
|
80
|
+
|
|
81
|
+
默认推荐顺序:
|
|
82
|
+
|
|
83
|
+
1. 先在临时 worktree 完成 review。
|
|
84
|
+
2. 如果无需继续修改,优先给出“删除 worktree 并归档”作为推荐收尾组合。
|
|
85
|
+
3. 如果用户只想清理本地环境,则允许“仅删除 worktree”。
|
|
86
|
+
4. 如果代码已经真正位于目标分支,则允许“仅归档”。
|
|
87
|
+
|
|
88
|
+
## 2.2 多选收尾组合
|
|
89
|
+
|
|
90
|
+
收尾选项不要再只做单选。动作本身就应围绕组合展开;组合输入使用编号列表,例如 `1,3`:
|
|
91
|
+
|
|
92
|
+
- `删除 worktree`
|
|
93
|
+
- `执行归档`
|
|
94
|
+
|
|
95
|
+
推荐的组合校验逻辑:
|
|
96
|
+
|
|
97
|
+
- `{删除 worktree 并归档}`:等价于 `{删除 worktree, 执行归档}`。
|
|
98
|
+
- `{删除 worktree, 执行归档}`:合法。适用于临时 worktree 已完成使命,需要直接清理并归档。
|
|
99
|
+
- `{仅删除 worktree}`:等价于 `{删除 worktree}`。
|
|
100
|
+
- `{删除 worktree}`:合法。适用于只清理本地临时 worktree,稍后再归档。
|
|
101
|
+
- `{仅归档}`:等价于 `{执行归档}`。
|
|
102
|
+
- `{执行归档}`:仅当代码已经位于目标分支时合法;如果当前还在临时 branch / worktree,默认不合法。
|
|
103
|
+
|
|
104
|
+
如果用户勾选了非法组合,必须明确指出冲突原因,不要替用户猜测执行顺序。
|
|
105
|
+
|
|
106
|
+
默认推荐组合:
|
|
107
|
+
|
|
108
|
+
- 当前在 Superpowers 临时 worktree:推荐 `{删除 worktree, 执行归档}`。
|
|
109
|
+
- 当前在临时 worktree,但用户只想先清理本地环境:推荐 `{删除 worktree}`。
|
|
110
|
+
- 当前不在临时 worktree,且代码已真正位于目标分支:推荐 `{执行归档}`。
|
|
111
|
+
|
|
112
|
+
推荐向用户展示的收尾选项至少包含:
|
|
113
|
+
|
|
114
|
+
1. 删除 worktree 并归档
|
|
115
|
+
2. 仅删除 worktree
|
|
116
|
+
3. 仅归档
|
|
117
|
+
其他:如果意图不在以上选项里,允许用户直接补充说明;任意非编号内容视为继续修改当前实现
|
|
118
|
+
|
|
119
|
+
菜单解释规则:
|
|
120
|
+
|
|
121
|
+
- 用户回复 `1`:执行“删除 worktree 并归档”。
|
|
122
|
+
- 用户回复 `2`:仅删除 worktree。
|
|
123
|
+
- 用户回复 `3`:仅当代码已合并且归档前置条件满足时,执行 archive;否则先说明阻塞条件。
|
|
124
|
+
- 用户回复多项编号,如 `1,3`:按组合规则校验。合法时按安全执行顺序运行;不合法时明确指出冲突原因。
|
|
125
|
+
- 用户输入数字外的自由文本:默认视为继续修改当前实现,直接回到代码处理环节;只有意图不清晰时才补一个最短澄清问题。
|
|
126
|
+
|
|
127
|
+
## 3. 归档规则
|
|
128
|
+
|
|
129
|
+
进入 archive 或删除 worktree 的最终收尾前,必须检查当前是否仍有“与本次 change 相关的未提交代码”:
|
|
130
|
+
|
|
131
|
+
```bash
|
|
132
|
+
"$ONESPEC_BASH" "$ONESPEC_COMMIT" related-dirty <change-id>
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
- 如果结果为空,继续后续收尾。
|
|
136
|
+
- 如果结果为空,即使工作区里还有无关未跟踪目录,也不要阻塞收尾;例如未记录到 `.onespec.yaml` tracked file 列表里的 `.superpowers/` 可以明确说明“未纳入本次提交”,但不应视为本次 change 的阻塞项。
|
|
137
|
+
- 如果结果非空,先向用户明确提示这些文件尚未提交,并暂停归档。
|
|
138
|
+
- 若用户要求现在提交,只能 stage 本次 change 相关文件:
|
|
139
|
+
|
|
140
|
+
```bash
|
|
141
|
+
"$ONESPEC_BASH" "$ONESPEC_COMMIT" stage-related <change-id>
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
- 提交信息优先遵循项目自身指定的 Git 提交策略。先探测项目内文档和配置:
|
|
145
|
+
|
|
146
|
+
```bash
|
|
147
|
+
"$ONESPEC_BASH" "$ONESPEC_COMMIT" detect-policy <change-id>
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
- 如果项目里存在明确规范,按项目要求处理 commit message 的格式、scope 和语言。
|
|
151
|
+
- 如果项目里没有明确规范,回退到通用 Conventional Commits:`<type>(<scope>): <简要描述>`。
|
|
152
|
+
- 只能提交 `.onespec.yaml` 中记录的 tracked files 与当前脏文件的交集;如果 `.onespec.yaml` 本身是脏的,也应一并提交,不允许把无关改动一并提交。
|
|
153
|
+
- 例外:位于 `openspec/changes/<change-id>/` 下、专属于本次 change 的临时压缩包、导出包或交接工件,也视为本次 change 相关文件;自动提交时要一并带上,这样 archive 后仍能保留在 change 历史里。
|
|
154
|
+
- 如果代码已合并到目标分支且用户选择归档,直接执行 OpenSpec archive,并将状态设为 `archived`。
|
|
155
|
+
- 如果用户当前只删除 worktree、不归档,将状态设为 `done`,并提示之后可再运行归档;此时不要删除 `.onespec.yaml`。
|
|
156
|
+
- 只有真正执行 archive 后,才删除运行时状态文件:
|
|
157
|
+
|
|
158
|
+
```bash
|
|
159
|
+
"$ONESPEC_BASH" "$ONESPEC_CLOSEOUT" cleanup-runtime <change-id>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
用户一旦通过收尾菜单明确选择了归档或组合归档动作,就把这次选择视为唯一确认;不要再追加第二次确认。
|
|
163
|
+
|
|
164
|
+
实际执行收尾动作时,优先使用:
|
|
165
|
+
|
|
166
|
+
```bash
|
|
167
|
+
"$ONESPEC_BASH" "$ONESPEC_CLOSEOUT" run-actions <change-id> [delete-worktree] [archive]
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
```bash
|
|
171
|
+
"$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> phase done
|
|
172
|
+
"$ONESPEC_BASH" "$ONESPEC_STATE" set <change-id> archive <skipped|archived>
|
|
173
|
+
```
|
|
174
|
+
|
|
175
|
+
归档前必须确认:
|
|
176
|
+
|
|
177
|
+
- `tasks.md` 已按实际完成项勾选
|
|
178
|
+
- 项目测试已通过,或未通过项已明确说明
|
|
179
|
+
- `openspec validate <change-id> --strict` 已通过
|
|
180
|
+
- 用户明确选择了删除 worktree、归档或它们的组合策略
|
|
181
|
+
- 没有未处理的用户评审反馈
|
|
182
|
+
|
|
183
|
+
## 4. 汇报
|
|
184
|
+
|
|
185
|
+
收尾汇报必须覆盖:
|
|
186
|
+
|
|
187
|
+
- 用户评审结果
|
|
188
|
+
- 选择的收尾路径:删除 worktree、归档或组合执行
|
|
189
|
+
- 分支/worktree 的最终状态
|
|
190
|
+
- 当前分支与 `origin_branch` 的关系,以及是否仍保留临时 worktree
|
|
191
|
+
- `tasks.md`、测试与 OpenSpec validate 状态
|
|
192
|
+
- archive 字段:`skipped` 或 `archived`
|
|
193
|
+
|
|
194
|
+
## 5. 停止条件
|
|
195
|
+
|
|
196
|
+
以下情况必须暂停并向用户说明:
|
|
197
|
+
|
|
198
|
+
- 用户还没有完成最终评审
|
|
199
|
+
- 用户没有明确选择收尾路径
|
|
200
|
+
- 用户没有明确确认删除 worktree 或 OpenSpec archive
|
|
201
|
+
- 代码未合并到目标分支且也未选择允许的删除 worktree 组合时,却要求单独 archive
|
|
202
|
+
- 测试或 `openspec validate <change-id> --strict` 未通过且用户未明确接受风险
|