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
@@ -14,6 +14,8 @@ import sys
14
14
  from continuation import add_continuation_args, append_continuation_handoff, is_continuation
15
15
  from prompt_framework import (
16
16
  browser_enabled_from_item,
17
+ checkpoint_group,
18
+ checkpoint_step,
17
19
  detect_dev_server_port,
18
20
  generate_checkpoint_from_sections,
19
21
  load_section,
@@ -34,26 +36,23 @@ LOGGER = setup_logging("generate-bugfix-prompt")
34
36
 
35
37
  # Section name -> checkpoint contract.
36
38
  BUGFIX_SECTION_TO_SKILL = {
37
- "bugfix-phase-init": ("prizmkit-init", "Initialize", [".prizmkit/bugfix/{slug}"]),
38
- "bugfix-phase-diagnose-plan": (
39
+ "bugfix-phase-init": checkpoint_step("prizmkit-init", "Initialize", [".prizmkit/bugfix/{slug}"]),
40
+ "bugfix-phase-diagnose-plan": checkpoint_step(
39
41
  "bug-diagnosis-and-plan", "Diagnose & Plan",
40
42
  [".prizmkit/bugfix/{slug}/spec.md", ".prizmkit/bugfix/{slug}/plan.md"],
41
43
  ),
42
- "bugfix-phase-critic": ("critic-plan-review", "Critic: Root Cause & Fix Plan Review", []),
43
- "bugfix-phase-implement": ("prizmkit-implement", "Implement Fix", []),
44
- "bugfix-phase-review": ("prizmkit-code-review", "Code Review", []),
45
- "phase-browser": ("browser-verification", "Browser Verification", []),
46
- "bugfix-phase-commit-report": None,
47
- }
48
-
49
- BUGFIX_SPLIT_STEPS = {
50
- "bugfix-phase-commit-report": [
44
+ "bugfix-phase-implement": checkpoint_step("prizmkit-implement", "Implement Fix", []),
45
+ "bugfix-phase-review": checkpoint_step("prizmkit-code-review", "Code Review", []),
46
+ "phase-browser": checkpoint_step("browser-verification", "Browser Verification", []),
47
+ "bugfix-phase-commit-report": checkpoint_group([
51
48
  ("prizmkit-retrospective", "Retrospective", []),
52
49
  ("prizmkit-committer", "Commit", []),
53
50
  ("bug-report", "Generate Fix Report", [".prizmkit/bugfix/{slug}/fix-report.md"]),
54
- ],
51
+ ]),
55
52
  }
56
53
 
54
+ BUGFIX_SPLIT_STEPS = {}
55
+
57
56
 
58
57
  def parse_args():
59
58
  parser = argparse.ArgumentParser(
@@ -73,7 +72,6 @@ def parse_args():
73
72
  parser.add_argument("--project-root", default=None, help="AI-visible project root used for rendered prompt paths")
74
73
  parser.add_argument("--template", default=None, help="Custom legacy template path. Defaults to section assembly.")
75
74
  parser.add_argument("--mode", choices=["lite", "standard", "full"], default=None, help="Pipeline execution mode override")
76
- parser.add_argument("--critic", choices=["true", "false"], default=None, help="Enable critic agent: true/false")
77
75
  add_continuation_args(parser, "bugfix")
78
76
  return parser.parse_args()
79
77
 
@@ -264,7 +262,6 @@ def build_replacements(args, bug, global_context, script_dir):
264
262
  agent_ext = ".toml" if platform == "codex" else ".md"
265
263
  dev_subagent = os.path.join(agents_dir, "prizm-dev-team-dev{}".format(agent_ext))
266
264
  reviewer_subagent = os.path.join(agents_dir, "prizm-dev-team-reviewer{}".format(agent_ext))
267
- critic_subagent = os.path.join(agents_dir, "prizm-dev-team-critic{}".format(agent_ext))
268
265
 
269
266
  state_dir = (
270
267
  os.path.abspath(args.state_dir)
@@ -284,14 +281,6 @@ def build_replacements(args, bug, global_context, script_dir):
284
281
  dev_url = "http://localhost:{}".format(dev_port) if dev_port.isdigit() else "<UNKNOWN_DETECT_FROM_PROJECT_CONFIG>"
285
282
 
286
283
  pipeline_mode = determine_bugfix_mode(args, bug)
287
- critic_requested = args.critic == "true" if args.critic is not None else bool(bug.get("critic", False))
288
- critic_enabled = critic_requested and pipeline_mode != "lite"
289
- if critic_requested and pipeline_mode == "lite":
290
- LOGGER.warning("Critic requested for bug %s but lite mode skips critic phases", args.bug_id)
291
- if critic_enabled and not os.path.isfile(critic_subagent):
292
- LOGGER.warning("Critic enabled but agent file not found: %s. Critic phase will be skipped.", critic_subagent)
293
- critic_enabled = False
294
-
295
284
  recovery_like = is_recovery_like(args)
296
285
  manual_or_hybrid = verification_type in ("manual", "hybrid")
297
286
  manual_policy = (
@@ -312,8 +301,6 @@ def build_replacements(args, bug, global_context, script_dir):
312
301
  "{{BUG_TITLE}}": bug.get("title", ""),
313
302
  "{{SEVERITY}}": bug.get("severity", "medium"),
314
303
  "{{PIPELINE_MODE}}": pipeline_mode,
315
- "{{CRITIC_ENABLED}}": "true" if critic_enabled else "false",
316
- "{{CRITIC_COUNT}}": str(bug.get("critic_count", 1)),
317
304
  "{{VERIFICATION_TYPE}}": verification_type,
318
305
  "{{MANUAL_VERIFICATION_POLICY}}": manual_policy,
319
306
  "{{BUG_DESCRIPTION}}": bug.get("description", ""),
@@ -327,7 +314,6 @@ def build_replacements(args, bug, global_context, script_dir):
327
314
  "{{TEAM_CONFIG_PATH}}": team_config_path,
328
315
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
329
316
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
330
- "{{CRITIC_SUBAGENT_PATH}}": critic_subagent,
331
317
  "{{SESSION_STATUS_PATH}}": session_status_path,
332
318
  "{{PROJECT_ROOT}}": project_root,
333
319
  "{{ARTIFACT_DIR}}": ".prizmkit/bugfix/{}".format(args.bug_id),
@@ -388,13 +374,13 @@ def _bugfix_header(pipeline_mode):
388
374
  title = "# Dev-Pipeline Bug Fix Session Bootstrap — {}\n".format(pipeline_mode.title())
389
375
  desc = {
390
376
  "lite": "**Tier 1 — Single Agent**: direct root-cause fix by the main orchestrator.",
391
- "standard": "**Tier 2 — Orchestrator + optional Critic**: direct implementation with review gates.",
392
- "full": "**Tier 3 — Full Bugfix Guardrails**: direct implementation with stronger diagnosis and critic checks.",
377
+ "standard": "**Tier 2 — Orchestrator + Reviewer**: direct implementation with review gates.",
378
+ "full": "**Tier 3 — Full Bugfix Guardrails**: direct implementation with stronger diagnosis and review gates.",
393
379
  }.get(pipeline_mode, "**Bugfix pipeline**")
394
380
  return title + "\n" + desc + "\n"
395
381
 
396
382
 
397
- def assemble_bugfix_sections(pipeline_mode, sections_dir, critic_enabled, browser_enabled, manual_gate):
383
+ def assemble_bugfix_sections(pipeline_mode, sections_dir, browser_enabled, manual_gate):
398
384
  sections = [
399
385
  ("header", _bugfix_header(pipeline_mode)),
400
386
  ("bugfix-session-context", load_section(sections_dir, "bugfix-session-context.md")),
@@ -407,8 +393,6 @@ def assemble_bugfix_sections(pipeline_mode, sections_dir, critic_enabled, browse
407
393
  ("bugfix-phase-init", load_section(sections_dir, "bugfix-phase-init.md")),
408
394
  ("bugfix-phase-diagnose-plan", load_section(sections_dir, "bugfix-phase-diagnose-plan.md")),
409
395
  ]
410
- if critic_enabled:
411
- sections.append(("bugfix-phase-critic", load_section(sections_dir, "bugfix-phase-critic.md")))
412
396
  sections.extend([
413
397
  ("bugfix-phase-implement", load_section(sections_dir, "bugfix-phase-implement.md")),
414
398
  ("bugfix-phase-review", load_section(sections_dir, "bugfix-phase-review.md")),
@@ -488,7 +472,6 @@ def main():
488
472
 
489
473
  replacements = build_replacements(args, bug, global_context, script_dir)
490
474
  pipeline_mode = replacements["{{PIPELINE_MODE}}"]
491
- critic_enabled = replacements["{{CRITIC_ENABLED}}"] == "true"
492
475
  browser_enabled = replacements["{{BROWSER_ENABLED}}"] == "true"
493
476
  manual_gate = bug.get("verification_type", "automated") in ("manual", "hybrid") and not is_recovery_like(args)
494
477
 
@@ -497,7 +480,7 @@ def main():
497
480
  if use_sections:
498
481
  try:
499
482
  sections = assemble_bugfix_sections(
500
- pipeline_mode, sections_dir, critic_enabled, browser_enabled, manual_gate,
483
+ pipeline_mode, sections_dir, browser_enabled, manual_gate,
501
484
  )
502
485
  rendered = render_from_sections(sections, replacements)
503
486
  except FileNotFoundError as exc:
@@ -555,15 +538,13 @@ def main():
555
538
  if err:
556
539
  emit_failure(err)
557
540
 
558
- critic_count = int(replacements.get("{{CRITIC_COUNT}}", "1"))
559
541
  output = {
560
542
  "success": True,
561
543
  "output_path": os.path.abspath(args.output),
562
544
  "checkpoint_path": checkpoint_path,
563
545
  "model": bug.get("model", ""),
564
546
  "pipeline_mode": pipeline_mode,
565
- "agent_count": mode_agent_count(pipeline_mode, critic_enabled, critic_count),
566
- "critic_enabled": critic_enabled,
547
+ "agent_count": mode_agent_count(pipeline_mode),
567
548
  "render_mode": "sections" if use_sections else "legacy",
568
549
  "validation_warnings": len(warnings),
569
550
  "validation_errors": len(errors),
@@ -14,6 +14,8 @@ import sys
14
14
  from continuation import add_continuation_args, append_continuation_handoff, is_continuation
15
15
  from prompt_framework import (
16
16
  browser_enabled_from_item,
17
+ checkpoint_group,
18
+ checkpoint_step,
17
19
  detect_dev_server_port,
18
20
  generate_checkpoint_from_sections,
19
21
  load_section,
@@ -33,33 +35,30 @@ LOGGER = setup_logging("generate-refactor-prompt")
33
35
 
34
36
 
35
37
  REFACTOR_SECTION_TO_SKILL = {
36
- "refactor-phase-init": ("prizmkit-init", "Initialize", [".prizmkit/refactor/{slug}"]),
37
- "refactor-phase-plan": (
38
+ "refactor-phase-init": checkpoint_step("prizmkit-init", "Initialize", [".prizmkit/refactor/{slug}"]),
39
+ "refactor-phase-plan": checkpoint_step(
38
40
  "prizmkit-plan", "Plan — Specification & Plan Generation",
39
41
  [".prizmkit/refactor/{slug}/spec.md", ".prizmkit/refactor/{slug}/plan.md"],
40
42
  ),
41
- "refactor-phase-critic": ("critic-plan-review", "Critic: Behavior Preservation Review", []),
42
- "refactor-phase-implement": (
43
+ "refactor-phase-implement": checkpoint_step(
43
44
  "prizmkit-implement", "Implement — Behavior-Preserving Refactoring",
44
45
  [".prizmkit/refactor/{slug}/plan.md"],
45
46
  ),
46
- "refactor-phase-review": (
47
+ "refactor-phase-review": checkpoint_step(
47
48
  "prizmkit-code-review", "Review — Code Review & Behavior Verification",
48
49
  [".prizmkit/refactor/{slug}/review-report.md"],
49
50
  ),
50
- "phase-browser": ("browser-verification", "Browser Verification", []),
51
- "refactor-phase-commit-report": None,
52
- }
53
-
54
- REFACTOR_SPLIT_STEPS = {
55
- "refactor-phase-commit-report": [
51
+ "phase-browser": checkpoint_step("browser-verification", "Browser Verification", []),
52
+ "refactor-phase-commit-report": checkpoint_group([
56
53
  ("prizmkit-retrospective", "Retrospective", []),
57
54
  ("prizmkit-committer", "Commit", []),
58
55
  ("refactor-report", "Generate Refactor Report", [".prizmkit/refactor/{slug}/refactor-report.md"]),
59
56
  ("completion-summary", "Completion Summary", [".prizmkit/refactor/{slug}/completion-summary.json"]),
60
- ],
57
+ ]),
61
58
  }
62
59
 
60
+ REFACTOR_SPLIT_STEPS = {}
61
+
63
62
 
64
63
  def parse_args():
65
64
  parser = argparse.ArgumentParser(
@@ -79,7 +78,6 @@ def parse_args():
79
78
  parser.add_argument("--project-root", default=None, help="AI-visible project root used for rendered prompt paths")
80
79
  parser.add_argument("--template", default=None, help="Custom legacy template path. Defaults to section assembly.")
81
80
  parser.add_argument("--mode", choices=["lite", "standard", "full"], default=None, help="Pipeline execution mode override")
82
- parser.add_argument("--critic", choices=["true", "false"], default=None, help="Enable critic agent: true/false")
83
81
  add_continuation_args(parser, "refactor")
84
82
  return parser.parse_args()
85
83
 
@@ -290,7 +288,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
290
288
  agent_ext = ".toml" if platform == "codex" else ".md"
291
289
  dev_subagent = os.path.join(agents_dir, "prizm-dev-team-dev{}".format(agent_ext))
292
290
  reviewer_subagent = os.path.join(agents_dir, "prizm-dev-team-reviewer{}".format(agent_ext))
293
- critic_subagent = os.path.join(agents_dir, "prizm-dev-team-critic{}".format(agent_ext))
294
291
 
295
292
  state_dir = (
296
293
  os.path.abspath(args.state_dir)
@@ -316,14 +313,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
316
313
  dev_url = "http://localhost:{}".format(dev_port) if dev_port.isdigit() else "<UNKNOWN_DETECT_FROM_PROJECT_CONFIG>"
317
314
 
318
315
  pipeline_mode = determine_refactor_mode(args, refactor)
319
- critic_requested = args.critic == "true" if args.critic is not None else bool(refactor.get("critic", False))
320
- critic_enabled = critic_requested and pipeline_mode != "lite"
321
- if critic_requested and pipeline_mode == "lite":
322
- LOGGER.warning("Critic requested for refactor %s but lite mode skips critic phases", args.refactor_id)
323
- if critic_enabled and not os.path.isfile(critic_subagent):
324
- LOGGER.warning("Critic enabled but agent file not found: %s. Critic phase will be skipped.", critic_subagent)
325
- critic_enabled = False
326
-
327
316
  replacements = {
328
317
  "{{SESSION_LOG_PATH}}": os.path.join(".prizmkit", "state", "refactor", args.refactor_id, "sessions", args.session_id, "logs", "session.log"),
329
318
  "{{RUN_ID}}": args.run_id,
@@ -342,8 +331,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
342
331
  "{{PRIORITY}}": refactor.get("priority", "medium"),
343
332
  "{{COMPLEXITY}}": refactor.get("complexity", "medium"),
344
333
  "{{PIPELINE_MODE}}": pipeline_mode,
345
- "{{CRITIC_ENABLED}}": "true" if critic_enabled else "false",
346
- "{{CRITIC_COUNT}}": str(refactor.get("critic_count", 1)),
347
334
  "{{REFACTOR_DESCRIPTION}}": refactor.get("description", ""),
348
335
  "{{USER_CONTEXT}}": format_user_context(refactor.get("user_context", [])),
349
336
  "{{ACCEPTANCE_CRITERIA}}": format_acceptance_criteria(refactor.get("acceptance_criteria", [])),
@@ -353,7 +340,6 @@ def build_replacements(args, refactor, refactors, global_context, script_dir):
353
340
  "{{TEAM_CONFIG_PATH}}": team_config_path,
354
341
  "{{DEV_SUBAGENT_PATH}}": dev_subagent,
355
342
  "{{REVIEWER_SUBAGENT_PATH}}": reviewer_subagent,
356
- "{{CRITIC_SUBAGENT_PATH}}": critic_subagent,
357
343
  "{{SESSION_STATUS_PATH}}": session_status_path,
358
344
  "{{PROJECT_ROOT}}": project_root,
359
345
  "{{ARTIFACT_DIR}}": ".prizmkit/refactor/{}".format(args.refactor_id),
@@ -409,13 +395,13 @@ def _refactor_header(pipeline_mode):
409
395
  title = "# Dev-Pipeline Refactor Session Bootstrap — {}\n".format(pipeline_mode.title())
410
396
  desc = {
411
397
  "lite": "**Tier 1 — Single Agent**: direct behavior-preserving refactor by the main orchestrator.",
412
- "standard": "**Tier 2 — Orchestrator + optional Critic**: direct refactor with review gates.",
398
+ "standard": "**Tier 2 — Orchestrator + Reviewer**: direct refactor with review gates.",
413
399
  "full": "**Tier 3 — Full Refactor Guardrails**: direct refactor with stronger behavior-preservation evidence.",
414
400
  }.get(pipeline_mode, "**Refactor pipeline**")
415
401
  return title + "\n" + desc + "\n"
416
402
 
417
403
 
418
- def assemble_refactor_sections(pipeline_mode, sections_dir, critic_enabled, browser_enabled):
404
+ def assemble_refactor_sections(pipeline_mode, sections_dir, browser_enabled):
419
405
  sections = [
420
406
  ("header", _refactor_header(pipeline_mode)),
421
407
  ("refactor-session-context", load_section(sections_dir, "refactor-session-context.md")),
@@ -428,8 +414,6 @@ def assemble_refactor_sections(pipeline_mode, sections_dir, critic_enabled, brow
428
414
  ("refactor-phase-init", load_section(sections_dir, "refactor-phase-init.md")),
429
415
  ("refactor-phase-plan", load_section(sections_dir, "refactor-phase-plan.md")),
430
416
  ]
431
- if critic_enabled:
432
- sections.append(("refactor-phase-critic", load_section(sections_dir, "refactor-phase-critic.md")))
433
417
  sections.extend([
434
418
  ("refactor-phase-implement", load_section(sections_dir, "refactor-phase-implement.md")),
435
419
  ("refactor-phase-review", load_section(sections_dir, "refactor-phase-review.md")),
@@ -497,14 +481,13 @@ def main():
497
481
 
498
482
  replacements = build_replacements(args, refactor, refactors, global_context, script_dir)
499
483
  pipeline_mode = replacements["{{PIPELINE_MODE}}"]
500
- critic_enabled = replacements["{{CRITIC_ENABLED}}"] == "true"
501
484
  browser_enabled = replacements["{{BROWSER_ENABLED}}"] == "true"
502
485
 
503
486
  use_sections = os.path.isdir(sections_dir) and not args.template
504
487
  sections = []
505
488
  if use_sections:
506
489
  try:
507
- sections = assemble_refactor_sections(pipeline_mode, sections_dir, critic_enabled, browser_enabled)
490
+ sections = assemble_refactor_sections(pipeline_mode, sections_dir, browser_enabled)
508
491
  rendered = render_from_sections(sections, replacements)
509
492
  except FileNotFoundError as exc:
510
493
  LOGGER.warning("Refactor section assembly failed (%s), falling back to legacy template", exc)
@@ -561,15 +544,13 @@ def main():
561
544
  if err:
562
545
  emit_failure(err)
563
546
 
564
- critic_count = int(replacements.get("{{CRITIC_COUNT}}", "1"))
565
547
  output = {
566
548
  "success": True,
567
549
  "output_path": os.path.abspath(args.output),
568
550
  "checkpoint_path": checkpoint_path,
569
551
  "model": refactor.get("model", ""),
570
552
  "pipeline_mode": pipeline_mode,
571
- "agent_count": mode_agent_count(pipeline_mode, critic_enabled, critic_count),
572
- "critic_enabled": critic_enabled,
553
+ "agent_count": mode_agent_count(pipeline_mode),
573
554
  "render_mode": "sections" if use_sections else "legacy",
574
555
  "validation_warnings": len(warnings),
575
556
  "validation_errors": len(errors),
@@ -114,6 +114,12 @@ def validate_bugs(bugs):
114
114
  errors.append("Bug at index {} is not an object".format(i))
115
115
  continue
116
116
 
117
+ for retired_field in ("critic", "critic_count"):
118
+ if retired_field in bug:
119
+ errors.append(
120
+ "Bug at index {} uses retired field '{}'".format(i, retired_field)
121
+ )
122
+
117
123
  # Check required fields
118
124
  for field in REQUIRED_BUG_FIELDS:
119
125
  if field not in bug:
@@ -106,6 +106,12 @@ def validate_features(features):
106
106
  errors.append("Feature at index {} is not an object".format(i))
107
107
  continue
108
108
 
109
+ for retired_field in ("critic", "critic_count"):
110
+ if retired_field in feature:
111
+ errors.append(
112
+ "Feature at index {} uses retired field '{}'".format(i, retired_field)
113
+ )
114
+
109
115
  # Check required fields
110
116
  for field in REQUIRED_FEATURE_FIELDS:
111
117
  if field not in feature:
@@ -109,6 +109,12 @@ def validate_refactors(refactors):
109
109
  errors.append("Refactor at index {} is not an object".format(i))
110
110
  continue
111
111
 
112
+ for retired_field in ("critic", "critic_count"):
113
+ if retired_field in refactor:
114
+ errors.append(
115
+ "Refactor at index {} uses retired field '{}'".format(i, retired_field)
116
+ )
117
+
112
118
  # Check required fields
113
119
  for field in REQUIRED_REFACTOR_FIELDS:
114
120
  if field not in refactor:
@@ -959,7 +959,7 @@ class ProgressTracker:
959
959
  summary = str(state.get("summary") or "")
960
960
  return bool(
961
961
  not task_type
962
- and summary.lower().startswith(("dev:", "critic:", "reviewer:", "agent:"))
962
+ and summary.lower().startswith(("dev:", "reviewer:", "agent:"))
963
963
  )
964
964
 
965
965
  def _has_tracked_claude_subagent_task(self):
@@ -113,6 +113,55 @@ def completed_step_artifacts_valid(project_root, artifacts):
113
113
  return all(artifact_exists(project_root, artifact) for artifact in artifacts)
114
114
 
115
115
 
116
+ def checkpoint_skip():
117
+ """Return an explicit no-checkpoint-step contract for a prompt section."""
118
+ return {"kind": "skip"}
119
+
120
+
121
+ def checkpoint_step(skill, name, artifacts=None):
122
+ """Return an explicit one-step checkpoint contract."""
123
+ return {
124
+ "kind": "step",
125
+ "skill": skill,
126
+ "name": name,
127
+ "required_artifacts": list(artifacts or []),
128
+ }
129
+
130
+
131
+ def checkpoint_group(steps):
132
+ """Return an explicit multi-step checkpoint contract."""
133
+ normalized = []
134
+ for step in steps:
135
+ if isinstance(step, dict):
136
+ normalized.append(step)
137
+ else:
138
+ skill, name, artifacts = step
139
+ normalized.append(checkpoint_step(skill, name, artifacts))
140
+ return {"kind": "group", "steps": normalized}
141
+
142
+
143
+ def _normalize_checkpoint_contract(mapping):
144
+ """Normalize explicit checkpoint contracts and legacy step tuples."""
145
+ if mapping is None:
146
+ return checkpoint_skip()
147
+ if isinstance(mapping, dict):
148
+ return mapping
149
+ skill, name, artifacts = mapping
150
+ return checkpoint_step(skill, name, artifacts)
151
+
152
+
153
+ def _contract_steps(contract):
154
+ """Return normalized step specs for a step/group contract."""
155
+ kind = contract.get("kind")
156
+ if kind == "skip":
157
+ return []
158
+ if kind == "step":
159
+ return [contract]
160
+ if kind == "group":
161
+ return [_normalize_checkpoint_contract(step) for step in contract.get("steps", [])]
162
+ raise ValueError("Unknown checkpoint contract kind: {}".format(kind))
163
+
164
+
116
165
  def generate_checkpoint_from_sections(
117
166
  sections,
118
167
  step_mapping,
@@ -136,37 +185,32 @@ def generate_checkpoint_from_sections(
136
185
  continue
137
186
 
138
187
  mapping = step_mapping[section_name]
139
- if mapping is None:
140
- for skill, name, artifacts in split_steps.get(section_name, []):
141
- step_id = "S{:02d}".format(step_counter)
142
- steps.append({
143
- "id": step_id,
144
- "skill": skill,
145
- "name": name,
146
- "status": "pending",
147
- "required_artifacts": resolve_artifacts(artifacts, item_slug),
148
- "depends_on": prev_step_id,
149
- })
150
- prev_step_id = step_id
151
- step_counter += 1
152
- continue
153
-
154
- skill, name, artifacts = mapping
155
- step_id = "S{:02d}".format(step_counter)
156
- status = "pending"
157
- if init_done and section_name in ("phase0-init", "phase0-test-baseline"):
158
- status = "skipped"
159
-
160
- steps.append({
161
- "id": step_id,
162
- "skill": skill,
163
- "name": name,
164
- "status": status,
165
- "required_artifacts": resolve_artifacts(artifacts, item_slug),
166
- "depends_on": prev_step_id,
167
- })
168
- prev_step_id = step_id
169
- step_counter += 1
188
+ if mapping is None and section_name in split_steps:
189
+ # Backward-compatible explicit group conversion for callers that
190
+ # have not yet moved split steps into their mapping. Plain None is
191
+ # always a skip, so no-op sections cannot expand accidentally.
192
+ mapping = checkpoint_group(split_steps[section_name])
193
+ contract = _normalize_checkpoint_contract(mapping)
194
+
195
+ for step_spec in _contract_steps(contract):
196
+ skill = step_spec["skill"]
197
+ name = step_spec["name"]
198
+ artifacts = step_spec.get("required_artifacts", [])
199
+ step_id = "S{:02d}".format(step_counter)
200
+ status = "pending"
201
+ if init_done and section_name in ("phase0-init", "phase0-test-baseline"):
202
+ status = "skipped"
203
+
204
+ steps.append({
205
+ "id": step_id,
206
+ "skill": skill,
207
+ "name": name,
208
+ "status": status,
209
+ "required_artifacts": resolve_artifacts(artifacts, item_slug),
210
+ "depends_on": prev_step_id,
211
+ })
212
+ prev_step_id = step_id
213
+ step_counter += 1
170
214
 
171
215
  return {
172
216
  "version": 1,
@@ -182,42 +226,50 @@ def generate_checkpoint_from_sections(
182
226
  def merge_checkpoint_state(existing, fresh, project_root, validator=None, logger=None):
183
227
  """Merge existing checkpoint state into a freshly generated definition."""
184
228
  validator = validator or (lambda _skill, artifacts: completed_step_artifacts_valid(project_root, artifacts))
185
- existing_status = {}
186
- existing_artifacts = {}
187
- fresh_artifacts = {
188
- step["skill"]: step.get("required_artifacts", [])
189
- for step in fresh.get("steps", [])
190
- }
191
-
229
+ existing_by_id = {}
230
+ existing_by_skill = {}
192
231
  for step in existing.get("steps", []):
193
- existing_status[step["skill"]] = step.get("status", "pending")
194
- existing_artifacts[step["skill"]] = step.get("required_artifacts", [])
195
-
196
- valid_completed = set()
197
- for skill_key, status in existing_status.items():
198
- if status == "completed":
199
- artifacts = fresh_artifacts.get(skill_key, existing_artifacts.get(skill_key, []))
200
- if validator(skill_key, artifacts):
201
- valid_completed.add(skill_key)
202
- elif logger is not None:
203
- logger.warning(
204
- "Step '%s' was completed but artifacts missing — resetting to pending",
205
- skill_key,
206
- )
207
- elif status == "skipped":
208
- valid_completed.add(skill_key)
232
+ if not isinstance(step, dict):
233
+ continue
234
+ sid = step.get("id")
235
+ skill = step.get("skill")
236
+ if sid:
237
+ existing_by_id[sid] = step
238
+ if skill:
239
+ existing_by_skill.setdefault(skill, []).append(step)
240
+
241
+ def matching_existing(fresh_step):
242
+ sid = fresh_step.get("id")
243
+ skill = fresh_step.get("skill")
244
+ if sid in existing_by_id and existing_by_id[sid].get("skill") == skill:
245
+ return existing_by_id[sid]
246
+ skill_matches = existing_by_skill.get(skill, [])
247
+ if len(skill_matches) == 1:
248
+ return skill_matches[0]
249
+ return None
209
250
 
210
251
  chain_broken = False
211
252
  for step in fresh.get("steps", []):
212
253
  if chain_broken:
213
254
  step["status"] = "pending"
214
255
  continue
215
- previous = existing_status.get(step["skill"])
216
- if step["skill"] in valid_completed:
217
- step["status"] = previous
218
- else:
219
- chain_broken = True
220
- step["status"] = "pending"
256
+ previous = matching_existing(step)
257
+ previous_status = previous.get("status", "pending") if isinstance(previous, dict) else "pending"
258
+ if previous_status == "completed":
259
+ artifacts = step.get("required_artifacts", [])
260
+ if validator(step.get("skill", ""), artifacts):
261
+ step["status"] = "completed"
262
+ continue
263
+ if logger is not None:
264
+ logger.warning(
265
+ "Step '%s' was completed but artifacts missing — resetting to pending",
266
+ step.get("id") or step.get("skill"),
267
+ )
268
+ elif previous_status == "skipped":
269
+ step["status"] = "skipped"
270
+ continue
271
+ chain_broken = True
272
+ step["status"] = "pending"
221
273
 
222
274
  return fresh
223
275
 
@@ -241,10 +293,9 @@ def normalize_mode(value, default="lite"):
241
293
  return value if value in {"lite", "standard", "full"} else default
242
294
 
243
295
 
244
- def mode_agent_count(pipeline_mode, critic_enabled=False, critic_count=1):
296
+ def mode_agent_count(pipeline_mode):
245
297
  """Return the prompt metadata agent count for a mode."""
246
- base = {"lite": 1, "standard": 3, "full": 3}.get(pipeline_mode, 1)
247
- return base + (int(critic_count or 1) if critic_enabled else 0)
298
+ return {"lite": 1, "standard": 3, "full": 3}.get(pipeline_mode, 1)
248
299
 
249
300
 
250
301
  def detect_dev_server_port(project_root):
@@ -232,11 +232,8 @@ def _read_bugfix_checkpoint_phase(project_root, bug_id):
232
232
 
233
233
 
234
234
  def _checkpoint_phase_or_cached(project_root, bug_id, status_data):
235
- """Return live checkpoint phase, falling back to cached status data."""
236
- checkpoint_phase = _read_bugfix_checkpoint_phase(project_root, bug_id)
237
- if checkpoint_phase is not None:
238
- return checkpoint_phase
239
- return status_data.get("resume_from_phase")
235
+ """Return live checkpoint phase from the authoritative checkpoint only."""
236
+ return _read_bugfix_checkpoint_phase(project_root, bug_id)
240
237
 
241
238
 
242
239
  def _sync_checkpoint_phase(project_root, bug_id, status_data):
@@ -758,8 +755,7 @@ def action_status(bug_list_data, state_dir, project_root=None):
758
755
  bs = load_bug_status(state_dir, bid)
759
756
  retry_count = bs.get("retry_count", 0)
760
757
  max_retries_val = bs.get("max_retries", 3)
761
- # Read phase cursor from checkpoint (single source of truth). The
762
- # status.json field is only a cache/fallback for missing checkpoints.
758
+ # Read phase cursor from checkpoint (authoritative when valid).
763
759
  resume_phase = _checkpoint_phase_or_cached(project_root, bid, bs)
764
760
  if resume_phase != bs.get("resume_from_phase"):
765
761
  bs["resume_from_phase"] = resume_phase