prizmkit 1.1.120 → 1.1.121

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +23 -25
  3. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +8 -2
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +10 -1
  5. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +43 -29
  6. package/bundled/dev-pipeline/scripts/continuation.py +51 -6
  7. package/bundled/dev-pipeline/scripts/update-bug-status.py +57 -14
  8. package/bundled/dev-pipeline/scripts/update-feature-status.py +54 -5
  9. package/bundled/dev-pipeline/scripts/update-refactor-status.py +57 -14
  10. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +2 -2
  11. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -1
  12. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
  13. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  14. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +3 -3
  15. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
  16. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  17. package/bundled/dev-pipeline/tests/test_auto_skip.py +5 -5
  18. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +10 -3
  19. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +9 -2
  20. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +9 -1
  21. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +222 -18
  22. package/bundled/dev-pipeline/tests/test_unified_cli.py +14 -10
  23. package/bundled/skills/_metadata.json +4 -4
  24. package/bundled/skills/prizmkit-code-review/SKILL.md +49 -48
  25. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +13 -8
  26. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +46 -32
  27. package/bundled/skills/prizmkit-code-review/references/reviewer-workspace-protocol.md +90 -0
  28. package/bundled/skills/prizmkit-test/SKILL.md +159 -155
  29. package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +420 -0
  30. package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +116 -0
  31. package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +112 -0
  32. package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +97 -0
  33. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +76 -192
  34. package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +65 -0
  35. package/bundled/skills/prizmkit-test/references/evidence-protocol.md +194 -0
  36. package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +78 -0
  37. package/bundled/skills/prizmkit-test/references/examples.md +65 -108
  38. package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +10 -7
  39. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +90 -82
  40. package/bundled/skills/prizmkit-test/references/test-report-template.md +77 -98
  41. package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +97 -0
  42. package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +723 -0
  43. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +1426 -0
  44. package/package.json +1 -1
  45. package/bundled/skills/prizmkit-test/scripts/validate_boundary_report.py +0 -347
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.120",
3
- "bundledAt": "2026-07-10T01:27:14.094Z",
4
- "bundledFrom": "3d5e4ff"
2
+ "frameworkVersion": "1.1.121",
3
+ "bundledAt": "2026-07-11T06:00:42.680Z",
4
+ "bundledFrom": "c2f98e2"
5
5
  }
@@ -3,11 +3,12 @@
3
3
  from __future__ import annotations
4
4
 
5
5
  import hashlib
6
+ import json
6
7
  import subprocess
7
8
  from pathlib import Path
8
9
 
9
10
  from .checkpoint_state import load_checkpoint_state
10
- from .gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES, git_status_safe, run_git_command
11
+ from .gitops import git_status_safe, run_git_command
11
12
  from .runner_models import PromptGenerationResult, SessionClassification
12
13
  from .sessions import AISessionResult
13
14
 
@@ -54,8 +55,6 @@ def classify_session(
54
55
  reason=f"invalid_workflow_checkpoint:{invalid_checkpoint.error_code}",
55
56
  progress_fingerprint=fingerprint,
56
57
  )
57
- has_commit_since_base = _has_commit_since(project_root, base_head)
58
-
59
58
  if _is_terminal_success(result) and _has_completed_workflow_artifact(prompt):
