prizmkit 1.1.114 → 1.1.115

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 (75) 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/heartbeat.py +0 -1
  9. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +25 -31
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -8
  11. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +0 -3
  12. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +13 -7
  13. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +13 -1
  14. package/bundled/dev-pipeline/scripts/continuation.py +8 -40
  15. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +76 -261
  16. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +16 -35
  17. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +15 -34
  18. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +6 -0
  19. package/bundled/dev-pipeline/scripts/init-pipeline.py +6 -0
  20. package/bundled/dev-pipeline/scripts/init-refactor-pipeline.py +6 -0
  21. package/bundled/dev-pipeline/scripts/parse-stream-progress.py +1 -1
  22. package/bundled/dev-pipeline/scripts/prompt_framework.py +114 -63
  23. package/bundled/dev-pipeline/scripts/update-bug-status.py +3 -7
  24. package/bundled/dev-pipeline/scripts/update-checkpoint.py +54 -22
  25. package/bundled/dev-pipeline/scripts/update-feature-status.py +3 -7
  26. package/bundled/dev-pipeline/scripts/update-refactor-status.py +3 -7
  27. package/bundled/dev-pipeline/scripts/utils.py +6 -37
  28. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  29. package/bundled/dev-pipeline/templates/bootstrap-tier1.md +1 -1
  30. package/bundled/dev-pipeline/templates/bootstrap-tier2.md +9 -91
  31. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +9 -66
  32. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +10 -16
  33. package/bundled/dev-pipeline/templates/feature-list-schema.json +32 -17
  34. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +0 -16
  35. package/bundled/dev-pipeline/templates/refactor-list-schema.json +10 -16
  36. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +1 -1
  37. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  38. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -1
  39. package/bundled/dev-pipeline/templates/sections/critical-paths-full.md +0 -1
  40. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +1 -1
  41. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +1 -1
  42. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +1 -1
  43. package/bundled/dev-pipeline/tests/conftest.py +1 -0
  44. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +164 -0
  45. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +48 -76
  46. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +25 -3
  47. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +25 -5
  48. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +128 -10
  49. package/bundled/dev-pipeline/tests/test_unified_cli.py +40 -9
  50. package/bundled/skills/_metadata.json +1 -1
  51. package/bundled/skills/bug-planner/SKILL.md +5 -4
  52. package/bundled/skills/bug-planner/references/verification.md +29 -0
  53. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +8 -0
  54. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +2 -2
  55. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +1 -6
  56. package/bundled/skills/feature-pipeline-launcher/SKILL.md +6 -12
  57. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +0 -1
  58. package/bundled/skills/feature-planner/SKILL.md +13 -12
  59. package/bundled/skills/feature-planner/assets/planning-guide.md +2 -2
  60. package/bundled/skills/feature-planner/scripts/validate-and-generate.py +8 -11
  61. package/bundled/skills/prizmkit-plan/SKILL.md +15 -1
  62. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +101 -0
  63. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +5 -5
  64. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +2 -7
  65. package/bundled/skills/refactor-planner/SKILL.md +10 -8
  66. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +9 -0
  67. package/bundled/team/prizm-dev-team.json +2 -9
  68. package/package.json +1 -1
  69. package/bundled/agents/prizm-dev-team-critic.md +0 -179
  70. package/bundled/dev-pipeline/templates/agent-prompts/critic-plan-challenge.md +0 -8
  71. package/bundled/dev-pipeline/templates/sections/bugfix-phase-critic.md +0 -22
  72. package/bundled/dev-pipeline/templates/sections/phase-critic-plan-full.md +0 -61
  73. package/bundled/dev-pipeline/templates/sections/phase-critic-plan.md +0 -38
  74. package/bundled/dev-pipeline/templates/sections/refactor-phase-critic.md +0 -22
  75. package/bundled/skills/bug-planner/references/critic-and-verification.md +0 -44
@@ -215,7 +215,6 @@ class TestExplicitProjectRoot:
215
215
  output=str(main_checkout / ".prizmkit" / "state" / "features" / "F-001" / "sessions" / "session-1" / "bootstrap-prompt.md"),
216
216
  template=None,
217
217
  mode=None,
218
- critic=None,
219
218
  extract_baselines=False,
220
219
  project_root=str(execution_root),
221
220
  )
@@ -403,23 +402,6 @@ class TestProcessModeBlocks:
403
402
  result = process_mode_blocks(tpl, "standard", init_done=False)
404
403
  assert "need init" in result
405
404
 
