@rpamis/comet 0.3.5 → 0.3.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.
Files changed (54) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +180 -56
  3. package/assets/manifest.json +11 -1
  4. package/assets/skills/comet/SKILL.md +59 -24
  5. package/assets/skills/comet/reference/dirty-worktree.md +1 -0
  6. package/assets/skills/comet/rules/comet-phase-guard.md +90 -0
  7. package/assets/skills/comet/scripts/comet-archive.sh +75 -57
  8. package/assets/skills/comet/scripts/comet-env.sh +57 -2
  9. package/assets/skills/comet/scripts/comet-guard.sh +187 -29
  10. package/assets/skills/comet/scripts/comet-handoff.sh +137 -8
  11. package/assets/skills/comet/scripts/comet-hook-guard.sh +260 -0
  12. package/assets/skills/comet/scripts/comet-state.sh +312 -25
  13. package/assets/skills/comet/scripts/comet-yaml-validate.sh +26 -1
  14. package/assets/skills/comet-archive/SKILL.md +45 -12
  15. package/assets/skills/comet-build/SKILL.md +159 -33
  16. package/assets/skills/comet-design/SKILL.md +129 -23
  17. package/assets/skills/comet-hotfix/SKILL.md +57 -15
  18. package/assets/skills/comet-open/SKILL.md +90 -17
  19. package/assets/skills/comet-tweak/SKILL.md +40 -15
  20. package/assets/skills/comet-verify/SKILL.md +64 -25
  21. package/assets/skills-zh/comet/SKILL.md +63 -25
  22. package/assets/skills-zh/comet/reference/dirty-worktree.md +2 -1
  23. package/assets/skills-zh/comet-archive/SKILL.md +46 -13
  24. package/assets/skills-zh/comet-build/SKILL.md +161 -35
  25. package/assets/skills-zh/comet-design/SKILL.md +132 -25
  26. package/assets/skills-zh/comet-hotfix/SKILL.md +53 -15
  27. package/assets/skills-zh/comet-open/SKILL.md +90 -17
  28. package/assets/skills-zh/comet-tweak/SKILL.md +36 -15
  29. package/assets/skills-zh/comet-verify/SKILL.md +64 -27
  30. package/bin/comet.js +3 -3
  31. package/dist/commands/doctor.d.ts.map +1 -1
  32. package/dist/commands/doctor.js +22 -0
  33. package/dist/commands/doctor.js.map +1 -1
  34. package/dist/commands/init.d.ts +5 -1
  35. package/dist/commands/init.d.ts.map +1 -1
  36. package/dist/commands/init.js +64 -9
  37. package/dist/commands/init.js.map +1 -1
  38. package/dist/commands/update.d.ts.map +1 -1
  39. package/dist/commands/update.js +60 -1
  40. package/dist/commands/update.js.map +1 -1
  41. package/dist/core/codegraph.d.ts +9 -0
  42. package/dist/core/codegraph.d.ts.map +1 -0
  43. package/dist/core/codegraph.js +96 -0
  44. package/dist/core/codegraph.js.map +1 -0
  45. package/dist/core/platforms.d.ts +10 -0
  46. package/dist/core/platforms.d.ts.map +1 -1
  47. package/dist/core/platforms.js +163 -15
  48. package/dist/core/platforms.js.map +1 -1
  49. package/dist/core/skills.d.ts +34 -1
  50. package/dist/core/skills.d.ts.map +1 -1
  51. package/dist/core/skills.js +360 -1
  52. package/dist/core/skills.js.map +1 -1
  53. package/package.json +3 -1
  54. package/scripts/postinstall.js +44 -44
@@ -65,6 +65,26 @@ validate_enum() {
65
65
  exit 1
66
66
  }
67
67
 