60
59
  return SessionClassification(
61
60
  "success",
@@ -63,7 +62,7 @@ def classify_session(
63
62
  progress_fingerprint=fingerprint,
64
63
  )
65
64
 
66
- if _has_late_runtime_error_after_completion(result, prompt, has_commit_since_base):
65
+ if _has_late_runtime_error_after_completion(result, prompt):
67
66
  return SessionClassification(
68
67
  "success",
69
68
  reason="completed_checkpoint_with_late_runtime_error",
@@ -71,8 +70,6 @@ def classify_session(
71
70
  )
72
71
 
73
72
  if _is_context_overflow(result):
74
- if has_commit_since_base:
75
- return SessionClassification("success", reason="commit_since_base", progress_fingerprint=fingerprint)
76
73
  no_progress_count = _next_no_progress_count(previous_fingerprint, fingerprint, previous_no_progress_count)
77
74
  if no_progress_count >= 2:
78
75
  return SessionClassification(
@@ -90,19 +87,14 @@ def classify_session(
90
87
  no_progress_count=no_progress_count,
91
88
  )
92
89
 
93
- if _is_infra_error(result):
94
- return SessionClassification("infra_error", reason="ai_runtime_or_provider_error", progress_fingerprint=fingerprint)
95
90
  if result.termination_reason == "stale_session":
96
91
  return SessionClassification("crashed", reason="stale_session", progress_fingerprint=fingerprint)
97
92
  if result.timed_out or result.termination_reason == "timeout":
98
93
  return SessionClassification("timed_out", reason="timeout", progress_fingerprint=fingerprint)
94
+ if _is_infra_error(result):
95
+ return SessionClassification("infra_error", reason="ai_runtime_or_provider_error", progress_fingerprint=fingerprint)
99
96
  if result.exit_code not in (0, None):
100
97
  return SessionClassification("crashed", reason=f"exit_code={result.exit_code}", progress_fingerprint=fingerprint)
101
- if has_commit_since_base:
102
- return SessionClassification("success", reason="commit_since_base", progress_fingerprint=fingerprint)
103
- if _has_workspace_changes(project_root):
104
- _auto_commit_workspace(project_root)
105
- return SessionClassification("success", reason="auto_commit_uncommitted_changes", progress_fingerprint=fingerprint)
106
98
  return SessionClassification("crashed", reason="no_durable_success", progress_fingerprint=fingerprint)
107
99
 
108
100
 
@@ -138,9 +130,8 @@ def _is_terminal_success(result: AISessionResult) -> bool:
138
130
  def _has_late_runtime_error_after_completion(
139
131
  result: AISessionResult,
140
132
  prompt: PromptGenerationResult | None,
141
- has_commit_since_base: bool,
142
133
  ) -> bool:
143
- if not has_commit_since_base or not _has_completed_workflow_artifact(prompt):
134
+ if not _has_completed_workflow_artifact(prompt):
144
135
  return False
145
136
  return _is_context_overflow(result) or _is_infra_error(result)
146
137
 
@@ -148,6 +139,8 @@ def _has_late_runtime_error_after_completion(
148
139
  def _has_completed_workflow_artifact(prompt: PromptGenerationResult | None) -> bool:
149
140
  if prompt is None:
150
141
  return False
142
+ if _invalid_existing_workflow_checkpoint(prompt) is not None:
143
+ return False
151
144
  if prompt.checkpoint_path and prompt.checkpoint_path.is_file():
152
145
  state = load_checkpoint_state(prompt.checkpoint_path)
153
146
  return state.valid and state.all_completed
@@ -170,7 +163,21 @@ def has_completed_artifact_path(artifact_path: Path | None) -> bool:
170
163
  if checkpoint.is_file():
171
164
  state = load_checkpoint_state(checkpoint)
172
165
  return state.valid and state.all_completed
173
- return (artifact_path / "completion-summary.json").is_file()
166
+ return _has_valid_completion_summary(artifact_path / "completion-summary.json")
167
+
168
+
169
+ def _has_valid_completion_summary(path: Path) -> bool:
170
+ """Return whether a completion summary contains usable durable notes."""
171
+ try:
172
+ data = json.loads(path.read_text(encoding="utf-8"))
173
+ except (OSError, UnicodeDecodeError, json.JSONDecodeError):
174
+ return False
175
+ if not isinstance(data, dict):
176
+ return False
177
+ notes = data.get("completion_notes")
178
+ return isinstance(notes, list) and bool(notes) and all(
179
+ isinstance(note, str) and note.strip() for note in notes
180
+ )
174
181
 
175
182
 
176
183
  def _invalid_existing_workflow_checkpoint(prompt: PromptGenerationResult | None):
@@ -226,15 +233,6 @@ def _has_commit_since(project_root: Path, base_head: str) -> bool:
226
233
  return _has_commit_range(project_root, base_head, "HEAD")
227
234
 
228
235
 
229
- def _has_workspace_changes(project_root: Path) -> bool:
230
- return bool(git_status_safe(project_root).strip())
231
-
232
-
233
- def _auto_commit_workspace(project_root: Path) -> None:
234
- run_git_command(project_root, ("add", "-A", "--", ".", *HIDDEN_TOOL_WORKTREE_EXCLUDES))
235
- run_git_command(project_root, ("commit", "--no-verify", "-m", "chore: save runner session changes"))
236
-
237
-
238
236
  def _tail(path: Path, max_bytes: int = 8192) -> str:
239
237
  try:
240
238
  data = path.read_bytes()
@@ -133,17 +133,23 @@ class SessionPaths:
133
133
 
134
134
  @dataclass(frozen=True)
135
135
  class ContinuationState:
136
- """Context-overflow continuation inputs for the next prompt/status update."""
136
+ """Provider-neutral durable handoff inputs for the next AI session."""
137
137
 
138
138
  active: bool = False
139
139
  previous_session_id: str = ""
140
+ failure_classification: str = ""
140
141
  continuation_count: int = 0
141
142
  context_overflow_count: int = 0
143
+ retry_count: int = 0
144
+ infra_error_count: int = 0
145
+ max_retries: int = 0
146
+ max_infra_retries: int = 0
142
147
  no_progress_count: int = 0
148
+ progress_fingerprint: Mapping[str, object] = field(default_factory=dict)
143
149
  summary_path: Path | None = None
144
150
  active_dev_branch: str = ""
145
151
  base_branch: str = ""
146
- fatal_error_code: str = "context_overflow"
152
+ fatal_error_code: str = ""
147
153
 
148
154
 
149
155
  @dataclass(frozen=True)
@@ -50,11 +50,18 @@ def generate_prompt(
50
50
  if invocation.mode:
51
51
  command.extend(["--mode", invocation.mode])
52
52
  if continuation:
53
- command.extend(["--continuation-mode", "context_overflow"])
53
+ command.extend(["--continuation-mode", "ai_error"])
54
54
  for flag, key in (
55
55
  ("--previous-session-id", "previous_session_id"),
56
+ ("--failure-classification", "failure_classification"),
56
57
  ("--continuation-count", "continuation_count"),
57
58
  ("--context-overflow-count", "context_overflow_count"),
59
+ ("--code-retry-count", "retry_count"),
60
+ ("--infra-error-count", "infra_error_count"),
61
+ ("--max-code-retries", "max_retries"),
62
+ ("--max-infra-retries", "max_infra_retries"),
63
+ ("--no-progress-count", "no_progress_count"),
64
+ ("--progress-fingerprint", "progress_fingerprint"),
58
65
  ("--task-type", "task_type"),
59
66
  ("--active-dev-branch", "active_dev_branch"),
60
67
  ("--base-branch", "base_branch"),
@@ -62,6 +69,8 @@ def generate_prompt(
62
69
  ):
63
70
  value = continuation.get(key)
64
71
  if value not in (None, ""):
72
+ if key == "progress_fingerprint":
73
+ value = json.dumps(value, sort_keys=True)
65
74
  command.extend([flag, str(value)])
66
75
 
67
76
  completed = subprocess.run(
@@ -531,8 +531,11 @@ def _process_item(
531
531
  previous_no_progress_count = int(metadata.get("no_progress_count") or 0)
532
532
  continuation_pending = bool(metadata.get("continuation_pending"))
533
533
  previous_session_id = str(metadata.get("previous_session_id") or metadata.get("last_context_overflow_session_id") or "")
534
+ failure_classification = str(metadata.get("continuation_reason") or "context_overflow")
534
535
  continuation_count = int(metadata.get("continuation_count") or 0)
535
536
  context_overflow_count = int(metadata.get("context_overflow_count") or 0)
537
+ retry_count = int(metadata.get("retry_count") or 0)
538
+ infra_error_count = int(metadata.get("infra_error_count") or 0)
536
539
 
537
540
  while True:
538
541
  session_id = _session_id(item_id)
@@ -541,8 +544,15 @@ def _process_item(
541
544
  if continuation_pending:
542
545
  continuation = {
543
546
  "previous_session_id": previous_session_id,
547
+ "failure_classification": failure_classification,
544
548
  "continuation_count": continuation_count,
545
549
  "context_overflow_count": context_overflow_count,
550
+ "retry_count": retry_count,
551
+ "infra_error_count": infra_error_count,
552
+ "max_retries": _max_retries(invocation),
553
+ "max_infra_retries": invocation.max_infra_retries,
554
+ "no_progress_count": previous_no_progress_count,
555
+ "progress_fingerprint": previous_fingerprint or {},
546
556
  "task_type": family.task_type,
547
557
  "active_dev_branch": branch_name,
548
558
  "base_branch": base_branch,
@@ -554,7 +564,7 @@ def _process_item(
554
564
  item_id=item_id,
555
565
  session_id=session_id,
556
566
  run_id="run-python",
557
- retry_count=0,
567
+ retry_count=retry_count,
558
568
  session_paths=session_paths,
559
569
  project_root=paths.project_root,
560
570
  execution_root=execution_root,
@@ -599,34 +609,8 @@ def _process_item(
599
609
  previous_no_progress_count=previous_no_progress_count,
600
610
  )
601
611
  _emit_session_summary(session_result, session_paths, classification)
602
- if classification.session_status == "context_overflow":
603
- summary_path = _continuation_summary_path(execution_root, family, item_id, prompt)
604
- update = update_item(
605
- status_family,
606
- status_invocation,
607
- item_id,
608
- "context_overflow",
609
- session_id,
610
- status_root,
611
- active_dev_branch=branch_name,
612
- base_branch=base_branch,
613
- last_fatal_error_code=classification.fatal_error_code or "context_overflow",
614
- continuation_summary_path=summary_path,
615
- no_progress_count=classification.no_progress_count,
616
- progress_fingerprint=classification.progress_fingerprint,
617
- )
618
- if not update.ok:
619
- raise RuntimeError(update.text)
620
- _emit_update_summary(family, item_id, update)
621
- previous_fingerprint = classification.progress_fingerprint
622
- previous_no_progress_count = classification.no_progress_count
623
- continuation_pending = True
624
- previous_session_id = session_id
625
- continuation_count += 1
626
- context_overflow_count += 1
627
- continue
628
-
629
612
  final_session_status = classification.session_status
613
+ summary_path = _continuation_summary_path(execution_root, family, item_id, prompt)
630
614
 
631
615
  _write_session_status(
632
616
  session_paths,
@@ -651,6 +635,7 @@ def _process_item(
651
635
  active_dev_branch=branch_name,
652
636
  base_branch=base_branch,
653
637
  last_fatal_error_code=classification.fatal_error_code,
638
+ continuation_summary_path=summary_path,
654
639
  no_progress_count=classification.no_progress_count,
655
640
  progress_fingerprint=classification.progress_fingerprint,
656
641
  )
@@ -658,6 +643,22 @@ def _process_item(
658
643
  raise RuntimeError(update.text)
659
644
  _emit_update_summary(family, item_id, update)
660
645
  new_status = _status_from_update(update)
646
+ update_data = update.data if isinstance(update.data, dict) else {}
647
+ retry_count = int(update_data.get("retry_count", retry_count) or 0)
648
+ infra_error_count = int(update_data.get("infra_error_count", infra_error_count) or 0)
649
+
650
+ if _is_retryable_ai_outcome(final_session_status, new_status):
651
+ previous_fingerprint = classification.progress_fingerprint
652
+ previous_no_progress_count = classification.no_progress_count
653
+ continuation_pending = True
654
+ previous_session_id = session_id
655
+ failure_classification = classification.reason or final_session_status
656
+ continuation_count = int(update_data.get("continuation_count", continuation_count + 1) or 0)
657
+ context_overflow_count = int(update_data.get("context_overflow_count", context_overflow_count) or 0)
658
+ _emit_info(f"Retryable AI outcome persisted ({failure_classification}); continuing in 5s...")
659
+ time.sleep(5)
660
+ continue
661
+
661
662
  if final_session_status != "success":
662
663
  commit_final_bookkeeping(status_root, status_family, item_id, new_status)
663
664
  _save_failure_wip(execution_root, family, item_id, new_status, worktree_runtime)
@@ -694,6 +695,19 @@ def _process_item(
694
695
  raise
695
696
 
696
697
 
698
+ def _is_retryable_ai_outcome(session_status: str, new_status: str) -> bool:
699
+ """Return whether a persisted post-launch outcome should continue in-process."""
700
+ if session_status == "success" or session_status == "stalled_context_continuation":
701
+ return False
702
+ return new_status in {"in_progress", "pending"} and session_status in {
703
+ "context_overflow",
704
+ "infra_error",
705
+ "crashed",
706
+ "timed_out",
707
+ "failed",
708
+ }
709
+
710
+
697
711
  def _finalize_recovered_success(
698
712
  family: RunnerFamily,
699
713
  item_id: str,
@@ -1012,7 +1026,7 @@ def _default_branch_name(family: RunnerFamily, item_id: str) -> str:
1012
1026
 
1013
1027
 
1014
1028
  def _session_id(item_id: str) -> str:
1015
- return f"{item_id}-{time.strftime('%Y%m%d%H%M%S')}"
1029
+ return f"{item_id}-{time.strftime('%Y%m%d%H%M%S')}-{time.time_ns()}"
1016
1030
 
1017
1031
 
1018
1032
  def _continuation_summary_path(project_root: Path, family: RunnerFamily, item_id: str, prompt: PromptGenerationResult | None) -> Path:
@@ -1,5 +1,6 @@
1
1
  """Continuation prompt and durable handoff helpers for pipeline prompt generators."""
2
2
 
3
+ import json
3
4
  import os
4
5
  import sys
5
6
 
@@ -9,7 +10,7 @@ if _PIPELINE_ROOT not in sys.path:
9
10
 
10
11
  from prizmkit_runtime.checkpoint_state import checkpoint_cursor as shared_checkpoint_cursor # noqa: E402
11
12
 
12
- SUPPORTED_CONTINUATION_MODES = {"context_overflow"}
13
+ SUPPORTED_CONTINUATION_MODES = {"context_overflow", "ai_error"}
13
14
  TASK_TYPES = {"feature", "bugfix", "refactor"}
14
15
 
15
16
 
@@ -19,13 +20,18 @@ def add_continuation_args(parser, default_task_type):
19
20
  "--continuation-mode",
20
21
  choices=sorted(SUPPORTED_CONTINUATION_MODES),
21
22
  default="",
22
- help="Continuation recovery mode, currently context_overflow.",
23
+ help="Provider-neutral in-process AI error continuation mode.",
23
24
  )
24
25
  parser.add_argument(
25
26
  "--previous-session-id",
26
27
  default="",
27
28
  help="Session ID that triggered this continuation.",
28
29
  )
30
+ parser.add_argument(
31
+ "--failure-classification",
32
+ default="context_overflow",
33
+ help="Classified outcome from the previous launched AI session.",
34
+ )
29
35
  parser.add_argument(
30
36
  "--continuation-count",
31
37
  default="0",
@@ -36,6 +42,12 @@ def add_continuation_args(parser, default_task_type):
36
42
  default="0",
37
43
  help="Cumulative context overflow count for this task.",
38
44
  )
45
+ parser.add_argument("--code-retry-count", default="0", help="Persisted code retry count.")
46
+ parser.add_argument("--infra-error-count", default="0", help="Persisted infrastructure retry count.")
47
+ parser.add_argument("--max-code-retries", default="0", help="Configured code retry budget.")
48
+ parser.add_argument("--max-infra-retries", default="0", help="Configured infrastructure retry budget.")
49
+ parser.add_argument("--no-progress-count", default="0", help="Consecutive context continuations without durable progress.")
50
+ parser.add_argument("--progress-fingerprint", default="", help="JSON durable Git/checkpoint/artifact progress summary.")
39
51
  parser.add_argument(
40
52
  "--task-type",
41
53
  choices=sorted(TASK_TYPES),
@@ -72,6 +84,16 @@ def _safe_int(value, default=0):
72
84
  return parsed if parsed >= 0 else default
73
85
 
74
86
 
87
+ def _safe_json_object(value):
88
+ if isinstance(value, dict):
89
+ return value
90
+ try:
91
+ parsed = json.loads(str(value or ""))
92
+ except (TypeError, ValueError, json.JSONDecodeError):
93
+ return {}
94
+ return parsed if isinstance(parsed, dict) else {}
95
+
96
+
75
97
  def _norm(path):
76
98
  return str(path).replace(os.sep, "/")
77
99
 
@@ -193,7 +215,7 @@ def _build_summary(context):
193
215
  "## Reason",
194
216
  "",
195
217
  "- Continuation mode: {}".format(context["mode"]),
196
- "- This continuation was started after context overflow.",
218
+ "- Previous AI session classification: {}".format(context["failure_classification"]),
197
219
  "- This artifact is the durable handoff summary; do not rely on the previous full transcript.",
198
220
  "",
199
221
  "## Previous Session",
@@ -211,6 +233,17 @@ def _build_summary(context):
211
233
  "- Base branch: {}".format(context["base_branch"] or "(unknown)"),
212
234
  "- Continuation count: {}".format(context["continuation_count"]),
213
235
  "- Context overflow count: {}".format(context["context_overflow_count"]),
236
+ "- Code retry count: {} / {}".format(context["code_retry_count"], context["max_code_retries"]),
237
+ "- Infrastructure retry count: {} / {}".format(context["infra_error_count"], context["max_infra_retries"]),
238
+ "- Consecutive no-progress context count: {}".format(context["no_progress_count"]),
239
+ "",
240
+ "## Git and Checkpoint Progress",
241
+ "",
242
+ "- Current checkpoint phase: {}".format(cursor["first_cursor"]),
243
+ "- Git status fingerprint: {}".format(context["progress_fingerprint"].get("git_diff_fingerprint", "(unknown)")),
244
+ "- HEAD commit: {}".format(context["progress_fingerprint"].get("head_commit", "(unknown)")),
245
+ "- Artifact fingerprint: {}".format(context["progress_fingerprint"].get("artifact_fingerprint", "(unknown)")),
246
+ "- Checkpoint cursor fingerprint: {}".format(context["progress_fingerprint"].get("checkpoint_cursor", "(unknown)")),
214
247
  "",
215
248
  "## Observed Partial Progress",
216
249
  "",
@@ -247,16 +280,18 @@ def _build_addendum(context):
247
280
  runtime = context["runtime"]
248
281
  summary_paths = context["summary_paths"]
249
282
  lines = [
250
- "## Continuation Addendum — Context Overflow Recovery",
283
+ "## Continuation Addendum — In-Process AI Error Recovery",
251
284
  "",
252
- "This is an automatic continuation after context overflow.",
285
+ "This is an automatic continuation after the previous launched AI session ended with `{}`.".format(context["failure_classification"]),
253
286
  "This is a fresh headless AI CLI session for the same task.",
254
287
  "Continue on the current branch/worktree.",
255
288
  "Do not reset, clean, switch branches, or discard work.",
256
289
  "",
257
290
  "### Required First Actions",
258
291
  "",
259
- "- Start with `git status` and `git diff`.",
292
+ "- Inspect `git status` and `git diff` before editing.",
293
+ "- Validate the workflow checkpoint and current phase before resuming.",
294
+ "- Inspect existing task artifacts and run only the tests necessary to establish current test state.",
260
295
  "- Reuse the same durable task artifact directory: `{}`.".format(_norm(durable["artifact_dir"])),
261
296
  "- Treat completed/skipped checkpoint steps as already done.",
262
297
  "- Continue from the first pending/in-progress checkpoint step: `{}`.".format(cursor["first_cursor"]),
@@ -276,7 +311,10 @@ def _build_addendum(context):
276
311
  "- Failure log path: `{}`".format(_norm(durable["failure_log"])),
277
312
  "- Continuation summary path(s): `{}`".format(", ".join(_norm(p) for p in summary_paths)),
278
313
  "- Continuation count: `{}`".format(context["continuation_count"]),
314
+ "- Previous failure classification: `{}`".format(context["failure_classification"]),
279
315
  "- Context overflow count: `{}`".format(context["context_overflow_count"]),
316
+ "- Code retry count: `{}` / `{}`".format(context["code_retry_count"], context["max_code_retries"]),
317
+ "- Infrastructure retry count: `{}` / `{}`".format(context["infra_error_count"], context["max_infra_retries"]),
280
318
  "- Previous session id: `{}`".format(context["previous_session_id"] or "(unknown)"),
281
319
  "- Previous session log reference: `{}`".format(runtime["previous_session_log"]),
282
320
  "",
@@ -316,8 +354,15 @@ def append_continuation_handoff(rendered_prompt, args, project_root, task_type,
316
354
  "task_id": task_id,
317
355
  "task_slug": task_slug,
318
356
  "previous_session_id": previous_session_id,
357
+ "failure_classification": getattr(args, "failure_classification", "") or "context_overflow",
319
358
  "continuation_count": _safe_int(getattr(args, "continuation_count", 0)),
320
359
  "context_overflow_count": _safe_int(getattr(args, "context_overflow_count", 0)),
360
+ "code_retry_count": _safe_int(getattr(args, "code_retry_count", 0)),
361
+ "infra_error_count": _safe_int(getattr(args, "infra_error_count", 0)),
362
+ "max_code_retries": _safe_int(getattr(args, "max_code_retries", 0)),
363
+ "max_infra_retries": _safe_int(getattr(args, "max_infra_retries", 0)),
364
+ "no_progress_count": _safe_int(getattr(args, "no_progress_count", 0)),
365
+ "progress_fingerprint": _safe_json_object(getattr(args, "progress_fingerprint", "")),
321
366
  "active_dev_branch": getattr(args, "active_dev_branch", ""),
322
367
  "base_branch": getattr(args, "base_branch", ""),
323
368
  "durable": durable,
@@ -276,6 +276,33 @@ def _set_continuation_common(status_data, args, session_id):
276
276
  status_data["last_session_id"] = session_id
277
277
 
278
278
 
279
+ def _set_ai_error_continuation_metadata(status_data, args, session_id, reason):
280
+ """Preserve durable task/session state for any retryable launched AI error."""
281
+ if args.active_dev_branch:
282
+ status_data["active_dev_branch"] = args.active_dev_branch
283
+ if args.base_branch:
284
+ status_data["base_branch"] = args.base_branch
285
+ status_data["continuation_pending"] = True
286
+ status_data["continuation_reason"] = reason
287
+ status_data["continuation_count"] = status_data.get("continuation_count", 0) + 1
288
+ status_data["previous_session_id"] = session_id
289
+ status_data["last_fatal_error_code"] = args.last_fatal_error_code or reason
290
+ if args.continuation_summary_path:
291
+ status_data["continuation_summary_path"] = args.continuation_summary_path
292
+ if args.no_progress_count is not None:
293
+ status_data["no_progress_count"] = max(0, args.no_progress_count)
294
+ fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
295
+ if fingerprint is not None:
296
+ status_data["last_progress_fingerprint"] = fingerprint
297
+ status_data["resume_from_phase"] = None
298
+ if session_id:
299
+ sessions = status_data.get("sessions", [])
300
+ if session_id not in sessions:
301
+ sessions.append(session_id)
302
+ status_data["sessions"] = sessions
303
+ status_data["last_session_id"] = session_id
304
+
305
+
279
306
  def set_context_overflow_continuation_metadata(status_data, args, session_id):
280
307
  """Record continuation metadata for a context-overflow outcome in status.json."""
281
308
  status_data["context_overflow_count"] = status_data.get("context_overflow_count", 0) + 1
@@ -456,6 +483,18 @@ def action_update(args, bug_list_path, state_dir):
456
483
 
457
484
  # Track what status we write to bug-fix-list.json
458
485
  new_status = current_list_status
486
+ if args.active_dev_branch:
487
+ bs["active_dev_branch"] = args.active_dev_branch
488
+ if args.base_branch:
489
+ bs["base_branch"] = args.base_branch
490
+ if args.last_fatal_error_code:
491
+ bs["last_fatal_error_code"] = args.last_fatal_error_code
492
+ if args.continuation_summary_path:
493
+ bs["continuation_summary_path"] = args.continuation_summary_path
494
+ fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
495
+ if fingerprint is not None:
496
+ bs["last_progress_fingerprint"] = fingerprint
497
+
459
498
 
460
499
  if session_status == "success":
461
500
  bs["infra_error_count"] = 0
@@ -499,10 +538,14 @@ def action_update(args, bug_list_path, state_dir):
499
538
 
500
539
  if infra_error_count >= max_infra_retries:
501
540
  new_status = "failed"
541
+ bs["continuation_pending"] = False
542
+ bs["continuation_reason"] = session_status
543
+ bs["needs_attention"] = True
502
544
  if session_id:
503
545
  bs["last_failed_session_id"] = session_id
504
546
  else:
505
- new_status = "pending"
547
+ new_status = "in_progress"
548
+ _set_ai_error_continuation_metadata(bs, args, session_id, session_status)
506
549
 
507
550
  err = update_bug_in_list(bug_list_path, bug_id, new_status)
508
551
  if err:
@@ -528,27 +571,25 @@ def action_update(args, bug_list_path, state_dir):
528
571
  else:
529
572
  bs["retry_count"] = bs.get("retry_count", 0) + 1
530
573
 
531
- cleaned = cleanup_bug_artifacts(
532
- state_dir=state_dir,
533
- bug_id=bug_id,
534
- project_root=args.project_root,
535
- )
536
-
537
574
  if bs["retry_count"] >= max_retries:
538
575
  new_status = "failed"
576
+ bs["continuation_pending"] = False
577
+ bs["continuation_reason"] = session_status
578
+ bs["needs_attention"] = True
539
579
  else:
540
- new_status = "pending"
580
+ new_status = "in_progress"
581
+ _set_ai_error_continuation_metadata(bs, args, session_id, session_status)
541
582
 
542
583
  bs["resume_from_phase"] = None
543
- bs["sessions"] = []
544
- bs["last_session_id"] = None
584
+ if session_id:
585
+ bs["last_failed_session_id"] = session_id
545
586
 
546
587
  err = update_bug_in_list(bug_list_path, bug_id, new_status)
547
588
  if err:
548
589
  error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
549
590
  return
550
591
 
551
- if session_status == "success" and session_id:
592
+ if session_id:
552
593
  sessions = bs.get("sessions", [])
553
594
  if session_id not in sessions:
554
595
  sessions.append(session_id)
@@ -581,8 +622,9 @@ def action_update(args, bug_list_path, state_dir):
581
622
  summary["degraded_reason"] = session_status
582
623
  summary["restart_policy"] = "finalization_retry"
583
624
  elif session_status == "infra_error":
584
- summary["restart_policy"] = "infra_retry"
625
+ summary["restart_policy"] = "in_process_ai_error_continuation" if new_status == "in_progress" else "needs_attention"
585
626
  summary["infra_error_count"] = bs.get("infra_error_count", 0)
627
+ summary.update(continuation_metadata_summary(bs))
586
628
  summary["artifacts_preserved"] = True
587
629
  elif session_status == "context_overflow":
588
630
  summary["restart_policy"] = "context_overflow_continuation"
@@ -593,8 +635,9 @@ def action_update(args, bug_list_path, state_dir):
593
635
  summary.update(continuation_metadata_summary(bs))
594
636
  summary["artifacts_preserved"] = True
595
637
  elif session_status != "success":
596
- summary["restart_policy"] = "full_restart"
597
- summary["cleanup_performed"] = cleaned
638
+ summary["restart_policy"] = "in_process_ai_error_continuation" if new_status == "in_progress" else "needs_attention"
639
+ summary.update(continuation_metadata_summary(bs))
640
+ summary["artifacts_preserved"] = True
598
641
 
599
642
  print(json.dumps(summary, indent=2, ensure_ascii=False))
600
643