prizmkit 1.1.99 → 1.1.100

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (57) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/lib/common.sh +244 -25
  3. package/bundled/dev-pipeline/reset-bug.sh +30 -0
  4. package/bundled/dev-pipeline/reset-feature.sh +30 -0
  5. package/bundled/dev-pipeline/reset-refactor.sh +30 -0
  6. package/bundled/dev-pipeline/run-bugfix.sh +128 -13
  7. package/bundled/dev-pipeline/run-feature.sh +130 -14
  8. package/bundled/dev-pipeline/run-refactor.sh +128 -13
  9. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  10. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  11. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +27 -30
  12. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +15 -20
  13. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -20
  14. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
  15. package/bundled/dev-pipeline/scripts/update-bug-status.py +214 -6
  16. package/bundled/dev-pipeline/scripts/update-feature-status.py +237 -6
  17. package/bundled/dev-pipeline/scripts/update-refactor-status.py +214 -6
  18. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
  19. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
  20. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
  21. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
  22. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  23. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  24. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
  25. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  26. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  27. package/bundled/dev-pipeline/tests/test_auto_skip.py +510 -0
  28. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
  29. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
  30. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  31. package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
  32. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +93 -9
  33. package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
  34. package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
  35. package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
  36. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
  37. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
  38. package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
  39. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +27 -30
  40. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +16 -20
  41. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +16 -20
  42. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
  43. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +214 -6
  44. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +237 -6
  45. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +214 -6
  46. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
  47. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
  48. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
  49. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
  50. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
  51. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
  52. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
  53. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
  54. package/bundled/skills/_metadata.json +1 -1
  55. package/package.json +1 -1
  56. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  57. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.99",
3
- "bundledAt": "2026-07-04T12:56:36.867Z",
4
- "bundledFrom": "54fe284"
2
+ "frameworkVersion": "1.1.100",
3
+ "bundledAt": "2026-07-05T09:02:21.156Z",
4
+ "bundledFrom": "c1f7dba"
5
5
  }
@@ -634,26 +634,264 @@ prizm_detect_infra_error() {
634
634
  return 1
635
635
  }
636
636
 
