@house365/comate 0.9.5 → 0.9.7

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.
@@ -19,7 +19,7 @@
19
19
  # sources find <file> — reverse lookup: changes citing a file
20
20
  # gate <prd> review — create/refresh gate.yaml next to the PRD, pin hash
21
21
  # gate <prd> set <field> <value> — write score | blocking_open | gate_status | …
22
- # gate <prd> pass [--tbd] — mark passed (needs blocking_open=0 + report)
22
+ # gate <prd> pass [--tbd] — pass clean gate or explicitly accepted TBD blockers
23
23
  # gate <prd> spawned <change> — record a change fanned out from this PRD
24
24
  # gate <prd> status [--json] — print gate status (computes stale on hash drift)
25
25
  # gate <prd> check — precondition guard before spawning a change
@@ -60,7 +60,7 @@ Usage: comate.sh <command> ...
60
60
  state <name> init <workflow> full | fix
61
61
  state <name> get <field>
62
62
  state <name> set <field> <value>
63
- state <name> transition <event> open-complete|design-complete|build-complete|verify-pass|verify-fail|archived
63
+ state <name> transition <event> open-complete|design-complete|build-complete|verify-pass|verify-fail|fix-upgrade|archived
64
64
  state <name> check <phase> [--json]
65
65
  state <name> recover <phase>
66
66
  state <name> scale
@@ -72,9 +72,10 @@ Usage: comate.sh <command> ...
72
72
  sources <name> check
73
73
  sources <name> list
74
74
  sources find <file>
75
+ dependency <name> archived verify an exact archived change exists
75
76
  gate <prd> review create/refresh gate.yaml next to PRD, pin hash
76
77
  gate <prd> set <field> <value> score | blocking_open | gate_status | …
77
- gate <prd> pass [--tbd] mark passed (needs blocking_open=0 + report)
78
+ gate <prd> pass [--tbd] pass: zero blockers; --tbd: accepted blockers
78
79
  gate <prd> spawned <change> record a change fanned out from this PRD
79
80
  gate <prd> status [--json]
80
81
  gate <prd> check precondition guard before spawning a change
@@ -138,6 +139,8 @@ state_init() {
138
139
  schema_version: $COMATE_SCHEMA_VERSION
139
140
  workflow: $workflow
140
141
  phase: open
142
+ phase_step: null
143
+ phase_step_data: null
141
144
  build_mode: $build_mode
142
145
  tdd_mode: $tdd_mode
143
146
  isolation: $isolation
@@ -237,7 +240,7 @@ state_transition() {
237
240
  validate_change_name "$name"
238
241
  validate_enum_event() {
239
242
  case "$1" in
240
- open-complete|design-complete|build-complete|verify-pass|verify-fail|archived) return 0 ;;
243
+ open-complete|design-complete|build-complete|verify-pass|verify-fail|fix-upgrade|archived) return 0 ;;
241
244
  *) die "$COMATE_USAGE" "ERROR: unknown event '$1'" ;;
242
245
  esac
243
246
  }
@@ -246,6 +249,13 @@ state_transition() {
246
249
  local yaml; yaml=$(require_yaml "$name")
247
250
  validate_flat_yaml "$yaml"
248
251
 
252
+ # phase_step is local to the current phase. Carrying it into the next phase
253
+ # makes identical step numbers ambiguous and can skip mandatory work.
254
+ _clear_phase_progress() {
255
+ yaml_set "$yaml" phase_step null
256
+ yaml_set "$yaml" phase_step_data null
257
+ }
258
+
249
259
  case "$event" in
250
260
  open-complete)
251
261
  _require_phase "$yaml" open
@@ -255,10 +265,12 @@ state_transition() {
255
265
  else
256
266
  yaml_set "$yaml" phase build
257
267
  fi
268
+ _clear_phase_progress
258
269
  ;;
259
270
  design-complete)
260
271
  _require_phase "$yaml" design
261
272
  yaml_set "$yaml" phase build
273
+ _clear_phase_progress
262
274
  ;;
263
275
  build-complete)
264
276
  _require_phase "$yaml" build
