prizmkit 1.1.114 → 1.1.116

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 (77) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/.env.example +0 -1
  3. package/bundled/dev-pipeline/README.md +4 -6
  4. package/bundled/dev-pipeline/SCHEMA_ANALYSIS.md +40 -518
  5. package/bundled/dev-pipeline/assets/prizm-dev-team-integration.md +27 -99
  6. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +283 -0
  7. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +3 -13
  8. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +2 -0
  9. package/bundled/dev-pipeline/prizmkit_runtime/heartbeat.py +0 -1
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_bookkeeping.py +14 -2
  11. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +25 -31
  12. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -8
  13. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +0 -3
  14. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +13 -7
  15. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +13 -1
  16. package/bundled/dev-pipeline/scripts/continuation.py +8 -40
  17. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +76 -261
  18. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +16 -35
  19. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -34
  20. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +6 -0
  21. package/bundled/dev-pipeline/scripts/init-pipeline.py +6 -0
  22. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +6 -0
  23. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -1
  24. package/bundled/dev-pipeline/scripts/prompt_framework.py +114 -63
  25. package/bundled/dev-pipeline/scripts/update-bug-status.py +3 -7
  26. package/bundled/dev-pipeline/scripts/update-checkpoint.py +54 -22
  27. package/bundled/dev-pipeline/scripts/update-feature-status.py +3 -7
  28. package/bundled/dev-pipeline/scripts/update-refactor-status.py +3 -7
  29. package/bundled/dev-pipeline/scripts/utils.py +6 -37
  30. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  31. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +1 -1
  32. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +9 -91
  33. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +9 -66
  34. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +10 -16
  35. package/bundled/dev-pipeline/templates/feature-list-schema.json +32 -17
  36. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +0 -16
  37. package/bundled/dev-pipeline/templates/refactor-list-schema.json +10 -16
  38. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +1 -1
  39. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  40. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -1
  41. package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +0 -1
  42. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
  43. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
  44. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +1 -1
  45. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  46. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +164 -0
  47. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +48 -76
  48. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +25 -3
  49. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +25 -5
  50. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +329 -16
  51. package/bundled/dev-pipeline/tests/test_unified_cli.py +40 -9
  52. package/bundled/skills/_metadata.json +1 -1
  53. package/bundled/skills/bug-planner/SKILL.md +5 -4
  54. package/bundled/skills/bug-planner/references/verification.md +29 -0
  55. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +8 -0
  56. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +2 -2
  57. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +1 -6
  58. package/bundled/skills/feature-pipeline-launcher/SKILL.md +6 -12
  59. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +0 -1
  60. package/bundled/skills/feature-planner/SKILL.md +13 -12
  61. package/bundled/skills/feature-planner/assets/planning-guide.md +2 -2
  62. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +8 -11
  63. package/bundled/skills/prizmkit-plan/SKILL.md +15 -1
  64. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +101 -0
  65. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +5 -5
  66. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +2 -7
  67. package/bundled/skills/refactor-planner/SKILL.md +10 -8
  68. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +9 -0
  69. package/bundled/team/prizm-dev-team.json +2 -9
  70. package/package.json +1 -1
  71. package/bundled/agents/prizm-dev-team-critic.md +0 -179
  72. package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +0 -8
  73. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +0 -22
  74. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +0 -61
  75. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +0 -38
  76. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +0 -22
  77. package/bundled/skills/bug-planner/references/critic-and-verification.md +0 -44
@@ -320,7 +320,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
320
320
  invocation = parse_invocation(
321
321
  family,
322
322
  "run",
323
- ("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5", "--mode", "full", "--critic"),
323
+ ("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5", "--mode", "full"),
324
324
  )
325
325
  env = RunnerEnvironment.from_env(
326
326
  {
@@ -340,7 +340,6 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
340
340
  assert invocation.max_retries == 7
341
341
  assert invocation.max_infra_retries == 5
342
342
  assert invocation.mode == "full"
343
- assert invocation.critic is True
344
343
  assert env.stop_on_failure is True
345
344
  assert env.auto_push is True
346
345
  assert env.dev_branch == "dev/custom"
@@ -765,7 +764,6 @@ def test_prompt_generation_result_preserves_per_item_model(monkeypatch, tmp_path
765
764
  "model": "per-item-model",
766
765
  "pipeline_mode": "standard",
767
766
  "agent_count": 3,
768
- "critic_enabled": False,
769
767
  }
770
768
  ),
771
769
  stderr="",
@@ -815,7 +813,6 @@ def test_prompt_generation_passes_explicit_execution_root_to_generator(monkeypat
815
813
  "model": "",
816
814
  "pipeline_mode": "lite",
817
815
  "agent_count": 1,
818
- "critic_enabled": False,
819
816
  }
820
817
  ),
821
818
  stderr="",
@@ -881,7 +878,6 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
881
878
  model="per-item-model",
882
879
  pipeline_mode="lite",
883
880
  agent_count=1,
884
- critic_enabled=False,
885
881
  checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json",
886
882
  artifact_path=execution_root / ".prizmkit" / "specs" / "001-low",
