prizmkit 1.1.154 → 1.1.156

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 (164) 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 +21 -27
  6. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +341 -15
  7. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +0 -3
  8. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +1 -15
  9. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
  11. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -9
  12. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
  13. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +120 -22
  14. package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
  15. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
  16. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
  17. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
  18. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
  19. package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
  20. package/bundled/dev-pipeline/scripts/update-bug-status.py +1 -4
  21. package/bundled/dev-pipeline/scripts/update-checkpoint.py +61 -13
  22. package/bundled/dev-pipeline/scripts/update-feature-status.py +1 -4
  23. package/bundled/dev-pipeline/scripts/update-refactor-status.py +1 -4
  24. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
  25. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
  26. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
  27. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
  28. package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
  29. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
  32. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
  33. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  34. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
  35. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
  36. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
  37. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
  38. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
  39. package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
  40. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
  41. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
  42. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
  43. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
  44. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
  45. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
  46. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
  47. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
  48. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
  49. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  50. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
  51. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
  52. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
  53. package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
  54. package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
  55. package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
  56. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +469 -18
  57. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -175
  58. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
  59. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
  60. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +168 -18
  61. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +111 -0
  62. package/bundled/dev-pipeline/tests/test_reset_preserve.py +0 -2
  63. package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
  64. package/bundled/dev-pipeline/tests/test_unified_cli.py +167 -25
  65. package/bundled/skills/_metadata.json +9 -9
  66. package/bundled/skills/app-planner/SKILL.md +3 -3
  67. package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
  68. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
  69. package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
  70. package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
  71. package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
  72. package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
  73. package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
  74. package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
  75. package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
  76. package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
  77. package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
  78. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
  79. package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
  80. package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
  81. package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
  82. package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
  83. package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
  84. package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
  85. package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
  86. package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
  87. package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
  88. package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
  89. package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
  90. package/bundled/skills/prizmkit/SKILL.md +17 -18
  91. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
  92. package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
  93. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
  94. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
  95. package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
  96. package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
  97. package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
  98. package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
  99. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
  100. package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
  101. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
  102. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
  103. package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
  104. package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
  105. package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
  106. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
  107. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
  108. package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
  109. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
  110. package/bundled/skills/prizmkit-init/SKILL.md +4 -0
  111. package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
  112. package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
  113. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
  114. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
  115. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +75 -11
  116. package/bundled/skills/prizmkit-prizm-docs/assets/prizm-docs-format.md +191 -56
  117. package/bundled/skills/prizmkit-prizm-docs/references/op-init.md +18 -18
  118. package/bundled/skills/prizmkit-prizm-docs/references/op-rebuild.md +14 -8
  119. package/bundled/skills/prizmkit-prizm-docs/references/op-status.md +12 -5
  120. package/bundled/skills/prizmkit-prizm-docs/references/op-update.md +23 -12
  121. package/bundled/skills/prizmkit-prizm-docs/references/op-validate.md +13 -9
  122. package/bundled/skills/prizmkit-retrospective/SKILL.md +94 -97
  123. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +79 -37
  124. package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
  125. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +111 -54
  126. package/bundled/skills/prizmkit-test/SKILL.md +64 -51
  127. package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
  128. package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
  129. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
  130. package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
  131. package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
  132. package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
  133. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
  134. package/bundled/skills/recovery-workflow/SKILL.md +2 -1
  135. package/bundled/skills/recovery-workflow/references/detection.md +4 -3
  136. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +15 -5
  137. package/bundled/templates/hooks/validate-prizm-docs.py +124 -12
  138. package/package.json +1 -1
  139. package/src/scaffold.js +33 -22
  140. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
  141. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
  142. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
  143. package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
  144. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
  145. package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
  146. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
  147. package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
  148. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
  149. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
  150. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
  151. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
  152. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
  153. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
  154. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
  155. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
  156. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
  157. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
  158. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
  159. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
  160. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
  161. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
  162. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
  163. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
  164. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
@@ -442,7 +442,7 @@ def ensure_linked_worktree(runtime: WorktreeRuntimeContext) -> WorktreeOperation
442
442
 
