prizmkit 1.1.153 → 1.1.155

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 (178) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codex/skill-adapter.js +1 -1
  3. package/bundled/adapters/pi/skill-adapter.js +2 -1
  4. package/bundled/dev-pipeline/.env.example +2 -6
  5. package/bundled/dev-pipeline/README.md +120 -113
  6. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  7. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +355 -15
  8. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +189 -110
  9. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  10. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +4 -20
  11. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
  12. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  14. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  15. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  16. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
  17. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -10
  18. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
  19. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +139 -74
  20. package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
  21. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  22. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
  23. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
  24. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
  25. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
  26. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  27. package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
  28. package/bundled/dev-pipeline/scripts/update-bug-status.py +197 -71
  29. package/bundled/dev-pipeline/scripts/update-checkpoint.py +77 -16
  30. package/bundled/dev-pipeline/scripts/update-feature-status.py +46 -121
  31. package/bundled/dev-pipeline/scripts/update-refactor-status.py +46 -123
  32. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  33. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
  34. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
  35. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  36. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
  37. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
  38. package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
  39. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
  40. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
  41. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
  42. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
  43. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  44. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
  45. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
  46. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
  47. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
  48. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
  49. package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
  50. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
  51. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
  52. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
  53. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
  54. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
  55. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
  56. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
  57. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
  58. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
  59. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  60. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
  61. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
  62. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
  63. package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
  64. package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
  65. package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
  66. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  67. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +617 -30
  68. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -194
  69. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
  70. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
  71. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +281 -206
  72. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +322 -0
  73. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  74. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -7
  75. package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
  76. package/bundled/dev-pipeline/tests/test_unified_cli.py +617 -188
  77. package/bundled/skills/_metadata.json +9 -9
  78. package/bundled/skills/app-planner/SKILL.md +3 -3
  79. package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
  80. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
  81. package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
  82. package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
  83. package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
  84. package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
  85. package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
  86. package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
  87. package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
  88. package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
  89. package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
  90. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
  91. package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
  92. package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
  93. package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
  94. package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
  95. package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
  96. package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
  97. package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
  98. package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
  99. package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
  100. package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
  101. package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
  102. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  103. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  104. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  105. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  106. package/bundled/skills/prizmkit/SKILL.md +17 -18
  107. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
  108. package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
  109. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
  110. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
  111. package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
  112. package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
  113. package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
  114. package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
  115. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
  116. package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
  117. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
  118. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
  119. package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
  120. package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
  121. package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
  122. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
  123. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
  124. package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
  125. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
  126. package/bundled/skills/prizmkit-init/SKILL.md +4 -0
  127. package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
  128. package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
  129. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
  130. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
  131. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +11 -1
  132. package/bundled/skills/prizmkit-retrospective/SKILL.md +63 -77
  133. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -3
  134. package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
  135. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +17 -23
  136. package/bundled/skills/prizmkit-test/SKILL.md +64 -51
  137. package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
  138. package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
  139. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
  140. package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
  141. package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
  142. package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
  143. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
  144. package/bundled/skills/recovery-workflow/SKILL.md +9 -6
  145. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  146. package/bundled/skills/recovery-workflow/references/detection.md +7 -6
  147. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +16 -6
  148. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  149. package/bundled/templates/project-memory-template.md +19 -11
  150. package/package.json +1 -1
  151. package/src/scaffold.js +33 -22
  152. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  153. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
  154. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
  155. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
  156. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
  157. package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
  158. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
  159. package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
  160. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
  161. package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
  162. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
  163. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
  164. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
  165. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
  166. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
  167. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
  168. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
  169. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
  170. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
  171. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
  172. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
  173. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
  174. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
  175. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
  176. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
  177. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
  178. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
@@ -9,12 +9,12 @@ Handles eight actions:
9
9
  - pause: Save pipeline state for graceful shutdown
10
10
  - reset: Reset a bug to pending (status + retry count)
11
11
  - clean: Reset + delete session history + delete bugfix artifacts
12
- - unskip: Reset skipped bugs back to pending
12
+ - reset_state: Recover blocking roots and causally recoverable auto-skipped descendants
13
13
 
14
14
  Usage:
15
15
  python3 update-bug-status.py \
16
16
  --bug-list <path> --state-dir <path> \
