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.
Files changed (79) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +5 -5
  3. package/bundled/dev-pipeline/launch-bugfix-daemon.sh +1 -1
  4. package/bundled/dev-pipeline/launch-feature-daemon.sh +1 -1
  5. package/bundled/dev-pipeline/launch-refactor-daemon.sh +1 -1
  6. package/bundled/dev-pipeline/lib/common.sh +244 -25
  7. package/bundled/dev-pipeline/reset-bug.sh +32 -2
  8. package/bundled/dev-pipeline/reset-feature.sh +32 -2
  9. package/bundled/dev-pipeline/reset-refactor.sh +32 -2
  10. package/bundled/dev-pipeline/run-bugfix.sh +133 -18
  11. package/bundled/dev-pipeline/run-feature.sh +134 -18
  12. package/bundled/dev-pipeline/run-refactor.sh +133 -18
  13. package/bundled/dev-pipeline/scripts/check-session-status.py +74 -1
  14. package/bundled/dev-pipeline/scripts/continuation.py +374 -0
  15. package/bundled/dev-pipeline/scripts/detect-stuck.py +5 -5
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +29 -35
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +19 -25
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +19 -25
  19. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +1 -1
  20. package/bundled/dev-pipeline/scripts/init-pipeline.py +1 -1
  21. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +1 -1
  22. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +146 -16
  23. package/bundled/dev-pipeline/scripts/update-bug-status.py +220 -13
  24. package/bundled/dev-pipeline/scripts/update-feature-status.py +244 -18
  25. package/bundled/dev-pipeline/scripts/update-refactor-status.py +220 -13
  26. package/bundled/dev-pipeline/templates/agent-prompts/dev-implement.md +1 -1
  27. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +2 -2
  28. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +3 -3
  29. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +3 -3
  30. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +1 -1
  31. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -3
  32. package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +31 -66
  33. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  34. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  35. package/bundled/dev-pipeline/tests/test_auto_skip.py +518 -8
  36. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +103 -0
  37. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +60 -0
  38. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +43 -0
  39. package/bundled/dev-pipeline-windows/lib/common.ps1 +172 -10
  40. package/bundled/dev-pipeline-windows/lib/pipeline.ps1 +95 -11
  41. package/bundled/dev-pipeline-windows/lib/reset.ps1 +34 -0
  42. package/bundled/dev-pipeline-windows/reset-bug.ps1 +1 -0
  43. package/bundled/dev-pipeline-windows/reset-feature.ps1 +1 -0
  44. package/bundled/dev-pipeline-windows/reset-refactor.ps1 +1 -0
  45. package/bundled/dev-pipeline-windows/scripts/check-session-status.py +74 -1
  46. package/bundled/dev-pipeline-windows/scripts/continuation.py +374 -0
  47. package/bundled/dev-pipeline-windows/scripts/detect-stuck.py +5 -5
  48. package/bundled/dev-pipeline-windows/scripts/generate-bootstrap-prompt.py +29 -35
  49. package/bundled/dev-pipeline-windows/scripts/generate-bugfix-prompt.py +20 -25
  50. package/bundled/dev-pipeline-windows/scripts/generate-refactor-prompt.py +20 -25
  51. package/bundled/dev-pipeline-windows/scripts/init-bugfix-pipeline.py +1 -1
  52. package/bundled/dev-pipeline-windows/scripts/init-pipeline.py +1 -1
  53. package/bundled/dev-pipeline-windows/scripts/init-refactor-pipeline.py +1 -1
  54. package/bundled/dev-pipeline-windows/scripts/parse-stream-progress.py +146 -16
  55. package/bundled/dev-pipeline-windows/scripts/update-bug-status.py +220 -13
  56. package/bundled/dev-pipeline-windows/scripts/update-feature-status.py +244 -18
  57. package/bundled/dev-pipeline-windows/scripts/update-refactor-status.py +220 -13
  58. package/bundled/dev-pipeline-windows/templates/agent-prompts/dev-implement.md +1 -1
  59. package/bundled/dev-pipeline-windows/templates/bootstrap-tier1.md +2 -2
  60. package/bundled/dev-pipeline-windows/templates/bootstrap-tier2.md +3 -3
  61. package/bundled/dev-pipeline-windows/templates/bootstrap-tier3.md +3 -3
  62. package/bundled/dev-pipeline-windows/templates/bugfix-bootstrap-prompt.md +1 -1
  63. package/bundled/dev-pipeline-windows/templates/refactor-bootstrap-prompt.md +3 -3
  64. package/bundled/dev-pipeline-windows/templates/sections/log-size-awareness.md +31 -68
  65. package/bundled/dev-pipeline-windows/templates/session-status-schema.json +1 -1
  66. package/bundled/skills/_metadata.json +1 -1
  67. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +2 -2
  68. package/bundled/skills/feature-pipeline-launcher/SKILL.md +2 -2
  69. package/bundled/skills/recovery-workflow/SKILL.md +2 -2
  70. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  71. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +2 -2
  72. package/bundled/skills-windows/bugfix-pipeline-launcher/SKILL.md +2 -2
  73. package/bundled/skills-windows/feature-pipeline-launcher/SKILL.md +2 -2
  74. package/bundled/skills-windows/recovery-workflow/SKILL.md +2 -2
  75. package/bundled/skills-windows/recovery-workflow/evals/evals.json +2 -2
  76. package/bundled/skills-windows/refactor-pipeline-launcher/SKILL.md +2 -2
  77. package/package.json +1 -1
  78. package/bundled/dev-pipeline/scripts/monitor-log.sh +0 -104
  79. package/bundled/dev-pipeline-windows/scripts/monitor-log.ps1 +0 -102
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.98",
3
- "bundledAt": "2026-07-03T08:19:11.345Z",
4
- "bundledFrom": "30e470e"
2
+ "frameworkVersion": "1.1.100",
3
+ "bundledAt": "2026-07-05T09:02:21.156Z",
4
+ "bundledFrom": "c1f7dba"
5
5
  }
