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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/lib/common.sh +244 -25
- package/bundled/dev-pipeline/reset-bug.sh +30 -0
- package/bundled/dev-pipeline/reset-feature.sh +30 -0
- package/bundled/dev-pipeline/reset-refactor.sh +30 -0
- package/bundled/dev-pipeline/run-bugfix.sh +128 -13
- package/bundled/dev-pipeline/run-feature.sh +130 -14
- package/bundled/dev-pipeline/run-refactor.sh +128 -13
- package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -30
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
- package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/conftest.py +1 -0
- package/bundled/dev-pipeline/tests/test_auto_skip.py +510 -0
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
- package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
- package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +93 -9
- package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
- package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
- package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
- package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
- package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
- package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
- package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +27 -30
- package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
- package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
- package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
- package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
- package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
- package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
- package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
- package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
- package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
- package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
- package/bundled/skills/_metadata.json +1 -1
- package/package.json +1 -1
- package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
- 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 =
|
|
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"] =
|
|
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
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
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"] =
|
|
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
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
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
|
|