68
+ validate_path_field() {
69
+ local value="$1"
70
+ local field="$2"
71
+ # null and empty are acceptable (means "not set")
72
+ if [ -z "$value" ] || [ "$value" = "null" ]; then
73
+ return 0
74
+ fi
75
+ # Reject absolute paths and home-directory references
76
+ case "$value" in
77
+ /*|~*|[A-Za-z]:*|\\*)
78
+ red "ERROR: $field must be a relative path within the repo: '$value'" >&2
79
+ exit 1
80
+ ;;
81
+ esac
82
+ if [[ "$value" =~ \.\. ]]; then
83
+ red "ERROR: $field cannot contain '..' (path traversal not allowed): '$value'" >&2
84
+ exit 1
85
+ fi
86
+ }
87
+
68
88
  # --- Helper functions ---
69
89
 
70
90
  yaml_field() {
@@ -126,9 +146,17 @@ replace_yaml_field() {
126
146
  local tmp_file
127
147
 
128
148
  tmp_file=$(mktemp)
149
+ chmod 600 "$tmp_file"
150
+ # Replace the target field, then deduplicate all fields keeping only the
151
+ # last occurrence of each key. Prevents stale earlier values from
152
+ # persisting when a field is set multiple times.
129
153
  awk -v field="$field" -v value="$value" '
130
- index($0, field ":") == 1 { print field ": " value; next }
131
- { print }
154
+ index($0, field ":") == 1 { $0 = field ": " value }
155
+ { buf[NR] = $0; keys[NR] = $0; sub(/:.*$/, "", keys[NR]); n = NR }
156
+ END {
157
+ for (i = 1; i <= n; i++) last[keys[i]] = i
158
+ for (i = 1; i <= n; i++) if (last[keys[i]] == i) print buf[i]
159
+ }
132
160
  ' "$yaml_file" > "$tmp_file"
133
161
  mv "$tmp_file" "$yaml_file"
134
162
  }
@@ -155,6 +183,57 @@ yaml_file_for() {
155
183
  echo "$change_dir/.comet.yaml"
156
184
  }
157
185
 
186
+ project_context_compression() {
187
+ local value="off"
188
+ local source="default"
189
+ if [ -n "${COMET_CONTEXT_COMPRESSION:-}" ]; then
190
+ value="$COMET_CONTEXT_COMPRESSION"
191
+ source="COMET_CONTEXT_COMPRESSION"
192
+ elif [ -f ".comet/config.yaml" ]; then
193
+ value=$(yaml_field "context_compression" ".comet/config.yaml")
194
+ value="${value:-off}"
195
+ source=".comet/config.yaml"
196
+ fi
197
+
198
+ case "$value" in
199
+ off|beta)
200
+ printf '%s\n' "$value"
201
+ ;;
202
+ *)
203
+ red "ERROR: Invalid context_compression from ${source}: '$value'" >&2
204
+ red "Valid values: off, beta" >&2
205
+ exit 1
206
+ ;;
207
+ esac
208
+ }
209
+
210
+ project_auto_transition_default() {
211
+ local value="true"
212
+ local source="default"
213
+ if [ -n "${COMET_AUTO_TRANSITION:-}" ]; then
214
+ value="$COMET_AUTO_TRANSITION"
215
+ source="COMET_AUTO_TRANSITION"
216
+ elif [ -f ".comet/config.yaml" ]; then
217
+ local raw
218
+ raw=$(yaml_field "auto_transition" ".comet/config.yaml" 2>/dev/null || true)
219
+ if [ -n "$raw" ]; then
220
+ value="$raw"
221
+ source=".comet/config.yaml"
222
+ fi
223
+ fi
224
+
225
+ case "$value" in
226
+ true|false)
227
+ printf '%s\n' "$value"
228
+ ;;
229
+ *)
230
+ red "ERROR: Invalid auto_transition from ${source}: '$value'" >&2
231
+ red "Valid values: true, false" >&2
232
+ exit 1
233
+ ;;
234
+ esac
235
+ }
236
+
158
237
  # --- Subcommands ---
159
238
 
160
239
  cmd_init() {
@@ -178,17 +257,21 @@ cmd_init() {
178
257
  mkdir -p "$change_dir"
179
258
 
180
259
  # Set workflow-appropriate defaults
181
- local phase build_mode isolation verify_mode
260
+ local phase build_mode isolation verify_mode context_compression auto_transition
182
261
  phase="open"
262
+ context_compression=$(project_context_compression)
263
+ auto_transition="$(project_auto_transition_default)"
183
264
 
184
265
  case "$workflow" in
185
266
  full)
186
267
  build_mode="null"
268
+ tdd_mode="null"
187
269
  isolation="null"
188
270
  verify_mode="null"
189
271
  ;;
190
272
  hotfix|tweak)
191
273
  build_mode="direct"
274
+ tdd_mode="direct"
192
275
  isolation="branch"
193
276
  verify_mode="light"
194
277
  ;;
@@ -204,16 +287,21 @@ cmd_init() {
204
287
  cat > "$yaml_file" <<EOF
205
288
  workflow: $workflow
206
289
  phase: $phase
290
+ context_compression: $context_compression
207
291
  build_mode: $build_mode
292
+ build_pause: null
293
+ subagent_dispatch: null
294
+ tdd_mode: $tdd_mode
208
295
  isolation: $isolation
209
296
  verify_mode: $verify_mode
297
+ auto_transition: $auto_transition
210
298
  base_ref: $base_ref
211
299
  design_doc: null
212
300
  plan: null
213
301
  verify_result: pending
214
302
  verification_report: null
215
303
  branch_status: pending
216
- created_at: $(date +%Y-%m-%d)
304
+ created_at: $(date -u +%Y-%m-%d)
217
305
  verified_at: null
218
306
  archived: false
219
307
  EOF
@@ -239,6 +327,9 @@ cmd_get() {
239
327
  # Read and output the field value
240
328
  local value
241
329
  value=$(yaml_field "$field" "$yaml_file")
330
+ if [ "$field" = "auto_transition" ] && { [ -z "$value" ] || [ "$value" = "null" ]; }; then
331
+ value="$(project_auto_transition_default)"
332
+ fi
242
333
  echo "${value:-}"
243
334
  }
244
335
 
@@ -264,14 +355,14 @@ cmd_set() {
264
355
  yellow "WARNING: Setting 'phase' directly bypasses state machine constraints." >&2
265
356
  yellow " Consider using: comet-state.sh transition <change-name> <event>" >&2
266
357
  ;;
267
- workflow|build_mode|isolation|verify_mode|verify_result|verification_report|branch_status|archived|design_doc|plan|verified_at|created_at|direct_override|build_command|verify_command|handoff_context|handoff_hash|base_ref)
358
+ workflow|context_compression|build_mode|build_pause|subagent_dispatch|tdd_mode|isolation|verify_mode|auto_transition|verify_result|verification_report|branch_status|archived|design_doc|plan|verified_at|created_at|direct_override|build_command|verify_command|handoff_context|handoff_hash|base_ref)
268
359
  # Valid field
269
360
  ;;
270
361
  *)
271
362
  red "ERROR: Unknown field: '$field'" >&2
272
363
  red "Valid fields:" >&2
273
- red " workflow, phase, design_doc, plan, build_mode, isolation," >&2
274
- red " verify_mode, verify_result, verification_report, branch_status," >&2
364
+ red " workflow, phase, context_compression, design_doc, plan, build_mode, build_pause, subagent_dispatch, tdd_mode, isolation," >&2
365
+ red " verify_mode, auto_transition, verify_result, verification_report, branch_status," >&2
275
366
  red " verified_at, created_at, archived, base_ref, direct_override," >&2
276
367
  red " build_command, verify_command, handoff_context, handoff_hash" >&2
277
368
  exit 1
@@ -283,18 +374,33 @@ cmd_set() {
283
374
  workflow)
284
375
  validate_enum "$value" "full" "hotfix" "tweak"
285
376
  ;;
377
+ context_compression)
378
+ validate_enum "$value" "off" "beta"
379
+ ;;
286
380
  phase)
287
381
  validate_enum "$value" "open" "design" "build" "verify" "archive"
288
382
  ;;
289
383
  build_mode)
290
384
  validate_enum "$value" "subagent-driven-development" "executing-plans" "direct"
291
385
  ;;
386
+ build_pause)
387
+ validate_enum "$value" "null" "plan-ready"
388
+ ;;
389
+ subagent_dispatch)
390
+ validate_enum "$value" "null" "confirmed"
391
+ ;;
392
+ tdd_mode)
393
+ validate_enum "$value" "tdd" "direct"
394
+ ;;
292
395
  isolation)
293
396
  validate_enum "$value" "branch" "worktree"
294
397
  ;;
295
398
  verify_mode)
296
399
  validate_enum "$value" "light" "full"
297
400
  ;;
401
+ auto_transition)
402
+ validate_enum "$value" "true" "false"
403
+ ;;
298
404
  verify_result)
299
405
  validate_enum "$value" "pending" "pass" "fail"
300
406
  ;;
@@ -307,8 +413,11 @@ cmd_set() {
307
413
  direct_override)
308
414
  validate_enum "$value" "true" "false"
309
415
  ;;
310
- design_doc|plan|verification_report|verified_at|created_at|build_command|verify_command|handoff_context|handoff_hash)
311
- # No validation for path fields, date fields, or project command strings
416
+ design_doc|plan|verification_report|handoff_context|handoff_hash)
417
+ validate_path_field "$value" "$field"
418
+ ;;
419
+ verified_at|created_at|build_command|verify_command)
420
+ # No validation for date fields or project command strings
312
421
  ;;
313
422
  esac
314
423
 
@@ -353,11 +462,13 @@ require_verification_evidence() {
353
462
 
354
463
  require_build_decisions() {
355
464
  local change_name="$1"
356
- local workflow build_mode isolation direct_override
465
+ local workflow build_mode isolation direct_override subagent_dispatch tdd_mode
357
466
  workflow=$(cmd_get "$change_name" "workflow")
358
467
  build_mode=$(cmd_get "$change_name" "build_mode")
359
468
  isolation=$(cmd_get "$change_name" "isolation")
360
469
  direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true)
470
+ subagent_dispatch=$(cmd_get "$change_name" "subagent_dispatch" 2>/dev/null || true)
471
+ tdd_mode=$(cmd_get "$change_name" "tdd_mode" 2>/dev/null || true)
361
472
 
362
473
  case "$isolation" in
363
474
  branch|worktree) ;;
@@ -379,6 +490,16 @@ require_build_decisions() {
379
490
  red "ERROR: Cannot transition '$change_name': build_mode=direct is only allowed for hotfix/tweak unless direct_override=true" >&2
380
491
  exit 1
381
492
  fi
493
+
494
+ if [ "$build_mode" = "subagent-driven-development" ] && [ "$subagent_dispatch" != "confirmed" ]; then
495
+ red "ERROR: Cannot transition '$change_name': subagent_dispatch must be confirmed before using build_mode=subagent-driven-development" >&2
496
+ exit 1
497
+ fi
498
+
499
+ if [ "$workflow" = "full" ] && { [ "$tdd_mode" = "null" ] || [ -z "$tdd_mode" ]; }; then
500
+ red "ERROR: Cannot transition '$change_name': tdd_mode must be selected before leaving build (full workflow)" >&2
501
+ exit 1
502
+ fi
382
503
  }
383
504
 
384
505
  cmd_transition() {
@@ -386,7 +507,7 @@ cmd_transition() {
386
507
  local event="$2"
387
508
 
388
509
  validate_change_name "$change_name"
389
- validate_enum "$event" "open-complete" "design-complete" "build-complete" "verify-pass" "verify-fail" "archived"
510
+ validate_enum "$event" "open-complete" "design-complete" "build-complete" "verify-pass" "verify-fail" "archive-reopen" "archived"
390
511
 
391
512
  case "$event" in
392
513
  open-complete)
@@ -406,23 +527,41 @@ cmd_transition() {
406
527
  build-complete)
407
528
  require_phase "$change_name" "build"
408
529
  require_build_decisions "$change_name"
530
+ local current_verify_result
531
+ current_verify_result=$(cmd_get "$change_name" "verify_result")
409
532
  cmd_set "$change_name" phase verify
410
533
  cmd_set "$change_name" verify_result pending
411
- cmd_set "$change_name" verification_report null
412
- cmd_set "$change_name" branch_status pending
534
+ # Preserve verification evidence on re-verify (verify-fail → build → build-complete)
535
+ # so the fix can reference the original failure report
536
+ if [ "$current_verify_result" != "fail" ]; then
537
+ cmd_set "$change_name" verification_report null
538
+ cmd_set "$change_name" branch_status pending
539
+ fi
413
540
  ;;
414
541
  verify-pass)
415
542
  require_phase "$change_name" "verify"
416
543
  require_verification_evidence "$change_name"
417
544
  cmd_set "$change_name" verify_result pass
418
545
  cmd_set "$change_name" phase archive
419
- cmd_set "$change_name" verified_at "$(date +%Y-%m-%d)"
546
+ cmd_set "$change_name" verified_at "$(date -u +%Y-%m-%d)"
420
547
  ;;
421
548
  verify-fail)
422
549
  require_phase "$change_name" "verify"
423
550
  cmd_set "$change_name" verify_result fail
424
551
  cmd_set "$change_name" phase build
425
- cmd_set "$change_name" branch_status pending
552
+ # Preserve branch_status so re-verify doesn't require re-handling branches
553
+ ;;
554
+ archive-reopen)
555
+ require_phase "$change_name" "archive"
556
+ local archived
557
+ archived=$(cmd_get "$change_name" "archived")
558
+ if [ "$archived" = "true" ]; then
559
+ red "ERROR: Cannot transition '$change_name': already archived" >&2
560
+ exit 1
561
+ fi
562
+ cmd_set "$change_name" verify_result pending
563
+ cmd_set "$change_name" phase verify
564
+ cmd_set "$change_name" verified_at null
426
565
  ;;
427
566
  archived)
428
567
  require_phase "$change_name" "archive"
@@ -632,7 +771,7 @@ cmd_recover() {
632
771
 
633
772
  # Read all relevant fields
634
773
  local design_doc plan verify_result verify_mode verification_report
635
- local branch_status handoff_context handoff_hash isolation build_mode direct_override
774
+ local branch_status handoff_context handoff_hash isolation build_mode build_pause subagent_dispatch tdd_mode direct_override
636
775
  design_doc=$(cmd_get "$change_name" "design_doc")
637
776
  plan=$(cmd_get "$change_name" "plan")
638
777
  verify_result=$(cmd_get "$change_name" "verify_result")
@@ -643,6 +782,9 @@ cmd_recover() {
643
782
  handoff_hash=$(cmd_get "$change_name" "handoff_hash")
644
783
  isolation=$(cmd_get "$change_name" "isolation")
645
784
  build_mode=$(cmd_get "$change_name" "build_mode")
785
+ build_pause=$(cmd_get "$change_name" "build_pause" 2>/dev/null || true)
786
+ subagent_dispatch=$(cmd_get "$change_name" "subagent_dispatch" 2>/dev/null || true)
787
+ tdd_mode=$(cmd_get "$change_name" "tdd_mode" 2>/dev/null || true)
646
788
  direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true)
647
789
 
648
790
  echo "State fields:"
@@ -651,15 +793,23 @@ cmd_recover() {
651
793
  case "$phase" in
652
794
  open)
653
795
  echo " Artifacts:"
796
+ local artifacts_done=0
654
797
  for f in proposal.md design.md tasks.md; do
655
798
  if file_nonempty "$change_dir/$f"; then
656
799
  echo " - ${f}: DONE"
800
+ artifacts_done=$((artifacts_done + 1))
657
801
  else
658
802
  echo " - ${f}: PENDING"
659
803
  fi
660
804
  done
661
805
  echo ""
662
- echo "Recovery action: Create or complete missing artifacts, then use AskUserQuestion for user confirmation."
806
+ if [ "$artifacts_done" -eq 3 ]; then
807
+ echo "Recovery action: All artifacts complete. Run /comet-open user confirmation, then guard to transition."
808
+ elif [ "$artifacts_done" -eq 0 ]; then
809
+ echo "Recovery action: No artifacts created yet. Start from /comet-open Step 1 (explore and clarify)."
810
+ else
811
+ echo "Recovery action: Some artifacts incomplete. Resume /comet-open from the first missing artifact."
812
+ fi
663
813
  ;;
664
814
  design)
665
815
  echo " Artifacts:"
@@ -688,6 +838,11 @@ cmd_recover() {
688
838
  echo " Build decisions:"
689
839
  field_status "isolation" "$isolation"
690
840
  field_status "build_mode" "$build_mode"
841
+ field_status "build_pause" "$build_pause"
842
+ field_status "tdd_mode" "$tdd_mode"
843
+ if [ "$build_mode" = "subagent-driven-development" ] || { [ -n "$subagent_dispatch" ] && [ "$subagent_dispatch" != "null" ]; }; then
844
+ field_status "subagent_dispatch" "$subagent_dispatch"
845
+ fi
691
846
  if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ]; then
692
847
  field_status "direct_override" "$direct_override"
693
848
  fi
@@ -698,23 +853,72 @@ cmd_recover() {
698
853
  # Count completed vs pending tasks
699
854
  local tasks_file="$change_dir/tasks.md"
700
855
  local total=0 done=0 pending=0
856
+ local plan_total=0 plan_done=0 plan_pending=0
701
857
  if [ -f "$tasks_file" ]; then
702
- total=$(grep -c '^\- \[' "$tasks_file" 2>/dev/null || echo "0")
703
- done=$(grep -c '^\- \[x\]' "$tasks_file" 2>/dev/null || echo "0")
858
+ total=$(grep -c '^[[:space:]]*- \[' "$tasks_file" 2>/dev/null || true)
859
+ done=$(grep -c '^[[:space:]]*- \[x\]' "$tasks_file" 2>/dev/null || true)
860
+ total="${total:-0}"
861
+ done="${done:-0}"
704
862
  pending=$((total - done))
705
863
  echo " Tasks: ${done}/${total} done, ${pending} pending"
706
864
  else
707
865
  echo " Tasks: tasks.md MISSING"
708
866
  fi
867
+ if [ -n "$plan" ] && [ "$plan" != "null" ] && [ -f "$plan" ]; then
868
+ plan_total=$(grep -c '^[[:space:]]*- \[' "$plan" 2>/dev/null || true)
869
+ plan_done=$(grep -c '^[[:space:]]*- \[x\]' "$plan" 2>/dev/null || true)
870
+ plan_total="${plan_total:-0}"
871
+ plan_done="${plan_done:-0}"
872
+ plan_pending=$((plan_total - plan_done))
873
+ if [ "$plan_total" -gt 0 ]; then
874
+ echo " Plan tasks: ${plan_done}/${plan_total} done, ${plan_pending} pending"
875
+ fi
876
+ fi
709
877
  echo ""
710
- if [ "$isolation" = "null" ] || [ -z "$isolation" ]; then
711
- echo "Recovery action: Isolation not selected. Use AskUserQuestion to ask user for branch/worktree choice."
878
+ if [ "$build_pause" = "plan-ready" ] && [ -n "$plan" ] && [ "$plan" != "null" ] && [ -f "$plan" ] && { [ "$isolation" = "null" ] || [ -z "$isolation" ] || [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; }; then
879
+ echo "Recovery action: Plan-ready pause detected. Ask the user whether to continue, then choose isolation and build mode without regenerating the plan."
880
+ elif [ "$build_pause" = "plan-ready" ] && { [ -z "$plan" ] || [ "$plan" = "null" ] || [ ! -f "$plan" ]; }; then
881
+ echo "Recovery action: Plan-ready pause is recorded, but the plan file is missing. Restore the plan file or rerun writing-plans before choosing execution."
882
+ elif [ "$build_pause" = "plan-ready" ]; then
883
+ if [ "$build_mode" = "subagent-driven-development" ] && { [ "$pending" -gt 0 ] || [ "$plan_pending" -gt 0 ]; }; then
884
+ if [ "$subagent_dispatch" = "confirmed" ]; then
885
+ echo "Recovery action: Plan-ready pause is stale because build decisions are already selected. Clear build_pause to null, then inspect the first unchecked task (OpenSpec or plan additions) against recent git history/diff. If implemented, check it off; otherwise dispatch a real background subagent. Do not execute the pending task directly in the main window."
886
+ else
887
+ echo "Recovery action: Plan-ready pause is stale and subagent dispatch is not confirmed. Confirm a real background subagent/Task/multi-agent dispatcher and set subagent_dispatch to confirmed, or set build_mode to executing-plans before continuing."
888
+ fi
889
+ elif [ "$pending" -gt 0 ] || [ "$plan_pending" -gt 0 ]; then
890
+ echo "Recovery action: Plan-ready pause is stale because build decisions are already selected. Clear build_pause to null, then continue from the first unchecked task."
891
+ else
892
+ echo "Recovery action: Plan-ready pause is stale and all tasks are done. Clear build_pause to null, then run guard to transition to verify."
893
+ fi
894
+ elif [ "$isolation" = "null" ] || [ -z "$isolation" ]; then
895
+ echo "Recovery action: Isolation not selected. Use the current platform's user confirmation mechanism to ask user for branch/worktree choice."
712
896
  elif [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; then
713
- echo "Recovery action: Build mode not selected. Use AskUserQuestion to ask user for execution method."
897
+ echo "Recovery action: Build mode not selected. Use the current platform's user confirmation mechanism to ask user for execution method."
898
+ elif [ -z "$tdd_mode" ] || [ "$tdd_mode" = "null" ]; then
899
+ echo "Recovery action: TDD mode not selected. Use the current platform's user confirmation mechanism to ask user for tdd or direct."
714
900
  elif [ ! -f "$tasks_file" ]; then
715
901
  echo "Recovery action: tasks.md missing. Verify change directory integrity."
716
902
  elif [ "$pending" -gt 0 ]; then
717
- echo "Recovery action: Read tasks.md and continue from first unchecked task."
903
+ if [ "$build_mode" = "subagent-driven-development" ]; then
904
+ if [ "$subagent_dispatch" = "confirmed" ]; then
905
+ echo "Recovery action: Read tasks.md and the Superpowers plan (which may include additions beyond OpenSpec), then inspect the first unchecked task against recent git history/diff. If implemented, check it off; otherwise dispatch a real background subagent. Do not execute the pending task directly in the main window."
906
+ else
907
+ echo "Recovery action: Subagent dispatch is not confirmed. Confirm a real background subagent/Task/multi-agent dispatcher and set subagent_dispatch to confirmed, or set build_mode to executing-plans before continuing."
908
+ fi
909
+ else
910
+ echo "Recovery action: Read tasks.md and continue from first unchecked task."
911
+ fi
912
+ elif [ "$plan_pending" -gt 0 ]; then
913
+ if [ "$build_mode" = "subagent-driven-development" ]; then
914
+ if [ "$subagent_dispatch" = "confirmed" ]; then
915
+ echo "Recovery action: Read the Superpowers plan, then inspect the first unchecked Superpowers plan task against recent git history/diff. If implemented, check it off; otherwise dispatch a real background subagent. Do not execute the pending task directly in the main window."
916
+ else
917
+ echo "Recovery action: Subagent dispatch is not confirmed. Confirm a real background subagent/Task/multi-agent dispatcher and set subagent_dispatch to confirmed, or set build_mode to executing-plans before continuing."
918
+ fi
919
+ else
920
+ echo "Recovery action: Read the Superpowers plan and continue from the first unchecked plan task."
921
+ fi
718
922
  else
719
923
  echo "Recovery action: All tasks done. Run guard to transition to verify."
720
924
  fi
@@ -787,7 +991,7 @@ cmd_scale() {
787
991
  local plan_file base_ref=""
788
992
  plan_file=$(cmd_get "$change_name" "plan" 2>/dev/null || true)
789
993
  if [ -n "$plan_file" ] && [ "$plan_file" != "null" ] && [ -f "$plan_file" ]; then
790
- base_ref=$(grep '^base-ref:' "$plan_file" 2>/dev/null | head -1 | sed 's/^base-ref: *//')
994
+ base_ref=$(grep '^base-ref:' "$plan_file" 2>/dev/null | head -1 | sed 's/^base-ref: *//' || true)
791
995
  fi
792
996
  # Fallback to base_ref stored in .comet.yaml (set during init)
793
997
  if [ -z "$base_ref" ] || [ "$base_ref" = "null" ]; then
@@ -820,6 +1024,81 @@ cmd_scale() {
820
1024
  green "[SCALE] verify_mode=$result"
821
1025
  }
822
1026
 
1027
+ # Resolve the next workflow step after a guard --apply phase advance.
1028
+ # Reads the (already advanced) phase, workflow, and auto_transition, then emits
1029
+ # a deterministic next-step contract so skills don't hardcode the next skill name.
1030
+ #
1031
+ # Output contract (stdout):
1032
+ # NEXT: auto|manual|done
1033
+ # SKILL: <skill-name> (omitted when NEXT=done)
1034
+ # HINT: <message> (only when NEXT=manual)
1035
+ cmd_next() {
1036
+ local change_name="$1"
1037
+ validate_change_name "$change_name"
1038
+
1039
+ local change_dir="openspec/changes/$change_name"
1040
+ local yaml_file="$change_dir/.comet.yaml"
1041
+ if [ ! -f "$yaml_file" ]; then
1042
+ red "ERROR: .comet.yaml not found at $yaml_file" >&2
1043
+ exit 1
1044
+ fi
1045
+
1046
+ local phase workflow auto_transition archived
1047
+ phase=$(cmd_get "$change_name" "phase" 2>/dev/null || true)
1048
+ workflow=$(cmd_get "$change_name" "workflow" 2>/dev/null || true)
1049
+ auto_transition=$(cmd_get "$change_name" "auto_transition" 2>/dev/null || true)
1050
+ archived=$(cmd_get "$change_name" "archived" 2>/dev/null || true)
1051
+
1052
+ # Change-level auto_transition overrides project-level; fall back to project default
1053
+ if [ -z "$auto_transition" ] || [ "$auto_transition" = "null" ]; then
1054
+ auto_transition="$(project_auto_transition_default)"
1055
+ fi
1056
+
1057
+ # Terminal state: archived change has no next step.
1058
+ if [ "$archived" = "true" ]; then
1059
+ echo "NEXT: done"
1060
+ return 0
1061
+ fi
1062
+
1063
+ # Map the current (post-advance) phase to the skill that owns it.
1064
+ local skill=""
1065
+ case "$phase" in
1066
+ open)
1067
+ skill="comet-open"
1068
+ ;;
1069
+ design)
1070
+ skill="comet-design"
1071
+ ;;
1072
+ build)
1073
+ case "$workflow" in
1074
+ hotfix) skill="comet-hotfix" ;;
1075
+ tweak) skill="comet-tweak" ;;
1076
+ *) skill="comet-build" ;;
1077
+ esac
1078
+ ;;
1079
+ verify)
1080
+ skill="comet-verify"
1081
+ ;;
1082
+ archive)
1083
+ skill="comet-archive"
1084
+ ;;
1085
+ *)
1086
+ red "ERROR: Cannot resolve next step for '$change_name': unknown phase '${phase:-null}'" >&2
1087
+ exit 1
1088
+ ;;
1089
+ esac
1090
+
1091
+ # auto_transition=false pauses the next skill invocation only; phase is already advanced.
1092
+ if [ "$auto_transition" = "false" ]; then
1093
+ echo "NEXT: manual"
1094
+ echo "SKILL: $skill"
1095
+ echo "HINT: phase is '$phase'; run /$skill manually to continue"
1096
+ else
1097
+ echo "NEXT: auto"
1098
+ echo "SKILL: $skill"
1099
+ fi
1100
+ }
1101
+
823
1102
  # --- Main ---