637
+ # Detect context-window/token overflow as a distinct provider-neutral outcome.
638
+ # Returns success when progress.json or the tail of session.log proves the model
639
+ # request exceeded the usable context window. This intentionally does not inspect
640
+ # Claude Code internals; it uses normalized progress fields and generic log text.
641
+ prizm_detect_context_overflow_error() {
642
+ local session_log="${1:-}"
643
+ local progress_json="${2:-}"
644
+
645
+ python3 - "$session_log" "$progress_json" <<'PY'
646
+ import json
647
+ import os
648
+ import re
649
+ import sys
650
+
651
+ session_log = sys.argv[1] if len(sys.argv) > 1 else ""
652
+ progress_json = sys.argv[2] if len(sys.argv) > 2 else ""
653
+
654
+ CONTEXT_CODES = re.compile(r"\b(context_overflow|context_too_large|context_length_exceeded|model_context_window_exceeded)\b", re.I)
655
+ WRAPPER = re.compile(r"\bPRIZMKIT_FATAL_ERROR\s*=\s*context_overflow\b", re.I)
656
+ WEAK = re.compile(
657
+ r"Your input exceeds the context window|input exceeds (?:the )?(?:model )?context window|"
658
+ r"context window of this model|context window (?:was )?exceeded|exceeded (?:the )?context window|"
659
+ r"maximum context length exceeded|prompt is too long|too many tokens|input token count exceeds|"
660
+ r"input tokens? exceeds?|token count exceeds",
661
+ re.I,
662
+ )
663
+ REQUEST_TOO_LARGE = re.compile(r"\brequest (?:is )?too large\b", re.I)
664
+ REQUEST_SEMANTICS = re.compile(r"\b(context|token|tokens|prompt|input|model)\b", re.I)
665
+ ERROR_CONTEXT = re.compile(
666
+ r"\bapi error\b|invalid_request_error|\bbad request\b|\bstatus\s*[:=]?\s*(400|413)\b|"
667
+ r"\bapi_error_status\b|\bapi_error_code\b|\blast_result_is_error\b\s*[\"':=]*\s*true\b|"
668
+ r"\bis_error\b\s*[\"':=]*\s*true\b|\berror\s*[:=]|\bfatal\s*[:=]",
669
+ re.I,
670
+ )
671
+
672
+
673
+ def has_error_context(text):
674
+ return bool(text and ERROR_CONTEXT.search(text))
675
+
676
+
677
+ def is_context_overflow(text, *, require_error_context=False, structured_code=False):
678
+ if not text:
679
+ return False
680
+ if WRAPPER.search(text):
681
+ return True
682
+ if structured_code and CONTEXT_CODES.search(text):
683
+ return True
684
+ if CONTEXT_CODES.search(text):
685
+ return True
686
+ if require_error_context and not has_error_context(text):
687
+ return False
688
+ if WEAK.search(text):
689
+ return True
690
+ if REQUEST_TOO_LARGE.search(text) and REQUEST_SEMANTICS.search(text):
691
+ return True
692
+ return False
693
+
694
+
695
+ def flatten(value):
696
+ parts = []
697
+ if isinstance(value, dict):
698
+ for child in value.values():
699
+ parts.extend(flatten(child))
700
+ elif isinstance(value, list):
701
+ for child in value:
702
+ parts.extend(flatten(child))
703
+ elif value is not None:
704
+ parts.append(str(value))
705
+ return parts
706
+
707
+
708
+ def progress_matches(path):
709
+ if not path or not os.path.exists(path):
710
+ return False
711
+ try:
712
+ with open(path, encoding="utf-8") as fh:
713
+ data = json.load(fh)
714
+ except Exception:
715
+ return False
716
+ code_fields = []
717
+ for key in ("fatal_error_code", "api_error_code", "error_code", "stop_reason"):
718
+ value = data.get(key) if isinstance(data, dict) else None
719
+ if value not in (None, ""):
720
+ code_fields.append(str(value))
721
+ if is_context_overflow(" ".join(code_fields), structured_code=True):
722
+ return True
723
+ text = " ".join(flatten(data))
724
+ error_like = bool(code_fields) or bool(data.get("last_result_is_error")) or data.get("api_error_status") not in (None, "") or has_error_context(text)
725
+ return is_context_overflow(text, require_error_context=not error_like, structured_code=bool(code_fields))
726
+
727
+
728
+ def log_matches(path):
729
+ if not path or not os.path.exists(path):
730
+ return False
731
+ try:
732
+ with open(path, "rb") as fh:
733
+ fh.seek(0, os.SEEK_END)
734
+ size = fh.tell()
735
+ fh.seek(max(0, size - 65536))
736
+ text = fh.read().decode("utf-8", errors="ignore")
737
+ except Exception:
738
+ return False
739
+ if WRAPPER.search(text) or CONTEXT_CODES.search(text):
740
+ return True
741
+ return is_context_overflow(text, require_error_context=True)
742
+
743
+ raise SystemExit(0 if progress_matches(progress_json) or log_matches(session_log) else 1)
744
+ PY
745
+ }
746
+
747
+ # Produce a stable progress fingerprint for context-overflow continuation safety.
748
+ # Signals intentionally match the F-007 contract:
749
+ # - git_diff_fingerprint: current worktree diff/status hash
750
+ # - checkpoint_cursor: first pending/in-progress checkpoint plus counts
751
+ # - artifact_fingerprint: relevant task artifact directory hash
752
+ # - head_commit: current HEAD commit SHA
753
+ prizm_context_progress_fingerprint() {
754
+ local project_root="$1"
755
+ local task_type="$2"
756
+ local task_id="$3"
757
+ local artifact_dir="${4:-}"
758
+ local checkpoint_file="${5:-}"
759
+
760
+ python3 - "$project_root" "$task_type" "$task_id" "$artifact_dir" "$checkpoint_file" <<'PY'
761
+ import hashlib
762
+ import json
763
+ import os
764
+ import subprocess
765
+ import sys
766
+
767
+ project_root, task_type, task_id, artifact_dir, checkpoint_file = sys.argv[1:6]
768
+ project_root = os.path.abspath(project_root or ".")
769
+ artifact_dir = os.path.abspath(os.path.join(project_root, artifact_dir)) if artifact_dir and not os.path.isabs(artifact_dir) else artifact_dir
770
+ checkpoint_file = os.path.abspath(os.path.join(project_root, checkpoint_file)) if checkpoint_file and not os.path.isabs(checkpoint_file) else checkpoint_file
771
+
772
+
773
+ def run_git(args):
774
+ try:
775
+ return subprocess.check_output(["git", "-C", project_root, *args], stderr=subprocess.DEVNULL)
776
+ except Exception:
777
+ return b""
778
+
779
+
780
+ def sha_bytes(*parts):
781
+ h = hashlib.sha256()
782
+ for part in parts:
783
+ if isinstance(part, str):
784
+ part = part.encode("utf-8", errors="surrogateescape")
785
+ h.update(part or b"")
786
+ h.update(b"\0")
787
+ return h.hexdigest()
788
+
789
+
790
+ def git_diff_fingerprint():
791
+ status = run_git(["status", "--porcelain", "--untracked-files=all", "--", "."])
792
+ diff = run_git(["diff", "--binary", "HEAD", "--", "."])
793
+ return sha_bytes(status, diff)
794
+
795
+
796
+ def head_commit():
797
+ return run_git(["rev-parse", "HEAD"]).decode("utf-8", errors="ignore").strip()
798
+
799
+
800
+ def checkpoint_cursor(path):
801
+ if not path or not os.path.exists(path):
802
+ return "missing"
803
+ try:
804
+ with open(path, encoding="utf-8") as fh:
805
+ data = json.load(fh)
806
+ except Exception as exc:
807
+ return "unreadable:{}".format(type(exc).__name__)
808
+ steps = data.get("steps") if isinstance(data, dict) else None
809
+ if not isinstance(steps, list):
810
+ return "no-steps"
811
+ counts = {"completed": 0, "skipped": 0, "pending": 0, "in_progress": 0, "failed": 0}
812
+ first_open = None
813
+ for step in steps:
814
+ if not isinstance(step, dict):
815
+ continue
816
+ status = str(step.get("status", "pending"))
817
+ counts[status] = counts.get(status, 0) + 1
818
+ if first_open is None and status in ("pending", "in_progress"):
819
+ first_open = "{}:{}:{}".format(step.get("id", ""), step.get("skill", ""), status)
820
+ return "first_open={};counts={}".format(first_open or "none", ",".join("{}:{}".format(k, counts[k]) for k in sorted(counts)))
821
+
822
+
823
+ def artifact_fingerprint(path):
824
+ if not path or not os.path.isdir(path):
825
+ return "missing"
826
+ h = hashlib.sha256()
827
+ for root, dirs, files in os.walk(path):
828
+ dirs[:] = sorted(d for d in dirs if d not in {".git", "node_modules", "__pycache__"})
829
+ for name in sorted(files):
830
+ file_path = os.path.join(root, name)
831
+ rel = os.path.relpath(file_path, path).replace(os.sep, "/")
832
+ h.update(rel.encode("utf-8", errors="surrogateescape"))
833
+ h.update(b"\0")
834
+ try:
835
+ with open(file_path, "rb") as fh:
836
+ for chunk in iter(lambda: fh.read(65536), b""):
837
+ h.update(chunk)
838
+ except Exception as exc:
839
+ h.update("unreadable:{}".format(type(exc).__name__).encode("utf-8"))
840
+ h.update(b"\0")
841
+ return h.hexdigest()
842
+
843
+ fingerprint = {
844
+ "task_type": task_type,
845
+ "task_id": task_id,
846
+ "git_diff_fingerprint": git_diff_fingerprint(),
847
+ "checkpoint_cursor": checkpoint_cursor(checkpoint_file),
848
+ "artifact_fingerprint": artifact_fingerprint(artifact_dir),
849
+ "head_commit": head_commit(),
850
+ }
851
+ print(json.dumps(fingerprint, sort_keys=True, ensure_ascii=False))
852
+ PY
853
+ }
854
+
855
+ prizm_context_progress_changed() {
856
+ local before_json="$1"
857
+ local after_json="$2"
858
+ python3 - "$before_json" "$after_json" <<'PY'
859
+ import json
860
+ import sys
861
+
862
+ try:
863
+ before = json.loads(sys.argv[1] or "{}")
864
+ after = json.loads(sys.argv[2] or "{}")
865
+ except Exception:
866
+ raise SystemExit(0)
867
+ for key in ("git_diff_fingerprint", "checkpoint_cursor", "artifact_fingerprint", "head_commit"):
868
+ if before.get(key) != after.get(key):
869
+ raise SystemExit(0)
870
+ raise SystemExit(1)
871
+ PY
872
+ }
873
+
637
874
  # Detect AI runtime/provider request failures that are not caused by generated
