prizmkit 1.1.98 → 1.1.100

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 (79) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +5 -5
  3. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +1 -1
  4. package/bundled/dev-pipeline/launch-feature-daemon.sh +1 -1
  5. package/bundled/dev-pipeline/launch-refactor-daemon.sh +1 -1
  6. package/bundled/dev-pipeline/lib/common.sh +244 -25
  7. package/bundled/dev-pipeline/reset-bug.sh +32 -2
  8. package/bundled/dev-pipeline/reset-feature.sh +32 -2
  9. package/bundled/dev-pipeline/reset-refactor.sh +32 -2
  10. package/bundled/dev-pipeline/run-bugfix.sh +133 -18
  11. package/bundled/dev-pipeline/run-feature.sh +134 -18
  12. package/bundled/dev-pipeline/run-refactor.sh +133 -18
  13. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  14. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  15. package/bundled/dev-pipeline/scripts/detect-stuck.py +5 -5
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +29 -35
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +19 -25
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +19 -25
  19. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +1 -1
  20. package/bundled/dev-pipeline/scripts/init-pipeline.py +1 -1
  21. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +1 -1
  22. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
  23. package/bundled/dev-pipeline/scripts/update-bug-status.py +220 -13
  24. package/bundled/dev-pipeline/scripts/update-feature-status.py +244 -18
  25. package/bundled/dev-pipeline/scripts/update-refactor-status.py +220 -13
  26. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
  27. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
  28. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
  29. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
  30. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  31. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  32. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
  33. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  34. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  35. package/bundled/dev-pipeline/tests/test_auto_skip.py +518 -8
  36. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
  37. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
  38. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  39. package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
  40. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +95 -11
  41. package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
  42. package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
  43. package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
  44. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
  45. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
  46. package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
  47. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +5 -5
  48. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +29 -35
  49. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +20 -25
  50. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +20 -25
  51. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +1 -1
  52. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +1 -1
  53. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +1 -1
  54. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
  55. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +220 -13
  56. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +244 -18
  57. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +220 -13
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
  59. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
  60. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
  61. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
  62. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
  63. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
  64. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
  65. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
  66. package/bundled/skills/_metadata.json +1 -1
  67. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +2 -2
  68. package/bundled/skills/feature-pipeline-launcher/SKILL.md +2 -2
  69. package/bundled/skills/recovery-workflow/SKILL.md +2 -2
  70. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  71. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +2 -2
  72. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +2 -2
  73. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +2 -2
  74. package/bundled/skills-windows/recovery-workflow/SKILL.md +2 -2
  75. package/bundled/skills-windows/recovery-workflow/evals/evals.json +2 -2
  76. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +2 -2
  77. package/package.json +1 -1
  78. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  79. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
@@ -46,6 +46,8 @@ SESSION_STATUS_VALUES = [
46
46
  "crashed",
47
47
  "timed_out",
48
48
  "infra_error",
49
+ "context_overflow",
50
+ "stalled_context_continuation",
49
51
  "commit_missing",
50
52
  "docs_missing",
51
53
  "merge_conflict",
@@ -117,6 +119,37 @@ def parse_args():
117
119
  default=None,
118
120
  help="Feature filter: comma-separated IDs (F-001,F-003) or range (F-001:F-010), or mixed.",
119
121
  )
122
+ parser.add_argument(
123
+ "--active-dev-branch",
124
+ default=None,
125
+ help="Development branch preserved for continuation after context overflow.",
126
+ )
127
+ parser.add_argument(
128
+ "--base-branch",
129
+ default=None,
130
+ help="Original/base branch for continuation branch reuse.",
131
+ )
132
+ parser.add_argument(
133
+ "--last-fatal-error-code",
134
+ default=None,
135
+ help="Fatal runtime error code associated with continuation.",
136
+ )
137
+ parser.add_argument(
138
+ "--continuation-summary-path",
139
+ default=None,
140
+ help="Optional continuation summary artifact path.",
141
+ )
142
+ parser.add_argument(
143
+ "--no-progress-count",
144
+ type=int,
145
+ default=None,
146
+ help="Consecutive context-overflow sessions without objective progress.",
147
+ )
148
+ parser.add_argument(
149
+ "--progress-fingerprint",
150
+ default=None,
151
+ help="JSON progress fingerprint captured after the session.",
152
+ )
120
153
  return parser.parse_args()
121
154
 
122
155
 
