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
@@ -42,6 +42,8 @@ SESSION_STATUS_VALUES = [
42
42
  "crashed",
43
43
  "timed_out",
44
44
  "infra_error",
45
+ "context_overflow",
46
+ "stalled_context_continuation",
45
47
  "commit_missing",
46
48
  "docs_missing",
47
49
  "merge_conflict",
@@ -78,6 +80,12 @@ def parse_args():
78
80
  parser.add_argument("--max-retries", type=int, default=3, help="Maximum code retry count (default: 3)")
79
81
  parser.add_argument("--max-infra-retries", type=int, default=3, help="Maximum infrastructure retry count (default: 3)")
80
82
  parser.add_argument("--project-root", default=None, help="Project root directory. Required for 'clean' action.")
83
+ parser.add_argument("--active-dev-branch", default=None, help="Development branch preserved for continuation after context overflow.")
84
+ parser.add_argument("--base-branch", default=None, help="Original/base branch for continuation branch reuse.")
85
+ parser.add_argument("--last-fatal-error-code", default=None, help="Fatal runtime error code associated with continuation.")
86
+ parser.add_argument("--continuation-summary-path", default=None, help="Optional continuation summary artifact path.")
87
+ parser.add_argument("--no-progress-count", type=int, default=None, help="Consecutive context-overflow sessions without objective progress.")
88
+ parser.add_argument("--progress-fingerprint", default=None, help="JSON progress fingerprint captured after the session.")
81
89
  return parser.parse_args()
82
90
 
83
91
 
@@ -164,6 +172,128 @@ def update_bug_in_list(bug_list_path, bug_id, new_status):
164
172
  return write_json_file(bug_list_path, data)
165
173
 
166
174
 
175
+ CONTINUATION_PENDING_FIELDS = [
176
+ "active_dev_branch",
177
+ "base_branch",
178
+ "continuation_pending",
179
+ "continuation_reason",
180
+ "last_context_overflow_session_id",
181
+ "previous_session_id",
182
+ "last_fatal_error_code",
183
+ "continuation_summary_path",
184
+ "no_progress_count",
185
+ "last_progress_fingerprint",
186
+ "needs_attention",
187
+ "stalled_context_continuation",
188
+ "stalled_reason",
189
+ ]
190
+
191
+ CONTINUATION_COUNTER_FIELDS = [
192
+ "context_overflow_count",
193
+ "continuation_count",
194
+ ]
195
+
196
+
197
+ def has_pending_continuation_metadata(status_data):
198
+ """Return True when runtime state still has pending continuation metadata."""
199
+ return any(key in status_data for key in CONTINUATION_PENDING_FIELDS)
200
+
201
+
202
+ def clear_pending_continuation_metadata(status_data):
203
+ """Clear branch/pending continuation fields while preserving observability counters."""
204
+ for key in CONTINUATION_PENDING_FIELDS:
205
+ status_data.pop(key, None)
206
+
207
+
208
+ def reset_continuation_metadata(status_data):
209
+ """Clear all continuation runtime metadata, including manual-reset counters."""
210
+ clear_pending_continuation_metadata(status_data)
211
+ for key in CONTINUATION_COUNTER_FIELDS:
212
+ status_data.pop(key, None)
213
+
214
+
215
+ STALL_REASON = "context_overflow_without_git_checkpoint_or_artifact_progress"
216
+
217
+
218
+ def _load_progress_fingerprint(raw):
219
+ if not raw:
220
+ return None
221
+ try:
222
+ return json.loads(raw)
223
+ except (TypeError, ValueError):
224
+ return {"unparsed": str(raw)}
225
+
226
+
227
+ def _set_continuation_common(status_data, args, session_id):
228
+ if args.active_dev_branch:
229
+ status_data["active_dev_branch"] = args.active_dev_branch
230
+ if args.base_branch:
231
+ status_data["base_branch"] = args.base_branch
232
+ status_data["last_context_overflow_session_id"] = session_id
233
+ status_data["previous_session_id"] = session_id
234
+ status_data["last_fatal_error_code"] = args.last_fatal_error_code or "context_overflow"
235
+ if args.continuation_summary_path:
236
+ status_data["continuation_summary_path"] = args.continuation_summary_path
237
+ else:
238
+ status_data.pop("continuation_summary_path", None)
239
+ if args.no_progress_count is not None:
240
+ status_data["no_progress_count"] = max(0, args.no_progress_count)
241
+ fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
242
+ if fingerprint is not None:
243
+ status_data["last_progress_fingerprint"] = fingerprint
244
+ status_data["last_infra_error_session_id"] = None
245
+ status_data["resume_from_phase"] = None
246
+ if session_id:
247
+ status_data["last_session_id"] = session_id
248
+
249
+
250
+ def set_context_overflow_continuation_metadata(status_data, args, session_id):
251
+ """Record continuation metadata for a context-overflow outcome in status.json."""
252
+ status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
253
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
254
+ status_data["continuation_pending"] = True
255
+ status_data["continuation_reason"] = "context_overflow"
256
+ status_data.pop("needs_attention", None)
257
+ status_data.pop("stalled_context_continuation", None)
258
+ status_data.pop("stalled_reason", None)
259
+ _set_continuation_common(status_data, args, session_id)
260
+
261
+
262
+ def set_stalled_context_continuation_metadata(status_data, args, session_id):
263
+ """Record no-progress context-overflow stall without consuming retry budgets."""
264
+ status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
265
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
266
+ _set_continuation_common(status_data, args, session_id)
267
+ status_data["continuation_pending"] = False
268
+ status_data["continuation_reason"] = STALL_REASON
269
+ status_data["needs_attention"] = True
270
+ status_data["stalled_context_continuation"] = True
271
+ status_data["stalled_reason"] = STALL_REASON
272
+ if args.no_progress_count is None or args.no_progress_count < 2:
273
+ status_data["no_progress_count"] = 2
274
+
275
+
276
+ def continuation_metadata_summary(status_data):
277
+ """Return additive continuation fields for CLI JSON outputs."""
278
+ return {
279
+ "active_dev_branch": status_data.get("active_dev_branch"),
280
+ "base_branch": status_data.get("base_branch"),
281
+ "continuation_pending": status_data.get("continuation_pending", False),
282
+ "continuation_reason": status_data.get("continuation_reason"),
283
+ "last_context_overflow_session_id": status_data.get("last_context_overflow_session_id"),
284
+ "last_fatal_error_code": status_data.get("last_fatal_error_code"),
285
+ "previous_session_id": status_data.get("previous_session_id"),
286
+ "context_overflow_count": status_data.get("context_overflow_count", 0),
287
+ "continuation_count": status_data.get("continuation_count", 0),
288
+ "no_progress_count": status_data.get("no_progress_count", 0),
289
+ "last_progress_fingerprint": status_data.get("last_progress_fingerprint"),
290
+ "needs_attention": status_data.get("needs_attention", False),
291
+ "stalled_context_continuation": status_data.get("stalled_context_continuation", False),
292
+ "stalled_reason": status_data.get("stalled_reason"),
293
+ "continuation_summary_path": status_data.get("continuation_summary_path"),
294
+ }
295
+
296
+
167
297
  # ---------------------------------------------------------------------------
168
298
  # Action: get_next
169
299
  # ---------------------------------------------------------------------------
@@ -244,6 +374,7 @@ def action_get_next(bug_list_data, state_dir):
244
374
  "infra_error_count": chosen_status_data.get("infra_error_count", 0),
245
375
  "resume_from_phase": chosen_status_data.get("resume_from_phase", None),
246
376
  }