17
- --action <get_next|start|update|status|pause|reset|clean|unskip> \
17
+ --action <get_next|start|update|status|pause|reset|clean|reset_state> \
18
18
  [--bug-id <id>] [--session-status <status>] \
19
19
  [--session-id <id>] [--max-retries <n>] [--max-infra-retries <n>]
20
20
  """
@@ -23,6 +23,7 @@ import argparse
23
23
  import json
24
24
  import os
25
25
  import shutil
26
+ import sys
26
27
  from datetime import datetime, timezone
27
28
 
28
29
  from utils import (
@@ -33,6 +34,7 @@ from utils import (
33
34
  _build_progress_bar,
34
35
  read_checkpoint_cursor,
35
36
  dependency_rank_map,
37
+ causal_reset_selection,
36
38
  )
37
39
 
38
40
 
@@ -53,7 +55,8 @@ SESSION_STATUS_VALUES = [
53
55
  "merge_conflict",
54
56
  ]
55
57
 
56
- TERMINAL_STATUSES = {"completed", "failed", "skipped", "needs_info"}
58
+ TERMINAL_STATUSES = {"completed", "failed", "skipped", "needs_info", "auto_skipped"}
59
+ RESET_BLOCKING_ROOT_STATUSES = {"failed", "skipped", "needs_info"}
57
60
 
58
61
  # Severity priority (lower value = higher priority)
59
62
  SEVERITY_PRIORITY = {
@@ -72,7 +75,7 @@ def parse_args():
72
75
  parser.add_argument("--state-dir", required=True, help="Path to the state directory (default: .prizmkit/state/bugfix)")
73
76
  parser.add_argument(
74
77
  "--action", required=True,
75
- choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "unskip", "complete"],
78
+ choices=["get_next", "start", "update", "status", "pause", "reset", "clean", "reset_state", "complete"],
76
79
  help="Action to perform",
77
80
  )
78
81
  parser.add_argument("--bug-id", default=None, help="Bug ID (required for 'update'/'reset'/'clean' actions)")
@@ -383,19 +386,117 @@ def continuation_metadata_summary(status_data):
383
386
  }
384
387
 
385
388
 
389
+ # ---------------------------------------------------------------------------
390
+ # Auto-skip: cascade terminal blockers to pending downstream bugs
391
+ # ---------------------------------------------------------------------------
392
+
393
+ def auto_skip_blocked_bugs(bug_list_path, state_dir, blocking_bug_id=None):
394
+ """Mark pending descendants of terminal Bugfix blockers as auto_skipped."""
395
+ data, err = load_json_file(bug_list_path)
396
+ if err:
397
+ error_out("Cannot load bug fix list for auto-skip: {}".format(err))
398
+ return []
399
+ bugs = data.get("bugs", [])
400
+ status_map = {
401
+ bug.get("id"): bug.get("status", "pending")
402
+ for bug in bugs
403
+ if isinstance(bug, dict) and bug.get("id")
404
+ }
405
+
406
+ to_skip = set()
407
+ changed = True
408
+ while changed:
409
+ changed = False
410
+ for bug in bugs:
411
+ if not isinstance(bug, dict):
412
+ continue
413
+ bug_id = bug.get("id")
414
+ if not bug_id or bug_id in to_skip:
415
+ continue
416
+ if status_map.get(bug_id, "pending") != "pending":
417
+ continue
418
+ dependencies = bug.get("dependencies", [])
419
+ if not isinstance(dependencies, list):
420
+ continue
421
+ if any(
422
+ status_map.get(dependency_id, "pending")
423
+ in RESET_BLOCKING_ROOT_STATUSES | {"auto_skipped"}
424
+ or dependency_id in to_skip
425
+ for dependency_id in dependencies
426
+ ):
427
+ to_skip.add(bug_id)
428
+ status_map[bug_id] = "auto_skipped"
429
+ changed = True
430
+
431
+ if not to_skip:
432
+ return []
433
+
434
+ for bug in bugs:
435
+ if isinstance(bug, dict) and bug.get("id") in to_skip:
436
+ bug["status"] = "auto_skipped"
437
+ err = write_json_file(bug_list_path, data)
438
+ if err:
439
+ error_out("Failed to write auto-skipped bugs: {}".format(err))
440
+ return []
441
+
442
+ for bug_id in to_skip:
443
+ bs = load_bug_status(state_dir, bug_id)
444
+ bs["updated_at"] = now_iso()
445
+ err = save_bug_status(state_dir, bug_id, bs)
446
+ if err:
447
+ error_out("Failed to save auto-skipped bug status: {}".format(err))
448
+ return []
449
+
450
+ skipped_info = []
451
+ for bug in bugs:
452
+ if not isinstance(bug, dict) or bug.get("id") not in to_skip:
453
+ continue
454
+ dependencies = bug.get("dependencies", [])
455
+ blockers = [
456
+ dependency_id
457
+ for dependency_id in dependencies
458
+ if dependency_id == blocking_bug_id
459
+ or status_map.get(dependency_id, "pending")
460
+ in RESET_BLOCKING_ROOT_STATUSES | {"auto_skipped"}
461
+ ]
462
+ skipped_info.append({
463
+ "bug_id": bug["id"],
464
+ "title": bug.get("title", ""),
465
+ "blocked_by": blockers,
466
+ })
467
+
468
+ cause = blocking_bug_id or "existing terminal blockers"
469
+ print(
470
+ "[auto-skip] {} bug(s) auto-skipped due to {}".format(
471
+ len(skipped_info), cause
472
+ ),
473
+ file=sys.stderr,
474
+ )
475
+ return skipped_info
476
+
477
+
386
478
  # ---------------------------------------------------------------------------
387
479
  # Action: get_next
388
480
  # ---------------------------------------------------------------------------
389
481
 
390
- def action_get_next(bug_list_data, state_dir, project_root=None):
482
+ def action_get_next(bug_list_data, state_dir, project_root=None, bug_list_path=None):
391
483
  """Find the next bug to process.