@@ -167,9 +200,7 @@ def load_feature_status(state_dir, feature_id):
167
200
  The 'status' field is NOT included — status lives exclusively
168
201
  in feature-list.json.
169
202
  """
170
- status_path = os.path.join(
171
- state_dir, "features", feature_id, "status.json"
172
- )
203
+ status_path = os.path.join(state_dir, feature_id, "status.json")
173
204
  if not os.path.isfile(status_path):
174
205
  now = now_iso()
175
206
  return {
@@ -210,9 +241,7 @@ def save_feature_status(state_dir, feature_id, status_data):
210
241
  """Write the status.json for a feature (runtime fields only)."""
211
242
  # Defensively strip status — it belongs in feature-list.json
212
243
  status_data.pop("status", None)
213
- status_path = os.path.join(
214
- state_dir, "features", feature_id, "status.json"
215
- )
244
+ status_path = os.path.join(state_dir, feature_id, "status.json")
216
245
  return write_json_file(status_path, status_data)
217
246
 
218
247
 
@@ -248,6 +277,128 @@ def update_feature_in_list(feature_list_path, feature_id, new_status):
248
277
  return write_json_file(feature_list_path, data)
249
278
 
250
279
 
280
+ CONTINUATION_PENDING_FIELDS = [
281
+ "active_dev_branch",
282
+ "base_branch",
283
+ "continuation_pending",
284
+ "continuation_reason",
285
+ "last_context_overflow_session_id",
286
+ "previous_session_id",
287
+ "last_fatal_error_code",
288
+ "continuation_summary_path",
289
+ "no_progress_count",
290
+ "last_progress_fingerprint",
291
+ "needs_attention",
292
+ "stalled_context_continuation",
293
+ "stalled_reason",
294
+ ]
295
+
296
+ CONTINUATION_COUNTER_FIELDS = [
297
+ "context_overflow_count",
298
+ "continuation_count",
299
+ ]
300
+
301
+
302
+ def has_pending_continuation_metadata(status_data):
303
+ """Return True when runtime state still has pending continuation metadata."""
304
+ return any(key in status_data for key in CONTINUATION_PENDING_FIELDS)
305
+
306
+
307
+ def clear_pending_continuation_metadata(status_data):
308
+ """Clear branch/pending continuation fields while preserving observability counters."""
309
+ for key in CONTINUATION_PENDING_FIELDS:
310
+ status_data.pop(key, None)
311
+
312
+
313
+ def reset_continuation_metadata(status_data):
314
+ """Clear all continuation runtime metadata, including manual-reset counters."""
315
+ clear_pending_continuation_metadata(status_data)
316
+ for key in CONTINUATION_COUNTER_FIELDS:
317
+ status_data.pop(key, None)
318
+
319
+
320
+ STALL_REASON = "context_overflow_without_git_checkpoint_or_artifact_progress"
321
+
322
+
323
+ def _load_progress_fingerprint(raw):
324
+ if not raw:
325
+ return None
326
+ try:
327
+ return json.loads(raw)
328
+ except (TypeError, ValueError):
329
+ return {"unparsed": str(raw)}
330
+
331
+
332
+ def _set_continuation_common(status_data, args, session_id):
333
+ if args.active_dev_branch:
334
+ status_data["active_dev_branch"] = args.active_dev_branch
335
+ if args.base_branch:
336
+ status_data["base_branch"] = args.base_branch
337
+ status_data["last_context_overflow_session_id"] = session_id
338
+ status_data["previous_session_id"] = session_id
339
+ status_data["last_fatal_error_code"] = args.last_fatal_error_code or "context_overflow"
340
+ if args.continuation_summary_path:
341
+ status_data["continuation_summary_path"] = args.continuation_summary_path
342
+ else:
343
+ status_data.pop("continuation_summary_path", None)
344
+ if args.no_progress_count is not None:
345
+ status_data["no_progress_count"] = max(0, args.no_progress_count)
346
+ fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
347
+ if fingerprint is not None:
348
+ status_data["last_progress_fingerprint"] = fingerprint
349
+ status_data.pop("last_infra_error_session_id", None)
350
+ status_data["resume_from_phase"] = None
351
+ if session_id:
352
+ status_data["last_session_id"] = session_id
353
+
354
+
355
+ def set_context_overflow_continuation_metadata(status_data, args, session_id):
356
+ """Record continuation metadata for a context-overflow outcome in status.json."""
357
+ status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
358
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
359
+ status_data["continuation_pending"] = True
360
+ status_data["continuation_reason"] = "context_overflow"
361
+ status_data.pop("needs_attention", None)
362
+ status_data.pop("stalled_context_continuation", None)
363
+ status_data.pop("stalled_reason", None)
364
+ _set_continuation_common(status_data, args, session_id)
365
+
366
+
367
+ def set_stalled_context_continuation_metadata(status_data, args, session_id):
368
+ """Record no-progress context-overflow stall without consuming retry budgets."""
369
+ status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
370
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
371
+ _set_continuation_common(status_data, args, session_id)
372
+ status_data["continuation_pending"] = False
373
+ status_data["continuation_reason"] = STALL_REASON
374
+ status_data["needs_attention"] = True
375
+ status_data["stalled_context_continuation"] = True
376
+ status_data["stalled_reason"] = STALL_REASON
377
+ if args.no_progress_count is None or args.no_progress_count < 2:
378
+ status_data["no_progress_count"] = 2
379
+
380
+
381
+ def continuation_metadata_summary(status_data):
382
+ """Return additive continuation fields for CLI JSON outputs."""
383
+ return {
384
+ "active_dev_branch": status_data.get("active_dev_branch"),
385
+ "base_branch": status_data.get("base_branch"),
386
+ "continuation_pending": status_data.get("continuation_pending", False),
387
+ "continuation_reason": status_data.get("continuation_reason"),
388
+ "last_context_overflow_session_id": status_data.get("last_context_overflow_session_id"),
389
+ "last_fatal_error_code": status_data.get("last_fatal_error_code"),
390
+ "previous_session_id": status_data.get("previous_session_id"),
391
+ "context_overflow_count": status_data.get("context_overflow_count", 0),
392
+ "continuation_count": status_data.get("continuation_count", 0),
393
+ "no_progress_count": status_data.get("no_progress_count", 0),
394
+ "last_progress_fingerprint": status_data.get("last_progress_fingerprint"),
395
+ "needs_attention": status_data.get("needs_attention", False),
396
+ "stalled_context_continuation": status_data.get("stalled_context_continuation", False),
397
+ "stalled_reason": status_data.get("stalled_reason"),
398
+ "continuation_summary_path": status_data.get("continuation_summary_path"),
399
+ }
400
+
401
+
251
402
  def _default_project_root():
252
403
  # Script lives at <pipeline>/scripts/update-feature-status.py.
253
404
  # Pipeline may be either <project>/.prizmkit/dev-pipeline (user install)
@@ -284,6 +435,45 @@ def _get_feature_slug(feature_list_path, feature_id):
284
435
  return None
285
436
 
286
437
 
438
+ def _resolve_project_file(project_root, path):
439
+ if not path:
440
+ return None
441
+ if os.path.isabs(path):
442
+ target = os.path.abspath(path)
443
+ else:
444
+ target = os.path.abspath(os.path.join(project_root, path))
445
+ root = os.path.abspath(project_root)
446
+ try:
447
+ rel = os.path.relpath(target, root)
448
+ except ValueError:
449
+ return None
450
+ if rel == os.pardir or rel.startswith(os.pardir + os.sep):
451
+ return None
452
+ return target
453
+
454
+
455
+ def cleanup_continuation_artifacts(project_root, status_data, candidate_paths=None):
456
+ """Delete durable continuation handoff files for a manual clean reset."""
457
+ candidates = []
458
+ if isinstance(status_data, dict):
459
+ candidates.append(status_data.get("continuation_summary_path"))
460
+ candidates.extend(candidate_paths or [])
461
+
462
+ cleaned = []
463
+ seen = set()
464
+ for candidate in candidates:
465
+ target = _resolve_project_file(project_root, candidate)
466
+ if not target or target in seen:
467
+ continue
468
+ seen.add(target)
469
+ if os.path.basename(target) != "continuation-summary.md":
470
+ continue
471
+ if os.path.isfile(target):
472
+ os.remove(target)
473
+ cleaned.append("Deleted continuation artifact {}".format(target))
474
+ return cleaned
475
+
476
+
287
477
  def cleanup_feature_artifacts(feature_list_path, state_dir, feature_id, project_root=None):
288
478
  """Delete intermediate artifacts for a failed feature run.
