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
|
@@ -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
|
|
|
@@ -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 =
|
|
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"] =
|
|
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
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
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,
|
|
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
|
|