prizmkit 1.1.139 → 1.1.141

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 (74) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +41 -121
  3. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -6
  4. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +1 -2
  5. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +150 -0
  6. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +21 -804
  7. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +4 -6
  8. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  9. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +4 -6
  10. package/bundled/dev-pipeline/scripts/prompt_framework.py +56 -55
  11. package/bundled/dev-pipeline/scripts/update-bug-status.py +1 -2
  12. package/bundled/dev-pipeline/scripts/update-checkpoint.py +14 -96
  13. package/bundled/dev-pipeline/scripts/update-feature-status.py +1 -2
  14. package/bundled/dev-pipeline/scripts/update-refactor-status.py +1 -2
  15. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +3 -3
  16. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +7 -18
  17. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +6 -6
  18. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +5 -5
  19. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +1 -1
  20. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
  21. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +1 -1
  22. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +13 -10
  23. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +1 -1
  24. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +14 -9
  25. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +1 -1
  26. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +1 -1
  27. package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
  28. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +2 -2
  29. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +1 -1
  30. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +1 -1
  31. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +1 -1
  32. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +14 -26
  33. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
  34. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +13 -10
  35. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
  36. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +287 -384
  37. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +119 -684
  38. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +17 -40
  39. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +20 -23
  40. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +29 -32
  41. package/bundled/skills/_metadata.json +6 -6
  42. package/bundled/skills/prizmkit/SKILL.md +8 -13
  43. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +61 -80
  44. package/bundled/skills/prizmkit-code-review/SKILL.md +3 -3
  45. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +61 -80
  46. package/bundled/skills/prizmkit-committer/SKILL.md +3 -3
  47. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +61 -80
  48. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +61 -80
  49. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +61 -80
  50. package/bundled/skills/prizmkit-retrospective/SKILL.md +2 -2
  51. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +61 -80
  52. package/bundled/skills/prizmkit-test/SKILL.md +217 -187
  53. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +119 -0
  54. package/bundled/skills/prizmkit-test/references/independent-test-review.md +269 -0
  55. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +193 -0
  56. package/bundled/skills/prizmkit-test/references/test-report-template.md +164 -79
  57. package/bundled/skills/prizmkit-workflow/SKILL.md +15 -23
  58. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +61 -80
  59. package/package.json +1 -1
  60. package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +0 -476
  61. package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +0 -116
  62. package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +0 -111
  63. package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +0 -66
  64. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +0 -104
  65. package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +0 -65
  66. package/bundled/skills/prizmkit-test/references/evidence-protocol.md +0 -208
  67. package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +0 -80
  68. package/bundled/skills/prizmkit-test/references/examples.md +0 -100
  69. package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +0 -228
  70. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +0 -118
  71. package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +0 -110
  72. package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +0 -181
  73. package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +0 -1851
  74. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +0 -1793
@@ -4,7 +4,11 @@ from __future__ import annotations
4
4
 
5
5
  import json
6
6
 
7
- from prizmkit_runtime.checkpoint_state import load_checkpoint_state, read_checkpoint_cursor, validate_checkpoint_data
7
+ from prizmkit_runtime.checkpoint_state import (
8
+ load_checkpoint_state,
9
+ read_checkpoint_cursor,
10
+ validate_checkpoint_data,
11
+ )
8
12
  from update_checkpoint import update_checkpoint
9
13
 
10
14
 