824
1103
 
825
1104
  SUBCOMMAND="${1:-}"
@@ -851,7 +1130,7 @@ case "$SUBCOMMAND" in
851
1130
  transition)
852
1131
  if [ $# -lt 2 ]; then
853
1132
  red "Usage: comet-state.sh transition <change-name> <event>" >&2
854
- red "Events: open-complete, design-complete, build-complete, verify-pass, verify-fail, archived" >&2
1133
+ red "Events: open-complete, design-complete, build-complete, verify-pass, verify-fail, archive-reopen, archived" >&2
855
1134
  exit 1
856
1135
  fi
857
1136
  cmd_transition "$@"
@@ -876,6 +1155,13 @@ case "$SUBCOMMAND" in
876
1155
  fi
877
1156
  cmd_scale "$@"
878
1157
  ;;
1158
+ next)
1159
+ if [ $# -lt 1 ]; then
1160
+ red "Usage: comet-state.sh next <change-name>" >&2
1161
+ exit 1
1162
+ fi
1163
+ cmd_next "$@"
1164
+ ;;
879
1165
  *)
880
1166
  red "Unknown subcommand: $SUBCOMMAND" >&2
881
1167
  echo "" >&2
@@ -888,6 +1174,7 @@ case "$SUBCOMMAND" in
888
1174
  echo " transition <change-name> <event> — Apply a validated state transition" >&2