@@ -267,6 +279,7 @@ state_transition() {
267
279
  yaml_set "$yaml" verify_result pending
268
280
  yaml_set "$yaml" verification_report null
269
281
  yaml_set "$yaml" branch_status pending
282
+ _clear_phase_progress
270
283
  ;;
271
284
  verify-pass)
272
285
  _require_phase "$yaml" verify
@@ -274,12 +287,36 @@ state_transition() {
274
287
  yaml_set "$yaml" verify_result pass
275
288
  yaml_set "$yaml" phase archive
276
289
  yaml_set "$yaml" verified_at "$(today)"
290
+ _clear_phase_progress
277
291
  ;;
278
292
  verify-fail)
279
293
  _require_phase "$yaml" verify
280
- yaml_set "$yaml" verify_result fail
294
+ # The failure is captured in the report; once routed back to build the
295
+ # active verification state must be pending or the orchestrator loops.
296
+ yaml_set "$yaml" verify_result pending
281
297
  yaml_set "$yaml" phase build
298
+ yaml_set "$yaml" verification_report null
299
+ yaml_set "$yaml" branch_status pending
300
+ _clear_phase_progress
301
+ ;;
302
+ fix-upgrade)
303
+ [ "$(yaml_get "$yaml" workflow)" = "fix" ] || \
304
+ die "$COMATE_BLOCK" "ERROR: fix-upgrade requires workflow=fix"
305
+ case "$(yaml_get "$yaml" phase)" in
306
+ open|build) ;;
307
+ *) die "$COMATE_BLOCK" "ERROR: fix-upgrade is only valid from open or build" ;;
308
+ esac
309
+ yaml_set "$yaml" workflow full
310
+ yaml_set "$yaml" phase design
311
+ yaml_set "$yaml" build_mode null
312
+ yaml_set "$yaml" tdd_mode null
313
+ yaml_set "$yaml" isolation null
314
+ yaml_set "$yaml" verify_mode null
315
+ yaml_set "$yaml" direct_override false
316
+ yaml_set "$yaml" verify_result pending
317
+ yaml_set "$yaml" verification_report null
282
318
  yaml_set "$yaml" branch_status pending
319
+ _clear_phase_progress
283
320
  ;;
284
321
  archived)
285
322
  _require_phase "$yaml" archive