443
443
 
444
444
  def merge_linked_worktree(runtime: WorktreeRuntimeContext, *, auto_push: bool = False) -> WorktreeOperationResult:
445
- """Rebase a clean linked-worktree branch and fast-forward merge it into base."""
445
+ """Fast-forward a committed linked-worktree branch, rebasing only when needed."""
446
446
  results: list[GitCommandResult] = []
447
447
  clean = run_git_command(
448
448
  runtime.worktree_path,
@@ -459,11 +459,12 @@ def merge_linked_worktree(runtime: WorktreeRuntimeContext, *, auto_push: bool =
459
459
  if clean.return_code != 0:
460
460
  return WorktreeOperationResult(False, "dirty_task_checkout", runtime, tuple(results))
461
461
 
462
- rebase = run_git_command(runtime.worktree_path, ("rebase", runtime.context.base_branch))
463
- results.append(rebase)
464
- if rebase.return_code != 0:
465
- results.append(run_git_command(runtime.worktree_path, ("rebase", "--abort")))
466
- return WorktreeOperationResult(False, "merge_conflict", runtime, tuple(results))
462
+ if not branch_fast_forward_possible(runtime.context.project_root, runtime.context):
463
+ rebase = run_git_command(runtime.worktree_path, ("rebase", runtime.context.base_branch))
464
+ results.append(rebase)
465
+ if rebase.return_code != 0:
466
+ results.append(run_git_command(runtime.worktree_path, ("rebase", "--abort")))
467
+ return WorktreeOperationResult(False, "merge_conflict", runtime, tuple(results))
467
468
 
468
469
  checkout = run_git_command(runtime.context.project_root, ("checkout", runtime.context.base_branch))
469
470
  results.append(checkout)
@@ -581,14 +582,47 @@ def branch_return_plan(context: BranchContext) -> GitCommandPlan:
581
582
  )
582
583
 
583
584
 
584
- def branch_merge_plan(context: BranchContext, *, auto_push: bool = False) -> GitCommandPlan:
585
- """Plan a merge that consumes only a clean, committed task branch."""
585
+ def branch_fast_forward_possible(project_root: Path, context: BranchContext) -> bool:
586
+ """Return whether the base branch is an ancestor of the committed task branch."""
587
+ return run_git_command(
588
+ project_root,
589
+ ("merge-base", "--is-ancestor", context.base_branch, context.working_branch),
590
+ ).return_code == 0
591
+
592
+
593
+ def branch_merge_plan(
594
+ context: BranchContext,
595
+ *,
596
+ auto_push: bool = False,
597
+ rebase: bool = True,
598
+ ) -> GitCommandPlan:
599
+ """Plan a merge that consumes only task-visible committed branch state."""
586
600
  commands = [
587
- _git("status", "--porcelain", "--untracked-files=normal", description="require a clean task checkout"),
588
- _git("rebase", context.base_branch, context.working_branch, description="rebase dev onto original"),
589
- _git("checkout", context.base_branch, description="checkout original for fast-forward"),
590
- _git("merge", "--ff-only", context.working_branch, description="fast-forward original"),
601
+ _git(
602
+ "status",
603
+ "--porcelain",
604
+ "--untracked-files=normal",
605
+ "--",
606
+ ".",
607
+ *HIDDEN_TOOL_WORKTREE_EXCLUDES,
608
+ description="require a clean task checkout",
609
+ ),
591
610
  ]
611
+ if rebase:
612
+ commands.append(
613
+ _git(
614
+ "rebase",
615
+ context.base_branch,
616
+ context.working_branch,
617
+ description="rebase dev onto original",
618
+ )
619
+ )
620
+ commands.extend(
621
+ [
622
+ _git("checkout", context.base_branch, description="checkout original for fast-forward"),
623
+ _git("merge", "--ff-only", context.working_branch, description="fast-forward original"),
624
+ ]
625
+ )
592
626
  if auto_push:
593
627
  commands.append(_git("push", allow_failure=True, description="optional auto-push"))
594
628
  commands.append(_git("branch", "-d", context.working_branch, allow_failure=True, description="delete merged dev branch"))
@@ -596,8 +630,8 @@ def branch_merge_plan(context: BranchContext, *, auto_push: bool = False) -> Git
596
630
  name="branch_merge",
597
631
  commands=tuple(commands),
598
632
  notes=(
599
- "The task checkout must be clean because committed branch state is the only merge input.",
600
- "Rebase then fast-forward preserves merge history without pipeline-owned stashes.",
633
+ "Task-visible checkout state must be clean; recognized internal/support dirt remains outside merge input.",
634
+ "Rebase only when base ancestry requires it, then fast-forward without pipeline-owned stashes.",
601
635
  ),
602
636
  )