@@ -24,10 +28,74 @@ def _step(step_id, skill="skill", name="Step", status="pending", depends_on=None
24
28
 
25
29
 
26
30
  def _write(path, data):
31
+ path.parent.mkdir(parents=True, exist_ok=True)
27
32
  path.write_text(json.dumps(data), encoding="utf-8")
28
33
  return path
29
34
 
30
35
 
36
+ def _write_test_artifacts(root, slug="feature", *, result="TEST_PASS"):
37
+ artifact_dir = root / ".prizmkit" / "specs" / slug
38
+ artifact_dir.mkdir(parents=True, exist_ok=True)
39
+ (artifact_dir / "test-report.md").write_text(
40
+ f"# Test Report\n\n## Final Result\n\n- Result: {result}\n",
41
+ encoding="utf-8",
42
+ )
43
+ projection = {
44
+ "schema_version": 1,
45
+ "result": result,
46
+ "report": "test-report.md",
47
+ "main_review_rounds": 1,
48
+ "independent_review": {
49
+ "status": "downgraded",
50
+ "responses": 0,
51
+ "downgrade_reason": "read-only continuation capability unavailable",
52
+ "final_state_rechecked": False,
53
+ },
54
+ "repair_rounds": 0,
55
+ "production_changed": False,
56
+ "review_required": False,
57
+ "review_scope": None,
58
+ "unresolved_items": [],
59
+ }
60
+ (artifact_dir / "test-result.json").write_text(
61
+ json.dumps(projection), encoding="utf-8"
62
+ )
63
+ return artifact_dir
64
+
65
+
66
+ def _family_checkpoint(root=None, *, semantic=None, terminal=True):
67
+ skills = [
68
+ "prizmkit-implement",
69
+ "prizmkit-code-review",
70
+ "prizmkit-test",
71
+ "prizmkit-retrospective",
72
+ "prizmkit-committer",
73
+ "completion-summary",
74
+ ]
75
+ steps = []
76
+ previous = None
77
+ for index, skill in enumerate(skills, start=1):
78
+ step = _step(f"S{index:02d}", skill, skill, "completed", previous)
79
+ if skill == "prizmkit-code-review":
80
+ step["stage_result"] = "REVIEW_PASS"
81
+ if skill == "prizmkit-test":
82
+ step["stage_result"] = "TEST_PASS"
83
+ step["required_artifacts"] = [
84
+ ".prizmkit/specs/feature/test-report.md",
85
+ ".prizmkit/specs/feature/test-result.json",
86
+ ]
87
+ steps.append(step)
88
+ previous = step["id"]
89
+ if root is not None:
90
+ _write_test_artifacts(root)
91
+ return {
92
+ "version": 1,
93
+ "workflow_type": "feature-pipeline",
94
+ "steps": steps,
95
+ "feature_state": semantic or ({"terminal_status": "WORKFLOW_COMPLETED"} if terminal else {}),
96
+ }
97
+
98
+
31
99
  def test_valid_checkpoint_current_step_uses_first_non_terminal(tmp_path):
32
100
  path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
33
101
  _step("S01", "plan", "Plan", "completed"),
@@ -44,36 +112,17 @@ def test_valid_checkpoint_current_step_uses_first_non_terminal(tmp_path):
44
112
  assert read_checkpoint_cursor(path) == 2
45
113
 
46
114
 
47
- def test_all_completed_checkpoint_has_complete_cursor(tmp_path):
48
- path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
49
- _step("S01", "plan", "Plan", "completed"),
50
- _step("S02", "commit", "Commit", "skipped", "S01"),
51
- ]))
52
-
53
- state = load_checkpoint_state(path)
54
-
55
- assert state.valid is True
56
- assert state.all_completed is True
57
- assert state.current_step is None
58
- assert state.current_step_name == ""
59
- assert state.cursor == 2
60
- assert state.cursor_signature == "complete"
61
-
62
-
63
115
  def test_missing_and_malformed_checkpoints_are_invalid(tmp_path):
64
116
  missing = load_checkpoint_state(tmp_path / "missing.json")
65
117
  assert missing.valid is False
66
118
  assert missing.exists is False
67
119
  assert missing.error_code == "missing"
68
- assert missing.current_step_name == ""
69
120
 
70
121
  malformed_path = tmp_path / "bad.json"
71
122
  malformed_path.write_text("{not-json", encoding="utf-8")
72
123
  malformed = load_checkpoint_state(malformed_path)
73
124
  assert malformed.valid is False
74
- assert malformed.exists is True
75
125
  assert malformed.error_code == "malformed_json"
76
- assert malformed.current_step_name == ""
77
126
 
78
127
 
79
128
  def test_structural_validation_error_codes(tmp_path):
@@ -83,7 +132,6 @@ def test_structural_validation_error_codes(tmp_path):
83
132
  ("duplicate_step_id", _checkpoint([_step("S01"), _step("S01")])),
84
133
  ("invalid_status", _checkpoint([_step("S01", status="done")])),
85
134
  ("invalid_depends_on", _checkpoint([_step("S01", depends_on="S99")])),
86
- ("invalid_depends_on", _checkpoint([_step("S01", depends_on="S02"), _step("S02")])),
87
135
  ("malformed_step_id", _checkpoint([_step("one")])),
88
136
  ("invalid_step", {"steps": ["not-object"]}),
89
137
  ]
@@ -92,183 +140,67 @@ def test_structural_validation_error_codes(tmp_path):
92
140
  state = load_checkpoint_state(path)
93
141
  assert state.valid is False
94
142
  assert state.error_code == expected_code
95
- assert state.current_step_name == ""
96
143
  assert read_checkpoint_cursor(path) is None
97
144
 
98
145
 
99
- def test_failed_checkpoint_step_is_current_cursor(tmp_path):
146
+ def test_duplicate_skills_require_exact_step_id(tmp_path):
100
147
  path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
101
- _step("S01", "plan", "Plan", "completed"),
102
- _step("S02", "review", "Review", "failed", "S01"),
148
+ _step("S01", "prizmkit-committer", "First", "completed"),
149
+ _step("S02", "prizmkit-committer", "Second", "pending", "S01"),
103
150
  ]))
