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
@@ -76,6 +76,102 @@ def _write_family_plan(paths, kind, items):
76
76
  return plan_path
77
77
 
78
78
 
79
+ @pytest.mark.parametrize(
80
+ ("kind", "item_id", "filename", "list_key", "schema", "item"),
81
+ [
82
+ (
83
+ "feature", "F-001", "feature-list.json", "features",
84
+ "dev-pipeline-feature-list-v1",
85
+ {
86
+ "id": "F-001", "title": "Preview Feature", "description": "Preview only",
87
+ "priority": "high", "dependencies": [],
88
+ "acceptance_criteria": ["Prompt validates"], "status": "pending",
89
+ },
90
+ ),
91
+ (
92
+ "bugfix", "B-001", "bug-fix-list.json", "bugs",
93
+ "dev-pipeline-bug-fix-list-v1",
94
+ {
95
+ "id": "B-001", "title": "Preview Bug", "description": "Preview only",
96
+ "severity": "high", "priority": "high", "dependencies": [],
97
+ "error_source": {"type": "user_report", "reproduction_steps": ["preview"]},
98
+ "verification_type": "automated",
99
+ "acceptance_criteria": ["Prompt validates"], "status": "pending",
100
+ },
101
+ ),
102
+ (
103
+ "refactor", "R-001", "refactor-list.json", "refactors",
104
+ "dev-pipeline-refactor-list-v1",
105
+ {
106
+ "id": "R-001", "title": "Preview Refactor", "description": "Preview only",
107
+ "scope": {"files": ["src/example.js"], "modules": ["example"]},
108
+ "type": "simplify", "priority": "high", "complexity": "low",
109
+ "behavior_preservation": {"strategy": "test-gate", "existing_tests": True},
110
+ "acceptance_criteria": ["Prompt validates"],
111
+ "dependencies": [], "status": "pending",
112
+ },
113
+ ),
114
+ ],
115
+ )
116
+ def test_actual_dry_run_is_read_only_and_successful(
117
+ tmp_path, kind, item_id, filename, list_key, schema, item,
118
+ ):
119
+ from prizmkit_runtime.paths import resolve_runtime_paths
120
+ from prizmkit_runtime.runners import run_pipeline_command
121
+
122
+ project = tmp_path / kind
123
+ plans = project / ".prizmkit" / "plans"
124
+ plans.mkdir(parents=True)
125
+ plan_path = plans / filename
126
+ plan_path.write_text(
127
+ json.dumps({"$schema": schema, "project_name": "Dry Run", list_key: [item]}),
128
+ encoding="utf-8",
129
+ )
130
+ fake_cli = project / "fake-ai.py"
131
+ fake_cli.write_text(
132
+ "#!/usr/bin/env python3\nfrom pathlib import Path\nPath('AI_WAS_LAUNCHED').write_text('bad')\n",
133
+ encoding="utf-8",
134
+ )
135
+ fake_cli.chmod(0o755)
136
+ (project / ".prizmkit" / "config.json").write_text(
137
+ json.dumps({
138
+ "platform": "pi",
139
+ "ai_cli": str(fake_cli),
140
+ "ai_cli_launch": {"profile": "custom", "prompt_arg": "--prompt"},
141
+ }),
142
+ encoding="utf-8",
143
+ )
144
+ subprocess.run(["git", "init", "-b", "main"], cwd=project, check=True, stdout=subprocess.DEVNULL)
145
+ subprocess.run(["git", "config", "user.name", "Dry Run Test"], cwd=project, check=True)
146
+ subprocess.run(["git", "config", "user.email", "dry-run@example.invalid"], cwd=project, check=True)
147
+ subprocess.run(["git", "add", "."], cwd=project, check=True)
148
+ subprocess.run(["git", "commit", "-m", "initial"], cwd=project, check=True, stdout=subprocess.DEVNULL)
149
+ paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
150
+ branch_before = subprocess.check_output(["git", "branch", "--show-current"], cwd=project, text=True).strip()
151
+ head_before = subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project, text=True).strip()
152
+ status_before = subprocess.check_output(["git", "status", "--porcelain=v1", "-uall"], cwd=project, text=True)
153
+
154
+ run_args = (
155
+ (str(plan_path), "--dry-run")
156
+ if kind == "feature"
157
+ else (item_id, str(plan_path), "--dry-run")
158
+ )
159
+ result = run_pipeline_command(kind, "run", run_args, paths)
160
+
161
+ assert result.exit_code == 0
162
+ assert "processed=1 completed=False stopped_reason=dry_run" in result.render()
163
+ assert "last_status: dry_run" in result.render()
164
+ assert json.loads(plan_path.read_text(encoding="utf-8"))[list_key][0]["status"] == "pending"
165
+ assert subprocess.check_output(["git", "branch", "--show-current"], cwd=project, text=True).strip() == branch_before
166
+ assert subprocess.check_output(["git", "rev-parse", "HEAD"], cwd=project, text=True).strip() == head_before
167
+ assert subprocess.check_output(["git", "status", "--porcelain=v1", "-uall"], cwd=project, text=True) == status_before
168
+ assert not (project / "AI_WAS_LAUNCHED").exists()
169
+ assert not (project / ".prizmkit" / "state").exists()
170
+ assert not (project / ".prizmkit" / "specs").exists()
171
+ assert not (project / ".prizmkit" / "bugfix").exists()
172
+ assert not (project / ".prizmkit" / "refactor").exists()
173
+
174
+
79
175
  def _get_next_result(kind, paths):
80
176
  from prizmkit_runtime.runner_models import family_for, parse_invocation
81
177
  from prizmkit_runtime.runner_status import get_next
