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
|
@@ -20,11 +20,11 @@ import re
|
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
22
|
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
23
|
+
from continuation import add_continuation_args, append_continuation_handoff, is_continuation
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
DEFAULT_MAX_RETRIES = 3
|
|
26
27
|
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
27
|
-
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
28
28
|
|
|
29
29
|
LOGGER = setup_logging("generate-bugfix-prompt")
|
|
30
30
|
|
|
@@ -47,6 +47,8 @@ def parse_args():
|
|
|
47
47
|
parser.add_argument("--template", default=None, help="Custom template path. Defaults to {script_dir}/../templates/bugfix-bootstrap-prompt.md")
|
|
48
48
|
parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
|
|
49
49
|
parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
|
|
50
|
+
add_continuation_args(parser, "bugfix")
|
|
51
|
+
|
|
50
52
|
return parser.parse_args()
|
|
51
53
|
|
|
52
54
|
|
|
@@ -220,7 +222,7 @@ def get_prev_session_status(state_dir, bug_id):
|
|
|
220
222
|
if not state_dir:
|
|
221
223
|
return "N/A (first run)"
|
|
222
224
|
|
|
223
|
-
bug_status_path = os.path.join(state_dir,
|
|
225
|
+
bug_status_path = os.path.join(state_dir, bug_id, "status.json")
|
|
224
226
|
if not os.path.isfile(bug_status_path):
|
|
225
227
|
return "N/A (first run)"
|
|
226
228
|
|
|
@@ -234,8 +236,7 @@ def get_prev_session_status(state_dir, bug_id):
|
|
|
234
236
|
if not last_session_id:
|
|
235
237
|
return "N/A (first run)"
|
|
236
238
|
|
|
237
|
-
session_status_path = os.path.join(
|
|
238
|
-
state_dir, "bugs", bug_id, "sessions",
|
|
239
|
+
session_status_path = os.path.join(state_dir, bug_id, "sessions",
|
|
239
240
|
last_session_id, "session-status.json"
|
|
240
241
|
)
|
|
241
242
|
if not os.path.isfile(session_status_path):
|
|
@@ -306,7 +307,7 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
306
307
|
|
|
307
308
|
# Session status path
|
|
308
309
|
session_status_path = os.path.join(
|
|
309
|
-
project_root, ".prizmkit", "state", "bugfix",
|
|
310
|
+
project_root, ".prizmkit", "state", "bugfix", args.bug_id,
|
|
310
311
|
"sessions", args.session_id, "session-status.json"
|
|
311
312
|
)
|
|
312
313
|
|
|
@@ -358,33 +359,15 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
358
359
|
browser_verify_steps = " # (reproduce bug and verify fix)"
|
|
359
360
|
|
|
360
361
|
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
361
|
-
if max_log_size == 0:
|
|
362
|
-
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
363
|
-
else:
|
|
364
|
-
poll_interval = _parse_positive_int_env(
|
|
365
|
-
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
366
|
-
)
|
|
367
362
|
|
|
368
363
|
replacements = {
|
|
369
364
|
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
370
365
|
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
371
366
|
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
372
|
-
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
373
|
-
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
374
|
-
".prizmkit", "state", "bugfix", "bugs", args.bug_id,
|
|
375
|
-
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
376
|
-
),
|
|
377
|
-
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
378
|
-
".prizmkit", "state", "bugfix", "bugs", args.bug_id,
|
|
379
|
-
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
380
|
-
),
|
|
381
367
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
382
|
-
".prizmkit", "state", "bugfix",
|
|
368
|
+
".prizmkit", "state", "bugfix", args.bug_id,
|
|
383
369
|
"sessions", args.session_id, "logs", "session.log",
|
|
384
370
|
),
|
|
385
|
-
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
386
|
-
".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
|
|
387
|
-
),
|
|
388
371
|
"{{RUN_ID}}": args.run_id,
|
|
389
372
|
"{{SESSION_ID}}": args.session_id,
|
|
390
373
|
"{{BUG_ID}}": args.bug_id,
|
|
@@ -661,6 +644,8 @@ def main():
|
|
|
661
644
|
|
|
662
645
|
# Build replacements
|
|
663
646
|
replacements = build_replacements(args, bug, global_context, script_dir)
|
|
647
|
+
if is_continuation(args):
|
|
648
|
+
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
664
649
|
|
|
665
650
|
# Add checkpoint path to replacements
|
|
666
651
|
checkpoint_rel = os.path.join(
|
|
@@ -685,7 +670,7 @@ def main():
|
|
|
685
670
|
checkpoint = generate_bugfix_checkpoint(args.bug_id, args.session_id)
|
|
686
671
|
|
|
687
672
|
is_resume = args.resume_phase != "null"
|
|
688
|
-
if is_resume and os.path.exists(checkpoint_path):
|
|
673
|
+
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
689
674
|
try:
|
|
690
675
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
691
676
|
existing = json.load(f)
|
|
@@ -704,6 +689,15 @@ def main():
|
|
|
704
689
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
705
690
|
LOGGER.info("Wrote bugfix checkpoint to %s", checkpoint_path)
|
|
706
691
|
|
|
692
|
+
if is_continuation(args):
|
|
693
|
+
rendered = append_continuation_handoff(
|
|
694
|
+
rendered, args, project_root, args.task_type, args.bug_id,
|
|
695
|
+
args.bug_id, checkpoint,
|
|
696
|
+
)
|
|
697
|
+
err = write_output(args.output, rendered)
|
|
698
|
+
if err:
|
|
699
|
+
emit_failure(err)
|
|
700
|
+
|
|
707
701
|
# Resolve critic and mode
|
|
708
702
|
bug_critic = bug.get("critic", False)
|
|
709
703
|
if args.critic is not None:
|
|
@@ -20,11 +20,11 @@ import re
|
|
|
20
20
|
import sys
|
|
21
21
|
|
|
22
22
|
from utils import enrich_global_context, load_json_file, read_platform_conventions, setup_logging
|
|
23
|
+
from continuation import add_continuation_args, append_continuation_handoff, is_continuation
|
|
23
24
|
|
|
24
25
|
|
|
25
26
|
DEFAULT_MAX_RETRIES = 3
|
|
26
27
|
DEFAULT_MAX_LOG_SIZE = 2097152
|
|
27
|
-
DEFAULT_LOG_MONITOR_POLL_INTERVAL = 30
|
|
28
28
|
|
|
29
29
|
LOGGER = setup_logging("generate-refactor-prompt")
|
|
30
30
|
|
|
@@ -138,6 +138,8 @@ def parse_args():
|
|
|
138
138
|
parser.add_argument("--template", default=None, help="Custom template path. Defaults to {script_dir}/../templates/refactor-bootstrap-prompt.md")
|
|
139
139
|
parser.add_argument("--mode", default=None, help="Pipeline execution mode override: lite, standard, full")
|
|
140
140
|
parser.add_argument("--critic", default=None, help="Enable critic agent: true/false")
|
|
141
|
+
add_continuation_args(parser, "refactor")
|
|
142
|
+
|
|
141
143
|
return parser.parse_args()
|
|
142
144
|
|
|
143
145
|
|
|
@@ -375,7 +377,7 @@ def get_prev_session_status(state_dir, refactor_id):
|
|
|
375
377
|
if not state_dir:
|
|
376
378
|
return "N/A (first run)"
|
|
377
379
|
|
|
378
|
-
refactor_status_path = os.path.join(state_dir,
|
|
380
|
+
refactor_status_path = os.path.join(state_dir, refactor_id, "status.json")
|
|
379
381
|
try:
|
|
380
382
|
with open(refactor_status_path, "r", encoding="utf-8") as f:
|
|
381
383
|
refactor_status = json.load(f)
|
|
@@ -386,8 +388,7 @@ def get_prev_session_status(state_dir, refactor_id):
|
|
|
386
388
|
if not last_session_id:
|
|
387
389
|
return "N/A (first run)"
|
|
388
390
|
|
|
389
|
-
session_status_path = os.path.join(
|
|
390
|
-
state_dir, "refactors", refactor_id, "sessions",
|
|
391
|
+
session_status_path = os.path.join(state_dir, refactor_id, "sessions",
|
|
391
392
|
last_session_id, "session-status.json"
|
|
392
393
|
)
|
|
393
394
|
try:
|
|
@@ -455,7 +456,7 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
455
456
|
|
|
456
457
|
# Session status path
|
|
457
458
|
session_status_path = os.path.join(
|
|
458
|
-
project_root, ".prizmkit", "state", "refactor",
|
|
459
|
+
project_root, ".prizmkit", "state", "refactor", args.refactor_id,
|
|
459
460
|
"sessions", args.session_id, "session-status.json"
|
|
460
461
|
)
|
|
461
462
|
|
|
@@ -518,33 +519,15 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
|
|
|
518
519
|
browser_verify_steps = " # (validate UI renders correctly and feature still works)"
|
|
519
520
|
|
|
520
521
|
max_log_size = _parse_non_negative_int_env("MAX_LOG_SIZE", DEFAULT_MAX_LOG_SIZE)
|
|
521
|
-
if max_log_size == 0:
|
|
522
|
-
poll_interval = DEFAULT_LOG_MONITOR_POLL_INTERVAL
|
|
523
|
-
else:
|
|
524
|
-
poll_interval = _parse_positive_int_env(
|
|
525
|
-
"LOG_MONITOR_POLL_INTERVAL", DEFAULT_LOG_MONITOR_POLL_INTERVAL,
|
|
526
|
-
)
|
|
527
522
|
|
|
528
523
|
replacements = {
|
|
529
524
|
"{{LOG_SIZE_AWARENESS}}": load_log_size_section(script_dir),
|
|
530
525
|
"{{MAX_LOG_SIZE}}": str(max_log_size),
|
|
531
526
|
"{{MAX_LOG_SIZE_HUMAN}}": _format_bytes(max_log_size),
|
|
532
|
-
"{{LOG_MONITOR_POLL_INTERVAL}}": str(poll_interval),
|
|
533
|
-
"{{LOG_MONITOR_STATUS_FILE}}": os.path.join(
|
|
534
|
-
".prizmkit", "state", "refactor", "refactors", args.refactor_id,
|
|
535
|
-
"sessions", args.session_id, "logs", "log-monitor-status.txt",
|
|
536
|
-
),
|
|
537
|
-
"{{LOG_MONITOR_BASELINE_FILE}}": os.path.join(
|
|
538
|
-
".prizmkit", "state", "refactor", "refactors", args.refactor_id,
|
|
539
|
-
"sessions", args.session_id, "logs", "log-monitor-baseline.txt",
|
|
540
|
-
),
|
|
541
527
|
"{{SESSION_LOG_PATH}}": os.path.join(
|
|
542
|
-
".prizmkit", "state", "refactor",
|
|
528
|
+
".prizmkit", "state", "refactor", args.refactor_id,
|
|
543
529
|
"sessions", args.session_id, "logs", "session.log",
|
|
544
530
|
),
|
|
545
|
-
"{{LOG_MONITOR_SCRIPT}}": os.path.join(
|
|
546
|
-
".prizmkit", "dev-pipeline", "scripts", "monitor-log.sh",
|
|
547
|
-
),
|
|
548
531
|
"{{RUN_ID}}": args.run_id,
|
|
549
532
|
"{{SESSION_ID}}": args.session_id,
|
|
550
533
|
"{{REFACTOR_ID}}": args.refactor_id,
|
|
@@ -743,6 +726,8 @@ def main():
|
|
|
743
726
|
|
|
744
727
|
# Build replacements
|
|
745
728
|
replacements = build_replacements(args, refactor, refactors, global_context, script_dir)
|
|
729
|
+
if is_continuation(args):
|
|
730
|
+
replacements["{{LOG_SIZE_AWARENESS}}"] = ""
|
|
746
731
|
|
|
747
732
|
# Render the template
|
|
748
733
|
rendered = render_template(template_content, replacements, args.resume_phase, refactor)
|
|
@@ -764,7 +749,7 @@ def main():
|
|
|
764
749
|
checkpoint = generate_refactor_checkpoint(args.refactor_id, args.session_id)
|
|
765
750
|
|
|
766
751
|
is_resume = args.resume_phase != "null"
|
|
767
|
-
if is_resume and os.path.exists(checkpoint_path):
|
|
752
|
+
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
768
753
|
try:
|
|
769
754
|
with open(checkpoint_path, "r", encoding="utf-8") as f:
|
|
770
755
|
existing = json.load(f)
|
|
@@ -783,6 +768,15 @@ def main():
|
|
|
783
768
|
json.dump(checkpoint, f, indent=2, ensure_ascii=False)
|
|
784
769
|
LOGGER.info("Wrote refactor checkpoint to %s", checkpoint_path)
|
|
785
770
|
|
|
771
|
+
if is_continuation(args):
|
|
772
|
+
rendered = append_continuation_handoff(
|
|
773
|
+
rendered, args, project_root, args.task_type, args.refactor_id,
|
|
774
|
+
args.refactor_id, checkpoint,
|
|
775
|
+
)
|
|
776
|
+
err = write_output(args.output, rendered)
|
|
777
|
+
if err:
|
|
778
|
+
emit_failure(err)
|
|
779
|
+
|
|
786
780
|
# Resolve critic and mode
|
|
787
781
|
refactor_critic = refactor.get("critic", False)
|
|
788
782
|
if args.critic is not None:
|
|
@@ -207,7 +207,7 @@ def create_state_directory(state_dir, bug_list_path, bugs):
|
|
|
207
207
|
abs_bug_list_path = os.path.abspath(bug_list_path)
|
|
208
208
|
# Store as relative path from state_dir so pipeline.json is portable across machines
|
|
209
209
|
rel_bug_list_path = os.path.relpath(abs_bug_list_path, abs_state_dir)
|
|
210
|
-
bugs_dir =
|
|
210
|
+
bugs_dir = abs_state_dir
|
|
211
211
|
|
|
212
212
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
213
213
|
run_id = "bugfix-run-" + datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
|
|
@@ -242,7 +242,7 @@ def create_state_directory(state_dir, feature_list_path, features):
|
|
|
242
242
|
abs_feature_list_path = os.path.abspath(feature_list_path)
|
|
243
243
|
# Store as relative path from state_dir so pipeline.json is portable across machines
|
|
244
244
|
rel_feature_list_path = os.path.relpath(abs_feature_list_path, abs_state_dir)
|
|
245
|
-
features_dir =
|
|
245
|
+
features_dir = abs_state_dir
|
|
246
246
|
|
|
247
247
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
248
248
|
run_id = "run-" + datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
|
|
@@ -283,7 +283,7 @@ def create_state_directory(state_dir, refactor_list_path, refactors):
|
|
|
283
283
|
abs_refactor_list_path = os.path.abspath(refactor_list_path)
|
|
284
284
|
# Store as relative path from state_dir so pipeline.json is portable across machines
|
|
285
285
|
rel_refactor_list_path = os.path.relpath(abs_refactor_list_path, abs_state_dir)
|
|
286
|
-
refactors_dir =
|
|
286
|
+
refactors_dir = abs_state_dir
|
|
287
287
|
|
|
288
288
|
now = datetime.now(timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ")
|
|
289
289
|
run_id = "refactor-run-" + datetime.now(timezone.utc).strftime("%Y%m%d%H%M%S")
|
|
@@ -60,30 +60,66 @@ PHASE_KEYWORDS = {
|
|
|
60
60
|
},
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
-
|
|
63
|
+
CONTEXT_OVERFLOW_CODE = "context_overflow"
|
|
64
|
+
|
|
65
|
+
# Exact machine/wrapper signals. These are only treated as self-sufficient when
|
|
66
|
+
# they appear in structured error-code fields or as explicit wrapper markers.
|
|
67
|
+
CONTEXT_CODE_PATTERNS = [
|
|
68
|
+
re.compile(pattern, re.IGNORECASE)
|
|
69
|
+
for pattern in (
|
|
70
|
+
r"\bcontext_overflow\b",
|
|
71
|
+
r"\bcontext_too_large\b",
|
|
72
|
+
r"\bcontext_length_exceeded\b",
|
|
73
|
+
r"\bmodel_context_window_exceeded\b",
|
|
74
|
+
)
|
|
75
|
+
]
|
|
76
|
+
|
|
77
|
+
WRAPPER_CONTEXT_ERROR_PATTERNS = [
|
|
78
|
+
re.compile(pattern, re.IGNORECASE)
|
|
79
|
+
for pattern in (
|
|
80
|
+
r"\bPRIZMKIT_FATAL_ERROR\s*=\s*context_overflow\b",
|
|
81
|
+
)
|
|
82
|
+
]
|
|
83
|
+
|
|
84
|
+
# Natural-language provider messages are weak signals: they require an adjacent
|
|
85
|
+
# runtime/API error context unless a structured field already proves the result
|
|
86
|
+
# is an error.
|
|
87
|
+
WEAK_CONTEXT_ERROR_PATTERNS = [
|
|
64
88
|
re.compile(pattern, re.IGNORECASE)
|
|
65
89
|
for pattern in (
|
|
66
|
-
r"context_too_large",
|
|
67
|
-
r"model_context_window_exceeded",
|
|
68
90
|
r"Your input exceeds the context window",
|
|
69
|
-
r"input exceeds the context window",
|
|
91
|
+
r"input exceeds (?:the )?(?:model )?context window",
|
|
70
92
|
r"context window of this model",
|
|
71
|
-
r"context window exceeded",
|
|
72
|
-
r"
|
|
73
|
-
r"context
|
|
93
|
+
r"context window (?:was )?exceeded",
|
|
94
|
+
r"exceeded (?:the )?context window",
|
|
95
|
+
r"maximum context length exceeded",
|
|
96
|
+
r"prompt is too long",
|
|
97
|
+
r"too many tokens",
|
|
98
|
+
r"input token count exceeds",
|
|
99
|
+
r"input tokens? exceeds?",
|
|
100
|
+
r"token count exceeds",
|
|
74
101
|
)
|
|
75
102
|
]
|
|
76
103
|
|
|
104
|
+
REQUEST_TOO_LARGE_PATTERN = re.compile(r"\brequest (?:is )?too large\b", re.IGNORECASE)
|
|
105
|
+
REQUEST_TOO_LARGE_SEMANTIC_PATTERN = re.compile(
|
|
106
|
+
r"\b(context|token|tokens|prompt|input|model)\b",
|
|
107
|
+
re.IGNORECASE,
|
|
108
|
+
)
|
|
109
|
+
|
|
77
110
|
ERROR_CONTEXT_PATTERNS = [
|
|
78
111
|
re.compile(pattern, re.IGNORECASE)
|
|
79
112
|
for pattern in (
|
|
80
113
|
r"\bapi error\b",
|
|
81
114
|
r"invalid_request_error",
|
|
115
|
+
r"\bbad request\b",
|
|
82
116
|
r"\bstatus\s*[:=]?\s*(400|413)\b",
|
|
83
117
|
r"\bapi_error_status\b",
|
|
84
118
|
r"\bapi_error_code\b",
|
|
85
119
|
r"\blast_result_is_error\b\s*[\"':=]*\s*true\b",
|
|
86
120
|
r"\bis_error\b\s*[\"':=]*\s*true\b",
|
|
121
|
+
r"\berror\s*[:=]",
|
|
122
|
+
r"\bfatal\s*[:=]",
|
|
87
123
|
)
|
|
88
124
|
]
|
|
89
125
|
|
|
@@ -95,21 +131,41 @@ def _has_error_context(text):
|
|
|
95
131
|
return any(pattern.search(text) for pattern in ERROR_CONTEXT_PATTERNS)
|
|
96
132
|
|
|
97
133
|
|
|
98
|
-
def
|
|
134
|
+
def _has_context_overflow_signal(text, structured_code=False):
|
|
135
|
+
"""Return true when text contains a context-window/token overflow signal."""
|
|
136
|
+
if not text:
|
|
137
|
+
return False
|
|
138
|
+
if any(pattern.search(text) for pattern in WRAPPER_CONTEXT_ERROR_PATTERNS):
|
|
139
|
+
return True
|
|
140
|
+
if structured_code and any(pattern.search(text) for pattern in CONTEXT_CODE_PATTERNS):
|
|
141
|
+
return True
|
|
142
|
+
if any(pattern.search(text) for pattern in CONTEXT_CODE_PATTERNS):
|
|
143
|
+
return True
|
|
144
|
+
if any(pattern.search(text) for pattern in WEAK_CONTEXT_ERROR_PATTERNS):
|
|
145
|
+
return True
|
|
146
|
+
if REQUEST_TOO_LARGE_PATTERN.search(text) and REQUEST_TOO_LARGE_SEMANTIC_PATTERN.search(text):
|
|
147
|
+
return True
|
|
148
|
+
return False
|
|
149
|
+
|
|
150
|
+
|
|
151
|
+
def detect_api_error_code(text, require_error_context=False, structured_code=False):
|
|
99
152
|
"""Return a normalized fatal/runtime error code from terminal text.
|
|
100
153
|
|
|
101
|
-
Structured
|
|
102
|
-
directly.
|
|
103
|
-
|
|
104
|
-
|
|
154
|
+
Structured error-code fields and explicit wrapper markers can be matched
|
|
155
|
+
directly. Natural-language provider messages require runtime/API context to
|
|
156
|
+
avoid false positives from prompts, tests, or documentation that merely
|
|
157
|
+
mention context windows or token limits.
|
|
105
158
|
"""
|
|
106
159
|
if not text:
|
|
107
160
|
return ""
|
|
161
|
+
if any(pattern.search(text) for pattern in WRAPPER_CONTEXT_ERROR_PATTERNS):
|
|
162
|
+
return CONTEXT_OVERFLOW_CODE
|
|
163
|
+
if structured_code and any(pattern.search(text) for pattern in CONTEXT_CODE_PATTERNS):
|
|
164
|
+
return CONTEXT_OVERFLOW_CODE
|
|
108
165
|
if require_error_context and not _has_error_context(text):
|
|
109
166
|
return ""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
return "context_too_large"
|
|
167
|
+
if _has_context_overflow_signal(text, structured_code=structured_code):
|
|
168
|
+
return CONTEXT_OVERFLOW_CODE
|
|
113
169
|
return ""
|
|
114
170
|
|
|
115
171
|
|
|
@@ -171,6 +227,7 @@ class ProgressTracker:
|
|
|
171
227
|
with sessionId/cwd metadata, function_call for tool invocations).
|
|
172
228
|
"""
|
|
173
229
|
event_type = event.get("type", "")
|
|
230
|
+
self._detect_structured_terminal_fields(event)
|
|
174
231
|
|
|
175
232
|
# ── Codex exec --json JSONL format ──────────────────────────
|
|
176
233
|
if event_type in (
|
|
@@ -491,6 +548,7 @@ class ProgressTracker:
|
|
|
491
548
|
is_error=bool(event.get("is_error")),
|
|
492
549
|
api_error_status=event.get("api_error_status"),
|
|
493
550
|
api_error_code=str(api_error_code or ""),
|
|
551
|
+
stop_reason=str(event.get("stop_reason") or ""),
|
|
494
552
|
)
|
|
495
553
|
|
|
496
554
|
# ── Claude API raw stream format ────────────────────────────
|
|
@@ -579,6 +637,78 @@ class ProgressTracker:
|
|
|
579
637
|
# This is a sub-agent event; tool name is still tracked normally
|
|
580
638
|
pass
|
|
581
639
|
|
|
640
|
+
|
|
641
|
+
def _detect_structured_terminal_fields(self, event):
|
|
642
|
+
"""Detect provider-neutral terminal/context errors before format routing."""
|
|
643
|
+
if not isinstance(event, dict):
|
|
644
|
+
return
|
|
645
|
+
|
|
646
|
+
code_parts = []
|
|
647
|
+
text_parts = []
|
|
648
|
+
terminal_parts = []
|
|
649
|
+
|
|
650
|
+
for key in ("fatal_error_code", "api_error_code", "error_code", "stop_reason"):
|
|
651
|
+
value = event.get(key)
|
|
652
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
653
|
+
code_parts.append(str(value))
|
|
654
|
+
|
|
655
|
+
for key in ("terminal_result_text", "message", "result"):
|
|
656
|
+
value = event.get(key)
|
|
657
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
658
|
+
text_parts.append(str(value))
|
|
659
|
+
terminal_parts.append(str(value))
|
|
660
|
+
|
|
661
|
+
error_obj = event.get("error")
|
|
662
|
+
if isinstance(error_obj, dict):
|
|
663
|
+
for key in ("type", "code", "message"):
|
|
664
|
+
value = error_obj.get(key)
|
|
665
|
+
if isinstance(value, (str, int, float)) and str(value).strip():
|
|
666
|
+
text_parts.append(str(value))
|
|
667
|
+
terminal_parts.append(str(value))
|
|
668
|
+
elif isinstance(error_obj, (str, int, float)) and str(error_obj).strip():
|
|
669
|
+
text_parts.append(str(error_obj))
|
|
670
|
+
terminal_parts.append(str(error_obj))
|
|
671
|
+
|
|
672
|
+
combined_code_text = " ".join(code_parts)
|
|
673
|
+
combined_text = " ".join(code_parts + text_parts)
|
|
674
|
+
if not combined_text:
|
|
675
|
+
return
|
|
676
|
+
|
|
677
|
+
error_like = (
|
|
678
|
+
bool(code_parts)
|
|
679
|
+
or event.get("api_error_status") not in (None, "")
|
|
680
|
+
or bool(event.get("last_result_is_error"))
|
|
681
|
+
or bool(event.get("is_error"))
|
|
682
|
+
or _has_error_context(combined_text)
|
|
683
|
+
)
|
|
684
|
+
normalized_code = detect_api_error_code(
|
|
685
|
+
combined_code_text,
|
|
686
|
+
require_error_context=False,
|
|
687
|
+
structured_code=True,
|
|
688
|
+
) or detect_api_error_code(
|
|
689
|
+
combined_text,
|
|
690
|
+
require_error_context=not error_like,
|
|
691
|
+
structured_code=bool(code_parts),
|
|
692
|
+
)
|
|
693
|
+
if not normalized_code:
|
|
694
|
+
return
|
|
695
|
+
|
|
696
|
+
self.last_result_is_error = True
|
|
697
|
+
api_error_status = event.get("api_error_status") or event.get("status")
|
|
698
|
+
if api_error_status not in (None, ""):
|
|
699
|
+
try:
|
|
700
|
+
self.api_error_status = int(api_error_status)
|
|
701
|
+
except (TypeError, ValueError):
|
|
702
|
+
self.api_error_status = api_error_status
|
|
703
|
+
self.api_error_code = normalized_code
|
|
704
|
+
self.fatal_error_code = normalized_code
|
|
705
|
+
terminal_text = " ".join(terminal_parts) or combined_text
|
|
706
|
+
self.terminal_result_text = terminal_text[:1000]
|
|
707
|
+
if terminal_text.strip():
|
|
708
|
+
self.last_text_snippet = terminal_text.strip()[:120]
|
|
709
|
+
if not self.errors or self.errors[-1] != normalized_code:
|
|
710
|
+
self.errors.append(normalized_code)
|
|
711
|
+
|
|
582
712
|
def _record_cb_agent_tool_call(self, tool_name, raw_args):
|
|
583
713
|
"""Record a CodeBuddy Agent/Task* tool invocation for sub-agent tracking.
|
|
584
714
|
|
|
@@ -616,7 +746,7 @@ class ProgressTracker:
|
|
|
616
746
|
elif tool_name == "TaskCreate":
|
|
617
747
|
self._subagent_spawn_count += 1
|
|
618
748
|
|
|
619
|
-
def _record_terminal_result(self, text="", is_error=False, api_error_status=None, api_error_code=""):
|
|
749
|
+
def _record_terminal_result(self, text="", is_error=False, api_error_status=None, api_error_code="", stop_reason=""):
|
|
620
750
|
"""Record a Claude Code terminal result event."""
|
|
621
751
|
terminal_text = str(text or "")
|
|
622
752
|
self.last_result_is_error = bool(is_error)
|