prizmkit 1.1.119 → 1.1.121
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/bin/create-prizmkit.js +1 -5
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +41 -38
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +76 -0
- package/bundled/dev-pipeline/prizmkit_runtime/config.py +2 -8
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/platform_detection.py +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +23 -25
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +8 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +10 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +88 -32
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +28 -6
- package/bundled/dev-pipeline/scripts/continuation.py +51 -6
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +60 -102
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +1 -19
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +1 -19
- package/bundled/dev-pipeline/scripts/{init-dev-team.py → init-change-artifact.py} +6 -16
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +364 -76
- package/bundled/dev-pipeline/scripts/update-bug-status.py +57 -14
- package/bundled/dev-pipeline/scripts/update-feature-status.py +54 -5
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +57 -14
- package/bundled/dev-pipeline/scripts/utils.py +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +9 -11
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +2 -1
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -1
- package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +0 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +6 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +6 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +4 -2
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +3 -3
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +5 -3
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +20 -0
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
- package/bundled/dev-pipeline/tests/test_auto_skip.py +5 -5
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +214 -144
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +25 -12
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +11 -14
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +235 -32
- package/bundled/dev-pipeline/tests/test_unified_cli.py +219 -16
- package/bundled/skills/_metadata.json +4 -4
- package/bundled/skills/app-planner/SKILL.md +2 -2
- package/bundled/skills/app-planner/references/architecture-decisions.md +1 -3
- package/bundled/skills/prizmkit-code-review/SKILL.md +50 -47
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +13 -8
- package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +47 -33
- package/bundled/skills/prizmkit-code-review/references/reviewer-workspace-protocol.md +90 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +21 -1
- package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +67 -0
- package/bundled/skills/prizmkit-test/SKILL.md +159 -155
- package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +420 -0
- package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +116 -0
- package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +112 -0
- package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +97 -0
- package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +76 -192
- package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +65 -0
- package/bundled/skills/prizmkit-test/references/evidence-protocol.md +194 -0
- package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +78 -0
- package/bundled/skills/prizmkit-test/references/examples.md +65 -108
- package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +10 -7
- package/bundled/skills/prizmkit-test/references/test-generation-steps.md +90 -82
- package/bundled/skills/prizmkit-test/references/test-report-template.md +77 -98
- package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +97 -0
- package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +723 -0
- package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +1426 -0
- package/package.json +1 -1
- package/src/clean.js +12 -8
- package/src/config.js +24 -42
- package/src/index.js +1 -10
- package/src/manifest.js +3 -9
- package/src/metadata.js +0 -26
- package/src/prompts.js +0 -13
- package/src/scaffold.js +76 -201
- package/src/upgrade.js +16 -33
- package/bundled/adapters/claude/agent-adapter.js +0 -96
- package/bundled/adapters/claude/team-adapter.js +0 -183
- package/bundled/adapters/codebuddy/agent-adapter.js +0 -42
- package/bundled/adapters/codebuddy/team-adapter.js +0 -46
- package/bundled/adapters/codex/agent-adapter.js +0 -38
- package/bundled/adapters/codex/team-adapter.js +0 -37
- package/bundled/agents/prizm-dev-team-dev.md +0 -123
- package/bundled/agents/prizm-dev-team-reviewer.md +0 -113
- package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +0 -65
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +0 -436
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +0 -510
- package/bundled/skills/prizmkit-test/scripts/validate_boundary_report.py +0 -347
- package/bundled/team/prizm-dev-team.json +0 -27
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Tests for generate-refactor-prompt.py continuation behavior."""
|
|
2
2
|
|
|
3
|
+
import json
|
|
3
4
|
from argparse import Namespace
|
|
4
5
|
from pathlib import Path
|
|
5
6
|
import os
|
|
@@ -20,7 +21,14 @@ class TestRefactorContinuationHandoff:
|
|
|
20
21
|
args = Namespace(
|
|
21
22
|
output=str(tmp_path / ".prizmkit/state/refactor/R-007/sessions/R-007-new/bootstrap-prompt.md"),
|
|
22
23
|
state_dir=str(tmp_path / ".prizmkit/state/refactor"),
|
|
23
|
-
continuation_mode="
|
|
24
|
+
continuation_mode="ai_error",
|
|
25
|
+
failure_classification="ai_runtime_or_provider_error",
|
|
26
|
+
code_retry_count="1",
|
|
27
|
+
infra_error_count="2",
|
|
28
|
+
max_code_retries="3",
|
|
29
|
+
max_infra_retries="3",
|
|
30
|
+
no_progress_count="0",
|
|
31
|
+
progress_fingerprint=json.dumps({"git_diff_fingerprint": "diff", "head_commit": "abc", "checkpoint_cursor": "S03:pending"}),
|
|
24
32
|
previous_session_id="R-007-old",
|
|
25
33
|
continuation_count="1",
|
|
26
34
|
context_overflow_count="1",
|
|
@@ -82,10 +90,7 @@ class TestExplicitProjectRoot:
|
|
|
82
90
|
)
|
|
83
91
|
|
|
84
92
|
assert replacements["{{PROJECT_ROOT}}"] == str(project.resolve())
|
|
85
|
-
assert
|
|
86
|
-
".claude/agents/prizm-dev-team-reviewer.md"
|
|
87
|
-
)
|
|
88
|
-
assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].startswith(str(project.resolve()))
|
|
93
|
+
assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
|
|
89
94
|
assert replacements["{{SESSION_STATUS_PATH}}"] == str(
|
|
90
95
|
project / ".prizmkit" / "state" / "refactor" / "R-001" /
|
|
91
96
|
"sessions" / "session-1" / "session-status.json"
|
|
@@ -126,7 +131,7 @@ class TestExplicitProjectRoot:
|
|
|
126
131
|
"sessions" / "session-1" / "session-status.json"
|
|
127
132
|
)
|
|
128
133
|
assert not replacements["{{SESSION_STATUS_PATH}}"].startswith(str(execution_root.resolve()))
|
|
129
|
-
assert
|
|
134
|
+
assert "{{" + "DEV_" + "SUBAGENT_PATH" + "}}" not in replacements
|
|
130
135
|
assert "### Checkpoint Update Helper" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
131
136
|
assert "{{CHECKPOINT_PYTHON_CMD}}" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
132
137
|
assert "pipeline progress mechanism" not in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
@@ -234,14 +239,6 @@ def _render_refactor_prompt_full(tmp_path, platform="claude"):
|
|
|
234
239
|
(project / ".prizmkit" / "config.json").write_text(
|
|
235
240
|
json.dumps({"platform": platform, "ai_cli": platform}), encoding="utf-8"
|
|
236
241
|
)
|
|
237
|
-
# Ensure agent files
|
|
238
|
-
for plat in ("claude", "codex"):
|
|
239
|
-
platform_dir = ".codex" if plat == "codex" else ".claude"
|
|
240
|
-
suffix = ".toml" if plat == "codex" else ".md"
|
|
241
|
-
agents_dir = project / platform_dir / "agents"
|
|
242
|
-
agents_dir.mkdir(parents=True, exist_ok=True)
|
|
243
|
-
for name in ("dev", "reviewer"):
|
|
244
|
-
(agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
|
|
245
242
|
|
|
246
243
|
refactor_list = project / ".prizmkit" / "plans" / "refactor-list.json"
|
|
247
244
|
refactor_list.parent.mkdir(parents=True)
|
|
@@ -811,8 +811,8 @@ def test_prompt_generation_passes_explicit_execution_root_to_generator(monkeypat
|
|
|
811
811
|
"output_path": str(session_paths.prompt_path),
|
|
812
812
|
"checkpoint_path": str(execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json"),
|
|
813
813
|
"model": "",
|
|
814
|
-
"pipeline_mode": "
|
|
815
|
-
"agent_count":
|
|
814
|
+
"pipeline_mode": "full",
|
|
815
|
+
"agent_count": 3,
|
|
816
816
|
}
|
|
817
817
|
),
|
|
818
818
|
stderr="",
|
|
@@ -876,8 +876,8 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
876
876
|
return PromptGenerationResult(
|
|
877
877
|
output_path=session_paths.prompt_path,
|
|
878
878
|
model="per-item-model",
|
|
879
|
-
pipeline_mode="
|
|
880
|
-
agent_count=
|
|
879
|
+
pipeline_mode="full",
|
|
880
|
+
agent_count=3,
|
|
881
881
|
checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json",
|
|
882
882
|
artifact_path=execution_root / ".prizmkit" / "specs" / "001-low",
|
|
883
883
|
raw={},
|
|
@@ -904,7 +904,20 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
904
904
|
|
|
905
905
|
def fake_update(family, invocation, item_id, session_status, session_id, project_root, **metadata):
|
|
906
906
|
updates.append((session_status, session_id, metadata))
|
|
907
|
-
|
|
907
|
+
current_update_status = update_status
|
|
908
|
+
if session_status in {"context_overflow", "infra_error", "crashed", "timed_out", "failed"} and len(status_queue) > 0:
|
|
909
|
+
current_update_status = "in_progress"
|
|
910
|
+
return SimpleNamespace(
|
|
911
|
+
ok=True,
|
|
912
|
+
text="",
|
|
913
|
+
data={
|
|
914
|
+
"new_status": current_update_status,
|
|
915
|
+
"retry_count": sum(status in {"crashed", "timed_out", "failed"} for status, *_ in updates),
|
|
916
|
+
"infra_error_count": sum(status == "infra_error" for status, *_ in updates),
|
|
917
|
+
"continuation_count": len(updates),
|
|
918
|
+
"context_overflow_count": sum(status == "context_overflow" for status, *_ in updates),
|
|
919
|
+
},
|
|
920
|
+
)
|
|
908
921
|
|
|
909
922
|
def fake_cleanup(runtime, *, delete_branch=True):
|
|
910
923
|
cleanup_calls.append((runtime, delete_branch))
|
|
@@ -1090,15 +1103,14 @@ def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and
|
|
|
1090
1103
|
subprocess.run(["git", "add", ".gitignore", "tracked.txt"], cwd=project, check=True)
|
|
1091
1104
|
subprocess.run(["git", "commit", "-m", "base"], cwd=project, check=True, stdout=subprocess.DEVNULL)
|
|
1092
1105
|
|
|
1093
|
-
(project / ".claude" / "
|
|
1094
|
-
(project / ".claude" / "
|
|
1095
|
-
(project / ".claude" / "team-info.json").write_text("{}", encoding="utf-8")
|
|
1106
|
+
(project / ".claude" / "commands").mkdir(parents=True)
|
|
1107
|
+
(project / ".claude" / "commands" / "prizmkit.md").write_text("skill", encoding="utf-8")
|
|
1096
1108
|
(project / ".prizmkit" / "prizm-docs").mkdir(parents=True)
|
|
1097
1109
|
(project / ".prizmkit" / "prizm-docs" / "root.prizm").write_text("PRIZM_VERSION: 1\n", encoding="utf-8")
|
|
1098
1110
|
(project / ".prizmkit" / "dev-pipeline" / "scripts").mkdir(parents=True)
|
|
1099
|
-
(project / ".prizmkit" / "dev-pipeline" / "scripts" / "init-
|
|
1100
|
-
(project / ".prizmkit" / "config.json").write_text("
|
|
1101
|
-
(project / ".prizmkit" / "manifest.json").write_text("
|
|
1111
|
+
(project / ".prizmkit" / "dev-pipeline" / "scripts" / "init-change-artifact.py").write_text("# init", encoding="utf-8")
|
|
1112
|
+
(project / ".prizmkit" / "config.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
1113
|
+
(project / ".prizmkit" / "manifest.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
1102
1114
|
|
|
1103
1115
|
paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
|
|
1104
1116
|
family = family_for("feature", paths)
|
|
@@ -1128,18 +1140,18 @@ def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and
|
|
|
1128
1140
|
def fake_generate_prompt(family, invocation, **kwargs):
|
|
1129
1141
|
execution_root = kwargs["execution_root"]
|
|
1130
1142
|
assert execution_root != project
|
|
1131
|
-
assert (execution_root / ".claude" / "
|
|
1143
|
+
assert (execution_root / ".claude" / "commands" / "prizmkit.md").is_file()
|
|
1132
1144
|
assert (execution_root / ".prizmkit" / "prizm-docs" / "root.prizm").is_file()
|
|
1133
1145
|
assert (execution_root / ".prizmkit" / "config.json").is_file()
|
|
1134
1146
|
assert (execution_root / ".prizmkit" / "manifest.json").is_file()
|
|
1135
|
-
assert (execution_root / ".prizmkit" / "dev-pipeline" / "scripts" / "init-
|
|
1147
|
+
assert (execution_root / ".prizmkit" / "dev-pipeline" / "scripts" / "init-change-artifact.py").is_file()
|
|
1136
1148
|
assert not (execution_root / ".prizmkit" / "dev-pipeline" / "run-feature.sh").exists()
|
|
1137
1149
|
session_paths = kwargs["session_paths"]
|
|
1138
1150
|
return PromptGenerationResult(
|
|
1139
1151
|
output_path=session_paths.prompt_path,
|
|
1140
1152
|
model="",
|
|
1141
|
-
pipeline_mode="
|
|
1142
|
-
agent_count=
|
|
1153
|
+
pipeline_mode="full",
|
|
1154
|
+
agent_count=3,
|
|
1143
1155
|
checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-support" / "workflow-checkpoint.json",
|
|
1144
1156
|
artifact_path=execution_root / ".prizmkit" / "specs" / "001-support",
|
|
1145
1157
|
raw={},
|
|
@@ -1226,7 +1238,7 @@ def test_has_branch_completion_evidence_requires_task_branch_ahead_of_base(tmp_p
|
|
|
1226
1238
|
subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
|
|
1227
1239
|
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
|
|
1228
1240
|
artifact_dir.mkdir(parents=True)
|
|
1229
|
-
(artifact_dir / "completion-summary.json").write_text("
|
|
1241
|
+
(artifact_dir / "completion-summary.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
1230
1242
|
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
1231
1243
|
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
1232
1244
|
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
@@ -1406,7 +1418,7 @@ def test_default_branch_names_keep_timestamp_for_each_runner_family(monkeypatch,
|
|
|
1406
1418
|
("refactor", "R-001", "refactor/R-001-20260707010203"),
|
|
1407
1419
|
],
|
|
1408
1420
|
)
|
|
1409
|
-
def
|
|
1421
|
+
def test_no_worktree_exhausted_failure_stays_on_task_branch_without_merge_for_all_families(monkeypatch, tmp_path, kind, item_id, branch):
|
|
1410
1422
|
from prizmkit_runtime import runners
|
|
1411
1423
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
1412
1424
|
|
|
@@ -1416,7 +1428,7 @@ def test_no_worktree_failure_stays_on_task_branch_without_merge_for_all_families
|
|
|
1416
1428
|
invocation = parse_invocation(family, "run", ())
|
|
1417
1429
|
plan_path = {"feature": paths.feature_plan, "bugfix": paths.bugfix_plan, "refactor": paths.refactor_plan}[kind]
|
|
1418
1430
|
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": plan_path})
|
|
1419
|
-
_install_runner_session_fakes(monkeypatch, runners, statuses=("crashed",), update_status="
|
|
1431
|
+
_install_runner_session_fakes(monkeypatch, runners, statuses=("crashed",), update_status="failed")
|
|
1420
1432
|
events = []
|
|
1421
1433
|
final_commits = []
|
|
1422
1434
|
wip_commits = []
|
|
@@ -1432,12 +1444,12 @@ def test_no_worktree_failure_stays_on_task_branch_without_merge_for_all_families
|
|
|
1432
1444
|
|
|
1433
1445
|
status = runners._process_item(family, invocation, item_id, paths, initial_metadata={})
|
|
1434
1446
|
|
|
1435
|
-
assert status == "
|
|
1447
|
+
assert status == "failed"
|
|
1436
1448
|
assert events[0] == "branch_create"
|
|
1437
1449
|
assert "branch_merge" not in events
|
|
1438
1450
|
assert events[-1] == ("return", "main", branch)
|
|
1439
|
-
assert final_commits == [(paths.project_root, item_id, "
|
|
1440
|
-
assert wip_commits == [(paths.project_root, item_id, "
|
|
1451
|
+
assert final_commits == [(paths.project_root, item_id, "failed")]
|
|
1452
|
+
assert wip_commits == [(paths.project_root, item_id, "failed")]
|
|
1441
1453
|
|
|
1442
1454
|
|
|
1443
1455
|
def test_no_worktree_runner_ignores_recovery_side_effect_branch(monkeypatch, tmp_path):
|
|
@@ -1535,8 +1547,8 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
|
|
|
1535
1547
|
assert "Feature: F-001 — Readable output" in captured.err
|
|
1536
1548
|
assert "Code retry: 1 / 3" in captured.err
|
|
1537
1549
|
assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
|
|
1538
|
-
assert "Pipeline mode:
|
|
1539
|
-
assert "Agents:
|
|
1550
|
+
assert "Pipeline mode: full (Tier 3 — Orchestrator + Reviewer)" in captured.err
|
|
1551
|
+
assert "Agents: 3" in captured.err
|
|
1540
1552
|
assert "Spawning AI CLI session: F-001-session-1" in captured.err
|
|
1541
1553
|
assert "Session result: success" in captured.err
|
|
1542
1554
|
|
|
@@ -1846,7 +1858,7 @@ def test_bookkeeping_commits_only_runner_owned_paths(tmp_path):
|
|
|
1846
1858
|
item_state.mkdir(parents=True)
|
|
1847
1859
|
plan = plans / "feature-list.json"
|
|
1848
1860
|
plan.write_text('{"features": []}', encoding="utf-8")
|
|
1849
|
-
(item_state / "status.json").write_text("
|
|
1861
|
+
(item_state / "status.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
1850
1862
|
source = tmp_path / "src.txt"
|
|
1851
1863
|
source.write_text("base", encoding="utf-8")
|
|
1852
1864
|
subprocess.run(["git", "add", ".prizmkit/plans/feature-list.json", ".prizmkit/state/features/F-001/status.json", "src.txt"], cwd=tmp_path, check=True)
|
|
@@ -1900,6 +1912,8 @@ def test_context_overflow_reuses_worktree_and_skips_cleanup_until_terminal_outco
|
|
|
1900
1912
|
invocation = parse_invocation(family, "run", ())
|
|
1901
1913
|
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
1902
1914
|
observed = _install_runner_session_fakes(monkeypatch, runners, statuses=("context_overflow", "success"))
|
|
1915
|
+
sleep_calls = []
|
|
1916
|
+
monkeypatch.setattr(runners.time, "sleep", lambda seconds: sleep_calls.append(seconds))
|
|
1903
1917
|
setup_calls = []
|
|
1904
1918
|
monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: (setup_calls.append(runtime) or SimpleNamespace(ok=True, runtime=runtime, reason="")), raising=False)
|
|
1905
1919
|
|
|
@@ -1912,6 +1926,7 @@ def test_context_overflow_reuses_worktree_and_skips_cleanup_until_terminal_outco
|
|
|
1912
1926
|
assert observed.prompt_calls[0].get("continuation") is None
|
|
1913
1927
|
assert observed.prompt_calls[1]["continuation"]["previous_session_id"] == "F-001-session-1"
|
|
1914
1928
|
assert observed.updates[0][0] == "context_overflow"
|
|
1929
|
+
assert sleep_calls == [5]
|
|
1915
1930
|
assert len(observed.cleanup_calls) == 1
|
|
1916
1931
|
assert observed.cleanup_calls[0][1] is True
|
|
1917
1932
|
|
|
@@ -1960,7 +1975,7 @@ def test_classification_accepts_late_infra_error_with_completed_checkpoint_and_c
|
|
|
1960
1975
|
assert classification.reason == "completed_checkpoint_with_late_runtime_error"
|
|
1961
1976
|
|
|
1962
1977
|
|
|
1963
|
-
def
|
|
1978
|
+
def test_classification_accepts_late_infra_with_completion_artifact_without_new_commit(tmp_path):
|
|
1964
1979
|
from prizmkit_runtime.runner_classification import classify_session
|
|
1965
1980
|
from prizmkit_runtime.runner_models import PromptGenerationResult
|
|
1966
1981
|
from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
|
|
@@ -1984,7 +1999,7 @@ def test_classification_rejects_late_infra_when_head_diff_has_no_commit_range(tm
|
|
|
1984
1999
|
subprocess.run(["git", "checkout", "side"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
1985
2000
|
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
|
|
1986
2001
|
artifact_dir.mkdir(parents=True)
|
|
1987
|
-
(artifact_dir / "completion-summary.json").write_text("
|
|
2002
|
+
(artifact_dir / "completion-summary.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
1988
2003
|
log = tmp_path / "session.log"
|
|
1989
2004
|
log.write_text("quota exceeded", encoding="utf-8")
|
|
1990
2005
|
raw = AISessionResult(
|
|
@@ -2005,7 +2020,8 @@ def test_classification_rejects_late_infra_when_head_diff_has_no_commit_range(tm
|
|
|
2005
2020
|
|
|
2006
2021
|
classification = classify_session(raw, project_root=tmp_path, base_head=side_head, prompt=prompt)
|
|
2007
2022
|
|
|
2008
|
-
assert classification.session_status == "
|
|
2023
|
+
assert classification.session_status == "success"
|
|
2024
|
+
assert classification.reason == "completed_checkpoint_with_late_runtime_error"
|
|
2009
2025
|
|
|
2010
2026
|
|
|
2011
2027
|
def test_classification_keeps_pre_completion_provider_error_as_infra_error(tmp_path):
|
|
@@ -2061,7 +2077,7 @@ def test_classification_accepts_terminal_success_with_completed_checkpoint_witho
|
|
|
2061
2077
|
artifact_dir.mkdir(parents=True)
|
|
2062
2078
|
checkpoint = artifact_dir / "workflow-checkpoint.json"
|
|
2063
2079
|
checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
|
|
2064
|
-
(artifact_dir / "completion-summary.json").write_text("
|
|
2080
|
+
(artifact_dir / "completion-summary.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
2065
2081
|
log = tmp_path / "session.log"
|
|
2066
2082
|
log.write_text("terminal success", encoding="utf-8")
|
|
2067
2083
|
raw = AISessionResult(
|
|
@@ -2123,8 +2139,8 @@ def test_classification_preserves_success_before_context_overflow(tmp_path):
|
|
|
2123
2139
|
)
|
|
2124
2140
|
|
|
2125
2141
|
classification = classify_session(raw, project_root=tmp_path, base_head=base_head)
|
|
2126
|
-
assert classification.session_status == "
|
|
2127
|
-
assert classification.reason == "
|
|
2142
|
+
assert classification.session_status == "context_overflow"
|
|
2143
|
+
assert classification.reason == "context_overflow"
|
|
2128
2144
|
|
|
2129
2145
|
|
|
2130
2146
|
def test_classification_crash_with_commit_since_base_does_not_become_success(tmp_path):
|
|
@@ -2257,7 +2273,7 @@ def _write_family_case(paths, kind, item_id=""):
|
|
|
2257
2273
|
state_dir = paths.refactor_state_dir
|
|
2258
2274
|
plan = paths.refactor_plan
|
|
2259
2275
|
state_dir.mkdir(parents=True, exist_ok=True)
|
|
2260
|
-
(state_dir / "pipeline.json").write_text("
|
|
2276
|
+
(state_dir / "pipeline.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
2261
2277
|
item_state = state_dir / item_id
|
|
2262
2278
|
item_state.mkdir(parents=True, exist_ok=True)
|
|
2263
2279
|
(item_state / "status.json").write_text(
|
|
@@ -2518,7 +2534,7 @@ def test_invalid_existing_checkpoint_blocks_terminal_success_with_completion_sum
|
|
|
2518
2534
|
artifact_dir.mkdir(parents=True)
|
|
2519
2535
|
checkpoint = artifact_dir / "workflow-checkpoint.json"
|
|
2520
2536
|
checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "not-valid"}]}), encoding="utf-8")
|
|
2521
|
-
(artifact_dir / "completion-summary.json").write_text("
|
|
2537
|
+
(artifact_dir / "completion-summary.json").write_text(json.dumps({"completion_notes": ["Completed durable workflow"]}), encoding="utf-8")
|
|
2522
2538
|
prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
|
|
2523
2539
|
|
|
2524
2540
|
classification = classify_session(_terminal_success_result(tmp_path), project_root=tmp_path, base_head=base_head, prompt=prompt)
|
|
@@ -2598,3 +2614,190 @@ def test_invalid_existing_checkpoint_blocks_workspace_auto_commit_success(tmp_pa
|
|
|
2598
2614
|
|
|
2599
2615
|
assert classification.session_status == "crashed"
|
|
2600
2616
|
assert classification.reason == "invalid_workflow_checkpoint:empty_steps"
|
|
2617
|
+
|
|
2618
|
+
|
|
2619
|
+
@pytest.mark.parametrize(
|
|
2620
|
+
("kind", "item_id", "branch"),
|
|
2621
|
+
[
|
|
2622
|
+
("feature", "F-001", "dev/F-001-ai-retry"),
|
|
2623
|
+
("bugfix", "B-001", "bugfix/B-001-ai-retry"),
|
|
2624
|
+
("refactor", "R-001", "refactor/R-001-ai-retry"),
|
|
2625
|
+
],
|
|
2626
|
+
)
|
|
2627
|
+
@pytest.mark.parametrize("retry_status", ["infra_error", "crashed", "timed_out"])
|
|
2628
|
+
def test_retryable_ai_errors_continue_in_process_for_all_families(monkeypatch, tmp_path, kind, item_id, branch, retry_status):
|
|
2629
|
+
from prizmkit_runtime import runners
|
|
2630
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
2631
|
+
|
|
2632
|
+
monkeypatch.setenv("DEV_BRANCH", branch)
|
|
2633
|
+
paths = _make_paths(tmp_path / kind / retry_status)
|
|
2634
|
+
family = family_for(kind, paths)
|
|
2635
|
+
invocation = parse_invocation(family, "run", ())
|
|
2636
|
+
plan_path = {"feature": paths.feature_plan, "bugfix": paths.bugfix_plan, "refactor": paths.refactor_plan}[kind]
|
|
2637
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": plan_path})
|
|
2638
|
+
observed = _install_runner_session_fakes(monkeypatch, runners, statuses=(retry_status, "success"))
|
|
2639
|
+
sleep_calls = []
|
|
2640
|
+
branch_plans = []
|
|
2641
|
+
monkeypatch.setattr(runners.time, "sleep", lambda seconds: sleep_calls.append(seconds))
|
|
2642
|
+
monkeypatch.setattr(
|
|
2643
|
+
runners,
|
|
2644
|
+
"run_git_plan",
|
|
2645
|
+
lambda project_root, plan: (branch_plans.append(plan.name) or SimpleNamespace(ok=True, plan=plan, results=())),
|
|
2646
|
+
)
|
|
2647
|
+
|
|
2648
|
+
status = runners._process_item(family, invocation, item_id, paths, initial_metadata={})
|
|
2649
|
+
|
|
2650
|
+
assert status == "completed"
|
|
2651
|
+
assert sleep_calls == [5]
|
|
2652
|
+
assert len(observed.launch_cwds) == 2
|
|
2653
|
+
assert observed.launch_cwds[0] == observed.launch_cwds[1]
|
|
2654
|
+
assert branch_plans.count("branch_create") == 1
|
|
2655
|
+
assert observed.updates[0][0] == retry_status
|
|
2656
|
+
continuation = observed.prompt_calls[1]["continuation"]
|
|
2657
|
+
assert continuation["previous_session_id"].endswith("session-1")
|
|
2658
|
+
assert continuation["failure_classification"] == retry_status
|
|
2659
|
+
assert continuation["active_dev_branch"] == branch
|
|
2660
|
+
|
|
2661
|
+
|
|
2662
|
+
def test_unique_session_ids_do_not_collide_when_wall_clock_second_is_frozen(monkeypatch):
|
|
2663
|
+
from prizmkit_runtime import runners
|
|
2664
|
+
|
|
2665
|
+
monkeypatch.setattr(runners.time, "strftime", lambda _fmt: "20260711030000")
|
|
2666
|
+
values = iter((101, 102))
|
|
2667
|
+
monkeypatch.setattr(runners.time, "time_ns", lambda: next(values))
|
|
2668
|
+
|
|
2669
|
+
first = runners._session_id("F-001")
|
|
2670
|
+
second = runners._session_id("F-001")
|
|
2671
|
+
|
|
2672
|
+
assert first != second
|
|
2673
|
+
assert first.endswith("-101")
|
|
2674
|
+
assert second.endswith("-102")
|
|
2675
|
+
|
|
2676
|
+
|
|
2677
|
+
@pytest.mark.parametrize("summary_text", ["{}", "{not-json", '{"completion_notes": []}', '{"completion_notes": [""]}'])
|
|
2678
|
+
def test_invalid_completion_summary_does_not_prove_late_runtime_success(tmp_path, summary_text):
|
|
2679
|
+
from prizmkit_runtime.runner_classification import classify_session
|
|
2680
|
+
from prizmkit_runtime.runner_models import PromptGenerationResult
|
|
2681
|
+
from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
|
|
2682
|
+
from prizmkit_runtime.status import ProgressSummary
|
|
2683
|
+
|
|
2684
|
+
subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2685
|
+
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
|
|
2686
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
|
|
2687
|
+
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
2688
|
+
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
2689
|
+
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2690
|
+
base_head = subprocess.run(["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip()
|
|
2691
|
+
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
|
|
2692
|
+
artifact_dir.mkdir(parents=True)
|
|
2693
|
+
(artifact_dir / "completion-summary.json").write_text(summary_text, encoding="utf-8")
|
|
2694
|
+
log = tmp_path / "session.log"
|
|
2695
|
+
log.write_text("quota exceeded", encoding="utf-8")
|
|
2696
|
+
result = AISessionResult(
|
|
2697
|
+
command=AISessionCommand(("fake",), None, "codebuddy", "stdin"),
|
|
2698
|
+
exit_code=1,
|
|
2699
|
+
timed_out=False,
|
|
2700
|
+
termination_reason=None,
|
|
2701
|
+
stale_marker=None,
|
|
2702
|
+
fatal_error_code="api_error",
|
|
2703
|
+
progress_summary=ProgressSummary(terminal_result_text="quota exceeded"),
|
|
2704
|
+
session_log=log,
|
|
2705
|
+
backup_log=tmp_path / "backup.log",
|
|
2706
|
+
log_recovery=None,
|
|
2707
|
+
started_epoch=0,
|
|
2708
|
+
ended_epoch=1,
|
|
2709
|
+
)
|
|
2710
|
+
|
|
2711
|
+
classification = classify_session(
|
|
2712
|
+
result,
|
|
2713
|
+
project_root=tmp_path,
|
|
2714
|
+
base_head=base_head,
|
|
2715
|
+
prompt=PromptGenerationResult(output_path=tmp_path / "prompt.md", artifact_path=artifact_dir),
|
|
2716
|
+
)
|
|
2717
|
+
|
|
2718
|
+
assert classification.session_status == "infra_error"
|
|
2719
|
+
|
|
2720
|
+
|
|
2721
|
+
def test_non_utf8_completion_summary_does_not_abort_classification(tmp_path):
|
|
2722
|
+
from prizmkit_runtime.runner_classification import classify_session
|
|
2723
|
+
from prizmkit_runtime.runner_models import PromptGenerationResult
|
|
2724
|
+
from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
|
|
2725
|
+
from prizmkit_runtime.status import ProgressSummary
|
|
2726
|
+
|
|
2727
|
+
subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2728
|
+
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
|
|
2729
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
|
|
2730
|
+
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
2731
|
+
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
2732
|
+
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2733
|
+
base_head = subprocess.run(["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip()
|
|
2734
|
+
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-corrupt"
|
|
2735
|
+
artifact_dir.mkdir(parents=True)
|
|
2736
|
+
(artifact_dir / "completion-summary.json").write_bytes(b"\xff\xfe")
|
|
2737
|
+
log = tmp_path / "session.log"
|
|
2738
|
+
log.write_text("quota exceeded", encoding="utf-8")
|
|
2739
|
+
result = AISessionResult(
|
|
2740
|
+
command=AISessionCommand(("fake",), None, "codebuddy", "stdin"),
|
|
2741
|
+
exit_code=1,
|
|
2742
|
+
timed_out=False,
|
|
2743
|
+
termination_reason=None,
|
|
2744
|
+
stale_marker=None,
|
|
2745
|
+
fatal_error_code="api_error",
|
|
2746
|
+
progress_summary=ProgressSummary(terminal_result_text="quota exceeded"),
|
|
2747
|
+
session_log=log,
|
|
2748
|
+
backup_log=tmp_path / "backup.log",
|
|
2749
|
+
log_recovery=None,
|
|
2750
|
+
started_epoch=0,
|
|
2751
|
+
ended_epoch=1,
|
|
2752
|
+
)
|
|
2753
|
+
|
|
2754
|
+
classification = classify_session(
|
|
2755
|
+
result,
|
|
2756
|
+
project_root=tmp_path,
|
|
2757
|
+
base_head=base_head,
|
|
2758
|
+
prompt=PromptGenerationResult(output_path=tmp_path / "prompt.md", artifact_path=artifact_dir),
|
|
2759
|
+
)
|
|
2760
|
+
|
|
2761
|
+
assert classification.session_status == "infra_error"
|
|
2762
|
+
|
|
2763
|
+
|
|
2764
|
+
@pytest.mark.parametrize(
|
|
2765
|
+
("timed_out", "termination_reason", "expected_status", "expected_reason"),
|
|
2766
|
+
[
|
|
2767
|
+
(True, "timeout", "timed_out", "timeout"),
|
|
2768
|
+
(False, "stale_session", "crashed", "stale_session"),
|
|
2769
|
+
],
|
|
2770
|
+
)
|
|
2771
|
+
def test_timeout_and_stale_termination_override_provider_error_markers(tmp_path, timed_out, termination_reason, expected_status, expected_reason):
|
|
2772
|
+
from prizmkit_runtime.runner_classification import classify_session
|
|
2773
|
+
from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
|
|
2774
|
+
from prizmkit_runtime.status import ProgressSummary
|
|
2775
|
+
|
|
2776
|
+
subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2777
|
+
subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
|
|
2778
|
+
subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
|
|
2779
|
+
(tmp_path / "base.txt").write_text("base", encoding="utf-8")
|
|
2780
|
+
subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
|
|
2781
|
+
subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2782
|
+
base_head = subprocess.run(["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip()
|
|
2783
|
+
log = tmp_path / "session.log"
|
|
2784
|
+
log.write_text("gateway API error", encoding="utf-8")
|
|
2785
|
+
result = AISessionResult(
|
|
2786
|
+
command=AISessionCommand(("fake",), None, "codebuddy", "stdin"),
|
|
2787
|
+
exit_code=1,
|
|
2788
|
+
timed_out=timed_out,
|
|
2789
|
+
termination_reason=termination_reason,
|
|
2790
|
+
stale_marker=None,
|
|
2791
|
+
fatal_error_code="api_error",
|
|
2792
|
+
progress_summary=ProgressSummary(terminal_result_text="provider gateway error"),
|
|
2793
|
+
session_log=log,
|
|
2794
|
+
backup_log=tmp_path / "backup.log",
|
|
2795
|
+
log_recovery=None,
|
|
2796
|
+
started_epoch=0,
|
|
2797
|
+
ended_epoch=1,
|
|
2798
|
+
)
|
|
2799
|
+
|
|
2800
|
+
classification = classify_session(result, project_root=tmp_path, base_head=base_head)
|
|
2801
|
+
|
|
2802
|
+
assert classification.session_status == expected_status
|
|
2803
|
+
assert classification.reason == expected_reason
|