@house365/comate 0.1.0

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.
@@ -0,0 +1,449 @@
1
+ #!/usr/bin/env bats
2
+ # comate state-machine + library tests.
3
+ # Run with: bats skills/comate/tests/comate.bats
4
+ # (or: bash skills/comate/tests/run.sh — plain-bash fallback, no bats needed)
5
+
6
+ setup() {
7
+ COMATE_ROOT="$(cd "$BATS_TEST_DIRNAME/../.." && pwd)"
8
+ COMATE="$COMATE_ROOT/comate/scripts/comate.sh"
9
+ [ -f "$COMATE" ] || COMATE="$BATS_TEST_DIRNAME/../scripts/comate.sh"
10
+ export COMATE_SKIP_BUILD=1
11
+ WORK="$(mktemp -d)"
12
+ cd "$WORK"
13
+ git init -q
14
+ git config user.email t@t.t
15
+ git config user.name t
16
+ git commit -q --allow-empty -m init
17
+ mkdir -p openspec/changes
18
+ }
19
+
20
+ teardown() {
21
+ cd /
22
+ rm -rf "$WORK"
23
+ }
24
+
25
+ comate() { bash "$COMATE" "$@"; }
26
+
27
+ seed_open() {
28
+ comate state demo init full >/dev/null
29
+ local d="openspec/changes/demo"
30
+ printf '# proposal\n' > "$d/proposal.md"
31
+ printf '# design\n' > "$d/design.md"
32
+ printf -- '- [ ] t1\n' > "$d/tasks.md"
33
+ }
34
+
35
+ # --- init / get / set ---
36
+
37
+ @test "init writes canonical field order with schema_version" {
38
+ comate state demo init full
39
+ run comate state demo get schema_version
40
+ [ "$status" -eq 0 ]
41
+ [ "$output" = "1" ]
42
+ run comate state demo get phase
43
+ [ "$output" = "open" ]
44
+ }
45
+
46
+ @test "init full leaves build decisions null" {
47
+ comate state demo init full
48
+ [ "$(comate state demo get build_mode)" = "null" ]
49
+ [ "$(comate state demo get tdd_mode)" = "null" ]
50
+ [ "$(comate state demo get isolation)" = "null" ]
51
+ }
52
+
53
+ @test "init hotfix prefills preset defaults" {
54
+ comate state demo init hotfix
55
+ [ "$(comate state demo get build_mode)" = "direct" ]
56
+ [ "$(comate state demo get tdd_mode)" = "direct" ]
57
+ [ "$(comate state demo get isolation)" = "branch" ]
58
+ [ "$(comate state demo get verify_mode)" = "light" ]
59
+ }
60
+
61
+ @test "init tweak prefills tdd direct" {
62
+ comate state demo init tweak
63
+ [ "$(comate state demo get build_mode)" = "direct" ]
64
+ [ "$(comate state demo get tdd_mode)" = "direct" ]
65
+ [ "$(comate state demo get isolation)" = "branch" ]
66
+ }
67
+
68
+ @test "init records base_ref and base_branch" {
69
+ comate state demo init full
70
+ run comate state demo get base_ref
71
+ [ "$output" != "null" ]
72
+ run comate state demo get base_branch
73
+ [ "$output" != "null" ]
74
+ }
75
+
76
+ @test "init twice fails with usage error" {
77
+ comate state demo init full
78
+ run comate state demo init full
79
+ [ "$status" -eq 2 ]
80
+ }
81
+
82
+ @test "set rejects unknown field" {
83
+ comate state demo init full
84
+ run comate state demo set bogus_field x
85
+ [ "$status" -eq 2 ]
86
+ }
87
+
88
+ @test "set rejects bad enum value" {
89
+ comate state demo init full
90
+ run comate state demo set phase nonsense
91
+ [ "$status" -eq 2 ]
92
+ }
93
+
94
+ @test "set rejects bad tdd_mode value" {
95
+ comate state demo init full
96
+ run comate state demo set tdd_mode maybe
97
+ [ "$status" -eq 2 ]
98
+ }
99
+
100
+ @test "set is idempotent and keeps single key" {
101
+ comate state demo init full
102
+ comate state demo set verify_mode light >/dev/null
103
+ comate state demo set verify_mode full >/dev/null
104
+ local count
105
+ count=$(grep -c '^verify_mode:' "openspec/changes/demo/.comate.yaml")
106
+ [ "$count" -eq 1 ]
107
+ [ "$(comate state demo get verify_mode)" = "full" ]
108
+ }
109
+
110
+ # --- input validation ---
111
+
112
+ @test "path traversal in change name rejected" {
113
+ run comate state "../evil" get phase
114
+ [ "$status" -eq 2 ]
115
+ }
116
+
117
+ @test "get on missing yaml returns corrupt code" {
118
+ run comate state nope get phase
119
+ [ "$status" -eq 4 ]
120
+ }
121
+
122
+ # --- flat yaml validation ---
123
+
124
+ @test "validate passes on clean init" {
125
+ comate state demo init full
126
+ run comate state demo validate
127
+ [ "$status" -eq 0 ]
128
+ }
129
+
130
+ @test "validate rejects nested yaml with corrupt code" {
131
+ comate state demo init full
132
+ printf 'nested:\n child: 1\n' >> "openspec/changes/demo/.comate.yaml"
133
+ run comate state demo validate
134
+ [ "$status" -eq 4 ]
135
+ }
136
+
137
+ @test "validate rejects list items" {
138
+ comate state demo init full
139
+ printf -- '- item\n' >> "openspec/changes/demo/.comate.yaml"
140
+ run comate state demo validate
141
+ [ "$status" -eq 4 ]
142
+ }
143
+
144
+ # --- state machine transitions ---
145
+
146
+ @test "transition rejects wrong-phase event" {
147
+ comate state demo init full
148
+ run comate state demo transition design-complete
149
+ [ "$status" -eq 1 ]
150
+ }
151
+
152
+ @test "open-complete routes full to design" {
153
+ seed_open
154
+ comate state demo transition open-complete
155
+ [ "$(comate state demo get phase)" = "design" ]
156
+ }
157
+
158
+ @test "open-complete routes hotfix to build" {
159
+ comate state demo init hotfix
160
+ comate state demo transition open-complete
161
+ [ "$(comate state demo get phase)" = "build" ]
162
+ }
163
+
164
+ @test "build-complete blocked without isolation" {
165
+ comate state demo init hotfix
166
+ comate state demo transition open-complete
167
+ comate state demo set isolation null >/dev/null
168
+ run comate state demo transition build-complete
169
+ [ "$status" -eq 1 ]
170
+ }
171
+
172
+ @test "full direct build blocked without override" {
173
+ comate state demo init full
174
+ comate state demo set isolation branch >/dev/null
175
+ comate state demo set build_mode direct >/dev/null
176
+ comate state demo set tdd_mode direct >/dev/null
177
+ # force phase to build to isolate the decision check
178
+ comate state demo set phase build >/dev/null
179
+ run comate state demo transition build-complete
180
+ [ "$status" -eq 1 ]
181
+ }
182
+
183
+ @test "build-complete blocked without tdd_mode" {
184
+ comate state demo init full
185
+ comate state demo set isolation branch >/dev/null
186
+ comate state demo set build_mode executing-plans >/dev/null
187
+ comate state demo set phase build >/dev/null
188
+ run comate state demo transition build-complete
189
+ [ "$status" -eq 1 ]
190
+ [[ "$output" == *"tdd_mode"* ]]
191
+ }
192
+
193
+ @test "build-complete accepts tdd_mode tdd" {
194
+ comate state demo init full
195
+ comate state demo set isolation branch >/dev/null
196
+ comate state demo set build_mode executing-plans >/dev/null
197
+ comate state demo set tdd_mode tdd >/dev/null
198
+ comate state demo set phase build >/dev/null
199
+ run comate state demo transition build-complete
200
+ [ "$status" -eq 0 ]
201
+ [ "$(comate state demo get phase)" = "verify" ]
202
+ }
203
+
204
+ @test "verify-pass blocked without report" {
205
+ comate state demo init full
206
+ comate state demo set phase verify >/dev/null
207
+ run comate state demo transition verify-pass
208
+ [ "$status" -eq 1 ]
209
+ }
210
+
211
+ # --- guard open ---
212
+
213
+ @test "guard open passes with full artifacts and applies" {
214
+ seed_open
215
+ printf -- '- [x] t1\n' > "openspec/changes/demo/tasks.md"
216
+ run comate guard demo open --apply
217
+ [ "$status" -eq 0 ]
218
+ [ "$(comate state demo get phase)" = "design" ]
219
+ }
220
+
221
+ @test "guard open json emits block false" {
222
+ seed_open
223
+ run comate guard demo open --json
224
+ [ "$status" -eq 0 ]
225
+ [[ "$output" == *'"block":false'* ]]
226
+ }
227
+
228
+ # --- handoff design + hash drift ---
229
+
230
+ @test "design handoff writes context and hash" {
231
+ seed_open
232
+ comate guard demo open --apply >/dev/null 2>&1
233
+ comate handoff demo design --write
234
+ run comate state demo get handoff_hash
235
+ [[ "$output" =~ ^[a-f0-9]{64}$ ]]
236
+ }
237
+
238
+ @test "design guard blocks on handoff hash drift" {
239
+ seed_open
240
+ comate guard demo open --apply >/dev/null 2>&1
241
+ comate handoff demo design --write >/dev/null 2>&1
242
+ printf 'edited after handoff\n' >> "openspec/changes/demo/proposal.md"
243
+ run comate guard demo design --json
244
+ [ "$status" -eq 1 ]
245
+ [[ "$output" == *'"block":true'* ]]
246
+ }
247
+
248
+ # --- build->verify drift ---
249
+
250
+ @test "verify guard blocks when tasks change after build handoff" {
251
+ comate state demo init hotfix
252
+ local d="openspec/changes/demo"
253
+ printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
254
+ comate guard demo open --apply >/dev/null 2>&1
255
+ comate handoff demo build --write >/dev/null 2>&1
256
+ comate guard demo build --apply >/dev/null 2>&1
257
+ printf 'rep\n' > "$d/rep.md"
258
+ comate state demo set verification_report "$d/rep.md" >/dev/null
259
+ comate state demo set branch_status handled >/dev/null
260
+ # tamper tasks after build exit
261
+ printf -- '- [x] t1\n- [x] sneaked\n' > "$d/tasks.md"
262
+ run comate guard demo verify --json
263
+ [ "$status" -eq 1 ]
264
+ [[ "$output" == *'impl_hash'*'fail'* ]] || [[ "$output" == *'"block":true'* ]]
265
+ }
266
+
267
+ # --- archive ---
268
+
269
+ @test "archive delegates spec merge to openspec instead of copying delta over main" {
270
+ comate state demo init full
271
+ local d="openspec/changes/demo"
272
+ printf '# p\n' > "$d/proposal.md"
273
+ printf '# d\n' > "$d/design.md"
274
+ printf -- '- [x] t1\n' > "$d/tasks.md"
275
+ mkdir -p "$d/specs/cap" openspec/specs/cap bin
276
+ printf '# Main Spec\n\n## Requirements\n\n### Requirement: Existing\n' > openspec/specs/cap/spec.md
277
+ printf '## ADDED Requirements\n\n### Requirement: Delta Only\n' > "$d/specs/cap/spec.md"
278
+
279
+ cat > bin/openspec <<'SH'
280
+ #!/bin/bash
281
+ set -e
282
+ [ "$1" = "archive" ] || exit 2
283
+ name="$2"
284
+ mkdir -p openspec/changes/archive
285
+ mv "openspec/changes/$name" "openspec/changes/archive/$(date +%Y-%m-%d)-$name"
286
+ SH
287
+ chmod +x bin/openspec
288
+ export PATH="$PWD/bin:$PATH"
289
+
290
+ comate state demo set phase archive >/dev/null
291
+ comate state demo set verify_result pass >/dev/null
292
+ run comate archive demo
293
+ [ "$status" -eq 0 ]
294
+ [[ "$(cat openspec/specs/cap/spec.md)" == *"Requirement: Existing"* ]]
295
+ [[ "$(cat openspec/specs/cap/spec.md)" != *"Delta Only"* ]]
296
+ [ "$(comate state "$(date +%Y-%m-%d)-demo" get archived)" = "true" ]
297
+ }
298
+
299
+ @test "archive repoints .comate.yaml self-pointers to archive path" {
300
+ comate state demo init full
301
+ local d="openspec/changes/demo"
302
+ printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
303
+ mkdir -p docs/knowledge-base/cb bin
304
+ printf '# frd\n' > docs/knowledge-base/cb/frd.md
305
+ comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
306
+ cat > bin/openspec <<'SH'
307
+ #!/bin/bash
308
+ set -e
309
+ [ "$1" = "archive" ] || exit 2
310
+ mkdir -p openspec/changes/archive
311
+ mv "openspec/changes/$2" "openspec/changes/archive/$(date +%Y-%m-%d)-$2"
312
+ SH
313
+ chmod +x bin/openspec
314
+ export PATH="$PWD/bin:$PATH"
315
+ comate state demo set phase archive >/dev/null
316
+ comate state demo set verify_result pass >/dev/null
317
+ run comate archive demo
318
+ [ "$status" -eq 0 ]
319
+ local an="$(date +%Y-%m-%d)-demo"
320
+ [ "$(comate state "$an" get source_manifest)" = "openspec/changes/archive/$an/.comate/sources.manifest" ]
321
+ run comate sources "$an" check
322
+ [ "$status" -eq 0 ]
323
+ }
324
+
325
+ @test "archive rewrites design doc body references to archive path" {
326
+ comate state demo init full
327
+ local d="openspec/changes/demo"
328
+ printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
329
+ mkdir -p docs/superpowers/specs bin
330
+ printf -- '---\ncomate_change: demo\nrole: technical-design\ncanonical_spec: openspec\n---\nref: openspec/changes/demo/specs/cap/spec.md\n' > docs/superpowers/specs/dd.md
331
+ comate state demo set design_doc docs/superpowers/specs/dd.md >/dev/null
332
+ cat > bin/openspec <<'SH'
333
+ #!/bin/bash
334
+ set -e
335
+ [ "$1" = "archive" ] || exit 2
336
+ mkdir -p openspec/changes/archive
337
+ mv "openspec/changes/$2" "openspec/changes/archive/$(date +%Y-%m-%d)-$2"
338
+ SH
339
+ chmod +x bin/openspec
340
+ export PATH="$PWD/bin:$PATH"
341
+ comate state demo set phase archive >/dev/null
342
+ comate state demo set verify_result pass >/dev/null
343
+ run comate archive demo
344
+ [ "$status" -eq 0 ]
345
+ local an="$(date +%Y-%m-%d)-demo"
346
+ [[ "$(cat docs/superpowers/specs/dd.md)" == *"openspec/changes/archive/$an/specs/cap/spec.md"* ]]
347
+ [[ "$(cat docs/superpowers/specs/dd.md)" == *"comate_change: demo"* ]]
348
+ [[ "$(cat docs/superpowers/specs/dd.md)" == *"archived-with: $an"* ]]
349
+ }
350
+
351
+ # --- scale ---
352
+
353
+ @test "scale sets verify_mode full when many tasks" {
354
+ comate state demo init full
355
+ local d="openspec/changes/demo"
356
+ printf -- '- [ ] a\n- [ ] b\n- [ ] c\n- [ ] d\n' > "$d/tasks.md"
357
+ comate state demo scale
358
+ [ "$(comate state demo get verify_mode)" = "full" ]
359
+ }
360
+
361
+ @test "scale sets verify_mode light when few tasks" {
362
+ comate state demo init full
363
+ printf -- '- [ ] a\n' > "openspec/changes/demo/tasks.md"
364
+ comate state demo scale
365
+ [ "$(comate state demo get verify_mode)" = "light" ]
366
+ }
367
+
368
+ # --- source tracking ---
369
+
370
+ @test "init defaults source_kind conversation with null manifest" {
371
+ comate state demo init full
372
+ [ "$(comate state demo get source_kind)" = "conversation" ]
373
+ [ "$(comate state demo get source_manifest)" = "null" ]
374
+ }
375
+
376
+ @test "sources check on conversation source is a no-op pass" {
377
+ comate state demo init full
378
+ run comate sources demo check
379
+ [ "$status" -eq 0 ]
380
+ }
381
+
382
+ @test "sources track records kb file and flips source_kind to file" {
383
+ comate state demo init full
384
+ mkdir -p docs/knowledge-base/cb
385
+ printf '# frd\n' > docs/knowledge-base/cb/frd.md
386
+ run comate sources demo track docs/knowledge-base/cb/frd.md --note "main entry"
387
+ [ "$status" -eq 0 ]
388
+ [ "$(comate state demo get source_kind)" = "file" ]
389
+ [ "$(comate state demo get source_manifest)" = "openspec/changes/demo/.comate/sources.manifest" ]
390
+ run comate sources demo list
391
+ [[ "$output" == *"path:docs/knowledge-base/cb/frd.md"* ]]
392
+ [[ "$output" == *"note:main entry"* ]]
393
+ }
394
+
395
+ @test "sources track rejects file outside knowledge-base" {
396
+ comate state demo init full
397
+ printf 'x\n' > docs/outside.md
398
+ run comate sources demo track docs/outside.md
399
+ [ "$(comate state demo get source_kind)" = "conversation" ]
400
+ [ "$(comate state demo get source_manifest)" = "null" ]
401
+ }
402
+
403
+ @test "sources check detects drift after file edited" {
404
+ comate state demo init full
405
+ mkdir -p docs/knowledge-base/cb
406
+ printf '# frd\n' > docs/knowledge-base/cb/frd.md
407
+ comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
408
+ printf 'edited\n' >> docs/knowledge-base/cb/frd.md
409
+ run comate sources demo check
410
+ [ "$status" -eq 1 ]
411
+ [[ "$output" == *"drifted"* ]]
412
+ }
413
+
414
+ @test "sources find reverse-looks-up a tracked file" {
415
+ comate state demo init full
416
+ mkdir -p docs/knowledge-base/cb
417
+ printf '# frd\n' > docs/knowledge-base/cb/frd.md
418
+ comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
419
+ run comate sources find docs/knowledge-base/cb/frd.md
420
+ [ "$status" -eq 0 ]
421
+ [[ "$output" == *"demo"* ]]
422
+ }
423
+
424
+ @test "build guard blocks when tracked source drifts" {
425
+ comate state demo init hotfix
426
+ local d="openspec/changes/demo"
427
+ printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
428
+ mkdir -p docs/knowledge-base/cb
429
+ printf '# frd\n' > docs/knowledge-base/cb/frd.md
430
+ comate sources demo track docs/knowledge-base/cb/frd.md >/dev/null 2>&1
431
+ comate guard demo open --apply >/dev/null 2>&1
432
+ comate handoff demo build --write >/dev/null 2>&1
433
+ printf 'edited\n' >> docs/knowledge-base/cb/frd.md
434
+ run comate guard demo build --json
435
+ [ "$status" -eq 1 ]
436
+ [[ "$output" == *'"block":true'* ]]
437
+ }
438
+
439
+ # --- doctor + usage ---
440
+
441
+ @test "doctor exits 0 when core tools present" {
442
+ run comate doctor
443
+ [ "$status" -eq 0 ]
444
+ }
445
+
446
+ @test "top-level usage error returns code 2" {
447
+ run comate frobnicate
448
+ [ "$status" -eq 2 ]
449
+ }
@@ -0,0 +1,101 @@
1
+ #!/bin/bash
2
+ # run.sh — plain-bash runner for comate.bats when `bats` is not installed.
3
+ #
4
+ # It sources each @test body in a subshell with setup/teardown, providing the
5
+ # minimal bats shims (`run`, `$status`, `$output`, `$BATS_TEST_DIRNAME`).
6
+ # If real bats is available, prefer: bats skills/comate/tests/comate.bats
7
+ #
8
+ # Usage: bash skills/comate/tests/run.sh
9
+
10
+ set -uo pipefail
11
+
12
+ TEST_DIR="$(cd "$(dirname "$0")" && pwd)"
13
+ BATS_FILE="$TEST_DIR/comate.bats"
14
+ export BATS_TEST_DIRNAME="$TEST_DIR"
15
+
16
+ # bats shim: run a command, capture combined output + status.
17
+ run() {
18
+ local _out _rc
19
+ _out="$("$@" 2>&1)"
20
+ _rc=$?
21
+ output="$_out"
22
+ status="$_rc"
23
+ # shellcheck disable=SC2034
24
+ lines=()
25
+ local _l
26
+ while IFS= read -r _l; do lines+=("$_l"); done <<< "$_out"
27
+ return 0
28
+ }
29
+
30
+ # Extract test names and bodies from the .bats file.
31
+ # A test is: @test "name" { ... } with closing } at column 0.
32
+ PASS=0
33
+ FAIL=0
34
+ FAILED_NAMES=()
35
+
36
+ # shellcheck disable=SC1090
37
+ source_helpers() {
38
+ # Pull setup(), teardown(), comate(), seed_open() definitions by sourcing
39
+ # a sanitized copy that strips the @test blocks.
40
+ local helpers
41
+ helpers="$(awk '
42
+ /^@test / { skip=1 }
43
+ skip==1 && /^}/ { skip=0; next }
44
+ skip==1 { next }
45
+ { print }
46
+ ' "$BATS_FILE")"
47
+ eval "$helpers"
48
+ }
49
+
50
+ run_one() {
51
+ local name="$1" body="$2"
52
+ ( # subshell isolates each test
53
+ set -uo pipefail
54
+ source_helpers
55
+ status=0; output=""
56
+ setup
57
+ _trap() { teardown 2>/dev/null || true; }
58
+ trap _trap EXIT
59
+ eval "$body"
60
+ )
61
+ }
62
+
63
+ main() {
64
+ local name="" body="" in_test=0
65
+ while IFS= read -r line; do
66
+ if [[ "$in_test" -eq 0 && "$line" =~ ^@test\ \"(.*)\"\ \{ ]]; then
67
+ name="${BASH_REMATCH[1]}"
68
+ body=""
69
+ in_test=1
70
+ continue
71
+ fi
72
+ if [[ "$in_test" -eq 1 && "$line" == "}" ]]; then
73
+ in_test=0
74
+ if run_one "$name" "$body" >/tmp/comate_test_out.$$ 2>&1; then
75
+ PASS=$((PASS+1))
76
+ printf ' ok %s\n' "$name"
77
+ else
78
+ FAIL=$((FAIL+1))
79
+ FAILED_NAMES+=("$name")
80
+ printf ' FAIL %s\n' "$name"
81
+ sed 's/^/ /' /tmp/comate_test_out.$$
82
+ fi
83
+ continue
84
+ fi
85
+ if [[ "$in_test" -eq 1 ]]; then
86
+ body+="$line"$'\n'
87
+ fi
88
+ done < "$BATS_FILE"
89
+ rm -f /tmp/comate_test_out.$$
90
+
91
+ echo ""
92
+ echo "comate tests: $PASS passed, $FAIL failed"
93
+ if [ "$FAIL" -gt 0 ]; then
94
+ printf 'Failed:\n'
95
+ printf ' - %s\n' "${FAILED_NAMES[@]}"
96
+ exit 1
97
+ fi
98
+ exit 0
99
+ }
100
+
101
+ main
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: comate-archive
3
+ description: "comate 阶段 5:归档。用 /comate-archive 调用。委托 OpenSpec 合并 delta spec 并归档 change。"
4
+ ---
5
+
6
+ # comate 阶段 5:归档(Archive)
7
+
8
+ ## 前置条件
9
+
10
+ - 验证已通过(阶段 4 完成),分支已处理
11
+ - `openspec/changes/<name>/.comate.yaml` 中 `verify_result: pass`
12
+
13
+ ## 步骤
14
+
15
+ ### 0. 入口状态验证
16
+
17
+ ```bash
18
+ COMATE="${COMATE:-$(for r in . ./.claude/skills "$HOME"/.claude/skills "$HOME"/.config/skills "$HOME"/.cursor/skills; do
19
+ [ -d "$r" ] && find "$r" -maxdepth 6 -path '*/comate/scripts/comate.sh' -type f -print -quit 2>/dev/null
20
+ done | head -1)}"
21
+ if [ -z "$COMATE" ]; then
22
+ echo "ERROR: comate.sh 未找到,请确认 comate skill 已安装。" >&2
23
+ return 1 2>/dev/null || exit 1
24
+ fi
25
+ bash "$COMATE" state <name> check archive
26
+ ```
27
+
28
+ ### 1. 执行归档
29
+
30
+ ```bash
31
+ bash "$COMATE" archive "<name>"
32
+ ```
33
+
34
+ 脚本自动完成:
35
+ 1. 入口状态验证(phase=archive, verify_result=pass, archived=false)
36
+ 2. 委托 `openspec archive <name> -y` 合并 delta spec 到主 spec;无 delta spec 时使用 `--skip-specs`
37
+ 3. Design doc 前置元数据标注(archived-with, status: final),并把正文中指向 `openspec/changes/<name>/` 的引用重写为归档路径
38
+ 4. Plan 前置元数据标注(archived-with),同样重写正文 OpenSpec 路径引用
39
+ 5. 由 OpenSpec 移动 change 到 `openspec/changes/archive/YYYY-MM-DD-<name>/`
40
+ 6. 通过 `state transition <archive-name> archived` 设 `archived: true`
41
+ 7. 重写归档后 `.comate.yaml` 中随 change 移动的自指字段(`source_manifest` / `handoff_context` / `impl_context`)指向新归档路径,使 `sources <name> check/list` 等命令对归档 change 仍可用
42
+
43
+ 非零退出码 → 报告错误并停止。零退出码 → 归档完成。摘要 `X/Y steps succeeded` 以真实执行步骤计数。
44
+
45
+ 需预览不实际执行:加 `--dry-run`。
46
+
47
+ ### 2. 生命周期闭环
48
+
49
+ ```
50
+ brainstorming → delta spec → 实施 → 验证 → OpenSpec 合并主 spec → design doc 标注 → 归档
51
+ ```
52
+
53
+ ## 退出条件
54
+
55
+ - 归档脚本退出码 0
56
+ - 归档目录 `openspec/changes/archive/YYYY-MM-DD-<name>/` 存在
57
+ - 归档后 `.comate.yaml` 中 `archived: true`
58
+
59
+ 归档成功后**不要再对原 change 名跑 guard**,原活跃目录已不存在。
60
+
61
+ ## 完成
62
+
63
+ comate 流程全部完成。如需开始新工作,调用 `/comate` 或 `/comate-open`。