406
- def test_critic_enabled_keeps_critic_block(self):
407
- tpl = "before\n{{IF_CRITIC_ENABLED}}\ncritic content\n{{END_IF_CRITIC_ENABLED}}\nafter"
408
- result = process_mode_blocks(tpl, "standard", init_done=True, critic_enabled=True)
409
- assert "critic content" in result
410
- assert "IF_CRITIC_ENABLED" not in result
411
-
412
- def test_critic_disabled_removes_critic_block(self):
413
- tpl = "before\n{{IF_CRITIC_ENABLED}}\ncritic content\n{{END_IF_CRITIC_ENABLED}}\nafter"
414
- result = process_mode_blocks(tpl, "standard", init_done=True, critic_enabled=False)
415
- assert "critic content" not in result
416
- assert "before" in result
417
- assert "after" in result
418
-
419
- def test_critic_default_is_disabled(self):
420
- tpl = "{{IF_CRITIC_ENABLED}}critic{{END_IF_CRITIC_ENABLED}}"
421
- result = process_mode_blocks(tpl, "standard", init_done=True)
422
- assert "critic" not in result
423
405
 
424
406
 
425
407
 
@@ -435,7 +417,6 @@ class TestScopedFeatureTestGate:
435
417
  str(sections_dir),
436
418
  init_done=True,
437
419
  is_resume=False,
438
- critic_enabled=False,
439
420
  browser_enabled=False,
440
421
  )
441
422
  names = [name for name, _ in sections]
@@ -470,7 +451,6 @@ class TestScopedFeatureTestGate:
470
451
  str(sections_dir),
471
452
  init_done=True,
472
453
  is_resume=False,
473
- critic_enabled=False,
474
454
  browser_enabled=False,
475
455
  )
476
456
  rendered = "\n".join(content for _, content in sections)
@@ -490,7 +470,6 @@ class TestScopedFeatureTestGate:
490
470
  str(sections_dir),
491
471
  init_done=True,
492
472
  is_resume=False,
493
- critic_enabled=False,
494
473
  browser_enabled=False,
495
474
  )
496
475
  names = [name for name, _ in sections]
@@ -506,7 +485,6 @@ class TestScopedFeatureTestGate:
506
485
  str(sections_dir),
507
486
  init_done=True,
508
487
  is_resume=False,
509
- critic_enabled=False,
510
488
  browser_enabled=False,
511
489
  )
512
490
  checkpoint = generate_checkpoint_definition(
@@ -853,7 +831,7 @@ class TestHeadlessRecoveryReframing:
853
831
  rendered = "\n".join(
854
832
  section for _name, section in assemble_sections(
855
833
  mode, str(sections_dir), init_done=True, is_resume=False,
856
- critic_enabled=False, browser_enabled=False,
834
+ browser_enabled=False,
857
835
  )
858
836
  )
859
837
  assert deleted_title not in rendered
@@ -883,7 +861,7 @@ class TestDirectOrchestratorImplementationPrompts:
883
861
  assert "{{AGENT_PROMPT_DEV_IMPLEMENT}}" not in replacements
884
862
  assert FORBIDDEN_DEV_FIX_PLACEHOLDER not in replacements
885
863
  assert FORBIDDEN_DEV_RESUME_PLACEHOLDER not in replacements
886
- assert "{{AGENT_PROMPT_CRITIC_PLAN_CHALLENGE}}" in replacements
864
+ assert ACTIVE_AGENT_PROMPTS == {}
887
865
  assert "{{AGENT_PROMPT_REVIEWER_REVIEW}}" not in replacements
888
866
  assert (templates_dir / "agent-prompts" / "reviewer-review.md").exists() is False
889
867
  assert (templates_dir / "agent-prompts" / "dev-implement.md").exists() is False
@@ -897,7 +875,7 @@ class TestDirectOrchestratorImplementationPrompts:
897
875
  rendered = "\n".join(
898
876
  section for _name, section in assemble_sections(
899
877
  mode, str(sections_dir), init_done=True, is_resume=False,
900
- critic_enabled=(mode == "full"), browser_enabled=False,
878
+ browser_enabled=False,
901
879
  )
902
880
  )
903
881
 
@@ -963,9 +941,9 @@ class TestContinuationHandoff:
963
941
  continuation_summary_path="",
964
942
  )
965
943
  checkpoint = {"steps": [
966
- {"id": "S01", "skill": "test-baseline", "status": "completed"},
967
- {"id": "S02", "skill": "context-snapshot", "status": "skipped"},
968
- {"id": "S03", "skill": "prizmkit-implement", "status": "pending"},
944
+ {"id": "S01", "skill": "test-baseline", "name": "Test Baseline", "status": "completed"},
945
+ {"id": "S02", "skill": "context-snapshot", "name": "Context Snapshot", "status": "skipped"},
946
+ {"id": "S03", "skill": "prizmkit-implement", "name": "Implement", "status": "pending"},
969
947
  ]}
