@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.
- package/LICENSE +21 -0
- package/README.md +78 -0
- package/bin/cli.mjs +45 -0
- package/bin/init-run.mjs +99 -0
- package/bin/init.mjs +159 -0
- package/docs/COMATE_MANUAL.md +612 -0
- package/package.json +52 -0
- package/skills/comate/SKILL.md +213 -0
- package/skills/comate/reference/decision-points.md +54 -0
- package/skills/comate/reference/dirty-worktree.md +58 -0
- package/skills/comate/reference/prerequisites.md +88 -0
- package/skills/comate/scripts/comate-lib.sh +430 -0
- package/skills/comate/scripts/comate.sh +1681 -0
- package/skills/comate/tests/comate.bats +449 -0
- package/skills/comate/tests/run.sh +101 -0
- package/skills/comate-archive/SKILL.md +63 -0
- package/skills/comate-build/SKILL.md +152 -0
- package/skills/comate-design/SKILL.md +106 -0
- package/skills/comate-hotfix/SKILL.md +115 -0
- package/skills/comate-open/SKILL.md +109 -0
- package/skills/comate-tweak/SKILL.md +109 -0
- package/skills/comate-verify/SKILL.md +130 -0
|
@@ -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`。
|