392
484
 
393
485
  Priority logic:
394
- 1. Skip terminal statuses (completed, failed, skipped, needs_info)
395
- 2. Only consider bugs whose dependencies are all completed
396
- 3. Prefer in_progress bugs (interrupted session resume) over pending
397
- 4. Within each status class, use stable topological list order
486
+ 1. Lazily normalize pending descendants of terminal blockers to auto_skipped
487
+ 2. Skip terminal statuses (completed, failed, skipped, needs_info, auto_skipped)
488
+ 3. Only consider bugs whose dependencies are all completed
489
+ 4. Prefer in_progress bugs (interrupted session resume) over pending
490
+ 5. Within each status class, use stable topological list order
398
491
  """
492
+ if bug_list_path:
493
+ auto_skip_blocked_bugs(bug_list_path, state_dir)
494
+ refreshed, refresh_error = load_json_file(bug_list_path)
495
+ if refresh_error:
496
+ error_out("Cannot reload bug fix list after auto-skip: {}".format(refresh_error))
497
+ return
498
+ bug_list_data = refreshed
499
+
399
500
  bugs = bug_list_data.get("bugs", [])
400
501
  if not bugs:
401
502
  print("PIPELINE_COMPLETE")
@@ -513,6 +614,8 @@ def action_update(args, bug_list_path, state_dir):
513
614
  bs = load_bug_status(state_dir, bug_id)
514
615
 
515
616
  current_list_status = get_bug_status_from_list(bug_list_path, bug_id)
617
+ bs["max_retries"] = max_retries
618
+ bs["max_infra_retries"] = max_infra_retries
516
619
 
517
620
  # Track what status we write to bug-fix-list.json
518
621
  new_status = current_list_status
@@ -677,6 +780,12 @@ def action_update(args, bug_list_path, state_dir):
677
780
  error_out("Failed to save bug status: {}".format(err))
678
781
  return
679
782
 
783
+ auto_skipped_bugs = []
784
+ if new_status in RESET_BLOCKING_ROOT_STATUSES:
785
+ auto_skipped_bugs = auto_skip_blocked_bugs(
786
+ bug_list_path, state_dir, bug_id
787
+ )
788
+
680
789
  summary = {
681
790
  "action": "update",
682
791
  "bug_id": bug_id,
@@ -689,6 +798,8 @@ def action_update(args, bug_list_path, state_dir):
689
798
  "checkpoint_state": semantic_checkpoint,
690
799
  "updated_at": bs["updated_at"],
691
800
  }
801
+ if auto_skipped_bugs:
802
+ summary["auto_skipped"] = [info["bug_id"] for info in auto_skipped_bugs]
692
803
  if session_status in ("commit_missing", "docs_missing", "merge_conflict"):
693
804
  summary["degraded_reason"] = session_status
694
805
  summary["restart_policy"] = "finalization_retry"
@@ -863,7 +974,20 @@ def action_status(bug_list_data, state_dir, project_root=None):
863
974
  project_name = bug_list_data.get("project_name", "Unknown")
864
975
  project_root = os.path.abspath(project_root) if project_root else _default_project_root()
865
976
 
866
- counts = {"completed": 0, "in_progress": 0, "failed": 0, "pending": 0, "needs_info": 0, "skipped": 0}
977
+ counts = {
978
+ "completed": 0,
979
+ "in_progress": 0,
980
+ "failed": 0,
981
+ "pending": 0,
982
+ "needs_info": 0,
983
+ "skipped": 0,
984
+ "auto_skipped": 0,
985
+ }
986
+ status_map = {
987
+ bug.get("id"): bug.get("status", "pending")
988
+ for bug in bugs
989
+ if isinstance(bug, dict) and bug.get("id")
990
+ }
867
991
  bug_lines = []
868
992
 
869
993
  for bug in bugs:
@@ -879,11 +1003,8 @@ def action_status(bug_list_data, state_dir, project_root=None):
879
1003
  bs = load_bug_status(state_dir, bid)
880
1004
  retry_count = bs.get("retry_count", 0)
881
1005
  max_retries_val = bs.get("max_retries", 3)
882
- # Read phase cursor from checkpoint (authoritative when valid).
1006
+ # Read the authoritative checkpoint cursor without mutating cached runtime state.
883
1007
  resume_phase = _checkpoint_phase_or_cached(project_root, bid, bs)
884
- if resume_phase != bs.get("resume_from_phase"):
885
- bs["resume_from_phase"] = resume_phase
886
- save_bug_status(state_dir, bid, bs)
887
1008
 
888
1009
  if bstatus in counts:
889
1010
  counts[bstatus] += 1
@@ -901,6 +1022,8 @@ def action_status(bug_list_data, state_dir, project_root=None):
901
1022
  icon = COLOR_MAGENTA + "[?]" + COLOR_RESET
902
1023
  elif bstatus == "skipped":
903
1024
  icon = COLOR_GRAY + "[—]" + COLOR_RESET
1025
+ elif bstatus == "auto_skipped":
1026
+ icon = COLOR_GRAY + "[⊘]" + COLOR_RESET
904
1027
  else:
905
1028
  icon = COLOR_GRAY + "[ ]" + COLOR_RESET
906
1029
 
@@ -921,6 +1044,16 @@ def action_status(bug_list_data, state_dir, project_root=None):
921
1044
  detail = " (failed after {} retries)".format(retry_count)
922
1045
  elif bstatus == "needs_info":
923
1046
  detail = " (needs more info)"
1047
+ elif bstatus == "auto_skipped":
1048
+ blockers = [
1049
+ dependency_id
1050
+ for dependency_id in bug.get("dependencies", [])
1051
+ if status_map.get(dependency_id, "pending")
1052
+ in RESET_BLOCKING_ROOT_STATUSES | {"auto_skipped"}
1053
+ ]
1054
+ detail = " (blocked by {})".format(
1055
+ ", ".join(blockers) if blockers else "dependency chain"
1056
+ )
924
1057
 
925
1058
  # Colorize
926
1059
  if bstatus == "completed":
@@ -954,8 +1087,12 @@ def action_status(bug_list_data, state_dir, project_root=None):
954
1087
  summary_line = "Total: {} bugs | Completed: {} | In Progress: {}".format(
955
1088
  total, completed, counts["in_progress"]
956
1089
  )
957
- summary_line2 = "Failed: {} | Pending: {} | Needs Info: {} | Skipped: {}".format(
958
- counts["failed"], counts["pending"], counts["needs_info"], counts["skipped"]
1090
+ summary_line2 = "Failed: {} | Pending: {} | Needs Info: {} | Skipped: {} | Auto-skipped: {}".format(
1091
+ counts["failed"],
1092
+ counts["pending"],
1093
+ counts["needs_info"],
1094
+ counts["skipped"],
1095
+ counts["auto_skipped"],
959
1096
  )
960
1097
 
961
1098
  inner = BOX_WIDTH - 2
@@ -1146,6 +1283,8 @@ def action_start(args, bug_list_path, state_dir):
1146
1283
  bs = load_bug_status(state_dir, bug_id)
1147
1284
  old_status = get_bug_status_from_list(bug_list_path, bug_id)
1148
1285
 
1286
+ bs["max_retries"] = args.max_retries
1287
+ bs["max_infra_retries"] = args.max_infra_retries
1149
1288
  bs["updated_at"] = now_iso()
1150
1289
 
1151
1290
  err = save_bug_status(state_dir, bug_id, bs)
@@ -1172,64 +1311,37 @@ def action_start(args, bug_list_path, state_dir):
1172
1311
 
1173
1312
 
1174
1313
  # ---------------------------------------------------------------------------
1175
- # Action: unskip
1314
+ # Action: reset_state
1176
1315
  # ---------------------------------------------------------------------------
1177
1316
 
1178
- def action_unskip(args, bug_list_path, state_dir):
1179
- """Reset skipped bugs back to pending.
1180
-
1181
- Two modes:
1182
- - --bug-id B-001: Reset the specified skipped bug to pending.
1183
- - No --bug-id: Reset ALL skipped bugs to pending.
1184
- """
1185
- bug_id = args.bug_id
1186
-
1317
+ def action_reset_state(args, bug_list_path, state_dir):
1318
+ """Reset one causal bug closure, or every recoverable closure, to pending."""
1187
1319
  data, err = load_json_file(bug_list_path)
1188
1320
  if err:
1189
1321
  error_out("Cannot load bug fix list: {}".format(err))
1190
1322
  return
1191
1323
  bugs = data.get("bugs", [])
1192
1324
 
1193
- to_reset = set()
1194
-
1195
- if bug_id:
1196
- # Find the target bug
1197
- target = None
1198
- for b in bugs:
1199
- if isinstance(b, dict) and b.get("id") == bug_id:
1200
- target = b
1201
- break
1202
- if not target:
1203
- error_out("Bug '{}' not found in .prizmkit/plans/bug-fix-list.json".format(bug_id))
1204
- return
1205
- if target.get("status") not in ("failed", "skipped", "needs_info"):
1206
- error_out(
1207
- "Bug '{}' has status '{}', expected 'failed', 'skipped', or 'needs_info'".format(
1208
- bug_id, target.get("status", "unknown")
1209
- )
1210
- )
1211
- return
1212
- to_reset.add(bug_id)
1213
- else:
1214
- # No bug-id: reset ALL skipped/failed/needs_info bugs
1215
- for b in bugs:
1216
- if isinstance(b, dict) and b.get("id"):
1217
- if b.get("status") in ("failed", "skipped", "needs_info"):
1218
- to_reset.add(b["id"])
1219
-
1220
- if not to_reset:
1221
- error_out("No bugs to unskip")
1325
+ try:
1326
+ recovery_ids = causal_reset_selection(
1327
+ bugs,
1328
+ selected_id=args.bug_id,
1329
+ blocking_root_statuses=RESET_BLOCKING_ROOT_STATUSES,
1330
+ item_label="Bug",
1331
+ )
1332
+ except ValueError as exc:
1333
+ error_out(str(exc))
1222
1334
  return
1223
1335
 
1224
- # Reset all collected bugs in bug-fix-list.json
1336
+ to_reset = set(recovery_ids)
1225
1337
  reset_details = []
1226
- for b in bugs:
1227
- if isinstance(b, dict) and b.get("id") in to_reset:
1228
- old_status = b.get("status", "unknown")
1229
- b["status"] = "pending"
1338
+ for bug in bugs:
1339
+ if isinstance(bug, dict) and bug.get("id") in to_reset:
1340
+ old_status = bug.get("status", "unknown")
1341
+ bug["status"] = "pending"
1230
1342
  reset_details.append({
1231
- "bug_id": b["id"],
1232
- "title": b.get("title", ""),
1343
+ "bug_id": bug["id"],
1344
+ "title": bug.get("title", ""),
1233
1345
  "old_status": old_status,
1234
1346
  })
1235
1347
 
@@ -1238,22 +1350,31 @@ def action_unskip(args, bug_list_path, state_dir):
1238
1350
  error_out("Failed to write .prizmkit/plans/bug-fix-list.json: {}".format(err))
1239
1351
  return
1240
1352
 
1241
- # Reset runtime fields in status.json for each bug
1242
- for bid in to_reset:
1243
- bs = load_bug_status(state_dir, bid)
1353
+ for bug_id in recovery_ids:
1354
+ bs = load_bug_status(state_dir, bug_id)
1355
+ checkout_identity = {
1356
+ key: bs[key]
1357
+ for key in ("active_dev_branch", "base_branch")
1358
+ if key in bs
1359
+ }
1244
1360
  bs["retry_count"] = 0
1245
1361
  bs["infra_error_count"] = 0
1246
1362
  bs["last_infra_error_session_id"] = None
1247
1363
  bs["sessions"] = []
1248
1364
  bs["last_session_id"] = None
1365
+ bs["last_failed_session_id"] = None
1249
1366
  bs["resume_from_phase"] = None
1250
1367
  reset_continuation_metadata(bs)
1368
+ bs.update(checkout_identity)
1251
1369
  bs["updated_at"] = now_iso()
1252
- save_bug_status(state_dir, bid, bs)
1370
+ err = save_bug_status(state_dir, bug_id, bs)
1371
+ if err:
1372
+ error_out("Failed to save bug status: {}".format(err))
1373
+ return
1253
1374
 
1254
1375
  result = {
1255
- "action": "unskip",
1256
- "reset_count": len(to_reset),
1376
+ "action": "reset_state",
1377
+ "reset_count": len(recovery_ids),
1257
1378
  "bugs": reset_details,
1258
1379
  }
1259
1380
  print(json.dumps(result, indent=2, ensure_ascii=False))
@@ -1283,7 +1404,12 @@ def main():
1283
1404
  error_out("Cannot load bug fix list: {}".format(err))
1284
1405
 
1285
1406
  if args.action == "get_next":
1286
- action_get_next(bug_list_data, args.state_dir, args.project_root)
1407
+ action_get_next(
1408
+ bug_list_data,
1409
+ args.state_dir,
1410
+ args.project_root,
1411
+ args.bug_list,
1412
+ )
1287
1413
  elif args.action == "start":
1288
1414
  action_start(args, args.bug_list, args.state_dir)
1289
1415
  elif args.action == "update":
@@ -1296,8 +1422,8 @@ def main():
1296
1422
  action_clean(args, args.bug_list, args.state_dir)
1297
1423
  elif args.action == "pause":
1298
1424
  action_pause(args.state_dir)
1299
- elif args.action == "unskip":
1300
- action_unskip(args, args.bug_list, args.state_dir)
1425
+ elif args.action == "reset_state":
1426
+ action_reset_state(args, args.bug_list, args.state_dir)
1301
1427
  elif args.action == "complete":
1302
1428
  # Shortcut: 'complete' is equivalent to 'update --session-status success'
1303
1429
  args.session_status = "success"
@@ -14,6 +14,7 @@ if _PIPELINE_ROOT not in sys.path:
14
14
 
15
15
  from prizmkit_runtime.checkpoint_state import ( # noqa: E402
16
16
  MAX_OUTER_REPAIR_ROUNDS,
17
+ PLAN_RESULTS,
17
18
  REVIEW_RESULTS,
18
19
  SKILL_WORKFLOW_STAGES,
19
20
  STEP_ID_PATTERN,
@@ -110,9 +111,16 @@ def _mandatory_predecessor_verdict_error(steps, index, workflow_type):
110
111
  preceding = steps[:index]
111
112
  review = next((item for item in preceding if item.get("skill") == "prizmkit-code-review"), None)
112
113
  test = next((item for item in preceding if item.get("skill") == "prizmkit-test"), None)
114
+ skill = steps[index].get("skill")
115
+ if skill == "prizmkit-implement":
116
+ planning = [
117
+ item for item in preceding
118
+ if item.get("stage_result") in PLAN_RESULTS
119
+ ]
120
+ if len(planning) != 1 or planning[0].get("status") != "completed" or planning[0].get("stage_result") != "PLAN_READY":
121
+ return "Implementation requires a completed PLAN_READY predecessor"
113
122
  if review is None and test is None:
114
123
  return None
115
- skill = steps[index].get("skill")
116
124
  if skill == "prizmkit-test":
117
125
  if not review or review.get("status") != "completed" or review.get("stage_result") != "REVIEW_PASS":
118
126
  return "Test stage requires a completed REVIEW_PASS predecessor"
@@ -153,29 +161,44 @@ def _validate_semantic_update(step, semantic_update):
153
161
  terminal_status = semantic_update.get("terminal_status")
154
162
  if terminal_status is not None and terminal_status not in VALID_TERMINAL_STATUSES:
155
163
  return "Invalid terminal_status '{}'".format(terminal_status)
156
- if terminal_status == "WORKFLOW_BLOCKED" and not semantic_update.get("blocked_reason"):
164
+ blocked_reason = semantic_update.get("blocked_reason")
165
+ blocked_policy_result = stage_result in {"TEST_NEEDS_FIXES", "TEST_BLOCKED"}
166
+ if terminal_status == "WORKFLOW_COMPLETED" and blocked_policy_result:
167
+ return "WORKFLOW_COMPLETED conflicts with blocking stage result {}".format(stage_result)
168
+ if blocked_reason is not None:
169
+ if not isinstance(blocked_reason, str) or not blocked_reason.strip():
170
+ return "blocked_reason must be a non-empty string"
171
+ if terminal_status == "WORKFLOW_COMPLETED":
172
+ return "WORKFLOW_COMPLETED cannot retain blocked_reason"
173
+ if terminal_status != "WORKFLOW_BLOCKED" and not blocked_policy_result:
174
+ return "blocked_reason requires terminal_status=WORKFLOW_BLOCKED"
175
+ if terminal_status == "WORKFLOW_BLOCKED" and not blocked_reason:
157
176
  return "WORKFLOW_BLOCKED requires blocked_reason"
158
177
  return None
159
178
 
160
179
 
161
180
  def _apply_repair_policy(l4_state, stage_result, repair_scope):
162
- if stage_result not in {"REVIEW_NEEDS_FIXES", "TEST_NEEDS_FIXES", "TEST_BLOCKED"}:
163
- return False
164
- current_round = l4_state.get("repair_round", 0)
165
- if not isinstance(current_round, int) or isinstance(current_round, bool):
166
- current_round = MAX_OUTER_REPAIR_ROUNDS
167
- if stage_result in {"TEST_NEEDS_FIXES", "TEST_BLOCKED"}:
181
+ terminal_blockers = {
182
+ "PLAN_BLOCKED": "plan_blocked",
183
+ "IMPLEMENT_REPAIR": "implementation_repair",
184
+ "IMPLEMENT_BLOCKED": "implementation_blocked",
185
+ "TEST_BLOCKED": "test_blocked",
186
+ "RETRO_BLOCKED": "retrospective_blocked",
187
+ "COMMIT_BLOCKED": "commit_blocked",
188
+ }
189
+ if stage_result in terminal_blockers:
168
190
  l4_state.update(
169
191
  terminal_status="WORKFLOW_BLOCKED",
170
- blocked_reason=(
171
- "test_needs_fixes"
172
- if stage_result == "TEST_NEEDS_FIXES"
173
- else "test_blocked"
174
- ),
192
+ blocked_reason=terminal_blockers[stage_result],
175
193
  current_stage=None,
176
194
  next_stage=None,
177
195
  )
178
196
  return False
197
+ if stage_result not in {"REVIEW_NEEDS_FIXES", "TEST_NEEDS_FIXES"}:
198
+ return False
199
+ current_round = l4_state.get("repair_round", 0)
200
+ if not isinstance(current_round, int) or isinstance(current_round, bool):
201
+ current_round = MAX_OUTER_REPAIR_ROUNDS
179
202
  if current_round >= MAX_OUTER_REPAIR_ROUNDS:
180
203
  l4_state.update(
181
204
  repair_round=MAX_OUTER_REPAIR_ROUNDS,
@@ -194,12 +217,36 @@ def _apply_repair_policy(l4_state, stage_result, repair_scope):
194
217
  )
195
218
  return False
196
219
  l4_state["repair_round"] = current_round + 1
220
+ l4_state["repair_scope"] = repair_scope
197
221
  l4_state["terminal_status"] = None
198
222
  l4_state["blocked_reason"] = None
199
223
  l4_state["next_stage"] = "prizmkit-implement"
200
224
  return True
201
225
 
202
226
 
227
+ def _prepare_repair_reentry(data, index):
228
+ """Rewind downstream gates when a routed repair re-enters implementation."""
229
+ step = data["steps"][index]
230
+ if step.get("skill") != "prizmkit-implement":
231
+ return
232
+ state_key = semantic_state_key(str(data.get("workflow_type") or ""))
233
+ l4_state = data.get(state_key)
234
+ if not isinstance(l4_state, dict):
235
+ return
236
+ if (
237
+ l4_state.get("next_stage") != "prizmkit-implement"
238
+ or l4_state.get("stage_result")
239
+ not in {"REVIEW_NEEDS_FIXES", "TEST_NEEDS_FIXES"}
240
+ ):
241
+ return
242
+ for later in data["steps"][index + 1:]:
243
+ later["status"] = "pending"
244
+ later.pop("stage_result", None)
245
+ later.pop("note", None)
246
+ l4_state["terminal_status"] = None
247
+ l4_state["blocked_reason"] = None
248
+
249
+
203
250
  def _sync_cursor(data, l4_state, preserve_next_stage=False):
204
251
  active = next(
205
252
  (step for step in data["steps"] if step.get("status") not in TERMINAL_STEP_STATUSES),
@@ -280,6 +327,9 @@ def update_checkpoint(
280
327
  suffix = " Available: {}".format(", ".join(available)) if available else ""
281
328
  return {"ok": False, "error": "{}{}".format(find_error or "Step not found.", suffix)}
282
329
 
330
+ if new_status == "in_progress":
331
+ _prepare_repair_reentry(data, index)
332
+
283
333
  if new_status in {"in_progress", "completed", "skipped"} and not _predecessors_terminal(data["steps"], index):
284
334
  return {
285
335
  "ok": False,
@@ -295,6 +345,11 @@ def update_checkpoint(
295
345
  return {"ok": False, "error": verdict_error}
296
346
 
297
347
  semantic_update = _normalize_semantic_update(semantic_update)
348
+ if semantic_update.get("stage_result") == "COMMITTED":
349
+ return {
350
+ "ok": False,
351
+ "error": "COMMITTED is Runtime-owned and cannot be written by the Prompt checkpoint helper",
352
+ }
298
353
  semantic_error = _validate_semantic_update(step, semantic_update)
299
354
  if semantic_error:
300
355
  return {"ok": False, "error": semantic_error}
@@ -308,6 +363,10 @@ def update_checkpoint(
308
363
  stage_result = stage_result or step.get("stage_result")
309
364
  if stage_result != "TEST_PASS":
310
365
  return {"ok": False, "error": "Test completion requires TEST_PASS"}
366
+ if new_status == "completed" and step.get("skill") == "prizmkit-committer":
367
+ stage_result = stage_result or step.get("stage_result")
368
+ if stage_result != "COMMITTED":
369
+ return {"ok": False, "error": "Committer completion requires Runtime-owned COMMITTED"}
311
370
  expected_status = expected_status_for_stage_result(stage_result)
312
371
  if expected_status is not None and new_status != expected_status:
313
372
  return {
@@ -339,7 +398,7 @@ def update_checkpoint(
339
398
  if key in semantic_update:
340
399
  l4_state[key] = semantic_update[key]
341
400
  l4_state["schema_version"] = 1
342
- l4_state["orchestrator"] = "prizmkit-l4"
401
+ l4_state["orchestrator"] = "pipeline-runtime"
343
402
  skill = step.get("skill")
344
403
  l4_state["stage"] = SKILL_WORKFLOW_STAGES.get(skill, skill)
345
404
  l4_state["status"] = new_status
@@ -361,11 +420,13 @@ def update_checkpoint(
361
420
  exists=True,
362
421
  project_root=_checkpoint_project_root(checkpoint_path),
363
422
  )
364
- if not final_state.valid:
423
+ if not final_state.valid or final_state.semantic.error_code:
424
+ error_code = final_state.error_code or final_state.semantic.error_code
425
+ error_message = final_state.error_message or final_state.semantic.error_message
365
426
  return {
366
427
  "ok": False,
367
428
  "error": "Update would create invalid checkpoint ({}): {}".format(
368
- final_state.error_code, final_state.error_message
429
+ error_code, error_message
369
430
  ),
370
431
  }
371
432