970
948
 
971
949
  rendered = append_continuation_handoff(
@@ -1000,15 +978,15 @@ class TestContinuationHandoff:
1000
978
 
1001
979
  def test_checkpoint_cursor_prefers_in_progress_before_pending(self):
1002
980
  checkpoint = {"steps": [
1003
- {"id": "S01", "skill": "done", "status": "completed"},
1004
- {"id": "S02", "skill": "active", "status": "in_progress"},
1005
- {"id": "S03", "skill": "later", "status": "pending"},
981
+ {"id": "S01", "skill": "done", "name": "Done", "status": "completed"},
982
+ {"id": "S02", "skill": "active", "name": "Active", "status": "in_progress", "depends_on": "S01"},
983
+ {"id": "S03", "skill": "later", "name": "Later", "status": "pending", "depends_on": "S02"},
1006
984
  ]}
1007
985
  assert checkpoint_cursor(checkpoint)["first_cursor"] == "S02 (active)"
1008
986
 
1009
987
 
1010
988
  # ---------------------------------------------------------------------------
1011
- # F-033 headless prompt cleanup and platform-aware critic paths
989
+ # F-033 headless prompt cleanup and platform-aware paths
1012
990
  # ---------------------------------------------------------------------------
1013
991
 
1014
992
  HEADLESS_FORBIDDEN_PROMPT_STRINGS = [
@@ -1030,7 +1008,7 @@ def _write_feature_list(path, feature):
1030
1008
  path.write_text(json.dumps({"features": [feature], "global_context": {"language": "Python"}}), encoding="utf-8")
1031
1009
 
1032
1010
 
1033
- def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=None, platform="claude", browser_interaction=None):
1011
+ def _render_feature_prompt(tmp_path, mode="standard", template=None, platform="claude", browser_interaction=None):
1034
1012
  project = tmp_path / "project"
1035
1013
  project.mkdir(parents=True)
1036
1014
  (project / ".prizmkit" / "plans").mkdir(parents=True)
@@ -1040,7 +1018,7 @@ def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=Non
1040
1018
  for platform_dir, suffix in ((".claude", ".md"), (".codex", ".toml")):
1041
1019
  agents_dir = project / platform_dir / "agents"
1042
1020
  agents_dir.mkdir(parents=True)
1043
- for name in ("dev", "reviewer", "critic"):
1021
+ for name in ("dev", "reviewer"):
1044
1022
  (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
1045
1023
  feature = {
1046
1024
  "id": "F-123",
@@ -1048,7 +1026,6 @@ def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=Non
1048
1026
  "description": "Clean prompt text",
1049
1027
  "estimated_complexity": {"lite": "low", "standard": "medium", "full": "high"}[mode],
1050
1028
  "acceptance_criteria": ["Prompts are clean"],
1051
- "critic": critic,
1052
1029
  }
1053
1030
  if browser_interaction is not None:
1054
1031
  feature["browser_interaction"] = browser_interaction
@@ -1068,8 +1045,6 @@ def _render_feature_prompt(tmp_path, mode="standard", critic=False, template=Non
1068
1045
  "--output", str(output),
1069
1046
  "--mode", mode,
1070
1047
  ]
1071
- if critic:
1072
- args.extend(["--critic", "true"])
1073
1048
  if template:
1074
1049
  args.extend(["--template", str(Path("dev-pipeline/templates") / template)])
1075
1050
  old_argv = os.sys.argv
@@ -1096,7 +1071,7 @@ def _ensure_agent_files(project, platforms=("claude", "codex")):
1096
1071
  suffix = ".toml" if platform == "codex" else ".md"
1097
1072
  agents_dir = project / platform_dir / "agents"
1098
1073
  agents_dir.mkdir(parents=True, exist_ok=True)
1099
- for name in ("dev", "reviewer", "critic"):
1074
+ for name in ("dev", "reviewer"):
1100
1075
  (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
1101
1076
 
1102
1077
 
@@ -1280,7 +1255,7 @@ class TestHeadlessPromptCleanupF033:
1280
1255
  def test_rendered_feature_bugfix_refactor_prompts_are_clean(self, tmp_path):
1281
1256
  prompts = []
1282
1257
  for mode in ("lite", "standard", "full"):
1283
- prompts.append(_render_feature_prompt(tmp_path / mode, mode=mode, critic=(mode != "lite")))
1258
+ prompts.append(_render_feature_prompt(tmp_path / mode, mode=mode))
1284
1259
  prompts.append(_render_bugfix_prompt(tmp_path))
1285
1260
  prompts.append(_render_refactor_prompt(tmp_path))
1286
1261
 
@@ -1309,7 +1284,7 @@ class TestHeadlessPromptCleanupF033:
1309
1284
 
1310
1285
  def test_legacy_feature_templates_are_clean_when_rendered(self, tmp_path):
1311
1286
  for template, mode in (("bootstrap-tier1.md", "lite"), ("bootstrap-tier2.md", "standard"), ("bootstrap-tier3.md", "full")):
1312
- prompt = _render_feature_prompt(tmp_path / template, mode=mode, critic=(mode != "lite"), template=template)
1287
+ prompt = _render_feature_prompt(tmp_path / template, mode=mode, template=template)
1313
1288
  for forbidden in HEADLESS_FORBIDDEN_PROMPT_STRINGS:
1314
1289
  assert forbidden not in prompt
1315
1290
  assert "prizmkit-test-gate.py" not in prompt
@@ -1342,29 +1317,13 @@ class TestHeadlessPromptCleanupF033:
1342
1317
  prompt = _render_feature_prompt(
1343
1318
  tmp_path / f"browser-{template}",
1344
1319
  mode=mode,
1345
- critic=(mode != "lite"),
1346
- template=template,
1320
+ template=template,
1347
1321
  browser_interaction={"tool": "auto", "verify_steps": ["Open the page"]},
1348
1322
  )
1349
1323
  assert "prizmkit-runtime-helper.py" in prompt
1350
1324
  for snippet in forbidden:
1351
1325
  assert snippet not in prompt
1352
1326
 
1353
- def test_claude_config_wins_over_codex_directory_for_critic_path(self, tmp_path, monkeypatch):
1354
- project = tmp_path / "mixed"
1355
- (project / ".claude" / "agents").mkdir(parents=True)
1356
- (project / ".codex" / "agents").mkdir(parents=True)
1357
- (project / ".prizmkit").mkdir()
1358
- (project / ".prizmkit" / "config.json").write_text(
1359
- json.dumps({"platform": "claude", "ai_cli": "claude"}), encoding="utf-8"
1360
- )
1361
- monkeypatch.delenv("PRIZMKIT_PLATFORM", raising=False)
1362
-
1363
- assert resolve_prompt_platform(str(project)) == "claude"
1364
- prompt = _render_feature_prompt(tmp_path / "rendered-mixed", mode="standard", critic=True, platform="claude")
1365
- assert ".claude/agents/prizm-dev-team-critic.md" in prompt
1366
- assert ".codex/agents/prizm-dev-team-critic.toml" not in prompt
1367
-
1368
1327
  def test_bugfix_refactor_share_claude_platform_resolution(self, tmp_path, monkeypatch):
1369
1328
  from generate_bugfix_prompt import build_replacements as bugfix_build_replacements
1370
1329
 
@@ -1380,7 +1339,7 @@ class TestHeadlessPromptCleanupF033:
1380
1339
  bug_id="B-001", session_id="session-1", run_id="run-1",
1381
1340
  retry_count="0", resume_phase="null", state_dir=None,
1382
1341
  output="out.md", project_root=str(bug_project), template=None,
1383
- mode=None, critic=None,
1342
+ mode=None,
1384
1343
  )
1385
1344
  replacements = bugfix_build_replacements(
1386
1345
  args, {"id": "B-001", "title": "Broken"}, {}, str(Path("dev-pipeline/scripts").resolve())
@@ -1401,7 +1360,7 @@ class TestHeadlessPromptCleanupF033:
1401
1360
  bug_id="B-001", session_id="session-1", run_id="run-1",
1402
1361
  retry_count="0", resume_phase="null", state_dir=None,
1403
1362
  output="out.md", project_root=str(bug_project), template=None,
1404
- mode=None, critic=None,
1363
+ mode=None,
1405
1364
  )
1406
1365
  replacements = bugfix_build_replacements(
1407
1366
  args, {"id": "B-001", "title": "Broken"}, {}, str(Path("dev-pipeline/scripts").resolve())
@@ -1411,15 +1370,6 @@ class TestHeadlessPromptCleanupF033:
1411
1370
  assert replacements["{{REVIEWER_SUBAGENT_PATH}}"].endswith(".codex/agents/prizm-dev-team-reviewer.toml")
1412
1371
  assert ".claude/agents/prizm-dev-team-reviewer.md" not in refactor_prompt
1413
1372
 
1414
- def test_explicit_codex_env_still_renders_codex_critic_path(self, tmp_path, monkeypatch):
1415
- project = tmp_path / "codex"
1416
- (project / ".claude" / "agents").mkdir(parents=True)
1417
- (project / ".codex" / "agents").mkdir(parents=True)
1418
- monkeypatch.setenv("PRIZMKIT_PLATFORM", "codex")
1419
-
1420
- assert resolve_prompt_platform(str(project)) == "codex"
1421
-
1422
-
1423
1373
 
1424
1374
  class TestFeatureBootstrapShellExtraction:
1425
1375
  RETIRED_EXTRACTED_SNIPPETS = [
@@ -1465,8 +1415,7 @@ class TestFeatureBootstrapShellExtraction:
1465
1415
  prompt = _render_feature_prompt(
1466
1416
  tmp_path / f"section-{mode}",
1467
1417
  mode=mode,
1468
- critic=(mode != "lite"),
1469
- browser_interaction={"tool": "auto", "verify_steps": ["Open the page"]},
1418
+ browser_interaction={"tool": "auto", "verify_steps": ["Open the page"]},
1470
1419
  )
1471
1420
 
1472
1421
  self.assert_shell_extracted_operations_removed(prompt)
@@ -1488,8 +1437,7 @@ class TestFeatureBootstrapShellExtraction:
1488
1437
  prompt = _render_feature_prompt(
1489
1438
  tmp_path / f"legacy-{template}",
1490
1439
  mode=mode,
1491
- critic=(mode != "lite"),
1492
- template=template,
1440
+ template=template,
1493
1441
  browser_interaction={"tool": "playwright-cli", "verify_steps": ["Open the page"]},
1494
1442
  )
1495
1443
 
@@ -1512,8 +1460,7 @@ class TestFeatureBootstrapShellExtraction:
1512
1460
  tier2 = _render_feature_prompt(
1513
1461
  tmp_path / "legacy-tier2-helper-map",
1514
1462
  mode="standard",
1515
- critic=True,
1516
- template="bootstrap-tier2.md",
1463
+ template="bootstrap-tier2.md",
1517
1464
  )
1518
1465
  assert "artifact exists .prizmkit/specs/123-prompt-cleanup/failure-log.md" in tier2
1519
1466
  assert "artifact tree . --max-depth 2" in tier2
@@ -1522,8 +1469,7 @@ class TestFeatureBootstrapShellExtraction:
1522
1469
  tier3 = _render_feature_prompt(
1523
1470
  tmp_path / "legacy-tier3-helper-map",
1524
1471
  mode="full",
1525
- critic=True,
1526
- template="bootstrap-tier3.md",
1472
+ template="bootstrap-tier3.md",
1527
1473
  )
1528
1474
  assert "text search . --pattern \"123-prompt-cleanup\" --json" in tier3
1529
1475
  assert "text count .prizmkit/specs/123-prompt-cleanup/plan.md --pattern \"- [ ]\"" in tier3
@@ -1619,3 +1565,29 @@ class TestDetectTestCommands:
1619
1565
  # Should detect test:unit as a viable test script
1620
1566
  assert "npm run test:unit" in result
1621
1567
 
1568
+
1569
+
1570
+ def test_feature_checkpoint_generation_skips_noop_and_expands_only_finalization_group():
1571
+ from generate_bootstrap_prompt import generate_checkpoint_definition
1572
+
1573
+ checkpoint = generate_checkpoint_definition(
1574
+ [
1575
+ ("phase0-test-baseline", "noop"),
1576
+ ("phase-context-snapshot", "context"),
1577
+ ("phase-commit", "finalize"),
1578
+ ],
1579
+ "standard",
1580
+ "feature-pipeline",
1581
+ "F-123",
1582
+ "123-prompt-cleanup",
1583
+ "session-1",
1584
+ )
1585
+
1586
+ assert [step["skill"] for step in checkpoint["steps"]] == [
1587
+ "context-snapshot",
1588
+ "prizmkit-retrospective",
1589
+ "prizmkit-committer",
1590
+ "completion-summary",
1591
+ ]
1592
+ assert checkpoint["steps"][0]["depends_on"] is None
1593
+ assert checkpoint["steps"][1]["depends_on"] == "S01"
@@ -12,6 +12,7 @@ from generate_bugfix_prompt import (
12
12
  format_global_context,
13
13
  format_error_source_details,
14
14
  format_environment,
15
+ generate_bugfix_checkpoint,
15
16
  process_conditional_blocks,
16
17
  render_template,
17
18
  )
@@ -178,7 +179,6 @@ class TestExplicitProjectRoot:
178
179
  output=str(main_checkout / ".prizmkit" / "state" / "bugfix" / "B-001" / "sessions" / "session-1" / "bootstrap-prompt.md"),
179
180
  template=None,
180
181
  mode=None,
181
- critic=None,
182
182
  project_root=str(execution_root),
183
183
  )
184
184
  bug = {"id": "B-001", "title": "Worktree Bug", "description": "d"}
@@ -275,7 +275,7 @@ class TestBugfixContinuationHandoff:
275
275
  )
276
276
  prompt = append_continuation_handoff(
277
277
  "BUGFIX PROMPT", args, str(tmp_path), "bugfix", "B-007", "B-007",
278
- {"steps": [{"id": "S01", "skill": "diagnose", "status": "pending"}]},
278
+ {"steps": [{"id": "S01", "skill": "diagnose", "name": "Diagnose", "status": "pending"}]},
279
279
  )
280
280
 
281
281
  assert prompt.startswith("BUGFIX PROMPT")
@@ -408,7 +408,7 @@ def _ensure_agent_files(project, platforms=None):
408
408
  suffix = ".toml" if plat == "codex" else ".md"
409
409
  agents_dir = project / platform_dir / "agents"
410
410
  agents_dir.mkdir(parents=True, exist_ok=True)
411
- for name in ("dev", "reviewer", "critic"):
411
+ for name in ("dev", "reviewer"):
412
412
  (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
413
413
 
414
414
 
@@ -439,3 +439,25 @@ class TestBugfixBootstrapShellExtraction:
439
439
  content = Path("dev-pipeline/scripts/generate-bugfix-prompt.py").read_text(encoding="utf-8")
440
440
  for snippet in RETIRED_EXTRACTED_SNIPPETS_BUGFIX:
441
441
  assert snippet not in content, f"Shell snippet in generate-bugfix-prompt.py: {snippet}"
442
+
443
+
444
+ class TestBugfixCheckpointGeneration:
445
+ def test_setup_section_skips_and_finalization_group_expands(self):
446
+ checkpoint = generate_bugfix_checkpoint(
447
+ "B-001",
448
+ "session-1",
449
+ sections=[
450
+ ("bugfix-phase-init", "setup"),
451
+ ("bugfix-phase-implement", "implement"),
452
+ ("bugfix-phase-commit-report", "finalize"),
453
+ ],
454
+ )
455
+
456
+ assert [step["skill"] for step in checkpoint["steps"]] == [
457
+ "prizmkit-init",
458
+ "prizmkit-implement",
459
+ "prizmkit-retrospective",
460
+ "prizmkit-committer",
461
+ "bug-report",
462
+ ]
463
+ assert checkpoint["steps"][2]["depends_on"] == "S02"
@@ -30,7 +30,7 @@ class TestRefactorContinuationHandoff:
30
30
  )
31
31
  prompt = append_continuation_handoff(
32
32
  "REFACTOR PROMPT", args, str(tmp_path), "refactor", "R-007", "R-007",
33
- {"steps": [{"id": "S01", "skill": "plan", "status": "pending"}]},
33
+ {"steps": [{"id": "S01", "skill": "plan", "name": "Plan", "status": "pending"}]},
34
34
  )
35
35
 
36
36
  assert prompt.startswith("REFACTOR PROMPT")
@@ -74,7 +74,6 @@ class TestExplicitProjectRoot:
74
74
  output=str(project / "out.md"),
75
75
  template=None,
76
76
  mode=None,
77
- critic=None,
78
77
  )
79
78
  refactor = {"id": "R-001", "title": "Restructure", "description": "d"}
80
79
 
@@ -109,7 +108,6 @@ class TestExplicitProjectRoot:
109
108
  output=str(main_checkout / ".prizmkit" / "state" / "refactor" / "R-001" / "sessions" / "session-1" / "bootstrap-prompt.md"),
110
109
  template=None,
111
110
  mode=None,
112
- critic=None,
113
111
  project_root=str(execution_root),
114
112
  )