377
+ result.update(continuation_metadata_summary(chosen_status_data))
247
378
  print(json.dumps(result, indent=2, ensure_ascii=False))
248
379
 
249
380
 
@@ -267,12 +398,15 @@ def action_update(args, bug_list_path, state_dir):
267
398
 
268
399
  bs = load_bug_status(state_dir, bug_id)
269
400
 
401
+ current_list_status = get_bug_status_from_list(bug_list_path, bug_id)
402
+
270
403
  # Track what status we write to bug-fix-list.json
271
- new_status = get_bug_status_from_list(bug_list_path, bug_id)
404
+ new_status = current_list_status
272
405
 
273
406
  if session_status == "success":
274
407
  bs["infra_error_count"] = 0
275
408
  bs["last_infra_error_session_id"] = None
409
+ clear_pending_continuation_metadata(bs)
276
410
  new_status = "completed"
277
411
  bs["resume_from_phase"] = None
278
412
  err = update_bug_in_list(bug_list_path, bug_id, "completed")
@@ -304,7 +438,7 @@ def action_update(args, bug_list_path, state_dir):
304
438
  bs["infra_error_count"] = infra_error_count
305
439
  bs["last_infra_error_session_id"] = session_id
306
440
  bs["max_infra_retries"] = max_infra_retries
307
- bs["degraded_reason"] = "infra_error"
441
+ bs["degraded_reason"] = session_status
308
442
  if session_id:
309
443
  bs["last_session_id"] = session_id
310
444
  bs["resume_from_phase"] = None
@@ -316,6 +450,23 @@ def action_update(args, bug_list_path, state_dir):
316
450
  else:
317
451
  new_status = "pending"
318
452
 
453
+ err = update_bug_in_list(bug_list_path, bug_id, new_status)
454
+ if err:
455
+ error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
456
+ return
457
+ elif session_status == "context_overflow":
458
+ # Context overflow is continuation state, not code or infra retry state.
459
+ set_context_overflow_continuation_metadata(bs, args, session_id)
460
+ new_status = "in_progress"
461
+ if current_list_status != "in_progress":
462
+ err = update_bug_in_list(bug_list_path, bug_id, "in_progress")
463
+ if err:
464
+ error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
465
+ return
466
+ elif session_status == "stalled_context_continuation":
467
+ # No-progress context-overflow stall pauses automation without consuming retry budgets.
468
+ set_stalled_context_continuation_metadata(bs, args, session_id)
469
+ new_status = "needs_info"
319
470
  err = update_bug_in_list(bug_list_path, bug_id, new_status)
320
471
  if err:
321
472
  error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