289
479
 
@@ -296,7 +486,7 @@ def cleanup_feature_artifacts(feature_list_path, state_dir, feature_id, project_
296
486
  cleaned = []
297
487
 
298
488
  # 1) Remove all session history
299
- sessions_dir = os.path.join(state_dir, "features", feature_id, "sessions")
489
+ sessions_dir = os.path.join(state_dir, feature_id, "sessions")
300
490
  sessions_deleted = 0
301
491
  if os.path.isdir(sessions_dir):
302
492
  for entry in os.listdir(sessions_dir):
@@ -307,7 +497,7 @@ def cleanup_feature_artifacts(feature_list_path, state_dir, feature_id, project_
307
497
  cleaned.append("Deleted {} session(s) from {}".format(sessions_deleted, sessions_dir))
308
498
 
309
499
  # 2) Remove transient files under feature state dir (keep status.json)
310
- feature_dir = os.path.join(state_dir, "features", feature_id)
500
+ feature_dir = os.path.join(state_dir, feature_id)
311
501
  if os.path.isdir(feature_dir):
312
502
  for entry in os.listdir(feature_dir):
313
503
  if entry == "status.json" or entry == "sessions":
@@ -344,8 +534,7 @@ def cleanup_feature_artifacts(feature_list_path, state_dir, feature_id, project_
344
534
 
345
535
  def load_session_status(state_dir, feature_id, session_id):
346
536
  """Load a session's session-status.json file."""
347
- session_status_path = os.path.join(
348
- state_dir, "features", feature_id, "sessions",
537
+ session_status_path = os.path.join(state_dir, feature_id, "sessions",
349
538
  session_id, "session-status.json"
350
539
  )
351
540
  data, err = load_json_file(session_status_path)
@@ -580,6 +769,7 @@ def action_get_next(feature_list_data, state_dir, feature_filter=None):
580
769
  "infra_error_count": chosen_status_data.get("infra_error_count", 0),
581
770
  "resume_from_phase": chosen_status_data.get("resume_from_phase", None),
582
771
  }
772
+ result.update(continuation_metadata_summary(chosen_status_data))
583
773
  print(json.dumps(result, indent=2, ensure_ascii=False))
584
774
 
585
775
 
@@ -616,6 +806,8 @@ def action_update(args, feature_list_path, state_dir):
616
806
  if session_status == "success":
617
807
  fs["infra_error_count"] = 0
618
808
  fs["last_infra_error_session_id"] = None
809
+ pending_continuation_metadata = has_pending_continuation_metadata(fs)
810
+ clear_pending_continuation_metadata(fs)
619
811
  # No-op guard: if this exact successful session was already recorded,
620
812
  # avoid rewriting state files again (prevents post-commit dirty changes).
621
813
  existing_sessions = fs.get("sessions", [])
@@ -625,7 +817,7 @@ def action_update(args, feature_list_path, state_dir):
625
817
  and session_id in existing_sessions
626
818
  and fs.get("last_session_id") == session_id
627
819
  )
628
- if already_completed and (same_session_already_recorded or not session_id):
820
+ if already_completed and not pending_continuation_metadata and (same_session_already_recorded or not session_id):
629
821
  summary = {
630
822
  "action": "update",
631
823
  "feature_id": feature_id,
@@ -678,7 +870,7 @@ def action_update(args, feature_list_path, state_dir):
678
870
  fs["infra_error_count"] = infra_error_count
679
871
  fs["last_infra_error_session_id"] = session_id
680
872
  fs["max_infra_retries"] = max_infra_retries
681
- fs["degraded_reason"] = "infra_error"
873
+ fs["degraded_reason"] = session_status
682
874
  if session_id:
683
875
  fs["last_session_id"] = session_id
684
876
  fs["resume_from_phase"] = None
@@ -690,6 +882,23 @@ def action_update(args, feature_list_path, state_dir):
690
882
  else:
691
883
  new_status = "pending"
692
884
 
885
+ err = update_feature_in_list(feature_list_path, feature_id, new_status)
886
+ if err:
887
+ error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
888
+ return
889
+ elif session_status == "context_overflow":
890
+ # Context overflow is continuation state, not code or infra retry state.
891
+ set_context_overflow_continuation_metadata(fs, args, session_id)
892
+ new_status = "in_progress"
893
+ if current_list_status != "in_progress":
894
+ err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
895
+ if err:
896
+ error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
897
+ return
898
+ elif session_status == "stalled_context_continuation":
899
+ # No-progress context-overflow stall pauses automation without consuming retry budgets.
900
+ set_stalled_context_continuation_metadata(fs, args, session_id)
901
+ new_status = "failed"
693
902
  err = update_feature_in_list(feature_list_path, feature_id, new_status)
694
903
  if err:
695
904
  error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
@@ -755,6 +964,14 @@ def action_update(args, feature_list_path, state_dir):
755
964
  summary["restart_policy"] = "infra_retry"
756
965
  summary["infra_error_count"] = fs.get("infra_error_count", 0)
757
966
  summary["artifacts_preserved"] = True
967
+ elif session_status == "context_overflow":
968
+ summary["restart_policy"] = "context_overflow_continuation"
969
+ summary.update(continuation_metadata_summary(fs))
970
+ summary["artifacts_preserved"] = True
971
+ elif session_status == "stalled_context_continuation":
972
+ summary["restart_policy"] = "needs_attention"
973
+ summary.update(continuation_metadata_summary(fs))
974
+ summary["artifacts_preserved"] = True
758
975
  elif session_status != "success":
759
976
  summary["restart_policy"] = "preserve_and_retry"
760
977
  summary["artifacts_preserved"] = True
@@ -785,7 +1002,7 @@ def _calc_feature_duration(state_dir, feature_id):
785
1002
  to the last update time for the calculation.
786
1003
  Returns None if the duration cannot be calculated.
787
1004
  """
788
- fs_path = os.path.join(state_dir, "features", feature_id, "status.json")
1005
+ fs_path = os.path.join(state_dir, feature_id, "status.json")
789
1006
  if not os.path.isfile(fs_path):
790
1007
  return None
791
1008
  data, err = load_json_file(fs_path)
@@ -1162,10 +1379,11 @@ def action_start(args, feature_list_path, state_dir):
1162
1379
  error_out("Failed to save feature status: {}".format(err))
1163
1380
  return
1164
1381
 
1165
- err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
1166
- if err:
1167
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1168
- return
1382
+ if old_status != "in_progress":
1383
+ err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
1384
+ if err:
1385
+ error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1386
+ return
1169
1387
 
1170
1388
  result = {
1171
1389
  "action": "start",
@@ -1207,6 +1425,7 @@ def action_reset(args, feature_list_path, state_dir):
1207
1425
  fs["sessions"] = []
1208
1426
  fs["last_session_id"] = None
1209
1427
  fs["resume_from_phase"] = None
1428
+ reset_continuation_metadata(fs)
1210
1429
  fs["updated_at"] = now_iso()
1211
1430
 
1212
1431
  # Write back status.json
@@ -1261,7 +1480,7 @@ def action_clean(args, feature_list_path, state_dir):
1261
1480
  cleaned = []
1262
1481
 
1263
1482
  # 1. Delete session history
1264
- sessions_dir = os.path.join(state_dir, "features", feature_id, "sessions")
1483
+ sessions_dir = os.path.join(state_dir, feature_id, "sessions")
1265
1484
  sessions_deleted = 0
1266
1485
  if os.path.isdir(sessions_dir):
1267
1486
  for entry in os.listdir(sessions_dir):
@@ -1296,12 +1515,18 @@ def action_clean(args, feature_list_path, state_dir):
1296
1515
  old_status = get_feature_status_from_list(feature_list_path, feature_id)
1297
1516
  old_retry = fs.get("retry_count", 0)
1298
1517
 
1518
+ continuation_candidates = [
1519
+ os.path.join(".prizmkit", "specs", feature_slug, "continuation-summary.md"),
1520
+ ]
1521
+ cleaned.extend(cleanup_continuation_artifacts(project_root, fs, continuation_candidates))
1522
+
1299
1523
  fs["retry_count"] = 0
1300
1524
  fs["infra_error_count"] = 0
1301
1525
  fs["last_infra_error_session_id"] = None
1302
1526
  fs["sessions"] = []
1303
1527
  fs["last_session_id"] = None
1304
1528
  fs["resume_from_phase"] = None
1529
+ reset_continuation_metadata(fs)
1305
1530
  fs["updated_at"] = now_iso()
1306
1531
 
1307
1532
  err = save_feature_status(state_dir, feature_id, fs)
@@ -1463,6 +1688,7 @@ def action_unskip(args, feature_list_path, state_dir):
1463
1688
  fs["sessions"] = []
1464
1689
  fs["last_session_id"] = None
1465
1690
  fs["resume_from_phase"] = None
1691
+ reset_continuation_metadata(fs)
1466
1692
  fs["updated_at"] = now_iso()
1467
1693
  save_feature_status(state_dir, fid, fs)
1468
1694