104
151
 
105
- state = load_checkpoint_state(path)
152
+ rejected = update_checkpoint(str(path), "prizmkit-committer", "completed")
153
+ accepted = update_checkpoint(str(path), "S02", "completed")
106
154
 
107
- assert state.valid is True
108
- assert state.current_step_name == "Review"
109
- assert state.cursor == 2
155
+ assert rejected["ok"] is False
156
+ assert "matches multiple checkpoint steps" in rejected["error"]
157
+ assert accepted["ok"] is True
110
158
 
111
159
 
112
- def test_duplicate_skills_are_reported_but_exact_ids_remain_valid(tmp_path):
113
- path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
114
- _step("S01", "prizmkit-committer", "Commit First", "pending"),
115
- _step("S02", "prizmkit-committer", "Commit Second", "pending", "S01"),
116
- ]))
160
+ def test_semantic_completion_requires_family_sequence_and_domain_results(tmp_path):
161
+ path = _write(
162
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
163
+ _family_checkpoint(tmp_path),
164
+ )
117
165
 
118
- state = load_checkpoint_state(path)
166
+ state = load_checkpoint_state(path, project_root=tmp_path)
119
167
 
120
168
  assert state.valid is True
121
- assert state.duplicate_skills == {"prizmkit-committer": ("S01", "S02")}
122
-
123
-
124
- def test_update_checkpoint_rejects_ambiguous_skill_without_mutation(tmp_path):
125
- path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
126
- _step("S01", "prizmkit-committer", "Commit First", "pending"),
127
- _step("S02", "prizmkit-committer", "Commit Second", "pending", "S01"),
128
- ]))
129
- before = path.read_text(encoding="utf-8")
130
-
131
- result = update_checkpoint(str(path), "prizmkit-committer", "completed")
132
-
133
- assert result["ok"] is False
134
- assert "matches multiple checkpoint steps" in result["error"]
135
- assert "Use an explicit step ID" in result["error"]
136
- assert path.read_text(encoding="utf-8") == before
137
-
138
-
139
- def test_update_checkpoint_exact_id_updates_one_duplicate_skill_step(tmp_path):
140
- path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
141
- _step("S01", "prizmkit-committer", "Commit First", "completed"),
142
- _step("S02", "prizmkit-committer", "Commit Second", "pending", "S01"),
143
- ]))
144
-
145
- result = update_checkpoint(str(path), "S02", "completed")
146
- data = json.loads(path.read_text(encoding="utf-8"))
147
-
148
- assert result["ok"] is True
149
- assert result["step_id"] == "S02"
150
- assert data["steps"][0]["status"] == "completed"
151
- assert data["steps"][1]["status"] == "completed"
152
-
153
-
154
- def test_update_checkpoint_invalid_checkpoint_fails_without_mutation(tmp_path):
155
- path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
156
- _step("S01", "plan", "Plan", "done"),
157
- ]))
158
- before = path.read_text(encoding="utf-8")
159
-
160
- result = update_checkpoint(str(path), "S01", "completed")
161
-
162
- assert result["ok"] is False
163
- assert "invalid_status" in result["error"]
164
- assert path.read_text(encoding="utf-8") == before
165
-
166
-
167
-
168
- def _family_checkpoint(workflow_type, skills, *, semantic=None):
169
- steps = []
170
- previous = None
171
- for index, skill in enumerate(skills, start=1):
172
- step = _step(f"S{index:02d}", skill, skill, "completed", previous)
173
- if skill == "prizmkit-code-review":
174
- step["stage_result"] = "REVIEW_PASS"
175
- if skill == "prizmkit-test":
176
- step["stage_result"] = "TEST_PASS"
177
- steps.append(step)
178
- previous = step["id"]
179
- payload = {"version": 1, "workflow_type": workflow_type, "steps": steps}
180
- state_key = {
181
- "feature-pipeline": "feature_state",
182
- "bugfix-pipeline": "bugfix_state",
183
- "refactor-pipeline": "refactor_state",
184
- }[workflow_type]
185
- payload[state_key] = semantic or {}
186
- return payload
187
-
188
-
189
- def _write_test_evidence(root, verdict="TEST_PASS", validation_result="passed", evidence_id="evidence-1"):
190
- evidence = root / ".prizmkit" / "test" / "evidence" / evidence_id
191
- evidence.mkdir(parents=True)
192
- manifest = evidence / "manifest.json"
193
- verdict_path = evidence / "verdict.json"
194
- validation = evidence / "validation.json"
195
- manifest.write_text(json.dumps({"evidence_id": evidence_id, "final_verdict": verdict}), encoding="utf-8")
196
- verdict_path.write_text(json.dumps({"verdict": verdict}), encoding="utf-8")
197
- validation.write_text(json.dumps({"verdict": verdict, "result": validation_result}), encoding="utf-8")
198
- return {
199
- "manifest": str(manifest.relative_to(root)),
200
- "verdict": str(verdict_path.relative_to(root)),
201
- "validation": str(validation.relative_to(root)),
202
- }
169
+ assert state.all_completed is True
170
+ assert state.semantic_complete is True
171
+ assert state.semantic.test_result_valid is True
172
+ assert state.semantic.terminal_status == "WORKFLOW_COMPLETED"
203
173
 