115
113
  refactor = {"id": "R-001", "title": "Worktree Refactor", "description": "d"}
@@ -161,7 +159,6 @@ class TestRefactorPromptMaxLogRemoval:
161
159
  output="out.md",
162
160
  template=None,
163
161
  mode=None,
164
- critic=None,
165
162
  )
166
163
  refactor = {"id": "R-001", "title": "Restructure", "description": "d"}
167
164
 
@@ -243,7 +240,7 @@ def _render_refactor_prompt_full(tmp_path, platform="claude"):
243
240
  suffix = ".toml" if plat == "codex" else ".md"
244
241
  agents_dir = project / platform_dir / "agents"
245
242
  agents_dir.mkdir(parents=True, exist_ok=True)
246
- for name in ("dev", "reviewer", "critic"):
243
+ for name in ("dev", "reviewer"):
247
244
  (agents_dir / f"prizm-dev-team-{name}{suffix}").write_text("agent\n", encoding="utf-8")
248
245
 
249
246
  refactor_list = project / ".prizmkit" / "plans" / "refactor-list.json"
@@ -318,3 +315,26 @@ class TestRefactorBootstrapShellExtraction:
318
315
  content = Path("dev-pipeline/scripts/generate-refactor-prompt.py").read_text(encoding="utf-8")
