@rpamis/comet 0.3.6 → 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 (53) hide show
  1. package/LICENSE +21 -21
  2. package/README.md +175 -63
  3. package/assets/manifest.json +11 -1
  4. package/assets/skills/comet/SKILL.md +44 -16
  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 +71 -55
  8. package/assets/skills/comet/scripts/comet-guard.sh +174 -18
  9. package/assets/skills/comet/scripts/comet-handoff.sh +133 -6
  10. package/assets/skills/comet/scripts/comet-hook-guard.sh +260 -0
  11. package/assets/skills/comet/scripts/comet-state.sh +300 -25
  12. package/assets/skills/comet/scripts/comet-yaml-validate.sh +24 -1
  13. package/assets/skills/comet-archive/SKILL.md +43 -10
  14. package/assets/skills/comet-build/SKILL.md +117 -22
  15. package/assets/skills/comet-design/SKILL.md +119 -13
  16. package/assets/skills/comet-hotfix/SKILL.md +51 -9
  17. package/assets/skills/comet-open/SKILL.md +86 -13
  18. package/assets/skills/comet-tweak/SKILL.md +33 -8
  19. package/assets/skills/comet-verify/SKILL.md +48 -9
  20. package/assets/skills-zh/comet/SKILL.md +47 -16
  21. package/assets/skills-zh/comet/reference/dirty-worktree.md +2 -1
  22. package/assets/skills-zh/comet-archive/SKILL.md +44 -11
  23. package/assets/skills-zh/comet-build/SKILL.md +120 -25
  24. package/assets/skills-zh/comet-design/SKILL.md +123 -16
  25. package/assets/skills-zh/comet-hotfix/SKILL.md +47 -9
  26. package/assets/skills-zh/comet-open/SKILL.md +87 -14
  27. package/assets/skills-zh/comet-tweak/SKILL.md +29 -8
  28. package/assets/skills-zh/comet-verify/SKILL.md +49 -12
  29. package/bin/comet.js +3 -3
  30. package/dist/commands/doctor.d.ts.map +1 -1
  31. package/dist/commands/doctor.js +22 -0
  32. package/dist/commands/doctor.js.map +1 -1
  33. package/dist/commands/init.d.ts +5 -1
  34. package/dist/commands/init.d.ts.map +1 -1
  35. package/dist/commands/init.js +64 -9
  36. package/dist/commands/init.js.map +1 -1
  37. package/dist/commands/update.d.ts.map +1 -1
  38. package/dist/commands/update.js +60 -1
  39. package/dist/commands/update.js.map +1 -1
  40. package/dist/core/codegraph.d.ts +9 -0
  41. package/dist/core/codegraph.d.ts.map +1 -0
  42. package/dist/core/codegraph.js +96 -0
  43. package/dist/core/codegraph.js.map +1 -0
  44. package/dist/core/platforms.d.ts +10 -0
  45. package/dist/core/platforms.d.ts.map +1 -1
  46. package/dist/core/platforms.js +163 -15
  47. package/dist/core/platforms.js.map +1 -1
  48. package/dist/core/skills.d.ts +34 -1
  49. package/dist/core/skills.d.ts.map +1 -1
  50. package/dist/core/skills.js +360 -1
  51. package/dist/core/skills.js.map +1 -1
  52. package/package.json +3 -1
  53. 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,17 +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
208
292
  build_pause: null
293
+ subagent_dispatch: null
294
+ tdd_mode: $tdd_mode
209
295
  isolation: $isolation
210
296
  verify_mode: $verify_mode
297
+ auto_transition: $auto_transition
211
298
  base_ref: $base_ref
212
299
  design_doc: null
213
300
  plan: null
214
301
  verify_result: pending
215
302
  verification_report: null
216
303
  branch_status: pending
217
- created_at: $(date +%Y-%m-%d)
304
+ created_at: $(date -u +%Y-%m-%d)
218
305
  verified_at: null
219
306
  archived: false
220
307
  EOF
@@ -240,6 +327,9 @@ cmd_get() {
240
327
  # Read and output the field value
241
328
  local value
242
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
243
333
  echo "${value:-}"
244
334
  }
245
335
 
