prizmkit 1.1.99 → 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 (57) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/lib/common.sh +244 -25
  3. package/bundled/dev-pipeline/reset-bug.sh +30 -0
  4. package/bundled/dev-pipeline/reset-feature.sh +30 -0
  5. package/bundled/dev-pipeline/reset-refactor.sh +30 -0
  6. package/bundled/dev-pipeline/run-bugfix.sh +128 -13
  7. package/bundled/dev-pipeline/run-feature.sh +130 -14
  8. package/bundled/dev-pipeline/run-refactor.sh +128 -13
  9. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  10. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  11. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -30
  12. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
  13. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
  14. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
  15. package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
  16. package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
  17. package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
  18. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
  19. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
  20. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
  21. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
  22. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  23. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  24. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
  25. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  26. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  27. package/bundled/dev-pipeline/tests/test_auto_skip.py +510 -0
  28. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
  29. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
  30. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  31. package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
  32. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +93 -9
  33. package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
  34. package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
  35. package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
  36. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
  37. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
  38. package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
  39. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +27 -30
  40. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
  41. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
  42. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
  43. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
  44. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
  45. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
  46. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
  47. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
  48. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
  49. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
  50. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
  51. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
  52. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
  53. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
  54. package/bundled/skills/_metadata.json +1 -1
  55. package/package.json +1 -1
  56. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  57. 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
 
@@ -244,6 +277,128 @@ def update_feature_in_list(feature_list_path, feature_id, new_status):
244
277
  return write_json_file(feature_list_path, data)
245
278
 
246
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
+
247
402
  def _default_project_root():
248
403
  # Script lives at <pipeline>/scripts/update-feature-status.py.
249
404
  # Pipeline may be either <project>/.prizmkit/dev-pipeline (user install)
@@ -280,6 +435,45 @@ def _get_feature_slug(feature_list_path, feature_id):
280
435
  return None
281
436
 
282
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
+
283
477
  def cleanup_feature_artifacts(feature_list_path, state_dir, feature_id, project_root=None):
