prizmkit 1.1.107 → 1.1.108
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 +4 -0
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/config.py +81 -14
- package/bundled/dev-pipeline/prizmkit_runtime/heartbeat.py +19 -8
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
- package/bundled/dev-pipeline/prizmkit_runtime/launch_profiles.py +18 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +22 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +3 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +20 -1
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +151 -30
- package/bundled/dev-pipeline/scripts/continuation.py +0 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +6 -46
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +7 -34
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +40 -33
- package/bundled/dev-pipeline/scripts/utils.py +123 -0
- package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +4 -3
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier1.md +8 -7
- package/bundled/dev-pipeline/templates/bootstrap-tier2.md +25 -36
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +16 -31
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +4 -16
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +3 -15
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +14 -48
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +7 -13
- package/bundled/dev-pipeline/templates/sections/feature-context.md +0 -4
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-auto.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification-opencli.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-browser-verification.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +0 -14
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -14
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +0 -2
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +6 -8
- package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +3 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +2 -9
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +1 -8
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +15 -16
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +2 -9
- package/bundled/dev-pipeline/templates/sections/phase0-init.md +0 -7
- package/bundled/dev-pipeline/templates/sections/phase0-test-baseline.md +2 -15
- package/bundled/dev-pipeline/tests/conftest.py +0 -1
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +350 -23
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +36 -17
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +117 -14
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +79 -2
- package/bundled/dev-pipeline/tests/test_unified_cli.py +658 -13
- package/bundled/skills/_metadata.json +4 -4
- package/bundled/skills/app-planner/SKILL.md +1 -1
- package/bundled/skills/app-planner/references/architecture-decisions.md +1 -1
- package/bundled/skills/bug-fix-workflow/SKILL.md +174 -128
- package/bundled/skills/bug-planner/SKILL.md +101 -15
- package/bundled/skills/bug-planner/references/severity-rules.md +20 -8
- package/bundled/skills/feature-planner/SKILL.md +87 -8
- package/bundled/skills/feature-planner/assets/planning-guide.md +34 -10
- package/bundled/skills/feature-workflow/SKILL.md +100 -81
- package/bundled/skills/{prizm-kit → prizmkit}/SKILL.md +1 -1
- package/bundled/skills/prizmkit-code-review/SKILL.md +1 -1
- package/bundled/skills/refactor-planner/SKILL.md +88 -7
- package/bundled/skills/refactor-planner/references/fast-path.md +2 -1
- package/bundled/skills/refactor-planner/references/planning-phases.md +29 -11
- package/bundled/skills/refactor-workflow/SKILL.md +116 -57
- package/bundled/templates/project-memory-template.md +1 -1
- package/package.json +1 -1
- package/src/ai-cli-launch.js +194 -0
- package/src/config.js +26 -14
- package/src/index.js +11 -44
- package/src/platforms.js +10 -3
- package/src/prompts.js +60 -2
- package/src/scaffold.js +26 -14
- package/bundled/dev-pipeline/scripts/prizmkit-test-gate.py +0 -446
- package/bundled/dev-pipeline/templates/sections/log-size-awareness.md +0 -20
- package/bundled/dev-pipeline/tests/test_prizmkit_test_gate.py +0 -107
|
@@ -7,7 +7,6 @@ from continuation import append_continuation_handoff
|
|
|
7
7
|
from generate_refactor_prompt import (
|
|
8
8
|
build_replacements,
|
|
9
9
|
generate_refactor_checkpoint,
|
|
10
|
-
load_log_size_section,
|
|
11
10
|
merge_refactor_checkpoint_state,
|
|
12
11
|
)
|
|
13
12
|
|
|
@@ -35,7 +34,7 @@ class TestRefactorContinuationHandoff:
|
|
|
35
34
|
|
|
36
35
|
assert prompt.startswith("REFACTOR PROMPT")
|
|
37
36
|
assert "This is a fresh headless AI CLI session for the same task." in prompt
|
|
38
|
-
assert "Do not attempt `/compact` as the recovery mechanism." in prompt
|
|
37
|
+
assert "Do not attempt `/compact` as the recovery mechanism." not in prompt
|
|
39
38
|
assert ("Log " + "monitor") not in prompt
|
|
40
39
|
assert ("log-" + "monitor") not in prompt
|
|
41
40
|
assert (tmp_path / ".prizmkit/specs/R-007/continuation-summary.md").exists()
|
|
@@ -54,19 +53,102 @@ class TestRefactorCheckpointMerge:
|
|
|
54
53
|
assert merged["steps"][1]["status"] == "pending"
|
|
55
54
|
|
|
56
55
|
|
|
56
|
+
class TestExplicitProjectRoot:
|
|
57
|
+
def test_build_replacements_infers_project_root_from_refactor_list(self, tmp_path, monkeypatch):
|
|
58
|
+
monkeypatch.delenv("PRIZMKIT_PLATFORM", raising=False)
|
|
59
|
+
project = tmp_path / "project"
|
|
60
|
+
plan_path = project / ".prizmkit" / "plans" / "refactor-list.json"
|
|
61
|
+
plan_path.parent.mkdir(parents=True)
|
|
62
|
+
(project / ".prizmkit" / "config.json").write_text(
|
|
63
|
+
'{"platform":"claude","ai_cli":"claude"}', encoding="utf-8"
|
|
64
|
+
)
|
|
65
|
+
args = Namespace(
|
|
66
|
+
refactor_list=str(plan_path),
|
|
67
|
+
refactor_id="R-001",
|
|
68
|
+
session_id="session-1",
|
|
69
|
+
run_id="run-1",
|
|
70
|
+
retry_count="0",
|
|
71
|
+
resume_phase="null",
|
|
72
|
+
state_dir=str(project / ".prizmkit" / "state" / "refactor"),
|
|
73
|
+
output=str(project / "out.md"),
|
|
74
|
+
template=None,
|
|
75
|
+
mode=None,
|
|
76
|
+
critic=None,
|
|
77
|
+
)
|
|
78
|
+
refactor = {"id": "R-001", "title": "Restructure", "description": "d"}
|
|
79
|
+
|
|
80
|
+
replacements = build_replacements(
|
|
81
|
+
args, refactor, [refactor], {}, str(Path("dev-pipeline/scripts").resolve())
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
assert replacements["{{PROJECT_ROOT}}"] == str(project.resolve())
|
|
85
|
+
assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].endswith(
|
|
86
|
+
".claude/agents/prizm-dev-team-reviewer.md"
|
|
87
|
+
)
|
|
88
|
+
assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].startswith(str(project.resolve()))
|
|
89
|
+
assert replacements["{{SESSION_STATUS_PATH}}"] == str(
|
|
90
|
+
project / ".prizmkit" / "state" / "refactor" / "R-001" /
|
|
91
|
+
"sessions" / "session-1" / "session-status.json"
|
|
92
|
+
)
|
|
93
|
+
|
|
94
|
+
def test_build_replacements_uses_cli_project_root_for_rendered_paths(self, tmp_path, monkeypatch):
|
|
95
|
+
monkeypatch.setenv("PRIZMKIT_PLATFORM", "claude")
|
|
96
|
+
execution_root = tmp_path / "worktree"
|
|
97
|
+
main_checkout = tmp_path / "main"
|
|
98
|
+
execution_root.mkdir()
|
|
99
|
+
main_checkout.mkdir()
|
|
100
|
+
args = Namespace(
|
|
101
|
+
refactor_list=str(main_checkout / ".prizmkit" / "plans" / "refactor-list.json"),
|
|
102
|
+
refactor_id="R-001",
|
|
103
|
+
session_id="session-1",
|
|
104
|
+
run_id="run-1",
|
|
105
|
+
retry_count="0",
|
|
106
|
+
resume_phase="null",
|
|
107
|
+
state_dir=str(main_checkout / ".prizmkit" / "state" / "refactor"),
|
|
108
|
+
output=str(main_checkout / ".prizmkit" / "state" / "refactor" / "R-001" / "sessions" / "session-1" / "bootstrap-prompt.md"),
|
|
109
|
+
template=None,
|
|
110
|
+
mode=None,
|
|
111
|
+
critic=None,
|
|
112
|
+
project_root=str(execution_root),
|
|
113
|
+
)
|
|
114
|
+
refactor = {"id": "R-001", "title": "Worktree Refactor", "description": "d"}
|
|
115
|
+
|
|
116
|
+
replacements = build_replacements(
|
|
117
|
+
args,
|
|
118
|
+
refactor,
|
|
119
|
+
[refactor],
|
|
120
|
+
{},
|
|
121
|
+
str(main_checkout / "dev-pipeline" / "scripts"),
|
|
122
|
+
)
|
|
123
|
+
|
|
124
|
+
assert replacements["{{PROJECT_ROOT}}"] == str(execution_root.resolve())
|
|
125
|
+
assert replacements["{{SESSION_STATUS_PATH}}"] == str(
|
|
126
|
+
main_checkout / ".prizmkit" / "state" / "refactor" / "R-001" /
|
|
127
|
+
"sessions" / "session-1" / "session-status.json"
|
|
128
|
+
)
|
|
129
|
+
assert not replacements["{{SESSION_STATUS_PATH}}"].startswith(str(execution_root.resolve()))
|
|
130
|
+
assert replacements["{{DEV_SUBAGENT_PATH}}"].startswith(str(execution_root.resolve()))
|
|
131
|
+
assert "### Checkpoint Update Helper" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
132
|
+
assert "python3 .prizmkit/dev-pipeline/scripts/update-checkpoint.py" in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
133
|
+
assert "pipeline progress mechanism" not in replacements["{{CHECKPOINT_SYSTEM}}"]
|
|
134
|
+
|
|
135
|
+
|
|
57
136
|
class TestRefactorPromptMaxLogRemoval:
|
|
58
|
-
def
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
assert ("
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
137
|
+
def test_refactor_source_has_no_deleted_log_size_injection(self):
|
|
138
|
+
deleted_section = "log-size-" + "awareness.md"
|
|
139
|
+
loader = "load_" + "log_size_section"
|
|
140
|
+
placeholder = "{{LOG_" + "SIZE_AWARENESS}}"
|
|
141
|
+
deleted_title = "Session " + "Checkpoint Awareness"
|
|
142
|
+
source = Path("dev-pipeline/scripts/generate-refactor-prompt.py").read_text(encoding="utf-8")
|
|
143
|
+
template = Path("dev-pipeline/templates/refactor-bootstrap-prompt.md").read_text(encoding="utf-8")
|
|
144
|
+
|
|
145
|
+
assert (Path("dev-pipeline/templates/sections") / deleted_section).exists() is False
|
|
146
|
+
for content in (source, template):
|
|
147
|
+
for forbidden in (deleted_section, loader, placeholder, deleted_title):
|
|
148
|
+
assert forbidden not in content
|
|
149
|
+
|
|
150
|
+
def test_refactor_replacements_keep_session_log_without_max_log_placeholders(self, monkeypatch):
|
|
151
|
+
monkeypatch.setenv("PRIZMKIT_PLATFORM", "claude")
|
|
70
152
|
args = Namespace(
|
|
71
153
|
refactor_id="R-001",
|
|
72
154
|
session_id="session-1",
|
|
@@ -90,3 +172,24 @@ class TestRefactorPromptMaxLogRemoval:
|
|
|
90
172
|
)
|
|
91
173
|
assert "{{" + REMOVED_MAX_LOG_ENV + "}}" not in replacements
|
|
92
174
|
assert "{{" + REMOVED_MAX_LOG_HUMAN + "}}" not in replacements
|
|
175
|
+
assert "{{LOG_" + "SIZE_AWARENESS}}" not in replacements
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
HEADLESS_FORBIDDEN_PROMPT_STRINGS = [
|
|
179
|
+
"### Interactive Claude Code Note",
|
|
180
|
+
"### Project Conventions",
|
|
181
|
+
"Scaffold / generated file awareness",
|
|
182
|
+
"### Resume Behavior",
|
|
183
|
+
"[NEEDS CLARIFICATION]",
|
|
184
|
+
"pause for interactive input",
|
|
185
|
+
"prizmkit-test-gate.py",
|
|
186
|
+
"/compact",
|
|
187
|
+
"Session " + "Checkpoint Awareness",
|
|
188
|
+
"{{LOG_" + "SIZE_AWARENESS}}",
|
|
189
|
+
]
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
def test_template_source_is_clean_for_headless_prompt_contract():
|
|
193
|
+
content = Path("dev-pipeline/templates/refactor-bootstrap-prompt.md").read_text(encoding="utf-8")
|
|
194
|
+
for forbidden in HEADLESS_FORBIDDEN_PROMPT_STRINGS:
|
|
195
|
+
assert forbidden not in content
|
|
@@ -604,6 +604,7 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
604
604
|
|
|
605
605
|
prompt_calls = []
|
|
606
606
|
launch_cwds = []
|
|
607
|
+
launch_configs = []
|
|
607
608
|
updates = []
|
|
608
609
|
cleanup_calls = []
|
|
609
610
|
merge_calls = []
|
|
@@ -621,6 +622,7 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
621
622
|
)
|
|
622
623
|
monkeypatch.setattr(runners, "detect_stream_json_support", lambda command, platform: SimpleNamespace(enabled=False))
|
|
623
624
|
monkeypatch.setattr(runners, "start_item", lambda *args, **kwargs: SimpleNamespace(ok=True, text="", data={"new_status": "in_progress"}))
|
|
625
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", lambda *args, **kwargs: SimpleNamespace(attempted=False, committed=False, ok=True, result=None))
|
|
624
626
|
monkeypatch.setattr(runners, "commit_pre_branch_bookkeeping", lambda *args, **kwargs: None)
|
|
625
627
|
monkeypatch.setattr(runners, "commit_final_bookkeeping", lambda *args, **kwargs: None)
|
|
626
628
|
monkeypatch.setattr(runners, "current_branch", lambda _root: "main")
|
|
@@ -652,6 +654,7 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
652
654
|
|
|
653
655
|
def run(self):
|
|
654
656
|
launch_cwds.append(self.config.cwd)
|
|
657
|
+
launch_configs.append(self.config)
|
|
655
658
|
return SimpleNamespace(progress_summary=SimpleNamespace(), fatal_error_code="")
|
|
656
659
|
|
|
657
660
|
def fake_classify(*args, **kwargs):
|
|
@@ -690,6 +693,7 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
690
693
|
return SimpleNamespace(
|
|
691
694
|
prompt_calls=prompt_calls,
|
|
692
695
|
launch_cwds=launch_cwds,
|
|
696
|
+
launch_configs=launch_configs,
|
|
693
697
|
updates=updates,
|
|
694
698
|
cleanup_calls=cleanup_calls,
|
|
695
699
|
merge_calls=merge_calls,
|
|
@@ -697,6 +701,36 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
|
|
|
697
701
|
)
|
|
698
702
|
|
|
699
703
|
|
|
704
|
+
|
|
705
|
+
def test_runner_launch_config_preserves_artifact_paths_live_output_and_split_thresholds(monkeypatch, tmp_path):
|
|
706
|
+
from prizmkit_runtime import runners
|
|
707
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
708
|
+
|
|
709
|
+
monkeypatch.setenv("PRIZMKIT_LIVE_OUTPUT", "0")
|
|
710
|
+
monkeypatch.setenv("HEARTBEAT_INTERVAL", "7")
|
|
711
|
+
monkeypatch.setenv("HEARTBEAT_STALE_THRESHOLD", "11")
|
|
712
|
+
monkeypatch.setenv("STALE_KILL_THRESHOLD", "13")
|
|
713
|
+
paths = _make_paths(tmp_path / "artifact-wiring")
|
|
714
|
+
family = family_for("feature", paths)
|
|
715
|
+
invocation = parse_invocation(family, "run", ())
|
|
716
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
717
|
+
observed = _install_runner_session_fakes(monkeypatch, runners)
|
|
718
|
+
monkeypatch.setattr(runners, "run_git_plan", lambda *args, **kwargs: SimpleNamespace(ok=True, reason="", results=()))
|
|
719
|
+
|
|
720
|
+
status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
721
|
+
|
|
722
|
+
config = observed.launch_configs[0]
|
|
723
|
+
expected_log = paths.feature_state_dir / "F-001" / "sessions" / "F-001-session-1" / "logs" / "session.log"
|
|
724
|
+
assert status == "completed"
|
|
725
|
+
assert config.live_output is False
|
|
726
|
+
assert config.live_progress_interval_seconds == 7
|
|
727
|
+
assert config.heartbeat_stale_threshold_seconds == 11
|
|
728
|
+
assert config.stale_kill_threshold_seconds == 13
|
|
729
|
+
assert config.log_path == expected_log
|
|
730
|
+
assert config.progress_path == expected_log.with_name("progress.json")
|
|
731
|
+
assert config.heartbeat_path == expected_log.with_name("heartbeat.json")
|
|
732
|
+
|
|
733
|
+
|
|
700
734
|
@pytest.mark.parametrize(("kind", "item_id", "branch"), [("feature", "F-001", "dev/F-001-test"), ("bugfix", "B-001", "bugfix/B-001-test")])
|
|
701
735
|
def test_feature_and_bugfix_explicit_worktree_launches_from_worktree_and_renders_worktree_root(monkeypatch, tmp_path, kind, item_id, branch):
|
|
702
736
|
from prizmkit_runtime import runners
|
|
@@ -710,18 +744,26 @@ def test_feature_and_bugfix_explicit_worktree_launches_from_worktree_and_renders
|
|
|
710
744
|
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan if kind == "feature" else paths.bugfix_plan})
|
|
711
745
|
observed = _install_runner_session_fakes(monkeypatch, runners)
|
|
712
746
|
setup_calls = []
|
|
747
|
+
lifecycle_order = []
|
|
713
748
|
|
|
714
749
|
def fake_setup(runtime):
|
|
750
|
+
lifecycle_order.append("worktree_setup")
|
|
715
751
|
setup_calls.append(runtime)
|
|
716
752
|
runtime.worktree_path.mkdir(parents=True, exist_ok=True)
|
|
717
753
|
return SimpleNamespace(ok=True, runtime=runtime, reason="")
|
|
718
754
|
|
|
755
|
+
def fake_preflight(*args, **kwargs):
|
|
756
|
+
lifecycle_order.append("preflight")
|
|
757
|
+
return SimpleNamespace(attempted=False, committed=False, ok=True, result=None)
|
|
758
|
+
|
|
759
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", fake_preflight)
|
|
719
760
|
monkeypatch.setattr(runners, "ensure_linked_worktree", fake_setup, raising=False)
|
|
720
761
|
|
|
721
762
|
status = runners._process_item(family, invocation, item_id, paths, initial_metadata={})
|
|
722
763
|
|
|
723
764
|
expected_worktree = paths.state_dir / "worktrees" / branch.replace("/", "-")
|
|
724
765
|
assert status == "completed"
|
|
766
|
+
assert lifecycle_order[:2] == ["preflight", "worktree_setup"]
|
|
725
767
|
assert setup_calls[0].worktree_path == expected_worktree.resolve()
|
|
726
768
|
assert observed.launch_cwds == [expected_worktree.resolve()]
|
|
727
769
|
assert observed.prompt_calls[0]["project_root"] == paths.project_root
|
|
@@ -842,6 +884,7 @@ def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and
|
|
|
842
884
|
)
|
|
843
885
|
monkeypatch.setattr(runners, "detect_stream_json_support", lambda command, platform: SimpleNamespace(enabled=False))
|
|
844
886
|
monkeypatch.setattr(runners, "start_item", lambda *args, **kwargs: SimpleNamespace(ok=True, text="", data={"new_status": "in_progress"}))
|
|
887
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", lambda *args, **kwargs: SimpleNamespace(attempted=False, committed=False, ok=True, result=None))
|
|
845
888
|
monkeypatch.setattr(runners, "commit_pre_branch_bookkeeping", lambda *args, **kwargs: None)
|
|
846
889
|
monkeypatch.setattr(runners, "commit_final_bookkeeping", lambda *args, **kwargs: None)
|
|
847
890
|
monkeypatch.setattr(runners, "_session_id", lambda item_id: f"{item_id}-session")
|
|
@@ -921,6 +964,11 @@ def test_default_no_worktree_preserves_main_worktree_branch_launch(monkeypatch,
|
|
|
921
964
|
lifecycle_order.append("start_item")
|
|
922
965
|
return SimpleNamespace(ok=True, text="", data={"new_status": "in_progress"})
|
|
923
966
|
|
|
967
|
+
def fake_preflight(*args, **kwargs):
|
|
968
|
+
lifecycle_order.append("preflight")
|
|
969
|
+
return SimpleNamespace(attempted=False, committed=False, ok=True, result=None)
|
|
970
|
+
|
|
971
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", fake_preflight)
|
|
924
972
|
monkeypatch.setattr(runners, "run_git_plan", fake_run_git_plan)
|
|
925
973
|
monkeypatch.setattr(runners, "start_item", fake_start_item)
|
|
926
974
|
monkeypatch.setattr(runners, "ensure_linked_worktree", lambda runtime: (_ for _ in ()).throw(AssertionError("worktree setup should not run")), raising=False)
|
|
@@ -932,7 +980,7 @@ def test_default_no_worktree_preserves_main_worktree_branch_launch(monkeypatch,
|
|
|
932
980
|
assert observed.prompt_calls[0].get("execution_root") == paths.project_root
|
|
933
981
|
assert "branch_create" in plan_names
|
|
934
982
|
assert "branch_merge" in plan_names
|
|
935
|
-
assert lifecycle_order[:
|
|
983
|
+
assert lifecycle_order[:3] == ["preflight", "branch_create", "start_item"]
|
|
936
984
|
assert branch_events == [("create", "dev/F-001-test"), ("merge", "dev/F-001-test")]
|
|
937
985
|
|
|
938
986
|
|
|
@@ -1024,6 +1072,7 @@ def test_no_worktree_recovers_completed_pending_branch_before_new_session(monkey
|
|
|
1024
1072
|
monkeypatch.setattr(runners, "has_branch_completion_evidence", lambda *args, **kwargs: True)
|
|
1025
1073
|
monkeypatch.setattr(runners, "commit_final_bookkeeping", lambda *args, **kwargs: None)
|
|
1026
1074
|
monkeypatch.setattr(runners, "commit_task_branch_changes", lambda *args, **kwargs: None)
|
|
1075
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", lambda *args, **kwargs: SimpleNamespace(attempted=False, committed=False, ok=True, result=None))
|
|
1027
1076
|
|
|
1028
1077
|
def fake_update(family, invocation, item_id, session_status, session_id, project_root, **metadata):
|
|
1029
1078
|
updates.append((session_status, session_id, metadata))
|
|
@@ -1076,6 +1125,7 @@ def test_worktree_recovers_completed_pending_branch_from_base_session_history(mo
|
|
|
1076
1125
|
monkeypatch.setattr(runners, "generate_prompt", lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("should not launch prompt")))
|
|
1077
1126
|
monkeypatch.setattr(runners, "commit_final_bookkeeping", lambda *args, **kwargs: None)
|
|
1078
1127
|
monkeypatch.setattr(runners, "commit_task_branch_changes", lambda *args, **kwargs: None)
|
|
1128
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", lambda *args, **kwargs: SimpleNamespace(attempted=False, committed=False, ok=True, result=None))
|
|
1079
1129
|
|
|
1080
1130
|
def fake_update(family, invocation, item_id, session_status, session_id, project_root, **metadata):
|
|
1081
1131
|
updates.append((session_status, session_id, project_root, metadata))
|
|
@@ -1187,6 +1237,33 @@ def test_no_worktree_runner_ignores_recovery_side_effect_branch(monkeypatch, tmp
|
|
|
1187
1237
|
assert observed.updates[-1][2]["base_branch"] == "main"
|
|
1188
1238
|
|
|
1189
1239
|
|
|
1240
|
+
def test_no_worktree_preflight_commit_failure_aborts_before_branch_or_status(monkeypatch, tmp_path):
|
|
1241
|
+
from prizmkit_runtime import runners
|
|
1242
|
+
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
1243
|
+
|
|
1244
|
+
monkeypatch.setenv("DEV_BRANCH", "dev/F-001-preflight-fail")
|
|
1245
|
+
paths = _make_paths(tmp_path)
|
|
1246
|
+
family = family_for("feature", paths)
|
|
1247
|
+
invocation = parse_invocation(family, "run", ())
|
|
1248
|
+
invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
|
|
1249
|
+
events = []
|
|
1250
|
+
|
|
1251
|
+
monkeypatch.setattr(
|
|
1252
|
+
runners,
|
|
1253
|
+
"load_runtime_config",
|
|
1254
|
+
lambda paths: SimpleNamespace(ai_client=SimpleNamespace(command="fake-ai", platform="codebuddy"), model="", effort=None),
|
|
1255
|
+
)
|
|
1256
|
+
monkeypatch.setattr(runners, "current_branch", lambda _root: "main")
|
|
1257
|
+
monkeypatch.setattr(runners, "commit_preflight_ready_changes", lambda *args, **kwargs: SimpleNamespace(attempted=True, committed=False, result=SimpleNamespace(stderr="commit failed\n", stdout="")))
|
|
1258
|
+
monkeypatch.setattr(runners, "run_git_plan", lambda *args, **kwargs: events.append("branch_create"))
|
|
1259
|
+
monkeypatch.setattr(runners, "start_item", lambda *args, **kwargs: events.append("start_item"))
|
|
1260
|
+
|
|
1261
|
+
with pytest.raises(RuntimeError, match="Preflight dirty workspace preservation failed"):
|
|
1262
|
+
runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
|
|
1263
|
+
|
|
1264
|
+
assert events == []
|
|
1265
|
+
|
|
1266
|
+
|
|
1190
1267
|
def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_path, capsys):
|
|
1191
1268
|
from prizmkit_runtime import runners
|
|
1192
1269
|
from prizmkit_runtime.runner_models import family_for, parse_invocation
|
|
@@ -1212,7 +1289,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
|
|
|
1212
1289
|
assert "────────────────────────────────────────────────────" in captured.err
|
|
1213
1290
|
assert "Feature: F-001 — Readable output" in captured.err
|
|
1214
1291
|
assert "Code retry: 1 / 3" in captured.err
|
|
1215
|
-
assert "
|
|
1292
|
+
assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
|
|
1216
1293
|
assert "Pipeline mode: lite (Tier 1 — Single Agent)" in captured.err
|
|
1217
1294
|
assert "Agents: 1 (critic: disabled)" in captured.err
|
|
1218
1295
|
assert "Spawning AI CLI session: F-001-session-1" in captured.err
|