@@ -375,6 +526,14 @@ def action_update(args, bug_list_path, state_dir):
375
526
  summary["restart_policy"] = "infra_retry"
376
527
  summary["infra_error_count"] = bs.get("infra_error_count", 0)
377
528
  summary["artifacts_preserved"] = True
529
+ elif session_status == "context_overflow":
530
+ summary["restart_policy"] = "context_overflow_continuation"
531
+ summary.update(continuation_metadata_summary(bs))
532
+ summary["artifacts_preserved"] = True
533
+ elif session_status == "stalled_context_continuation":
534
+ summary["restart_policy"] = "needs_attention"
535
+ summary.update(continuation_metadata_summary(bs))
536
+ summary["artifacts_preserved"] = True
378
537
  elif session_status != "success":
379
538
  summary["restart_policy"] = "full_restart"
380
539
  summary["cleanup_performed"] = cleaned
@@ -445,6 +604,45 @@ def cleanup_bug_artifacts(state_dir, bug_id, project_root=None):
445
604
  return cleaned
446
605
 
447
606
 
607
+ def _resolve_project_file(project_root, path):
608
+ if not path:
609
+ return None
610
+ if os.path.isabs(path):
611
+ target = os.path.abspath(path)
612
+ else:
613
+ target = os.path.abspath(os.path.join(project_root, path))
614
+ root = os.path.abspath(project_root)
615
+ try:
616
+ rel = os.path.relpath(target, root)
617
+ except ValueError:
618
+ return None
619
+ if rel == os.pardir or rel.startswith(os.pardir + os.sep):
620
+ return None
621
+ return target
622
+
623
+
624
+ def cleanup_continuation_artifacts(project_root, status_data, candidate_paths=None):
625
+ """Delete durable continuation handoff files for a manual clean reset."""
626
+ candidates = []
627
+ if isinstance(status_data, dict):
628
+ candidates.append(status_data.get("continuation_summary_path"))
629
+ candidates.extend(candidate_paths or [])
630
+
631
+ cleaned = []
632
+ seen = set()
633
+ for candidate in candidates:
634
+ target = _resolve_project_file(project_root, candidate)
635
+ if not target or target in seen:
636
+ continue
637
+ seen.add(target)
638
+ if os.path.basename(target) != "continuation-summary.md":
639
+ continue
640
+ if os.path.isfile(target):
641
+ os.remove(target)
642
+ cleaned.append("Deleted continuation artifact {}".format(target))
643
+ return cleaned
644
+
645
+
448
646
  def load_session_status(state_dir, bug_id, session_id):
449
647
  session_status_path = os.path.join(state_dir, bug_id, "sessions",
450
648
  session_id, "session-status.json"
@@ -608,6 +806,7 @@ def action_reset(args, bug_list_path, state_dir):
608
806
  bs["sessions"] = []
609
807
  bs["last_session_id"] = None
610
808
  bs["resume_from_phase"] = None
809
+ reset_continuation_metadata(bs)
611
810
  bs["updated_at"] = now_iso()
612
811
 
613
812
  err = save_bug_status(state_dir, bug_id, bs)
@@ -679,12 +878,19 @@ def action_clean(args, bug_list_path, state_dir):
679
878
  old_status = get_bug_status_from_list(bug_list_path, bug_id)
680
879
  old_retry = bs.get("retry_count", 0)
681
880
 
881
+ continuation_candidates = [
882
+ os.path.join(".prizmkit", "bugfix", bug_id, "continuation-summary.md"),
883
+ os.path.join(".prizmkit", "specs", bug_id, "continuation-summary.md"),
884
+ ]
885
+ cleaned.extend(cleanup_continuation_artifacts(project_root, bs, continuation_candidates))
886
+
682
887
  bs["retry_count"] = 0
683
888
  bs["infra_error_count"] = 0
684
889
  bs["last_infra_error_session_id"] = None
685
890
  bs["sessions"] = []
686
891
  bs["last_session_id"] = None
687
892
  bs["resume_from_phase"] = None
893
+ reset_continuation_metadata(bs)
688
894
  bs["updated_at"] = now_iso()
689
895
 
690
896
  err = save_bug_status(state_dir, bug_id, bs)
@@ -760,10 +966,11 @@ def action_start(args, bug_list_path, state_dir):
760
966
  error_out("Failed to save bug status: {}".format(err))
761
967
  return
762
968
 
763
- err = update_bug_in_list(bug_list_path, bug_id, "in_progress")
764
- if err:
765
- error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
766
- return
969
+ if old_status != "in_progress":
970
+ err = update_bug_in_list(bug_list_path, bug_id, "in_progress")
971
+ if err:
972
+ error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
973
+ return
767
974
 
768
975
  result = {
769
976
  "action": "start",
@@ -853,6 +1060,7 @@ def action_unskip(args, bug_list_path, state_dir):
853
1060
  bs["sessions"] = []
854
1061
  bs["last_session_id"] = None
855
1062
  bs["resume_from_phase"] = None
1063
+ reset_continuation_metadata(bs)
856
1064
  bs["updated_at"] = now_iso()
857
1065
  save_bug_status(state_dir, bid, bs)
858
1066
 
@@ -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