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