@@ -265,14 +355,14 @@ cmd_set() {
265
355
  yellow "WARNING: Setting 'phase' directly bypasses state machine constraints." >&2
266
356
  yellow " Consider using: comet-state.sh transition <change-name> <event>" >&2
267
357
  ;;
268
- workflow|build_mode|build_pause|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)
269
359
  # Valid field
270
360
  ;;
271
361
  *)
272
362
  red "ERROR: Unknown field: '$field'" >&2
273
363
  red "Valid fields:" >&2
274
- red " workflow, phase, design_doc, plan, build_mode, build_pause, isolation," >&2
275
- 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
276
366
  red " verified_at, created_at, archived, base_ref, direct_override," >&2
277
367
  red " build_command, verify_command, handoff_context, handoff_hash" >&2
278
368
  exit 1
@@ -284,6 +374,9 @@ cmd_set() {
284
374
  workflow)
285
375
  validate_enum "$value" "full" "hotfix" "tweak"
286
376
  ;;
377
+ context_compression)
378
+ validate_enum "$value" "off" "beta"
379
+ ;;
287
380
  phase)
288
381
  validate_enum "$value" "open" "design" "build" "verify" "archive"
289
382
  ;;
@@ -293,12 +386,21 @@ cmd_set() {
293
386
  build_pause)
294
387
  validate_enum "$value" "null" "plan-ready"
295
388
  ;;
389
+ subagent_dispatch)
390
+ validate_enum "$value" "null" "confirmed"
391
+ ;;
392
+ tdd_mode)
393
+ validate_enum "$value" "tdd" "direct"
394
+ ;;
296
395
  isolation)
297
396
  validate_enum "$value" "branch" "worktree"
298
397
  ;;
299
398
  verify_mode)
300
399
  validate_enum "$value" "light" "full"
301
400
  ;;
401
+ auto_transition)
402
+ validate_enum "$value" "true" "false"
403
+ ;;
302
404
  verify_result)
303
405
  validate_enum "$value" "pending" "pass" "fail"
304
406
  ;;
@@ -311,8 +413,11 @@ cmd_set() {
311
413
  direct_override)
312
414
  validate_enum "$value" "true" "false"
313
415
  ;;
314
- design_doc|plan|verification_report|verified_at|created_at|build_command|verify_command|handoff_context|handoff_hash)
315
- # 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
316
421
  ;;
317
422
  esac
318
423
 
@@ -357,11 +462,13 @@ require_verification_evidence() {
357
462
 
358
463
  require_build_decisions() {
359
464
  local change_name="$1"
360
- local workflow build_mode isolation direct_override
465
+ local workflow build_mode isolation direct_override subagent_dispatch tdd_mode
361
466
  workflow=$(cmd_get "$change_name" "workflow")
362
467
  build_mode=$(cmd_get "$change_name" "build_mode")
363
468
  isolation=$(cmd_get "$change_name" "isolation")
364
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)
365
472
 
366
473
  case "$isolation" in
367
474
  branch|worktree) ;;
@@ -383,6 +490,16 @@ require_build_decisions() {
383
490
  red "ERROR: Cannot transition '$change_name': build_mode=direct is only allowed for hotfix/tweak unless direct_override=true" >&2
384
491
  exit 1
385
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
386
503
  }
387
504
 
388
505
  cmd_transition() {
@@ -390,7 +507,7 @@ cmd_transition() {
390
507
  local event="$2"
391
508
 
392
509
  validate_change_name "$change_name"
393
- 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"
394
511
 
395
512
  case "$event" in
396
513
  open-complete)
@@ -410,23 +527,41 @@ cmd_transition() {
410
527
  build-complete)
411
528
  require_phase "$change_name" "build"
412
529
  require_build_decisions "$change_name"
530
+ local current_verify_result
531
+ current_verify_result=$(cmd_get "$change_name" "verify_result")
413
532
  cmd_set "$change_name" phase verify
414
533
  cmd_set "$change_name" verify_result pending
415
- cmd_set "$change_name" verification_report null
416
- 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
417
540
  ;;
418
541
  verify-pass)
419
542
  require_phase "$change_name" "verify"
420
543
  require_verification_evidence "$change_name"
421
544
  cmd_set "$change_name" verify_result pass