@@ -97,22 +193,22 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
97
193
  "feature_state",
98
194
  [
99
195
  "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
100
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
196
+ "completion-summary", "prizmkit-retrospective", "prizmkit-committer",
101
197
  ],
102
198
  ),
103
199
  "bugfix-pipeline": (
104
200
  "bugfix_state",
105
201
  [
106
202
  "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
107
- "prizmkit-retrospective", "prizmkit-committer", "bug-report",
203
+ "bug-report", "prizmkit-retrospective", "prizmkit-committer",
108
204
  ],
109
205
  ),
110
206
  "refactor-pipeline": (
111
207
  "refactor_state",
112
208
  [
113
209
  "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
114
- "prizmkit-retrospective", "prizmkit-committer", "refactor-report",
115
- "completion-summary",
210
+ "refactor-report", "completion-summary", "prizmkit-retrospective",
211
+ "prizmkit-committer",
116
212
  ],
117
213
  ),
118
214
  }
@@ -128,6 +224,8 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
128
224
  "required_artifacts": [],
129
225
  "depends_on": previous,
130
226
  }
227
+ if skill == "prizmkit-implement":
228
+ step["stage_result"] = "IMPLEMENTED"
131
229
  if skill == "prizmkit-code-review":
132
230
  step["stage_result"] = "REVIEW_PASS"
133
231
  if skill == "prizmkit-test":
@@ -137,6 +235,10 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
137
235
  str(relative_artifact / "test-report.md"),
138
236
  str(relative_artifact / "test-result.json"),
139
237
  ]
238
+ if skill == "prizmkit-retrospective":
239
+ step["stage_result"] = "RETRO_COMPLETE"
240
+ if skill == "prizmkit-committer":
241
+ step["stage_result"] = "COMMITTED"
140
242
  steps.append(step)
141
243
  previous = step["id"]
142
244
  artifact_dir.mkdir(parents=True, exist_ok=True)
@@ -158,12 +260,31 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
158
260
  },
159
261
  "repair_rounds": 0,
160
262
  "production_changed": False,
161
- "review_required": False,
162
- "review_scope": None,
163
263
  "unresolved_items": [],
164
264
  }),
165
265
  encoding="utf-8",
166
266
  )
267
+ runtime_commit_hash = "b" * 40
268
+ runtime_base_head = "a" * 40
269
+ head = subprocess.run(
270
+ ["git", "rev-parse", "HEAD"],
271
+ cwd=project_root,
272
+ stdout=subprocess.PIPE,
273
+ stderr=subprocess.DEVNULL,
274
+ text=True,
275
+ check=False,
276
+ )
277
+ parent = subprocess.run(
278
+ ["git", "rev-parse", "HEAD^"],
279
+ cwd=project_root,
280
+ stdout=subprocess.PIPE,
281
+ stderr=subprocess.DEVNULL,
282
+ text=True,
283
+ check=False,
284
+ )
285
+ if head.returncode == 0 and parent.returncode == 0:
286
+ runtime_commit_hash = head.stdout.strip()
287
+ runtime_base_head = parent.stdout.strip()
167
288
  checkpoint = artifact_dir / "workflow-checkpoint.json"
