prizmkit 1.1.127 → 1.1.129

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.
Files changed (50) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/claude/command-adapter.js +2 -75
  3. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +14 -11
  4. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +88 -58
  5. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +4 -3
  6. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +53 -1
  7. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +33 -17
  8. package/bundled/dev-pipeline/prizmkit_runtime/runtime_helper.py +0 -1
  9. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +28 -26
  10. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +1 -1
  11. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +4 -3
  12. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +1 -1
  13. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +3 -3
  14. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
  15. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +22 -21
  16. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +4 -4
  17. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +5 -5
  18. package/bundled/dev-pipeline/templates/sections/bugfix-mission.md +1 -1
  19. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +11 -4
  20. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  21. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +3 -2
  22. package/bundled/dev-pipeline/templates/sections/directory-convention-full.md +3 -2
  23. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +1 -1
  24. package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
  25. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
  26. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
  27. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +12 -5
  28. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -5
  29. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +1 -1
  30. package/bundled/dev-pipeline/templates/sections/phase0-init.md +1 -1
  31. package/bundled/dev-pipeline/templates/sections/refactor-mission.md +1 -1
  32. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +13 -6
  33. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
  34. package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +6 -4
  35. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +1 -1
  36. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +106 -7
  37. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +139 -11
  38. package/bundled/dev-pipeline/tests/test_runtime_helper.py +9 -3
  39. package/bundled/dev-pipeline/tests/test_unified_cli.py +225 -24
  40. package/bundled/skills/_metadata.json +3 -3
  41. package/bundled/skills/feature-planner/SKILL.md +2 -2
  42. package/bundled/skills/prizmkit/SKILL.md +1 -1
  43. package/bundled/skills/prizmkit-code-review/SKILL.md +63 -122
  44. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +9 -36
  45. package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +21 -111
  46. package/package.json +1 -1
  47. package/src/manifest.js +8 -3
  48. package/src/scaffold.js +5 -16
  49. package/bundled/skills/prizmkit-code-review/assets/reviewer-role.json +0 -19
  50. package/bundled/skills/prizmkit-code-review/references/reviewer-agent-prompt.md +0 -120
@@ -71,7 +71,7 @@ Before proceeding past CP-1, verify:
71
71
 
72
72
  ### Plan Self-Check — Orchestrator Direct
73
73
 
74
- Do not spawn a top-level Reviewer subagent for planning analysis. Perform a bounded self-check before implementation:
74
+ Do not delegate planning analysis. The current Main Agent performs a bounded self-check before implementation:
75
75
 
76
76
  1. Re-read only `context-snapshot.md`, `spec.md`, and `plan.md`.
77
77
  2. Cross-check spec.md and plan.md Tasks against the feature description and Verification Gates in the Task Contract.
@@ -1,6 +1,6 @@
1
1
  ### Phase 0: Project Bootstrap
2
2
  - Run `/prizmkit-init` (invoke the prizmkit-init skill)
3
- - Run `{{PYTHON_CMD}} {{INIT_SCRIPT_PATH}} --project-root {{PROJECT_ROOT}} --feature-id {{FEATURE_ID}} --feature-slug {{FEATURE_SLUG}}`
3
+ - Run `{{PYTHON_CMD}} .prizmkit/dev-pipeline/scripts/init-change-artifact.py --project-root {{PROJECT_ROOT}} --feature-id {{FEATURE_ID}} --feature-slug {{FEATURE_SLUG}}`
4
4
  - **CP-0**: Verify `.prizmkit/prizm-docs/root.prizm`, `.prizmkit/config.json` exist
5
5
 
6
6
 
@@ -4,6 +4,6 @@ You are the **refactor session orchestrator**. Execute Refactor {{REFACTOR_ID}}:
4
4
 
5
5
  **CRITICAL**: Preserve observable behavior. Do not change product behavior unless the refactor contract explicitly requires it.
6
6
 
7
- **Execution model**: You implement directly in the current checkout. Do not spawn a top-level Dev subagent. Do not spawn a top-level Reviewer subagent. `/prizmkit-code-review` owns its internal Reviewer Agent filter orchestrator fix loop.
7
+ **Execution model**: You implement directly in the current checkout. Do not spawn a top-level Dev subagent. The current Main Agent runs `/prizmkit-code-review` directly and owns its complete review, repair, verification, and reporting loop without direct or indirect review delegation.
8
8
 
9
9
  **NON-INTERACTIVE MODE**: There is NO human on the other end. Resolve uncertainty from existing code and conservative project patterns.
@@ -1,8 +1,16 @@
1
1
  ### Phase 3: Review — Code Review & Behavior Verification
2
2
 
3
- Run `/prizmkit-code-review` directly with `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/`. The skill first runs a Main-Agent self-review loop for up to ten completed rounds, then semantic risk permits independent Reviewers at limits `low=0`, `medium=1`, `high=2`. High risk starts Reviewer 2 only after Reviewer 1 produced an accepted high finding that was repaired, tested, and reconverged. Reviewer 3 is forbidden.
3
+ Run `/prizmkit-code-review` directly with `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/`. The skill runs a Main-Agent review loop for up to ten completed rounds. The current Main Agent is the only Code Review executor: it reviews the complete current change, rejects unsupported candidate findings, directly repairs accepted findings, verifies repairs, and repeats until `accepted = 0` with no unresolved findings.
4
4
 
