@house365/comate 0.10.2 → 1.0.2-next.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.
Files changed (74) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +104 -64
  3. package/bin/cli.mjs +197 -190
  4. package/bin/doctor.mjs +29 -41
  5. package/bin/init-run.mjs +310 -368
  6. package/bin/init.mjs +53 -71
  7. package/bin/postinstall.mjs +56 -56
  8. package/docs/CHANGELOG-fix-simplification.md +145 -0
  9. package/docs/COMATE_BETA.md +90 -0
  10. package/docs/COMATE_MANUAL.md +239 -665
  11. package/docs/superpowers/plans/2026-10-14-review-fixes.md +48 -0
  12. package/docs/superpowers/plans/2026-10-14-workflow-quality.md +48 -0
  13. package/package.json +54 -55
  14. package/skills/comate/SKILL.md +42 -325
  15. package/skills/comate/reference/archive.md +50 -0
  16. package/skills/comate/reference/build.md +67 -0
  17. package/skills/comate/reference/fix.md +150 -0
  18. package/skills/comate/reference/open.md +162 -0
  19. package/skills/comate/reference/verify.md +123 -0
  20. package/skills/comate/scaffold/comate/README.md +18 -0
  21. package/skills/{comate-gate/scaffold/docs → comate/scaffold/comate}/architecture/README.md +5 -6
  22. package/skills/comate/scaffold/comate/comate/config.json +10 -0
  23. package/skills/comate/scaffold/comate/knowledge-base/README.md +42 -0
  24. package/skills/{comate-gate/scaffold/docs → comate/scaffold/comate}/knowledge-base/integrations/_TEMPLATE/README.md +2 -2
  25. package/skills/{comate-gate/scaffold/docs → comate/scaffold/comate}/knowledge-base/integrations/_TEMPLATE/api-snapshot.md +47 -47
  26. package/skills/{comate-gate/scaffold/docs → comate/scaffold/comate}/knowledge-base/requirements/_templates/PRD.md +4 -4
  27. package/skills/comate/scripts/comate.mjs +147 -0
  28. package/skills/comate/scripts/lib/acceptance.mjs +112 -0
  29. package/skills/comate/scripts/lib/brief.mjs +50 -0
  30. package/skills/comate/scripts/lib/checks.mjs +178 -0
  31. package/skills/comate/scripts/lib/cmd-archive.mjs +152 -0
  32. package/skills/comate/scripts/lib/cmd-decide.mjs +116 -0
  33. package/skills/comate/scripts/lib/cmd-init.mjs +75 -0
  34. package/skills/comate/scripts/lib/cmd-loop.mjs +273 -0
  35. package/skills/comate/scripts/lib/cmd-open.mjs +161 -0
  36. package/skills/comate/scripts/lib/cmd-status.mjs +121 -0
  37. package/skills/comate/scripts/lib/common.mjs +85 -0
  38. package/skills/comate/scripts/lib/docs.mjs +45 -0
  39. package/skills/comate/scripts/lib/finish.mjs +108 -0
  40. package/skills/comate/scripts/lib/git.mjs +112 -0
  41. package/skills/comate/scripts/lib/md.mjs +85 -0
  42. package/skills/comate/scripts/lib/next.mjs +217 -0
  43. package/skills/comate/scripts/lib/paths.mjs +96 -0
  44. package/skills/comate/scripts/lib/render.mjs +46 -0
  45. package/skills/comate/scripts/lib/spec.mjs +106 -0
  46. package/skills/comate/scripts/lib/state.mjs +128 -0
  47. package/skills/comate/scripts/lib/tasks.mjs +53 -0
  48. package/skills/comate/scripts/lib/templates.mjs +58 -0
  49. package/skills/comate/scripts/lib/util.mjs +108 -0
  50. package/skills/comate-kb-integration/SKILL.md +128 -128
  51. package/skills/comate-kb-integration/scripts/fetch_rendered_doc.py +84 -84
  52. package/bin/cleanup-duplicate-superpowers.mjs +0 -119
  53. package/docs/CLEANUP_SUPERPOWERS.md +0 -128
  54. package/docs/COMATE_BEGINNER_GUIDE.md +0 -188
  55. package/docs/COMATE_INIT_DIRECTORY_GUIDE.md +0 -201
  56. package/docs/SUPERPOWERS_FIX_SUMMARY.md +0 -114
  57. package/skills/comate/reference/decision-points.md +0 -103
  58. package/skills/comate/reference/dirty-worktree.md +0 -58
  59. package/skills/comate/reference/prerequisites.md +0 -89
  60. package/skills/comate/scripts/comate-lib.sh +0 -559
  61. package/skills/comate/scripts/comate.sh +0 -2147
  62. package/skills/comate/tests/comate.bats +0 -1019
  63. package/skills/comate/tests/run.sh +0 -101
  64. package/skills/comate-archive/SKILL.md +0 -63
  65. package/skills/comate-build/SKILL.md +0 -278
  66. package/skills/comate-design/SKILL.md +0 -178
  67. package/skills/comate-fix/SKILL.md +0 -195
  68. package/skills/comate-gate/SKILL.md +0 -316
  69. package/skills/comate-gate/scaffold/docs/README.md +0 -36
  70. package/skills/comate-gate/scaffold/docs/knowledge-base/README.md +0 -47
  71. package/skills/comate-grill/SKILL.md +0 -185
  72. package/skills/comate-open/SKILL.md +0 -230
  73. package/skills/comate-verify/SKILL.md +0 -212
  74. /package/skills/{comate-gate/scaffold/docs → comate/scaffold/comate}/knowledge-base/integrations/README.md +0 -0
