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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
import os
|
|
4
4
|
import re
|
|
5
5
|
import time
|
|
6
|
+
from argparse import Namespace
|
|
6
7
|
from pathlib import Path
|
|
7
8
|
|
|
8
9
|
from generate_bootstrap_prompt import (
|
|
@@ -18,7 +19,9 @@ from generate_bootstrap_prompt import (
|
|
|
18
19
|
assemble_sections,
|
|
19
20
|
generate_checkpoint_definition,
|
|
20
21
|
merge_checkpoint_state,
|
|
22
|
+
load_log_size_section,
|
|
21
23
|
)
|
|
24
|
+
from continuation import append_continuation_handoff, checkpoint_cursor
|
|
22
25
|
|
|
23
26
|
|
|
24
27
|
def scoped_report_text(verdict="PASS", boundary_missing=0,
|
|
@@ -656,3 +659,103 @@ class TestScopedFeatureTestGate:
|
|
|
656
659
|
merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
|
|
657
660
|
|
|
658
661
|
assert merged["steps"][0]["status"] == "pending"
|
|
662
|
+
|
|
663
|
+
|
|
664
|
+
# ---------------------------------------------------------------------------
|
|
665
|
+
# Active compaction harness rollback
|
|
666
|
+
# ---------------------------------------------------------------------------
|
|
667
|
+
|
|
668
|
+
class TestHeadlessRecoveryReframing:
|
|
669
|
+
def test_log_size_awareness_is_passive_checkpoint_guidance(self):
|
|
670
|
+
content = load_log_size_section(str(Path("dev-pipeline/scripts").resolve()))
|
|
671
|
+
|
|
672
|
+
assert "Session Log Budget Awareness" in content
|
|
673
|
+
assert "passive checkpoint guidance" in content
|
|
674
|
+
assert "durable checkpoints and artifacts" in content
|
|
675
|
+
assert "fresh continuation session" in content
|
|
676
|
+
assert "optional interactive convenience only" in content
|
|
677
|
+
assert "COMPACT_NEEDED" not in content
|
|
678
|
+
assert "monitor-log" not in content
|
|
679
|
+
assert "background monitoring subagent" not in content
|
|
680
|
+
assert "Execute `/compact`" not in content
|
|
681
|
+
assert "run `/compact`" not in content.lower()
|
|
682
|
+
|
|
683
|
+
def test_assembled_prompts_do_not_spawn_log_monitor_or_require_compact(self):
|
|
684
|
+
sections_dir = Path("dev-pipeline/templates/sections").resolve()
|
|
685
|
+
|
|
686
|
+
for mode in ("lite", "standard", "full"):
|
|
687
|
+
rendered = "\n".join(
|
|
688
|
+
section for _name, section in assemble_sections(
|
|
689
|
+
mode, str(sections_dir), init_done=True, is_resume=False,
|
|
690
|
+
critic_enabled=False, browser_enabled=False,
|
|
691
|
+
)
|
|
692
|
+
)
|
|
693
|
+
assert "Headless recovery must rely on durable checkpoints" in rendered
|
|
694
|
+
assert "fresh continuation session" in rendered
|
|
695
|
+
assert "monitor-log" not in rendered
|
|
696
|
+
assert "COMPACT_NEEDED" not in rendered
|
|
697
|
+
assert "log-size monitor" not in rendered
|
|
698
|
+
assert "The log-size monitor is the only allowed" not in rendered
|
|
699
|
+
assert "Run this command and keep it running" not in rendered
|
|
700
|
+
assert "Execute `/compact`" not in rendered
|
|
701
|
+
assert "run `/compact` after" not in rendered
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
# ---------------------------------------------------------------------------
|
|
705
|
+
# continuation handoff
|
|
706
|
+
# ---------------------------------------------------------------------------
|
|
707
|
+
|
|
708
|
+
class TestContinuationHandoff:
|
|
709
|
+
def test_addendum_and_summary_include_required_context(self, tmp_path):
|
|
710
|
+
args = Namespace(
|
|
711
|
+
output=str(tmp_path / ".prizmkit/state/features/F-123/sessions/F-123-new/bootstrap-prompt.md"),
|
|
712
|
+
state_dir=str(tmp_path / ".prizmkit/state/features"),
|
|
713
|
+
continuation_mode="context_overflow",
|
|
714
|
+
previous_session_id="F-123-old",
|
|
715
|
+
continuation_count="2",
|
|
716
|
+
context_overflow_count="2",
|
|
717
|
+
active_dev_branch="dev/F-123",
|
|
718
|
+
base_branch="main",
|
|
719
|
+
continuation_summary_path="",
|
|
720
|
+
)
|
|
721
|
+
checkpoint = {"steps": [
|
|
722
|
+
{"id": "S01", "skill": "test-baseline", "status": "completed"},
|
|
723
|
+
{"id": "S02", "skill": "context-snapshot", "status": "skipped"},
|
|
724
|
+
{"id": "S03", "skill": "prizmkit-implement", "status": "pending"},
|
|
725
|
+
]}
|
|
726
|
+
|
|
727
|
+
rendered = append_continuation_handoff(
|
|
728
|
+
"NORMAL BOOTSTRAP", args, str(tmp_path), "feature", "F-123",
|
|
729
|
+
"123-continuation", checkpoint,
|
|
730
|
+
)
|
|
731
|
+
|
|
732
|
+
assert rendered.startswith("NORMAL BOOTSTRAP")
|
|
733
|
+
assert "## Continuation Addendum" in rendered
|
|
734
|
+
assert "This is an automatic continuation after context overflow." in rendered
|
|
735
|
+
assert "This is a fresh headless AI CLI session for the same task." in rendered
|
|
736
|
+
assert "Continue on the current branch/worktree." in rendered
|
|
737
|
+
assert "Do not reset, clean, switch branches, or discard work." in rendered
|
|
738
|
+
assert "Start with `git status` and `git diff`." in rendered
|
|
739
|
+
assert "Treat completed/skipped checkpoint steps as already done." in rendered
|
|
740
|
+
assert "Do not read the previous full `session.log` unless explicitly necessary." in rendered
|
|
741
|
+
assert "prefer synthesized artifacts and summaries over logs" in rendered
|
|
742
|
+
assert "Do not attempt `/compact` as the recovery mechanism." in rendered
|
|
743
|
+
assert ".prizmkit/specs/123-continuation/workflow-checkpoint.json" in rendered
|
|
744
|
+
assert ".prizmkit/state/features/F-123/sessions/F-123-new/logs/session.log" in rendered
|
|
745
|
+
assert ".prizmkit/state/features/F-123/sessions/F-123-new/logs/progress.json" in rendered
|
|
746
|
+
assert ".prizmkit/state/features/F-123/sessions/F-123-new/logs/ai.pid" in rendered
|
|
747
|
+
|
|
748
|
+
summary = tmp_path / ".prizmkit/specs/123-continuation/continuation-summary.md"
|
|
749
|
+
assert summary.exists()
|
|
750
|
+
summary_text = summary.read_text(encoding="utf-8")
|
|
751
|
+
assert "Previous session id: F-123-old" in summary_text
|
|
752
|
+
assert "First pending/in-progress checkpoint step: S03 (prizmkit-implement)" in summary_text
|
|
753
|
+
assert "Workflow checkpoint: .prizmkit/specs/123-continuation/workflow-checkpoint.json" in summary_text
|
|
754
|
+
|
|
755
|
+
def test_checkpoint_cursor_prefers_in_progress_before_pending(self):
|
|
756
|
+
checkpoint = {"steps": [
|
|
757
|
+
{"id": "S01", "skill": "done", "status": "completed"},
|
|
758
|
+
{"id": "S02", "skill": "active", "status": "in_progress"},
|
|
759
|
+
{"id": "S03", "skill": "later", "status": "pending"},
|
|
760
|
+
]}
|
|
761
|
+
assert checkpoint_cursor(checkpoint)["first_cursor"] == "S02 (active)"
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
"""Tests for generate-bugfix-prompt.py core functions."""
|
|
2
2
|
|
|
3
|
+
from argparse import Namespace
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
3
6
|
from generate_bugfix_prompt import (
|
|
4
7
|
find_bug,
|
|
5
8
|
format_acceptance_criteria,
|
|
@@ -8,7 +11,9 @@ from generate_bugfix_prompt import (
|
|
|
8
11
|
format_environment,
|
|
9
12
|
process_conditional_blocks,
|
|
10
13
|
render_template,
|
|
14
|
+
load_log_size_section,
|
|
11
15
|
)
|
|
16
|
+
from continuation import append_continuation_handoff
|
|
12
17
|
|
|
13
18
|
|
|
14
19
|
# ---------------------------------------------------------------------------
|
|
@@ -166,3 +171,58 @@ class TestSharedHelpers:
|
|
|
166
171
|
def test_format_global_context_dict(self):
|
|
167
172
|
result = format_global_context({"lang": "Python"})
|
|
168
173
|
assert "**lang**: Python" in result
|
|
174
|
+
|
|
175
|
+
|
|
176
|
+
# ---------------------------------------------------------------------------
|
|
177
|
+
# Active compaction harness rollback
|
|
178
|
+
# ---------------------------------------------------------------------------
|
|
179
|
+
|
|
180
|
+
class TestHeadlessRecoveryReframing:
|
|
181
|
+
def test_bugfix_log_size_section_is_passive_checkpoint_guidance(self):
|
|
182
|
+
content = load_log_size_section(str(Path("dev-pipeline/scripts").resolve()))
|
|
183
|
+
|
|
184
|
+
assert "passive checkpoint guidance" in content
|
|
185
|
+
assert "Headless recovery must rely on durable checkpoints" in content
|
|
186
|
+
assert "fresh continuation session" in content
|
|
187
|
+
assert "optional interactive convenience only" in content
|
|
188
|
+
assert "COMPACT_NEEDED" not in content
|
|
189
|
+
assert "monitor-log" not in content
|
|
190
|
+
assert "background monitoring subagent" not in content
|
|
191
|
+
assert "Execute `/compact`" not in content
|
|
192
|
+
assert "run `/compact`" not in content.lower()
|
|
193
|
+
|
|
194
|
+
def test_bugfix_template_does_not_spawn_monitor_or_require_compact(self):
|
|
195
|
+
content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
|
|
196
|
+
|
|
197
|
+
assert "Do not spawn persistent background subagents for headless recovery" in content
|
|
198
|
+
assert "log-size monitor" not in content
|
|
199
|
+
assert "monitor-log" not in content
|
|
200
|
+
assert "COMPACT_NEEDED" not in content
|
|
201
|
+
assert "Run this command and keep it running" not in content
|
|
202
|
+
assert "Execute `/compact`" not in content
|
|
203
|
+
assert "run `/compact` after" not in content
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
class TestBugfixContinuationHandoff:
|
|
207
|
+
def test_writes_specs_and_task_local_summaries(self, tmp_path):
|
|
208
|
+
args = Namespace(
|
|
209
|
+
output=str(tmp_path / ".prizmkit/state/bugfix/B-007/sessions/B-007-new/bootstrap-prompt.md"),
|
|
210
|
+
state_dir=str(tmp_path / ".prizmkit/state/bugfix"),
|
|
211
|
+
continuation_mode="context_overflow",
|
|
212
|
+
previous_session_id="B-007-old",
|
|
213
|
+
continuation_count="1",
|
|
214
|
+
context_overflow_count="1",
|
|
215
|
+
active_dev_branch="bugfix/B-007",
|
|
216
|
+
base_branch="main",
|
|
217
|
+
continuation_summary_path=".prizmkit/specs/B-007/continuation-summary.md",
|
|
218
|
+
)
|
|
219
|
+
prompt = append_continuation_handoff(
|
|
220
|
+
"BUGFIX PROMPT", args, str(tmp_path), "bugfix", "B-007", "B-007",
|
|
221
|
+
{"steps": [{"id": "S01", "skill": "diagnose", "status": "pending"}]},
|
|
222
|
+
)
|
|
223
|
+
|
|
224
|
+
assert prompt.startswith("BUGFIX PROMPT")
|
|
225
|
+
assert "This is an automatic continuation after context overflow." in prompt
|
|
226
|
+
assert "Do not attempt `/compact` as the recovery mechanism." in prompt
|
|
227
|
+
assert (tmp_path / ".prizmkit/specs/B-007/continuation-summary.md").exists()
|
|
228
|
+
assert (tmp_path / ".prizmkit/bugfix/B-007/continuation-summary.md").exists()
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""Tests for generate-refactor-prompt.py continuation behavior."""
|
|
2
|
+
|
|
3
|
+
from argparse import Namespace
|
|
4
|
+
|
|
5
|
+
from continuation import append_continuation_handoff
|
|
6
|
+
from generate_refactor_prompt import generate_refactor_checkpoint, merge_refactor_checkpoint_state
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TestRefactorContinuationHandoff:
|
|
10
|
+
def test_writes_specs_and_task_local_summaries(self, tmp_path):
|
|
11
|
+
args = Namespace(
|
|
12
|
+
output=str(tmp_path / ".prizmkit/state/refactor/R-007/sessions/R-007-new/bootstrap-prompt.md"),
|
|
13
|
+
state_dir=str(tmp_path / ".prizmkit/state/refactor"),
|
|
14
|
+
continuation_mode="context_overflow",
|
|
15
|
+
previous_session_id="R-007-old",
|
|
16
|
+
continuation_count="1",
|
|
17
|
+
context_overflow_count="1",
|
|
18
|
+
active_dev_branch="refactor/R-007",
|
|
19
|
+
base_branch="main",
|
|
20
|
+
continuation_summary_path=".prizmkit/specs/R-007/continuation-summary.md",
|
|
21
|
+
)
|
|
22
|
+
prompt = append_continuation_handoff(
|
|
23
|
+
"REFACTOR PROMPT", args, str(tmp_path), "refactor", "R-007", "R-007",
|
|
24
|
+
{"steps": [{"id": "S01", "skill": "plan", "status": "pending"}]},
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
assert prompt.startswith("REFACTOR PROMPT")
|
|
28
|
+
assert "This is a fresh headless AI CLI session for the same task." in prompt
|
|
29
|
+
assert "Do not attempt `/compact` as the recovery mechanism." in prompt
|
|
30
|
+
assert (tmp_path / ".prizmkit/specs/R-007/continuation-summary.md").exists()
|
|
31
|
+
assert (tmp_path / ".prizmkit/refactor/R-007/continuation-summary.md").exists()
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class TestRefactorCheckpointMerge:
|
|
35
|
+
def test_preserves_skipped_without_artifacts(self, tmp_path):
|
|
36
|
+
fresh = generate_refactor_checkpoint("R-008", "R-008-new")
|
|
37
|
+
existing = generate_refactor_checkpoint("R-008", "R-008-old")
|
|
38
|
+
existing["steps"][0]["status"] = "skipped"
|
|
39
|
+
|
|
40
|
+
merged = merge_refactor_checkpoint_state(existing, fresh, str(tmp_path))
|
|
41
|
+
|
|
42
|
+
assert merged["steps"][0]["status"] == "skipped"
|
|
43
|
+
assert merged["steps"][1]["status"] == "pending"
|
|
@@ -175,19 +175,30 @@ function Test-PrizmInfraError {
|
|
|
175
175
|
return ($haystack -match '(?i)auth_unavailable|no auth available|502 Bad Gateway|503 Service Unavailable|504 Gateway Timeout|gateway timeout|upstream (connect )?error|connection reset|ECONNRESET|ETIMEDOUT|ENOTFOUND|EAI_AGAIN|rate limit|rate_limit|temporarily unavailable|overloaded')
|
|
176
176
|
}
|
|
177
177
|
|
|
178
|
-
function Test-
|
|
178
|
+
function Test-PrizmContextOverflowError {
|
|
179
179
|
param([string]$SessionLog, [string]$ProgressJson)
|
|
180
180
|
|
|
181
|
+
$parts = @()
|
|
182
|
+
$codeParts = @()
|
|
183
|
+
$progressErrorLike = $false
|
|
184
|
+
|
|
181
185
|
if ($ProgressJson -and (Test-Path $ProgressJson)) {
|
|
182
186
|
try {
|
|
183
187
|
$progress = Get-Content $ProgressJson -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
184
|
-
|
|
185
|
-
|
|
188
|
+
foreach ($name in @('fatal_error_code', 'api_error_code', 'error_code', 'stop_reason')) {
|
|
189
|
+
if ($progress.PSObject.Properties[$name] -and $progress.$name) { $codeParts += [string]$progress.$name }
|
|
190
|
+
}
|
|
191
|
+
if (($progress.PSObject.Properties['last_result_is_error'] -and $progress.last_result_is_error) -or ($progress.PSObject.Properties['api_error_status'] -and $progress.api_error_status)) {
|
|
192
|
+
$progressErrorLike = $true
|
|
186
193
|
}
|
|
194
|
+
$parts += ($progress | ConvertTo-Json -Depth 12 -Compress)
|
|
187
195
|
} catch {}
|
|
188
196
|
}
|
|
189
197
|
|
|
190
|
-
$
|
|
198
|
+
$codeText = $codeParts -join ' '
|
|
199
|
+
$codePattern = '(?i)\b(context_overflow|context_too_large|context_length_exceeded|model_context_window_exceeded)\b'
|
|
200
|
+
if ($codeText -match $codePattern) { return $true }
|
|
201
|
+
|
|
191
202
|
if ($SessionLog -and (Test-Path $SessionLog)) {
|
|
192
203
|
try {
|
|
193
204
|
$text = Get-Content $SessionLog -Raw -ErrorAction Stop
|
|
@@ -195,15 +206,166 @@ function Test-PrizmAiRuntimeError {
|
|
|
195
206
|
$parts += $text
|
|
196
207
|
} catch {}
|
|
197
208
|
}
|
|
198
|
-
if ($ProgressJson -and (Test-Path $ProgressJson)) {
|
|
199
|
-
try { $parts += (Get-Content $ProgressJson -Raw -ErrorAction Stop) } catch {}
|
|
200
|
-
}
|
|
201
209
|
if ($parts.Count -eq 0) { return $false }
|
|
202
210
|
|
|
203
211
|
$haystack = $parts -join "`n"
|
|
204
|
-
$
|
|
205
|
-
$
|
|
206
|
-
if (
|
|
212
|
+
$wrapperPattern = '(?i)\bPRIZMKIT_FATAL_ERROR\s*=\s*context_overflow\b'
|
|
213
|
+
if ($haystack -match $wrapperPattern) { return $true }
|
|
214
|
+
if ($haystack -match $codePattern) { return $true }
|
|
215
|
+
|
|
216
|
+
$errorPattern = '(?i)\bapi error\b|invalid_request_error|\bbad request\b|\bstatus\s*[:=]?\s*(400|413)\b|\bapi_error_status\b|\bapi_error_code\b|\blast_result_is_error\b\s*["'':=]*\s*true\b|\bis_error\b\s*["'':=]*\s*true\b|\berror\s*[:=]|\bfatal\s*[:=]'
|
|
217
|
+
$hasErrorContext = $progressErrorLike -or ($haystack -match $errorPattern)
|
|
218
|
+
if (-not $hasErrorContext) { return $false }
|
|
219
|
+
|
|
220
|
+
$weakPattern = '(?i)Your input exceeds the context window|input exceeds (?:the )?(?:model )?context window|context window of this model|context window (?:was )?exceeded|exceeded (?:the )?context window|maximum context length exceeded|prompt is too long|too many tokens|input token count exceeds|input tokens? exceeds?|token count exceeds'
|
|
221
|
+
if ($haystack -match $weakPattern) { return $true }
|
|
222
|
+
|
|
223
|
+
$requestTooLargePattern = '(?i)\brequest (?:is )?too large\b'
|
|
224
|
+
$requestSemanticsPattern = '(?i)\b(context|token|tokens|prompt|input|model)\b'
|
|
225
|
+
if (($haystack -match $requestTooLargePattern) -and ($haystack -match $requestSemanticsPattern)) { return $true }
|
|
226
|
+
|
|
227
|
+
return $false
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
function Test-PrizmAiRuntimeError {
|
|
231
|
+
param([string]$SessionLog, [string]$ProgressJson)
|
|
232
|
+
|
|
233
|
+
if (Test-PrizmContextOverflowError -SessionLog $SessionLog -ProgressJson $ProgressJson) { return $false }
|
|
234
|
+
|
|
235
|
+
if ($ProgressJson -and (Test-Path $ProgressJson)) {
|
|
236
|
+
try {
|
|
237
|
+
$progress = Get-Content $ProgressJson -Raw -ErrorAction Stop | ConvertFrom-Json -ErrorAction Stop
|
|
238
|
+
if ($progress.PSObject.Properties['fatal_error_code'] -and $progress.fatal_error_code) {
|
|
239
|
+
return $true
|
|
240
|
+
}
|
|
241
|
+
} catch {}
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
return $false
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
function Get-PrizmContextProgressFingerprint {
|
|
248
|
+
param(
|
|
249
|
+
[string]$ProjectRoot,
|
|
250
|
+
[string]$TaskType,
|
|
251
|
+
[string]$TaskId,
|
|
252
|
+
[string]$ArtifactDir = '',
|
|
253
|
+
[string]$CheckpointFile = ''
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
$projectRootResolved = if ($ProjectRoot) { $ProjectRoot } else { (Get-Location).Path }
|
|
257
|
+
$artifactArg = if ($ArtifactDir) { $ArtifactDir } else { '' }
|
|
258
|
+
$checkpointArg = if ($CheckpointFile) { $CheckpointFile } else { '' }
|
|
259
|
+
$python = @'
|
|
260
|
+
import hashlib
|
|
261
|
+
import json
|
|
262
|
+
import os
|
|
263
|
+
import subprocess
|
|
264
|
+
import sys
|
|
265
|
+
|
|
266
|
+
project_root, task_type, task_id, artifact_dir, checkpoint_file = sys.argv[1:6]
|
|
267
|
+
project_root = os.path.abspath(project_root or ".")
|
|
268
|
+
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
|
|
269
|
+
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
|
|
270
|
+
|
|
271
|
+
def run_git(args):
|
|
272
|
+
try:
|
|
273
|
+
return subprocess.check_output(["git", "-C", project_root, *args], stderr=subprocess.DEVNULL)
|
|
274
|
+
except Exception:
|
|
275
|
+
return b""
|
|
276
|
+
|
|
277
|
+
def sha_bytes(*parts):
|
|
278
|
+
h = hashlib.sha256()
|
|
279
|
+
for part in parts:
|
|
280
|
+
if isinstance(part, str):
|
|
281
|
+
part = part.encode("utf-8", errors="surrogateescape")
|
|
282
|
+
h.update(part or b"")
|
|
283
|
+
h.update(b"\0")
|
|
284
|
+
return h.hexdigest()
|
|
285
|
+
|
|
286
|
+
def checkpoint_cursor(path):
|
|
287
|
+
if not path or not os.path.exists(path):
|
|
288
|
+
return "missing"
|
|
289
|
+
try:
|
|
290
|
+
with open(path, encoding="utf-8") as fh:
|
|
291
|
+
data = json.load(fh)
|
|
292
|
+
except Exception as exc:
|
|
293
|
+
return "unreadable:{}".format(type(exc).__name__)
|
|
294
|
+
steps = data.get("steps") if isinstance(data, dict) else None
|
|
295
|
+
if not isinstance(steps, list):
|
|
296
|
+
return "no-steps"
|
|
297
|
+
counts = {"completed": 0, "skipped": 0, "pending": 0, "in_progress": 0, "failed": 0}
|
|
298
|
+
first_open = None
|
|
299
|
+
for step in steps:
|
|
300
|
+
if not isinstance(step, dict):
|
|
301
|
+
continue
|
|
302
|
+
status = str(step.get("status", "pending"))
|
|
303
|
+
counts[status] = counts.get(status, 0) + 1
|
|
304
|
+
if first_open is None and status in ("pending", "in_progress"):
|
|
305
|
+
first_open = "{}:{}:{}".format(step.get("id", ""), step.get("skill", ""), status)
|
|
306
|
+
return "first_open={};counts={}".format(first_open or "none", ",".join("{}:{}".format(k, counts[k]) for k in sorted(counts)))
|
|
307
|
+
|
|
308
|
+
def artifact_fingerprint(path):
|
|
309
|
+
if not path or not os.path.isdir(path):
|
|
310
|
+
return "missing"
|
|
311
|
+
h = hashlib.sha256()
|
|
312
|
+
for root, dirs, files in os.walk(path):
|
|
313
|
+
dirs[:] = sorted(d for d in dirs if d not in {".git", "node_modules", "__pycache__"})
|
|
314
|
+
for name in sorted(files):
|
|
315
|
+
file_path = os.path.join(root, name)
|
|
316
|
+
rel = os.path.relpath(file_path, path).replace(os.sep, "/")
|
|
317
|
+
h.update(rel.encode("utf-8", errors="surrogateescape"))
|
|
318
|
+
h.update(b"\0")
|
|
319
|
+
try:
|
|
320
|
+
with open(file_path, "rb") as fh:
|
|
321
|
+
for chunk in iter(lambda: fh.read(65536), b""):
|
|
322
|
+
h.update(chunk)
|
|
323
|
+
except Exception as exc:
|
|
324
|
+
h.update("unreadable:{}".format(type(exc).__name__).encode("utf-8"))
|
|
325
|
+
h.update(b"\0")
|
|
326
|
+
return h.hexdigest()
|
|
327
|
+
|
|
328
|
+
fingerprint = {
|
|
329
|
+
"task_type": task_type,
|
|
330
|
+
"task_id": task_id,
|
|
331
|
+
"git_diff_fingerprint": sha_bytes(run_git(["status", "--porcelain", "--untracked-files=all", "--", "."]), run_git(["diff", "--binary", "HEAD", "--", "."])),
|
|
332
|
+
"checkpoint_cursor": checkpoint_cursor(checkpoint_file),
|
|
333
|
+
"artifact_fingerprint": artifact_fingerprint(artifact_dir),
|
|
334
|
+
"head_commit": run_git(["rev-parse", "HEAD"]).decode("utf-8", errors="ignore").strip(),
|
|
335
|
+
}
|
|
336
|
+
print(json.dumps(fingerprint, sort_keys=True, ensure_ascii=False))
|
|
337
|
+
'@
|
|
338
|
+
$scriptPath = [System.IO.Path]::GetTempFileName()
|
|
339
|
+
try {
|
|
340
|
+
Set-Content -Path $scriptPath -Value $python -Encoding UTF8
|
|
341
|
+
$pythonCmd = Get-Command python -ErrorAction SilentlyContinue
|
|
342
|
+
if ($pythonCmd) {
|
|
343
|
+
$output = & $pythonCmd.Source $scriptPath $projectRootResolved $TaskType $TaskId $artifactArg $checkpointArg
|
|
344
|
+
} else {
|
|
345
|
+
$pyCmd = Get-Command py -ErrorAction SilentlyContinue
|
|
346
|
+
if (-not $pyCmd) { throw 'Python 3 is required for progress fingerprinting.' }
|
|
347
|
+
$output = & $pyCmd.Source -3 $scriptPath $projectRootResolved $TaskType $TaskId $artifactArg $checkpointArg
|
|
348
|
+
}
|
|
349
|
+
if ($LASTEXITCODE -ne 0) { throw 'progress fingerprint python helper failed' }
|
|
350
|
+
return ($output -join "`n")
|
|
351
|
+
} finally {
|
|
352
|
+
Remove-Item -Force $scriptPath -ErrorAction SilentlyContinue
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
function Test-PrizmContextProgressChanged {
|
|
357
|
+
param([string]$BeforeJson, [string]$AfterJson)
|
|
358
|
+
try {
|
|
359
|
+
$before = if ($BeforeJson) { $BeforeJson | ConvertFrom-Json } else { $null }
|
|
360
|
+
$after = if ($AfterJson) { $AfterJson | ConvertFrom-Json } else { $null }
|
|
361
|
+
} catch {
|
|
362
|
+
return $true
|
|
363
|
+
}
|
|
364
|
+
foreach ($key in @('git_diff_fingerprint', 'checkpoint_cursor', 'artifact_fingerprint', 'head_commit')) {
|
|
365
|
+
$beforeValue = if ($before -and $before.PSObject.Properties[$key]) { [string]$before.$key } else { '' }
|
|
366
|
+
$afterValue = if ($after -and $after.PSObject.Properties[$key]) { [string]$after.$key } else { '' }
|
|
367
|
+
if ($beforeValue -ne $afterValue) { return $true }
|
|
368
|
+
}
|
|
207
369
|
return $false
|
|
208
370
|
}
|
|
209
371
|
|