prizmkit 1.1.140 → 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 -122
  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 +282 -432
  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 -189
  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 -477
  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 -107
  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 -1930
  74. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +0 -1847
@@ -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,316 +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:]:
359
- step["status"] = "pending"
360
- step.pop("stage_result", None)
361
- path = _write(tmp_path / "workflow-checkpoint.json", payload)
362
-
363
- result = update_checkpoint(
364
- str(path),
365
- "prizmkit-code-review",
366
- "failed",
367
- semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_round": 0},
368
- )
369
- data = json.loads(path.read_text(encoding="utf-8"))
370
-
371
- 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"]
375
-
376
-
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:]:
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:]:
388
217
  step["status"] = "pending"
389
218
  step.pop("stage_result", None)
390
- path = _write(tmp_path / "workflow-checkpoint.json", payload)
219
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
391
220
 
392
221
  result = update_checkpoint(
393
- str(path),
394
- "prizmkit-code-review",
222
+ str(checkpoint),
223
+ "prizmkit-test",
395
224
  "failed",
396
- semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_scope": "production"},
225
+ semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
397
226
  )
398
- data = json.loads(path.read_text(encoding="utf-8"))
227
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
399
228
 
400
229
  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
440
-
441
-
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]
442
235
 
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
- (evidence / "manifest.json").write_text(
449
- json.dumps({"evidence_id": "evidence-1", "final_verdict": "TEST_BLOCKED"}),
450
- encoding="utf-8",
451
- )
452
- (evidence / "verdict.json").write_text(
453
- json.dumps({"verdict": "TEST_BLOCKED"}),
454
- encoding="utf-8",
455
- )
456
- (evidence / "validation.json").write_text(
457
- json.dumps({"result": "passed", "verdict": "TEST_BLOCKED"}),
458
- encoding="utf-8",
459
- )
460
- (evidence / "lifecycle.json").write_text(
461
- json.dumps({"status": "sealed", "finalized": True}),
462
- encoding="utf-8",
463
- )
464
- report = evidence / "test-report.md"
465
- report.write_text("blocked", encoding="utf-8")
466
- (artifact / "test-report-path.txt").write_text(str(report), encoding="utf-8")
467
236
 
468
- payload = _family_checkpoint(
469
- "feature-pipeline",
470
- [
471
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
472
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
473
- ],
474
- semantic={"artifact_dir": ".prizmkit/specs/feature"},
475
- )
476
- payload["feature_state"]["artifact_dir"] = ".prizmkit/specs/feature"
477
- payload["steps"][1]["stage_result"] = "REVIEW_PASS"
237
+ def test_test_blocked_preserves_l1_result_without_evidence_discovery(tmp_path):
238
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
478
239
  payload["steps"][2]["status"] = "in_progress"
479
240
  payload["steps"][2].pop("stage_result")
480
241
  for step in payload["steps"][3:]:
481
242
  step["status"] = "pending"
482
243
  step.pop("stage_result", None)
483
- checkpoint = _write(tmp_path / ".prizmkit" / "specs" / "feature" / "workflow-checkpoint.json", payload)
244
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
484
245
 
485
246
  result = update_checkpoint(
486
247
  str(checkpoint),
487
248
  "prizmkit-test",
488
249
  "failed",
489
- semantic_update={
490
- "stage_result": "TEST_BLOCKED",
491
- "blocked_reason": "test_blocked",
492
- "terminal_status": "WORKFLOW_BLOCKED",
493
- },
250
+ semantic_update={"stage_result": "TEST_BLOCKED", "blocked_reason": "safe environment unavailable"},
494
251
  )
252
+ persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
495
253
 
496
254
  assert result["ok"] is True
497
- paths = json.loads(checkpoint.read_text(encoding="utf-8"))["steps"][2]["authoritative_evidence_paths"]
498
- assert paths["manifest"] == ".prizmkit/test/evidence/evidence-1/manifest.json"
499
- assert paths["verdict"] == ".prizmkit/test/evidence/evidence-1/verdict.json"
500
- assert paths["validation"] == ".prizmkit/test/evidence/evidence-1/validation.json"
501
-
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"]
502
259
 
503
260
 