284
478
  """Delete intermediate artifacts for a failed feature run.
285
479
 
@@ -575,6 +769,7 @@ def action_get_next(feature_list_data, state_dir, feature_filter=None):
575
769
  "infra_error_count": chosen_status_data.get("infra_error_count", 0),
576
770
  "resume_from_phase": chosen_status_data.get("resume_from_phase", None),
577
771
  }
772
+ result.update(continuation_metadata_summary(chosen_status_data))
578
773
  print(json.dumps(result, indent=2, ensure_ascii=False))
579
774
 
580
775
 
@@ -611,6 +806,8 @@ def action_update(args, feature_list_path, state_dir):
611
806
  if session_status == "success":
612
807
  fs["infra_error_count"] = 0
613
808
  fs["last_infra_error_session_id"] = None
809
+ pending_continuation_metadata = has_pending_continuation_metadata(fs)
810
+ clear_pending_continuation_metadata(fs)
614
811
  # No-op guard: if this exact successful session was already recorded,
615
812
  # avoid rewriting state files again (prevents post-commit dirty changes).
616
813
  existing_sessions = fs.get("sessions", [])
@@ -620,7 +817,7 @@ def action_update(args, feature_list_path, state_dir):
620
817
  and session_id in existing_sessions
621
818
  and fs.get("last_session_id") == session_id
622
819
  )
623
- 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):
624
821
  summary = {
625
822
  "action": "update",
626
823
  "feature_id": feature_id,
@@ -673,7 +870,7 @@ def action_update(args, feature_list_path, state_dir):
673
870
  fs["infra_error_count"] = infra_error_count
674
871
  fs["last_infra_error_session_id"] = session_id
675
872
  fs["max_infra_retries"] = max_infra_retries
676
- fs["degraded_reason"] = "infra_error"
873
+ fs["degraded_reason"] = session_status
677
874
  if session_id:
678
875
  fs["last_session_id"] = session_id
679
876
  fs["resume_from_phase"] = None
@@ -685,6 +882,23 @@ def action_update(args, feature_list_path, state_dir):
685
882
  else:
686
883
  new_status = "pending"
687
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"
688
902
  err = update_feature_in_list(feature_list_path, feature_id, new_status)
689
903
  if err:
690
904
  error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
@@ -750,6 +964,14 @@ def action_update(args, feature_list_path, state_dir):
750
964
  summary["restart_policy"] = "infra_retry"
751
965
  summary["infra_error_count"] = fs.get("infra_error_count", 0)
752
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
753
975
  elif session_status != "success":
754
976
  summary["restart_policy"] = "preserve_and_retry"
755
977
  summary["artifacts_preserved"] = True
@@ -1157,10 +1379,11 @@ def action_start(args, feature_list_path, state_dir):
1157
1379
  error_out("Failed to save feature status: {}".format(err))
1158
1380
  return
1159
1381
 
1160
- err = update_feature_in_list(feature_list_path, feature_id, "in_progress")
1161
- if err:
1162
- error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1163
- 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
1164
1387
 
1165
1388
  result = {
1166
1389
  "action": "start",
@@ -1202,6 +1425,7 @@ def action_reset(args, feature_list_path, state_dir):
1202
1425
  fs["sessions"] = []
1203
1426
  fs["last_session_id"] = None
1204
1427
  fs["resume_from_phase"] = None
1428
+ reset_continuation_metadata(fs)
1205
1429
  fs["updated_at"] = now_iso()
1206
1430
 
1207
1431
  # Write back status.json
@@ -1291,12 +1515,18 @@ def action_clean(args, feature_list_path, state_dir):
1291
1515
  old_status = get_feature_status_from_list(feature_list_path, feature_id)
1292
1516
  old_retry = fs.get("retry_count", 0)
1293
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
+
1294
1523
  fs["retry_count"] = 0
1295
1524
  fs["infra_error_count"] = 0
1296
1525
  fs["last_infra_error_session_id"] = None
1297
1526
  fs["sessions"] = []
1298
1527
  fs["last_session_id"] = None
1299
1528
  fs["resume_from_phase"] = None
1529
+ reset_continuation_metadata(fs)
1300
1530
  fs["updated_at"] = now_iso()
1301
1531
 
1302
1532
  err = save_feature_status(state_dir, feature_id, fs)
@@ -1458,6 +1688,7 @@ def action_unskip(args, feature_list_path, state_dir):
1458
1688
  fs["sessions"] = []
1459
1689
  fs["last_session_id"] = None
1460
1690
  fs["resume_from_phase"] = None
1691
+ reset_continuation_metadata(fs)
1461
1692
  fs["updated_at"] = now_iso()
1462
1693
  save_feature_status(state_dir, fid, fs)
1463
1694
 
@@ -43,6 +43,8 @@ SESSION_STATUS_VALUES = [
43
43
  "crashed",
44
44
  "timed_out",
45
45
  "infra_error",
46
+ "context_overflow",
47
+ "stalled_context_continuation",
46
48
  "commit_missing",
47
49
  "docs_missing",
48
50
  "merge_conflict",
@@ -90,6 +92,12 @@ def parse_args():
90
92
  parser.add_argument("--max-retries", type=int, default=3, help="Maximum code retry count (default: 3)")
91
93
  parser.add_argument("--max-infra-retries", type=int, default=3, help="Maximum infrastructure retry count (default: 3)")
92
94
  parser.add_argument("--project-root", default=None, help="Project root directory. Required for 'clean' action.")
95
+ parser.add_argument("--active-dev-branch", default=None, help="Development branch preserved for continuation after context overflow.")
96
+ parser.add_argument("--base-branch", default=None, help="Original/base branch for continuation branch reuse.")
97
+ parser.add_argument("--last-fatal-error-code", default=None, help="Fatal runtime error code associated with continuation.")
98
+ parser.add_argument("--continuation-summary-path", default=None, help="Optional continuation summary artifact path.")
99
+ parser.add_argument("--no-progress-count", type=int, default=None, help="Consecutive context-overflow sessions without objective progress.")
100
+ parser.add_argument("--progress-fingerprint", default=None, help="JSON progress fingerprint captured after the session.")
93
101
  return parser.parse_args()
94
102
 
95
103
 
@@ -168,6 +176,128 @@ def update_refactor_in_list(refactor_list_path, refactor_id, new_status):
168
176
  return write_json_file(refactor_list_path, data)
169
177
 
170
178
 
179
+ CONTINUATION_PENDING_FIELDS = [
180
+ "active_dev_branch",
181
+ "base_branch",
182
+ "continuation_pending",
183
+ "continuation_reason",
184
+ "last_context_overflow_session_id",
185
+ "previous_session_id",
186
+ "last_fatal_error_code",
187
+ "continuation_summary_path",
188
+ "no_progress_count",
189
+ "last_progress_fingerprint",
190
+ "needs_attention",
191
+ "stalled_context_continuation",
192
+ "stalled_reason",
193
+ ]
194
+
195
+ CONTINUATION_COUNTER_FIELDS = [
196
+ "context_overflow_count",
197
+ "continuation_count",
198
+ ]
199
+
200
+
201
+ def has_pending_continuation_metadata(status_data):
202
+ """Return True when runtime state still has pending continuation metadata."""
203
+ return any(key in status_data for key in CONTINUATION_PENDING_FIELDS)
204
+
205
+
206
+ def clear_pending_continuation_metadata(status_data):
207
+ """Clear branch/pending continuation fields while preserving observability counters."""
208
+ for key in CONTINUATION_PENDING_FIELDS:
209
+ status_data.pop(key, None)
210
+
211
+
212
+ def reset_continuation_metadata(status_data):
213
+ """Clear all continuation runtime metadata, including manual-reset counters."""
214
+ clear_pending_continuation_metadata(status_data)
215
+ for key in CONTINUATION_COUNTER_FIELDS:
216
+ status_data.pop(key, None)
217
+
218
+
219
+ STALL_REASON = "context_overflow_without_git_checkpoint_or_artifact_progress"
220
+
221
+
222
+ def _load_progress_fingerprint(raw):
223
+ if not raw:
224
+ return None
225
+ try:
226
+ return json.loads(raw)
227
+ except (TypeError, ValueError):
228
+ return {"unparsed": str(raw)}
229
+
230
+
231
+ def _set_continuation_common(status_data, args, session_id):
232
+ if args.active_dev_branch:
233
+ status_data["active_dev_branch"] = args.active_dev_branch
234
+ if args.base_branch:
235
+ status_data["base_branch"] = args.base_branch
236
+ status_data["last_context_overflow_session_id"] = session_id
237
+ status_data["previous_session_id"] = session_id
238
+ status_data["last_fatal_error_code"] = args.last_fatal_error_code or "context_overflow"
239
+ if args.continuation_summary_path:
240
+ status_data["continuation_summary_path"] = args.continuation_summary_path
241
+ else:
242
+ status_data.pop("continuation_summary_path", None)
243
+ if args.no_progress_count is not None:
244
+ status_data["no_progress_count"] = max(0, args.no_progress_count)
245
+ fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
246
+ if fingerprint is not None:
247
+ status_data["last_progress_fingerprint"] = fingerprint
248
+ status_data["last_infra_error_session_id"] = None
249
+ status_data["resume_from_phase"] = None
250
+ if session_id:
251
+ status_data["last_session_id"] = session_id
252
+
253
+
254
+ def set_context_overflow_continuation_metadata(status_data, args, session_id):
255
+ """Record continuation metadata for a context-overflow outcome in status.json."""
256
+ status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
257
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
258
+ status_data["continuation_pending"] = True
259
+ status_data["continuation_reason"] = "context_overflow"
260
+ status_data.pop("needs_attention", None)
261
+ status_data.pop("stalled_context_continuation", None)
262
+ status_data.pop("stalled_reason", None)
263
+ _set_continuation_common(status_data, args, session_id)
264
+
265
+
266
+ def set_stalled_context_continuation_metadata(status_data, args, session_id):
267
+ """Record no-progress context-overflow stall without consuming retry budgets."""
268
+ status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
269
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
270
+ _set_continuation_common(status_data, args, session_id)
271
+ status_data["continuation_pending"] = False
272
+ status_data["continuation_reason"] = STALL_REASON
273
+ status_data["needs_attention"] = True
274
+ status_data["stalled_context_continuation"] = True
275
+ status_data["stalled_reason"] = STALL_REASON
276
+ if args.no_progress_count is None or args.no_progress_count < 2:
277
+ status_data["no_progress_count"] = 2
278
+
279
+
280
+ def continuation_metadata_summary(status_data):
281
+ """Return additive continuation fields for CLI JSON outputs."""
282
+ return {
283
+ "active_dev_branch": status_data.get("active_dev_branch"),
284
+ "base_branch": status_data.get("base_branch"),
285
+ "continuation_pending": status_data.get("continuation_pending", False),
286
+ "continuation_reason": status_data.get("continuation_reason"),
287
+ "last_context_overflow_session_id": status_data.get("last_context_overflow_session_id"),
288
+ "last_fatal_error_code": status_data.get("last_fatal_error_code"),
289
+ "previous_session_id": status_data.get("previous_session_id"),
290
+ "context_overflow_count": status_data.get("context_overflow_count", 0),
291
+ "continuation_count": status_data.get("continuation_count", 0),
292
+ "no_progress_count": status_data.get("no_progress_count", 0),
293
+ "last_progress_fingerprint": status_data.get("last_progress_fingerprint"),
294
+ "needs_attention": status_data.get("needs_attention", False),
295
+ "stalled_context_continuation": status_data.get("stalled_context_continuation", False),
296
+ "stalled_reason": status_data.get("stalled_reason"),
297
+ "continuation_summary_path": status_data.get("continuation_summary_path"),
298
+ }
299
+
300
+
171
301
  # ---------------------------------------------------------------------------
172
302
  # Action: get_next
173
303
  # ---------------------------------------------------------------------------
@@ -275,6 +405,7 @@ def action_get_next(refactor_list_data, state_dir):
275
405
  "infra_error_count": chosen_status_data.get("infra_error_count", 0),
276
406
  "resume_from_phase": chosen_status_data.get("resume_from_phase", None),
277
407
  }
408
+ result.update(continuation_metadata_summary(chosen_status_data))
278
409
  print(json.dumps(result, indent=2, ensure_ascii=False))
279
410
 
280
411
 
@@ -298,12 +429,15 @@ def action_update(args, refactor_list_path, state_dir):
298
429
 
299
430
  rs = load_refactor_status(state_dir, refactor_id)
300
431
 
432
+ current_list_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
433
+
301
434
  # Track what status we write to refactor-list.json
302
- new_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
435
+ new_status = current_list_status
303
436
 
304
437
  if session_status == "success":
305
438
  rs["infra_error_count"] = 0
306
439
  rs["last_infra_error_session_id"] = None
440
+ clear_pending_continuation_metadata(rs)
307
441
  new_status = "completed"
308
442
  rs["resume_from_phase"] = None
309
443
  err = update_refactor_in_list(refactor_list_path, refactor_id, "completed")
@@ -335,7 +469,7 @@ def action_update(args, refactor_list_path, state_dir):
335
469
  rs["infra_error_count"] = infra_error_count
336
470
  rs["last_infra_error_session_id"] = session_id
337
471
  rs["max_infra_retries"] = max_infra_retries
338
- rs["degraded_reason"] = "infra_error"
472
+ rs["degraded_reason"] = session_status
339
473
  if session_id:
340
474
  rs["last_session_id"] = session_id
341
475
  rs["resume_from_phase"] = None
@@ -347,6 +481,23 @@ def action_update(args, refactor_list_path, state_dir):
347
481
  else:
348
482
  new_status = "pending"
349
483
 
484
+ err = update_refactor_in_list(refactor_list_path, refactor_id, new_status)
485
+ if err:
486
+ error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
487
+ return
488
+ elif session_status == "context_overflow":
489
+ # Context overflow is continuation state, not code or infra retry state.
490
+ set_context_overflow_continuation_metadata(rs, args, session_id)
491
+ new_status = "in_progress"
492
+ if current_list_status != "in_progress":
493
+ err = update_refactor_in_list(refactor_list_path, refactor_id, "in_progress")
494
+ if err:
495
+ error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
496
+ return
497
+ elif session_status == "stalled_context_continuation":
498
+ # No-progress context-overflow stall pauses automation without consuming retry budgets.
499
+ set_stalled_context_continuation_metadata(rs, args, session_id)
500
+ new_status = "failed"
350
501
  err = update_refactor_in_list(refactor_list_path, refactor_id, new_status)
351
502
  if err:
352
503
  error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
@@ -415,6 +566,14 @@ def action_update(args, refactor_list_path, state_dir):
415
566
  summary["restart_policy"] = "infra_retry"
416
567
  summary["infra_error_count"] = rs.get("infra_error_count", 0)
417
568
  summary["artifacts_preserved"] = True
569
+ elif session_status == "context_overflow":
570
+ summary["restart_policy"] = "context_overflow_continuation"
571
+ summary.update(continuation_metadata_summary(rs))
572
+ summary["artifacts_preserved"] = True
573
+ elif session_status == "stalled_context_continuation":
574
+ summary["restart_policy"] = "needs_attention"
575
+ summary.update(continuation_metadata_summary(rs))
576
+ summary["artifacts_preserved"] = True
418
577
  elif session_status != "success":
419
578
  summary["restart_policy"] = "full_restart"
420
579
  summary["cleanup_performed"] = cleaned
@@ -481,6 +640,45 @@ def cleanup_refactor_artifacts(state_dir, refactor_id, project_root=None):
481
640
  return cleaned
482
641
 
483
642
 
643
+ def _resolve_project_file(project_root, path):
644
+ if not path:
645
+ return None
646
+ if os.path.isabs(path):
647
+ target = os.path.abspath(path)
648
+ else:
649
+ target = os.path.abspath(os.path.join(project_root, path))
650
+ root = os.path.abspath(project_root)
651
+ try:
652
+ rel = os.path.relpath(target, root)
653
+ except ValueError:
654
+ return None
655
+ if rel == os.pardir or rel.startswith(os.pardir + os.sep):
656
+ return None
657
+ return target
658
+
659
+
660
+ def cleanup_continuation_artifacts(project_root, status_data, candidate_paths=None):
661
+ """Delete durable continuation handoff files for a manual clean reset."""
662
+ candidates = []
663
+ if isinstance(status_data, dict):
664
+ candidates.append(status_data.get("continuation_summary_path"))
665
+ candidates.extend(candidate_paths or [])
666
+
667
+ cleaned = []
668
+ seen = set()
669
+ for candidate in candidates:
670
+ target = _resolve_project_file(project_root, candidate)
671
+ if not target or target in seen:
672
+ continue
673
+ seen.add(target)
674
+ if os.path.basename(target) != "continuation-summary.md":
675
+ continue
676
+ if os.path.isfile(target):
677
+ os.remove(target)
678
+ cleaned.append("Deleted continuation artifact {}".format(target))
679
+ return cleaned
680
+
681
+
484
682
  def load_session_status(state_dir, refactor_id, session_id):
485
683
  session_status_path = os.path.join(state_dir, refactor_id, "sessions",
486
684
  session_id, "session-status.json"
@@ -756,6 +954,7 @@ def action_reset(args, refactor_list_path, state_dir):
756
954
  rs["sessions"] = []
757
955
  rs["last_session_id"] = None
758
956
  rs["resume_from_phase"] = None
957
+ reset_continuation_metadata(rs)
759
958
  rs["updated_at"] = now_iso()
760
959
 
761
960
  err = save_refactor_status(state_dir, refactor_id, rs)
@@ -823,12 +1022,19 @@ def action_clean(args, refactor_list_path, state_dir):
823
1022
  old_status = get_refactor_status_from_list(refactor_list_path, refactor_id)
824
1023
  old_retry = rs.get("retry_count", 0)
825
1024
 
1025
+ continuation_candidates = [
1026
+ os.path.join(".prizmkit", "refactor", refactor_id, "continuation-summary.md"),
1027
+ os.path.join(".prizmkit", "specs", refactor_id, "continuation-summary.md"),
1028
+ ]
1029
+ cleaned.extend(cleanup_continuation_artifacts(project_root, rs, continuation_candidates))
1030
+
826
1031
  rs["retry_count"] = 0
827
1032
  rs["infra_error_count"] = 0
828
1033
  rs["last_infra_error_session_id"] = None
829
1034
  rs["sessions"] = []
830
1035
  rs["last_session_id"] = None
831
1036
  rs["resume_from_phase"] = None
1037
+ reset_continuation_metadata(rs)
832
1038
  rs["updated_at"] = now_iso()
833
1039
 
834
1040
  err = save_refactor_status(state_dir, refactor_id, rs)
@@ -904,10 +1110,11 @@ def action_start(args, refactor_list_path, state_dir):
904
1110
  error_out("Failed to save refactor status: {}".format(err))
905
1111
  return
906
1112
 
907
- err = update_refactor_in_list(refactor_list_path, refactor_id, "in_progress")
908
- if err:
909
- error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
910
- return
1113
+ if old_status != "in_progress":
1114
+ err = update_refactor_in_list(refactor_list_path, refactor_id, "in_progress")
1115
+ if err:
1116
+ error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
1117
+ return
911
1118
 
912
1119
  result = {
913
1120
  "action": "start",
@@ -1059,6 +1266,7 @@ def action_unskip(args, refactor_list_path, state_dir):
1059
1266
  rs["sessions"] = []
1060
1267
  rs["last_session_id"] = None
1061
1268
  rs["resume_from_phase"] = None
1269
+ reset_continuation_metadata(rs)
1062
1270
  rs["updated_at"] = now_iso()
1063
1271
  save_refactor_status(state_dir, rid, rs)
1064
1272
 
@@ -35,7 +35,7 @@ Test command:
35
35
  Known baseline failures:
36
36
  `{{BASELINE_FAILURES}}`
37
37
 
38
- If plan.md has more than 5 tasks, run `/compact` after completing every 3 tasks to manage context budget. If `/compact` is unavailable, continue without it.
38
+ If plan.md has more than 5 tasks, update durable checkpoints/artifacts after every 3 tasks, minimize output, and rely on runner continuation if context overflow occurs. In an interactive Claude Code session operated manually outside the headless pipeline, `/compact` may be used as an optional convenience only.
39
39
 
40
40
  ## Required Outputs
41
41