638
- # project code. Unlike generic infra errors, these can be deterministic for the
639
- # current transcript (for example context_too_large), so the runner must first
640
- # check semantic completion before deciding whether to retry.
875
+ # project code and are not context-window overflow. Context overflow has its own
876
+ # outcome and must be checked before this helper in session wait functions.
641
877
  prizm_detect_ai_runtime_error() {
642
878
  local session_log="${1:-}"
643
879
  local progress_json="${2:-}"
644
880
 
881
+ if prizm_detect_context_overflow_error "$session_log" "$progress_json"; then
882
+ return 1
883
+ fi
884
+
645
885
  if [[ -n "$progress_json" && -f "$progress_json" ]]; then
646
886
  local fatal_error_code
647
- fatal_error_code=$(python3 - "$progress_json" <<'PY' 2>/dev/null || true
887
+ fatal_error_code=$(python3 - "$progress_json" <<'PY'
648
888
  import json
649
889
  import sys
650
-
651
890
  try:
652
891
  with open(sys.argv[1], encoding="utf-8") as fh:
653
892
  progress = json.load(fh)
654
893
  except Exception:
655
894
  raise SystemExit(0)
656
-
657
895
  code = progress.get("fatal_error_code")
658
896
  if code:
659
897
  print(str(code))
@@ -664,25 +902,6 @@ PY
664
902
  fi
665
903
  fi
666
904
 
667
- local haystack=""
668
- if [[ -n "$session_log" && -f "$session_log" ]]; then
669
- haystack="$(tail -c 65536 "$session_log" 2>/dev/null || true)"
670
- fi
671
- if [[ -n "$progress_json" && -f "$progress_json" ]]; then
672
- haystack+=$'\n'
673
- haystack+="$(cat "$progress_json" 2>/dev/null || true)"
674
- fi
675
-
676
- [[ -n "$haystack" ]] || return 1
677
-
678
- if printf '%s' "$haystack" | grep -Eiq \
679
- 'context_too_large|model_context_window_exceeded|input exceeds the context window|context window of this model|context window (was )?exceeded|exceeded (the )?context window|invalid_request_error.*context window|context window.*invalid_request_error'; then
680
- if printf '%s' "$haystack" | grep -Eiq \
681
- 'api error|invalid_request_error|api_error_status|api_error_code|status[[:space:]]*[:=]?[[:space:]]*(400|413)|last_result_is_error[[:space:]"'\'':=]+true|is_error[[:space:]"'\'':=]+true'; then
682
- return 0
683
- fi
684
- fi
685
-
686
905
  return 1
687
906
  }
688
907
 
@@ -63,6 +63,35 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
63
63
  log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
64
64
  log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
65
65
 
66
+ warn_if_continuation_pending() {
67
+ local status_file="$1"
68
+ local item_id="$2"
69
+ local item_label="$3"
70
+ [[ -f "$status_file" ]] || return 0
71
+
72
+ local info continuation_pending active_dev_branch continuation_reason
73
+ info=$(python3 - "$status_file" <<'PY' 2>/dev/null || true
74
+ import json
75
+ import sys
76
+ with open(sys.argv[1], encoding="utf-8") as fh:
77
+ data = json.load(fh)
78
+ pending = data.get("continuation_pending") is True
79
+ print("{}\t{}\t{}".format(
80
+ "true" if pending else "false",
81
+ data.get("active_dev_branch") or "",
82
+ data.get("continuation_reason") or "context_overflow",
83
+ ))
84
+ PY
85
+ )
86
+ IFS=$'\t' read -r continuation_pending active_dev_branch continuation_reason <<< "$info"
87
+ if [[ "$continuation_pending" == "true" ]]; then
88
+ log_warn "Continuation pending for $item_label $item_id (${continuation_reason:-context_overflow}). Manual reset will abandon automatic continuation progress."
89
+ if [[ -n "$active_dev_branch" ]]; then
90
+ log_warn "Pending continuation branch: $active_dev_branch"
91
+ fi
92
+ fi
93
+ }
94
+
66
95
  # ============================================================
67
96
  # Parse args
68
97
  # ============================================================
@@ -259,6 +288,7 @@ print('?')
259
288
  CURRENT_RETRY=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(d.get('retry_count',0))")
260
289
  SESSION_COUNT=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(len(d.get('sessions',[])))")
261
290
  log_info "Current status: $CURRENT_STATUS (retry $CURRENT_RETRY, $SESSION_COUNT sessions)"
291
+ warn_if_continuation_pending "$STATUS_FILE" "$CUR_BUG_ID" "bug"
262
292
  else
263
293
  log_info "Current status: $CURRENT_STATUS (no runtime state file)"
264
294
  fi
@@ -63,6 +63,35 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
63
63
  log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
64
64
  log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
65
65
 
66
+ warn_if_continuation_pending() {
67
+ local status_file="$1"
68
+ local item_id="$2"
69
+ local item_label="$3"
70
+ [[ -f "$status_file" ]] || return 0
71
+
72
+ local info continuation_pending active_dev_branch continuation_reason
73
+ info=$(python3 - "$status_file" <<'PY' 2>/dev/null || true
74
+ import json
75
+ import sys
76
+ with open(sys.argv[1], encoding="utf-8") as fh:
77
+ data = json.load(fh)
78
+ pending = data.get("continuation_pending") is True
79
+ print("{}\t{}\t{}".format(
80
+ "true" if pending else "false",
81
+ data.get("active_dev_branch") or "",
82
+ data.get("continuation_reason") or "context_overflow",
83
+ ))
84
+ PY
85
+ )
86
+ IFS=$'\t' read -r continuation_pending active_dev_branch continuation_reason <<< "$info"
87
+ if [[ "$continuation_pending" == "true" ]]; then
88
+ log_warn "Continuation pending for $item_label $item_id (${continuation_reason:-context_overflow}). Manual reset will abandon automatic continuation progress."
89
+ if [[ -n "$active_dev_branch" ]]; then
90
+ log_warn "Pending continuation branch: $active_dev_branch"
91
+ fi
92
+ fi
93
+ }
94
+
66
95
  # ============================================================
67
96
  # Parse args
68
97
  # ============================================================
@@ -268,6 +297,7 @@ print('?')
268
297
  CURRENT_RETRY=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(d.get('retry_count',0))")
269
298
  SESSION_COUNT=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(len(d.get('sessions',[])))")
270
299
  log_info "Current status: $CURRENT_STATUS (retry $CURRENT_RETRY, $SESSION_COUNT sessions)"
300
+ warn_if_continuation_pending "$STATUS_FILE" "$CUR_FEATURE_ID" "feature"
271
301
  else
272
302
  log_info "Current status: $CURRENT_STATUS (no runtime state file)"
273
303
  fi
@@ -53,6 +53,35 @@ log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
53
53
  log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
54
54
  log_success() { echo -e "${GREEN}[OK]${NC} $*"; }
55
55
 
56
+ warn_if_continuation_pending() {
57
+ local status_file="$1"
58
+ local item_id="$2"
59
+ local item_label="$3"
60
+ [[ -f "$status_file" ]] || return 0
61
+
62
+ local info continuation_pending active_dev_branch continuation_reason
63
+ info=$(python3 - "$status_file" <<'PY' 2>/dev/null || true
64
+ import json
65
+ import sys
66
+ with open(sys.argv[1], encoding="utf-8") as fh:
67
+ data = json.load(fh)
68
+ pending = data.get("continuation_pending") is True
69
+ print("{}\t{}\t{}".format(
70
+ "true" if pending else "false",
71
+ data.get("active_dev_branch") or "",
72
+ data.get("continuation_reason") or "context_overflow",
73
+ ))
74
+ PY
75
+ )
76
+ IFS=$'\t' read -r continuation_pending active_dev_branch continuation_reason <<< "$info"
77
+ if [[ "$continuation_pending" == "true" ]]; then
78
+ log_warn "Continuation pending for $item_label $item_id (${continuation_reason:-context_overflow}). Manual reset will abandon automatic continuation progress."
79
+ if [[ -n "$active_dev_branch" ]]; then
80
+ log_warn "Pending continuation branch: $active_dev_branch"
81
+ fi
82
+ fi
83
+ }
84
+
56
85
  # ============================================================
57
86
  # Parse args
58
87
  # ============================================================
@@ -257,6 +286,7 @@ print('?')
257
286
  CURRENT_RETRY=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(d.get('retry_count',0))")
258
287
  SESSION_COUNT=$(python3 -c "import json; d=json.load(open('$STATUS_FILE')); print(len(d.get('sessions',[])))")
259
288
  log_info "Current status: $CURRENT_STATUS (retry $CURRENT_RETRY, $SESSION_COUNT sessions)"
289
+ warn_if_continuation_pending "$STATUS_FILE" "$CUR_REFACTOR_ID" "refactor"
260
290
  else
261
291
  log_info "Current status: $CURRENT_STATUS (no runtime state file)"
262
292
  fi