504
- def test_blocked_test_handoff_without_finalized_evidence_preserves_test_result(tmp_path):
505
- artifact = tmp_path / ".prizmkit" / "specs" / "feature"
506
- artifact.mkdir(parents=True)
507
- payload = _family_checkpoint(
508
- "feature-pipeline",
509
- [
510
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
511
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
512
- ],
513
- semantic={"artifact_dir": ".prizmkit/specs/feature"},
514
- )
515
- payload["feature_state"]["artifact_dir"] = ".prizmkit/specs/feature"
516
- 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)
517
263
  payload["steps"][2]["status"] = "in_progress"
518
264
  payload["steps"][2].pop("stage_result")
519
265
  for step in payload["steps"][3:]:
520
266
  step["status"] = "pending"
521
267
  step.pop("stage_result", None)
522
- checkpoint = _write(artifact / "workflow-checkpoint.json", payload)
268
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
523
269
 
524
270
  result = update_checkpoint(
525
271
  str(checkpoint),
526
272
  "prizmkit-test",
527
- "failed",
528
- semantic_update={
529
- "stage_result": "TEST_BLOCKED",
530
- "blocked_reason": "test_blocked",
531
- "terminal_status": "WORKFLOW_BLOCKED",
532
- },
273
+ "completed",
274
+ semantic_update={"stage_result": "TEST_PASS"},
533
275
  )
534
276
 
535
- persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
536
277
  assert result["ok"] is True
537
- assert result["semantic"]["stage_result"] == "TEST_BLOCKED"
538
- assert persisted["steps"][2]["stage_result"] == "TEST_BLOCKED"
539
- assert persisted["feature_state"]["stage_result"] == "TEST_BLOCKED"
540
- assert "authoritative_evidence_paths" not in persisted["steps"][2]
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"
541
281
 
542
282
 
543
- def test_update_checkpoint_rejects_test_pass_when_validator_replay_fails(tmp_path, monkeypatch):
544
- paths = _write_test_evidence(tmp_path)
545
- payload = _family_checkpoint(
546
- "feature-pipeline",
547
- [
548
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
549
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
550
- ],
551
- )
283
+ def test_update_checkpoint_rejects_test_completion_for_non_pass(tmp_path):
284
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
552
285
  payload["steps"][2]["status"] = "in_progress"
286
+ payload["steps"][2].pop("stage_result")
553
287
  for step in payload["steps"][3:]:
554
288
  step["status"] = "pending"
555
289
  step.pop("stage_result", None)
556
- checkpoint = tmp_path / "workflow-checkpoint.json"
557
- _write(checkpoint, payload)
558
- monkeypatch.setattr(
559
- "prizmkit_runtime.checkpoint_state._resolve_evidence_validator",
560
- lambda _root: tmp_path / "validator.py",
561
- )
562
- monkeypatch.setattr(
563
- "prizmkit_runtime.checkpoint_state._evidence_validator_passes",
564
- lambda *_args: False,
565
- )
290
+ checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
566
291
 
567
292
  result = update_checkpoint(
568
293
  str(checkpoint),
569
294
  "prizmkit-test",
570
295
  "completed",
571
- semantic_update={
572
- "stage_result": "TEST_PASS",
573
- "authoritative_evidence_paths": paths,
574
- },
296
+ semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
575
297
  )
576
298
 
577
299
  assert result["ok"] is False
578
- assert "validator-attested" in result["error"]
579
-
300
+ assert "requires TEST_PASS" in result["error"]
580
301
 
581
302
 
582
303
  def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
583
- payload = _family_checkpoint(
584
- "feature-pipeline",
585
- [
586
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
587
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
588
- ],
589
- )
304
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
590
305
  payload["steps"][1]["status"] = "skipped"
591
306
  payload["steps"][1].pop("stage_result", None)
592
307
  payload["steps"][2]["status"] = "pending"
@@ -602,13 +317,7 @@ def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
602
317
 
603
318
 
604
319
  def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
605
- payload = _family_checkpoint(
606
- "feature-pipeline",
607
- [
608
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
609
- "prizmkit-retrospective", "prizmkit-committer", "completion-summary",
610
- ],
611
- )
320
+ payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
612
321
  payload["steps"][2]["status"] = "skipped"
613
322
  payload["steps"][2].pop("stage_result", None)
614
323
  payload["steps"][3]["status"] = "pending"
@@ -621,3 +330,144 @@ def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
621
330
 
622
331
  assert result["ok"] is False
623
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"