319
316
  for snippet in RETIRED_EXTRACTED_SNIPPETS_REFACTOR:
320
317
  assert snippet not in content, f"Shell snippet in generate-refactor-prompt.py: {snippet}"
318
+
319
+
320
+ class TestRefactorCheckpointGeneration:
321
+ def test_setup_section_is_step_and_finalization_group_expands(self):
322
+ checkpoint = generate_refactor_checkpoint(
323
+ "R-001",
324
+ "session-1",
325
+ sections=[
326
+ ("refactor-phase-init", "setup"),
327
+ ("refactor-phase-implement", "implement"),
328
+ ("refactor-phase-commit-report", "finalize"),
329
+ ],
330
+ )
331
+
332
+ assert [step["skill"] for step in checkpoint["steps"]] == [
333
+ "prizmkit-init",
334
+ "prizmkit-implement",
335
+ "prizmkit-retrospective",
336
+ "prizmkit-committer",
337
+ "refactor-report",
338
+ "completion-summary",
339
+ ]
340
+ assert checkpoint["steps"][2]["depends_on"] == "S02"
@@ -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 = []
@@ -1530,7 +1525,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
1530
1525
  assert "Code retry: 1 / 3" in captured.err
1531
1526
  assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
1532
1527
  assert "Pipeline mode: lite (Tier 1 — Single Agent)" in captured.err