422
545
  cmd_set "$change_name" phase archive
423
- cmd_set "$change_name" verified_at "$(date +%Y-%m-%d)"
546
+ cmd_set "$change_name" verified_at "$(date -u +%Y-%m-%d)"
424
547
  ;;
425
548
  verify-fail)
426
549
  require_phase "$change_name" "verify"
427
550
  cmd_set "$change_name" verify_result fail
428
551
  cmd_set "$change_name" phase build
429
- 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
430
565
  ;;
431
566
  archived)
432
567
  require_phase "$change_name" "archive"
@@ -636,7 +771,7 @@ cmd_recover() {
636
771
 
637
772
  # Read all relevant fields
638
773
  local design_doc plan verify_result verify_mode verification_report
639
- local branch_status handoff_context handoff_hash isolation build_mode build_pause direct_override
774
+ local branch_status handoff_context handoff_hash isolation build_mode build_pause subagent_dispatch tdd_mode direct_override
640
775
  design_doc=$(cmd_get "$change_name" "design_doc")
641
776
  plan=$(cmd_get "$change_name" "plan")
642
777
  verify_result=$(cmd_get "$change_name" "verify_result")
@@ -648,6 +783,8 @@ cmd_recover() {
648
783
  isolation=$(cmd_get "$change_name" "isolation")
649
784
  build_mode=$(cmd_get "$change_name" "build_mode")
650
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)
651
788
  direct_override=$(cmd_get "$change_name" "direct_override" 2>/dev/null || true)
652
789
 
653
790
  echo "State fields:"
@@ -656,15 +793,23 @@ cmd_recover() {
656
793
  case "$phase" in
657
794
  open)
658
795
  echo " Artifacts:"
796
+ local artifacts_done=0
659
797
  for f in proposal.md design.md tasks.md; do
660
798
  if file_nonempty "$change_dir/$f"; then
661
799
  echo " - ${f}: DONE"
800
+ artifacts_done=$((artifacts_done + 1))
662
801
  else
663
802
  echo " - ${f}: PENDING"
664
803
  fi
665
804
  done
666
805
  echo ""
667
- 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
668
813
  ;;
669
814
  design)
670
815
  echo " Artifacts:"