887
883
  raw={},
@@ -1144,7 +1140,6 @@ def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and
1144
1140
  model="",
1145
1141
  pipeline_mode="lite",
1146
1142
  agent_count=1,
1147
- critic_enabled=False,
1148
1143
  checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-support" / "workflow-checkpoint.json",
1149
1144
  artifact_path=execution_root / ".prizmkit" / "specs" / "001-support",
1150
1145
  raw={},
@@ -1294,7 +1289,7 @@ def test_no_worktree_recovers_completed_pending_branch_before_new_session(monkey
1294
1289
  artifact_dir = paths.project_root / ".prizmkit" / "specs" / "001-low"
1295
1290
  artifact_dir.mkdir(parents=True)
1296
1291
  (artifact_dir / "workflow-checkpoint.json").write_text(
1297
- json.dumps({"steps": [{"id": "S01", "skill": "done", "status": "completed"}]}),
1292
+ json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "completed"}]}),
1298
1293
  encoding="utf-8",
1299
1294
  )
1300
1295
  updates = []
@@ -1476,15 +1471,25 @@ def test_no_worktree_runner_ignores_recovery_side_effect_branch(monkeypatch, tmp
1476
1471
  assert observed.updates[-1][2]["base_branch"] == "main"
1477
1472
 
1478
1473
 
1479
- def test_no_worktree_preflight_commit_failure_aborts_before_branch_or_status(monkeypatch, tmp_path):
1474
+ @pytest.mark.parametrize(
1475
+ ("kind", "item_id", "branch"),
1476
+ [
1477
+ ("feature", "F-001", "dev/F-001-preflight-fail"),
1478
+ ("bugfix", "B-001", "bugfix/B-001-preflight-fail"),
1479
+ ("refactor", "R-001", "refactor/R-001-preflight-fail"),
1480
+ ],
1481
+ )
1482
+ def test_preflight_commit_failure_aborts_before_branch_worktree_or_status_for_all_families(monkeypatch, tmp_path, kind, item_id, branch):
1480
1483
  from prizmkit_runtime import runners
1481
1484
  from prizmkit_runtime.runner_models import family_for, parse_invocation
1482
1485
 
1483
- monkeypatch.setenv("DEV_BRANCH", "dev/F-001-preflight-fail")
1484
- paths = _make_paths(tmp_path)
1485
- family = family_for("feature", paths)
1486
+ monkeypatch.setenv("DEV_BRANCH", branch)
1487
+ monkeypatch.setenv("USE_WORKTREE", "1")
1488
+ paths = _make_paths(tmp_path / kind)
1489
+ family = family_for(kind, paths)
1486
1490
  invocation = parse_invocation(family, "run", ())
1487
- invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
1491
+ plan_path = {"feature": paths.feature_plan, "bugfix": paths.bugfix_plan, "refactor": paths.refactor_plan}[kind]
1492
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": plan_path})
1488
1493
  events = []
1489
1494
 
1490
1495
  monkeypatch.setattr(
@@ -1495,10 +1500,11 @@ def test_no_worktree_preflight_commit_failure_aborts_before_branch_or_status(mon
1495
1500
  monkeypatch.setattr(runners, "current_branch", lambda _root: "main")
1496
1501
  monkeypatch.setattr(runners, "commit_preflight_ready_changes", lambda *args, **kwargs: SimpleNamespace(attempted=True, committed=False, result=SimpleNamespace(stderr="commit failed\n", stdout="")))
1497
1502
  monkeypatch.setattr(runners, "run_git_plan", lambda *args, **kwargs: events.append("branch_create"))
1503
+ monkeypatch.setattr(runners, "ensure_linked_worktree", lambda *args, **kwargs: events.append("worktree_create"), raising=False)
1498
1504
  monkeypatch.setattr(runners, "start_item", lambda *args, **kwargs: events.append("start_item"))
1499
1505
 
1500
1506
  with pytest.raises(RuntimeError, match="Preflight dirty workspace preservation failed"):
1501
- runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
1507
+ runners._process_item(family, invocation, item_id, paths, initial_metadata={})
1502
1508
 
1503
1509
  assert events == []
1504
1510
 
@@ -1530,7 +1536,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
1530
1536
  assert "Code retry: 1 / 3" in captured.err
1531
1537
  assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
1532
1538
  assert "Pipeline mode: lite (Tier 1 — Single Agent)" in captured.err
1533
- assert "Agents: 1 (critic: disabled)" in captured.err
1539
+ assert "Agents: 1" in captured.err
1534
1540
  assert "Spawning AI CLI session: F-001-session-1" in captured.err
1535
1541
  assert "Session result: success" in captured.err
1536
1542
 
@@ -1564,6 +1570,190 @@ def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_pa
1564
1570
  assert ensure_calls == [("main", "dev/F-001-interrupt")]
1565
1571
 
1566
1572
 
1573
+ def _init_bookkeeping_repo(project_root):
1574
+ subprocess.run(["git", "init", "-b", "main"], cwd=project_root, check=True, stdout=subprocess.DEVNULL)
1575
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=project_root, check=True)
1576
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=project_root, check=True)
1577
+ (project_root / "staged.txt").write_text("base staged\n", encoding="utf-8")
1578
+ (project_root / "unstaged.txt").write_text("base unstaged\n", encoding="utf-8")
1579
+ subprocess.run(["git", "add", "staged.txt", "unstaged.txt"], cwd=project_root, check=True)
1580
+ subprocess.run(["git", "commit", "-m", "base"], cwd=project_root, check=True, stdout=subprocess.DEVNULL)
1581
+
1582
+
1583
+ def _head_commit_count(project_root):
1584
+ return int(
1585
+ subprocess.run(
1586
+ ["git", "rev-list", "--count", "HEAD"],
1587
+ cwd=project_root,
1588
+ check=True,
1589
+ stdout=subprocess.PIPE,
1590
+ text=True,
1591
+ ).stdout.strip()
1592
+ )
1593
+
1594
+
1595
+ def _head_message(project_root):
1596
+ return subprocess.run(
1597
+ ["git", "log", "-1", "--format=%s"],
1598
+ cwd=project_root,
1599
+ check=True,
1600
+ stdout=subprocess.PIPE,
1601
+ text=True,
1602
+ ).stdout.strip()
1603
+
1604
+
1605
+ def _head_files(project_root):
1606
+ return set(
1607
+ subprocess.run(
1608
+ ["git", "show", "--name-only", "--format=", "HEAD"],
1609
+ cwd=project_root,
1610
+ check=True,
1611
+ stdout=subprocess.PIPE,
1612
+ text=True,
1613
+ ).stdout.splitlines()
1614
+ )
1615
+
1616
+
1617
+ @pytest.mark.parametrize(
1618
+ ("scenario", "expected_files"),
1619
+ [
1620
+ ("staged_only", {"staged.txt"}),
1621
+ ("unstaged_only", {"unstaged.txt"}),
1622
+ ("mixed", {"staged.txt", "unstaged.txt", "new-visible.txt"}),
1623
+ ],
1624
+ )
1625
+ def test_preflight_ready_commit_preserves_staged_unstaged_and_mixed_visible_changes(tmp_path, scenario, expected_files):
1626
+ from prizmkit_runtime.gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES
1627
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1628
+
1629
+ _init_bookkeeping_repo(tmp_path)
1630
+ if scenario in {"staged_only", "mixed"}:
1631
+ (tmp_path / "staged.txt").write_text("staged change\n", encoding="utf-8")
1632
+ subprocess.run(["git", "add", "staged.txt"], cwd=tmp_path, check=True)
1633
+ if scenario in {"unstaged_only", "mixed"}:
1634
+ (tmp_path / "unstaged.txt").write_text("unstaged change\n", encoding="utf-8")
1635
+ if scenario == "mixed":
1636
+ (tmp_path / "new-visible.txt").write_text("new visible\n", encoding="utf-8")
1637
+
1638
+ before_count = _head_commit_count(tmp_path)
1639
+ result = commit_preflight_ready_changes(tmp_path, "F-123")
1640
+ visible_status = subprocess.run(
1641
+ ["git", "status", "--porcelain", "--", ".", *HIDDEN_TOOL_WORKTREE_EXCLUDES],
1642
+ cwd=tmp_path,
1643
+ check=True,
1644
+ stdout=subprocess.PIPE,
1645
+ text=True,
1646
+ ).stdout
1647
+
1648
+ assert result.attempted is True
1649
+ assert result.committed is True
1650
+ assert _head_commit_count(tmp_path) == before_count + 1
1651
+ assert _head_message(tmp_path) == "chore: ready for F-123"
1652
+ assert expected_files.issubset(_head_files(tmp_path))
1653
+ assert visible_status == ""
1654
+
1655
+
1656
+ def test_preflight_ready_commit_excludes_hidden_support_assets_and_state(tmp_path):
1657
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1658
+
1659
+ _init_bookkeeping_repo(tmp_path)
1660
+ (tmp_path / "unstaged.txt").write_text("visible change\n", encoding="utf-8")
1661
+ hidden_paths = [
1662
+ tmp_path / ".claude" / "settings.json",
1663
+ tmp_path / ".codebuddy" / "skills" / "local.md",
1664
+ tmp_path / ".agents" / "skills" / "local.md",
1665
+ tmp_path / ".codex" / "config.toml",
1666
+ tmp_path / ".prizmkit" / "state" / "features" / "F-001" / "status.json",
1667
+ tmp_path / ".claude" / "worktrees" / "agent" / "scratch.txt",
1668
+ ]
1669
+ for path in hidden_paths:
1670
+ path.parent.mkdir(parents=True, exist_ok=True)
1671
+ path.write_text("hidden support\n", encoding="utf-8")
1672
+
1673
+ result = commit_preflight_ready_changes(tmp_path, "F-124")
1674
+ committed_files = _head_files(tmp_path)
1675
+ hidden_status = subprocess.run(
1676
+ ["git", "status", "--porcelain", "--", ".claude", ".codebuddy", ".agents", ".codex", ".prizmkit/state"],
1677
+ cwd=tmp_path,
1678
+ check=True,
1679
+ stdout=subprocess.PIPE,
1680
+ text=True,
1681
+ ).stdout
1682
+
1683
+ assert result.committed is True
1684
+ assert committed_files == {"unstaged.txt"}
1685
+ assert ".claude" in hidden_status or ".prizmkit/state" in hidden_status
1686
+
1687
+
1688
+ def test_preflight_ready_commit_noops_when_workspace_has_no_visible_dirty_content(tmp_path):
1689
+ from prizmkit_runtime.runner_bookkeeping import commit_preflight_ready_changes
1690
+
1691
+ _init_bookkeeping_repo(tmp_path)
1692
+ (tmp_path / ".claude" / "worktrees" / "agent").mkdir(parents=True)
1693
+ (tmp_path / ".claude" / "worktrees" / "agent" / "scratch.txt").write_text("hidden only\n", encoding="utf-8")
1694
+ before_count = _head_commit_count(tmp_path)
1695
+
1696
+ result = commit_preflight_ready_changes(tmp_path, "F-125")
1697
+
1698
+ assert result.attempted is False
1699
+ assert result.committed is False
1700
+ assert _head_commit_count(tmp_path) == before_count
1701
+
1702
+
1703
+ def test_process_item_creates_one_ready_commit_before_branch_setup_for_mixed_visible_changes(monkeypatch, tmp_path):
1704
+ from prizmkit_runtime import runner_bookkeeping, runners
1705
+ from prizmkit_runtime.gitops import HIDDEN_TOOL_WORKTREE_EXCLUDES
1706
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
1707
+
1708
+ paths = _make_paths(tmp_path)
1709
+ _init_bookkeeping_repo(paths.project_root)
1710
+ (paths.project_root / "staged.txt").write_text("staged before run\n", encoding="utf-8")
1711
+ subprocess.run(["git", "add", "staged.txt"], cwd=paths.project_root, check=True)
1712
+ (paths.project_root / "unstaged.txt").write_text("unstaged before run\n", encoding="utf-8")
1713
+ before_count = _head_commit_count(paths.project_root)
1714
+ branch_events = []
1715
+
1716
+ monkeypatch.setenv("DEV_BRANCH", "dev/F-001-ready")
1717
+ family = family_for("feature", paths)
1718
+ invocation = parse_invocation(family, "run", ())
1719
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
1720
+ _install_runner_session_fakes(monkeypatch, runners)
1721
+ monkeypatch.setattr(runners, "commit_preflight_ready_changes", runner_bookkeeping.commit_preflight_ready_changes)
1722
+
1723
+ def fake_run_git_plan(project_root, plan):
1724
+ if plan.name == "branch_create":
1725
+ branch_events.append(
1726
+ {
1727
+ "commit_count": _head_commit_count(project_root),
1728
+ "message": _head_message(project_root),
1729
+ "visible_status": subprocess.run(
1730
+ ["git", "status", "--porcelain", "--", ".", *HIDDEN_TOOL_WORKTREE_EXCLUDES],
1731
+ cwd=project_root,
1732
+ check=True,
1733
+ stdout=subprocess.PIPE,
1734
+ text=True,
1735
+ ).stdout,
1736
+ }
1737
+ )
1738
+ return SimpleNamespace(ok=True, plan=plan, results=())
1739
+
1740
+ monkeypatch.setattr(runners, "run_git_plan", fake_run_git_plan)
1741
+
1742
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
1743
+
1744
+ assert status == "completed"
1745
+ assert branch_events == [
1746
+ {
1747
+ "commit_count": before_count + 1,
1748
+ "message": "chore: ready for F-001",
1749
+ "visible_status": "",
1750
+ }
1751
+ ]
1752
+ assert _head_commit_count(paths.project_root) == before_count + 1
1753
+ assert {"staged.txt", "unstaged.txt"}.issubset(_head_files(paths.project_root))
1754
+
1755
+
1756
+
1567
1757
  def test_bookkeeping_commits_only_runner_owned_paths(tmp_path):
1568
1758
  from prizmkit_runtime.runner_bookkeeping import commit_final_bookkeeping
1569
1759
  from prizmkit_runtime.runner_models import RunnerFamily
@@ -1667,7 +1857,7 @@ def test_classification_accepts_late_infra_error_with_completed_checkpoint_and_c
1667
1857
  artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
1668
1858
  artifact_dir.mkdir(parents=True)
1669
1859
  checkpoint = artifact_dir / "workflow-checkpoint.json"
1670
- checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "status": "completed"}]}), encoding="utf-8")
1860
+ checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
1671
1861
  log = tmp_path / "session.log"