@@ -713,13 +713,13 @@ Features are executed in dependency order via DAG:
713
713
 
714
714
  ```bash
715
715
  # Live tail current session
716
- tail -f .prizmkit/state/features/features/F-*/sessions/*/logs/session.log
716
+ tail -f .prizmkit/state/features/F-*/sessions/*/logs/session.log
717
717
 
718
718
  # Specific feature
719
- tail -f .prizmkit/state/features/features/F-003/sessions/*/logs/session.log
719
+ tail -f .prizmkit/state/features/F-003/sessions/*/logs/session.log
720
720
 
721
721
  # Review completed session
722
- cat .prizmkit/state/features/features/F-003/sessions/F-003-*/logs/session.log | less
722
+ cat .prizmkit/state/features/F-003/sessions/F-003-*/logs/session.log | less
723
723
  ```
724
724
 
725
725
  ### Pause & Resume
@@ -735,7 +735,7 @@ cat .prizmkit/state/features/features/F-003/sessions/F-003-*/logs/session.log |
735
735
  ./.prizmkit/dev-pipeline/run-feature.sh status .prizmkit/plans/feature-list.json
736
736
 
737
737
  # Review session logs
738
- cat .prizmkit/state/features/features/F-XXX/sessions/*/logs/session.log
738
+ cat .prizmkit/state/features/F-XXX/sessions/*/logs/session.log
739
739
 
740
740
  # Option A: Fix manually and mark complete
741
741
  python3 .prizmkit/dev-pipeline/scripts/update-feature-status.py \