@@ -1,1019 +0,0 @@
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 fix prefills preset defaults" {
54
- comate state demo init fix
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 records base_ref and base_branch" {
62
- comate state demo init full
63
- run comate state demo get base_ref
64
- [ "$output" != "null" ]
65
- run comate state demo get base_branch
66
- [ "$output" != "null" ]
67
- }
68
-
69
- @test "init twice fails with usage error" {
70
- comate state demo init full
71
- run comate state demo init full
72
- [ "$status" -eq 2 ]
73
- }
74
-
75
- @test "set rejects unknown field" {
76
- comate state demo init full
77
- run comate state demo set bogus_field x
78
- [ "$status" -eq 2 ]
79
- }
80
-
81
- @test "set rejects bad enum value" {
82
- comate state demo init full
83
- run comate state demo set phase nonsense
84
- [ "$status" -eq 2 ]
85
- }
86
-
87
- @test "set rejects bad tdd_mode value" {
88
- comate state demo init full
89
- run comate state demo set tdd_mode maybe
90
- [ "$status" -eq 2 ]
91
- }
92
-
93
- @test "set is idempotent and keeps single key" {
94
- comate state demo init full
95
- comate state demo set verify_mode light >/dev/null
96
- comate state demo set verify_mode full >/dev/null
97
- local count
98
- count=$(grep -c '^verify_mode:' "openspec/changes/demo/.comate.yaml")
99
- [ "$count" -eq 1 ]
100
- [ "$(comate state demo get verify_mode)" = "full" ]
101
- }
102
-
103
- # --- input validation ---
104
-
105
- @test "path traversal in change name rejected" {
106
- run comate state "../evil" get phase
107
- [ "$status" -eq 2 ]
108
- }
109
-
110
- @test "get on missing yaml returns corrupt code" {
111
- run comate state nope get phase
112
- [ "$status" -eq 4 ]
113
- }
114
-
115
- # --- flat yaml validation ---
116
-
117
- @test "validate passes on clean init" {
118
- comate state demo init full
119
- run comate state demo validate
120
- [ "$status" -eq 0 ]
121
- }
122
-
123
- @test "validate rejects nested yaml with corrupt code" {
124
- comate state demo init full
125
- printf 'nested:\n child: 1\n' >> "openspec/changes/demo/.comate.yaml"
126
- run comate state demo validate
127
- [ "$status" -eq 4 ]
128
- }
129
-
130
- @test "validate rejects list items" {
131
- comate state demo init full
132
- printf -- '- item\n' >> "openspec/changes/demo/.comate.yaml"
133
- run comate state demo validate
134
- [ "$status" -eq 4 ]
135
- }
136
-
137
- # --- state machine transitions ---
138
-
139
- @test "transition rejects wrong-phase event" {
140
- comate state demo init full
141
- run comate state demo transition design-complete
142
- [ "$status" -eq 1 ]
143
- }
144
-
145
- @test "open-complete routes full to design" {
146
- seed_open
147
- comate state demo set phase_step open.review-confirmed >/dev/null
148
- comate state demo transition open-complete
149
- [ "$(comate state demo get phase)" = "design" ]
150
- [ "$(comate state demo get phase_step)" = "null" ]
151
- }
152
-
153
- @test "open-complete routes fix to build" {
154
- comate state demo init fix
155
- comate state demo transition open-complete
156
- [ "$(comate state demo get phase)" = "build" ]
157
- }
158
-
159
- @test "build-complete blocked without isolation" {
160
- comate state demo init fix
161
- comate state demo transition open-complete
162
- comate state demo set isolation null >/dev/null
163
- run comate state demo transition build-complete
164
- [ "$status" -eq 1 ]
165
- }
166
-
167
- @test "full direct build blocked without override" {
168
- comate state demo init full
169
- comate state demo set isolation branch >/dev/null
170
- comate state demo set build_mode direct >/dev/null
171
- comate state demo set tdd_mode direct >/dev/null
172
- # force phase to build to isolate the decision check
173
- comate state demo set phase build >/dev/null
174
- run comate state demo transition build-complete
175
- [ "$status" -eq 1 ]
176
- }
177
-
178
- @test "build-complete blocked without tdd_mode" {
179
- comate state demo init full
180
- comate state demo set isolation branch >/dev/null
181
- comate state demo set build_mode executing-plans >/dev/null
182
- comate state demo set phase build >/dev/null
183
- run comate state demo transition build-complete
184
- [ "$status" -eq 1 ]
185
- [[ "$output" == *"tdd_mode"* ]]
186
- }
187
-
188
- @test "build-complete accepts tdd_mode tdd" {
189
- comate state demo init full
190
- comate state demo set isolation branch >/dev/null
191
- comate state demo set build_mode executing-plans >/dev/null
192
- comate state demo set tdd_mode tdd >/dev/null
193
- comate state demo set phase build >/dev/null
194
- run comate state demo transition build-complete
195
- [ "$status" -eq 0 ]
196
- [ "$(comate state demo get phase)" = "verify" ]
197
- }
198
-
199
- @test "verify-pass blocked without report" {
200
- comate state demo init full
201
- comate state demo set phase verify >/dev/null
202
- run comate state demo transition verify-pass
203
- [ "$status" -eq 1 ]
204
- }
205
-
206
- @test "fix-upgrade resets preset decisions and enters design" {
207
- comate state demo init fix
208
- comate state demo transition open-complete
209
- comate state demo set phase_step build.options-confirmed >/dev/null
210
- comate state demo transition fix-upgrade
211
- [ "$(comate state demo get workflow)" = "full" ]
212
- [ "$(comate state demo get phase)" = "design" ]
213
- [ "$(comate state demo get build_mode)" = "null" ]
214
- [ "$(comate state demo get tdd_mode)" = "null" ]
215
- [ "$(comate state demo get isolation)" = "null" ]
216
- [ "$(comate state demo get phase_step)" = "null" ]
217
- }
218
-
219
- @test "discarded branch cannot pass verification" {
220
- comate state demo init full
221
- comate state demo set phase verify >/dev/null
222
- printf '# pass\n' > report.md
223
- comate state demo set verification_report report.md >/dev/null
224
- comate state demo set branch_status discarded >/dev/null
225
- run comate state demo transition verify-pass
226
- [ "$status" -eq 1 ]
227
- [ "$(comate state demo get phase)" = "verify" ]
228
- }
229
-
230
- @test "verify-fail returns to build without a failure routing loop" {
231
- comate state demo init full
232
- comate state demo set phase verify >/dev/null
233
- comate state demo set verify_result fail >/dev/null
234
- comate state demo transition verify-fail
235
- [ "$(comate state demo get phase)" = "build" ]
236
- [ "$(comate state demo get verify_result)" = "pending" ]
237
- [ "$(comate state demo get verification_report)" = "null" ]
238
- }
239
-
240
- @test "verify-fail keeps build authoritative when tasks remain checked" {
241
- comate state demo init full
242
- printf '%s\n' '# Tasks' '- [x] already implemented' > openspec/changes/demo/tasks.md
243
- comate state demo set phase verify >/dev/null
244
- comate state demo set verify_result fail >/dev/null
245
- comate state demo transition verify-fail
246
- [ "$(comate state demo get phase)" = "build" ]
247
- [ "$(comate state demo get verify_result)" = "pending" ]
248
- # The router must use the persisted phase; task completion is not a phase.
249
- [ "$(comate state demo get phase)" != "verify" ]
250
- }
251
-
252
- @test "recover reports phase-local progress" {
253
- comate state demo init full
254
- comate state demo set phase_step open.review-awaiting >/dev/null
255
- run comate state demo recover open
256
- [ "$status" -eq 0 ]
257
- [[ "$output" == *"Phase step: open.review-awaiting"* ]]
258
- }
259
-
260
- @test "status exposes awaiting decisions" {
261
- comate state demo init full
262
- comate state demo set phase_step open.review-awaiting >/dev/null
263
- run comate status
264
- [ "$status" -eq 0 ]
265
- [[ "$output" == *"step: open.review-awaiting"* ]]
266
- }
267
-
268
- # --- guard open ---
269
-
270
- @test "guard open passes with full artifacts and applies" {
271
- seed_open
272
- printf -- '- [x] t1\n' > "openspec/changes/demo/tasks.md"
273
- run comate guard demo open --apply
274
- [ "$status" -eq 0 ]
275
- [ "$(comate state demo get phase)" = "design" ]
276
- }
277
-
278
- @test "fix guards continuously advance open to build to verify" {
279
- comate state demo init fix
280
- local d="openspec/changes/demo"
281
- printf '# p\n' > "$d/proposal.md"
282
- printf '# d\n' > "$d/design.md"
283
- printf -- '- [x] t1\n' > "$d/tasks.md"
284
-
285
- comate guard demo open --apply >/dev/null 2>&1
286
- [ "$(comate state demo get phase)" = "build" ]
287
-
288
- comate handoff demo build --write >/dev/null 2>&1
289
- comate guard demo build --apply >/dev/null 2>&1
290
- [ "$(comate state demo get phase)" = "verify" ]
291
- }
292
-
293
- @test "guard open json emits block false" {
294
- seed_open
295
- run comate guard demo open --json
296
- [ "$status" -eq 0 ]
297
- [[ "$output" == *'"block":false'* ]]
298
- }
299
-
300
- # --- handoff design + hash drift ---
301
-
302
- @test "design handoff writes context and hash" {
303
- seed_open
304
- comate guard demo open --apply >/dev/null 2>&1
305
- comate handoff demo design --write
306
- run comate state demo get handoff_hash
307
- [[ "$output" =~ ^[a-f0-9]{64}$ ]]
308
- }
309
-
310
- @test "design guard blocks on handoff hash drift" {
311
- seed_open
312
- comate guard demo open --apply >/dev/null 2>&1
313
- comate handoff demo design --write >/dev/null 2>&1
314
- printf 'edited after handoff\n' >> "openspec/changes/demo/proposal.md"
315
- run comate guard demo design --json
316
- [ "$status" -eq 1 ]
317
- [[ "$output" == *'"block":true'* ]]
318
- }
319
-
320
- # --- build->verify drift ---
321
-
322
- @test "verify guard blocks when tasks change after build handoff" {
323
- comate state demo init fix
324
- local d="openspec/changes/demo"
325
- printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
326
- comate guard demo open --apply >/dev/null 2>&1
327
- comate handoff demo build --write >/dev/null 2>&1
328
- comate guard demo build --apply >/dev/null 2>&1
329
- printf 'rep\n' > "$d/rep.md"
330
- comate state demo set verification_report "$d/rep.md" >/dev/null
331
- comate state demo set branch_status handled >/dev/null
332
- # tamper tasks after build exit
333
- printf -- '- [x] t1\n- [x] sneaked\n' > "$d/tasks.md"
334
- run comate guard demo verify --json
335
- [ "$status" -eq 1 ]
336
- [[ "$output" == *'impl_hash'*'fail'* ]] || [[ "$output" == *'"block":true'* ]]
337
- }
338
-
339
- # --- line-ending stability ---
340
-
341
- @test "impl_hash ignores text line-ending changes" {
342
- comate state demo init fix
343
- local d="openspec/changes/demo"
344
- printf '# p\n' > "$d/proposal.md"
345
- printf '# d\n' > "$d/design.md"
346
- printf '# plan\n' > "$d/plan.md"
347
- printf -- '- [x] t1\n' > "$d/tasks.md"
348
- comate state demo set plan "$d/plan.md" >/dev/null
349
- comate guard demo open --apply >/dev/null 2>&1
350
- comate handoff demo build --write >/dev/null 2>&1
351
- comate guard demo build --apply >/dev/null 2>&1
352
-
353
- printf '# plan\r\n' > "$d/plan.md"
354
- printf -- '- [x] t1\r\n' > "$d/tasks.md"
355
- printf 'verification\n' > "$d/verification.md"
356
- comate state demo set verification_report "$d/verification.md" >/dev/null
357
- comate state demo set branch_status handled >/dev/null
358
- run comate guard demo verify --json
359
- [ "$status" -eq 0 ]
360
- [[ "$output" == *'impl_hash matches current plan+tasks'*'pass'* ]]
361
- }
362
-
363
- # --- archive ---
364
-
365
- @test "archive delegates spec merge to openspec instead of copying delta over main" {
366
- comate state demo init full
367
- local d="openspec/changes/demo"
368
- printf '# p\n' > "$d/proposal.md"
369
- printf '# d\n' > "$d/design.md"
370
- printf -- '- [x] t1\n' > "$d/tasks.md"
371
- mkdir -p "$d/specs/cap" openspec/specs/cap bin
372
- printf '# Main Spec\n\n## Requirements\n\n### Requirement: Existing\n' > openspec/specs/cap/spec.md
373
- printf '## ADDED Requirements\n\n### Requirement: Delta Only\n' > "$d/specs/cap/spec.md"
374
-
375
- cat > bin/openspec <<'SH'
376
- #!/bin/bash
377
- set -e
378
- [ "$1" = "archive" ] || exit 2
379
- name="$2"
380
- mkdir -p openspec/changes/archive
381
- mv "openspec/changes/$name" "openspec/changes/archive/$(date +%Y-%m-%d)-$name"
382
- SH
383
- chmod +x bin/openspec
384
- export PATH="$PWD/bin:$PATH"
385
-
386
- comate state demo set phase archive >/dev/null
387
- comate state demo set verify_result pass >/dev/null
388
- run comate archive demo
389
- [ "$status" -eq 0 ]
390
- [[ "$(cat openspec/specs/cap/spec.md)" == *"Requirement: Existing"* ]]
391
- [[ "$(cat openspec/specs/cap/spec.md)" != *"Delta Only"* ]]
392
- [ "$(comate state "$(date +%Y-%m-%d)-demo" get archived)" = "true" ]
393
- }
394
-
395
- @test "archive repoints .comate.yaml self-pointers to archive path" {
396
- comate state demo init full
397
- local d="openspec/changes/demo"
398
- printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
399
- mkdir -p docs/knowledge-base/cb bin
400
- printf '# source\n' > docs/knowledge-base/cb/source.md
401
- comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
402
- cat > bin/openspec <<'SH'
403
- #!/bin/bash
404
- set -e
405
- [ "$1" = "archive" ] || exit 2
406
- mkdir -p openspec/changes/archive
407
- mv "openspec/changes/$2" "openspec/changes/archive/$(date +%Y-%m-%d)-$2"
408
- SH
409
- chmod +x bin/openspec
410
- export PATH="$PWD/bin:$PATH"
411
- comate state demo set phase archive >/dev/null
412
- comate state demo set verify_result pass >/dev/null
413
- run comate archive demo
414
- [ "$status" -eq 0 ]
415
- local an="$(date +%Y-%m-%d)-demo"
416
- [ "$(comate state "$an" get source_manifest)" = "openspec/changes/archive/$an/.comate/sources.manifest" ]
417
- run comate sources "$an" check
418
- [ "$status" -eq 0 ]
419
- }
420
-
421
- @test "archive rewrites design doc body references to archive path" {
422
- comate state demo init full
423
- local d="openspec/changes/demo"
424
- printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
425
- mkdir -p docs/superpowers/specs bin
426
- 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
427
- comate state demo set design_doc docs/superpowers/specs/dd.md >/dev/null
428
- cat > bin/openspec <<'SH'
429
- #!/bin/bash
430
- set -e
431
- [ "$1" = "archive" ] || exit 2
432
- mkdir -p openspec/changes/archive
433
- mv "openspec/changes/$2" "openspec/changes/archive/$(date +%Y-%m-%d)-$2"
434
- SH
435
- chmod +x bin/openspec
436
- export PATH="$PWD/bin:$PATH"
437
- comate state demo set phase archive >/dev/null
438
- comate state demo set verify_result pass >/dev/null
439
- run comate archive demo
440
- [ "$status" -eq 0 ]
441
- local an="$(date +%Y-%m-%d)-demo"
442
- [[ "$(cat docs/superpowers/specs/dd.md)" == *"openspec/changes/archive/$an/specs/cap/spec.md"* ]]
443
- [[ "$(cat docs/superpowers/specs/dd.md)" == *"comate_change: demo"* ]]
444
- [[ "$(cat docs/superpowers/specs/dd.md)" == *"archived-with: $an"* ]]
445
- }
446
-
447
- # --- scale ---
448
-
449
- @test "scale sets verify_mode full when many tasks" {
450
- comate state demo init full
451
- local d="openspec/changes/demo"
452
- printf -- '- [ ] a\n- [ ] b\n- [ ] c\n- [ ] d\n' > "$d/tasks.md"
453
- comate state demo scale
454
- [ "$(comate state demo get verify_mode)" = "full" ]
455
- }
456
-
457
- @test "scale sets verify_mode light when few tasks" {
458
- comate state demo init full
459
- printf -- '- [ ] a\n' > "openspec/changes/demo/tasks.md"
460
- comate state demo scale
461
- [ "$(comate state demo get verify_mode)" = "light" ]
462
- }
463
-
464
- # --- source tracking ---
465
-
466
- @test "init defaults source_kind conversation with null manifest" {
467
- comate state demo init full
468
- [ "$(comate state demo get source_kind)" = "conversation" ]
469
- [ "$(comate state demo get source_manifest)" = "null" ]
470
- }
471
-
472
- @test "sources check on conversation source is a no-op pass" {
473
- comate state demo init full
474
- run comate sources demo check
475
- [ "$status" -eq 0 ]
476
- }
477
-
478
- @test "sources track records kb file and flips source_kind to file" {
479
- comate state demo init full
480
- mkdir -p docs/knowledge-base/cb
481
- printf '# source\n' > docs/knowledge-base/cb/source.md
482
- run comate sources demo track docs/knowledge-base/cb/source.md --note "main entry"
483
- [ "$status" -eq 0 ]
484
- [ "$(comate state demo get source_kind)" = "file" ]
485
- [ "$(comate state demo get source_manifest)" = "openspec/changes/demo/.comate/sources.manifest" ]
486
- run comate sources demo list
487
- [[ "$output" == *"path:docs/knowledge-base/cb/source.md"* ]]
488
- [[ "$output" == *"note:main entry"* ]]
489
- }
490
-
491
- @test "sources track rejects file outside knowledge-base" {
492
- comate state demo init full
493
- printf 'x\n' > docs/outside.md
494
- run comate sources demo track docs/outside.md
495
- [ "$(comate state demo get source_kind)" = "conversation" ]
496
- [ "$(comate state demo get source_manifest)" = "null" ]
497
- }
498
-
499
- @test "sources check detects drift after file edited" {
500
- comate state demo init full
501
- mkdir -p docs/knowledge-base/cb
502
- printf '# source\n' > docs/knowledge-base/cb/source.md
503
- comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
504
- printf 'edited\n' >> docs/knowledge-base/cb/source.md
505
- run comate sources demo check
506
- [ "$status" -eq 1 ]
507
- [[ "$output" == *"drifted"* ]]
508
- }
509
-
510
- @test "sources find reverse-looks-up a tracked file" {
511
- comate state demo init full
512
- mkdir -p docs/knowledge-base/cb
513
- printf '# source\n' > docs/knowledge-base/cb/source.md
514
- comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
515
- run comate sources find docs/knowledge-base/cb/source.md
516
- [ "$status" -eq 0 ]
517
- [[ "$output" == *"demo"* ]]
518
- }
519
-
520
- @test "build guard blocks when tracked source drifts" {
521
- comate state demo init fix
522
- local d="openspec/changes/demo"
523
- printf '# p\n' > "$d/proposal.md"; printf '# d\n' > "$d/design.md"; printf -- '- [x] t1\n' > "$d/tasks.md"
524
- mkdir -p docs/knowledge-base/cb
525
- printf '# source\n' > docs/knowledge-base/cb/source.md
526
- comate sources demo track docs/knowledge-base/cb/source.md >/dev/null 2>&1
527
- comate guard demo open --apply >/dev/null 2>&1
528
- comate handoff demo build --write >/dev/null 2>&1
529
- printf 'edited\n' >> docs/knowledge-base/cb/source.md
530
- run comate guard demo build --json
531
- [ "$status" -eq 1 ]
532
- [[ "$output" == *'"block":true'* ]]
533
- }
534
-
535
- # --- doctor + usage ---
536
-
537
- @test "doctor exits 0 when core tools present" {
538
- run comate doctor
539
- [ "$status" -eq 0 ]
540
- }
541
-
542
- @test "doctor recognizes Claude Code skills-cli Superpowers layout" {
543
- local fake_home="$WORK/fake-home"
544
- mkdir -p "$fake_home/.claude/skills/using-superpowers"
545
- printf '# using-superpowers\n' > "$fake_home/.claude/skills/using-superpowers/SKILL.md"
546
- run env HOME="$fake_home" bash "$COMATE" doctor
547
- [ "$status" -eq 0 ]
548
- [[ "$output" == *"Superpowers skills found"* ]]
549
- [[ "$output" != *"Superpowers skills not found"* ]]
550
- }
551
-
552
- @test "dependency archived requires an archived=true record" {
553
- comate state dep init full
554
- run comate dependency dep archived
555
- [ "$status" -eq 1 ]
556
- comate state dep set phase archive >/dev/null
557
- comate state dep transition archived >/dev/null
558
- mkdir -p openspec/changes/archive
559
- mv openspec/changes/dep openspec/changes/archive/2026-08-20-dep
560
- run comate dependency dep archived
561
- [ "$status" -eq 0 ]
562
- }
563
-
564
- @test "top-level usage error returns code 2" {
565
- run comate frobnicate
566
- [ "$status" -eq 2 ]
567
- }
568
-
569
- # --- gate ---
570
-
571
- seed_prd() {
572
- mkdir -p docs/knowledge-base/requirements/test
573
- printf '# PRD-001\n功能清单:登录、登出\n' > \
574
- docs/knowledge-base/requirements/test/PRD-001-demo.md
575
- echo "docs/knowledge-base/requirements/test/PRD-001-demo.md"
576
- }
577
-
578
- seed_gate_skeleton() {
579
- local prd="$1" change="$2" d="openspec/changes/$2"
580
- mkdir -p "$d"
581
- printf 'schema: specs-driven\n' > "$d/.openspec.yaml"
582
- printf '# proposal\n' > "$d/proposal.md"
583
- printf '# design\n' > "$d/design.md"
584
- printf -- '- [ ] task\n' > "$d/tasks.md"
585
- comate state "$change" init full >/dev/null
586
- comate sources "$change" track "$prd" >/dev/null
587
- }
588
-
589
- @test "gate review creates gate.yaml next to PRD" {
590
- local prd; prd=$(seed_prd)
591
- run comate gate "$prd" review
592
- [ "$status" -eq 0 ]
593
- [ -f "docs/knowledge-base/requirements/test/PRD-001.gate.yaml" ]
594
- }
595
-
596
- @test "gate rejects PRD filenames outside the naming convention" {
597
- mkdir -p docs/knowledge-base/requirements/test
598
- printf '# PRD\n' > docs/knowledge-base/requirements/test/PRD-1.md
599
- run comate gate docs/knowledge-base/requirements/test/PRD-1.md review
600
- [ "$status" -eq 2 ]
601
- [[ "$output" == *"PRD-NNN-title.md"* ]]
602
- }
603
-
604
- @test "gate names key off PRD-NNN stem, dropping title" {
605
- local prd; prd=$(seed_prd)
606
- run comate gate "$prd" review
607
- [ "$status" -eq 0 ]
608
- # gate.yaml and review report drop the -demo title, key off PRD-001
609
- [ -f "docs/knowledge-base/requirements/test/PRD-001.gate.yaml" ]
610
- [ ! -f "docs/knowledge-base/requirements/test/PRD-001-demo.gate.yaml" ]
611
- [[ "$output" == *"PRD-001-REVIEW.md"* ]]
612
- grep -q "review_report: docs/knowledge-base/requirements/test/PRD-001-REVIEW.md" \
613
- "docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
614
- }
615
-
616
- @test "gate review writes reviewing status and pins prd_hash" {
617
- local prd; prd=$(seed_prd)
618
- comate gate "$prd" review >/dev/null
619
- local gate="docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
620
- run comate gate "$prd" status
621
- [ "$status" -eq 0 ]
622
- [[ "$output" == *"reviewing"* ]]
623
- local hash; hash=$(grep '^prd_hash:' "$gate" | awk '{print $2}')
624
- [ -n "$hash" ]
625
- [ "$hash" != "null" ]
626
- }
627
-
628
- @test "gate check blocks when status is reviewing" {
629
- local prd; prd=$(seed_prd)
630
- comate gate "$prd" review >/dev/null
631
- run comate gate "$prd" check
632
- [ "$status" -eq 1 ]
633
- [[ "$output" == *"BLOCK"* ]]
634
- }
635
-
636
- @test "gate pass blocked when blocking_open not zero" {
637
- local prd; prd=$(seed_prd)
638
- comate gate "$prd" review >/dev/null
639
- comate gate "$prd" set score 80 >/dev/null
640
- run comate gate "$prd" pass
641
- [ "$status" -eq 1 ]
642
- [[ "$output" == *"blocking_open"* ]]
643
- }
644
-
645
- @test "gate pass blocked when review report missing" {
646
- local prd; prd=$(seed_prd)
647
- comate gate "$prd" review >/dev/null
648
- comate gate "$prd" set score 90 >/dev/null
649
- comate gate "$prd" set blocking_open 0 >/dev/null
650
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
651
- run comate gate "$prd" pass
652
- [ "$status" -eq 1 ]
653
- [[ "$output" == *"report"* ]]
654
- }
655
-
656
- @test "gate pass blocks without a confirmed changes map" {
657
- local prd; prd=$(seed_prd)
658
- comate gate "$prd" review >/dev/null
659
- comate gate "$prd" set score 90 >/dev/null
660
- comate gate "$prd" set blocking_open 0 >/dev/null
661
- printf '# report\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
662
- run comate gate "$prd" pass
663
- [ "$status" -eq 1 ]
664
- [[ "$output" == *"changes_map"* ]]
665
- }
666
-
667
- @test "gate rejects changes_map entries without a function mapping" {
668
- local prd; prd=$(seed_prd)
669
- comate gate "$prd" review >/dev/null
670
- run comate gate "$prd" set changes_map "login-flow"
671
- [ "$status" -eq 2 ]
672
- [[ "$output" == *"malformed changes_map"* ]]
673
- }
674
-
675
- @test "gate rejects duplicate change names in changes_map" {
676
- local prd; prd=$(seed_prd)
677
- comate gate "$prd" review >/dev/null
678
- run comate gate "$prd" set changes_map "login-flow:功能项1;login-flow:功能项2"
679
- [ "$status" -eq 2 ]
680
- [[ "$output" == *"duplicate change"* ]]
681
- }
682
-
683
- @test "gate rejects an empty middle entry in changes_map" {
684
- local prd; prd=$(seed_prd)
685
- comate gate "$prd" review >/dev/null
686
- run comate gate "$prd" set changes_map "login-flow:功能项1;;logout-flow:功能项2"
687
- [ "$status" -eq 2 ]
688
- [[ "$output" == *"empty entry"* ]]
689
- }
690
-
691
- @test "gate rejects a trailing empty entry in changes_map" {
692
- local prd; prd=$(seed_prd)
693
- comate gate "$prd" review >/dev/null
694
- run comate gate "$prd" set changes_map "login-flow:功能项1;"
695
- [ "$status" -eq 2 ]
696
- [[ "$output" == *"empty entry"* ]]
697
- }
698
-
699
- @test "gate rejects an empty function mapping" {
700
- local prd; prd=$(seed_prd)
701
- comate gate "$prd" review >/dev/null
702
- run comate gate "$prd" set changes_map "login-flow:"
703
- [ "$status" -eq 2 ]
704
- [[ "$output" == *"empty function mapping"* ]]
705
- }
706
-
707
- @test "gate rejects an all-whitespace function mapping" {
708
- local prd; prd=$(seed_prd)
709
- comate gate "$prd" review >/dev/null
710
- run comate gate "$prd" set changes_map "login-flow: "
711
- [ "$status" -eq 2 ]
712
- [[ "$output" == *"empty function mapping"* ]]
713
- }
714
-
715
- @test "gate keeps a passed changes_map immutable except for idempotent writes" {
716
- local prd; prd=$(seed_prd)
717
- comate gate "$prd" review >/dev/null
718
- comate gate "$prd" set score 90 >/dev/null
719
- comate gate "$prd" set blocking_open 0 >/dev/null
720
- comate gate "$prd" set changes_map "a:功能项1;b:功能项2;c:功能项3;d:功能项4" >/dev/null
721
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
722
- comate gate "$prd" pass >/dev/null
723
- run comate gate "$prd" set changes_map "a:功能项1;b:功能项2;c:功能项3;d:功能项4"
724
- [ "$status" -eq 0 ]
725
- run comate gate "$prd" set changes_map "a:功能项1"
726
- [ "$status" -eq 1 ]
727
- [[ "$output" == *"immutable after gate pass"* ]]
728
- run comate gate "$prd" fanout-status --json
729
- [[ "$output" == *'"expected":4'* ]]
730
- }
731
-
732
- @test "gate pass --tbd requires unresolved blockers" {
733
- local prd; prd=$(seed_prd)
734
- comate gate "$prd" review >/dev/null
735
- comate gate "$prd" set score 90 >/dev/null
736
- comate gate "$prd" set blocking_open 0 >/dev/null
737
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
738
- printf '# report\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
739
- run comate gate "$prd" pass --tbd
740
- [ "$status" -eq 1 ]
741
- [[ "$output" == *"requires at least one"* ]]
742
- }
743
-
744
- @test "gate pass succeeds and check passes after report written" {
745
- local prd; prd=$(seed_prd)
746
- comate gate "$prd" review >/dev/null
747
- comate gate "$prd" set score 90 >/dev/null
748
- comate gate "$prd" set blocking_open 0 >/dev/null
749
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
750
- printf '# 评审报告\n' > \
751
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
752
- run comate gate "$prd" pass
753
- [ "$status" -eq 0 ]
754
- run comate gate "$prd" check
755
- [ "$status" -eq 0 ]
756
- }
757
-
758
- @test "gate pass --tbd sets passed-with-tbd" {
759
- local prd; prd=$(seed_prd)
760
- comate gate "$prd" review >/dev/null
761
- comate gate "$prd" set score 65 >/dev/null
762
- comate gate "$prd" set blocking_open 2 >/dev/null
763
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
764
- printf '# 评审报告\n' > \
765
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
766
- run comate gate "$prd" pass --tbd
767
- [ "$status" -eq 0 ]
768
- run comate gate "$prd" status
769
- [[ "$output" == *"passed-with-tbd"* ]]
770
- }
771
-
772
- @test "gate check blocks after PRD modified post-pass (stale)" {
773
- local prd; prd=$(seed_prd)
774
- comate gate "$prd" review >/dev/null
775
- comate gate "$prd" set score 90 >/dev/null
776
- comate gate "$prd" set blocking_open 0 >/dev/null
777
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
778
- printf '# 评审报告\n' > \
779
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
780
- comate gate "$prd" pass >/dev/null
781
- # mutate PRD after passing
782
- printf '\n额外内容\n' >> "$prd"
783
- run comate gate "$prd" check
784
- [ "$status" -eq 1 ]
785
- [[ "$output" == *"stale"* ]] || [[ "$output" == *"hash mismatch"* ]]
786
- }
787
-
788
- @test "gate status reports stale on hash drift without mutating file" {
789
- local prd; prd=$(seed_prd)
790
- comate gate "$prd" review >/dev/null
791
- comate gate "$prd" set score 90 >/dev/null
792
- comate gate "$prd" set blocking_open 0 >/dev/null
793
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
794
- printf '# 评审报告\n' > \
795
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
796
- comate gate "$prd" pass >/dev/null
797
- printf '\n改动\n' >> "$prd"
798
- run comate gate "$prd" status
799
- [ "$status" -eq 0 ]
800
- [[ "$output" == *"stale"* ]]
801
- # gate.yaml should still say passed (status is computed, not written by status cmd)
802
- local gate="docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
803
- local stored; stored=$(grep '^gate_status:' "$gate" | awk '{print $2}')
804
- [ "$stored" = "passed" ]
805
- }
806
-
807
- @test "gate spawned records change name in spawned_changes" {
808
- local prd; prd=$(seed_prd)
809
- comate gate "$prd" review >/dev/null
810
- comate gate "$prd" set score 90 >/dev/null
811
- comate gate "$prd" set blocking_open 0 >/dev/null
812
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
813
- printf '# 报告\n' > \
814
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
815
- comate gate "$prd" pass >/dev/null
816
- seed_gate_skeleton "$prd" login-flow
817
- run comate gate "$prd" spawned login-flow
818
- [ "$status" -eq 0 ]
819
- local gate="docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
820
- local sc; sc=$(grep '^spawned_changes:' "$gate" | sed 's/spawned_changes: //')
821
- [[ "$sc" == *"login-flow"* ]]
822
- }
823
-
824
- @test "gate spawned appends second change comma-joined" {
825
- local prd; prd=$(seed_prd)
826
- comate gate "$prd" review >/dev/null
827
- comate gate "$prd" set score 90 >/dev/null
828
- comate gate "$prd" set blocking_open 0 >/dev/null
829
- comate gate "$prd" set changes_map "login-flow:功能项1;logout-flow:功能项2" >/dev/null
830
- printf '# 报告\n' > \
831
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
832
- comate gate "$prd" pass >/dev/null
833
- seed_gate_skeleton "$prd" login-flow
834
- seed_gate_skeleton "$prd" logout-flow
835
- comate gate "$prd" spawned login-flow >/dev/null
836
- comate gate "$prd" spawned logout-flow >/dev/null
837
- local gate="docs/knowledge-base/requirements/test/PRD-001.gate.yaml"
838
- local sc; sc=$(grep '^spawned_changes:' "$gate" | sed 's/spawned_changes: //')
839
- [[ "$sc" == *"login-flow"* ]]
840
- [[ "$sc" == *"logout-flow"* ]]
841
- }
842
-
843
- @test "gate spawned remains idempotent after an already recorded change leaves the active directory" {
844
- local prd; prd=$(seed_prd)
845
- comate gate "$prd" review >/dev/null
846
- comate gate "$prd" set score 90 >/dev/null
847
- comate gate "$prd" set blocking_open 0 >/dev/null
848
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
849
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
850
- comate gate "$prd" pass >/dev/null
851
- seed_gate_skeleton "$prd" login-flow
852
- comate gate "$prd" spawned login-flow >/dev/null
853
- mkdir -p openspec/changes/archive
854
- mv openspec/changes/login-flow openspec/changes/archive/2026-08-28-login-flow
855
- run comate gate "$prd" spawned login-flow
856
- [ "$status" -eq 0 ]
857
- [[ "$output" == *"already recorded"* ]]
858
- }
859
-
860
- @test "gate fanout status reports missing changes instead of treating one of four as complete" {
861
- local prd; prd=$(seed_prd)
862
- comate gate "$prd" review >/dev/null
863
- comate gate "$prd" set score 90 >/dev/null
864
- comate gate "$prd" set blocking_open 0 >/dev/null
865
- comate gate "$prd" set changes_map "login-flow:功能项1;logout-flow:功能项2;ops:功能项3;report:功能项4" >/dev/null
866
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
867
- comate gate "$prd" pass >/dev/null
868
- seed_gate_skeleton "$prd" login-flow
869
- comate gate "$prd" spawned login-flow >/dev/null
870
- run comate gate "$prd" fanout-status --json
871
- [ "$status" -eq 0 ]
872
- [[ "$output" == *'"expected":4'* ]]
873
- [[ "$output" == *'"spawned":1'* ]]
874
- [[ "$output" == *'"complete":false'* ]]
875
- [[ "$output" == *'logout-flow'* ]]
876
- }
877
-
878
- @test "gate spawned rejects a change outside the confirmed map" {
879
- local prd; prd=$(seed_prd)
880
- comate gate "$prd" review >/dev/null
881
- comate gate "$prd" set score 90 >/dev/null
882
- comate gate "$prd" set blocking_open 0 >/dev/null
883
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
884
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
885
- comate gate "$prd" pass >/dev/null
886
- seed_gate_skeleton "$prd" other-flow
887
- run comate gate "$prd" spawned other-flow
888
- [ "$status" -eq 1 ]
889
- [[ "$output" == *"not present in changes_map"* ]]
890
- }
891
-
892
- @test "gate fanout status becomes complete only after every mapped change is spawned" {
893
- local prd; prd=$(seed_prd)
894
- comate gate "$prd" review >/dev/null
895
- comate gate "$prd" set score 90 >/dev/null
896
- comate gate "$prd" set blocking_open 0 >/dev/null
897
- comate gate "$prd" set changes_map "login-flow:功能项1;logout-flow:功能项2" >/dev/null
898
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
899
- comate gate "$prd" pass >/dev/null
900
- seed_gate_skeleton "$prd" login-flow
901
- seed_gate_skeleton "$prd" logout-flow
902
- comate gate "$prd" spawned login-flow >/dev/null
903
- comate gate "$prd" spawned logout-flow >/dev/null
904
- run comate gate "$prd" fanout-status
905
- [ "$status" -eq 0 ]
906
- [[ "$output" == *"complete=true"* ]]
907
- [[ "$output" == *"missing=none"* ]]
908
- }
909
-
910
- @test "gate spawned rejects an incomplete change skeleton" {
911
- local prd; prd=$(seed_prd)
912
- comate gate "$prd" review >/dev/null
913
- comate gate "$prd" set score 90 >/dev/null
914
- comate gate "$prd" set blocking_open 0 >/dev/null
915
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
916
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
917
- comate gate "$prd" pass >/dev/null
918
- mkdir -p openspec/changes/login-flow
919
- run comate gate "$prd" spawned login-flow
920
- [ "$status" -eq 1 ]
921
- [[ "$output" == *"skeleton incomplete"* ]]
922
- }
923
-
924
- @test "gate spawned blocks a new registration after the PRD becomes stale" {
925
- local prd; prd=$(seed_prd)
926
- comate gate "$prd" review >/dev/null
927
- comate gate "$prd" set score 90 >/dev/null
928
- comate gate "$prd" set blocking_open 0 >/dev/null
929
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
930
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
931
- comate gate "$prd" pass >/dev/null
932
- seed_gate_skeleton "$prd" login-flow
933
- printf '\nchanged\n' >> "$prd"
934
- run comate gate "$prd" spawned login-flow
935
- [ "$status" -eq 1 ]
936
- [[ "$output" == *"hash mismatch"* ]]
937
- }
938
-
939
- @test "gate spawned accepts a normalized PRD path recorded in the source manifest" {
940
- local prd; prd=$(seed_prd)
941
- prd="./$prd"
942
- comate gate "$prd" review >/dev/null
943
- comate gate "$prd" set score 90 >/dev/null
944
- comate gate "$prd" set blocking_open 0 >/dev/null
945
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
946
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
947
- comate gate "$prd" pass >/dev/null
948
- seed_gate_skeleton "$prd" login-flow
949
- run comate gate "$prd" spawned login-flow
950
- [ "$status" -eq 0 ]
951
- }
952
-
953
- @test "gate spawned rejects a truncated PRD source record without sha256" {
954
- local prd; prd=$(seed_prd)
955
- comate gate "$prd" review >/dev/null
956
- comate gate "$prd" set score 90 >/dev/null
957
- comate gate "$prd" set blocking_open 0 >/dev/null
958
- comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
959
- printf '# 报告\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
960
- comate gate "$prd" pass >/dev/null
961
- seed_gate_skeleton "$prd" login-flow
962
- printf 'path:%s\n' "$prd" > openspec/changes/login-flow/.comate/sources.manifest
963
- run comate gate "$prd" spawned login-flow
964
- [ "$status" -eq 1 ]
965
- [[ "$output" == *"complete path/sha256 record"* ]]
966
- }
967
-
968
- @test "gate status returns none when gate does not exist" {
969
- mkdir -p docs/knowledge-base/requirements/test
970
- run comate gate "docs/knowledge-base/requirements/test/PRD-001-demo.md" status
971
- [ "$status" -eq 0 ]
972
- [[ "$output" == *"none"* ]]
973
- }
974
-
975
- @test "gate unknown subcommand returns usage error" {
976
- local prd; prd=$(seed_prd)
977
- run comate gate "$prd" frobnicate
978
- [ "$status" -eq 2 ]
979
- }
980
-
981
- @test "gate review re-opens reviewing when PRD changes after pass" {
982
- local prd; prd=$(seed_prd)
983
- comate gate "$prd" review >/dev/null
984
- comate gate "$prd" set score 90 >/dev/null
985
- comate gate "$prd" set blocking_open 0 >/dev/null
986
- comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
987
- printf '# 报告\n' > \
988
- "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
989
- comate gate "$prd" pass >/dev/null
990
- printf '\n改动\n' >> "$prd"
991
- # re-run review after PRD change — should reset to reviewing
992
- run comate gate "$prd" review
993
- [ "$status" -eq 0 ]
994
- run comate gate "$prd" status
995
- [[ "$output" == *"reviewing"* ]]
996
- }
997
-
998
- @test "gate set rejects unknown field" {
999
- local prd; prd=$(seed_prd)
1000
- comate gate "$prd" review >/dev/null
1001
- run comate gate "$prd" set nonexistent_field foo
1002
- [ "$status" -eq 2 ]
1003
- }
1004
-
1005
- @test "gate set rejects invalid gate_status value" {
1006
- local prd; prd=$(seed_prd)
1007
- comate gate "$prd" review >/dev/null
1008
- run comate gate "$prd" set gate_status flying
1009
- [ "$status" -eq 2 ]
1010
- }
1011
-
1012
- @test "gate status --json emits json with status field" {
1013
- local prd; prd=$(seed_prd)
1014
- comate gate "$prd" review >/dev/null
1015
- run comate gate "$prd" status --json
1016
- [ "$status" -eq 0 ]
1017
- [[ "$output" == *'"status"'* ]]
1018
- [[ "$output" == *'"reviewing"'* ]]
1019
- }