prizmkit 1.1.140 → 1.1.142
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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/README.md +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +165 -125
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +13 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -6
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +5 -3
- package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +160 -0
- package/bundled/dev-pipeline/scripts/check-session-status.py +11 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +23 -806
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +6 -8
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +6 -8
- package/bundled/dev-pipeline/scripts/prompt_framework.py +68 -56
- package/bundled/dev-pipeline/scripts/update-bug-status.py +21 -2
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +52 -131
- package/bundled/dev-pipeline/scripts/update-feature-status.py +21 -2
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +21 -2
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +8 -19
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +6 -6
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +5 -5
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +13 -10
- package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +1 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +18 -9
- package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +1 -1
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +14 -26
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +13 -10
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +494 -413
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +144 -684
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +20 -41
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +24 -24
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +176 -36
- package/bundled/skills/_metadata.json +6 -6
- package/bundled/skills/prizmkit/SKILL.md +13 -18
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +73 -80
- package/bundled/skills/prizmkit-code-review/SKILL.md +7 -7
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +73 -80
- package/bundled/skills/prizmkit-committer/SKILL.md +8 -7
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +73 -80
- package/bundled/skills/prizmkit-implement/SKILL.md +3 -2
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +73 -80
- package/bundled/skills/prizmkit-plan/SKILL.md +2 -1
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +73 -80
- package/bundled/skills/prizmkit-retrospective/SKILL.md +8 -6
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +73 -80
- package/bundled/skills/prizmkit-test/SKILL.md +217 -189
- package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +119 -0
- package/bundled/skills/prizmkit-test/references/independent-test-review.md +269 -0
- package/bundled/skills/prizmkit-test/references/test-coverage-model.md +193 -0
- package/bundled/skills/prizmkit-test/references/test-report-template.md +164 -79
- package/bundled/skills/prizmkit-workflow/SKILL.md +28 -36
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +73 -80
- package/package.json +1 -1
- package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +0 -477
- package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +0 -116
- package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +0 -111
- package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +0 -66
- package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +0 -104
- package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +0 -65
- package/bundled/skills/prizmkit-test/references/evidence-protocol.md +0 -208
- package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +0 -80
- package/bundled/skills/prizmkit-test/references/examples.md +0 -100
- package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +0 -228
- package/bundled/skills/prizmkit-test/references/test-generation-steps.md +0 -118
- package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +0 -107
- package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +0 -181
- package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +0 -1930
- package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +0 -1847
|
@@ -4,7 +4,14 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
6
|
|
|
7
|
-
from prizmkit_runtime.checkpoint_state import
|
|
7
|
+
from prizmkit_runtime.checkpoint_state import (
|
|
8
|
+
VALID_CHECKPOINT_STATUSES,
|
|
9
|
+
VALID_STAGE_RESULTS,
|
|
10
|
+
expected_status_for_stage_result,
|
|
11
|
+
load_checkpoint_state,
|
|
12
|
+
read_checkpoint_cursor,
|
|
13
|
+
validate_checkpoint_data,
|
|
14
|
+
)
|
|
8
15
|
from update_checkpoint import update_checkpoint
|
|
9
16
|
|
|
10
17
|
|
|
@@ -24,10 +31,74 @@ def _step(step_id, skill="skill", name="Step", status="pending", depends_on=None
|
|
|
24
31
|
|
|
25
32
|
|
|
26
33
|
def _write(path, data):
|
|
34
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
27
35
|
path.write_text(json.dumps(data), encoding="utf-8")
|
|
28
36
|
return path
|
|
29
37
|
|
|
30
38
|
|
|
39
|
+
def _write_test_artifacts(root, slug="feature", *, result="TEST_PASS"):
|
|
40
|
+
artifact_dir = root / ".prizmkit" / "specs" / slug
|
|
41
|
+
artifact_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
(artifact_dir / "test-report.md").write_text(
|
|
43
|
+
f"# Test Report\n\n## Final Result\n\n- Result: {result}\n",
|
|
44
|
+
encoding="utf-8",
|
|
45
|
+
)
|
|
46
|
+
projection = {
|
|
47
|
+
"schema_version": 1,
|
|
48
|
+
"result": result,
|
|
49
|
+
"report": "test-report.md",
|
|
50
|
+
"main_review_rounds": 1,
|
|
51
|
+
"independent_review": {
|
|
52
|
+
"status": "downgraded",
|
|
53
|
+
"responses": 0,
|
|
54
|
+
"downgrade_reason": "read-only continuation capability unavailable",
|
|
55
|
+
"final_state_rechecked": False,
|
|
56
|
+
},
|
|
57
|
+
"repair_rounds": 0,
|
|
58
|
+
"production_changed": False,
|
|
59
|
+
"review_required": False,
|
|
60
|
+
"review_scope": None,
|
|
61
|
+
"unresolved_items": [],
|
|
62
|
+
}
|
|
63
|
+
(artifact_dir / "test-result.json").write_text(
|
|
64
|
+
json.dumps(projection), encoding="utf-8"
|
|
65
|
+
)
|
|
66
|
+
return artifact_dir
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def _family_checkpoint(root=None, *, semantic=None, terminal=True):
|
|
70
|
+
skills = [
|
|
71
|
+
"prizmkit-implement",
|
|
72
|
+
"prizmkit-code-review",
|
|
73
|
+
"prizmkit-test",
|
|
74
|
+
"prizmkit-retrospective",
|
|
75
|
+
"prizmkit-committer",
|
|
76
|
+
"completion-summary",
|
|
77
|
+
]
|
|
78
|
+
steps = []
|
|
79
|
+
previous = None
|
|
80
|
+
for index, skill in enumerate(skills, start=1):
|
|
81
|
+
step = _step(f"S{index:02d}", skill, skill, "completed", previous)
|
|
82
|
+
if skill == "prizmkit-code-review":
|
|
83
|
+
step["stage_result"] = "REVIEW_PASS"
|
|
84
|
+
if skill == "prizmkit-test":
|
|
85
|
+
step["stage_result"] = "TEST_PASS"
|
|
86
|
+
step["required_artifacts"] = [
|
|
87
|
+
".prizmkit/specs/feature/test-report.md",
|
|
88
|
+
".prizmkit/specs/feature/test-result.json",
|
|
89
|
+
]
|
|
90
|
+
steps.append(step)
|
|
91
|
+
previous = step["id"]
|
|
92
|
+
if root is not None:
|
|
93
|
+
_write_test_artifacts(root)
|
|
94
|
+
return {
|
|
95
|
+
"version": 1,
|
|
96
|
+
"workflow_type": "feature-pipeline",
|
|
97
|
+
"steps": steps,
|
|
98
|
+
"feature_state": semantic or ({"terminal_status": "WORKFLOW_COMPLETED"} if terminal else {}),
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
31
102
|
def test_valid_checkpoint_current_step_uses_first_non_terminal(tmp_path):
|
|
32
103
|
path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
|
|
33
104
|
_step("S01", "plan", "Plan", "completed"),
|
|
@@ -44,36 +115,17 @@ def test_valid_checkpoint_current_step_uses_first_non_terminal(tmp_path):
|
|
|
44
115
|
assert read_checkpoint_cursor(path) == 2
|
|
45
116
|
|
|
46
117
|
|
|
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
118
|
def test_missing_and_malformed_checkpoints_are_invalid(tmp_path):
|
|
64
119
|
missing = load_checkpoint_state(tmp_path / "missing.json")
|
|
65
120
|
assert missing.valid is False
|
|
66
121
|
assert missing.exists is False
|
|
67
122
|
assert missing.error_code == "missing"
|
|
68
|
-
assert missing.current_step_name == ""
|
|
69
123
|
|
|
70
124
|
malformed_path = tmp_path / "bad.json"
|
|
71
125
|
malformed_path.write_text("{not-json", encoding="utf-8")
|
|
72
126
|
malformed = load_checkpoint_state(malformed_path)
|
|
73
127
|
assert malformed.valid is False
|
|
74
|
-
assert malformed.exists is True
|
|
75
128
|
assert malformed.error_code == "malformed_json"
|
|
76
|
-
assert malformed.current_step_name == ""
|
|
77
129
|
|
|
78
130
|
|
|
79
131
|
def test_structural_validation_error_codes(tmp_path):
|
|
@@ -83,7 +135,6 @@ def test_structural_validation_error_codes(tmp_path):
|
|
|
83
135
|
("duplicate_step_id", _checkpoint([_step("S01"), _step("S01")])),
|
|
84
136
|
("invalid_status", _checkpoint([_step("S01", status="done")])),
|
|
85
137
|
("invalid_depends_on", _checkpoint([_step("S01", depends_on="S99")])),
|
|
86
|
-
("invalid_depends_on", _checkpoint([_step("S01", depends_on="S02"), _step("S02")])),
|
|
87
138
|
("malformed_step_id", _checkpoint([_step("one")])),
|
|
88
139
|
("invalid_step", {"steps": ["not-object"]}),
|
|
89
140
|
]
|
|
@@ -92,183 +143,230 @@ def test_structural_validation_error_codes(tmp_path):
|
|
|
92
143
|
state = load_checkpoint_state(path)
|
|
93
144
|
assert state.valid is False
|
|
94
145
|
assert state.error_code == expected_code
|
|
95
|
-
assert state.current_step_name == ""
|
|
96
146
|
assert read_checkpoint_cursor(path) is None
|
|
97
147
|
|
|
98
148
|
|
|
99
|
-
def
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
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
|
+
}
|
|
104
167
|
|
|
105
|
-
|
|
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"
|
|
106
187
|
|
|
107
|
-
assert state.valid is True
|
|
108
|
-
assert state.current_step_name == "Review"
|
|
109
|
-
assert state.cursor == 2
|
|
110
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"
|
|
111
192
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
]))
|
|
193
|
+
state = validate_checkpoint_data(
|
|
194
|
+
_checkpoint([step]),
|
|
195
|
+
path=tmp_path / "mismatch.json",
|
|
196
|
+
)
|
|
117
197
|
|
|
118
|
-
state
|
|
198
|
+
assert state.valid is False
|
|
199
|
+
assert state.error_code == "stage_result_status_mismatch"
|
|
119
200
|
|
|
120
|
-
assert state.valid is True
|
|
121
|
-
assert state.duplicate_skills == {"prizmkit-committer": ("S01", "S02")}
|
|
122
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"
|
|
123
206
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
]))
|
|
129
|
-
before = path.read_text(encoding="utf-8")
|
|
207
|
+
state = validate_checkpoint_data(
|
|
208
|
+
_checkpoint([step]),
|
|
209
|
+
path=tmp_path / f"{skill}-owner-mismatch.json",
|
|
210
|
+
)
|
|
130
211
|
|
|
131
|
-
|
|
212
|
+
assert state.valid is False
|
|
213
|
+
assert state.error_code == "stage_result_owner_mismatch"
|
|
132
214
|
|
|
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
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
|
+
]
|
|
138
223
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
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
|
+
)
|
|
144
229
|
|
|
145
|
-
|
|
146
|
-
|
|
230
|
+
assert state.valid is True
|
|
231
|
+
assert state.semantic.error_code == error_code
|
|
147
232
|
|
|
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
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)
|
|
153
252
|
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
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"))
|
|
159
259
|
|
|
160
|
-
result
|
|
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"]
|
|
161
268
|
|
|
162
|
-
assert result["ok"] is False
|
|
163
|
-
assert "invalid_status" in result["error"]
|
|
164
|
-
assert path.read_text(encoding="utf-8") == before
|
|
165
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
|
+
]
|
|
166
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
|
+
|
|
312
|
+
def test_duplicate_skills_require_exact_step_id(tmp_path):
|
|
313
|
+
path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
|
|
314
|
+
_step("S01", "prizmkit-committer", "First", "completed"),
|
|
315
|
+
_step("S02", "prizmkit-committer", "Second", "pending", "S01"),
|
|
316
|
+
]))
|
|
167
317
|
|
|
168
|
-
|
|
169
|
-
|
|
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
|
-
}
|
|
318
|
+
rejected = update_checkpoint(str(path), "prizmkit-committer", "completed")
|
|
319
|
+
accepted = update_checkpoint(str(path), "S02", "completed")
|
|
203
320
|
|
|
321
|
+
assert rejected["ok"] is False
|
|
322
|
+
assert "matches multiple checkpoint steps" in rejected["error"]
|
|
323
|
+
assert accepted["ok"] is True
|
|
204
324
|
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
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
|
-
},
|
|
325
|
+
|
|
326
|
+
def test_semantic_completion_requires_family_sequence_and_domain_results(tmp_path):
|
|
327
|
+
path = _write(
|
|
328
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
329
|
+
_family_checkpoint(tmp_path),
|
|
227
330
|
)
|
|
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)
|
|
232
331
|
|
|
233
|
-
state = load_checkpoint_state(
|
|
332
|
+
state = load_checkpoint_state(path, project_root=tmp_path)
|
|
234
333
|
|
|
235
334
|
assert state.valid is True
|
|
236
335
|
assert state.all_completed is True
|
|
237
336
|
assert state.semantic_complete is True
|
|
238
|
-
assert state.semantic.
|
|
337
|
+
assert state.semantic.test_result_valid is True
|
|
239
338
|
assert state.semantic.terminal_status == "WORKFLOW_COMPLETED"
|
|
240
339
|
|
|
241
340
|
|
|
242
|
-
def
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
341
|
+
def test_completion_does_not_require_retired_evidence_paths(tmp_path):
|
|
342
|
+
payload = _family_checkpoint(tmp_path)
|
|
343
|
+
assert "authoritative_evidence_paths" not in payload["steps"][2]
|
|
344
|
+
assert "authoritative_evidence_paths" not in payload["feature_state"]
|
|
345
|
+
|
|
346
|
+
state = validate_checkpoint_data(payload, project_root=tmp_path)
|
|
347
|
+
|
|
348
|
+
assert state.semantic_complete is True
|
|
349
|
+
assert state.semantic.test_result_valid is True
|
|
350
|
+
|
|
351
|
+
|
|
352
|
+
def test_completed_steps_without_review_or_test_results_do_not_complete(tmp_path):
|
|
353
|
+
payload = _family_checkpoint(tmp_path)
|
|
248
354
|
payload["steps"][1].pop("stage_result")
|
|
249
355
|
payload["steps"][2].pop("stage_result")
|
|
250
|
-
payload["feature_state"]["terminal_status"] = "WORKFLOW_COMPLETED"
|
|
251
356
|
|
|
252
357
|
state = validate_checkpoint_data(payload, project_root=tmp_path)
|
|
253
358
|
|
|
254
|
-
assert state.valid is True
|
|
255
|
-
assert state.all_completed is True
|
|
256
359
|
assert state.semantic_complete is False
|
|
257
360
|
assert state.semantic.error_code == "incomplete_semantic_evidence"
|
|
258
361
|
|
|
259
362
|
|
|
260
363
|
def test_family_sequence_mismatch_cannot_complete(tmp_path):
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
]
|
|
266
|
-
payload =
|
|
267
|
-
"feature-pipeline",
|
|
268
|
-
skills,
|
|
269
|
-
semantic={"authoritative_evidence_paths": paths, "terminal_status": "WORKFLOW_COMPLETED"},
|
|
270
|
-
)
|
|
271
|
-
payload["steps"][1]["authoritative_evidence_paths"] = paths
|
|
364
|
+
payload = _family_checkpoint(tmp_path)
|
|
365
|
+
payload["steps"][1], payload["steps"][2] = payload["steps"][2], payload["steps"][1]
|
|
366
|
+
payload["steps"][1]["id"] = "S02"
|
|
367
|
+
payload["steps"][2]["id"] = "S03"
|
|
368
|
+
payload["steps"][1]["depends_on"] = "S01"
|
|
369
|
+
payload["steps"][2]["depends_on"] = "S02"
|
|
272
370
|
|
|
273
371
|
state = validate_checkpoint_data(payload, project_root=tmp_path)
|
|
274
372
|
|
|
@@ -277,157 +375,139 @@ def test_family_sequence_mismatch_cannot_complete(tmp_path):
|
|
|
277
375
|
assert state.semantic.error_code == "invalid_stage_sequence"
|
|
278
376
|
|
|
279
377
|
|
|
280
|
-
def
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
378
|
+
def test_test_needs_fixes_is_valid_domain_result_and_blocks_l4(tmp_path):
|
|
379
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
380
|
+
payload["steps"][2]["status"] = "in_progress"
|
|
381
|
+
payload["steps"][2].pop("stage_result")
|
|
382
|
+
for step in payload["steps"][3:]:
|
|
383
|
+
step["status"] = "pending"
|
|
384
|
+
step.pop("stage_result", None)
|
|
385
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
386
|
+
|
|
387
|
+
result = update_checkpoint(
|
|
388
|
+
str(checkpoint),
|
|
389
|
+
"prizmkit-test",
|
|
390
|
+
"failed",
|
|
391
|
+
semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
|
|
290
392
|
)
|
|
291
|
-
|
|
393
|
+
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
292
394
|
|
|
293
|
-
|
|
395
|
+
assert result["ok"] is True
|
|
396
|
+
assert persisted["steps"][2]["stage_result"] == "TEST_NEEDS_FIXES"
|
|
397
|
+
assert persisted["feature_state"]["stage_result"] == "TEST_NEEDS_FIXES"
|
|
398
|
+
assert persisted["feature_state"]["blocked_reason"] == "test_needs_fixes"
|
|
399
|
+
assert persisted["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"
|
|
400
|
+
assert "authoritative_evidence_paths" not in persisted["steps"][2]
|
|
294
401
|
|
|
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
402
|
|
|
403
|
+
def test_test_blocked_preserves_l1_result_without_evidence_discovery(tmp_path):
|
|
404
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
405
|
+
payload["steps"][2]["status"] = "in_progress"
|
|
406
|
+
payload["steps"][2].pop("stage_result")
|
|
407
|
+
for step in payload["steps"][3:]:
|
|
408
|
+
step["status"] = "pending"
|
|
409
|
+
step.pop("stage_result", None)
|
|
410
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
299
411
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
"
|
|
303
|
-
|
|
304
|
-
|
|
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
|
-
},
|
|
412
|
+
result = update_checkpoint(
|
|
413
|
+
str(checkpoint),
|
|
414
|
+
"prizmkit-test",
|
|
415
|
+
"failed",
|
|
416
|
+
semantic_update={"stage_result": "TEST_BLOCKED", "blocked_reason": "safe environment unavailable"},
|
|
313
417
|
)
|
|
314
|
-
|
|
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"
|
|
418
|
+
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
322
419
|
|
|
420
|
+
assert result["ok"] is True
|
|
421
|
+
assert persisted["steps"][2]["stage_result"] == "TEST_BLOCKED"
|
|
422
|
+
assert persisted["feature_state"]["stage_result"] == "TEST_BLOCKED"
|
|
423
|
+
assert persisted["feature_state"]["blocked_reason"] == "test_blocked"
|
|
424
|
+
assert "authoritative_evidence_paths" not in persisted["feature_state"]
|
|
323
425
|
|
|
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
426
|
|
|
330
|
-
|
|
427
|
+
def test_update_checkpoint_completes_test_pass_without_validator(tmp_path):
|
|
428
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
429
|
+
payload["steps"][2]["status"] = "in_progress"
|
|
430
|
+
payload["steps"][2].pop("stage_result")
|
|
431
|
+
for step in payload["steps"][3:]:
|
|
432
|
+
step["status"] = "pending"
|
|
433
|
+
step.pop("stage_result", None)
|
|
434
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
331
435
|
|
|
332
|
-
|
|
333
|
-
|
|
436
|
+
result = update_checkpoint(
|
|
437
|
+
str(checkpoint),
|
|
438
|
+
"prizmkit-test",
|
|
439
|
+
"completed",
|
|
440
|
+
semantic_update={"stage_result": "TEST_PASS"},
|
|
441
|
+
)
|
|
334
442
|
|
|
443
|
+
assert result["ok"] is True
|
|
444
|
+
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
445
|
+
assert persisted["steps"][2]["status"] == "completed"
|
|
446
|
+
assert persisted["steps"][2]["stage_result"] == "TEST_PASS"
|
|
335
447
|
|
|
336
448
|
|
|
337
|
-
def
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
]
|
|
449
|
+
def test_update_checkpoint_rejects_test_completion_for_non_pass(tmp_path):
|
|
450
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
451
|
+
payload["steps"][2]["status"] = "in_progress"
|
|
452
|
+
payload["steps"][2].pop("stage_result")
|
|
453
|
+
for step in payload["steps"][3:]:
|
|
454
|
+
step["status"] = "pending"
|
|
455
|
+
step.pop("stage_result", None)
|
|
456
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
342
457
|
|
|
343
|
-
result = update_checkpoint(
|
|
458
|
+
result = update_checkpoint(
|
|
459
|
+
str(checkpoint),
|
|
460
|
+
"prizmkit-test",
|
|
461
|
+
"completed",
|
|
462
|
+
semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
|
|
463
|
+
)
|
|
344
464
|
|
|
345
465
|
assert result["ok"] is False
|
|
346
|
-
assert "
|
|
466
|
+
assert "requires TEST_PASS" in result["error"]
|
|
347
467
|
|
|
348
468
|
|
|
349
|
-
def
|
|
350
|
-
payload = _family_checkpoint(
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
)
|
|
357
|
-
payload["steps"][1]["status"] = "in_progress"
|
|
358
|
-
for step in payload["steps"][2:]:
|
|
469
|
+
def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
|
|
470
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
471
|
+
payload["steps"][1]["status"] = "skipped"
|
|
472
|
+
payload["steps"][1].pop("stage_result", None)
|
|
473
|
+
payload["steps"][2]["status"] = "pending"
|
|
474
|
+
payload["steps"][2].pop("stage_result", None)
|
|
475
|
+
for step in payload["steps"][3:]:
|
|
359
476
|
step["status"] = "pending"
|
|
360
477
|
step.pop("stage_result", None)
|
|
361
|
-
|
|
478
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
362
479
|
|
|
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"))
|
|
480
|
+
result = update_checkpoint(str(checkpoint), "prizmkit-test", "in_progress")
|
|
370
481
|
|
|
371
|
-
assert result["ok"] is
|
|
372
|
-
assert
|
|
373
|
-
assert data["feature_state"]["blocked_reason"] == "unsafe_repair_scope"
|
|
374
|
-
assert "retry_count" not in data["feature_state"]
|
|
482
|
+
assert result["ok"] is False
|
|
483
|
+
assert "REVIEW_PASS" in result["error"]
|
|
375
484
|
|
|
376
485
|
|
|
377
|
-
def
|
|
378
|
-
payload = _family_checkpoint(
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
],
|
|
384
|
-
semantic={"repair_scope": "production", "repair_round": 3},
|
|
385
|
-
)
|
|
386
|
-
payload["steps"][1]["status"] = "in_progress"
|
|
387
|
-
for step in payload["steps"][2:]:
|
|
486
|
+
def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
|
|
487
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
488
|
+
payload["steps"][2]["status"] = "skipped"
|
|
489
|
+
payload["steps"][2].pop("stage_result", None)
|
|
490
|
+
payload["steps"][3]["status"] = "pending"
|
|
491
|
+
for step in payload["steps"][4:]:
|
|
388
492
|
step["status"] = "pending"
|
|
389
493
|
step.pop("stage_result", None)
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
result = update_checkpoint(
|
|
393
|
-
str(path),
|
|
394
|
-
"prizmkit-code-review",
|
|
395
|
-
"failed",
|
|
396
|
-
semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_scope": "production"},
|
|
397
|
-
)
|
|
398
|
-
data = json.loads(path.read_text(encoding="utf-8"))
|
|
494
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
399
495
|
|
|
400
|
-
|
|
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"
|
|
496
|
+
result = update_checkpoint(str(checkpoint), "prizmkit-retrospective", "in_progress")
|
|
404
497
|
|
|
498
|
+
assert result["ok"] is False
|
|
499
|
+
assert "TEST_PASS" in result["error"]
|
|
405
500
|
|
|
406
501
|
|
|
407
|
-
def
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
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",
|
|
502
|
+
def test_declared_final_artifact_must_exist_for_completion(tmp_path):
|
|
503
|
+
payload = _family_checkpoint(tmp_path)
|
|
504
|
+
payload["steps"][-1]["required_artifacts"] = [
|
|
505
|
+
".prizmkit/specs/feature/completion-summary.json"
|
|
420
506
|
]
|
|
421
|
-
|
|
422
|
-
"feature-
|
|
423
|
-
|
|
424
|
-
semantic={"authoritative_evidence_paths": paths, "terminal_status": "WORKFLOW_COMPLETED"},
|
|
507
|
+
checkpoint = _write(
|
|
508
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
509
|
+
payload,
|
|
425
510
|
)
|
|
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
511
|
|
|
432
512
|
missing = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
433
513
|
(checkpoint.parent / "completion-summary.json").write_text("{}", encoding="utf-8")
|
|
@@ -439,185 +519,186 @@ def test_declared_final_artifact_must_exist_for_semantic_completion(tmp_path, mo
|
|
|
439
519
|
assert present.semantic.artifacts_valid is True
|
|
440
520
|
|
|
441
521
|
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
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",
|
|
522
|
+
def test_completion_rejects_invalid_test_result_projection(tmp_path):
|
|
523
|
+
payload = _family_checkpoint(tmp_path)
|
|
524
|
+
checkpoint = _write(
|
|
525
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
526
|
+
payload,
|
|
451
527
|
)
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
528
|
+
result_path = checkpoint.parent / "test-result.json"
|
|
529
|
+
result = json.loads(result_path.read_text(encoding="utf-8"))
|
|
530
|
+
result["main_review_rounds"] = 0
|
|
531
|
+
result_path.write_text(json.dumps(result), encoding="utf-8")
|
|
532
|
+
|
|
533
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
534
|
+
|
|
535
|
+
assert state.semantic_complete is False
|
|
536
|
+
assert state.semantic.artifacts_valid is False
|
|
537
|
+
|
|
538
|
+
|
|
539
|
+
def test_completion_rejects_inconsistent_independent_review_metadata(tmp_path):
|
|
540
|
+
payload = _family_checkpoint(tmp_path)
|
|
541
|
+
checkpoint = _write(
|
|
542
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
543
|
+
payload,
|
|
455
544
|
)
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
545
|
+
result_path = checkpoint.parent / "test-result.json"
|
|
546
|
+
result = json.loads(result_path.read_text(encoding="utf-8"))
|
|
547
|
+
result["independent_review"] = {
|
|
548
|
+
"status": "completed",
|
|
549
|
+
"responses": 0,
|
|
550
|
+
"downgrade_reason": None,
|
|
551
|
+
"final_state_rechecked": True,
|
|
552
|
+
}
|
|
553
|
+
result_path.write_text(json.dumps(result), encoding="utf-8")
|
|
554
|
+
|
|
555
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
556
|
+
|
|
557
|
+
assert state.semantic_complete is False
|
|
558
|
+
assert state.semantic.artifacts_valid is False
|
|
559
|
+
|
|
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,
|
|
459
566
|
)
|
|
460
|
-
(
|
|
461
|
-
|
|
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",
|
|
462
570
|
encoding="utf-8",
|
|
463
571
|
)
|
|
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
572
|
|
|
468
|
-
|
|
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"
|
|
478
|
-
payload["steps"][2]["status"] = "in_progress"
|
|
479
|
-
payload["steps"][2].pop("stage_result")
|
|
480
|
-
for step in payload["steps"][3:]:
|
|
481
|
-
step["status"] = "pending"
|
|
482
|
-
step.pop("stage_result", None)
|
|
483
|
-
checkpoint = _write(tmp_path / ".prizmkit" / "specs" / "feature" / "workflow-checkpoint.json", payload)
|
|
573
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
484
574
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
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",
|
|
494
589
|
)
|
|
495
590
|
|
|
496
|
-
|
|
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"
|
|
591
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
501
592
|
|
|
593
|
+
assert state.semantic_complete is False
|
|
594
|
+
assert state.semantic.artifacts_valid is False
|
|
502
595
|
|
|
503
596
|
|
|
504
|
-
def
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
[
|
|
510
|
-
"prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
|
|
511
|
-
"prizmkit-retrospective", "prizmkit-committer", "completion-summary",
|
|
512
|
-
],
|
|
513
|
-
semantic={"artifact_dir": ".prizmkit/specs/feature"},
|
|
597
|
+
def test_completion_rejects_mismatched_test_report_result(tmp_path):
|
|
598
|
+
payload = _family_checkpoint(tmp_path)
|
|
599
|
+
checkpoint = _write(
|
|
600
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
601
|
+
payload,
|
|
514
602
|
)
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
payload["steps"][2].pop("stage_result")
|
|
519
|
-
for step in payload["steps"][3:]:
|
|
520
|
-
step["status"] = "pending"
|
|
521
|
-
step.pop("stage_result", None)
|
|
522
|
-
checkpoint = _write(artifact / "workflow-checkpoint.json", payload)
|
|
523
|
-
|
|
524
|
-
result = update_checkpoint(
|
|
525
|
-
str(checkpoint),
|
|
526
|
-
"prizmkit-test",
|
|
527
|
-
"failed",
|
|
528
|
-
semantic_update={
|
|
529
|
-
"stage_result": "TEST_BLOCKED",
|
|
530
|
-
"blocked_reason": "test_blocked",
|
|
531
|
-
"terminal_status": "WORKFLOW_BLOCKED",
|
|
532
|
-
},
|
|
603
|
+
(checkpoint.parent / "test-report.md").write_text(
|
|
604
|
+
"# Test Report\n\n## Final Result\n\n- Result: TEST_BLOCKED\n",
|
|
605
|
+
encoding="utf-8",
|
|
533
606
|
)
|
|
534
607
|
|
|
535
|
-
|
|
536
|
-
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]
|
|
608
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
541
609
|
|
|
610
|
+
assert state.semantic_complete is False
|
|
611
|
+
assert state.semantic.artifacts_valid is False
|
|
542
612
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
payload = _family_checkpoint(
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
"prizmkit-retrospective", "prizmkit-committer", "completion-summary",
|
|
550
|
-
],
|
|
551
|
-
)
|
|
552
|
-
payload["steps"][2]["status"] = "in_progress"
|
|
553
|
-
for step in payload["steps"][3:]:
|
|
554
|
-
step["status"] = "pending"
|
|
555
|
-
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",
|
|
613
|
+
|
|
614
|
+
def test_completion_rejects_report_with_result_only_outside_final_section(tmp_path):
|
|
615
|
+
payload = _family_checkpoint(tmp_path)
|
|
616
|
+
checkpoint = _write(
|
|
617
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
618
|
+
payload,
|
|
561
619
|
)
|
|
562
|
-
|
|
563
|
-
"
|
|
564
|
-
|
|
620
|
+
(checkpoint.parent / "test-report.md").write_text(
|
|
621
|
+
"# Test Report\n\n## Final Result\n\n- Result: TEST_BLOCKED\n\n"
|
|
622
|
+
"## Remaining Risks and Unresolved Items\n\n- Expected eventual result: TEST_PASS\n",
|
|
623
|
+
encoding="utf-8",
|
|
565
624
|
)
|
|
566
625
|
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
626
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
627
|
+
|
|
628
|
+
assert state.semantic_complete is False
|
|
629
|
+
assert state.semantic.artifacts_valid is False
|
|
630
|
+
|
|
631
|
+
|
|
632
|
+
def test_completion_rejects_test_artifacts_outside_project(tmp_path):
|
|
633
|
+
outside = tmp_path.parent / f"{tmp_path.name}-outside"
|
|
634
|
+
outside.mkdir()
|
|
635
|
+
_write_test_artifacts(outside)
|
|
636
|
+
payload = _family_checkpoint(tmp_path)
|
|
637
|
+
test_step = payload["steps"][2]
|
|
638
|
+
test_step["required_artifacts"] = [
|
|
639
|
+
f"../{outside.name}/.prizmkit/specs/feature/test-report.md",
|
|
640
|
+
f"../{outside.name}/.prizmkit/specs/feature/test-result.json",
|
|
641
|
+
]
|
|
642
|
+
checkpoint = _write(
|
|
643
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json",
|
|
644
|
+
payload,
|
|
575
645
|
)
|
|
576
646
|
|
|
577
|
-
|
|
578
|
-
assert "validator-attested" in result["error"]
|
|
647
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
579
648
|
|
|
649
|
+
assert state.semantic_complete is False
|
|
650
|
+
assert state.semantic.artifacts_valid is False
|
|
580
651
|
|
|
581
652
|
|
|
582
|
-
def
|
|
653
|
+
def test_review_needs_fixes_preserves_implementation_repair_route(tmp_path):
|
|
583
654
|
payload = _family_checkpoint(
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
"prizmkit-retrospective", "prizmkit-committer", "completion-summary",
|
|
588
|
-
],
|
|
655
|
+
tmp_path,
|
|
656
|
+
semantic={"repair_scope": "production", "repair_round": 0},
|
|
657
|
+
terminal=False,
|
|
589
658
|
)
|
|
590
|
-
payload["steps"][1]["status"] = "
|
|
591
|
-
payload["steps"][1].pop("stage_result"
|
|
592
|
-
payload["steps"][2]
|
|
593
|
-
for step in payload["steps"][3:]:
|
|
659
|
+
payload["steps"][1]["status"] = "in_progress"
|
|
660
|
+
payload["steps"][1].pop("stage_result")
|
|
661
|
+
for step in payload["steps"][2:]:
|
|
594
662
|
step["status"] = "pending"
|
|
595
663
|
step.pop("stage_result", None)
|
|
596
664
|
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
597
665
|
|
|
598
|
-
result = update_checkpoint(
|
|
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"))
|
|
599
673
|
|
|
600
|
-
assert result["ok"] is
|
|
601
|
-
assert "
|
|
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"
|
|
602
679
|
|
|
603
680
|
|
|
604
|
-
def
|
|
681
|
+
def test_review_outer_repair_cap_remains_runtime_owned(tmp_path):
|
|
605
682
|
payload = _family_checkpoint(
|
|
606
|
-
"
|
|
607
|
-
|
|
608
|
-
"prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
|
|
609
|
-
"prizmkit-retrospective", "prizmkit-committer", "completion-summary",
|
|
610
|
-
],
|
|
683
|
+
tmp_path, semantic={"repair_scope": "production", "repair_round": 3},
|
|
684
|
+
terminal=False,
|
|
611
685
|
)
|
|
612
|
-
payload["steps"][
|
|
613
|
-
payload["steps"][
|
|
614
|
-
payload["steps"][
|
|
615
|
-
for step in payload["steps"][4:]:
|
|
686
|
+
payload["steps"][1]["status"] = "in_progress"
|
|
687
|
+
payload["steps"][1].pop("stage_result")
|
|
688
|
+
for step in payload["steps"][2:]:
|
|
616
689
|
step["status"] = "pending"
|
|
617
690
|
step.pop("stage_result", None)
|
|
618
691
|
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
619
692
|
|
|
620
|
-
result = update_checkpoint(
|
|
693
|
+
result = update_checkpoint(
|
|
694
|
+
str(checkpoint),
|
|
695
|
+
"prizmkit-code-review",
|
|
696
|
+
"failed",
|
|
697
|
+
semantic_update={"stage_result": "REVIEW_NEEDS_FIXES", "repair_scope": "production"},
|
|
698
|
+
)
|
|
699
|
+
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
621
700
|
|
|
622
|
-
assert result["ok"] is
|
|
623
|
-
assert "
|
|
701
|
+
assert result["ok"] is True
|
|
702
|
+
assert persisted["feature_state"]["repair_round"] == 3
|
|
703
|
+
assert persisted["feature_state"]["blocked_reason"] == "repair_round_limit"
|
|
704
|
+
assert persisted["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"
|