prizmkit 1.1.141 → 1.1.143

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 (48) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +1 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +124 -4
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +13 -1
  5. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +4 -1
  6. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +20 -10
  7. package/bundled/dev-pipeline/scripts/check-session-status.py +11 -1
  8. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +2 -2
  9. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +2 -2
  10. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +2 -2
  11. package/bundled/dev-pipeline/scripts/prompt_framework.py +12 -1
  12. package/bundled/dev-pipeline/scripts/update-bug-status.py +20 -0
  13. package/bundled/dev-pipeline/scripts/update-checkpoint.py +38 -9
  14. package/bundled/dev-pipeline/scripts/update-feature-status.py +20 -0
  15. package/bundled/dev-pipeline/scripts/update-refactor-status.py +20 -0
  16. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +1 -1
  17. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +1 -1
  18. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +5 -1
  19. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +2 -2
  20. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
  21. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  22. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +231 -0
  23. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +26 -1
  24. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +4 -2
  25. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +5 -2
  26. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +147 -4
  27. package/bundled/skills/_metadata.json +1 -1
  28. package/bundled/skills/prizmkit/SKILL.md +6 -6
  29. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +23 -11
  30. package/bundled/skills/prizmkit-code-review/SKILL.md +4 -4
  31. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +23 -11
  32. package/bundled/skills/prizmkit-committer/SKILL.md +5 -4
  33. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +23 -11
  34. package/bundled/skills/prizmkit-implement/SKILL.md +3 -2
  35. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +23 -11
  36. package/bundled/skills/prizmkit-plan/SKILL.md +2 -1
  37. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +23 -11
  38. package/bundled/skills/prizmkit-retrospective/SKILL.md +6 -4
  39. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +23 -11
  40. package/bundled/skills/prizmkit-test/SKILL.md +1 -1
  41. package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
  42. package/bundled/skills/prizmkit-workflow/SKILL.md +13 -13
  43. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +23 -11
  44. package/bundled/templates/hooks/commit-intent-status.py +50 -0
  45. package/bundled/templates/hooks/commit-intent.json +2 -2
  46. package/bundled/templates/hooks/post-command-prizm-drift.py +44 -0
  47. package/package.json +1 -1
  48. package/src/scaffold.js +2 -0
@@ -237,7 +237,7 @@ For each Verification Gate from the Task Contract, write one evidence line to Im
237
237
 
238
238
  Review the completed implementation before the full Feature test gate. Run `/prizmkit-code-review artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` and follow the skill's current review contract for review execution, repairs, verification, evidence, and handoff. Do not invoke another review skill or add a second review path outside that contract.
239
239
 
240
- - `REVIEW_PASS` → record `stage=code-review`, `status=REVIEW_PASS`, `stage_result=PASS`, `next_stage=prizmkit-test`, and `resume_from=prizmkit-test`; then run the test gate.
240
+ - `REVIEW_PASS` → record `stage=code-review`, `status=completed`, `stage_result=REVIEW_PASS`, `next_stage=prizmkit-test`, and `resume_from=prizmkit-test`; then run the test gate.
241
241
  - `REVIEW_NEEDS_FIXES` → increment the shared outer `repair_round` and route `prizmkit-implement` → `prizmkit-code-review` → `prizmkit-test`.
242
242
  - Stop as `WORKFLOW_BLOCKED` when the outer repair round is already three. The Main-Agent review loop has its separate limit of up to ten completed rounds.
243
243
 
@@ -16,4 +16,4 @@ Missing required evidence, an unsafe repair, a failed verification, or failure t
16
16
  - `REVIEW_NEEDS_FIXES` → route through bounded implementation/review/test repair; do not commit.
17
17
  - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` → inspect progress once, write `failure-log.md`, and stop. Do not accept old or incidental verdict text and do not synthesize a passing result outside the skill.
18
18
 
19
- **Checkpoint update**: Set step `prizmkit-code-review` to `completed` with `stage_result=REVIEW_PASS` only for an accepted valid Final Result.
19
+ **Checkpoint update**: Set step `prizmkit-code-review` to `completed` with `stage_result=REVIEW_PASS` only for an accepted valid Final Result. Set it to `failed` with `stage_result=REVIEW_NEEDS_FIXES` when the review returns known unresolved findings.
@@ -19,8 +19,12 @@ Use this helper to update checkpoint status. Do not hand-edit `workflow-checkpoi
19
19
 
20
20
  - Use the step `skill` value such as `prizmkit-implement`, or the step id such as `S04`.
21
21
  - Omit `--stage-result` for stages without a domain result.
22
+ - Complete `prizmkit-plan` with `--status completed --stage-result PLAN_READY`.
23
+ - Complete `prizmkit-implement` with `--status completed --stage-result IMPLEMENTED`.
22
24
  - Complete `prizmkit-code-review` only with `--status completed --stage-result REVIEW_PASS`.