@@ -498,15 +535,19 @@ state_recover() {
498
535
  yaml="$dir/.comate.yaml"
499
536
  [ -f "$yaml" ] || die "$COMATE_CORRUPT" "ERROR: .comate.yaml not found at $yaml"
500
537
 
501
- local phase workflow
538
+ local phase workflow phase_step phase_step_data
502
539
  phase=$(yaml_get "$yaml" phase)
503
540
  workflow=$(yaml_get "$yaml" workflow)
541
+ phase_step=$(yaml_get "$yaml" phase_step)
542
+ phase_step_data=$(yaml_get "$yaml" phase_step_data)
504
543
  # If a phase arg was passed, honor it for reporting; else use stored phase.
505
544
  [ -n "$phase_arg" ] && phase="$phase_arg"
506
545
 
507
546
  echo "=== Recovery Context: $name ==="
508
547
  echo "Phase: $phase"
509
548
  echo "Workflow: $workflow"
549
+ echo "Phase step: ${phase_step:-null}"
550
+ echo "Phase step data: ${phase_step_data:-null}"
510
551
  echo ""
511
552
 
512
553
  local design_doc plan verify_result verify_mode verification_report
@@ -578,7 +619,11 @@ state_recover() {
578
619
  echo "Tasks: tasks.md MISSING"
579
620
  fi
580
621
  echo ""
581
- if [ "$isolation" = "null" ] || [ -z "$isolation" ]; then
622
+ if [[ "$phase_step" == build.blocked-by-* ]]; then
623
+ local dep="$phase_step_data"
624
+ if [ -z "$dep" ] || [ "$dep" = "null" ]; then dep="${phase_step#build.blocked-by-}"; fi
625
+ echo "Recovery action: Blocked by $dep. Run: comate.sh dependency $dep archived"
626
+ elif [ "$isolation" = "null" ] || [ -z "$isolation" ]; then
582
627
  echo "Recovery action: Isolation not selected. AskUserQuestion for branch/worktree."
583
628
  elif [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; then
584
629
  echo "Recovery action: Build mode not selected. AskUserQuestion for execution method."
@@ -600,7 +645,9 @@ state_recover() {
600
645
  _field_status "branch_status" "$branch_status"
601
646
  _field_status "impl_hash" "$impl_hash"
602
647
  echo ""
603
- if [ "$verify_result" = "pass" ] && [ "$branch_status" = "handled" ]; then
648
+ if [ "$branch_status" = "discarded" ]; then
649
+ echo "Recovery action: Work was discarded. Run verify-fail to return to build before reimplementing, or explicitly abandon the change."
650
+ elif [ "$verify_result" = "pass" ] && [ "$branch_status" = "handled" ]; then
604
651
  echo "Recovery action: Verification complete. Run guard to transition to archive."
605
652
  elif [ "$verify_result" = "pass" ]; then
606
653
  echo "Recovery action: Verified but branch not handled. Complete branch handling, set branch_status=handled."
@@ -1345,6 +1392,23 @@ sources_find() {
1345
1392
  return 0
1346
1393
  }
1347
1394
 
1395
+ # dependency <name> archived — prove completion from archived state, rather
1396
+ # than inferring it from absence in `openspec list`.
1397
+ dependency_archived() {
1398
+ local name="$1"
1399
+ validate_change_name "$name"
1400
+ [ ! -d "openspec/changes/$name" ] || \
1401
+ die "$COMATE_BLOCK" "BLOCK: dependency '$name' is still active"
1402
+ local found="" yaml
1403
+ while IFS= read -r yaml; do
1404
+ [ "$(yaml_get "$yaml" archived)" = "true" ] && found="$yaml"
1405
+ done < <(find openspec/changes/archive -mindepth 2 -maxdepth 2 \
1406
+ -path "*/????-??-??-${name}/.comate.yaml" -type f 2>/dev/null | sort)
1407
+ [ -n "$found" ] || \
1408
+ die "$COMATE_BLOCK" "BLOCK: no archived=true record found for dependency '$name'"
1409
+ log_pass "[DEPENDENCY] archived: $name ($found)"
1410
+ }
1411
+
1348
1412
  # =====================================================================
1349
1413
  # gate — phase 0: PRD quality gate + split precondition
1350
1414
  # =====================================================================
@@ -1416,10 +1480,8 @@ gate_set() {
1416
1480
  log_pass "[GATE] set $field=$value"
1417
1481
  }
1418
1482
 
1419
- # gate <prd> pass [--tbd] — flip to passed/passed-with-tbd. Hard precondition:
1420
- # blocking_open must be 0 (no open blocking issues). Re-pins prd_hash so a later
1421
- # edit invalidates the pass. --tbd records that the user authorised proceeding
1422
- # with unresolved TBDs (spawned changes must carry ⚠ markers).
1483
+ # gate <prd> pass [--tbd] — flip to passed/passed-with-tbd. Normal pass requires
1484
+ # zero blockers; --tbd requires at least one explicitly accepted blocker.
1423
1485
  gate_pass() {
1424
1486
  local prd; prd=$(require_prd "$1")
1425
1487
  local tbd=0
@@ -1427,9 +1489,22 @@ gate_pass() {
1427
1489
  local gate; gate=$(gate_path_for "$prd")
1428
1490
  [ -f "$gate" ] || die "$COMATE_CORRUPT" "ERROR: gate not found (run 'gate review' first): $gate"
1429
1491
 
1430
- local blocking; blocking=$(yaml_get "$gate" blocking_open)
1431
- if [ "$blocking" != "0" ]; then
1432
- die "$COMATE_BLOCK" "BLOCK: blocking_open='$blocking' — resolve all blocking issues (set blocking_open 0) before passing the gate"
1492
+ local score blocking changes_map
1493
+ score=$(yaml_get "$gate" score)
1494
+ blocking=$(yaml_get "$gate" blocking_open)
1495
+ changes_map=$(yaml_get "$gate" changes_map)
1496
+ [[ "$score" =~ ^[0-9]+$ ]] && [ "$score" -ge 0 ] && [ "$score" -le 100 ] || \
1497
+ die "$COMATE_BLOCK" "BLOCK: score must be an integer from 0 to 100 (got '$score')"
1498
+ [[ "$blocking" =~ ^[0-9]+$ ]] || \
1499
+ die "$COMATE_BLOCK" "BLOCK: blocking_open must be a non-negative integer (got '$blocking')"
1500
+ if [ "$tbd" -eq 1 ]; then
1501
+ [ "$blocking" -gt 0 ] || \
1502
+ die "$COMATE_BLOCK" "BLOCK: --tbd requires at least one recorded unresolved blocker"
1503
+ elif [ "$blocking" -ne 0 ]; then
1504
+ die "$COMATE_BLOCK" "BLOCK: blocking_open='$blocking' — resolve all blocking issues or use --tbd after explicit user authorization"
1505
+ fi
1506
+ if [ -z "$changes_map" ] || [ "$changes_map" = "null" ]; then
1507
+ die "$COMATE_BLOCK" "BLOCK: changes_map is required before passing the gate"
1433
1508
  fi
1434
1509
  local report; report=$(yaml_get "$gate" review_report)
1435
1510
  if [ -z "$report" ] || [ "$report" = "null" ] || ! file_nonempty "$report"; then
@@ -1703,7 +1778,7 @@ status() {
1703
1778
  echo "No active changes."
1704
1779
  return 0
1705
1780
  fi
1706
- local any=0 d name yaml phase workflow build_mode vr dd plan total done tasks_file
1781
+ local any=0 d name yaml phase phase_step workflow build_mode vr dd plan total done tasks_file
1707
1782
  for d in "$changes_dir"/*/; do
1708
1783
  [ -d "$d" ] || continue
1709
1784
  name=$(basename "$d")
@@ -1713,6 +1788,7 @@ status() {
1713
1788
  [ "$(yaml_get "$yaml" archived)" = "true" ] && continue
1714
1789
  any=1
1715
1790
  phase=$(yaml_get "$yaml" phase)
1791
+ phase_step=$(yaml_get "$yaml" phase_step)
1716
1792
  workflow=$(yaml_get "$yaml" workflow)
1717
1793
  build_mode=$(yaml_get "$yaml" build_mode)
1718
1794
  vr=$(yaml_get "$yaml" verify_result)
@@ -1726,6 +1802,7 @@ status() {
1726
1802
  fi
1727
1803
  echo "• $name [phase: $phase, $done/$total tasks]"
1728
1804
  echo " workflow: $workflow | build_mode: $build_mode"
1805
+ [ -n "$phase_step" ] && [ "$phase_step" != "null" ] && echo " step: $phase_step"
1729
1806
  [ -n "$dd" ] && [ "$dd" != "null" ] && echo " design: $dd"
1730
1807
  [ -n "$plan" ] && [ "$plan" != "null" ] && echo " plan: $plan"
1731
1808
  [ "$phase" = "verify" ] && echo " verify_result: $vr"
@@ -1870,6 +1947,11 @@ case "$CMD" in
1870
1947
  esac
1871
1948
  fi
1872
1949
  ;;
1950
+ dependency)
1951
+ NAME="${1:-}"; SUB="${2:-}"
1952
+ [ -n "$NAME" ] && [ "$SUB" = "archived" ] || { usage_top; exit "$COMATE_USAGE"; }
1953
+ dependency_archived "$NAME"
1954
+ ;;
1873
1955
  gate)
1874
1956
  PRD="${1:-}"; SUB="${2:-}"
1875
1957
  [ -n "$PRD" ] && [ -n "$SUB" ] || { usage_top; exit "$COMATE_USAGE"; }
@@ -144,8 +144,10 @@ seed_open() {
144
144
 
145
145
  @test "open-complete routes full to design" {
146
146
  seed_open
147
+ comate state demo set phase_step open.review-confirmed >/dev/null
147
148
  comate state demo transition open-complete
148
149
  [ "$(comate state demo get phase)" = "design" ]
150
+ [ "$(comate state demo get phase_step)" = "null" ]
149
151
  }
150
152
 
151
153
  @test "open-complete routes fix to build" {
@@ -201,6 +203,56 @@ seed_open() {
201
203
  [ "$status" -eq 1 ]
202
204
  }
203
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 "recover reports phase-local progress" {
241
+ comate state demo init full
242
+ comate state demo set phase_step open.review-awaiting >/dev/null
243
+ run comate state demo recover open
244
+ [ "$status" -eq 0 ]
245
+ [[ "$output" == *"Phase step: open.review-awaiting"* ]]
246
+ }
247
+
248
+ @test "status exposes awaiting decisions" {
249
+ comate state demo init full
250
+ comate state demo set phase_step open.review-awaiting >/dev/null
251
+ run comate status
252
+ [ "$status" -eq 0 ]
253
+ [[ "$output" == *"step: open.review-awaiting"* ]]
254
+ }
255
+
204
256
  # --- guard open ---
205
257
 
206
258
  @test "guard open passes with full artifacts and applies" {
@@ -436,6 +488,18 @@ SH
436
488
  [ "$status" -eq 0 ]
437
489
  }
438
490
 
491
+ @test "dependency archived requires an archived=true record" {
492
+ comate state dep init full
493
+ run comate dependency dep archived
494
+ [ "$status" -eq 1 ]
495
+ comate state dep set phase archive >/dev/null
496
+ comate state dep transition archived >/dev/null
497
+ mkdir -p openspec/changes/archive
498
+ mv openspec/changes/dep openspec/changes/archive/2026-08-20-dep
499
+ run comate dependency dep archived
500
+ [ "$status" -eq 0 ]
501
+ }
502
+
439
503
  @test "top-level usage error returns code 2" {
440
504
  run comate frobnicate
441
505
  [ "$status" -eq 2 ]
@@ -503,16 +567,41 @@ seed_prd() {
503
567
  comate gate "$prd" review >/dev/null
504
568
  comate gate "$prd" set score 90 >/dev/null
505
569
  comate gate "$prd" set blocking_open 0 >/dev/null
570
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
506
571
  run comate gate "$prd" pass
507
572
  [ "$status" -eq 1 ]
508
573
  [[ "$output" == *"report"* ]]
509
574
  }
510
575
 
576
+ @test "gate pass blocks without a confirmed changes map" {
577
+ local prd; prd=$(seed_prd)
578
+ comate gate "$prd" review >/dev/null
579
+ comate gate "$prd" set score 90 >/dev/null
580
+ comate gate "$prd" set blocking_open 0 >/dev/null
581
+ printf '# report\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
582
+ run comate gate "$prd" pass
583
+ [ "$status" -eq 1 ]
584
+ [[ "$output" == *"changes_map"* ]]
585
+ }
586
+
587
+ @test "gate pass --tbd requires unresolved blockers" {
588
+ local prd; prd=$(seed_prd)
589
+ comate gate "$prd" review >/dev/null
590
+ comate gate "$prd" set score 90 >/dev/null
591
+ comate gate "$prd" set blocking_open 0 >/dev/null
592
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
593
+ printf '# report\n' > "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
594
+ run comate gate "$prd" pass --tbd
595
+ [ "$status" -eq 1 ]
596
+ [[ "$output" == *"requires at least one"* ]]
597
+ }
598
+
511
599
  @test "gate pass succeeds and check passes after report written" {
512
600
  local prd; prd=$(seed_prd)
513
601
  comate gate "$prd" review >/dev/null
514
602
  comate gate "$prd" set score 90 >/dev/null
515
603
  comate gate "$prd" set blocking_open 0 >/dev/null
604
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
516
605
  printf '# 评审报告\n' > \
517
606
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
518
607
  run comate gate "$prd" pass
@@ -525,7 +614,8 @@ seed_prd() {
525
614
  local prd; prd=$(seed_prd)
526
615
  comate gate "$prd" review >/dev/null
527
616
  comate gate "$prd" set score 65 >/dev/null
528
- comate gate "$prd" set blocking_open 0 >/dev/null
617
+ comate gate "$prd" set blocking_open 2 >/dev/null
618
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
529
619
  printf '# 评审报告\n' > \
530
620
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
531
621
  run comate gate "$prd" pass --tbd
@@ -539,6 +629,7 @@ seed_prd() {
539
629
  comate gate "$prd" review >/dev/null
540
630
  comate gate "$prd" set score 90 >/dev/null
541
631
  comate gate "$prd" set blocking_open 0 >/dev/null
632
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
542
633
  printf '# 评审报告\n' > \
543
634
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
544
635
  comate gate "$prd" pass >/dev/null
@@ -554,6 +645,7 @@ seed_prd() {
554
645
  comate gate "$prd" review >/dev/null
555
646
  comate gate "$prd" set score 90 >/dev/null
556
647
  comate gate "$prd" set blocking_open 0 >/dev/null
648
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
557
649
  printf '# 评审报告\n' > \
558
650
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
559
651
  comate gate "$prd" pass >/dev/null
@@ -572,6 +664,7 @@ seed_prd() {
572
664
  comate gate "$prd" review >/dev/null
573
665
  comate gate "$prd" set score 90 >/dev/null
574
666
  comate gate "$prd" set blocking_open 0 >/dev/null
667
+ comate gate "$prd" set changes_map "login-flow:功能项1" >/dev/null
575
668
  printf '# 报告\n' > \
576
669
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
577
670
  comate gate "$prd" pass >/dev/null
@@ -587,6 +680,7 @@ seed_prd() {
587
680
  comate gate "$prd" review >/dev/null
588
681
  comate gate "$prd" set score 90 >/dev/null
589
682
  comate gate "$prd" set blocking_open 0 >/dev/null
683
+ comate gate "$prd" set changes_map "login-flow:功能项1;logout-flow:功能项2" >/dev/null
590
684
  printf '# 报告\n' > \
591
685
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
592
686
  comate gate "$prd" pass >/dev/null
@@ -616,6 +710,7 @@ seed_prd() {
616
710
  comate gate "$prd" review >/dev/null
617
711
  comate gate "$prd" set score 90 >/dev/null
618
712
  comate gate "$prd" set blocking_open 0 >/dev/null
713
+ comate gate "$prd" set changes_map "demo:功能项1" >/dev/null
619
714
  printf '# 报告\n' > \
620
715
  "docs/knowledge-base/requirements/test/PRD-001-REVIEW.md"
621
716
  comate gate "$prd" pass >/dev/null
@@ -25,7 +25,43 @@ fi
25
25
  bash "$COMATE" state <name> check build
26
26
  ```
27
27
 
28
- **幂等性**:build 阶段操作可安全重复。读 `phase` 确认仍在 build,读 plan 文件头的 `base-ref`,再读 tasks.md 找第一个未勾选任务继续。已提交的任务不得重复提交。
28
+ **幂等性检查**:
29
+
30
+ 1. 读取 `.comate.yaml` 的 `plan`、`isolation`、`build_mode`、`tdd_mode`、`phase_step`、`phase_step_data`
31
+
32
+ 2. **检查是否被依赖阻塞**(最高优先级检查):
33
+ - 若 `phase_step` 为 `build.blocked-by-<dependency>`:
34
+ - 从 `phase_step` 或 `phase_step_data` 中提取依赖的 change 名称
35
+ - 运行 `bash "$COMATE" dependency <dependency> archived` 检查归档目录中是否存在该 change 且其 `.comate.yaml` 明确记录 `archived: true`;不得用“不在活跃列表”推断归档完成
36
+ - **若依赖已归档**:
37
+ ```bash
38
+ bash "$COMATE" state <name> set phase_step build.options-confirmed
39
+ bash "$COMATE" state <name> set phase_step_data null
40
+ ```
41
+ 清除阻塞标记,说明「依赖 change <dependency> 已完成,继续当前 change 的 build」,继续第 3 步检查
42
+ - **若依赖未归档**:提示「当前 change 正在等待 <dependency> 完成。请先完成 <dependency> 的全流程后再继续。」,**立即退出**
43
+
44
+ 3. **已完成 Step 3(工作方式已选定)** 的判定条件:
45
+ - `isolation`、`build_mode`、`tdd_mode` **三个字段全部已设置且非 null**
46
+ - 或 `phase_step` 值为 `build.options-confirmed`
47
+
48
+ 4. **幂等分支**:
49
+ - **三个决策字段全部已设置** → 跳过 Step 3a-3c,说明「工作方式已选定:isolation={value}, build_mode={value}, tdd_mode={value}」,直接进入执行(加载对应技能)
50
+ - **任一字段未设置或为 null**:
51
+ - 若 `phase_step` 为 `build.plan-created` 或 `build.plan-recorded` → 继续对应步骤(见下方第 5 点)
52
+ - 若 `plan` 未设置 → 从 Step 1 开始
53
+ - 若 `plan` 已设置但决策未完成 → 从 Step 3 开始
54
+
55
+ 5. **断点恢复**(根据 `phase_step` 值):
56
+ - `phase_step` 为 `build.plan-created`(或旧版 `1`)→ 从 Step 2 开始并改写新格式
57
+ - `phase_step` 为 `build.plan-recorded` / `build.options-awaiting`(或旧版 `2`)→ 从 Step 3 恢复并改写新格式
58
+ - `phase_step` 为 `build.options-confirmed`(或旧版 `3-confirmed`)→ 直接加载执行技能并改写新格式
59
+ - `phase_step` 为 `build.blocked-by-*` → 见第 2 点的依赖阻塞检查
60
+
61
+ 6. **已进入执行阶段**(plan 已设置、决策已完成):
62
+ - 读 tasks.md 找第一个未勾选任务继续
63
+ - 已提交的任务不得重复提交
64
+ - 上下文压缩恢复时,重新加载对应的执行技能,然后继续未完成的任务
29
65
 
30
66
  ### 1. 制定计划
31
67
 
@@ -44,10 +80,17 @@ base-ref: <实施前的 git rev-parse HEAD>
44
80
 
45
81
  `base-ref` 用于验证阶段跨提交统计改动规模。创建计划时先 `git rev-parse HEAD` 记录。
46
82
 
83
+ **步骤完成后记录**:
84
+
85
+ ```bash
86
+ bash "$COMATE" state <name> set phase_step build.plan-created
87
+ ```
88
+
47
89
  ### 2. 记录计划路径
48
90
 
49
91
  ```bash
50
92
  bash "$COMATE" state <name> set plan docs/superpowers/plans/YYYY-MM-DD-feature.md
93
+ bash "$COMATE" state <name> set phase_step build.plan-recorded
51
94
  ```
52
95
 
53
96
  无需手动改 phase,guard 会在退出条件满足后自动流转。
@@ -79,11 +122,17 @@ bash "$COMATE" state <name> set plan docs/superpowers/plans/YYYY-MM-DD-feature.m
79
122
 
80
123
  #### 3c. 联合决策(仍有 ≥2 有效选项的维度才问)
81
124
 
82
- 对仍有多个有效选项的维度,**一次 AskUserQuestion 问清全部**,选 `branch` 时**分支名也在同一次确认**,不得先问是否继续再二次追问配置或命名。已在 3b 自动写入的维度不再列入问题。每个选项附推荐理由(推荐只说明倾向,不代替用户拍板):
125
+ 对仍有多个有效选项的维度,**一次 AskUserQuestion 问清全部**,选 `branch` 时**分支名也在同一次确认**,不得先问是否继续再二次追问配置或命名。已在 3b 自动写入的维度不再列入问题。每个选项附推荐理由(推荐只说明倾向,不代替用户拍板):
83
126
 
84
127
  - **工作区隔离**:`branch` 新建分支(简单快速,≤3 文件推荐)/ `worktree` 独立工作区(需并行或当前分支有未提交工作时推荐)。
85
128
  - **执行方式**:`subagent-driven-development`(任务 ≥3、独立、需双阶段审查)/ `executing-plans`(任务 ≤2 且无跨模块依赖,或来自 fix 预设)。
86
- - **TDD 模式**:`tdd`(涉及业务逻辑/新功能/API,推荐)/ `direct`(无需测试覆盖或用户明确跳过)。
129
+ - **TDD 模式**:`tdd`(涉及业务逻辑/新功能/API,推荐)/ `direct`(无需测试覆盖或用户明确跳过)。
130
+
131
+ 提问前先持久化:
132
+
133
+ ```bash
134
+ bash "$COMATE" state <name> set phase_step build.options-awaiting
135
+ ```
87
136
 
88
137
  用户选择后(连同 3b 已自动应用的维度)写入:
89
138
 
@@ -91,6 +140,7 @@ bash "$COMATE" state <name> set plan docs/superpowers/plans/YYYY-MM-DD-feature.m
91
140
  bash "$COMATE" state <name> set isolation <branch|worktree>
92
141
  bash "$COMATE" state <name> set build_mode <subagent-driven-development|executing-plans|direct>
93
142
  bash "$COMATE" state <name> set tdd_mode <tdd|direct>
143
+ bash "$COMATE" state <name> set phase_step build.options-confirmed
94
144
  ```
95
145
 
96
146
  `isolation` 是脚本级硬约束,保持 `null` 时 `build → verify` 的 guard 和 `state transition build-complete` 都会失败。
@@ -136,9 +186,36 @@ bash "$COMATE" state <name> set build_mode direct
136
186
  |------|------|------|
137
187
  | 小 | 遗漏验收场景、边界条件 | 直接编辑 delta spec + design.md,追加 tasks.md |
138
188
  | 中 | 接口变更、新增组件、数据流变化 | **AskUserQuestion 确认后**加载 `superpowers:brainstorming` 更新 Design Doc + delta spec |
139
- | 大 | 全新 capability | **AskUserQuestion 确认拆分后**用 `/comate-open` 创建独立 change |
140
-
141
- **50% 阈值**:以 tasks.md 初始任务总数为基准,新增任务超过总数一半即视为超范围,**必须 AskUserQuestion 暂停**决定是否拆分。创建独立 change 必须调 `/comate-open`,不得直接调 `/opsx:new`。
189
+ | 大 | 全新 capability | **AskUserQuestion 确认拆分后**用 `/comate-open` 创建独立 change,**当前 change 的 build 流程立即终止** |
190
+
191
+ **50% 阈值**:以 tasks.md 初始任务总数为基准,新增任务超过总数一半即视为超范围,**必须 AskUserQuestion 暂停**决定是否拆分。创建独立 change 必须调 `/comate-open`,不得直接调 `/opsx:new`。
192
+
193
+ 凡需 AskUserQuestion 决定范围扩张时,提问前先写 `phase_step: build.scope-awaiting`;用户决定继续当前 change 后恢复为 `build.options-confirmed`。
194
+
195
+ **大规模扩张(全新 capability)确认拆分后的流程终止规则**:
196
+
197
+ 1. 用 `/comate-open` 创建独立 change `<new-change-name>`
198
+ 2. **当前 change `<current>` 的 build 流程立即暂停**,记录依赖关系:
199
+ ```bash
200
+ bash "$COMATE" state <current> set phase_step "build.blocked-by-<new-change-name>"
201
+ bash "$COMATE" state <current> set phase_step_data "<new-change-name>"
202
+ ```
203
+ 3. 提示用户:
204
+ ```
205
+ 已创建独立 change <new-change-name>。
206
+ 当前 change <current> 的 build 流程已暂停,等待 <new-change-name> 完成。
207
+
208
+ 请先完成 <new-change-name> 的全流程(design → build → verify → archive),
209
+ 然后再继续 <current> 的 build。
210
+ ```
211
+ 4. **立即退出当前 /comate 调用**,不继续执行当前 change 的后续任务,不进入 Step 5
212
+
213
+ **恢复被阻塞的 change**:
214
+
215
+ 当用户稍后运行 `/comate <current>` 时,Step 0 幂等性检查会发现 `phase_step` 包含 `blocked-by` 标记:
216
+ - 检查依赖的 change 是否已归档
217
+ - 若 `dependency <name> archived` 通过 → 清除阻塞标记,恢复 `phase_step` 为 `build.options-confirmed`,继续执行 tasks.md 中未完成的任务
218
+ - 若未归档 → 提示用户先完成依赖 change,退出
142
219
 
143
220
  ### 5. 上下文管理与 build 交接包
144
221