1533
- assert "Agents: 1 (critic: disabled)" in captured.err
1528
+ assert "Agents: 1" in captured.err
1534
1529
  assert "Spawning AI CLI session: F-001-session-1" in captured.err
1535
1530
  assert "Session result: success" in captured.err
1536
1531
 
@@ -1667,7 +1662,7 @@ def test_classification_accepts_late_infra_error_with_completed_checkpoint_and_c
1667
1662
  artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
1668
1663
  artifact_dir.mkdir(parents=True)
1669
1664
  checkpoint = artifact_dir / "workflow-checkpoint.json"
1670
- checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "status": "completed"}]}), encoding="utf-8")
1665
+ checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
1671
1666
  log = tmp_path / "session.log"
1672
1667
  log.write_text("model quota exceeded; API error after commit", encoding="utf-8")
1673
1668
  raw = AISessionResult(
@@ -1792,7 +1787,7 @@ def test_classification_accepts_terminal_success_with_completed_checkpoint_witho
1792
1787
  artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
1793
1788
  artifact_dir.mkdir(parents=True)
1794
1789
  checkpoint = artifact_dir / "workflow-checkpoint.json"
1795
- checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "status": "completed"}]}), encoding="utf-8")
1790
+ checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
1796
1791
  (artifact_dir / "completion-summary.json").write_text("{}", encoding="utf-8")