1672
1862
  log.write_text("model quota exceeded; API error after commit", encoding="utf-8")
1673
1863
  raw = AISessionResult(
@@ -1792,7 +1982,7 @@ def test_classification_accepts_terminal_success_with_completed_checkpoint_witho
1792
1982
  artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
1793
1983
  artifact_dir.mkdir(parents=True)
1794
1984
  checkpoint = artifact_dir / "workflow-checkpoint.json"
1795
- checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "status": "completed"}]}), encoding="utf-8")
1985
+ checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
1796
1986
  (artifact_dir / "completion-summary.json").write_text("{}", encoding="utf-8")
1797
1987
  log = tmp_path / "session.log"
1798
1988
  log.write_text("terminal success", encoding="utf-8")
@@ -2207,3 +2397,126 @@ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path)
2207
2397
  else:
2208
2398
  assert summary["auto_skipped"] == 1
2209
2399
 
2400
+
2401
+
2402
+ def _init_git_repo_for_invalid_checkpoint_test(tmp_path):
2403
+ subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2404
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
2405
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
2406
+ (tmp_path / "base.txt").write_text("base", encoding="utf-8")
2407
+ subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
2408
+ subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2409
+ return subprocess.run(["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip()
2410
+
2411
+
2412
+ def _terminal_success_result(tmp_path):
2413
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
2414
+ from prizmkit_runtime.status import ProgressSummary
2415
+
2416
+ log = tmp_path / "session.log"
2417
+ log.write_text("terminal success", encoding="utf-8")
2418
+ return AISessionResult(
2419
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
2420
+ exit_code=0,
2421
+ timed_out=False,
2422
+ termination_reason=None,
2423
+ stale_marker=None,
2424
+ fatal_error_code="",
2425
+ progress_summary=ProgressSummary(result_subtype="success", terminal_result_text="Completed"),
2426
+ session_log=log,
2427
+ backup_log=tmp_path / "backup.log",
2428
+ log_recovery=None,
2429
+ started_epoch=0,
2430
+ ended_epoch=1,
2431
+ )
2432
+
2433
+
2434
+ def test_invalid_existing_checkpoint_blocks_terminal_success_with_completion_summary(tmp_path):
2435
+ from prizmkit_runtime.runner_classification import classify_session
2436
+ from prizmkit_runtime.runner_models import PromptGenerationResult
2437
+
2438
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
2439
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
2440
+ artifact_dir.mkdir(parents=True)
2441
+ checkpoint = artifact_dir / "workflow-checkpoint.json"
2442
+ checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "not-valid"}]}), encoding="utf-8")
2443
+ (artifact_dir / "completion-summary.json").write_text("{}", encoding="utf-8")
2444
+ prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
2445
+
2446
+ classification = classify_session(_terminal_success_result(tmp_path), project_root=tmp_path, base_head=base_head, prompt=prompt)
2447
+
2448
+ assert classification.session_status == "crashed"
2449
+ assert classification.reason == "invalid_workflow_checkpoint:invalid_status"
2450
+
2451
+
2452
+ def test_invalid_existing_checkpoint_blocks_head_change_success(tmp_path):
2453
+ from prizmkit_runtime.runner_classification import classify_session
2454
+ from prizmkit_runtime.runner_models import PromptGenerationResult
2455
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
2456
+ from prizmkit_runtime.status import ProgressSummary
2457
+
2458
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
2459
+ (tmp_path / "done.txt").write_text("done", encoding="utf-8")
2460
+ subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
2461
+ subprocess.run(["git", "commit", "-m", "done"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2462
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
2463
+ artifact_dir.mkdir(parents=True)
2464
+ checkpoint = artifact_dir / "workflow-checkpoint.json"
2465
+ checkpoint.write_text(json.dumps({"steps": [{"id": "bad", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
2466
+ raw = AISessionResult(
2467
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
2468
+ exit_code=1,
2469
+ timed_out=False,
2470
+ termination_reason=None,
2471
+ stale_marker=None,
2472
+ fatal_error_code="context_overflow",
2473
+ progress_summary=ProgressSummary(fatal_error_code="context_overflow"),
2474
+ session_log=tmp_path / "session.log",
2475
+ backup_log=tmp_path / "backup.log",
2476
+ log_recovery=None,
2477
+ started_epoch=0,
2478
+ ended_epoch=1,
2479
+ )
2480
+ raw.session_log.write_text("context overflow", encoding="utf-8")
2481
+ prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
2482
+
2483
+ classification = classify_session(raw, project_root=tmp_path, base_head=base_head, prompt=prompt)
2484
+
2485
+ assert classification.session_status == "crashed"
2486
+ assert classification.reason == "invalid_workflow_checkpoint:malformed_step_id"
2487
+
2488
+
2489
+ def test_invalid_existing_checkpoint_blocks_workspace_auto_commit_success(tmp_path):
2490
+ from prizmkit_runtime.runner_classification import classify_session
2491
+ from prizmkit_runtime.runner_models import PromptGenerationResult
2492
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
2493
+ from prizmkit_runtime.status import ProgressSummary
2494
+
2495
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
2496
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
2497
+ artifact_dir.mkdir(parents=True)
2498
+ checkpoint = artifact_dir / "workflow-checkpoint.json"
2499
+ checkpoint.write_text(json.dumps({"steps": []}), encoding="utf-8")
2500
+ (tmp_path / "dirty.txt").write_text("dirty", encoding="utf-8")
2501
+ log = tmp_path / "session.log"
2502
+ log.write_text("clean exit but dirty workspace", encoding="utf-8")
2503
+ raw = AISessionResult(
2504
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
2505
+ exit_code=0,
2506
+ timed_out=False,
2507
+ termination_reason=None,
2508
+ stale_marker=None,
2509
+ fatal_error_code="",
2510
+ progress_summary=ProgressSummary(result_subtype="", terminal_result_text=""),
2511
+ session_log=log,
2512
+ backup_log=tmp_path / "backup.log",
2513
+ log_recovery=None,
2514
+ started_epoch=0,
2515
+ ended_epoch=1,
2516
+ )
2517
+ prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
2518
+
2519
+ classification = classify_session(raw, project_root=tmp_path, base_head=base_head, prompt=prompt)
2520
+
2521
+ assert classification.session_status == "crashed"
2522
+ assert classification.reason == "invalid_workflow_checkpoint:empty_steps"
@@ -793,9 +793,6 @@ def test_session_preamble_writes_redacted_start_command_for_all_launch_profiles(
793
793
 
794
794
  def test_headless_subagent_prompt_guidance_keeps_no_worktree_boundary_visible():
795
795
  guidance_files = [
796
- REPO_ROOT / "dev-pipeline" / "templates" / "sections" / "phase-critic-plan.md",
797
- REPO_ROOT / "dev-pipeline" / "templates" / "sections" / "phase-critic-plan-full.md",
798
- REPO_ROOT / "dev-pipeline" / "templates" / "agent-prompts" / "critic-plan-challenge.md",
799
796
  REPO_ROOT / "dev-pipeline" / "templates" / "bootstrap-tier2.md",
800
797
  REPO_ROOT / "dev-pipeline" / "templates" / "bootstrap-tier3.md",
801
798
  REPO_ROOT / "core" / "skills" / "prizmkit-skill" / "prizmkit-code-review" / "SKILL.md",
@@ -1029,12 +1026,13 @@ def test_compact_progress_line_hides_stale_until_no_progress(tmp_path):
1029
1026
 
1030
1027
  session_log = tmp_path / "session.log"
1031
1028
  progress = tmp_path / "progress.json"
1029
+ checkpoint = tmp_path / "workflow-checkpoint.json"
1032
1030
  progress.write_text(
1033
1031
  json.dumps(
1034
1032
  {
1035
1033
  "child_total_bytes": 2 * 1024 * 1024,
1036
1034
  "subagent_spawn_count": 5,
1037
- "current_phase": "retrospective",
1035
+ "current_phase": "stream-derived-phase",
1038
1036
  "current_tool": "Read",
1039
1037
  "message_count": 465,
1040
1038
  "total_tool_calls": 412,
@@ -1042,20 +1040,28 @@ def test_compact_progress_line_hides_stale_until_no_progress(tmp_path):
1042
1040
  ),
1043
1041
  encoding="utf-8",
1044
1042
  )
1043
+ checkpoint.write_text(
1044
+ json.dumps({"steps": [
1045
+ {"id": "S01", "skill": "plan", "name": "Plan", "status": "completed"},
1046
+ {"id": "S02", "skill": "retro", "name": "Retrospective", "status": "pending", "depends_on": "S01"},
1047
+ ]}),
1048
+ encoding="utf-8",
1049
+ )
1045
1050
  running = HeartbeatState(last_seen_epoch=1.0, stale=False, details="progress", stale_seconds=0, log_size=4 * 1024 * 1024)
1046
1051
  brief_pause = HeartbeatState(last_seen_epoch=1.0, stale=False, details="no progress", stale_seconds=125, log_size=4 * 1024 * 1024)
1047
1052
  paused = HeartbeatState(last_seen_epoch=1.0, stale=True, details="no progress", stale_seconds=15 * 60, log_size=4 * 1024 * 1024)
1048
1053
  resumed = HeartbeatState(last_seen_epoch=3.0, stale=False, details="progress", stale_seconds=0, log_size=5 * 1024 * 1024)
1049
1054
 
1050
- running_line = _compact_progress_line(0, session_log, progress, running, stale_threshold_seconds=15 * 60)
1051
- brief_pause_line = _compact_progress_line(0, session_log, progress, brief_pause, stale_threshold_seconds=15 * 60)
1052
- paused_line = _compact_progress_line(0, session_log, progress, paused, stale_threshold_seconds=15 * 60)
1053
- resumed_line = _compact_progress_line(0, session_log, progress, resumed, stale_threshold_seconds=15 * 60)
1055
+ running_line = _compact_progress_line(0, session_log, progress, running, checkpoint_path=checkpoint, stale_threshold_seconds=15 * 60)
1056
+ brief_pause_line = _compact_progress_line(0, session_log, progress, brief_pause, checkpoint_path=checkpoint, stale_threshold_seconds=15 * 60)
1057
+ paused_line = _compact_progress_line(0, session_log, progress, paused, checkpoint_path=checkpoint, stale_threshold_seconds=15 * 60)
1058
+ resumed_line = _compact_progress_line(0, session_log, progress, resumed, checkpoint_path=checkpoint, stale_threshold_seconds=15 * 60)
1054
1059
 
1055
1060
  assert "▶ [HEARTBEAT]" in running_line
1056
1061
  assert "log: 4MB" in running_line
1057
1062
  assert "child: 2MB/5" in running_line
1058
- assert "phase: retrospective" in running_line
1063
+ assert "phase: Retrospective" in running_line
1064
+ assert "stream-derived-phase" not in running_line
1059
1065
  assert "tool: Read" in running_line
1060
1066
  assert "msgs: 465" in running_line
1061
1067
  assert "412 tool calls" in running_line
@@ -1068,6 +1074,31 @@ def test_compact_progress_line_hides_stale_until_no_progress(tmp_path):
1068
1074
  assert "log: 5MB" in resumed_line
1069
1075
  assert "stale:" not in resumed_line
1070
1076
 
1077
+ def test_compact_progress_line_omits_phase_without_valid_checkpoint(tmp_path):
1078
+ from prizmkit_runtime.heartbeat import HeartbeatState
1079
+ from prizmkit_runtime.sessions import _compact_progress_line
1080
+
1081
+ session_log = tmp_path / "session.log"
1082
+ progress = tmp_path / "progress.json"
1083
+ malformed = tmp_path / "bad-checkpoint.json"
1084
+ invalid = tmp_path / "invalid-checkpoint.json"
1085
+ progress.write_text(
1086
+ json.dumps({"current_phase": "legacy-plan", "current_tool": "Edit"}),
1087
+ encoding="utf-8",
1088
+ )
1089
+ malformed.write_text("{bad", encoding="utf-8")
1090
+ invalid.write_text(json.dumps({"steps": [{"id": "S01", "skill": "x", "name": "X", "status": "done"}]}), encoding="utf-8")
1091
+ state = HeartbeatState(last_seen_epoch=1.0, stale=False, details="progress", stale_seconds=0, log_size=12)
1092
+
1093
+ missing_line = _compact_progress_line(0, session_log, progress, state, checkpoint_path=tmp_path / "missing.json")
1094
+ malformed_line = _compact_progress_line(0, session_log, progress, state, checkpoint_path=malformed)
1095
+ invalid_line = _compact_progress_line(0, session_log, progress, state, checkpoint_path=invalid)
1096
+
1097
+ for line in (missing_line, malformed_line, invalid_line):
1098
+ assert "phase:" not in line
1099
+ assert "legacy-plan" not in line
1100
+ assert "tool: Edit" in line
1101
+
1071
1102
 
1072
1103
  def test_heartbeat_observation_stale_accumulates_by_elapsed_time_and_resets_on_progress(tmp_path, monkeypatch):
1073
1104
  import prizmkit_runtime.heartbeat as heartbeat
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.114",
2
+ "version": "1.1.116",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Full-lifecycle dev toolkit. Covers spec-driven development, Prizm context docs, code quality, debugging, deployment, and knowledge management.",
@@ -414,14 +414,15 @@ Bug List (by priority):
414
414
 
415
415
  ---
416
416
 
417
- ## Adversarial Critic & Browser Verification
417
+ ## Verification Defaults
418
418
 
419
- When configuring critic settings or browser verification for bugs, read `${SKILL_DIR}/references/critic-and-verification.md` for default behavior tables and verification type guidance.
419
+ When configuring bug verification, read `${SKILL_DIR}/references/verification.md` for verification type and browser verification guidance.
420
420
 
421
421
  Key points:
422
- - Critic is enabled by default for critical/high severity, disabled for medium/low
423
- - Users can override critic settings per-bug during Phase 2 or Phase 3
422
+ - Critical/high severity bugs require clear automated, manual, or hybrid verification evidence.
423
+ - Medium/low severity bugs use the smallest verification that proves the fix and guards against regression.
424
424
  - `browser_interaction` is supported for UI-reproducible bugs; `verification_type` still declares whether verification is automated, manual, or hybrid.
425
+ - Do not generate `critic` or `critic_count`; those fields are fully removed and validators reject them.
425
426
 
426
427
  ---
427
428
 
@@ -0,0 +1,29 @@
1
+ # Bug Verification Guidance
2
+
3
+ ## Verification Defaults
4
+
5
+ All bug fixes need verification that matches severity, reproduction clarity, and risk.
6
+
7
+ | Severity | Default verification expectation | Rationale |
8
+ |---|---|---|
9
+ | critical | Automated or hybrid verification with explicit regression coverage | Crash/data-loss/security bugs need durable proof. |
10
+ | high | Automated or hybrid verification for the broken core path | Core-feature failures need regression protection. |
11
+ | medium | Focused automated or manual verification | Workaround cases need proof without over-scoping. |
12
+ | low | Smallest meaningful check | Cosmetic or minor edge cases should stay lightweight. |
13
+
14
+ ## Verification Type
15
+
16
+ - `automated`: use when a unit, integration, or E2E test can reliably reproduce the bug and prove the fix.
17
+ - `manual`: use when reproduction needs external state or human judgment and no stable automated check is practical.
18
+ - `hybrid`: use when automated regression coverage is possible but final confidence needs manual/browser confirmation.
19
+
20
+ ## Browser Verification
21
+
22
+ Use `browser_interaction` for UI-reproducible bugs.
23
+
24
+ - `tool: auto` lets the runtime choose the best browser tool.
25
+ - `tool: playwright-cli` is best for local dev-server verification in an isolated browser.
26
+ - `tool: opencli` is best when an existing Chrome login/session or third-party integration cookies are needed.
27
+ - `verify_steps` should describe what to prove, not low-level click scripts.
28
+
29
+ Do not emit `critic` or `critic_count`; those fields are retired and validators reject them.
@@ -139,6 +139,14 @@ def validate(data, feature_ids=None):
139
139
  for i, bug in enumerate(bugs):
140
140
  prefix = "bugs[{}]".format(i)
141
141
 
142
+ for retired_field in ("critic", "critic_count"):
143
+ if retired_field in bug:
144
+ errors.append(
145
+ "{}: '{}' field is retired; remove it from bug-fix-list data".format(
146
+ prefix, retired_field
147
+ )
148
+ )
149
+
142
150
  # Required fields
143
151
  bug_id = bug.get("id", "")
144
152
  if not bug_id:
@@ -123,7 +123,7 @@ Detect user intent from their message, then follow the corresponding workflow:
123
123
  ```
124
124
  Selected-options example:
125
125
  ```bash
126
- VERBOSE=1 ENABLE_CRITIC=1 MAX_RETRIES=5 STOP_ON_FAILURE=1 ENABLE_DEPLOY=1 PRIZMKIT_EFFORT=high \
126
+ VERBOSE=1 MAX_RETRIES=5 STOP_ON_FAILURE=1 ENABLE_DEPLOY=1 PRIZMKIT_EFFORT=high \
127
127
  python3 ./.prizmkit/dev-pipeline/cli.py bugfix run .prizmkit/plans/bug-fix-list.json
128
128
  ```
129
129
 
@@ -134,7 +134,7 @@ Detect user intent from their message, then follow the corresponding workflow:
134
134
  Selected-options example:
135
135
  ```bash
136
136
  python3 ./.prizmkit/dev-pipeline/cli.py daemon bugfix start .prizmkit/plans/bug-fix-list.json \
137
- --env "VERBOSE=1 ENABLE_CRITIC=1 MAX_RETRIES=5 STOP_ON_FAILURE=1 ENABLE_DEPLOY=1 PRIZMKIT_EFFORT=high"
137
+ --env "VERBOSE=1 MAX_RETRIES=5 STOP_ON_FAILURE=1 ENABLE_DEPLOY=1 PRIZMKIT_EFFORT=high"
138
138
  ```
139
139
 
140
140
  **Manual mode**: Print the assembled command(s) and **stop here**. Do not execute anything. Do not proceed to step 7.
@@ -11,7 +11,6 @@ Translating user responses to env vars:
11
11
  | Verbose: Off | `VERBOSE=0` |
12
12
  | Verbose: On | `VERBOSE=1` |
13
13
  | Max retries: N | `MAX_RETRIES=N` |
14
- | Critic: On | `ENABLE_CRITIC=1` |
15
14
  | Stop on failure: On | `STOP_ON_FAILURE=1` |
16
15
  | Deploy: Yes | `ENABLE_DEPLOY=1` |
17
16
  | Effort: value | `PRIZMKIT_EFFORT=<value>` |
@@ -43,15 +42,11 @@ Present these via `AskUserQuestion` in step 5. Round 1 is one `AskUserQuestion`
43
42
  - 1
44
43
  - 5
45
44
 
46
- **Question 3 — Critic review** (multiSelect: false):
47
- - Off (default) — Skip adversarial review (`ENABLE_CRITIC=0`)
48
- - On — Enable adversarial critic review (`ENABLE_CRITIC=1`): an independent AI agent reviews the diagnosis/plan for completeness and the fix for defects, edge cases, and regression risks. Adds ~5-10 min per bug.
49
-
50
45
  **Question 4 — Advanced config?** (multiSelect: false):
51
46
  - No (default) — Use defaults for failure behavior
52
47
  - Yes — Configure stop-on-failure, deploy, and reasoning effort options
53
48
 
54
- Note: Bug filter defaults to all bugs. Runtime selects eligible bugs in stable list order after dependencies are completed; planners should order unrelated bugs by severity/priority before launch. Default Critic to Off unless bugs have `severity: "critical"` or `severity: "high"` (in which case default to On). If the user selects "Other" on any option, handle their custom input.
49
+ Note: Bug filter defaults to all bugs. Runtime selects eligible bugs in stable list order after dependencies are completed; planners should order unrelated bugs by severity/priority before launch. If the user selects "Other" on any option, handle their custom input.
55
50
 
56
51
  ### Round 2 (only if Advanced config = Yes)
57
52