prizmkit 1.1.128 → 1.1.130
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/prizmkit_runtime/gitops.py +88 -58
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +4 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +53 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +33 -17
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +30 -28
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +8 -3
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +3 -2
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +11 -4
- package/bundled/dev-pipeline/scripts/parse-stream-progress.py +3 -3
- package/bundled/dev-pipeline/scripts/prompt_framework.py +4 -6
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +20 -19
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +10 -2
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
- package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +3 -2
- package/bundled/dev-pipeline/templates/sections/directory-convention-full.md +3 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +11 -3
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +11 -3
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-mission.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +11 -3
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
- package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +6 -4
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +192 -5
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +139 -11
- package/bundled/dev-pipeline/tests/test_unified_cli.py +215 -9
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/feature-planner/SKILL.md +2 -2
- package/bundled/skills/prizmkit/SKILL.md +1 -1
- package/bundled/skills/prizmkit-code-review/SKILL.md +10 -1
- package/package.json +1 -1
- package/src/manifest.js +8 -3
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"""Tests for generate-bootstrap-prompt.py core functions."""
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
|
+
import importlib.util
|
|
4
5
|
import os
|
|
5
6
|
import re
|
|
6
7
|
import time
|
|
@@ -25,6 +26,7 @@ from generate_bootstrap_prompt import (
|
|
|
25
26
|
generate_checkpoint_definition,
|
|
26
27
|
merge_checkpoint_state,
|
|
27
28
|
load_active_agent_prompts,
|
|
29
|
+
validate_rendered,
|
|
28
30
|
main as generate_bootstrap_main,
|
|
29
31
|
)
|
|
30
32
|
from continuation import append_continuation_handoff, checkpoint_cursor
|
|
@@ -44,6 +46,69 @@ FORBIDDEN_TEST_CMD_PLACEHOLDER = "(" + "$TEST_CMD" + ")"
|
|
|
44
46
|
FORBIDDEN_THREE_STRIKE = "3-" + "strike"
|
|
45
47
|
FORBIDDEN_TWENTY_STEP = "20-" + "step window"
|
|
46
48
|
|
|
49
|
+
REVIEW_PROMPT_REQUIRED = (
|
|
50
|
+
"The current Main Agent is the only Code Review executor",
|
|
51
|
+
"Do not invoke another review skill or review workflow",
|
|
52
|
+
"finder, verifier, audit, compatibility review, verification, or gap sweep",
|
|
53
|
+
"`review-report.md` is the only persisted review artifact",
|
|
54
|
+
)
|
|
55
|
+
|
|
56
|
+
REVIEW_PROMPT_FORBIDDEN = (
|
|
57
|
+
"Orchestrator + Reviewer",
|
|
58
|
+
"Reviewer reviews",
|
|
59
|
+
"Reviewer subagents",
|
|
60
|
+
"Reviewer appends Review Notes",
|
|
61
|
+
"Implementation Log/Review Notes",
|
|
62
|
+
"internal Reviewer Agent",
|
|
63
|
+
"spawning Reviewer",
|
|
64
|
+
"delayed teammate/reviewer findings",
|
|
65
|
+
"Gate checks enforce Implementation Log and Review Notes",
|
|
66
|
+
)
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def assert_unambiguous_main_agent_review(prompt):
|
|
70
|
+
for required in REVIEW_PROMPT_REQUIRED:
|
|
71
|
+
assert required in prompt
|
|
72
|
+
for forbidden in REVIEW_PROMPT_FORBIDDEN:
|
|
73
|
+
assert forbidden not in prompt
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
PROMPT_RENDERING_FORBIDDEN = (
|
|
77
|
+
"If implementation is delegated to a Dev subagent",
|
|
78
|
+
"Document in Implementation Log or review notes",
|
|
79
|
+
"{{INIT_SCRIPT_PATH}}",
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def assert_complete_prompt_rendering(prompt):
|
|
84
|
+
assert_unambiguous_main_agent_review(prompt)
|
|
85
|
+
for forbidden in PROMPT_RENDERING_FORBIDDEN:
|
|
86
|
+
assert forbidden not in prompt
|
|
87
|
+
assert re.findall(r"\{\{[A-Z][A-Z_0-9]+\}\}", prompt) == []
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
def _load_recovery_prompt_module():
|
|
91
|
+
path = Path("dev-pipeline/scripts/generate-recovery-prompt.py")
|
|
92
|
+
spec = importlib.util.spec_from_file_location("generate_recovery_prompt", path)
|
|
93
|
+
module = importlib.util.module_from_spec(spec)
|
|
94
|
+
spec.loader.exec_module(module)
|
|
95
|
+
return module
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_rendered_prompt_validation_rejects_unresolved_control_placeholders():
|
|
99
|
+
content = (
|
|
100
|
+
"## Your Mission\n"
|
|
101
|
+
"## Execution\n"
|
|
102
|
+
"Run {{INIT_SCRIPT_PATH}}\n"
|
|
103
|
+
"## Failure Capture\n"
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
is_valid, warnings, errors = validate_rendered(content)
|
|
107
|
+
|
|
108
|
+
assert is_valid is False
|
|
109
|
+
assert warnings == []
|
|
110
|
+
assert errors == ["Unreplaced placeholders: {{INIT_SCRIPT_PATH}}"]
|
|
111
|
+
|
|
47
112
|
|
|
48
113
|
def scoped_report_text(verdict="PASS", boundary_missing=0,
|
|
49
114
|
happy_path_only=0, completion_gate="yes",
|
|
@@ -884,10 +949,38 @@ class TestDirectOrchestratorImplementationPrompts:
|
|
|
884
949
|
assert FORBIDDEN_TWENTY_STEP not in rendered
|
|
885
950
|
assert "Main-Agent review loop" in rendered
|
|
886
951
|
assert "up to ten completed rounds" in rendered
|
|
887
|
-
assert "
|
|
952
|
+
assert "The current Main Agent is the only Code Review executor" in rendered
|
|
953
|
+
assert "Do not invoke another review skill or review workflow" in rendered
|
|
888
954
|
assert "low=0" not in rendered
|
|
889
955
|
assert "Reviewer 3" not in rendered
|
|
890
956
|
|
|
957
|
+
def test_all_rendered_pipeline_prompts_have_one_main_agent_review_model(self, tmp_path):
|
|
958
|
+
recovery = _load_recovery_prompt_module()
|
|
959
|
+
prompts = [
|
|
960
|
+
_render_feature_prompt(tmp_path / "feature-lite", mode="lite"),
|
|
961
|
+
_render_feature_prompt(tmp_path / "feature-standard", mode="standard"),
|
|
962
|
+
_render_feature_prompt(tmp_path / "feature-full", mode="full"),
|
|
963
|
+
_render_feature_prompt(
|
|
964
|
+
tmp_path / "feature-tier3",
|
|
965
|
+
mode="full",
|
|
966
|
+
template="bootstrap-tier3.md",
|
|
967
|
+
),
|
|
968
|
+
_render_bugfix_prompt(tmp_path / "bugfix"),
|
|
969
|
+
_render_refactor_prompt(tmp_path / "refactor"),
|
|
970
|
+
recovery.build_bugfix_prompt(
|
|
971
|
+
{
|
|
972
|
+
"workflow_type": "bug-fix-workflow",
|
|
973
|
+
"phase": 5,
|
|
974
|
+
"phase_name": "Review",
|
|
975
|
+
"workflow_data": {"bug_id": "B-001"},
|
|
976
|
+
},
|
|
977
|
+
str(tmp_path),
|
|
978
|
+
),
|
|
979
|
+
]
|
|
980
|
+
|
|
981
|
+
for prompt in prompts:
|
|
982
|
+
assert_complete_prompt_rendering(prompt)
|
|
983
|
+
|
|
891
984
|
def test_retained_tier3_fallback_template_uses_direct_orchestrator_implementation(self):
|
|
892
985
|
rendered = Path("dev-pipeline/templates/bootstrap-tier3.md").read_text(encoding="utf-8")
|
|
893
986
|
|
|
@@ -1217,7 +1310,7 @@ class TestFeaturePromptUnifiedFullGuidance:
|
|
|
1217
1310
|
assert "### Scoped Feature Test Gate — PrizmKit Test" in prompt
|
|
1218
1311
|
assert "## Test Failure Recovery Protocol" in prompt
|
|
1219
1312
|
assert "Boundary Matrix" in prompt
|
|
1220
|
-
assert "### Browser Verification — MANDATORY" in prompt
|
|
1313
|
+
assert "### Browser Verification — MANDATORY" not in prompt
|
|
1221
1314
|
assert "### TERMINAL STOP — Commit Completed" in prompt
|
|
1222
1315
|
assert "# Dev-Pipeline Session Bootstrap — Tier 1" not in prompt
|
|
1223
1316
|
assert "# Dev-Pipeline Session Bootstrap — Tier 2" not in prompt
|
|
@@ -1327,7 +1420,99 @@ class TestBrowserBlockingPromptPolicy:
|
|
|
1327
1420
|
assert "Failures do NOT block the commit" not in prompt
|
|
1328
1421
|
|
|
1329
1422
|
|
|
1330
|
-
class
|
|
1423
|
+
class TestBrowserOptInPromptPolicy:
|
|
1424
|
+
@staticmethod
|
|
1425
|
+
def _rendered_outputs(tmp_path, browser_interaction=None, env_value=None):
|
|
1426
|
+
previous_env = os.environ.get("BROWSER_VERIFY")
|
|
1427
|
+
if env_value is None:
|
|
1428
|
+
os.environ.pop("BROWSER_VERIFY", None)
|
|
1429
|
+
else:
|
|
1430
|
+
os.environ["BROWSER_VERIFY"] = env_value
|
|
1431
|
+
try:
|
|
1432
|
+
feature_prompt = _render_feature_prompt(
|
|
1433
|
+
tmp_path / "feature", browser_interaction=browser_interaction,
|
|
1434
|
+
)
|
|
1435
|
+
bugfix_prompt = _render_bugfix_prompt(
|
|
1436
|
+
tmp_path / "bugfix",
|
|
1437
|
+
bug_overrides=(
|
|
1438
|
+
{"browser_interaction": browser_interaction}
|
|
1439
|
+
if browser_interaction is not None else None
|
|
1440
|
+
),
|
|
1441
|
+
)
|
|
1442
|
+
refactor_prompt = _render_refactor_prompt(
|
|
1443
|
+
tmp_path / "refactor",
|
|
1444
|
+
refactor_overrides=(
|
|
1445
|
+
{"browser_interaction": browser_interaction}
|
|
1446
|
+
if browser_interaction is not None else None
|
|
1447
|
+
),
|
|
1448
|
+
)
|
|
1449
|
+
return feature_prompt, bugfix_prompt, refactor_prompt
|
|
1450
|
+
finally:
|
|
1451
|
+
if previous_env is None:
|
|
1452
|
+
os.environ.pop("BROWSER_VERIFY", None)
|
|
1453
|
+
else:
|
|
1454
|
+
os.environ["BROWSER_VERIFY"] = previous_env
|
|
1455
|
+
|
|
1456
|
+
@staticmethod
|
|
1457
|
+
def _checkpoint_skills(project_root, family):
|
|
1458
|
+
if family == "feature":
|
|
1459
|
+
pattern = "**/workflow-checkpoint.json"
|
|
1460
|
+
elif family == "bugfix":
|
|
1461
|
+
pattern = "**/bugfix/B-001/workflow-checkpoint.json"
|
|
1462
|
+
else:
|
|
1463
|
+
pattern = "**/refactor/R-001/workflow-checkpoint.json"
|
|
1464
|
+
path = next(project_root.glob(pattern))
|
|
1465
|
+
return [step["skill"] for step in json.loads(path.read_text(encoding="utf-8"))["steps"]]
|
|
1466
|
+
|
|
1467
|
+
def test_omitted_browser_interaction_disables_browser_for_all_families(self, tmp_path):
|
|
1468
|
+
prompts = self._rendered_outputs(tmp_path / "omitted")
|
|
1469
|
+
|
|
1470
|
+
for prompt in prompts:
|
|
1471
|
+
assert "Browser Verification" not in prompt
|
|
1472
|
+
assert "playwright-cli" not in prompt
|
|
1473
|
+
assert "opencli" not in prompt
|
|
1474
|
+
assert "development-server" not in prompt
|
|
1475
|
+
assert "browser-dev-server.pid" not in prompt
|
|
1476
|
+
|
|
1477
|
+
assert "browser-verification" not in self._checkpoint_skills(tmp_path / "omitted" / "feature", "feature")
|
|
1478
|
+
assert "browser-verification" not in self._checkpoint_skills(tmp_path / "omitted" / "bugfix", "bugfix")
|
|
1479
|
+
assert "browser-verification" not in self._checkpoint_skills(tmp_path / "omitted" / "refactor", "refactor")
|
|
1480
|
+
|
|
1481
|
+
def test_explicit_browser_interaction_preserves_configuration_for_all_families(self, tmp_path):
|
|
1482
|
+
browser_interaction = {
|
|
1483
|
+
"tool": "playwright-cli",
|
|
1484
|
+
"verify_steps": ["Open the page", "Submit the form"],
|
|
1485
|
+
}
|
|
1486
|
+
prompts = self._rendered_outputs(tmp_path / "explicit", browser_interaction)
|
|
1487
|
+
|
|
1488
|
+
for prompt in prompts:
|
|
1489
|
+
assert "Browser Verification" in prompt
|
|
1490
|
+
assert "Browser Verification (playwright-cli)" in prompt
|
|
1491
|
+
assert "playwright-cli" in prompt
|
|
1492
|
+
assert "opencli" not in prompt
|
|
1493
|
+
assert "Goal 1: Open the page" in prompt
|
|
1494
|
+
assert "Goal 2: Submit the form" in prompt
|
|
1495
|
+
|
|
1496
|
+
assert "browser-verification" in self._checkpoint_skills(tmp_path / "explicit" / "feature", "feature")
|
|
1497
|
+
assert "browser-verification" in self._checkpoint_skills(tmp_path / "explicit" / "bugfix", "bugfix")
|
|
1498
|
+
assert "browser-verification" in self._checkpoint_skills(tmp_path / "explicit" / "refactor", "refactor")
|
|
1499
|
+
|
|
1500
|
+
def test_global_disable_suppresses_explicit_browser_for_all_families(self, tmp_path):
|
|
1501
|
+
prompts = self._rendered_outputs(
|
|
1502
|
+
tmp_path / "global-disable",
|
|
1503
|
+
{"tool": "opencli", "verify_steps": ["Open the page"]},
|
|
1504
|
+
env_value="false",
|
|
1505
|
+
)
|
|
1506
|
+
|
|
1507
|
+
for prompt in prompts:
|
|
1508
|
+
assert "Browser Verification" not in prompt
|
|
1509
|
+
assert "opencli" not in prompt
|
|
1510
|
+
assert "browser-dev-server.pid" not in prompt
|
|
1511
|
+
|
|
1512
|
+
assert "browser-verification" not in self._checkpoint_skills(tmp_path / "global-disable" / "feature", "feature")
|
|
1513
|
+
assert "browser-verification" not in self._checkpoint_skills(tmp_path / "global-disable" / "bugfix", "bugfix")
|
|
1514
|
+
assert "browser-verification" not in self._checkpoint_skills(tmp_path / "global-disable" / "refactor", "refactor")
|
|
1515
|
+
|
|
1331
1516
|
def test_fresh_manual_bugfix_prompt_stops_before_commit_phase(self, tmp_path):
|
|
1332
1517
|
prompt = _render_bugfix_prompt(
|
|
1333
1518
|
tmp_path / "fresh-manual-bugfix",
|
|
@@ -1446,7 +1631,8 @@ class TestHeadlessPromptCleanupF033:
|
|
|
1446
1631
|
|
|
1447
1632
|
assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
|
|
1448
1633
|
assert "Main-Agent review loop" in refactor_prompt
|
|
1449
|
-
assert "
|
|
1634
|
+
assert "The current Main Agent is the only Code Review executor" in refactor_prompt
|
|
1635
|
+
assert "Do not invoke another review skill or review workflow" in refactor_prompt
|
|
1450
1636
|
assert "Reviewer 3" not in refactor_prompt
|
|
1451
1637
|
|
|
1452
1638
|
def test_bugfix_refactor_explicit_codex_override(self, tmp_path, monkeypatch):
|
|
@@ -1469,7 +1655,8 @@ class TestHeadlessPromptCleanupF033:
|
|
|
1469
1655
|
|
|
1470
1656
|
assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
|
|
1471
1657
|
assert "Main-Agent review loop" in refactor_prompt
|
|
1472
|
-
assert "
|
|
1658
|
+
assert "The current Main Agent is the only Code Review executor" in refactor_prompt
|
|
1659
|
+
assert "Do not invoke another review skill or review workflow" in refactor_prompt
|
|
1473
1660
|
assert "Reviewer 3" not in refactor_prompt
|
|
1474
1661
|
|
|
1475
1662
|
|
|
@@ -1408,7 +1408,8 @@ def test_default_no_worktree_preserves_main_worktree_branch_launch(monkeypatch,
|
|
|
1408
1408
|
lifecycle_order.append("branch_create")
|
|
1409
1409
|
branch_events.append(("create", plan.commands[1].args[2]))
|
|
1410
1410
|
if plan.name == "branch_merge":
|
|
1411
|
-
|
|
1411
|
+
merge_command = next(command for command in plan.commands if command.args[:2] == ("merge", "--ff-only"))
|
|
1412
|
+
branch_events.append(("merge", merge_command.args[2]))
|
|
1412
1413
|
return SimpleNamespace(ok=True, plan=plan, results=())
|
|
1413
1414
|
|
|
1414
1415
|
def fake_start_item(*args, **kwargs):
|
|
@@ -1603,6 +1604,79 @@ def test_worktree_recovers_completed_pending_branch_from_base_session_history(mo
|
|
|
1603
1604
|
assert merge_calls and cleanup_calls[-1][1] is True
|
|
1604
1605
|
|
|
1605
1606
|
|
|
1607
|
+
def test_success_final_commit_failure_preserves_checkout_without_merge(monkeypatch, tmp_path):
|
|
1608
|
+
from prizmkit_runtime import runners
|
|
1609
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
1610
|
+
from prizmkit_runtime.task_checkout import load_task_checkout
|
|
1611
|
+
|
|
1612
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-finalize-fail")
|
|
1613
|
+
paths = _make_paths(tmp_path)
|
|
1614
|
+
family = family_for("feature", paths)
|
|
1615
|
+
invocation = parse_invocation(family, "run", ())
|
|
1616
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
1617
|
+
_install_runner_session_fakes(monkeypatch, runners)
|
|
1618
|
+
merge_calls = []
|
|
1619
|
+
|
|
1620
|
+
monkeypatch.setattr(
|
|
1621
|
+
runners,
|
|
1622
|
+
"commit_final_bookkeeping",
|
|
1623
|
+
lambda *args, **kwargs: SimpleNamespace(ok=False, attempted=True, committed=False),
|
|
1624
|
+
)
|
|
1625
|
+
monkeypatch.setattr(
|
|
1626
|
+
runners,
|
|
1627
|
+
"commit_task_branch_changes",
|
|
1628
|
+
lambda *args, **kwargs: SimpleNamespace(ok=True, attempted=False, committed=False),
|
|
1629
|
+
)
|
|
1630
|
+
monkeypatch.setattr(
|
|
1631
|
+
runners,
|
|
1632
|
+
"run_git_plan",
|
|
1633
|
+
lambda root, plan: merge_calls.append(plan.name) or SimpleNamespace(ok=True, plan=plan, results=()),
|
|
1634
|
+
)
|
|
1635
|
+
|
|
1636
|
+
status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
1637
|
+
|
|
1638
|
+
assert status == "completed"
|
|
1639
|
+
assert "branch_merge" not in merge_calls
|
|
1640
|
+
checkout = load_task_checkout(family.state_dir, family.task_type, "F-001")
|
|
1641
|
+
assert checkout is not None and checkout.is_active
|
|
1642
|
+
session_status = paths.feature_state_dir / "F-001" / "sessions" / "F-001-session-1" / "session-status.json"
|
|
1643
|
+
assert json.loads(session_status.read_text(encoding="utf-8"))["status"] == "merge_conflict"
|
|
1644
|
+
|
|
1645
|
+
|
|
1646
|
+
def test_wip_commit_failure_skips_base_return(monkeypatch, tmp_path):
|
|
1647
|
+
from prizmkit_runtime import runners
|
|
1648
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
1649
|
+
|
|
1650
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-wip-fail")
|
|
1651
|
+
paths = _make_paths(tmp_path)
|
|
1652
|
+
family = family_for("feature", paths)
|
|
1653
|
+
invocation = parse_invocation(family, "run", ())
|
|
1654
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
1655
|
+
_install_runner_session_fakes(monkeypatch, runners, statuses=("crashed",), update_status="failed")
|
|
1656
|
+
returns = []
|
|
1657
|
+
|
|
1658
|
+
monkeypatch.setattr(
|
|
1659
|
+
runners,
|
|
1660
|
+
"commit_wip_changes",
|
|
1661
|
+
lambda *args, **kwargs: SimpleNamespace(ok=False, attempted=True, committed=False),
|
|
1662
|
+
)
|
|
1663
|
+
monkeypatch.setattr(
|
|
1664
|
+
runners,
|
|
1665
|
+
"branch_ensure_return",
|
|
1666
|
+
lambda *args, **kwargs: returns.append(args) or SimpleNamespace(ok=True),
|
|
1667
|
+
)
|
|
1668
|
+
monkeypatch.setattr(
|
|
1669
|
+
runners,
|
|
1670
|
+
"run_git_plan",
|
|
1671
|
+
lambda root, plan: SimpleNamespace(ok=True, plan=plan, results=()),
|
|
1672
|
+
)
|
|
1673
|
+
|
|
1674
|
+
status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
1675
|
+
|
|
1676
|
+
assert status == "failed"
|
|
1677
|
+
assert returns == []
|
|
1678
|
+
|
|
1679
|
+
|
|
1606
1680
|
def test_default_branch_names_keep_timestamp_for_each_runner_family(monkeypatch, tmp_path):
|
|
1607
1681
|
from prizmkit_runtime import runners
|
|
1608
1682
|
from prizmkit_runtime.runner_models import family_for
|
|
@@ -1752,7 +1826,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
|
|
|
1752
1826
|
assert "Feature: F-001 — Readable output" in captured.err
|
|
1753
1827
|
assert "Code retry: 1 / 3" in captured.err
|
|
1754
1828
|
assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
|
|
1755
|
-
assert "Pipeline mode: full (Tier 3 —
|
|
1829
|
+
assert "Pipeline mode: full (Tier 3 — Main-Agent Full Guardrails)" in captured.err
|
|
1756
1830
|
assert "Agents: 3" in captured.err
|
|
1757
1831
|
assert "Spawning AI CLI session: F-001-session-1" in captured.err
|
|
1758
1832
|
assert "Session result: success" in captured.err
|
|
@@ -2098,24 +2172,27 @@ def test_preflight_ready_commit_includes_tracked_state_but_excludes_untracked_st
|
|
|
2098
2172
|
assert untracked_state.relative_to(tmp_path).as_posix() not in _head_files(tmp_path)
|
|
2099
2173
|
|
|
2100
2174
|
|
|
2101
|
-
def
|
|
2175
|
+
def test_preflight_ready_commit_excludes_tracked_framework_manifest_but_preserves_other_tracked_support(tmp_path):
|
|
2102
2176
|
from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
|
|
2103
2177
|
|
|
2104
2178
|
_init_bookkeeping_repo(tmp_path)
|
|
2179
|
+
manifest_path = tmp_path / ".prizmkit" / "manifest.json"
|
|
2105
2180
|
tracked_support_paths = [
|
|
2106
|
-
tmp_path / ".prizmkit" / "manifest.json",
|
|
2107
2181
|
tmp_path / ".prizmkit" / "dev-pipeline" / "prizmkit_runtime" / "runners.py",
|
|
2108
2182
|
tmp_path / ".claude" / "commands" / "prizmkit.md",
|
|
2109
2183
|
]
|
|
2184
|
+
manifest_path.parent.mkdir(parents=True, exist_ok=True)
|
|
2185
|
+
manifest_path.write_text('{"version":"1.1.127"}\n', encoding="utf-8")
|
|
2110
2186
|
for path in tracked_support_paths:
|
|
2111
2187
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
2112
2188
|
path.write_text("installed\n", encoding="utf-8")
|
|
2113
2189
|
subprocess.run(
|
|
2114
|
-
["git", "add", *(path.relative_to(tmp_path).as_posix() for path in tracked_support_paths)],
|
|
2190
|
+
["git", "add", manifest_path.relative_to(tmp_path).as_posix(), *(path.relative_to(tmp_path).as_posix() for path in tracked_support_paths)],
|
|
2115
2191
|
cwd=tmp_path,
|
|
2116
2192
|
check=True,
|
|
2117
2193
|
)
|
|
2118
2194
|
subprocess.run(["git", "commit", "-m", "track support"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2195
|
+
manifest_path.write_text('{"version":"1.1.128"}\n', encoding="utf-8")
|
|
2119
2196
|
for path in tracked_support_paths:
|
|
2120
2197
|
path.write_text("upgraded\n", encoding="utf-8")
|
|
2121
2198
|
|
|
@@ -2129,19 +2206,23 @@ def test_preflight_ready_commit_includes_tracked_framework_support_but_excludes_
|
|
|
2129
2206
|
path.write_text("transient\n", encoding="utf-8")
|
|
2130
2207
|
|
|
2131
2208
|
result = commit_preflight_ready_changes(tmp_path, "B-001")
|
|
2132
|
-
committed_files = _head_files(tmp_path)
|
|
2133
2209
|
|
|
2134
|
-
assert result.
|
|
2135
|
-
assert
|
|
2136
|
-
assert
|
|
2137
|
-
assert
|
|
2210
|
+
assert result.attempted is True
|
|
2211
|
+
assert result.committed is False
|
|
2212
|
+
assert result.result is not None
|
|
2213
|
+
assert "installer-owned .prizmkit/manifest.json" in result.result.stderr
|
|
2214
|
+
assert _head_message(tmp_path) == "track support"
|
|
2138
2215
|
assert subprocess.run(
|
|
2139
2216
|
["git", "status", "--short", "--untracked-files=no"],
|
|
2140
2217
|
cwd=tmp_path,
|
|
2141
2218
|
check=True,
|
|
2142
2219
|
stdout=subprocess.PIPE,
|
|
2143
2220
|
text=True,
|
|
2144
|
-
).stdout ==
|
|
2221
|
+
).stdout.splitlines() == [
|
|
2222
|
+
" M .claude/commands/prizmkit.md",
|
|
2223
|
+
" M .prizmkit/dev-pipeline/prizmkit_runtime/runners.py",
|
|
2224
|
+
" M .prizmkit/manifest.json",
|
|
2225
|
+
]
|
|
2145
2226
|
|
|
2146
2227
|
|
|
2147
2228
|
def test_preflight_ready_commit_excludes_untracked_support_assets_and_state(tmp_path):
|
|
@@ -2176,6 +2257,53 @@ def test_preflight_ready_commit_excludes_untracked_support_assets_and_state(tmp_
|
|
|
2176
2257
|
assert ".claude" in hidden_status or ".prizmkit/state" in hidden_status
|
|
2177
2258
|
|
|
2178
2259
|
|
|
2260
|
+
def test_preflight_ready_commit_rejects_unmerged_index(tmp_path):
|
|
2261
|
+
from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
|
|
2262
|
+
|
|
2263
|
+
_init_bookkeeping_repo(tmp_path)
|
|
2264
|
+
subprocess.run(["git", "checkout", "-b", "other"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2265
|
+
(tmp_path / "staged.txt").write_text("other\n", encoding="utf-8")
|
|
2266
|
+
subprocess.run(["git", "commit", "-am", "other"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2267
|
+
subprocess.run(["git", "checkout", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2268
|
+
(tmp_path / "staged.txt").write_text("main\n", encoding="utf-8")
|
|
2269
|
+
subprocess.run(["git", "commit", "-am", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
|
|
2270
|
+
subprocess.run(["git", "merge", "other"], cwd=tmp_path, check=False, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)
|
|
2271
|
+
before_count = _head_commit_count(tmp_path)
|
|
2272
|
+
|
|
2273
|
+
result = commit_preflight_ready_changes(tmp_path, "F-126")
|
|
2274
|
+
|
|
2275
|
+
assert result.attempted is True
|
|
2276
|
+
assert result.committed is False
|
|
2277
|
+
assert result.result is not None
|
|
2278
|
+
assert "unmerged Git paths" in result.result.stderr
|
|
2279
|
+
assert _head_commit_count(tmp_path) == before_count
|
|
2280
|
+
|
|
2281
|
+
|
|
2282
|
+
def test_preflight_ready_commit_rejects_conflict_markers_and_malformed_manifest(tmp_path):
|
|
2283
|
+
from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
|
|
2284
|
+
|
|
2285
|
+
_init_bookkeeping_repo(tmp_path)
|
|
2286
|
+
(tmp_path / "unstaged.txt").write_text("<<<<<<< Updated upstream\ncurrent\n=======\nstashed\n>>>>>>> Stashed changes\n", encoding="utf-8")
|
|
2287
|
+
|
|
2288
|
+
marker_result = commit_preflight_ready_changes(tmp_path, "F-127")
|
|
2289
|
+
|
|
2290
|
+
assert marker_result.committed is False
|
|
2291
|
+
assert marker_result.result is not None
|
|
2292
|
+
assert "unresolved conflict markers" in marker_result.result.stderr
|
|
2293
|
+
|
|
2294
|
+
(tmp_path / "unstaged.txt").write_text("safe\n", encoding="utf-8")
|
|
2295
|
+
manifest = tmp_path / ".prizmkit" / "manifest.json"
|
|
2296
|
+
manifest.parent.mkdir(parents=True, exist_ok=True)
|
|
2297
|
+
manifest.write_text("<<<<<<< Updated upstream\n{}\n", encoding="utf-8")
|
|
2298
|
+
|
|
2299
|
+
manifest_result = commit_preflight_ready_changes(tmp_path, "F-128")
|
|
2300
|
+
|
|
2301
|
+
assert manifest_result.committed is False
|
|
2302
|
+
assert manifest_result.result is not None
|
|
2303
|
+
assert ".prizmkit/manifest.json" in manifest_result.result.stderr
|
|
2304
|
+
assert "valid JSON" in manifest_result.result.stderr
|
|
2305
|
+
|
|
2306
|
+
|
|
2179
2307
|
def test_preflight_ready_commit_noops_when_workspace_has_no_visible_dirty_content(tmp_path):
|
|
2180
2308
|
from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
|
|
2181
2309
|
|