1797
1792
  log = tmp_path / "session.log"
1798
1793
  log.write_text("terminal success", encoding="utf-8")
@@ -2207,3 +2202,126 @@ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path)
2207
2202
  else:
2208
2203
  assert summary["auto_skipped"] == 1
2209
2204
 
2205
+
2206
+
2207
+ def _init_git_repo_for_invalid_checkpoint_test(tmp_path):
2208
+ subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2209
+ subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
2210
+ subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
2211
+ (tmp_path / "base.txt").write_text("base", encoding="utf-8")
2212
+ subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
2213
+ subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2214
+ return subprocess.run(["git", "rev-parse", "HEAD"], cwd=tmp_path, check=True, stdout=subprocess.PIPE, text=True).stdout.strip()
2215
+
2216
+
2217
+ def _terminal_success_result(tmp_path):
2218
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
2219
+ from prizmkit_runtime.status import ProgressSummary
2220
+
2221
+ log = tmp_path / "session.log"
2222
+ log.write_text("terminal success", encoding="utf-8")
2223
+ return AISessionResult(
2224
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
2225
+ exit_code=0,
2226
+ timed_out=False,
2227
+ termination_reason=None,
2228
+ stale_marker=None,
2229
+ fatal_error_code="",
2230
+ progress_summary=ProgressSummary(result_subtype="success", terminal_result_text="Completed"),
2231
+ session_log=log,
2232
+ backup_log=tmp_path / "backup.log",
2233
+ log_recovery=None,
2234
+ started_epoch=0,
2235
+ ended_epoch=1,
2236
+ )
2237
+
2238
+
2239
+ def test_invalid_existing_checkpoint_blocks_terminal_success_with_completion_summary(tmp_path):
2240
+ from prizmkit_runtime.runner_classification import classify_session
2241
+ from prizmkit_runtime.runner_models import PromptGenerationResult
2242
+
2243
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
2244
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
2245
+ artifact_dir.mkdir(parents=True)
2246
+ checkpoint = artifact_dir / "workflow-checkpoint.json"
2247
+ checkpoint.write_text(json.dumps({"steps": [{"id": "S01", "skill": "done", "name": "Done", "status": "not-valid"}]}), encoding="utf-8")
2248
+ (artifact_dir / "completion-summary.json").write_text("{}", encoding="utf-8")
2249
+ prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
2250
+
2251
+ classification = classify_session(_terminal_success_result(tmp_path), project_root=tmp_path, base_head=base_head, prompt=prompt)
2252
+
2253
+ assert classification.session_status == "crashed"
2254
+ assert classification.reason == "invalid_workflow_checkpoint:invalid_status"
2255
+
2256
+
2257
+ def test_invalid_existing_checkpoint_blocks_head_change_success(tmp_path):
2258
+ from prizmkit_runtime.runner_classification import classify_session
2259
+ from prizmkit_runtime.runner_models import PromptGenerationResult
2260
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
2261
+ from prizmkit_runtime.status import ProgressSummary
2262
+
2263
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
2264
+ (tmp_path / "done.txt").write_text("done", encoding="utf-8")
2265
+ subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
2266
+ subprocess.run(["git", "commit", "-m", "done"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
2267
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
2268
+ artifact_dir.mkdir(parents=True)
2269
+ checkpoint = artifact_dir / "workflow-checkpoint.json"
2270
+ checkpoint.write_text(json.dumps({"steps": [{"id": "bad", "skill": "done", "name": "Done", "status": "completed"}]}), encoding="utf-8")
2271
+ raw = AISessionResult(
2272
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
2273
+ exit_code=1,
2274
+ timed_out=False,
2275
+ termination_reason=None,
2276
+ stale_marker=None,
2277
+ fatal_error_code="context_overflow",
2278
+ progress_summary=ProgressSummary(fatal_error_code="context_overflow"),
2279
+ session_log=tmp_path / "session.log",
2280
+ backup_log=tmp_path / "backup.log",
2281
+ log_recovery=None,
2282
+ started_epoch=0,
2283
+ ended_epoch=1,
2284
+ )
2285
+ raw.session_log.write_text("context overflow", encoding="utf-8")
2286
+ prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
2287
+
2288
+ classification = classify_session(raw, project_root=tmp_path, base_head=base_head, prompt=prompt)
2289
+
2290
+ assert classification.session_status == "crashed"
2291
+ assert classification.reason == "invalid_workflow_checkpoint:malformed_step_id"
2292
+
2293
+
2294
+ def test_invalid_existing_checkpoint_blocks_workspace_auto_commit_success(tmp_path):
2295
+ from prizmkit_runtime.runner_classification import classify_session
2296
+ from prizmkit_runtime.runner_models import PromptGenerationResult
2297
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
2298
+ from prizmkit_runtime.status import ProgressSummary
2299
+
2300
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
2301
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-invalid"
2302
+ artifact_dir.mkdir(parents=True)
2303
+ checkpoint = artifact_dir / "workflow-checkpoint.json"
2304
+ checkpoint.write_text(json.dumps({"steps": []}), encoding="utf-8")
2305
+ (tmp_path / "dirty.txt").write_text("dirty", encoding="utf-8")
2306
+ log = tmp_path / "session.log"
2307
+ log.write_text("clean exit but dirty workspace", encoding="utf-8")
2308
+ raw = AISessionResult(
2309
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
2310
+ exit_code=0,
2311
+ timed_out=False,
2312
+ termination_reason=None,
2313
+ stale_marker=None,
2314
+ fatal_error_code="",
2315
+ progress_summary=ProgressSummary(result_subtype="", terminal_result_text=""),
2316
+ session_log=log,
2317
+ backup_log=tmp_path / "backup.log",
2318
+ log_recovery=None,
2319
+ started_epoch=0,
2320
+ ended_epoch=1,
2321
+ )
2322
+ prompt = PromptGenerationResult(output_path=tmp_path / "prompt.md", checkpoint_path=checkpoint, artifact_path=artifact_dir)
2323
+
2324
+ classification = classify_session(raw, project_root=tmp_path, base_head=base_head, prompt=prompt)
2325
+
2326
+ assert classification.session_status == "crashed"
2327
+ assert classification.reason == "invalid_workflow_checkpoint:empty_steps"