23
25
  - Complete `prizmkit-test` only after validating the sibling report/result pair, with `--status completed --stage-result TEST_PASS`.
26
+ - Complete `prizmkit-retrospective` with `--status completed --stage-result RETRO_COMPLETE`.
27
+ - Record commit authorization wait with `--status in_progress --stage-result COMMIT_PENDING`; after the local commit, use `--status completed --stage-result COMMITTED`.
24
28
  - For `TEST_NEEDS_FIXES` or `TEST_BLOCKED`, use `--status failed --stage-result <result>` and preserve its report/result and blocker; never mark the test step completed.
25
29
  - Omit `--note` when there is no useful failure or handoff note.
26
30
 
@@ -28,7 +32,7 @@ Use this helper to update checkpoint status. Do not hand-edit `workflow-checkpoi
28
32
 
29
33
  1. Before each skill, verify its predecessor is `completed` or `skipped`.
30
34
  2. Starting a skill marks its step `in_progress`; a valid success marks it `completed`; a non-pass records its truthful `stage_result` and leaves later stages incomplete.
31
- 3. Pipeline metadata owns only `current_stage`, `next_stage`, `stage_result`, `repair_scope`, `repair_round`, `blocked_reason`, and `terminal_status`.
35
+ 3. Pipeline metadata owns only `current_stage`, `next_stage`, `stage_result`, `repair_scope`, `repair_round`, `blocked_reason`, and `terminal_status`; lifecycle `status` belongs to each checkpoint step.
32
36
  4. Skills own their reports and terminal results. The pipeline must not duplicate a skill's internal state machine or require evidence manifests, hashes, or attestations.
33
37
  5. Preserve all reports and session artifacts on recovery. Never replace skill artifacts with the pipeline checkpoint or merge their schemas.
34
38
  6. `TEST_NEEDS_FIXES` is a known testing-domain non-pass. `TEST_BLOCKED` records terminal `WORKFLOW_BLOCKED` with a deterministic reason. Neither is an AI CLI crash.
@@ -4,8 +4,8 @@ Review the completed implementation before the full Feature test gate runs. A re
4
4
 
5
5
  Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so `/prizmkit-code-review` can read the spec, plan, context snapshot, implementation log, and current diff. Follow the skill's current review contract for review execution, repairs, verification, evidence, and handoff. Do not invoke another review skill or add a second review path outside that contract.
6
6
 
7
- - `REVIEW_PASS` → update checkpoint metadata with `stage=code-review`, `status=REVIEW_PASS`, `stage_result=REVIEW_PASS`, `next_stage=prizmkit-test`, and `resume_from=prizmkit-test`; then run the full test gate.
8
- - `REVIEW_NEEDS_FIXES` → preserve findings, increment the shared outer `repair_round`, route `prizmkit-implement` → `prizmkit-code-review` → `prizmkit-test`, and do not accept any test result from before the repaired review.
7
+ - `REVIEW_PASS` → update checkpoint metadata with `stage=code-review`, `status=completed`, `stage_result=REVIEW_PASS`, `next_stage=prizmkit-test`, and `resume_from=prizmkit-test`; then run the full test gate.
8
+ - `REVIEW_NEEDS_FIXES` → update the review step with `status=failed` and `stage_result=REVIEW_NEEDS_FIXES`, preserve findings, increment the shared outer `repair_round`, route `prizmkit-implement` → `prizmkit-code-review` → `prizmkit-test`, and do not accept any test result from before the repaired review.
9
9
  - If the outer repair round is already three, record `WORKFLOW_BLOCKED`; do not start another repair route.
10
10
 
11
11
  Missing required evidence, unsafe repair, failed verification, or failure to converge under the review contract produces `REVIEW_NEEDS_FIXES`.
@@ -16,4 +16,4 @@ Missing required evidence, an unsafe repair, a failed verification, or failure t
16
16
 
17
17
  Verify directly that the review checks behavior preservation, public API compatibility, imports, tests, and rollback assumptions.
18
18
 
19
- **Checkpoint update**: Set step `prizmkit-code-review` to `completed` with `stage_result=REVIEW_PASS` only for an accepted valid Final Result. Do not complete the checkpoint for incomplete or invalid reports.
19
+ **Checkpoint update**: Set step `prizmkit-code-review` to `completed` with `stage_result=REVIEW_PASS` only for an accepted valid Final Result. Set it to `failed` with `stage_result=REVIEW_NEEDS_FIXES` when the review returns known unresolved findings. Do not complete the checkpoint for incomplete or invalid reports.
@@ -15,7 +15,7 @@
15
15
  },
16
16
  "status": {
17
17
  "type": "string",
18
- "enum": ["success", "partial", "failed", "commit_missing", "docs_missing", "merge_conflict", "context_overflow"]
18
+ "enum": ["success", "partial", "failed", "crashed", "timed_out", "infra_error", "commit_missing", "docs_missing", "merge_conflict", "context_overflow", "stalled_context_continuation", "workflow_blocked", "workflow_skipped"]
19
19
  },
20
20
  "completed_phases": {
21
21
  "type": "array",
@@ -5,6 +5,9 @@ from __future__ import annotations
5
5
  import json
6
6
 
7
7
  from prizmkit_runtime.checkpoint_state import (
8
+ VALID_CHECKPOINT_STATUSES,
9
+ VALID_STAGE_RESULTS,
10
+ expected_status_for_stage_result,
8
11
  load_checkpoint_state,
9
12
  read_checkpoint_cursor,
10
13
  validate_checkpoint_data,
@@ -143,6 +146,169 @@ def test_structural_validation_error_codes(tmp_path):
143
146
  assert read_checkpoint_cursor(path) is None
144
147
 
145
148
 
149
+ def test_domain_results_map_to_lifecycle_statuses():
150
+ expected = {
151
+ "PLAN_READY": "completed",
152
+ "PLAN_BLOCKED": "failed",
153
+ "IMPLEMENTED": "completed",
154
+ "IMPLEMENT_REPAIR": "failed",
155
+ "IMPLEMENT_BLOCKED": "failed",
156
+ "REVIEW_PASS": "completed",
157
+ "REVIEW_NEEDS_FIXES": "failed",
158
+ "TEST_PASS": "completed",
159
+ "TEST_NEEDS_FIXES": "failed",
160
+ "TEST_BLOCKED": "failed",
161
+ "RETRO_COMPLETE": "completed",
162
+ "RETRO_BLOCKED": "failed",
163
+ "COMMIT_PENDING": "in_progress",
164
+ "COMMITTED": "completed",
165
+ "COMMIT_BLOCKED": "failed",
166
+ }
167
+
168
+ assert VALID_CHECKPOINT_STATUSES == {
169
+ "pending", "in_progress", "completed", "failed", "skipped",
170
+ }
171
+ for result, status in expected.items():
172
+ assert expected_status_for_stage_result(result) == status
173
+
174
+
175
+ def test_structural_validation_rejects_domain_result_as_status(tmp_path):
176
+ for domain_status in [
177
+ "PLAN_READY", "IMPLEMENTED", "REVIEW_PASS", "REVIEW_NEEDS_FIXES",
178
+ "TEST_PASS", "TEST_NEEDS_FIXES", "TEST_BLOCKED", "RETRO_COMPLETE",
179
+ "COMMIT_PENDING", "COMMITTED",
180
+ ]:
181
+ state = validate_checkpoint_data(
182
+ _checkpoint([_step("S01", status=domain_status)]),
183
+ path=tmp_path / f"{domain_status}.json",
184
+ )
185
+ assert state.valid is False
186
+ assert state.error_code == "invalid_status"
187
+
188
+
189
+ def test_structural_validation_rejects_status_result_mismatch(tmp_path):
190
+ step = _step("S01", "prizmkit-code-review", "Review", "failed")
191
+ step["stage_result"] = "REVIEW_PASS"
192
+
193
+ state = validate_checkpoint_data(
194
+ _checkpoint([step]),
195
+ path=tmp_path / "mismatch.json",
196
+ )
197
+
198
+ assert state.valid is False
199
+ assert state.error_code == "stage_result_status_mismatch"
200
+
201
+
202
+ def test_structural_validation_rejects_stage_result_owner_mismatch(tmp_path):
203
+ for skill in ("prizmkit-test", "custom-stage"):
204
+ step = _step("S01", skill, "Step", "completed")
205
+ step["stage_result"] = "REVIEW_PASS"
206
+
207
+ state = validate_checkpoint_data(
208
+ _checkpoint([step]),
209
+ path=tmp_path / f"{skill}-owner-mismatch.json",
210
+ )
211
+
212
+ assert state.valid is False
213
+ assert state.error_code == "stage_result_owner_mismatch"
214
+
215
+
216
+ def test_semantic_metadata_rejects_legacy_or_mismatched_status(tmp_path):
217
+ invalid = [
218
+ ({"status": "TEST_PASS", "stage_result": "TEST_PASS"}, "invalid_semantic_status"),
219
+ ({"status": "failed", "stage_result": "TEST_PASS"}, "semantic_stage_result_status_mismatch"),
220
+ ({"status": "completed", "stage_result": "UNKNOWN_RESULT"}, "invalid_semantic_stage_result"),
221
+ ({"stage": "test", "status": "completed", "stage_result": "REVIEW_PASS"}, "semantic_stage_result_owner_mismatch"),
222
+ ]
223
+
224
+ for index, (semantic, error_code) in enumerate(invalid):
225
+ state = validate_checkpoint_data(
226
+ _checkpoint([_step("S01")]) | {"feature_state": semantic},
227
+ path=tmp_path / f"semantic-status-{index}.json",
228
+ )
229
+
230
+ assert state.valid is True
231
+ assert state.semantic.error_code == error_code
232
+
233
+
234
+ def test_update_checkpoint_reenters_failed_stage_without_stale_result(tmp_path):
235
+ payload = _family_checkpoint(
236
+ tmp_path,
237
+ semantic={
238
+ "stage": "code-review",
239
+ "status": "failed",
240
+ "stage_result": "REVIEW_NEEDS_FIXES",
241
+ "repair_scope": "production",
242
+ "repair_round": 1,
243
+ },
244
+ terminal=False,
245
+ )
246
+ payload["steps"][1]["status"] = "failed"
247
+ payload["steps"][1]["stage_result"] = "REVIEW_NEEDS_FIXES"
248
+ for step in payload["steps"][2:]:
249
+ step["status"] = "pending"
250
+ step.pop("stage_result", None)
251
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
252
+
253
+ result = update_checkpoint(
254
+ str(checkpoint),
255
+ "prizmkit-code-review",
256
+ "in_progress",
257
+ )
258
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
259
+
260
+ assert result["ok"] is True
261
+ assert result["semantic"]["current_stage"] == "prizmkit-code-review"
262
+ assert result["semantic"]["next_stage"] == "prizmkit-code-review"
263
+ assert persisted["steps"][1]["status"] == "in_progress"
264
+ assert "stage_result" not in persisted["steps"][1]
265
+ assert persisted["feature_state"]["stage"] == "code-review"
266
+ assert persisted["feature_state"]["status"] == "in_progress"
267
+ assert "stage_result" not in persisted["feature_state"]
268
+
269
+
270
+ def test_checkpoint_updater_supports_every_owned_stage_result(tmp_path):
271
+ cases = [
272
+ ("prizmkit-plan", "PLAN_READY", "completed"),
273
+ ("prizmkit-implement", "IMPLEMENTED", "completed"),
274
+ ("prizmkit-code-review", "REVIEW_PASS", "completed"),
275
+ ("prizmkit-test", "TEST_BLOCKED", "failed"),
276
+ ("prizmkit-retrospective", "RETRO_COMPLETE", "completed"),
277
+ ("prizmkit-committer", "COMMIT_PENDING", "in_progress"),
278
+ ("prizmkit-committer", "COMMITTED", "completed"),
279
+ ]
280
+
281
+ assert {stage_result for _skill, stage_result, _status in cases} <= VALID_STAGE_RESULTS
282
+ for index, (skill, stage_result, status) in enumerate(cases):
283
+ step = _step("S01", skill, skill, "pending")
284
+ checkpoint = _write(
285
+ tmp_path / f"owned-result-{index}.json",
286
+ _checkpoint([step]),
287
+ )
288
+
289
+ result = update_checkpoint(
290
+ str(checkpoint),
291
+ skill,
292
+ status,
293
+ semantic_update={"stage_result": stage_result},
294
+ )
295
+
296
+ assert result["ok"] is True
297
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
298
+ assert persisted["steps"][0]["status"] == status
299
+ assert persisted["steps"][0]["stage_result"] == stage_result
300
+ assert persisted["feature_state"]["stage"] == {
301
+ "prizmkit-plan": "plan",
302
+ "prizmkit-implement": "implement",
303
+ "prizmkit-code-review": "code-review",
304
+ "prizmkit-test": "test",
305
+ "prizmkit-retrospective": "retrospective",
306
+ "prizmkit-committer": "committer",
307
+ }[skill]
308
+ assert persisted["feature_state"]["status"] == status
309
+ assert persisted["feature_state"]["stage_result"] == stage_result
310
+
311
+
146
312
  def test_duplicate_skills_require_exact_step_id(tmp_path):
147
313
  path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
148
314
  _step("S01", "prizmkit-committer", "First", "completed"),
@@ -305,6 +471,7 @@ def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
305
471
  payload["steps"][1]["status"] = "skipped"
306
472
  payload["steps"][1].pop("stage_result", None)
307
473
  payload["steps"][2]["status"] = "pending"
474
+ payload["steps"][2].pop("stage_result", None)
308
475
  for step in payload["steps"][3:]:
309
476
  step["status"] = "pending"
310
477
  step.pop("stage_result", None)
@@ -391,6 +558,42 @@ def test_completion_rejects_inconsistent_independent_review_metadata(tmp_path):
391
558
  assert state.semantic.artifacts_valid is False
392
559
 
393
560
 
561
+ def test_completion_accepts_legacy_terminal_result_report(tmp_path):
562
+ payload = _family_checkpoint(tmp_path)
563
+ checkpoint = _write(
564
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
565
+ payload,
566
+ )
567
+ (checkpoint.parent / "test-report.md").write_text(
568
+ "# Test Report\n\n## Terminal Result\n\n- Result: TEST_PASS\n\n"
569
+ "## Final Decision\n\nTEST_PASS\n",
570
+ encoding="utf-8",
571
+ )
572
+
573
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
574
+
575
+ assert state.semantic_complete is True
576
+ assert state.semantic.artifacts_valid is True
577
+
578
+
579
+ def test_completion_rejects_conflicting_supported_report_results(tmp_path):
580
+ payload = _family_checkpoint(tmp_path)
581
+ checkpoint = _write(
582
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
583
+ payload,
584
+ )
585
+ (checkpoint.parent / "test-report.md").write_text(
586
+ "# Test Report\n\n## Terminal Result\n\n- Result: TEST_PASS\n\n"
587
+ "## Final Decision\n\nTEST_BLOCKED\n",
588
+ encoding="utf-8",
589
+ )
590
+
591
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
592
+
593
+ assert state.semantic_complete is False
594
+ assert state.semantic.artifacts_valid is False
595
+
596
+
394
597
  def test_completion_rejects_mismatched_test_report_result(tmp_path):
395
598
  payload = _family_checkpoint(tmp_path)
396
599
  checkpoint = _write(
@@ -447,6 +650,34 @@ def test_completion_rejects_test_artifacts_outside_project(tmp_path):
447
650
  assert state.semantic.artifacts_valid is False
448
651
 
449
652
 
653
+ def test_review_needs_fixes_preserves_implementation_repair_route(tmp_path):
654
+ payload = _family_checkpoint(
655
+ tmp_path,
656
+ semantic={"repair_scope": "production", "repair_round": 0},
657
+ terminal=False,
658
+ )
659
+ payload["steps"][1]["status"] = "in_progress"
660
+ payload["steps"][1].pop("stage_result")
661
+ for step in payload["steps"][2:]:
662
+ step["status"] = "pending"
663
+ step.pop("stage_result", None)
664
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
665
+
666
+ result = update_checkpoint(
667
+ str(checkpoint),
668
+ "prizmkit-code-review",
669
+ "failed",
670
+ semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_scope": "production"},
671
+ )
672
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
673
+
674
+ assert result["ok"] is True
675
+ assert result["semantic"]["next_stage"] == "prizmkit-implement"
676
+ assert persisted["feature_state"]["repair_round"] == 1
677
+ assert persisted["feature_state"]["current_stage"] == "prizmkit-code-review"
678
+ assert persisted["feature_state"]["next_stage"] == "prizmkit-implement"
679
+
680
+
450
681
  def test_review_outer_repair_cap_remains_runtime_owned(tmp_path):
451
682
  payload = _family_checkpoint(
452
683
  tmp_path, semantic={"repair_scope": "production", "repair_round": 3},
@@ -31,6 +31,7 @@ from generate_bootstrap_prompt import (
31
31
  main as generate_bootstrap_main,
32
32
  )
33
33
  from continuation import append_continuation_handoff, checkpoint_cursor
34
+ from prompt_framework import pipeline_checkpoint_metadata
34
35
  from utils import resolve_prompt_platform
35
36
 
36
37
  REMOVED_MAX_LOG_ENV = "MAX_" + "LOG_SIZE"
@@ -472,8 +473,13 @@ class TestScopedFeatureTestGate:
472
473
  assert "{{CHECKPOINT_PYTHON_CMD}}" in rendered
473
474
  assert "--checkpoint-path {{CHECKPOINT_PATH}}" in rendered
474
475
  assert "--stage-result <optional-domain-result>" in rendered
476
+ assert "--status completed --stage-result PLAN_READY" in rendered
477
+ assert "--status completed --stage-result IMPLEMENTED" in rendered
475
478
  assert "--status completed --stage-result REVIEW_PASS" in rendered
476
479
  assert "--status completed --stage-result TEST_PASS" in rendered
480
+ assert "--status completed --stage-result RETRO_COMPLETE" in rendered
481
+ assert "--status in_progress --stage-result COMMIT_PENDING" in rendered
482
+ assert "--status completed --stage-result COMMITTED" in rendered
477
483
  assert "Do not hand-edit `workflow-checkpoint.json` directly." in rendered
478
484
  assert "authoritative_evidence_paths" not in rendered
479
485
 
@@ -562,14 +568,33 @@ class TestScopedFeatureTestGate:
562
568
  assert blocked["blocked_reason"] == "test_blocked"
563
569
  assert blocked["terminal_status"] == "WORKFLOW_BLOCKED"
564
570
 
571
+ def test_pipeline_checkpoint_metadata_rejects_legacy_or_mismatched_status(self):
572
+ invalid = [
573
+ {"status": "TEST_PASS", "stage_result": "TEST_PASS"},
574
+ {"status": "failed", "stage_result": "TEST_PASS"},
575
+ {"status": "completed", "stage_result": "UNKNOWN_RESULT"},
576
+ ]
577
+
578
+ for values in invalid:
579
+ try:
580
+ pipeline_checkpoint_metadata(
581
+ ".prizmkit/specs/contract-test",
582
+ stage="test",
583
+ **values,
584
+ )
585
+ except ValueError:
586
+ continue
587
+ raise AssertionError("invalid lifecycle metadata was accepted: {}".format(values))
588
+
565
589
  def test_feature_checkpoint_metadata_has_no_evidence_paths(self):
566
590
  metadata = feature_checkpoint_metadata(
567
591
  ".prizmkit/specs/123-scope-test",
568
- stage="test", status="TEST_NEEDS_FIXES",
592
+ stage="test", status="failed",
569
593
  stage_result="TEST_NEEDS_FIXES", repair_round=0,
570
594
  next_stage=None, resume_from=None,
571
595
  )
572
596
 
597
+ assert metadata["status"] == "failed"
573
598
  assert metadata["stage_result"] == "TEST_NEEDS_FIXES"
574
599
  assert "authoritative_evidence_paths" not in metadata
575
600
  assert metadata["artifact_dir"] == ".prizmkit/specs/123-scope-test"
@@ -472,7 +472,7 @@ class TestBugfixCheckpointGeneration:
472
472
  existing["bugfix_state"] = bugfix_checkpoint_metadata(
473
473
  ".prizmkit/bugfix/B-001",
474
474
  stage="test",
475
- status="TEST_BLOCKED",
475
+ status="failed",
476
476
  stage_result="TEST_BLOCKED",
477
477
  repair_round=2,
478
478
  next_stage="prizmkit-test",
@@ -481,6 +481,7 @@ class TestBugfixCheckpointGeneration:
481
481
 
482
482
  merged = merge_bugfix_checkpoint_state(existing, fresh, str(tmp_path))
483
483
 
484
+ assert merged["bugfix_state"]["status"] == "failed"
484
485
  assert merged["bugfix_state"]["stage_result"] == "TEST_BLOCKED"
485
486
  assert merged["bugfix_state"]["resume_from"] == "prizmkit-test"
486
487
 
@@ -527,7 +528,7 @@ class TestBugfixCheckpointGeneration:
527
528
  metadata = bugfix_checkpoint_metadata(
528
529
  ".prizmkit/bugfix/B-001",
529
530
  stage="test",
530
- status="TEST_NEEDS_FIXES",
531
+ status="failed",
531
532
  stage_result="TEST_NEEDS_FIXES",
532
533
  repair_round=0,
533
534
  next_stage=None,
@@ -535,6 +536,7 @@ class TestBugfixCheckpointGeneration:
535
536
  )
536
537
 
537
538
  assert metadata["artifact_dir"] == ".prizmkit/bugfix/B-001"
539
+ assert metadata["status"] == "failed"
538
540
  assert metadata["stage_result"] == "TEST_NEEDS_FIXES"
539
541
  assert "authoritative_evidence_paths" not in metadata
540
542
 
@@ -59,7 +59,7 @@ class TestRefactorCheckpointMerge:
59
59
  existing["refactor_state"] = refactor_checkpoint_metadata(
60
60
  ".prizmkit/refactor/R-008",
61
61
  stage="test",
62
- status="TEST_NEEDS_FIXES",
62
+ status="failed",
63
63
  stage_result="TEST_NEEDS_FIXES",
64
64
  repair_scope="public-interface",
65
65
  repair_round=2,
@@ -72,6 +72,7 @@ class TestRefactorCheckpointMerge:
72
72
 
73
73
  assert merged["steps"][0]["status"] == "skipped"
74
74
  assert merged["steps"][1]["status"] == "pending"
75
+ assert merged["refactor_state"]["status"] == "failed"
75
76
  assert merged["refactor_state"]["repair_scope"] == "public-interface"
76
77
  assert merged["refactor_state"]["repair_round"] == 2
77
78
 
@@ -376,13 +377,15 @@ class TestRefactorCheckpointGeneration:
376
377
  metadata = refactor_checkpoint_metadata(
377
378
  ".prizmkit/refactor/R-001",
378
379
  stage="test",
379
- status="TEST_PASS",
380
+ status="completed",
380
381
  stage_result="TEST_PASS",
381
382
  next_stage="prizmkit-retrospective",
382
383
  resume_from="prizmkit-retrospective",
383
384
  )
384
385
 
385
386
  assert metadata["artifact_dir"] == ".prizmkit/refactor/R-001"
387
+ assert metadata["status"] == "completed"
388
+ assert metadata["stage_result"] == "TEST_PASS"
386
389
  assert "authoritative_evidence_paths" not in metadata
387
390
  assert metadata["next_stage"] == "prizmkit-retrospective"
388
391
  assert metadata["resume_from"] == "prizmkit-retrospective"
@@ -3426,7 +3426,150 @@ def _terminal_success_result(tmp_path):
3426
3426
  )
3427
3427
 
3428
3428
 
3429
- def test_invalid_existing_checkpoint_blocks_terminal_success_with_completion_summary(tmp_path):
3429
+ def test_session_status_parser_preserves_current_runtime_outcomes():
3430
+ import importlib.util
3431
+
3432
+ script = PIPELINE_ROOT / "scripts" / "check-session-status.py"
3433
+ spec = importlib.util.spec_from_file_location("check_session_status_contract", script)
3434
+ module = importlib.util.module_from_spec(spec)
3435
+ spec.loader.exec_module(module)
3436
+
3437
+ for status in (
3438
+ "infra_error",
3439
+ "stalled_context_continuation",
3440
+ "workflow_blocked",
3441
+ "workflow_skipped",
3442
+ ):
3443
+ assert module.determine_status({"status": status}) == status
3444
+
3445
+
3446
+ def test_terminal_success_without_semantic_completion_is_skipped(tmp_path):
3447
+ from prizmkit_runtime.runner_classification import classify_session
3448
+ from prizmkit_runtime.runner_models import PromptGenerationResult
3449
+
3450
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
3451
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-incomplete"
3452
+ checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact_dir)
3453
+ payload = json.loads(checkpoint.read_text(encoding="utf-8"))
3454
+ payload["steps"][2]["required_artifacts"] = [
3455
+ ".prizmkit/specs/001-incomplete/test-report.md",
3456
+ ".prizmkit/specs/001-incomplete/missing-test-result.json",
3457
+ ]
3458
+ checkpoint.write_text(json.dumps(payload), encoding="utf-8")
3459
+ prompt = PromptGenerationResult(
3460
+ output_path=tmp_path / "prompt.md",
3461
+ checkpoint_path=checkpoint,
3462
+ artifact_path=artifact_dir,
3463
+ )
3464
+
3465
+ classification = classify_session(
3466
+ _terminal_success_result(tmp_path),
3467
+ project_root=tmp_path,
3468
+ base_head=base_head,
3469
+ prompt=prompt,
3470
+ )
3471
+
3472
+ assert classification.session_status == "workflow_skipped"
3473
+ assert classification.reason == "no_semantic_completion:incomplete_semantic_evidence"
3474
+ assert classification.fatal_error_code == ""
3475
+
3476
+
3477
+ def test_terminal_success_text_does_not_hide_provider_failure(tmp_path):
3478
+ from prizmkit_runtime.runner_classification import classify_session
3479
+ from prizmkit_runtime.runner_models import PromptGenerationResult
3480
+ from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
3481
+ from prizmkit_runtime.status import ProgressSummary
3482
+
3483
+ base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
3484
+ artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-provider-error"
3485
+ checkpoint = _write_semantic_completion_checkpoint(tmp_path, artifact_dir)
3486
+ payload = json.loads(checkpoint.read_text(encoding="utf-8"))
3487
+ payload["steps"][2]["required_artifacts"] = [
3488
+ ".prizmkit/specs/001-provider-error/test-report.md",
3489
+ ".prizmkit/specs/001-provider-error/missing-test-result.json",
3490
+ ]
3491
+ checkpoint.write_text(json.dumps(payload), encoding="utf-8")
3492
+ log = tmp_path / "provider-error.log"
3493
+ log.write_text("gateway failure after output", encoding="utf-8")
3494
+ raw = AISessionResult(
3495
+ command=AISessionCommand(("fake",), None, "claude", "stdin"),
3496
+ exit_code=0,
3497
+ timed_out=False,
3498
+ termination_reason=None,
3499
+ stale_marker=None,
3500
+ fatal_error_code="provider_error",
3501
+ progress_summary=ProgressSummary(result_subtype="success"),
3502
+ session_log=log,
3503
+ backup_log=tmp_path / "backup.log",
3504
+ log_recovery=None,
3505
+ started_epoch=0,
3506
+ ended_epoch=1,
3507
+ )
3508
+
3509
+ classification = classify_session(
3510
+ raw,
3511
+ project_root=tmp_path,
3512
+ base_head=base_head,
3513
+ prompt=PromptGenerationResult(
3514
+ output_path=tmp_path / "prompt.md",
3515
+ checkpoint_path=checkpoint,
3516
+ artifact_path=artifact_dir,
3517
+ ),
3518
+ )
3519
+
3520
+ assert classification.session_status == "infra_error"
3521
+ assert classification.reason == "ai_runtime_or_provider_error"
3522
+
3523
+
3524
+ @pytest.mark.parametrize(
3525
+ ("kind", "item_id"),
3526
+ [("feature", "F-001"), ("bugfix", "B-001"), ("refactor", "R-001")],
3527
+ )
3528
+ def test_family_status_skips_semantic_completion_failure_without_retry_spend(
3529
+ tmp_path, kind, item_id
3530
+ ):
3531
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
3532
+ from prizmkit_runtime.runner_status import start_item, update_item
3533
+
3534
+ paths = _make_paths(tmp_path / kind)
3535
+ item = {
3536
+ "id": item_id,
3537
+ "title": "Skip invalid completion evidence",
3538
+ "status": "pending",
3539
+ "priority": "high",
3540
+ "dependencies": [],
3541
+ }
3542
+ if kind == "bugfix":
3543
+ item.update(severity="high", description="invalid evidence", reproduction_steps=["run"])
3544
+ elif kind == "refactor":
3545
+ item.update(description="invalid evidence", targets=["src"], goals=["preserve"])
3546
+ _write_family_plan(paths, kind, [item])
3547
+ family = family_for(kind, paths)
3548
+ invocation = parse_invocation(family, "run", ())
3549
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": family.plan_path})
3550
+ assert start_item(family, invocation, item_id, paths.project_root).ok is True
3551
+
3552
+ result = update_item(
3553
+ family,
3554
+ invocation,
3555
+ item_id,
3556
+ "workflow_skipped",
3557
+ "session-1",
3558
+ paths.project_root,
3559
+ checkpoint_state={},
3560
+ )
3561
+ status = json.loads((family.state_dir / item_id / "status.json").read_text(encoding="utf-8"))
3562
+
3563
+ assert result.ok is True
3564
+ assert result.data["new_status"] == "skipped"
3565
+ assert result.data["restart_policy"] == "terminal_skipped"
3566
+ assert status["retry_count"] == 0
3567
+ assert status["infra_error_count"] == 0
3568
+ assert status["needs_attention"] is True
3569
+ assert status["skipped_reason"] == "workflow_completion_evidence_invalid"
3570
+
3571
+
3572
+ def test_invalid_existing_checkpoint_skips_terminal_success_with_completion_summary(tmp_path):
3430
3573
  from prizmkit_runtime.runner_classification import classify_session
3431
3574
  from prizmkit_runtime.runner_models import PromptGenerationResult
3432
3575
 
@@ -3440,11 +3583,11 @@ def test_invalid_existing_checkpoint_blocks_terminal_success_with_completion_sum
3440
3583
 
3441
3584
  classification = classify_session(_terminal_success_result(tmp_path), project_root=tmp_path, base_head=base_head, prompt=prompt)
3442
3585
 
3443
- assert classification.session_status == "crashed"
3586
+ assert classification.session_status == "workflow_skipped"
3444
3587
  assert classification.reason == "invalid_workflow_checkpoint:invalid_status"
3445
3588
 
3446
3589
 
3447
- def test_invalid_existing_checkpoint_blocks_head_change_success(tmp_path):
3590
+ def test_invalid_existing_checkpoint_blocks_runtime_failure_with_head_change(tmp_path):
3448
3591
  from prizmkit_runtime.runner_classification import classify_session
3449
3592
  from prizmkit_runtime.runner_models import PromptGenerationResult
3450
3593
  from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
@@ -3481,7 +3624,7 @@ def test_invalid_existing_checkpoint_blocks_head_change_success(tmp_path):
3481
3624
  assert classification.reason == "invalid_workflow_checkpoint:malformed_step_id"
3482
3625
 
3483
3626
 
3484
- def test_invalid_existing_checkpoint_blocks_workspace_auto_commit_success(tmp_path):
3627
+ def test_invalid_existing_checkpoint_blocks_ambiguous_clean_exit(tmp_path):
3485
3628
  from prizmkit_runtime.runner_classification import classify_session
3486
3629
  from prizmkit_runtime.runner_models import PromptGenerationResult
3487
3630
  from prizmkit_runtime.sessions import AISessionCommand, AISessionResult
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.141",
2
+ "version": "1.1.143",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Framework introduction and navigation for the formal single-requirement lifecycle, project initialization, Prizm docs, and independent deployment.",