889
1175
  echo " check <change-name> <phase> — Verify entry requirements for a phase" >&2
890
1176
  echo " scale <change-name> — Assess and set verification mode based on metrics" >&2
1177
+ echo " next <change-name> — Resolve the next workflow step (auto/manual/done)" >&2
891
1178
  echo "" >&2
892
1179
  echo "Workflows: full, hotfix, tweak" >&2
893
1180
  echo "Phases for check: open, design, build, verify, archive" >&2
@@ -123,11 +123,29 @@ validate_enum() {
123
123
  fail "$field='$value' is not valid. Expected: $valid_values"
124
124
  }
125
125
 
126
+ validate_required_enum() {
127
+ local field="$1" value="$2"
128
+ shift 2
129
+ local valid_values="$*"
130
+
131
+ if [ -z "$value" ] || [ "$value" = "null" ]; then
132
+ fail "$field='${value:-}' is not valid. Expected: $valid_values"
133
+ return 0
134
+ fi
135
+
136
+ validate_enum "$field" "$value" "$@"
137
+ }
138
+
126
139
  workflow=$(field_value "workflow")
127
140
  phase=$(field_value "phase")
141
+ context_compression=$(field_value "context_compression")
128
142
  build_mode=$(field_value "build_mode")
143
+ build_pause=$(field_value "build_pause")
144
+ subagent_dispatch=$(field_value "subagent_dispatch")
145
+ tdd_mode=$(field_value "tdd_mode")
129
146
  isolation=$(field_value "isolation")
