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
package/bundled/VERSION.json
CHANGED
|
@@ -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/
|
|
716
|
+
tail -f .prizmkit/state/features/F-*/sessions/*/logs/session.log
|
|
717
717
|
|
|
718
718
|
# Specific feature
|
|
719
|
-
tail -f .prizmkit/state/features/
|
|
719
|
+
tail -f .prizmkit/state/features/F-003/sessions/*/logs/session.log
|
|
720
720
|
|
|
721
721
|
# Review completed session
|
|
722
|
-
cat .prizmkit/state/features/
|
|
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/
|
|
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
|
-
+--
|
|
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',
|
|
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,
|
|
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',
|
|
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
|
|
639
|
-
#
|
|
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'
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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 ' ')
|