204
174
 
205
- def test_semantic_completion_requires_family_sequence_verdicts_and_structured_evidence(tmp_path, monkeypatch):
206
- monkeypatch.setattr(
207
- "prizmkit_runtime.checkpoint_state._resolve_evidence_validator",
208
- lambda _root: tmp_path / "validator.py",
209
- )
210
- monkeypatch.setattr(
211
- "prizmkit_runtime.checkpoint_state._evidence_validator_passes",
212
- lambda *_args: True,
213
- )
214
- paths = _write_test_evidence(tmp_path)
215
- skills = [
216
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
217
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
218
- ]
219
- payload = _family_checkpoint(
220
- "feature-pipeline",
221
- skills,
222
- semantic={
223
- "repair_round": 0,
224
- "authoritative_evidence_paths": paths,
225
- "terminal_status": "WORKFLOW_COMPLETED",
226
- },
227
- )
228
- payload["steps"][2]["authoritative_evidence_paths"] = paths
229
- checkpoint = tmp_path / ".prizmkit" / "specs" / "feature" / "workflow-checkpoint.json"
230
- checkpoint.parent.mkdir(parents=True)
231
- _write(checkpoint, payload)
175
+ def test_completion_does_not_require_retired_evidence_paths(tmp_path):
176
+ payload = _family_checkpoint(tmp_path)
177
+ assert "authoritative_evidence_paths" not in payload["steps"][2]
178
+ assert "authoritative_evidence_paths" not in payload["feature_state"]
232
179
 
233
- state = load_checkpoint_state(checkpoint, project_root=tmp_path)
180
+ state = validate_checkpoint_data(payload, project_root=tmp_path)
234
181
 
235
- assert state.valid is True
236
- assert state.all_completed is True
237
182
  assert state.semantic_complete is True
238
- assert state.semantic.evidence_valid is True
239
- assert state.semantic.terminal_status == "WORKFLOW_COMPLETED"
183
+ assert state.semantic.test_result_valid is True
240
184
 
241
185
 
242
- def test_completed_steps_without_stage_verdicts_are_not_semantic_completion(tmp_path):
243
- skills = [
244
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
245
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
246
- ]
247
- payload = _family_checkpoint("feature-pipeline", skills)
186
+ def test_completed_steps_without_review_or_test_results_do_not_complete(tmp_path):
187
+ payload = _family_checkpoint(tmp_path)
248
188
  payload["steps"][1].pop("stage_result")
249
189
  payload["steps"][2].pop("stage_result")
250
- payload["feature_state"]["terminal_status"] = "WORKFLOW_COMPLETED"
251
190
 
252
191
  state = validate_checkpoint_data(payload, project_root=tmp_path)
253
192
 
254
- assert state.valid is True
255
- assert state.all_completed is True
256
193
  assert state.semantic_complete is False
257
194
  assert state.semantic.error_code == "incomplete_semantic_evidence"
258
195
 
259
196
 
260
197
  def test_family_sequence_mismatch_cannot_complete(tmp_path):
261
- paths = _write_test_evidence(tmp_path)
262
- skills = [
263
- "prizmkit-implement", "prizmkit-test", "prizmkit-code-review",
264
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
265
- ]
266
- payload = _family_checkpoint(
267
- "feature-pipeline",
268
- skills,
269
- semantic={"authoritative_evidence_paths": paths, "terminal_status": "WORKFLOW_COMPLETED"},
270
- )
271
- payload["steps"][1]["authoritative_evidence_paths"] = paths
198
+ payload = _family_checkpoint(tmp_path)
199
+ payload["steps"][1], payload["steps"][2] = payload["steps"][2], payload["steps"][1]
200
+ payload["steps"][1]["id"] = "S02"
201
+ payload["steps"][2]["id"] = "S03"
202
+ payload["steps"][1]["depends_on"] = "S01"
203
+ payload["steps"][2]["depends_on"] = "S02"
272
204
 
273
205
  state = validate_checkpoint_data(payload, project_root=tmp_path)
274
206
 
@@ -277,263 +209,99 @@ def test_family_sequence_mismatch_cannot_complete(tmp_path):
277
209
  assert state.semantic.error_code == "invalid_stage_sequence"