@@ -802,7 +802,7 @@ run-bugfix.sh main loop
802
802
  ```
803
803
  .prizmkit/state/bugfix/ # Runtime state (gitignored)
804
804
  +-- pipeline.json
805
- +-- bugs/B-XXX/
805
+ +-- B-XXX/
806
806
  +-- status.json
807
807
  +-- sessions/B-XXX-YYYYMMDDHHMMSS/
808
808
  +-- bootstrap-prompt.md
@@ -384,7 +384,7 @@ total = len(bugs)
384
384
  counts = {'completed': 0, 'in_progress': 0, 'failed': 0, 'pending': 0, 'needs_info': 0, 'skipped': 0}
385
385
  for bug in bugs:
386
386
  bid = bug.get('id', '')
387
- sp = os.path.join('$STATE_DIR', 'bugs', bid, 'status.json')
387
+ sp = os.path.join('$STATE_DIR', bid, 'status.json')
388
388
  if os.path.isfile(sp):
389
389
  fs = json.load(open(sp))
390
390
  st = fs.get('status', 'pending')
@@ -487,7 +487,7 @@ total = len(features)
487
487
  counts = {'completed': 0, 'in_progress': 0, 'failed': 0, 'pending': 0, 'skipped': 0, 'auto_skipped': 0}
488
488
  for feat in features:
489
489
  fid = feat.get('id', '')
490
- sp = os.path.join(state_dir, 'features', fid, 'status.json')
490
+ sp = os.path.join(state_dir, fid, 'status.json')
491
491
  if os.path.isfile(sp):
492
492
  fs = load_json(sp)
493
493
  st = fs.get('status', 'pending')
@@ -384,7 +384,7 @@ total = len(items)
384
384
  counts = {'completed': 0, 'in_progress': 0, 'failed': 0, 'pending': 0, 'skipped': 0, 'auto_skipped': 0}
385
385
  for item in items:
386
386
  rid = item.get('id', '')
387
- sp = os.path.join('$STATE_DIR', 'refactors', rid, 'status.json')
387
+ sp = os.path.join('$STATE_DIR', rid, 'status.json')
388
388
  if os.path.isfile(sp):
389
389
  fs = json.load(open(sp))
390
390
  st = fs.get('status', 'pending')
@@ -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
  # ============================================================
@@ -243,7 +272,7 @@ sys.exit(1)
243
272
  echo -e "${BOLD} Reset: $CUR_BUG_ID — $BUG_TITLE${NC}"
244
273
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
245
274
 
246
- STATUS_FILE="$STATE_DIR/bugs/$CUR_BUG_ID/status.json"
275
+ STATUS_FILE="$STATE_DIR/$CUR_BUG_ID/status.json"
247
276
  # Read status from bug-fix-list.json (single source of truth)
248
277
  CURRENT_STATUS=$(python3 -c "
249
278
  import json, sys
@@ -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
@@ -270,7 +300,7 @@ print('?')
270
300
  log_info "PrizmKit artifacts: $BUGFIX_COUNT files in .prizmkit/bugfix/$CUR_BUG_ID/"
271
301
  fi
272
302
 
273
- SESSIONS_DIR="$STATE_DIR/bugs/$CUR_BUG_ID/sessions"
303
+ SESSIONS_DIR="$STATE_DIR/$CUR_BUG_ID/sessions"
274
304
  SESSIONS_COUNT=0
275
305
  if [[ -d "$SESSIONS_DIR" ]]; then
276
306
  SESSIONS_COUNT=$(find "$SESSIONS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
@@ -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
  # ============================================================
@@ -252,7 +281,7 @@ sys.exit(1)
252
281
  echo -e "${BOLD} Reset: $CUR_FEATURE_ID — $FEATURE_TITLE${NC}"
253
282
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
254
283
 
255
- STATUS_FILE="$STATE_DIR/features/$CUR_FEATURE_ID/status.json"
284
+ STATUS_FILE="$STATE_DIR/$CUR_FEATURE_ID/status.json"
256
285
  # Read status from feature-list.json (single source of truth)
257
286
  CURRENT_STATUS=$(python3 -c "
258
287
  import json, sys
@@ -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
@@ -279,7 +309,7 @@ print('?')
279
309
  log_info "PrizmKit artifacts: $SPECS_COUNT files in .prizmkit/specs/$FEATURE_SLUG/"
280
310
  fi
281
311
 
282
- SESSIONS_DIR="$STATE_DIR/features/$CUR_FEATURE_ID/sessions"
312
+ SESSIONS_DIR="$STATE_DIR/$CUR_FEATURE_ID/sessions"
283
313
  SESSIONS_COUNT=0
284
314
  if [[ -d "$SESSIONS_DIR" ]]; then
285
315
  SESSIONS_COUNT=$(find "$SESSIONS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')
@@ -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
  # ============================================================
@@ -241,7 +270,7 @@ sys.exit(1)
241
270
  echo -e "${BOLD} Reset: $CUR_REFACTOR_ID — $REFACTOR_TITLE${NC}"
242
271
  echo -e "${BOLD}════════════════════════════════════════════════════${NC}"
243
272
 
244
- STATUS_FILE="$STATE_DIR/refactors/$CUR_REFACTOR_ID/status.json"
273
+ STATUS_FILE="$STATE_DIR/$CUR_REFACTOR_ID/status.json"
245
274
  # Read status from refactor-list.json (single source of truth)
246
275
  CURRENT_STATUS=$(python3 -c "
247
276
  import json, sys
@@ -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
@@ -268,7 +298,7 @@ print('?')
268
298
  log_info "PrizmKit artifacts: $SPECS_COUNT files in .prizmkit/specs/$REFACTOR_SLUG/"
269
299
  fi
270
300
 
271
- SESSIONS_DIR="$STATE_DIR/refactors/$CUR_REFACTOR_ID/sessions"
301
+ SESSIONS_DIR="$STATE_DIR/$CUR_REFACTOR_ID/sessions"
272
302
  SESSIONS_COUNT=0
273
303
  if [[ -d "$SESSIONS_DIR" ]]; then
274
304
  SESSIONS_COUNT=$(find "$SESSIONS_DIR" -mindepth 1 -maxdepth 1 -type d 2>/dev/null | wc -l | tr -d ' ')