130
147
  verify_mode=$(field_value "verify_mode")
148
+ auto_transition=$(field_value "auto_transition")
131
149
  verify_result=$(field_value "verify_result")
132
150
  branch_status=$(field_value "branch_status")
133
151
  archived=$(field_value "archived")
@@ -139,9 +157,16 @@ handoff_hash=$(field_value "handoff_hash")
139
157
 
140
158
  validate_enum "workflow" "$workflow" "full hotfix tweak"
141
159
  validate_enum "phase" "$phase" "open design build verify archive"
160
+ validate_enum "context_compression" "$context_compression" "off beta"
142
161
  validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct"
162
+ validate_enum "build_pause" "$build_pause" "null plan-ready"
163
+ validate_enum "subagent_dispatch" "$subagent_dispatch" "null confirmed"
164
+ validate_enum "tdd_mode" "$tdd_mode" "tdd direct null"
143
165
  validate_enum "isolation" "$isolation" "branch worktree"
144
166
  validate_enum "verify_mode" "$verify_mode" "light full"
167
+ if grep -q "^auto_transition:" "$YAML" 2>/dev/null; then
168
+ validate_required_enum "auto_transition" "$auto_transition" "true false"
169
+ fi
145
170
  validate_enum "verify_result" "$verify_result" "pending pass fail"
146
171
  validate_enum "branch_status" "$branch_status" "pending handled"
147
172
  validate_enum "archived" "$archived" "true false"
@@ -174,7 +199,7 @@ if [ -n "$handoff_hash" ] && [ "$handoff_hash" != "null" ]; then
174
199
  fi
175
200
 
176
201
  # --- Unknown keys check ---
177
- KNOWN_KEYS="workflow phase design_doc plan build_mode isolation verify_mode verify_result verification_report branch_status verified_at created_at archived direct_override build_command verify_command handoff_context handoff_hash base_ref"
202
+ KNOWN_KEYS="workflow phase context_compression design_doc plan build_mode build_pause subagent_dispatch tdd_mode isolation verify_mode auto_transition verify_result verification_report branch_status verified_at created_at archived direct_override build_command verify_command handoff_context handoff_hash base_ref"
178
203
  while IFS=: read -r key _; do
179
204
  key="${key// /}"
180
205
  [ -z "$key" ] && continue