278
210
 
279
211
 
280
- def test_stale_or_mismatched_test_evidence_cannot_complete(tmp_path):
281
- paths = _write_test_evidence(tmp_path, verdict="TEST_FAIL")
282
- skills = [
283
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
284
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
285
- ]
286
- payload = _family_checkpoint(
287
- "feature-pipeline",
288
- skills,
289
- semantic={"authoritative_evidence_paths": paths, "terminal_status": "WORKFLOW_COMPLETED"},
290
- )
291
- payload["steps"][2]["authoritative_evidence_paths"] = paths
292
-
293
- state = validate_checkpoint_data(payload, project_root=tmp_path)
294
-
295
- assert state.semantic_complete is False
296
- assert state.semantic.evidence_valid is False
297
- assert state.semantic.error_code == "incomplete_semantic_evidence"
298
-
299
-
300
- def test_blocked_terminal_is_semantic_and_requires_reason(tmp_path):
301
- payload = _family_checkpoint(
302
- "feature-pipeline",
303
- [
304
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
305
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
306
- ],
307
- semantic={
308
- "repair_scope": "production",
309
- "repair_round": 3,
310
- "blocked_reason": "repair_round_limit",
311
- "terminal_status": "WORKFLOW_BLOCKED",
312
- },
313
- )
314
- payload["steps"][2]["stage_result"] = "TEST_FAIL"
315
-
316
- state = validate_checkpoint_data(payload, project_root=tmp_path)
317
-
318
- assert state.semantic_blocked is True
319
- assert state.semantic_complete is False
320
- assert state.semantic.blocked_reason == "repair_round_limit"
321
- assert state.cursor_signature == "blocked:repair_round_limit"
322
-
323
-
324
- def test_step_progress_must_be_a_terminal_prefix(tmp_path):
325
- payload = _checkpoint([
326
- _step("S01", "plan", "Plan", "pending"),
327
- _step("S02", "implement", "Implement", "completed", "S01"),
328
- ])
329
-
330
- state = validate_checkpoint_data(payload)
331
-
332
- assert state.valid is False
333
- assert state.error_code == "invalid_step_progress"
334
-
335
-
336
-
337
- def test_update_checkpoint_rejects_out_of_order_transition(tmp_path):
338
- path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
339
- _step("S01", "plan", "Plan", "pending"),
340
- _step("S02", "implement", "Implement", "pending", "S01"),
341
- ]))
342
-
343
- result = update_checkpoint(str(path), "S02", "in_progress")
344
-
345
- assert result["ok"] is False
346
- assert "before all predecessors" in result["error"]
347
-
348
-
349
- def test_update_checkpoint_blocks_unknown_repair_scope_without_mutating_retry_budget(tmp_path):
350
- payload = _family_checkpoint(
351
- "feature-pipeline",
352
- [
353
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
354
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
355
- ],
356
- )
357
- payload["steps"][1]["status"] = "in_progress"
358
- for step in payload["steps"][2:]:
212
+ def test_test_needs_fixes_is_valid_domain_result_and_blocks_l4(tmp_path):
213
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
214
+ payload["steps"][2]["status"] = "in_progress"
215
+ payload["steps"][2].pop("stage_result")
216
+ for step in payload["steps"][3:]:
359
217
  step["status"] = "pending"
360
218
  step.pop("stage_result", None)
361
- path = _write(tmp_path / "workflow-checkpoint.json", payload)
219
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
362
220
 
363
221
  result = update_checkpoint(
364
- str(path),
365
- "prizmkit-code-review",
222
+ str(checkpoint),
223
+ "prizmkit-test",
366
224
  "failed",
367
- semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_round": 0},
225
+ semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
368
226
  )
369
- data = json.loads(path.read_text(encoding="utf-8"))
227
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
370
228
 
371
229
  assert result["ok"] is True