@@ -694,6 +839,10 @@ cmd_recover() {
694
839
  field_status "isolation" "$isolation"
695
840
  field_status "build_mode" "$build_mode"
696
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
697
846
  if [ "$build_mode" = "direct" ] && [ "$workflow" != "hotfix" ] && [ "$workflow" != "tweak" ]; then
698
847
  field_status "direct_override" "$direct_override"
699
848
  fi
@@ -704,29 +853,72 @@ cmd_recover() {
704
853
  # Count completed vs pending tasks
705
854
  local tasks_file="$change_dir/tasks.md"
706
855
  local total=0 done=0 pending=0
856
+ local plan_total=0 plan_done=0 plan_pending=0
707
857
  if [ -f "$tasks_file" ]; then
708
- total=$(grep -c '^\- \[' "$tasks_file" 2>/dev/null || echo "0")
709
- 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}"
710
862
  pending=$((total - done))
711
863
  echo " Tasks: ${done}/${total} done, ${pending} pending"
712
864
  else
713
865
  echo " Tasks: tasks.md MISSING"
714
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
715
877
  echo ""
716
878
  if [ "$build_pause" = "plan-ready" ] && [ -n "$plan" ] && [ "$plan" != "null" ] && [ -f "$plan" ] && { [ "$isolation" = "null" ] || [ -z "$isolation" ] || [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; }; then
717
879
  echo "Recovery action: Plan-ready pause detected. Ask the user whether to continue, then choose isolation and build mode without regenerating the plan."
718
880
  elif [ "$build_pause" = "plan-ready" ] && { [ -z "$plan" ] || [ "$plan" = "null" ] || [ ! -f "$plan" ]; }; then
719
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."
720
882
  elif [ "$build_pause" = "plan-ready" ]; then
721
- 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."
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
722
894
  elif [ "$isolation" = "null" ] || [ -z "$isolation" ]; then
723
- echo "Recovery action: Isolation not selected. Use AskUserQuestion to ask user for branch/worktree choice."
895
+ echo "Recovery action: Isolation not selected. Use the current platform's user confirmation mechanism to ask user for branch/worktree choice."
724
896
  elif [ "$build_mode" = "null" ] || [ -z "$build_mode" ]; then
725
- 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."
726
900
  elif [ ! -f "$tasks_file" ]; then
727
901
  echo "Recovery action: tasks.md missing. Verify change directory integrity."
728
902
  elif [ "$pending" -gt 0 ]; then
729
- 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
730
922
  else
731
923
  echo "Recovery action: All tasks done. Run guard to transition to verify."
732
924
  fi
@@ -799,7 +991,7 @@ cmd_scale() {
799
991
  local plan_file base_ref=""
800
992
  plan_file=$(cmd_get "$change_name" "plan" 2>/dev/null || true)
801
993
  if [ -n "$plan_file" ] && [ "$plan_file" != "null" ] && [ -f "$plan_file" ]; then
802
- 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)
803
995
  fi
804
996
  # Fallback to base_ref stored in .comet.yaml (set during init)
805
997
  if [ -z "$base_ref" ] || [ "$base_ref" = "null" ]; then
@@ -832,6 +1024,81 @@ cmd_scale() {
832
1024
  green "[SCALE] verify_mode=$result"
833
1025
  }
834
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
+
835
1102
  # --- Main ---
836
1103
 
837
1104
  SUBCOMMAND="${1:-}"
@@ -863,7 +1130,7 @@ case "$SUBCOMMAND" in
863
1130
  transition)
864
1131
  if [ $# -lt 2 ]; then
865
1132
  red "Usage: comet-state.sh transition <change-name> <event>" >&2
866
- 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
867
1134
  exit 1
868
1135
  fi
869
1136
  cmd_transition "$@"
@@ -888,6 +1155,13 @@ case "$SUBCOMMAND" in
888
1155
  fi
889
1156
  cmd_scale "$@"
890
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
+ ;;
891
1165
  *)
892
1166
  red "Unknown subcommand: $SUBCOMMAND" >&2
893
1167
  echo "" >&2
@@ -900,6 +1174,7 @@ case "$SUBCOMMAND" in
900
1174
  echo " transition <change-name> <event> — Apply a validated state transition" >&2
901
1175
  echo " check <change-name> <phase> — Verify entry requirements for a phase" >&2
902
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
903
1178
  echo "" >&2
904
1179
  echo "Workflows: full, hotfix, tweak" >&2
905
1180
  echo "Phases for check: open, design, build, verify, archive" >&2
@@ -123,12 +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")
129
143
  build_pause=$(field_value "build_pause")
144
+ subagent_dispatch=$(field_value "subagent_dispatch")
145
+ tdd_mode=$(field_value "tdd_mode")
130
146
  isolation=$(field_value "isolation")
131
147
  verify_mode=$(field_value "verify_mode")
148
+ auto_transition=$(field_value "auto_transition")
132
149
  verify_result=$(field_value "verify_result")
133
150
  branch_status=$(field_value "branch_status")
134
151
  archived=$(field_value "archived")
@@ -140,10 +157,16 @@ handoff_hash=$(field_value "handoff_hash")
140
157
 
141
158
  validate_enum "workflow" "$workflow" "full hotfix tweak"
142
159
  validate_enum "phase" "$phase" "open design build verify archive"
160
+ validate_enum "context_compression" "$context_compression" "off beta"
143
161
  validate_enum "build_mode" "$build_mode" "subagent-driven-development executing-plans direct"
144
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"
145
165
  validate_enum "isolation" "$isolation" "branch worktree"
146
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
147
170
  validate_enum "verify_result" "$verify_result" "pending pass fail"
148
171
  validate_enum "branch_status" "$branch_status" "pending handled"
149
172
  validate_enum "archived" "$archived" "true false"
@@ -176,7 +199,7 @@ if [ -n "$handoff_hash" ] && [ "$handoff_hash" != "null" ]; then
176
199
  fi
177
200
 
178
201
  # --- Unknown keys check ---
179
- KNOWN_KEYS="workflow phase design_doc plan build_mode build_pause 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"
180
203
  while IFS=: read -r key _; do
181
204
  key="${key// /}"
182
205
  [ -z "$key" ] && continue