5
- Do not spawn a top-level Reviewer subagent. The code-review skill owns its unchanged independent Reviewer prompt, current-checkout preference, forced-worktree changed-content fallback, one bounded infrastructure retry per required Reviewer position, and append-only report progress.
5
+ The Code Review execution boundary is absolute for this phase:
6
+
7
+ - Do not delegate any review work directly or indirectly.
8
+ - Do not invoke another review skill or review workflow.
9
+ - Do not use a general-purpose execution unit for review or relabel delegated work as a finder, verifier, audit, compatibility review, verification, or gap sweep.
10
+ - Direct reading, searching, editing, and testing by the current Main Agent remain allowed.
11
+ - `review-report.md` is the only persisted review artifact; do not append separate Review Notes to another artifact.
12
+
13
+ Missing required evidence, an unsafe repair, a failed verification, or failure to converge by round ten produces `NEEDS_FIXES`.
6
14
 
7
15
  **Gate Check — Final Review Result**:
8
16
 
@@ -12,9 +20,8 @@ Do not spawn a top-level Reviewer subagent. The code-review skill owns its uncha
12
20
 
13
21
  - `REVIEW_PASS` → proceed.
14
22
  - `REVIEW_NEEDS_FIXES` → log unresolved findings and proceed only when they do not block behavior preservation under explicit task policy.
15
- - `REVIEW_BLOCKED` → log infrastructure evidence and stop so recovery can retry.
16
- - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` → inspect progress once, write `failure-log.md`, and stop. Do not accept stale verdict text or replace required independent review with Main-Agent self-review.
23
+ - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` inspect progress once, write `failure-log.md`, and stop. Do not accept stale verdict text or synthesize a passing result outside the skill.
17
24
 
18
- Verify the review explicitly checks behavior preservation, public API compatibility, imports, tests, and rollback assumptions.
25
+ Verify directly that the review checks behavior preservation, public API compatibility, imports, tests, and rollback assumptions.
19
26
 
20
- **Checkpoint update**: Set step `prizmkit-code-review` to `completed` only for an accepted valid Final Result. Do not complete the checkpoint for blocked, incomplete, or invalid reports.
27
+ **Checkpoint update**: Set step `prizmkit-code-review` to `completed` only for an accepted valid Final Result. Do not complete the checkpoint for incomplete or invalid reports.
@@ -3,7 +3,7 @@
3
3
  - Use L1 skills: `/prizmkit-plan`, `/prizmkit-implement`, `/prizmkit-code-review`, `/prizmkit-retrospective`, `/prizmkit-committer`.
4
4
  - All refactor skills use `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/`.
5
5
  - Behavior preservation is the first priority.
6
- - Do not spawn top-level Dev or Reviewer subagents.
6
+ - Do not spawn a top-level Dev implementation subagent; Code Review runs only in the current Main Agent without delegation.
7
7
  - Main orchestrator runs `/prizmkit-code-review` directly.
8
8
  - Commit with `refactor(<scope>):`, not `feat:` or `fix:`.
9
9
  - Checkpoint state is the recovery truth; skipped steps must be marked `skipped`.
@@ -1,6 +1,8 @@
1
- ## Subagent Timeout Recovery
1
+ ## Normal Work Delegation Timeout Recovery
2
2
 
3
- If any agent times out:
3
+ This recovery path applies only to normal work delegated outside Code Review. Code Review never uses delegated execution or this retry procedure.
4
+
5
+ If a normal work execution unit times out:
4
6
  1. `{{RUNTIME_HELPER_CMD}} artifact list .prizmkit/specs/{{FEATURE_SLUG}}` — check what exists
5
- 2. If `context-snapshot.md` exists: open recovery prompt with `"Read .prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md for project context and any Implementation Log/Review Notes from previous agents. Run git diff HEAD to see actual code changes already made. Do NOT re-read individual source files unless the File Manifest directs you to."` + only remaining steps + `model: "lite"`
6
- 3. Max 2 retries per phase. After 2 failures, orchestrator completes the work directly and appends a Recovery Note to context-snapshot.md.
7
+ 2. If `context-snapshot.md` exists: open a recovery prompt with `"Read .prizmkit/specs/{{FEATURE_SLUG}}/context-snapshot.md for project context and the Implementation Log. Run git diff HEAD to see actual code changes already made. Do NOT re-read individual source files unless the File Manifest directs you to."` + only remaining steps + `model: "lite"`
8
+ 3. Max 2 retries per phase. After 2 failures, the Main Agent completes the work directly and appends a Recovery Note to context-snapshot.md.
@@ -6,7 +6,7 @@ Use this protocol whenever the post-review PrizmKit Test gate fails. Its purpose
6
6
 
7
7
  | Class | Meaning | Required Action | May Continue? |
8
8
  |-------|---------|-----------------|---------------|