372
- assert data["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"
373
- assert data["feature_state"]["blocked_reason"] == "unsafe_repair_scope"
374
- assert "retry_count" not in data["feature_state"]
230
+ assert persisted["steps"][2]["stage_result"] == "TEST_NEEDS_FIXES"
231
+ assert persisted["feature_state"]["stage_result"] == "TEST_NEEDS_FIXES"
232
+ assert persisted["feature_state"]["blocked_reason"] == "test_needs_fixes"
233
+ assert persisted["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"
234
+ assert "authoritative_evidence_paths" not in persisted["steps"][2]
375
235
 
376
236
 
377
- def test_update_checkpoint_enforces_three_outer_repair_rounds(tmp_path):
378
- payload = _family_checkpoint(
379
- "feature-pipeline",
380
- [
381
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
382
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
383
- ],
384
- semantic={"repair_scope": "production", "repair_round": 3},
385
- )
386
- payload["steps"][1]["status"] = "in_progress"
387
- for step in payload["steps"][2:]:
237
+ def test_test_blocked_preserves_l1_result_without_evidence_discovery(tmp_path):
238
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
239
+ payload["steps"][2]["status"] = "in_progress"
240
+ payload["steps"][2].pop("stage_result")
241
+ for step in payload["steps"][3:]:
388
242
  step["status"] = "pending"
389
243
  step.pop("stage_result", None)
390
- path = _write(tmp_path / "workflow-checkpoint.json", payload)
244
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
391
245
 
392
246
  result = update_checkpoint(
393
- str(path),
394
- "prizmkit-code-review",
247
+ str(checkpoint),
248
+ "prizmkit-test",
395
249
  "failed",
396
- semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_scope": "production"},
250
+ semantic_update={"stage_result": "TEST_BLOCKED", "blocked_reason": "safe environment unavailable"},
397
251
  )
398
- data = json.loads(path.read_text(encoding="utf-8"))
252
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
399
253
 
400
254
  assert result["ok"] is True
401
- assert data["feature_state"]["repair_round"] == 3
402
- assert data["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"
403
- assert data["feature_state"]["blocked_reason"] == "repair_round_limit"
404
-
405
-
406
-
407
- def test_declared_final_artifact_must_exist_for_semantic_completion(tmp_path, monkeypatch):
408
- monkeypatch.setattr(
409
- "prizmkit_runtime.checkpoint_state._resolve_evidence_validator",
410
- lambda _root: tmp_path / "validator.py",
411
- )
412
- monkeypatch.setattr(
413
- "prizmkit_runtime.checkpoint_state._evidence_validator_passes",
414
- lambda *_args: True,
415
- )
416
- paths = _write_test_evidence(tmp_path)
417
- skills = [
418
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
419
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
420
- ]
421
- payload = _family_checkpoint(
422
- "feature-pipeline",
423
- skills,
424
- semantic={"authoritative_evidence_paths": paths, "terminal_status": "WORKFLOW_COMPLETED"},
425
- )
426
- payload["steps"][2]["authoritative_evidence_paths"] = paths
427
- payload["steps"][-1]["required_artifacts"] = [".prizmkit/specs/feature/completion-summary.json"]
428
- checkpoint = tmp_path / ".prizmkit" / "specs" / "feature" / "workflow-checkpoint.json"
429
- checkpoint.parent.mkdir(parents=True)
430
- _write(checkpoint, payload)
431
-
432
- missing = load_checkpoint_state(checkpoint, project_root=tmp_path)
433
- (checkpoint.parent / "completion-summary.json").write_text("{}", encoding="utf-8")
434
- present = load_checkpoint_state(checkpoint, project_root=tmp_path)
435
-
436
- assert missing.semantic_complete is False
437
- assert missing.semantic.artifacts_valid is False
438
- assert present.semantic_complete is True
439
- assert present.semantic.artifacts_valid is True
255
+ assert persisted["steps"][2]["stage_result"] == "TEST_BLOCKED"
256
+ assert persisted["feature_state"]["stage_result"] == "TEST_BLOCKED"
257
+ assert persisted["feature_state"]["blocked_reason"] == "test_blocked"
258
+ assert "authoritative_evidence_paths" not in persisted["feature_state"]
440
259
 
441
260
 
442
-
443
- def test_blocked_test_handoff_discovers_finalized_evidence_paths(tmp_path):
444
- artifact = tmp_path / ".prizmkit" / "specs" / "feature"
445
- artifact.mkdir(parents=True)
446
- evidence = tmp_path / ".prizmkit" / "test" / "evidence" / "evidence-1"
447
- evidence.mkdir(parents=True)
448
- for name in ("manifest.json", "verdict.json", "validation.json"):
449
- (evidence / name).write_text("{}", encoding="utf-8")
450
- report = evidence / "test-report.md"
451
- report.write_text("blocked", encoding="utf-8")
452
- (artifact / "test-report-path.txt").write_text(str(report), encoding="utf-8")
453
-
454
- payload = _family_checkpoint(
455
- "feature-pipeline",
456
- [
457
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
458
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
459
- ],
460
- semantic={"artifact_dir": ".prizmkit/specs/feature"},
461
- )
462
- payload["feature_state"]["artifact_dir"] = ".prizmkit/specs/feature"
463
- payload["steps"][1]["stage_result"] = "REVIEW_PASS"
261
+ def test_update_checkpoint_completes_test_pass_without_validator(tmp_path):
262
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
464
263
  payload["steps"][2]["status"] = "in_progress"
465
264
  payload["steps"][2].pop("stage_result")
466
265
  for step in payload["steps"][3:]:
467
266
  step["status"] = "pending"
468
267
  step.pop("stage_result", None)
469
- checkpoint = _write(tmp_path / ".prizmkit" / "specs" / "feature" / "workflow-checkpoint.json", payload)
268
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
470
269
 
471
270
  result = update_checkpoint(
472
271
  str(checkpoint),
473
272
  "prizmkit-test",
474
- "failed",
475
- semantic_update={
476
- "stage_result": "TEST_BLOCKED",
477
- "blocked_reason": "test_blocked",
478
- "terminal_status": "WORKFLOW_BLOCKED",
479
- },
273
+ "completed",
274
+ semantic_update={"stage_result": "TEST_PASS"},
480
275
  )
481
276
 
482
277
  assert result["ok"] is True
483
- paths = json.loads(checkpoint.read_text(encoding="utf-8"))["steps"][2]["authoritative_evidence_paths"]
484
- assert paths["manifest"] == ".prizmkit/test/evidence/evidence-1/manifest.json"
485
- assert paths["verdict"] == ".prizmkit/test/evidence/evidence-1/verdict.json"
486
- assert paths["validation"] == ".prizmkit/test/evidence/evidence-1/validation.json"
278
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
279
+ assert persisted["steps"][2]["status"] == "completed"
280
+ assert persisted["steps"][2]["stage_result"] == "TEST_PASS"
487
281
 
488
282
 
489
-
490
- def test_update_checkpoint_rejects_test_pass_when_validator_replay_fails(tmp_path, monkeypatch):
491
- paths = _write_test_evidence(tmp_path)
492
- payload = _family_checkpoint(
493
- "feature-pipeline",
494
- [
495
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
496
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
497
- ],
498
- )
283
+ def test_update_checkpoint_rejects_test_completion_for_non_pass(tmp_path):
284
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
499
285
  payload["steps"][2]["status"] = "in_progress"
286
+ payload["steps"][2].pop("stage_result")
500
287
  for step in payload["steps"][3:]:
501
288
  step["status"] = "pending"
502
289
  step.pop("stage_result", None)
503
- checkpoint = tmp_path / "workflow-checkpoint.json"
504
- _write(checkpoint, payload)
505
- monkeypatch.setattr(
506
- "prizmkit_runtime.checkpoint_state._resolve_evidence_validator",
507
- lambda _root: tmp_path / "validator.py",
508
- )
509
- monkeypatch.setattr(
510
- "prizmkit_runtime.checkpoint_state._evidence_validator_passes",
511
- lambda *_args: False,
512
- )
290
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
513
291
 
514
292
  result = update_checkpoint(
515
293
  str(checkpoint),
516
294
  "prizmkit-test",
517
295
  "completed",
518
- semantic_update={
519
- "stage_result": "TEST_PASS",
520
- "authoritative_evidence_paths": paths,
521
- },
296
+ semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
522
297
  )
523
298
 
524
299
  assert result["ok"] is False
525
- assert "validator-attested" in result["error"]
526
-
300
+ assert "requires TEST_PASS" in result["error"]
527
301
 
528
302
 
529
303
  def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
530
- payload = _family_checkpoint(
531
- "feature-pipeline",
532
- [
533
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
534
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
535
- ],
536
- )
304
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
537
305
  payload["steps"][1]["status"] = "skipped"
538
306
  payload["steps"][1].pop("stage_result", None)
539
307
  payload["steps"][2]["status"] = "pending"
@@ -549,13 +317,7 @@ def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
549
317
 
550
318
 
551
319
  def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
552
- payload = _family_checkpoint(
553
- "feature-pipeline",
554
- [
555
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
556
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
557
- ],
558
- )
320
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
559
321
  payload["steps"][2]["status"] = "skipped"
560
322
  payload["steps"][2].pop("stage_result", None)
561
323
  payload["steps"][3]["status"] = "pending"
@@ -568,3 +330,144 @@ def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
568
330
 
569
331
  assert result["ok"] is False
570
332
  assert "TEST_PASS" in result["error"]
333
+
334
+
335
+ def test_declared_final_artifact_must_exist_for_completion(tmp_path):
336
+ payload = _family_checkpoint(tmp_path)
337
+ payload["steps"][-1]["required_artifacts"] = [
338
+ ".prizmkit/specs/feature/completion-summary.json"
339
+ ]
340
+ checkpoint = _write(
341
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
342
+ payload,
343
+ )
344
+
345
+ missing = load_checkpoint_state(checkpoint, project_root=tmp_path)
346
+ (checkpoint.parent / "completion-summary.json").write_text("{}", encoding="utf-8")
347
+ present = load_checkpoint_state(checkpoint, project_root=tmp_path)
348
+
349
+ assert missing.semantic_complete is False
350
+ assert missing.semantic.artifacts_valid is False
351
+ assert present.semantic_complete is True
352
+ assert present.semantic.artifacts_valid is True
353
+
354
+
355
+ def test_completion_rejects_invalid_test_result_projection(tmp_path):
356
+ payload = _family_checkpoint(tmp_path)
357
+ checkpoint = _write(
358
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
359
+ payload,
360
+ )
361
+ result_path = checkpoint.parent / "test-result.json"
362
+ result = json.loads(result_path.read_text(encoding="utf-8"))
363
+ result["main_review_rounds"] = 0
364
+ result_path.write_text(json.dumps(result), encoding="utf-8")
365
+
366
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
367
+
368
+ assert state.semantic_complete is False
369
+ assert state.semantic.artifacts_valid is False
370
+
371
+
372
+ def test_completion_rejects_inconsistent_independent_review_metadata(tmp_path):
373
+ payload = _family_checkpoint(tmp_path)
374
+ checkpoint = _write(
375
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
376
+ payload,
377
+ )
378
+ result_path = checkpoint.parent / "test-result.json"
379
+ result = json.loads(result_path.read_text(encoding="utf-8"))
380
+ result["independent_review"] = {
381
+ "status": "completed",
382
+ "responses": 0,
383
+ "downgrade_reason": None,
384
+ "final_state_rechecked": True,
385
+ }
386
+ result_path.write_text(json.dumps(result), encoding="utf-8")
387
+
388
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
389
+
390
+ assert state.semantic_complete is False
391
+ assert state.semantic.artifacts_valid is False
392
+
393
+
394
+ def test_completion_rejects_mismatched_test_report_result(tmp_path):
395
+ payload = _family_checkpoint(tmp_path)
396
+ checkpoint = _write(
397
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
398
+ payload,
399
+ )
400
+ (checkpoint.parent / "test-report.md").write_text(
401
+ "# Test Report\n\n## Final Result\n\n- Result: TEST_BLOCKED\n",
402
+ encoding="utf-8",
403
+ )
404
+
405
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
406
+
407
+ assert state.semantic_complete is False
408
+ assert state.semantic.artifacts_valid is False
409
+
410
+
411
+ def test_completion_rejects_report_with_result_only_outside_final_section(tmp_path):
412
+ payload = _family_checkpoint(tmp_path)
413
+ checkpoint = _write(
414
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
415
+ payload,
416
+ )
417
+ (checkpoint.parent / "test-report.md").write_text(
418
+ "# Test Report\n\n## Final Result\n\n- Result: TEST_BLOCKED\n\n"
419
+ "## Remaining Risks and Unresolved Items\n\n- Expected eventual result: TEST_PASS\n",
420
+ encoding="utf-8",
421
+ )
422
+
423
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
424
+
425
+ assert state.semantic_complete is False
426
+ assert state.semantic.artifacts_valid is False
427
+
428
+
429
+ def test_completion_rejects_test_artifacts_outside_project(tmp_path):
430
+ outside = tmp_path.parent / f"{tmp_path.name}-outside"
431
+ outside.mkdir()
432
+ _write_test_artifacts(outside)
433
+ payload = _family_checkpoint(tmp_path)
434
+ test_step = payload["steps"][2]
435
+ test_step["required_artifacts"] = [
436
+ f"../{outside.name}/.prizmkit/specs/feature/test-report.md",
437
+ f"../{outside.name}/.prizmkit/specs/feature/test-result.json",
438
+ ]
439
+ checkpoint = _write(
440
+ tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
441
+ payload,
442
+ )
443
+
444
+ state = load_checkpoint_state(checkpoint, project_root=tmp_path)
445
+
446
+ assert state.semantic_complete is False
447
+ assert state.semantic.artifacts_valid is False
448
+
449
+
450
+ def test_review_outer_repair_cap_remains_runtime_owned(tmp_path):
451
+ payload = _family_checkpoint(
452
+ tmp_path, semantic={"repair_scope": "production", "repair_round": 3},
453
+ terminal=False,
454
+ )
455
+ payload["steps"][1]["status"] = "in_progress"
456
+ payload["steps"][1].pop("stage_result")
457
+ for step in payload["steps"][2:]:
458
+ step["status"] = "pending"
459
+ step.pop("stage_result", None)
460
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
461
+
462
+ result = update_checkpoint(
463
+ str(checkpoint),
464
+ "prizmkit-code-review",
465
+ "failed",
466
+ semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_scope": "production"},
467
+ )
468
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
469
+
470
+ assert result["ok"] is True
471
+ assert persisted["feature_state"]["repair_round"] == 3
472
+ assert persisted["feature_state"]["blocked_reason"] == "repair_round_limit"
473
+ assert persisted["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"