168
289
  checkpoint.write_text(
169
290
  json.dumps({
@@ -173,6 +294,8 @@ def _write_semantic_completion_checkpoint(project_root, artifact_dir, workflow_t
173
294
  state_key: {
174
295
  "repair_round": 0,
175
296
  "terminal_status": "WORKFLOW_COMPLETED",
297
+ "runtime_base_head": runtime_base_head,
298
+ "runtime_commit_hash": runtime_commit_hash,
176
299
  },
177
300
  }),
178
301
  encoding="utf-8",
@@ -215,9 +338,8 @@ def test_python_parity_suite_declares_coverage_for_all_legacy_unix_snapshot_scen
215
338
  "entrypointContracts": {
216
339
  "runnerHelpContracts": ["test_cli_help_exposes_python_runtime_command_groups"],
217
340
  "runnerMissingListErrors": ["test_python_runner_missing_list_errors_match_snapshot_contract"],
218
- "recoveryHelpContract": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
219
- "resetHelpContracts": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
220
- "daemonStatusContracts": ["test_python_entrypoint_contracts_match_snapshot_contracts"],
341
+ "resetHelpContracts": ["test_reset_and_daemon_entrypoint_contracts_match_snapshot_contracts"],
342
+ "daemonStatusContracts": ["test_reset_and_daemon_entrypoint_contracts_match_snapshot_contracts"],
221
343
  },
222
344
  "envResolution": {
223
345
  "verboseAndStreamJson": ["test_python_env_resolution_scenarios_match_snapshot_contracts"],
@@ -283,8 +405,7 @@ def test_python_runner_missing_list_errors_match_snapshot_contract(tmp_path):
283
405
  assert "Plan list not found" in result.render()
284
406
 
285
407
 
286
- def test_python_entrypoint_contracts_match_snapshot_contracts(tmp_path):
287
- from prizmkit_runtime.commands import handle_runtime_command
408
+ def test_reset_and_daemon_entrypoint_contracts_match_snapshot_contracts(tmp_path):
288
409
  from prizmkit_runtime.daemon import run_daemon_command
289
410
  from prizmkit_runtime.paths import resolve_runtime_paths
290
411
  from prizmkit_runtime.reset import run_reset_command
@@ -292,11 +413,6 @@ def test_python_entrypoint_contracts_match_snapshot_contracts(tmp_path):
292
413
  snapshot = _load_unix_runtime_snapshot()["entrypointContracts"]
293
414
  paths = _make_paths(tmp_path / "entrypoints")
294
415
 
295
- recovery = handle_runtime_command("recovery", "describe", paths=paths)
296
- assert recovery.exit_code == snapshot["recoveryHelpContract"]["exitCode"]
297
- assert "Python runtime command contract" in recovery.render()
298
- assert "recovery describe" in recovery.render()
299
-
300
416
  for kind in ("feature", "bugfix", "refactor"):
301
417
  reset = run_reset_command(kind, ("--help",), paths)
302
418
  assert reset.exit_code == snapshot["resetHelpContracts"][kind]["exitCode"]
@@ -410,7 +526,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
410
526
  invocation = parse_invocation(
411
527
  family,
412
528
  "run",
413
- ("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5", "--mode", "full"),
529
+ ("custom-list.json", "F-2", "--features", "F-003:F-001", "--max-retries", "7", "--max-infra-retries=5"),
414
530
  )
415
531
  env = RunnerEnvironment.from_env(
416
532
  {
@@ -429,7 +545,7 @@ def test_runner_models_parse_legacy_args_and_environment(tmp_path):
429
545
  assert invocation.item_filter == "F-003:F-001"
430
546
  assert invocation.max_retries == 7
431
547
  assert invocation.max_infra_retries == 5
432
- assert invocation.mode == "full"
548
+ assert not hasattr(invocation, "mode")
433
549
  assert env.stop_on_failure is True
434
550
  assert env.auto_push is True
435
551
  assert env.dev_branch == "dev/custom"
@@ -722,7 +838,7 @@ def test_status_bridge_passes_max_retries_from_invocation_or_environment(monkeyp
722
838
  ("kind", "item_key"),
723
839
  (("feature", "feature_id"), ("bugfix", "bug_id"), ("refactor", "refactor_id")),
724
840
  )
725
- def test_pipeline_aggregate_replaces_same_item_status_in_first_seen_order(monkeypatch, tmp_path, kind, item_key):
841
+ def test_pipeline_aggregate_reselection_keeps_first_seen_details_and_stops(monkeypatch, tmp_path, kind, item_key):
726
842
  from prizmkit_runtime import runners
727
843
  from prizmkit_runtime.runner_models import family_for, parse_invocation
728
844
 
@@ -743,11 +859,11 @@ def test_pipeline_aggregate_replaces_same_item_status_in_first_seen_order(monkey
743
859
 
744
860
  result = runners._run_pipeline(family, invocation, paths)
745
861
 
746
- assert result.processed == 3
747
- assert result.completed is True
748
- assert result.stopped_reason == "pipeline_complete"
749
- assert result.last_status == "completed"
750
- assert result.details == ("F-049:completed", "F-050:completed")
862
+ assert result.processed == 2
863
+ assert result.completed is False
864
+ assert result.stopped_reason == "reselected_item"
865
+ assert result.last_status == "pending"
866
+ assert result.details == ("F-049:completed", "F-050:pending")
751
867
 
752
868
 
753
869
  @pytest.mark.parametrize(
@@ -785,7 +901,46 @@ def test_pipeline_aggregate_keeps_distinct_unresolved_item_blocking(monkeypatch,
785
901
  ("kind", "item_key"),
786
902
  (("feature", "feature_id"), ("bugfix", "bug_id"), ("refactor", "refactor_id")),
787
903
  )
788
- def test_pipeline_aggregate_repeated_terminal_update_remains_unique(monkeypatch, tmp_path, kind, item_key):
904
+ def test_full_list_stops_after_newly_skipped_item(monkeypatch, tmp_path, kind, item_key):
905
+ from prizmkit_runtime import runners
906
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
907
+
908
+ paths = _make_paths(tmp_path)
909
+ family = family_for(kind, paths)
910
+ invocation = parse_invocation(family, "run", ())
911
+ selector_calls = 0
912
+ processor_calls = []
913
+
914
+ def fake_get_next(*args, **kwargs):
915
+ nonlocal selector_calls
916
+ selector_calls += 1
917
+ if selector_calls > 1:
918
+ raise AssertionError("selector called after terminal skipped item")
919
+ return SimpleNamespace(ok=True, stdout="", data={item_key: "X-001"}, text="")
920
+
921
+ def fake_process(_family, _invocation, item_id, _paths, *, initial_metadata):
922
+ processor_calls.append(item_id)
923
+ return "skipped"
924
+
925
+ monkeypatch.setattr(runners, "get_next", fake_get_next)
926
+ monkeypatch.setattr(runners, "_process_item", fake_process)
927
+
928
+ result = runners._run_pipeline(family, invocation, paths)
929
+
930
+ assert selector_calls == 1
931
+ assert processor_calls == ["X-001"]
932
+ assert result.completed is False
933
+ assert result.processed == 1
934
+ assert result.stopped_reason == "item_skipped"
935
+ assert result.last_status == "skipped"
936
+ assert result.details == ("X-001:skipped",)
937
+
938
+
939
+ @pytest.mark.parametrize(
940
+ ("kind", "item_key"),
941
+ (("feature", "feature_id"), ("bugfix", "bug_id"), ("refactor", "refactor_id")),
942
+ )
943
+ def test_full_list_rejects_reselected_processed_item_before_second_launch(monkeypatch, tmp_path, kind, item_key):
789
944
  from prizmkit_runtime import runners
790
945
  from prizmkit_runtime.runner_models import family_for, parse_invocation
791
946
 
@@ -793,20 +948,29 @@ def test_pipeline_aggregate_repeated_terminal_update_remains_unique(monkeypatch,
793
948
  family = family_for(kind, paths)
794
949
  invocation = parse_invocation(family, "run", ())
795
950
  queue = [
796
- SimpleNamespace(ok=True, stdout="", data={item_key: "F-001"}, text=""),
797
- SimpleNamespace(ok=True, stdout="", data={item_key: "F-001"}, text=""),
951
+ SimpleNamespace(ok=True, stdout="", data={item_key: "X-001"}, text=""),
952
+ SimpleNamespace(ok=True, stdout="", data={item_key: "X-001"}, text=""),
798
953
  SimpleNamespace(ok=True, stdout="PIPELINE_COMPLETE\n", data=None, text="PIPELINE_COMPLETE"),
799
954
  ]
955
+ processor_calls = []
800
956
 
801
957
  monkeypatch.setattr(runners, "get_next", lambda *args, **kwargs: queue.pop(0))
802
- monkeypatch.setattr(runners, "_process_item", lambda *args, **kwargs: "completed")
958
+
959
+ def fake_process(_family, _invocation, item_id, _paths, *, initial_metadata):
960
+ processor_calls.append(item_id)
961
+ return "completed"
962
+
963
+ monkeypatch.setattr(runners, "_process_item", fake_process)
803
964
  monkeypatch.setattr(runners, "_emit_info", lambda *args, **kwargs: None)
804
965
 
805
966
  result = runners._run_pipeline(family, invocation, paths)
806
967
 
807
- assert result.processed == 2
808
- assert result.details == ("F-001:completed",)
968
+ assert processor_calls == ["X-001"]
969
+ assert result.completed is False
970
+ assert result.processed == 1
971
+ assert result.stopped_reason == "reselected_item"
809
972
  assert result.last_status == "completed"
973
+ assert result.details == ("X-001:completed",)
810
974
 
811
975
 
812
976
  @pytest.mark.parametrize("kind", ("feature", "bugfix", "refactor"))
@@ -841,15 +1005,15 @@ def test_pipeline_aggregate_empty_authoritative_completion(monkeypatch, tmp_path
841
1005
  ("kind", "status", "expected_completed"),
842
1006
  (
843
1007
  ("feature", "completed", True),
844
- ("feature", "skipped", True),
1008
+ ("feature", "skipped", False),
845
1009
  ("feature", "auto_skipped", True),
846
1010
  ("feature", "split", True),
847
1011
  ("bugfix", "completed", True),
848
- ("bugfix", "skipped", True),
1012
+ ("bugfix", "skipped", False),
849
1013
  ("bugfix", "auto_skipped", True),
850
1014
  ("bugfix", "split", False),
851
1015
  ("refactor", "completed", True),
852
- ("refactor", "skipped", True),
1016
+ ("refactor", "skipped", False),
853
1017
  ("refactor", "auto_skipped", True),
854
1018
  ("refactor", "split", False),
855
1019
  ),
@@ -875,7 +1039,9 @@ def test_pipeline_aggregate_completion_compatible_statuses(monkeypatch, tmp_path
875
1039
 
876
1040
  assert result.completed is expected_completed
877
1041
  assert result.stopped_reason == (
878
- "pipeline_complete" if expected_completed else "pipeline_complete_with_non_terminal_history"
1042
+ "item_skipped"
1043
+ if status == "skipped"
1044
+ else ("pipeline_complete" if expected_completed else "pipeline_complete_with_non_terminal_history")
879
1045
  )
880
1046
  assert result.last_status == status
881
1047
  assert result.details == (f"X-001:{status}",)
@@ -906,6 +1072,37 @@ def test_run_pipeline_command_omits_empty_last_status_detail(monkeypatch, tmp_pa
906
1072
  assert result.details == ()
907
1073
 
908
1074
 
1075
+ @pytest.mark.parametrize("kind", ("feature", "bugfix", "refactor"))
1076
+ def test_run_pipeline_command_returns_nonzero_for_terminal_skipped_item(monkeypatch, tmp_path, kind):
1077
+ from prizmkit_runtime import runners
1078
+ from prizmkit_runtime.runner_models import PipelineRunResult
1079
+
1080
+ paths = _make_paths(tmp_path)
1081
+ plan_path = {
1082
+ "feature": paths.feature_plan,
1083
+ "bugfix": paths.bugfix_plan,
1084
+ "refactor": paths.refactor_plan,
1085
+ }[kind]
1086
+ plan_path.write_text("{}", encoding="utf-8")
1087
+ monkeypatch.setattr(
1088
+ runners,
1089
+ "_run_pipeline",
1090
+ lambda *args, **kwargs: PipelineRunResult(
1091
+ False,
1092
+ 1,
1093
+ "item_skipped",
1094
+ "skipped",
1095
+ ("X-001:skipped",),
1096
+ ),
1097
+ )
1098
+
1099
+ result = runners.run_pipeline_command(kind, "run", (), paths)
1100
+
1101
+ assert result.exit_code == 1
1102
+ assert result.message == "processed=1 completed=False stopped_reason=item_skipped"
1103
+ assert result.details == ("X-001:skipped", "last_status: skipped")
1104
+
1105
+
909
1106
  def test_pipeline_complete_rejects_retryable_detail(monkeypatch, tmp_path):
910
1107
  from prizmkit_runtime import runners
911
1108
  from prizmkit_runtime.runner_models import family_for, parse_invocation
@@ -1008,7 +1205,6 @@ def test_prompt_generation_result_preserves_per_item_model(monkeypatch, tmp_path
1008
1205
  "output_path": str(session_paths.prompt_path),
1009
1206
  "checkpoint_path": str(paths.project_root / ".prizmkit" / "bugfix" / "B-001" / "workflow-checkpoint.json"),
1010
1207
  "model": "per-item-model",
1011
- "pipeline_mode": "standard",
1012
1208
  }
1013
1209
  ),
1014
1210
  stderr="",
@@ -1056,7 +1252,6 @@ def test_prompt_generation_passes_explicit_execution_root_to_generator(monkeypat
1056
1252
  "output_path": str(session_paths.prompt_path),
1057
1253
  "checkpoint_path": str(execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json"),
1058
1254
  "model": "",
1059
- "pipeline_mode": "full",
1060
1255
  }
1061
1256
  ),
1062
1257
  stderr="",
@@ -1121,7 +1316,6 @@ def _install_runner_session_fakes(monkeypatch, runners, *, statuses=("success",)
1121
1316
  return PromptGenerationResult(
1122
1317
  output_path=session_paths.prompt_path,
1123
1318
  model="per-item-model",
1124
- pipeline_mode="full",
1125
1319
  checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-low" / "workflow-checkpoint.json",
1126
1320
  artifact_path=execution_root / ".prizmkit" / "specs" / "001-low",
1127
1321
  raw={},
@@ -1454,7 +1648,6 @@ def test_explicit_worktree_materializes_ignored_support_assets_before_prompt_and
1454
1648
  return PromptGenerationResult(
1455
1649
  output_path=session_paths.prompt_path,
1456
1650
  model="",
1457
- pipeline_mode="full",
1458
1651
  checkpoint_path=execution_root / ".prizmkit" / "specs" / "001-support" / "workflow-checkpoint.json",
1459
1652
  artifact_path=execution_root / ".prizmkit" / "specs" / "001-support",
1460
1653
  raw={},
@@ -1541,7 +1734,6 @@ def test_has_branch_completion_evidence_requires_task_branch_ahead_of_base(tmp_p
1541
1734
  subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
1542
1735
  subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
1543
1736
  artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-done"
1544
- _write_semantic_completion_checkpoint(tmp_path, artifact_dir)
1545
1737
  (tmp_path / "base.txt").write_text("base", encoding="utf-8")
1546
1738
  subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
1547
1739
  subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
@@ -1549,6 +1741,7 @@ def test_has_branch_completion_evidence_requires_task_branch_ahead_of_base(tmp_p
1549
1741
  (tmp_path / "done.txt").write_text("done", encoding="utf-8")
1550
1742
  subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
1551
1743
  subprocess.run(["git", "commit", "-m", "done"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
1744
+ _write_semantic_completion_checkpoint(tmp_path, artifact_dir)
1552
1745
 
1553
1746
  assert has_branch_completion_evidence(tmp_path, "main", "dev/F-001", artifact_dir) is True
1554
1747
 
@@ -1898,6 +2091,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
1898
2091
  from prizmkit_runtime.runner_models import family_for, parse_invocation
1899
2092
 
1900
2093
  monkeypatch.setenv("DEV_BRANCH", "dev/F-001-output")
2094
+ monkeypatch.delenv("MAX_RETRIES", raising=False)
1901
2095
  paths = _make_paths(tmp_path)
1902
2096
  family = family_for("feature", paths)
1903
2097
  invocation = parse_invocation(family, "run", ())
@@ -1919,7 +2113,7 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
1919
2113
  assert "Feature: F-001 — Readable output" in captured.err
1920
2114
  assert "Code retry: 1 / 3" in captured.err
1921
2115
  assert "Preflight: no user-visible changes to preserve before feature task F-001" in captured.err
1922
- assert "Pipeline mode: full (Full Guidance)" in captured.err
2116
+ assert "Pipeline mode:" not in captured.err
1923
2117
  assert "Spawning AI CLI session: F-001-session-1" in captured.err
1924
2118
  assert "Session result: success" in captured.err
1925
2119
 
@@ -1976,6 +2170,8 @@ def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_pa
1976
2170
  from prizmkit_runtime.task_checkout import load_task_checkout
1977
2171
 
1978
2172
  monkeypatch.setenv("DEV_BRANCH", "dev/F-001-interrupt")
2173
+ monkeypatch.delenv("MAX_RETRIES", raising=False)
2174
+ monkeypatch.delenv("MAX_INFRA_RETRIES", raising=False)
1979
2175
  paths = _make_paths(tmp_path)
1980
2176
  _init_bookkeeping_repo(paths.project_root)
1981
2177
  family = family_for("feature", paths)
@@ -3089,6 +3285,14 @@ def test_classification_accepts_final_success_after_internal_stream_retry(tmp_pa
3089
3285
  stdout=subprocess.PIPE,
3090
3286
  text=True,
3091
3287
  ).stdout.strip()
3288
+ (tmp_path / "done.txt").write_text("done", encoding="utf-8")
3289
+ subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
3290
+ subprocess.run(
3291
+ ["git", "commit", "-m", "done"],
3292
+ cwd=tmp_path,
3293
+ check=True,
3294
+ stdout=subprocess.DEVNULL,
3295
+ )
3092
3296
  artifact_dir = tmp_path / ".prizmkit" / "bugfix" / "B-004"
3093
3297
  checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact_dir, "bugfix-pipeline")
3094
3298
 
@@ -3236,7 +3440,7 @@ def test_classification_keeps_pre_completion_provider_error_as_infra_error(tmp_p
3236
3440
  assert classification.reason == "ai_runtime_or_provider_error"
3237
3441
 
3238
3442
 
3239
- def test_classification_accepts_terminal_success_with_completed_checkpoint_without_git_delta(tmp_path):
3443
+ def test_classification_rejects_completed_checkpoint_without_runtime_git_receipt(tmp_path):
3240
3444
  from prizmkit_runtime.runner_classification import classify_session
3241
3445
  from prizmkit_runtime.runner_models import PromptGenerationResult
3242
3446
  from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
@@ -3276,8 +3480,8 @@ def test_classification_accepts_terminal_success_with_completed_checkpoint_witho
3276
3480
  )
3277
3481
 
3278
3482
  classification = classify_session(raw, project_root=tmp_path, base_head=base_head, prompt=prompt)
3279
- assert classification.session_status == "success"
3280
- assert classification.reason == "terminal_success_with_semantic_checkpoint"
3483
+ assert classification.session_status == "commit_missing"
3484
+ assert classification.reason == "runtime_commit_receipt_mismatch"
3281
3485
 
3282
3486
 
3283
3487
  def test_classification_preserves_success_before_context_overflow(tmp_path):
@@ -3390,37 +3594,11 @@ def test_classification_stalls_after_repeated_no_progress_context_overflow(tmp_p
3390
3594
  assert classification.no_progress_count == 2
3391
3595
 
3392
3596
 
3393
- def test_recovery_prompt_keeps_existing_branch_instead_of_recovery_suffix():
3394
- text = (PIPELINE_ROOT / "scripts" / "generate-recovery-prompt.py").read_text(encoding="utf-8")
3395
-
3396
- assert "fix/{bug_id}-recovery" not in text
3397
- assert "do not create an additional" in text
3398
- assert "last-resort fallback" in text
3399
-
3400
-
3401
- def test_recovery_detector_resolver_prefers_installed_platform_asset(tmp_path):
3402
- from prizmkit_runtime.paths import resolve_runtime_paths
3403
- from prizmkit_runtime.runner_recovery import RecoveryDetectorResolver
3404
-
3405
- project = tmp_path / "project"
3406
- agents = project / ".agents" / "skills" / "recovery-workflow" / "scripts"
3407
- claude = project / ".claude" / "command-assets" / "recovery-workflow" / "scripts"
3408
- agents.mkdir(parents=True)
3409
- claude.mkdir(parents=True)
3410
- agent_detector = agents / "detect-recovery-state.py"
3411
- claude_detector = claude / "detect-recovery-state.py"
3412
- agent_detector.write_text("# agent", encoding="utf-8")
3413
- claude_detector.write_text("# claude", encoding="utf-8")
3414
- paths = resolve_runtime_paths(pipeline_root=PIPELINE_ROOT, project_root=project)
3415
-
3416
- assert RecoveryDetectorResolver(paths).resolve() == agent_detector
3417
-
3418
-
3419
3597
  def test_cli_family_status_uses_python_dispatch_not_transition_description(tmp_path):
3420
3598
  paths = _make_paths(tmp_path)
3421
3599
  _write_feature_list(paths)
3422
3600
  result = subprocess.run(
3423
- [PYTHON_310_PLUS, str(CLI_PATH), "--project-root", str(paths.project_root), "feature", "status"],
3601
+ [PYTHON_310_PLUS, str(CLI_PATH), "--project-root", str(paths.project_root), "status", "feature"],
3424
3602
  cwd=REPO_ROOT,
3425
3603
  stdout=subprocess.PIPE,
3426
3604
  stderr=subprocess.PIPE,
@@ -3480,7 +3658,7 @@ def test_python_reset_warns_before_mutation_and_matches_snapshot_cleanup(tmp_pat
3480
3658
  for kind, item_id in [("feature", "F-001"), ("bugfix", "B-001"), ("refactor", "R-001")]:
3481
3659
  paths = _make_paths(tmp_path / kind)
3482
3660
  _plan, _state_dir, status_file = _write_family_case(paths, kind, item_id)
3483
- result = run_reset_command(kind, (item_id,), paths)
3661
+ result = run_reset_command(kind, (item_id, "--fresh-checkout"), paths)
3484
3662
  output = result.render()
3485
3663
  reset_marker = f"Resetting {item_id} status"
3486
3664
 
@@ -3522,7 +3700,7 @@ def test_python_reset_cleans_authoritative_custom_branch_before_reset_marker(mon
3522
3700
  or SimpleNamespace(branch=branch, cleaned=True, deleted=False, returned_to=return_branch, warnings=()),
3523
3701
  )
3524
3702
 
3525
- result = run_reset_command("feature", ("F-001",), paths)
3703
+ result = run_reset_command("feature", ("F-001", "--fresh-checkout"), paths)
3526
3704
 
3527
3705
  assert result.exit_code == 0
3528
3706
  assert cleanup_calls == [(custom_branch, False)]
@@ -3554,7 +3732,7 @@ def test_python_reset_failure_keeps_authoritative_checkout_active(monkeypatch, t
3554
3732
  ),
3555
3733
  )
3556
3734
 
3557
- result = run_reset_command("feature", ("F-001",), paths)
3735
+ result = run_reset_command("feature", ("F-001", "--fresh-checkout"), paths)
3558
3736
 
3559
3737
  assert result.exit_code == 1
3560
3738
  assert load_task_checkout(family.state_dir, family.task_type, "F-001") == checkout
@@ -3674,7 +3852,7 @@ def test_python_reset_stalled_filter_selects_stalled_context_items(tmp_path):
3674
3852
  data[{"feature": "features", "bugfix": "bugs", "refactor": "refactors"}[kind]][0]["status"] = "stalled_context_continuation"
3675
3853
  plan.write_text(json.dumps(data), encoding="utf-8")
3676
3854
 
3677
- result = run_reset_command(kind, ("--stalled",), paths)
3855
+ result = run_reset_command(kind, ("--stalled", "--fresh-checkout"), paths)
3678
3856
 
3679
3857
  assert result.exit_code == 0
3680
3858
  assert f"Reset: {item_id}" in result.render()
@@ -3707,6 +3885,7 @@ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path)
3707
3885
  {"id": "B-001", "title": "Done", "status": "completed"},
3708
3886
  {"id": "B-002", "title": "Failed", "status": "failed"},
3709
3887
  {"id": "B-003", "title": "Needs Info", "status": "needs_info"},
3888
+ {"id": "B-004", "title": "Blocked", "status": "auto_skipped"},
3710
3889
  ]}),
3711
3890
  encoding="utf-8",
3712
3891
  )
@@ -3724,12 +3903,13 @@ def test_python_daemon_progress_uses_list_status_without_runtime_files(tmp_path)
3724
3903
 
3725
3904
  summary = _progress_summary(family_for(kind, paths), plan)
3726
3905
 
3727
- assert summary["total"] == 3
3906
+ assert summary["total"] == (4 if kind == "bugfix" else 3)
3728
3907
  assert summary["completed"] == 1
3729
3908
  assert summary["failed"] == 1
3730
- assert summary["percent"] == 33.3
3909
+ assert summary["percent"] == (25.0 if kind == "bugfix" else 33.3)
3731
3910
  if kind == "bugfix":
3732
3911
  assert summary["needs_info"] == 1
3912
+ assert summary["auto_skipped"] == 1
3733
3913
  else:
3734
3914
  assert summary["auto_skipped"] == 1
3735
3915
 
@@ -3862,6 +4042,14 @@ def test_pi_length_stop_does_not_override_completed_checkpoint(tmp_path):
3862
4042
  from prizmkit_runtime.status import ProgressSummary
3863
4043
 
3864
4044
  base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
4045
+ (tmp_path / "done.txt").write_text("done", encoding="utf-8")
4046
+ subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
4047
+ subprocess.run(
4048
+ ["git", "commit", "-m", "done"],
4049
+ cwd=tmp_path,
4050
+ check=True,
4051
+ stdout=subprocess.DEVNULL,
4052
+ )
3865
4053
  artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-length-complete"
3866
4054
  checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact_dir)
3867
4055
  prompt = PromptGenerationResult(
@@ -4308,6 +4496,21 @@ def test_semantic_completion_classification_has_cross_family_parity(tmp_path, wo
4308
4496
  (tmp_path / "base.txt").write_text("base", encoding="utf-8")
4309
4497
  subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
4310
4498
  subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
4499
+ base_head = subprocess.run(
4500
+ ["git", "rev-parse", "HEAD"],
4501
+ cwd=tmp_path,
4502
+ check=True,
4503
+ stdout=subprocess.PIPE,
4504
+ text=True,
4505
+ ).stdout.strip()
4506
+ (tmp_path / "done.txt").write_text("done", encoding="utf-8")
4507
+ subprocess.run(["git", "add", "done.txt"], cwd=tmp_path, check=True)
4508
+ subprocess.run(
4509
+ ["git", "commit", "-m", "done"],
4510
+ cwd=tmp_path,
4511
+ check=True,
4512
+ stdout=subprocess.DEVNULL,
4513
+ )
4311
4514
  artifact = tmp_path / ".prizmkit" / "artifacts" / workflow_type
4312
4515
  checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact, workflow_type)
4313
4516
  log = tmp_path / f"{workflow_type}.log"
@@ -4330,7 +4533,7 @@ def test_semantic_completion_classification_has_cross_family_parity(tmp_path, wo
4330
4533
  classification = classify_session(
4331
4534
  raw,
4332
4535
  project_root=tmp_path,
4333
- base_head="",
4536
+ base_head=base_head,
4334
4537
  prompt=PromptGenerationResult(
4335
4538
  output_path=tmp_path / "prompt.md",
4336
4539
  checkpoint_path=checkpoint,
@@ -4396,38 +4599,10 @@ def test_semantic_blocked_classification_is_terminal_and_retry_neutral(tmp_path)
4396
4599
  assert classification.checkpoint_state["repair_round"] == 3
4397
4600
 
4398
4601
 
4399
- def test_recovery_outcome_preserves_dirty_partial_work_and_uses_semantic_checkpoint(tmp_path):
4400
- from prizmkit_runtime.runner_recovery import _recovery_outcome
4401
-
4402
- subprocess.run(["git", "init", "-b", "main"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
4403
- subprocess.run(["git", "config", "user.email", "test@example.com"], cwd=tmp_path, check=True)
4404
- subprocess.run(["git", "config", "user.name", "Test"], cwd=tmp_path, check=True)
4405
- (tmp_path / "base.txt").write_text("base", encoding="utf-8")
4406
- subprocess.run(["git", "add", "base.txt"], cwd=tmp_path, check=True)
4407
- subprocess.run(["git", "commit", "-m", "base"], cwd=tmp_path, check=True, stdout=subprocess.DEVNULL)
4408
- (tmp_path / "partial.txt").write_text("preserve me", encoding="utf-8")
4409
- artifact = tmp_path / ".prizmkit" / "specs" / "done"
4410
- checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact)
4411
-
4412
- outcome, semantic = _recovery_outcome(tmp_path, 1, checkpoint_path=checkpoint)
4413
-
4414
- assert outcome == "success"
4415
- assert semantic["terminal_status"] == "WORKFLOW_COMPLETED"
4416
- assert (tmp_path / "partial.txt").read_text(encoding="utf-8") == "preserve me"
4417
- assert subprocess.run(
4418
- ["git", "status", "--porcelain", "--", "partial.txt"],
4419
- cwd=tmp_path,
4420
- check=True,
4421
- stdout=subprocess.PIPE,
4422
- text=True,
4423
- ).stdout.startswith("??")
4424
-
4425
-
4426
4602
  def test_runtime_semantic_paths_do_not_invoke_composite_workflow():
4427
4603
  sources = [
4428
4604
  PIPELINE_ROOT / "prizmkit_runtime" / "checkpoint_state.py",
4429
4605
  PIPELINE_ROOT / "prizmkit_runtime" / "runner_classification.py",
4430
- PIPELINE_ROOT / "prizmkit_runtime" / "runner_recovery.py",
4431
4606
  PIPELINE_ROOT / "prizmkit_runtime" / "runners.py",
4432
4607
  ]
4433
4608
 
@@ -4494,110 +4669,10 @@ def test_family_status_persists_identical_semantic_blocked_snapshot_without_retr
4494
4669
  assert status[key] == value
4495
4670
 
4496
4671
 
4497
-
4498
- def test_recovery_detector_returns_no_candidate_for_empty_project(tmp_path):
4499
- import importlib.util
4500
-
4501
- detector_path = (
4502
- REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
4503
- / "recovery-workflow" / "scripts" / "detect-recovery-state.py"
4504
- )
4505
- spec = importlib.util.spec_from_file_location("recovery_detector_empty", detector_path)
4506
- module = importlib.util.module_from_spec(spec)
4507
- spec.loader.exec_module(module)
4508
-
4509
- report = module.build_report(tmp_path)
4510
-
4511
- assert report["detected"] is False
4512
- assert report["candidates"] == []
4513
-
4514
-
4515
- def test_recovery_detector_returns_active_checkout_command(tmp_path, monkeypatch):
4516
- import importlib.util
4517
-
4518
- detector_path = (
4519
- REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
4520
- / "recovery-workflow" / "scripts" / "detect-recovery-state.py"
4521
- )
4522
- spec = importlib.util.spec_from_file_location("recovery_detector_active", detector_path)
4523
- module = importlib.util.module_from_spec(spec)
4524
- spec.loader.exec_module(module)
4525
-
4526
- checkout_path = tmp_path / ".prizmkit" / "state" / "features" / "F-001" / "checkout.json"
4527
- checkout_path.parent.mkdir(parents=True)
4528
- checkout_path.write_text(
4529
- json.dumps({
4530
- "task_type": "feature",
4531
- "task_id": "F-001",
4532
- "state": "active",
4533
- "active_dev_branch": "dev/F-001-resume",
4534
- "base_branch": "main",
4535
- "checkout_mode": "branch",
4536
- }),
4537
- encoding="utf-8",
4538
- )
4539
- monkeypatch.setattr(module, "branch_exists", lambda *_args: True)
4540
-
4541
- report = module.build_report(tmp_path)
4542
-
4543
- assert report["detected"] is True
4544
- assert report["selection_required"] is False
4545
- assert report["candidates"][0]["continue_command"] == (
4546
- "python3 ./.prizmkit/dev-pipeline/cli.py feature run F-001"
4547
- )
4548
-
4549
-
4550
- def test_recovery_detector_ignores_completed_checkout(tmp_path, monkeypatch):
4551
- import importlib.util
4552
-
4553
- detector_path = (
4554
- REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
4555
- / "recovery-workflow" / "scripts" / "detect-recovery-state.py"
4556
- )
4557
- spec = importlib.util.spec_from_file_location("recovery_detector_completed", detector_path)
4558
- module = importlib.util.module_from_spec(spec)
4559
- spec.loader.exec_module(module)
4560
-
4561
- checkout_path = tmp_path / ".prizmkit" / "state" / "bugfix" / "B-001" / "checkout.json"
4562
- checkout_path.parent.mkdir(parents=True)
4563
- checkout_path.write_text(
4564
- json.dumps({
4565
- "task_type": "bugfix",
4566
- "task_id": "B-001",
4567
- "state": "completed",
4568
- "active_dev_branch": "bugfix/B-001-done",
4569
- "base_branch": "main",
4570
- }),
4571
- encoding="utf-8",
4572
- )
4573
- monkeypatch.setattr(module, "branch_exists", lambda *_args: True)
4574
-
4575
- report = module.build_report(tmp_path)
4576
-
4577
- assert report["detected"] is False
4578
-
4579
-
4580
- def test_l4_runtime_and_recovery_sources_do_not_depend_on_direct_l1_composite():
4672
+ def test_runtime_sources_do_not_depend_on_direct_lifecycle_composite():
4581
4673
  runtime_root = REPO_ROOT / "dev-pipeline"
4582
4674
  source_files = list(runtime_root.rglob("*.py"))
4583
4675
  for source in source_files:
4584
4676
  if "__pycache__" in source.parts or "tests" in source.parts or not source.is_file():
4585
4677
  continue
4586
4678
  assert "prizmkit-workflow" not in source.read_text(encoding="utf-8")
4587
-
4588
-
4589
- def test_recovery_detector_has_no_direct_lifecycle_fallback(tmp_path):
4590
- import importlib.util
4591
-
4592
- detector_path = (
4593
- REPO_ROOT / "core" / "skills" / "orchestration-skill" / "workflows"
4594
- / "recovery-workflow" / "scripts" / "detect-recovery-state.py"
4595
- )
4596
- spec = importlib.util.spec_from_file_location("recovery_detector_contract", detector_path)
4597
- module = importlib.util.module_from_spec(spec)
4598
- spec.loader.exec_module(module)
4599
-
4600
- report = module.build_report(tmp_path)
4601
-
4602
- assert "continue_command" not in report
4603
- assert report["detected"] is False