603
637
 
@@ -65,6 +65,17 @@ def classify_session(
65
65
  progress_fingerprint=fingerprint,
66
66
  checkpoint_state=checkpoint.semantic_snapshot,
67
67
  )
68
+ if (
69
+ checkpoint is not None
70
+ and checkpoint.semantic_complete
71
+ and not _runtime_commit_receipt_matches(project_root, checkpoint)
72
+ ):
73
+ return SessionClassification(
74
+ "commit_missing",
75
+ reason="runtime_commit_receipt_mismatch",
76
+ progress_fingerprint=fingerprint,
77
+ checkpoint_state=checkpoint.semantic_snapshot,
78
+ )
68
79
  if _is_terminal_success(result) and checkpoint is not None and checkpoint.semantic_complete:
69
80
  return SessionClassification(
70
81
  "success",
@@ -172,6 +183,25 @@ def _is_terminal_success(result: AISessionResult) -> bool:
172
183
  )
173
184
 
174
185
 
186
+ def _runtime_commit_receipt_matches(
187
+ project_root: Path,
188
+ checkpoint: CheckpointState,
189
+ ) -> bool:
190
+ """Verify canonical Runtime receipt identity against the current Git HEAD."""
191
+ commit_hash = checkpoint.semantic.runtime_commit_hash
192
+ base_head = checkpoint.semantic.runtime_base_head
193
+ if not commit_hash and not base_head:
194
+ return True
195
+ head = run_git_command(project_root, ("rev-parse", "HEAD"))
196
+ parent = run_git_command(project_root, ("rev-parse", f"{commit_hash}^"))
197
+ return bool(
198
+ head.return_code == 0
199
+ and parent.return_code == 0
200
+ and head.stdout.strip().lower() == commit_hash.lower()
201
+ and parent.stdout.strip().lower() == base_head.lower()
202
+ )
203
+
204
+
175
205
  def _has_late_runtime_error_after_completion(
176
206
  result: AISessionResult,
177
207
  checkpoint: CheckpointState | None,
@@ -201,9 +231,27 @@ def has_branch_completion_evidence(
201
231
  working_branch: str,
202
232
  artifact_path: Path | None,
203
233
  ) -> bool:
204
- """Return true only when a task branch is ahead and semantically complete."""
205
- if not base_branch or not working_branch or not has_completed_artifact_path(artifact_path, project_root):
234
+ """Require semantic completion and matching Runtime receipt on the task branch."""
235
+ if not base_branch or not working_branch or artifact_path is None:
236
+ return False
237
+ checkpoint_path = artifact_path / "workflow-checkpoint.json"
238
+ if not checkpoint_path.is_file():
239
+ return False
240
+ checkpoint = load_checkpoint_state(checkpoint_path, project_root=project_root)
241
+ if not checkpoint.semantic_complete:
206
242
  return False
243
+ commit_hash = checkpoint.semantic.runtime_commit_hash
244
+ base_head = checkpoint.semantic.runtime_base_head
245
+ if commit_hash or base_head:
246
+ branch_head = run_git_command(project_root, ("rev-parse", working_branch))
247
+ parent = run_git_command(project_root, ("rev-parse", f"{commit_hash}^"))
248
+ if not (
249
+ branch_head.return_code == 0
250
+ and parent.return_code == 0
251
+ and branch_head.stdout.strip().lower() == commit_hash.lower()
252
+ and parent.stdout.strip().lower() == base_head.lower()
253
+ ):
254
+ return False
207
255
  return _has_commit_range(project_root, base_branch, working_branch)
208
256
 
209
257
 
@@ -79,7 +79,6 @@ class RunnerInvocation:
79
79
  item_filter: str = ""
80
80
  max_retries: int | None = None
81
81
  max_infra_retries: int = 3
82
- mode: str | None = None
83
82
  dry_run: bool = False
84
83
  clean: bool = False
85
84
  no_reset: bool = False
@@ -95,7 +94,6 @@ class PromptGenerationResult:
95
94
 
96
95
  output_path: Path
97
96
  model: str = ""
98
- pipeline_mode: str = ""
99
97
  checkpoint_path: Path | None = None
100
98
  artifact_path: Path | None = None
101
99
  raw: Mapping[str, object] = field(default_factory=dict)
@@ -273,7 +271,6 @@ def parse_invocation(
273
271
  item_filter = ""
274
272
  max_retries: int | None = None
275
273
  max_infra_retries = 3
276
- mode: str | None = None
277
274
  dry_run = False
278
275
  clean = False
279
276
  no_reset = False
@@ -308,11 +305,6 @@ def parse_invocation(
308
305
  max_infra_retries = _safe_int(args[index], 3)
309
306
  elif arg.startswith("--max-infra-retries="):
310
307
  max_infra_retries = _safe_int(arg.split("=", 1)[1], 3)
311
- elif arg == "--mode" and index + 1 < len(args):
312
- index += 1
313
- mode = args[index]
314
- elif arg.startswith("--mode="):
315
- mode = arg.split("=", 1)[1]
316
308
  elif arg == "--resume-phase" and index + 1 < len(args):
317
309
  index += 1
318
310
  resume_phase = args[index]
@@ -339,7 +331,6 @@ def parse_invocation(
339
331
  item_filter=item_filter,
340
332
  max_retries=max_retries,
341
333
  max_infra_retries=max_infra_retries,
342
- mode=mode,
343
334
  dry_run=dry_run,
344
335
  clean=clean,
345
336
  no_reset=no_reset,
@@ -22,6 +22,7 @@ def generate_prompt(
22
22
  project_root: Path,
23
23
  execution_root: Path | None = None,
24
24
  continuation: dict[str, object] | None = None,
25
+ preview: bool = False,
25
26
  ) -> PromptGenerationResult:
26
27
  """Run the existing prompt generator and return its structured JSON result."""
27
28
  visible_root = execution_root or project_root
@@ -47,8 +48,8 @@ def generate_prompt(
47
48
  "--output",
48
49
  str(session_paths.prompt_path),
49
50
  ]
50
- if invocation.mode:
51
- command.extend(["--mode", invocation.mode])
51
+ if preview:
52
+ command.append("--no-checkpoint-write")
52
53
  if continuation:
53
54
  command.extend(["--continuation-mode", "ai_error"])
54
55
  for flag, key in (
@@ -95,7 +96,6 @@ def generate_prompt(
95
96
  return PromptGenerationResult(
96
97
  output_path=output_path,
97
98
  model=str(data.get("model") or ""),
98
- pipeline_mode=str(data.get("pipeline_mode") or ""),
99
99
  checkpoint_path=Path(str(checkpoint)) if checkpoint else None,
100
100
  artifact_path=_artifact_path(family, visible_root, item_id, data),
101
101
  raw=data,
@@ -6,6 +6,7 @@ import json
6
6
  import os
7
7
  import signal
8
8
  import sys
9
+ import tempfile
9
10
  import threading
10
11
  import time
11
12
  from collections.abc import Iterator, Mapping
@@ -19,6 +20,7 @@ from .gitops import (
19
20
  WorktreePolicy,
20
21
  branch_create_plan,
21
22
  branch_ensure_return,
23
+ branch_fast_forward_possible,
22
24
  branch_merge_plan,
23
25
  branch_save_wip,
24
26
  current_branch,
@@ -41,8 +43,17 @@ from .runner_bookkeeping import (
41
43
  commit_wip_changes,
42
44
  )
43
45
  from .runner_classification import classify_session, has_branch_completion_evidence, has_completed_artifact_path
44
- from .runner_models import RunnerEnvironment, RunnerFamily, RunnerInvocation, SessionPaths, family_for, parse_invocation
46
+ from .runner_models import (
47
+ RunnerEnvironment,
48
+ RunnerFamily,
49
+ RunnerInvocation,
50
+ SessionClassification,
51
+ SessionPaths,
52
+ family_for,
53
+ parse_invocation,
54
+ )
45
55
  from .runner_prompts import PromptGenerationResult, generate_prompt
56
+ from .runtime_commit import REQUEST_FILENAME, RuntimeCommitResult, finalize_runtime_commit
46
57
  from .runner_status import get_next, start_item, status_text, update_item
47
58
  from .sessions import AISessionConfig, AISessionLauncher, detect_stream_json_support
48
59
  from .task_checkout import (
@@ -105,7 +116,7 @@ def run_pipeline_command(kind: str, action: str, legacy_args: tuple[str, ...], p
105
116
  return CommandResult(1, f"Python {kind} runner failed", str(exc))
106
117
  details = result.details + ((f"last_status: {result.last_status}",) if result.last_status else ())
107
118
  return CommandResult(
108
- 0 if result.completed else 1,
119
+ 0 if result.completed or result.last_status == "dry_run" else 1,
109
120
  f"Python {kind} runner",
110
121
  f"processed={result.processed} completed={result.completed} stopped_reason={result.stopped_reason or 'none'}",
111
122
  details=details,
@@ -123,7 +134,12 @@ def _run_pipeline(family: RunnerFamily, invocation: RunnerInvocation, paths) ->
123
134
  except KeyboardInterrupt:
124
135
  with shield_interruption_signals():
125
136
  return PipelineRunResult(False, 1, "interrupted", "interrupted")
126
- return PipelineRunResult(status == "completed", 1, "single_item_done", status)
137
+ return PipelineRunResult(
138
+ status == "completed",
139
+ 1,
140
+ "dry_run" if status == "dry_run" else "single_item_done",
141
+ status,
142
+ )
127
143
 
128
144
  while True:
129
145
  next_result = get_next(family, invocation, paths.project_root)
@@ -178,6 +194,14 @@ def _run_pipeline(family: RunnerFamily, invocation: RunnerInvocation, paths) ->
178
194
  )
179
195
  processed += 1
180
196
  item_statuses[item_id] = final_status
197
+ if final_status == "dry_run":
198
+ return PipelineRunResult(
199
+ False,
200
+ processed,
201
+ "dry_run",
202
+ final_status,
203
+ _render_item_statuses(item_statuses),
204
+ )
181
205
  if final_status == "skipped":
182
206
  return PipelineRunResult(
183
207
  False,
@@ -412,21 +436,10 @@ def _child_log_activity_summary(progress: Mapping[str, object]) -> str:
412
436
  return _bytes_text(child_bytes)
413
437
 
414
438
 
415
- def _emit_prompt_summary(prompt: PromptGenerationResult, session_id: str) -> None:
416
- if prompt.pipeline_mode:
417
- _emit_info(f"Pipeline mode: {_pipeline_mode_label(prompt.pipeline_mode)}")
439
+ def _emit_prompt_summary(_prompt: PromptGenerationResult, session_id: str) -> None:
418
440
  _emit_info(f"Spawning AI CLI session: {session_id}")
419
441
 
420
442
 
421
- def _pipeline_mode_label(mode: str) -> str:
422
- labels = {
423
- "lite": "lite (Tier 1 — Scoped Guidance)",
424
- "standard": "standard (Tier 2 — Review Gates)",
425
- "full": "full (Full Guidance)",
426
- }
427
- return labels.get(mode, mode)
428
-
429
-
430
443
  def _emit_session_summary(session_result, session_paths: SessionPaths, classification) -> None:
431
444
  lines, size = _session_log_summary(session_paths.session_log)
432
445
  _emit_info(f"Session log: {lines} lines, {size}")
@@ -471,6 +484,8 @@ def _process_item(
471
484
  *,
472
485
  initial_metadata: dict[str, object],
473
486
  ) -> str:
487
+ if invocation.dry_run:
488
+ return _preview_item(family, invocation, item_id, paths, initial_metadata)
474
489
  try:
475
490
  with task_checkout_guard(family.state_dir, item_id):
476
491
  return _process_item_locked(family, invocation, item_id, paths, initial_metadata=initial_metadata)
@@ -478,6 +493,44 @@ def _process_item(
478
493
  raise
479
494
 
480
495
 
496
+ def _preview_item(
497
+ family: RunnerFamily,
498
+ invocation: RunnerInvocation,
499
+ item_id: str,
500
+ paths,
501
+ initial_metadata: dict[str, object],
502
+ ) -> str:
503
+ """Render one disposable Prompt preview without mutating task or Git state."""
504
+ config = load_runtime_config(paths)
505
+ if not config.ai_client.command:
506
+ raise RuntimeError("No supported AI CLI command was found")
507
+ metadata = _with_recovery_metadata(family, item_id, initial_metadata)
508
+ retry_count = int(metadata.get("retry_count") or 0)
509
+ session_id = f"{item_id}-dry-run"
510
+ _emit_item_header(family, invocation, item_id, metadata)
511
+ with tempfile.TemporaryDirectory(prefix="prizmkit-dry-run-") as temporary:
512
+ session_paths = SessionPaths.for_item(Path(temporary), item_id, session_id)
513
+ prompt = generate_prompt(
514
+ family,
515
+ invocation,
516
+ item_id=item_id,
517
+ session_id=session_id,
518
+ run_id="dry-run",
519
+ retry_count=retry_count,
520
+ session_paths=session_paths,
521
+ project_root=paths.project_root,
522
+ execution_root=paths.project_root,
523
+ preview=True,
524
+ )
525
+ prompt_size = prompt.output_path.stat().st_size
526
+ model = prompt.model or config.model or "default"
527
+ _emit_info(
528
+ f"Dry-run Prompt validated for {item_id}: model={model}, bytes={prompt_size}; "
529
+ "no checkout, checkpoint, task status, or AI session was created"
530
+ )
531
+ return "dry_run"
532
+
533
+
481
534
  def _process_item_locked(
482
535
  family: RunnerFamily,
483
536
  invocation: RunnerInvocation,
@@ -666,8 +719,6 @@ def _process_item_locked(
666
719
  execution_root=execution_root,
667
720
  continuation=continuation,
668
721
  )
669
- if invocation.dry_run:
670
- return "dry_run"
671
722
  _emit_prompt_summary(prompt, session_id)
672
723
  stream_json = False
673
724
  if getattr(config.ai_client, "launch_profile", None) is None:
@@ -696,6 +747,10 @@ def _process_item_locked(
696
747
  suppress_stream_output=True,
697
748
  )
698
749
  ).run()
750
+ runtime_commit = _finalize_prepared_runtime_commit(
751
+ execution_root,
752
+ prompt,
753
+ )
699
754
  classification = classify_session(
700
755
  session_result,
701
756
  project_root=execution_root,
@@ -704,6 +759,13 @@ def _process_item_locked(
704
759
  previous_fingerprint=previous_fingerprint,
705
760
  previous_no_progress_count=previous_no_progress_count,
706
761
  )
762
+ if runtime_commit.status == "failed":
763
+ classification = SessionClassification(
764
+ "commit_missing",
765
+ reason=runtime_commit.reason,
766
+ progress_fingerprint=classification.progress_fingerprint,
767
+ checkpoint_state=classification.checkpoint_state,
768
+ )
707
769
  _emit_session_summary(session_result, session_paths, classification)
708
770
  final_session_status = classification.session_status
709
771
  summary_path = _continuation_summary_path(execution_root, family, item_id, prompt)
@@ -927,6 +989,34 @@ def _emit_interruption_cleanup_failure(result, message: str, recovery: str) -> N
927
989
  _emit_warning(recovery)
928
990
 
929
991
 
992
+ def _finalize_prepared_runtime_commit(
993
+ execution_root: Path,
994
+ prompt: PromptGenerationResult,
995
+ ) -> RuntimeCommitResult:
996
+ """Execute a prepared commit only when the checkpoint proves readiness."""
997
+ checkpoint = prompt.checkpoint_path
998
+ artifact = prompt.artifact_path
999
+ if checkpoint is None or artifact is None or not checkpoint.is_file():
1000
+ return RuntimeCommitResult("not_ready", "commit_handoff_unavailable")
1001
+ from .checkpoint_state import load_checkpoint_state
1002
+
1003
+ state = load_checkpoint_state(checkpoint, project_root=execution_root)
1004
+ request_path = artifact / REQUEST_FILENAME
1005
+ cleanup_recovery = state.semantic_complete and request_path.is_file()
1006
+ if not state.semantic_commit_ready and not cleanup_recovery:
1007
+ return RuntimeCommitResult("not_ready", "checkpoint_not_commit_ready")
1008
+ result = finalize_runtime_commit(
1009
+ execution_root,
1010
+ artifact_dir=artifact,
1011
+ checkpoint_path=checkpoint,
1012
+ )
1013
+ if result.committed:
1014
+ _emit_success(f"Runtime committed prepared task change: {result.commit_hash}")
1015
+ else:
1016
+ _emit_warning(f"Runtime commit preparation failed: {result.reason}")
1017
+ return result
1018
+
1019
+
930
1020
  def _is_retryable_ai_outcome(session_status: str, new_status: str) -> bool:
931
1021
  """Return whether a persisted post-launch outcome should continue in-process."""
932
1022
  if session_status in {
@@ -1058,7 +1148,17 @@ def _complete_success_merge(
1058
1148
  return new_status
1059
1149
  else:
1060
1150
  _emit_info(f"Merging {branch_name} into {base_branch}...")
1061
- merge = run_git_plan(paths.project_root, branch_merge_plan(branch_context, auto_push=env.auto_push))
1151
+ merge = run_git_plan(
1152
+ paths.project_root,
1153
+ branch_merge_plan(
1154
+ branch_context,
1155
+ auto_push=env.auto_push,
1156
+ rebase=not branch_fast_forward_possible(
1157
+ paths.project_root,
1158
+ branch_context,
1159
+ ),
1160
+ ),
1161
+ )
1062
1162
  _emit_git_plan_output(merge)
1063
1163
  if merge.ok:
1064
1164
  checkout = load_task_checkout(family.state_dir, family.task_type, item_id)
@@ -1185,7 +1285,6 @@ def _invocation_for_execution_family(invocation: RunnerInvocation, family: Runne
1185
1285
  item_filter=invocation.item_filter,
1186
1286
  max_retries=invocation.max_retries,
1187
1287
  max_infra_retries=invocation.max_infra_retries,
1188
- mode=invocation.mode,
1189
1288
  dry_run=invocation.dry_run,
1190
1289
  clean=invocation.clean,
1191
1290
  no_reset=invocation.no_reset,
@@ -1308,7 +1407,6 @@ def _resolve_invocation_paths(invocation: RunnerInvocation, project_root: Path)
1308
1407
  item_filter=invocation.item_filter,
1309
1408
  max_retries=invocation.max_retries,
1310
1409
  max_infra_retries=invocation.max_infra_retries,
1311
- mode=invocation.mode,
1312
1410
  dry_run=invocation.dry_run,
1313
1411
  clean=invocation.clean,
1314
1412
  no_reset=invocation.no_reset,
@@ -1322,6 +1420,6 @@ def _resolve_invocation_paths(invocation: RunnerInvocation, project_root: Path)
1322
1420
  def _help_text(family: RunnerFamily) -> str:
1323
1421
  return (
1324
1422
  f"Python foreground runner for {family.kind}.\n"
1325
- "Supported legacy options include plan list path, item id, --features for feature, "
1326
- "--max-retries, --max-infra-retries, --mode, --resume-phase, and --dry-run."
1423
+ "Supported options include plan list path, item id, --features for feature, "
1424
+ "--max-retries, --max-infra-retries, --resume-phase, and --dry-run."
1327
1425
  )