9
- | Baseline failure | Existed before this session | Document in Implementation Log or review notes | Yes |
9
+ | Baseline failure | Existed before this session | Document in the Implementation Log or `failure-log.md` | Yes |
10
10
  | New regression | Introduced by this session | Fix before reporting success | No |
11
11
  | Brittle test | Test expectation/setup is wrong for the intended behavior | Fix the test or environment setup, then rerun | Only after fixed or documented as blocked |
12
12
  | Environment/tooling failure | External tool, network, install, or local environment prevents verification | Document in `failure-log.md` with impact on gates | Only if no Verification Gate is blocked |
@@ -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",
@@ -882,9 +947,39 @@ class TestDirectOrchestratorImplementationPrompts:
882
947
  assert FORBIDDEN_TEST_CMD_PLACEHOLDER not in rendered
883
948
  assert FORBIDDEN_THREE_STRIKE not in rendered
884
949
  assert FORBIDDEN_TWENTY_STEP not in rendered
885
- assert "Main-Agent self-review loop" in rendered
886
- assert "low=0" in rendered and "medium=1" in rendered and "high=2" in rendered
887
- assert "Reviewer 3 is forbidden" in rendered
950
+ assert "Main-Agent review loop" in rendered
951
+ assert "up to ten completed rounds" in rendered
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
954
+ assert "low=0" not in rendered
955
+ assert "Reviewer 3" not in rendered
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)
888
983
 
889
984
  def test_retained_tier3_fallback_template_uses_direct_orchestrator_implementation(self):
890
985
  rendered = Path("dev-pipeline/templates/bootstrap-tier3.md").read_text(encoding="utf-8")
@@ -1443,8 +1538,10 @@ class TestHeadlessPromptCleanupF033:
1443
1538
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-mixed", platform="claude")
1444
1539
 
1445
1540
  assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
1446
- assert "Main-Agent self-review" in refactor_prompt
1447
- assert "Reviewer 3 is forbidden" in refactor_prompt
1541
+ assert "Main-Agent review loop" in refactor_prompt
1542
+ assert "The current Main Agent is the only Code Review executor" in refactor_prompt
1543
+ assert "Do not invoke another review skill or review workflow" in refactor_prompt
1544
+ assert "Reviewer 3" not in refactor_prompt
1448
1545
 
1449
1546
  def test_bugfix_refactor_explicit_codex_override(self, tmp_path, monkeypatch):
1450
1547
  from generate_bugfix_prompt import build_replacements as bugfix_build_replacements
@@ -1465,8 +1562,10 @@ class TestHeadlessPromptCleanupF033:
1465
1562
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-codex", platform="claude", explicit_platform="codex")
1466
1563
 
1467
1564
  assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
1468
- assert "Main-Agent self-review" in refactor_prompt
1469
- assert "Reviewer 3 is forbidden" in refactor_prompt
1565
+ assert "Main-Agent review loop" in refactor_prompt
1566
+ assert "The current Main Agent is the only Code Review executor" in refactor_prompt
1567
+ assert "Do not invoke another review skill or review workflow" in refactor_prompt
1568
+ assert "Reviewer 3" not in refactor_prompt
1470
1569
 
1471
1570
 
1472
1571
  class TestFeatureBootstrapShellExtraction:
@@ -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
- branch_events.append(("merge", plan.commands[5].args[2]))
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 — Orchestrator + Reviewer)" in captured.err
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 test_preflight_ready_commit_includes_tracked_framework_support_but_excludes_untracked_support_and_state(tmp_path):
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.committed is True
2135
- assert {path.relative_to(tmp_path).as_posix() for path in tracked_support_paths}.issubset(committed_files)
2136
- assert not {path.relative_to(tmp_path).as_posix() for path in untracked_support_paths}.intersection(committed_files)
2137
- assert _head_message(tmp_path) == "chore: ready for B-001"
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
 
@@ -100,11 +100,17 @@ class TestArtifactAndTextHelpers:
100
100
  report = tmp_path / "review-report.md"
101
101
  report.write_text(
102
102
  "## Final Result\n- Verdict: PASS\n\n"
103
- "## Final Result\n- Verdict: BLOCKED\n",
103
+ "## Final Result\n- Verdict: NEEDS_FIXES\n",
104
104
  encoding="utf-8",
105
105
  )
106
- blocked = run_helper("text", "final-verdict", str(report))
107
- assert first_line(blocked) == "REVIEW_BLOCKED"
106
+ needs_fixes = run_helper("text", "final-verdict", str(report))
107
+ assert needs_fixes.returncode == 0
108
+ assert first_line(needs_fixes) == "REVIEW_NEEDS_FIXES"
109
+
110
+ report.write_text("## Final Result\n- Verdict: BLOCKED\n", encoding="utf-8")
111
+ retired = run_helper("text", "final-verdict", str(report))
112
+ assert retired.returncode != 0
113
+ assert first_line(retired) == "REVIEW_INVALID"
108
114
 
109
115
  report.write_text("## Final Result\n- Verdict: UNKNOWN\n", encoding="utf-8")
110
116
  invalid = run_helper("text", "final-verdict", str(report))