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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +41 -121
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -6
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +1 -2
- package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +150 -0
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +21 -804
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +4 -6
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +4 -6
- package/bundled/dev-pipeline/scripts/prompt_framework.py +56 -55
- package/bundled/dev-pipeline/scripts/update-bug-status.py +1 -2
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +14 -122
- package/bundled/dev-pipeline/scripts/update-feature-status.py +1 -2
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +1 -2
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +3 -3
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +7 -18
- 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-test.md +13 -10
- package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +1 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +14 -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-specify-plan-full.md +3 -3
- 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/tests/test_checkpoint_state.py +282 -432
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +119 -684
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +17 -40
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +20 -23
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +29 -32
- package/bundled/skills/_metadata.json +6 -6
- package/bundled/skills/prizmkit/SKILL.md +8 -13
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +61 -80
- package/bundled/skills/prizmkit-code-review/SKILL.md +3 -3
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +61 -80
- package/bundled/skills/prizmkit-committer/SKILL.md +3 -3
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +61 -80
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +61 -80
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +61 -80
- package/bundled/skills/prizmkit-retrospective/SKILL.md +2 -2
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +61 -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 +15 -23
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +61 -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,11 @@ 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
|
+
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
|
|
146
|
+
def test_duplicate_skills_require_exact_step_id(tmp_path):
|
|
100
147
|
path = _write(tmp_path / "workflow-checkpoint.json", _checkpoint([
|
|
101
|
-
_step("S01", "
|
|
102
|
-
_step("S02", "
|
|
148
|
+
_step("S01", "prizmkit-committer", "First", "completed"),
|
|
149
|
+
_step("S02", "prizmkit-committer", "Second", "pending", "S01"),
|
|
103
150
|
]))
|
|
104
151
|
|
|
105
|
-
|
|
152
|
+
rejected = update_checkpoint(str(path), "prizmkit-committer", "completed")
|
|
153
|
+
accepted = update_checkpoint(str(path), "S02", "completed")
|
|
106
154
|
|
|
107
|
-
assert
|
|
108
|
-
assert
|
|
109
|
-
assert
|
|
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
|
|
113
|
-
path = _write(
|
|
114
|
-
|
|
115
|
-
|
|
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.
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
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
|
|
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
|
-
},
|
|
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 =
|
|
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.
|
|
239
|
-
assert state.semantic.terminal_status == "WORKFLOW_COMPLETED"
|
|
183
|
+
assert state.semantic.test_result_valid is True
|
|
240
184
|
|
|
241
185
|
|
|
242
|
-
def
|
|
243
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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
|
-
|
|
219
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
391
220
|
|
|
392
221
|
result = update_checkpoint(
|
|
393
|
-
str(
|
|
394
|
-
"prizmkit-
|
|
222
|
+
str(checkpoint),
|
|
223
|
+
"prizmkit-test",
|
|
395
224
|
"failed",
|
|
396
|
-
semantic_update={"stage_result": "
|
|
225
|
+
semantic_update={"stage_result": "TEST_NEEDS_FIXES"},
|
|
397
226
|
)
|
|
398
|
-
|
|
227
|
+
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
399
228
|
|
|
400
229
|
assert result["ok"] is True
|
|
401
|
-
assert
|
|
402
|
-
assert
|
|
403
|
-
assert
|
|
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
|
-
|
|
469
|
-
|
|
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 / "
|
|
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
|
-
|
|
498
|
-
assert
|
|
499
|
-
assert
|
|
500
|
-
assert
|
|
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
|
|
505
|
-
|
|
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(
|
|
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
|
-
"
|
|
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
|
-
|
|
538
|
-
assert persisted["steps"][2]["
|
|
539
|
-
assert persisted["
|
|
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
|
|
544
|
-
|
|
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 "
|
|
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"
|