prizmkit 1.1.153 → 1.1.155
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/adapters/codex/skill-adapter.js +1 -1
- package/bundled/adapters/pi/skill-adapter.js +2 -1
- package/bundled/dev-pipeline/.env.example +2 -6
- package/bundled/dev-pipeline/README.md +120 -113
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +355 -15
- package/bundled/dev-pipeline/prizmkit_runtime/cli.py +189 -110
- package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
- package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +4 -20
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
- package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
- package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
- package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -10
- package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +139 -74
- package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
- package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
- package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
- package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
- package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
- package/bundled/dev-pipeline/scripts/update-bug-status.py +197 -71
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +77 -16
- package/bundled/dev-pipeline/scripts/update-feature-status.py +46 -121
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +46 -123
- package/bundled/dev-pipeline/scripts/utils.py +119 -0
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
- package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
- package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
- package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
- package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
- package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
- package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
- package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
- package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
- package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
- package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
- package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
- package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
- package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
- package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +617 -30
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -194
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +281 -206
- package/bundled/dev-pipeline/tests/test_recovery_workflow.py +322 -0
- package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
- package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -7
- package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +617 -188
- package/bundled/skills/_metadata.json +9 -9
- package/bundled/skills/app-planner/SKILL.md +3 -3
- package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
- package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
- package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
- package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
- package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
- package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
- package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
- package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
- package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
- package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
- package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
- package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
- package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
- package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
- package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
- package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
- package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
- package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
- package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
- package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
- package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
- package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
- package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
- package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
- package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
- package/bundled/skills/prizmkit/SKILL.md +17 -18
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
- package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
- package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
- package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
- package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
- package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
- package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
- package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
- package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
- package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
- package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
- package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
- package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
- package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
- package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
- package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
- package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
- package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
- package/bundled/skills/prizmkit-init/SKILL.md +4 -0
- package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
- package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
- package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
- package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
- package/bundled/skills/prizmkit-prizm-docs/SKILL.md +11 -1
- package/bundled/skills/prizmkit-retrospective/SKILL.md +63 -77
- package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -3
- package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +17 -23
- package/bundled/skills/prizmkit-test/SKILL.md +64 -51
- package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
- package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
- package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
- package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
- package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
- package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
- package/bundled/skills/recovery-workflow/SKILL.md +9 -6
- package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
- package/bundled/skills/recovery-workflow/references/detection.md +7 -6
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +16 -6
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
- package/bundled/templates/project-memory-template.md +19 -11
- package/package.json +1 -1
- package/src/scaffold.js +33 -22
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
- package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
- package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
- package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
- package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
- package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
- package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
- package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
- package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
- package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
- package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
- package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
- package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
- package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
- package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
- package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
- package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
|
@@ -3,6 +3,10 @@
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
5
5
|
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
|
|
9
|
+
import pytest
|
|
6
10
|
|
|
7
11
|
from prizmkit_runtime.checkpoint_state import (
|
|
8
12
|
VALID_CHECKPOINT_STATUSES,
|
|
@@ -12,6 +16,7 @@ from prizmkit_runtime.checkpoint_state import (
|
|
|
12
16
|
read_checkpoint_cursor,
|
|
13
17
|
validate_checkpoint_data,
|
|
14
18
|
)
|
|
19
|
+
from prizmkit_runtime.test_result import load_test_result
|
|
15
20
|
from update_checkpoint import update_checkpoint
|
|
16
21
|
|
|
17
22
|
|
|
@@ -56,8 +61,6 @@ def _write_test_artifacts(root, slug="feature", *, result="TEST_PASS"):
|
|
|
56
61
|
},
|
|
57
62
|
"repair_rounds": 0,
|
|
58
63
|
"production_changed": False,
|
|
59
|
-
"review_required": False,
|
|
60
|
-
"review_scope": None,
|
|
61
64
|
"unresolved_items": [],
|
|
62
65
|
}
|
|
63
66
|
(artifact_dir / "test-result.json").write_text(
|
|
@@ -71,14 +74,16 @@ def _family_checkpoint(root=None, *, semantic=None, terminal=True):
|
|
|
71
74
|
"prizmkit-implement",
|
|
72
75
|
"prizmkit-code-review",
|
|
73
76
|
"prizmkit-test",
|
|
77
|
+
"completion-summary",
|
|
74
78
|
"prizmkit-retrospective",
|
|
75
79
|
"prizmkit-committer",
|
|
76
|
-
"completion-summary",
|
|
77
80
|
]
|
|
78
81
|
steps = []
|
|
79
82
|
previous = None
|
|
80
83
|
for index, skill in enumerate(skills, start=1):
|
|
81
84
|
step = _step(f"S{index:02d}", skill, skill, "completed", previous)
|
|
85
|
+
if skill == "prizmkit-implement":
|
|
86
|
+
step["stage_result"] = "IMPLEMENTED"
|
|
82
87
|
if skill == "prizmkit-code-review":
|
|
83
88
|
step["stage_result"] = "REVIEW_PASS"
|
|
84
89
|
if skill == "prizmkit-test":
|
|
@@ -87,6 +92,10 @@ def _family_checkpoint(root=None, *, semantic=None, terminal=True):
|
|
|
87
92
|
".prizmkit/specs/feature/test-report.md",
|
|
88
93
|
".prizmkit/specs/feature/test-result.json",
|
|
89
94
|
]
|
|
95
|
+
if skill == "prizmkit-retrospective":
|
|
96
|
+
step["stage_result"] = "RETRO_COMPLETE"
|
|
97
|
+
if skill == "prizmkit-committer":
|
|
98
|
+
step["stage_result"] = "COMMITTED"
|
|
90
99
|
steps.append(step)
|
|
91
100
|
previous = step["id"]
|
|
92
101
|
if root is not None:
|
|
@@ -95,7 +104,14 @@ def _family_checkpoint(root=None, *, semantic=None, terminal=True):
|
|
|
95
104
|
"version": 1,
|
|
96
105
|
"workflow_type": "feature-pipeline",
|
|
97
106
|
"steps": steps,
|
|
98
|
-
"feature_state": semantic or (
|
|
107
|
+
"feature_state": semantic or (
|
|
108
|
+
{
|
|
109
|
+
"terminal_status": "WORKFLOW_COMPLETED",
|
|
110
|
+
"runtime_base_head": "a" * 40,
|
|
111
|
+
"runtime_commit_hash": "b" * 40,
|
|
112
|
+
}
|
|
113
|
+
if terminal else {}
|
|
114
|
+
),
|
|
99
115
|
}
|
|
100
116
|
|
|
101
117
|
|
|
@@ -199,6 +215,16 @@ def test_structural_validation_rejects_status_result_mismatch(tmp_path):
|
|
|
199
215
|
assert state.error_code == "stage_result_status_mismatch"
|
|
200
216
|
|
|
201
217
|
|
|
218
|
+
def test_family_planning_wrappers_own_plan_results(tmp_path):
|
|
219
|
+
for skill in ("context-snapshot-and-plan", "bug-diagnosis-and-plan"):
|
|
220
|
+
step = _step("S01", skill, "Plan", "completed")
|
|
221
|
+
step["stage_result"] = "PLAN_READY"
|
|
222
|
+
state = validate_checkpoint_data(
|
|
223
|
+
_checkpoint([step]), path=tmp_path / f"{skill}.json",
|
|
224
|
+
)
|
|
225
|
+
assert state.valid is True
|
|
226
|
+
|
|
227
|
+
|
|
202
228
|
def test_structural_validation_rejects_stage_result_owner_mismatch(tmp_path):
|
|
203
229
|
for skill in ("prizmkit-test", "custom-stage"):
|
|
204
230
|
step = _step("S01", skill, "Step", "completed")
|
|
@@ -231,6 +257,120 @@ def test_semantic_metadata_rejects_legacy_or_mismatched_status(tmp_path):
|
|
|
231
257
|
assert state.semantic.error_code == error_code
|
|
232
258
|
|
|
233
259
|
|
|
260
|
+
def test_semantic_blocker_metadata_requires_blocked_terminal_state(tmp_path):
|
|
261
|
+
cases = [
|
|
262
|
+
({"blocked_reason": ""}, "invalid_blocked_reason"),
|
|
263
|
+
({"blocked_reason": " "}, "invalid_blocked_reason"),
|
|
264
|
+
({"blocked_reason": "unexpected blocker"}, "blocked_reason_without_blocked_terminal"),
|
|
265
|
+
(
|
|
266
|
+
{"blocked_reason": "unexpected blocker", "terminal_status": "WORKFLOW_COMPLETED"},
|
|
267
|
+
"completed_workflow_has_blocked_reason",
|
|
268
|
+
),
|
|
269
|
+
({"terminal_status": "WORKFLOW_BLOCKED"}, "missing_blocked_reason"),
|
|
270
|
+
]
|
|
271
|
+
|
|
272
|
+
for index, (semantic, expected_error) in enumerate(cases):
|
|
273
|
+
payload = _family_checkpoint(tmp_path, semantic=semantic)
|
|
274
|
+
state = validate_checkpoint_data(
|
|
275
|
+
payload,
|
|
276
|
+
path=tmp_path / f"invalid-blocker-{index}.json",
|
|
277
|
+
project_root=tmp_path,
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
assert state.valid is True
|
|
281
|
+
assert state.semantic.error_code == expected_error
|
|
282
|
+
assert state.semantic_complete is False
|
|
283
|
+
|
|
284
|
+
|
|
285
|
+
def test_checkpoint_updater_rejects_invalid_blocker_metadata_without_mutation(tmp_path):
|
|
286
|
+
cases = [
|
|
287
|
+
({"blocked_reason": "", "terminal_status": "WORKFLOW_COMPLETED"}, True),
|
|
288
|
+
({"blocked_reason": " ", "terminal_status": "WORKFLOW_COMPLETED"}, True),
|
|
289
|
+
({"blocked_reason": "unexpected blocker", "terminal_status": "WORKFLOW_COMPLETED"}, True),
|
|
290
|
+
({"blocked_reason": "unexpected blocker"}, False),
|
|
291
|
+
({"terminal_status": "WORKFLOW_BLOCKED"}, False),
|
|
292
|
+
]
|
|
293
|
+
|
|
294
|
+
for index, (semantic_update, completed) in enumerate(cases):
|
|
295
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
296
|
+
if not completed:
|
|
297
|
+
payload["steps"][0]["status"] = "in_progress"
|
|
298
|
+
for step in payload["steps"][1:]:
|
|
299
|
+
step["status"] = "pending"
|
|
300
|
+
step.pop("stage_result", None)
|
|
301
|
+
checkpoint = _write(tmp_path / f"atomic-blocker-{index}.json", payload)
|
|
302
|
+
before = checkpoint.read_bytes()
|
|
303
|
+
|
|
304
|
+
result = update_checkpoint(
|
|
305
|
+
str(checkpoint),
|
|
306
|
+
"S06" if completed else "S01",
|
|
307
|
+
"completed" if completed else "in_progress",
|
|
308
|
+
semantic_update=semantic_update,
|
|
309
|
+
)
|
|
310
|
+
|
|
311
|
+
assert result["ok"] is False
|
|
312
|
+
assert checkpoint.read_bytes() == before
|
|
313
|
+
|
|
314
|
+
|
|
315
|
+
def test_checkpoint_updater_rejects_completed_terminal_for_blocking_stage_result(tmp_path):
|
|
316
|
+
for index, blocked_reason in enumerate((None, "safe environment unavailable")):
|
|
317
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
318
|
+
payload["steps"][2]["status"] = "in_progress"
|
|
319
|
+
payload["steps"][2].pop("stage_result", None)
|
|
320
|
+
for step in payload["steps"][3:]:
|
|
321
|
+
step["status"] = "pending"
|
|
322
|
+
step.pop("stage_result", None)
|
|
323
|
+
checkpoint = _write(tmp_path / f"conflicting-test-terminal-{index}.json", payload)
|
|
324
|
+
before = checkpoint.read_bytes()
|
|
325
|
+
semantic_update = {
|
|
326
|
+
"stage_result": "TEST_BLOCKED",
|
|
327
|
+
"terminal_status": "WORKFLOW_COMPLETED",
|
|
328
|
+
}
|
|
329
|
+
if blocked_reason is not None:
|
|
330
|
+
semantic_update["blocked_reason"] = blocked_reason
|
|
331
|
+
|
|
332
|
+
result = update_checkpoint(
|
|
333
|
+
str(checkpoint),
|
|
334
|
+
"prizmkit-test",
|
|
335
|
+
"failed",
|
|
336
|
+
semantic_update=semantic_update,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
assert result["ok"] is False
|
|
340
|
+
assert checkpoint.read_bytes() == before
|
|
341
|
+
|
|
342
|
+
|
|
343
|
+
def test_checkpoint_updater_cli_returns_nonzero_without_mutating_invalid_blocker_input(tmp_path):
|
|
344
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
345
|
+
checkpoint = _write(tmp_path / "cli-invalid-blocker.json", payload)
|
|
346
|
+
before = checkpoint.read_bytes()
|
|
347
|
+
script = __import__("update_checkpoint").__file__
|
|
348
|
+
|
|
349
|
+
result = subprocess.run(
|
|
350
|
+
[
|
|
351
|
+
sys.executable,
|
|
352
|
+
script,
|
|
353
|
+
"--checkpoint-path",
|
|
354
|
+
str(checkpoint),
|
|
355
|
+
"--step",
|
|
356
|
+
"S06",
|
|
357
|
+
"--status",
|
|
358
|
+
"completed",
|
|
359
|
+
"--terminal-status",
|
|
360
|
+
"WORKFLOW_COMPLETED",
|
|
361
|
+
"--blocked-reason",
|
|
362
|
+
"",
|
|
363
|
+
],
|
|
364
|
+
text=True,
|
|
365
|
+
capture_output=True,
|
|
366
|
+
check=False,
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
assert result.returncode == 1
|
|
370
|
+
assert json.loads(result.stdout)["ok"] is False
|
|
371
|
+
assert checkpoint.read_bytes() == before
|
|
372
|
+
|
|
373
|
+
|
|
234
374
|
def test_update_checkpoint_reenters_failed_stage_without_stale_result(tmp_path):
|
|
235
375
|
payload = _family_checkpoint(
|
|
236
376
|
tmp_path,
|
|
@@ -275,16 +415,40 @@ def test_checkpoint_updater_supports_every_owned_stage_result(tmp_path):
|
|
|
275
415
|
("prizmkit-test", "TEST_BLOCKED", "failed"),
|
|
276
416
|
("prizmkit-retrospective", "RETRO_COMPLETE", "completed"),
|
|
277
417
|
("prizmkit-committer", "COMMIT_PENDING", "in_progress"),
|
|
278
|
-
("prizmkit-committer", "COMMITTED", "completed"),
|
|
279
418
|
]
|
|
280
419
|
|
|
281
420
|
assert {stage_result for _skill, stage_result, _status in cases} <= VALID_STAGE_RESULTS
|
|
282
421
|
for index, (skill, stage_result, status) in enumerate(cases):
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
422
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
423
|
+
if skill == "prizmkit-plan":
|
|
424
|
+
for offset, step in enumerate(payload["steps"], start=2):
|
|
425
|
+
step["id"] = f"S{offset:02d}"
|
|
426
|
+
step["depends_on"] = f"S{offset - 1:02d}"
|
|
427
|
+
payload["steps"].insert(0, _step("S01", skill, skill, "pending"))
|
|
428
|
+
target_index = 0
|
|
429
|
+
else:
|
|
430
|
+
payload["steps"].insert(
|
|
431
|
+
0,
|
|
432
|
+
{
|
|
433
|
+
"id": "S00",
|
|
434
|
+
"skill": "context-snapshot-and-plan",
|
|
435
|
+
"name": "Plan",
|
|
436
|
+
"status": "completed",
|
|
437
|
+
"stage_result": "PLAN_READY",
|
|
438
|
+
"required_artifacts": [],
|
|
439
|
+
"depends_on": None,
|
|
440
|
+
},
|
|
441
|
+
)
|
|
442
|
+
payload["steps"][1]["depends_on"] = "S00"
|
|
443
|
+
target_index = next(
|
|
444
|
+
position
|
|
445
|
+
for position, step in enumerate(payload["steps"])
|
|
446
|
+
if step["skill"] == skill
|
|
447
|
+
)
|
|
448
|
+
for step in payload["steps"][target_index:]:
|
|
449
|
+
step["status"] = "pending"
|
|
450
|
+
step.pop("stage_result", None)
|
|
451
|
+
checkpoint = _write(tmp_path / f"owned-result-{index}.json", payload)
|
|
288
452
|
|
|
289
453
|
result = update_checkpoint(
|
|
290
454
|
str(checkpoint),
|
|
@@ -295,8 +459,9 @@ def test_checkpoint_updater_supports_every_owned_stage_result(tmp_path):
|
|
|
295
459
|
|
|
296
460
|
assert result["ok"] is True
|
|
297
461
|
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
298
|
-
|
|
299
|
-
assert
|
|
462
|
+
target = next(step for step in persisted["steps"] if step["skill"] == skill)
|
|
463
|
+
assert target["status"] == status
|
|
464
|
+
assert target["stage_result"] == stage_result
|
|
300
465
|
assert persisted["feature_state"]["stage"] == {
|
|
301
466
|
"prizmkit-plan": "plan",
|
|
302
467
|
"prizmkit-implement": "implement",
|
|
@@ -310,12 +475,19 @@ def test_checkpoint_updater_supports_every_owned_stage_result(tmp_path):
|
|
|
310
475
|
|
|
311
476
|
|
|
312
477
|
def test_duplicate_skills_require_exact_step_id(tmp_path):
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
478
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
479
|
+
for offset, step in enumerate(payload["steps"], start=3):
|
|
480
|
+
step["id"] = f"S{offset:02d}"
|
|
481
|
+
step["depends_on"] = f"S{offset - 1:02d}"
|
|
482
|
+
step["status"] = "pending"
|
|
483
|
+
step.pop("stage_result", None)
|
|
484
|
+
payload["steps"][:0] = [
|
|
485
|
+
_step("S01", "custom-stage", "First", "completed"),
|
|
486
|
+
_step("S02", "custom-stage", "Second", "pending", "S01"),
|
|
487
|
+
]
|
|
488
|
+
path = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
317
489
|
|
|
318
|
-
rejected = update_checkpoint(str(path), "
|
|
490
|
+
rejected = update_checkpoint(str(path), "custom-stage", "completed")
|
|
319
491
|
accepted = update_checkpoint(str(path), "S02", "completed")
|
|
320
492
|
|
|
321
493
|
assert rejected["ok"] is False
|
|
@@ -375,11 +547,26 @@ def test_family_sequence_mismatch_cannot_complete(tmp_path):
|
|
|
375
547
|
assert state.semantic.error_code == "invalid_stage_sequence"
|
|
376
548
|
|
|
377
549
|
|
|
378
|
-
def
|
|
550
|
+
def test_test_needs_fixes_routes_to_implementation_and_rewinds_stale_gates(tmp_path):
|
|
379
551
|
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
380
|
-
payload["steps"]
|
|
381
|
-
|
|
382
|
-
|
|
552
|
+
payload["steps"].insert(
|
|
553
|
+
0,
|
|
554
|
+
{
|
|
555
|
+
"id": "S00",
|
|
556
|
+
"skill": "context-snapshot-and-plan",
|
|
557
|
+
"name": "Plan",
|
|
558
|
+
"status": "completed",
|
|
559
|
+
"stage_result": "PLAN_READY",
|
|
560
|
+
"required_artifacts": [],
|
|
561
|
+
"depends_on": None,
|
|
562
|
+
},
|
|
563
|
+
)
|
|
564
|
+
payload["steps"][1]["depends_on"] = "S00"
|
|
565
|
+
test_step = next(step for step in payload["steps"] if step["skill"] == "prizmkit-test")
|
|
566
|
+
test_index = payload["steps"].index(test_step)
|
|
567
|
+
test_step["status"] = "in_progress"
|
|
568
|
+
test_step.pop("stage_result")
|
|
569
|
+
for step in payload["steps"][test_index + 1:]:
|
|
383
570
|
step["status"] = "pending"
|
|
384
571
|
step.pop("stage_result", None)
|
|
385
572
|
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
@@ -388,16 +575,80 @@ def test_test_needs_fixes_is_valid_domain_result_and_blocks_l4(tmp_path):
|
|
|
388
575
|
str(checkpoint),
|
|
389
576
|
"prizmkit-test",
|
|
390
577
|
"failed",
|
|
391
|
-
semantic_update={
|
|
578
|
+
semantic_update={
|
|
579
|
+
"stage_result": "TEST_NEEDS_FIXES",
|
|
580
|
+
"repair_scope": "production",
|
|
581
|
+
},
|
|
392
582
|
)
|
|
393
|
-
|
|
583
|
+
routed = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
394
584
|
|
|
395
585
|
assert result["ok"] is True
|
|
396
|
-
assert
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
assert "
|
|
586
|
+
assert test_step["skill"] == "prizmkit-test"
|
|
587
|
+
persisted_test = next(
|
|
588
|
+
step for step in routed["steps"] if step["skill"] == "prizmkit-test"
|
|
589
|
+
)
|
|
590
|
+
assert persisted_test["stage_result"] == "TEST_NEEDS_FIXES"
|
|
591
|
+
assert routed["feature_state"]["stage_result"] == "TEST_NEEDS_FIXES"
|
|
592
|
+
assert routed["feature_state"]["next_stage"] == "prizmkit-implement"
|
|
593
|
+
assert routed["feature_state"]["repair_round"] == 1
|
|
594
|
+
|
|
595
|
+
restarted = update_checkpoint(
|
|
596
|
+
str(checkpoint), "prizmkit-implement", "in_progress"
|
|
597
|
+
)
|
|
598
|
+
rewound = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
599
|
+
|
|
600
|
+
assert restarted["ok"] is True
|
|
601
|
+
implementation = next(
|
|
602
|
+
step for step in rewound["steps"] if step["skill"] == "prizmkit-implement"
|
|
603
|
+
)
|
|
604
|
+
assert implementation["status"] == "in_progress"
|
|
605
|
+
for step in rewound["steps"][rewound["steps"].index(implementation) + 1:]:
|
|
606
|
+
assert step["status"] == "pending"
|
|
607
|
+
assert "stage_result" not in step
|
|
608
|
+
|
|
609
|
+
|
|
610
|
+
def test_terminal_atomic_blockers_become_workflow_blocked(tmp_path):
|
|
611
|
+
cases = [
|
|
612
|
+
("context-snapshot-and-plan", "PLAN_BLOCKED"),
|
|
613
|
+
("prizmkit-implement", "IMPLEMENT_BLOCKED"),
|
|
614
|
+
("prizmkit-retrospective", "RETRO_BLOCKED"),
|
|
615
|
+
("prizmkit-committer", "COMMIT_BLOCKED"),
|
|
616
|
+
]
|
|
617
|
+
for index, (skill, stage_result) in enumerate(cases):
|
|
618
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
619
|
+
if skill == "context-snapshot-and-plan":
|
|
620
|
+
payload["steps"].insert(
|
|
621
|
+
0,
|
|
622
|
+
{
|
|
623
|
+
"id": "S00",
|
|
624
|
+
"skill": skill,
|
|
625
|
+
"name": "Plan",
|
|
626
|
+
"status": "pending",
|
|
627
|
+
"required_artifacts": [],
|
|
628
|
+
"depends_on": None,
|
|
629
|
+
},
|
|
630
|
+
)
|
|
631
|
+
payload["steps"][1]["depends_on"] = "S00"
|
|
632
|
+
target_index = 0
|
|
633
|
+
else:
|
|
634
|
+
target_index = next(
|
|
635
|
+
position for position, step in enumerate(payload["steps"])
|
|
636
|
+
if step["skill"] == skill
|
|
637
|
+
)
|
|
638
|
+
for step in payload["steps"][target_index:]:
|
|
639
|
+
step["status"] = "pending"
|
|
640
|
+
step.pop("stage_result", None)
|
|
641
|
+
checkpoint = _write(tmp_path / f"terminal-blocker-{index}.json", payload)
|
|
642
|
+
|
|
643
|
+
result = update_checkpoint(
|
|
644
|
+
str(checkpoint), skill, "failed",
|
|
645
|
+
semantic_update={"stage_result": stage_result},
|
|
646
|
+
)
|
|
647
|
+
persisted = json.loads(checkpoint.read_text(encoding="utf-8"))
|
|
648
|
+
|
|
649
|
+
assert result["ok"] is True
|
|
650
|
+
assert persisted["feature_state"]["terminal_status"] == "WORKFLOW_BLOCKED"
|
|
651
|
+
assert persisted["feature_state"]["blocked_reason"]
|
|
401
652
|
|
|
402
653
|
|
|
403
654
|
def test_test_blocked_preserves_l1_result_without_evidence_discovery(tmp_path):
|
|
@@ -466,6 +717,31 @@ def test_update_checkpoint_rejects_test_completion_for_non_pass(tmp_path):
|
|
|
466
717
|
assert "requires TEST_PASS" in result["error"]
|
|
467
718
|
|
|
468
719
|
|
|
720
|
+
def test_prompt_checkpoint_helper_rejects_runtime_owned_committed_result(tmp_path):
|
|
721
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
722
|
+
committer = next(
|
|
723
|
+
step for step in payload["steps"] if step["skill"] == "prizmkit-committer"
|
|
724
|
+
)
|
|
725
|
+
committer["status"] = "in_progress"
|
|
726
|
+
committer["stage_result"] = "COMMIT_PENDING"
|
|
727
|
+
checkpoint = _write(tmp_path / "workflow-checkpoint.json", payload)
|
|
728
|
+
|
|
729
|
+
result = update_checkpoint(
|
|
730
|
+
str(checkpoint),
|
|
731
|
+
"prizmkit-committer",
|
|
732
|
+
"completed",
|
|
733
|
+
semantic_update={"stage_result": "COMMITTED"},
|
|
734
|
+
)
|
|
735
|
+
|
|
736
|
+
assert result["ok"] is False
|
|
737
|
+
assert "Runtime-owned" in result["error"]
|
|
738
|
+
without_result = update_checkpoint(
|
|
739
|
+
str(checkpoint), "prizmkit-committer", "completed"
|
|
740
|
+
)
|
|
741
|
+
assert without_result["ok"] is False
|
|
742
|
+
assert "Runtime-owned COMMITTED" in without_result["error"]
|
|
743
|
+
|
|
744
|
+
|
|
469
745
|
def test_update_checkpoint_rejects_test_before_review_pass(tmp_path):
|
|
470
746
|
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
471
747
|
payload["steps"][1]["status"] = "skipped"
|
|
@@ -487,7 +763,7 @@ def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
|
|
|
487
763
|
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
488
764
|
payload["steps"][2]["status"] = "skipped"
|
|
489
765
|
payload["steps"][2].pop("stage_result", None)
|
|
490
|
-
payload["steps"][3]["status"] = "
|
|
766
|
+
payload["steps"][3]["status"] = "completed"
|
|
491
767
|
for step in payload["steps"][4:]:
|
|
492
768
|
step["status"] = "pending"
|
|
493
769
|
step.pop("stage_result", None)
|
|
@@ -499,9 +775,53 @@ def test_update_checkpoint_rejects_finalization_before_test_pass(tmp_path):
|
|
|
499
775
|
assert "TEST_PASS" in result["error"]
|
|
500
776
|
|
|
501
777
|
|
|
778
|
+
def test_commit_pending_is_runtime_ready_but_not_workflow_complete(tmp_path):
|
|
779
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
780
|
+
payload["steps"].insert(
|
|
781
|
+
0,
|
|
782
|
+
{
|
|
783
|
+
"id": "S00",
|
|
784
|
+
"skill": "context-snapshot-and-plan",
|
|
785
|
+
"name": "Plan",
|
|
786
|
+
"status": "completed",
|
|
787
|
+
"stage_result": "PLAN_READY",
|
|
788
|
+
"required_artifacts": [],
|
|
789
|
+
"depends_on": None,
|
|
790
|
+
},
|
|
791
|
+
)
|
|
792
|
+
payload["steps"][1]["depends_on"] = "S00"
|
|
793
|
+
committer = next(
|
|
794
|
+
step for step in payload["steps"] if step["skill"] == "prizmkit-committer"
|
|
795
|
+
)
|
|
796
|
+
committer["status"] = "in_progress"
|
|
797
|
+
committer["stage_result"] = "COMMIT_PENDING"
|
|
798
|
+
committer["required_artifacts"] = [
|
|
799
|
+
".prizmkit/specs/feature/runtime-commit-request.json"
|
|
800
|
+
]
|
|
801
|
+
artifact_dir = tmp_path / ".prizmkit/specs/feature"
|
|
802
|
+
(artifact_dir / "runtime-commit-request.json").write_text("{}", encoding="utf-8")
|
|
803
|
+
payload["feature_state"] = {
|
|
804
|
+
"stage": "committer",
|
|
805
|
+
"status": "in_progress",
|
|
806
|
+
"stage_result": "COMMIT_PENDING",
|
|
807
|
+
"repair_round": 0,
|
|
808
|
+
}
|
|
809
|
+
checkpoint = _write(artifact_dir / "workflow-checkpoint.json", payload)
|
|
810
|
+
|
|
811
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
812
|
+
|
|
813
|
+
assert state.valid is True
|
|
814
|
+
assert state.semantic_commit_ready is True
|
|
815
|
+
assert state.semantic_complete is False
|
|
816
|
+
assert state.semantic_snapshot["commit_ready"] is True
|
|
817
|
+
|
|
818
|
+
|
|
502
819
|
def test_declared_final_artifact_must_exist_for_completion(tmp_path):
|
|
503
820
|
payload = _family_checkpoint(tmp_path)
|
|
504
|
-
|
|
821
|
+
summary_step = next(
|
|
822
|
+
step for step in payload["steps"] if step["skill"] == "completion-summary"
|
|
823
|
+
)
|
|
824
|
+
summary_step["required_artifacts"] = [
|
|
505
825
|
".prizmkit/specs/feature/completion-summary.json"
|
|
506
826
|
]
|
|
507
827
|
checkpoint = _write(
|
|
@@ -519,6 +839,239 @@ def test_declared_final_artifact_must_exist_for_completion(tmp_path):
|
|
|
519
839
|
assert present.semantic.artifacts_valid is True
|
|
520
840
|
|
|
521
841
|
|
|
842
|
+
def test_completion_requires_declared_review_report_with_terminal_pass(tmp_path):
|
|
843
|
+
payload = _family_checkpoint(tmp_path)
|
|
844
|
+
review = next(
|
|
845
|
+
step for step in payload["steps"]
|
|
846
|
+
if step["skill"] == "prizmkit-code-review"
|
|
847
|
+
)
|
|
848
|
+
review["required_artifacts"] = [
|
|
849
|
+
".prizmkit/specs/feature/review-report.md"
|
|
850
|
+
]
|
|
851
|
+
checkpoint = _write(
|
|
852
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json", payload,
|
|
853
|
+
)
|
|
854
|
+
|
|
855
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is False
|
|
856
|
+
(checkpoint.parent / "review-report.md").write_text(
|
|
857
|
+
"# Review Report\n\n## Final Result\n\n- Verdict: NEEDS_FIXES\n",
|
|
858
|
+
encoding="utf-8",
|
|
859
|
+
)
|
|
860
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is False
|
|
861
|
+
(checkpoint.parent / "review-report.md").write_text(
|
|
862
|
+
"# Review Report\n\n## Final Result\n\n- Verdict: PASS\n",
|
|
863
|
+
encoding="utf-8",
|
|
864
|
+
)
|
|
865
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is True
|
|
866
|
+
|
|
867
|
+
|
|
868
|
+
def test_completion_requires_valid_declared_retrospective_result(tmp_path):
|
|
869
|
+
payload = _family_checkpoint(tmp_path)
|
|
870
|
+
retrospective = next(
|
|
871
|
+
step for step in payload["steps"]
|
|
872
|
+
if step["skill"] == "prizmkit-retrospective"
|
|
873
|
+
)
|
|
874
|
+
retrospective["required_artifacts"] = [
|
|
875
|
+
".prizmkit/specs/feature/retrospective-result.json"
|
|
876
|
+
]
|
|
877
|
+
checkpoint = _write(
|
|
878
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json", payload,
|
|
879
|
+
)
|
|
880
|
+
|
|
881
|
+
(checkpoint.parent / "retrospective-result.json").write_text(
|
|
882
|
+
"{}\n", encoding="utf-8",
|
|
883
|
+
)
|
|
884
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is False
|
|
885
|
+
(checkpoint.parent / "retrospective-result.json").write_text(
|
|
886
|
+
json.dumps(
|
|
887
|
+
{
|
|
888
|
+
"schema_version": 1,
|
|
889
|
+
"outcome": "RETRO_COMPLETE",
|
|
890
|
+
"result": "NO_DOC_CHANGE",
|
|
891
|
+
"reason": "No durable change",
|
|
892
|
+
"change_paths": ["src/example.py"],
|
|
893
|
+
"documentation_paths": [],
|
|
894
|
+
"validation": [],
|
|
895
|
+
}
|
|
896
|
+
),
|
|
897
|
+
encoding="utf-8",
|
|
898
|
+
)
|
|
899
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is True
|
|
900
|
+
|
|
901
|
+
|
|
902
|
+
@pytest.mark.parametrize(
|
|
903
|
+
"invalid_path",
|
|
904
|
+
[
|
|
905
|
+
".prizmkit/prizm-docs/../escape.prizm",
|
|
906
|
+
".prizmkit/prizm-docs/./module.prizm",
|
|
907
|
+
".prizmkit/prizm-docs//module.prizm",
|
|
908
|
+
".prizmkit\\prizm-docs\\module.prizm",
|
|
909
|
+
".prizmkit/prizm-docs/module.md",
|
|
910
|
+
],
|
|
911
|
+
)
|
|
912
|
+
def test_completion_rejects_unsafe_retrospective_documentation_path(
|
|
913
|
+
tmp_path, invalid_path,
|
|
914
|
+
):
|
|
915
|
+
payload = _family_checkpoint(tmp_path)
|
|
916
|
+
retrospective = next(
|
|
917
|
+
step for step in payload["steps"]
|
|
918
|
+
if step["skill"] == "prizmkit-retrospective"
|
|
919
|
+
)
|
|
920
|
+
retrospective["required_artifacts"] = [
|
|
921
|
+
".prizmkit/specs/feature/retrospective-result.json"
|
|
922
|
+
]
|
|
923
|
+
checkpoint = _write(
|
|
924
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json", payload,
|
|
925
|
+
)
|
|
926
|
+
(checkpoint.parent / "retrospective-result.json").write_text(
|
|
927
|
+
json.dumps(
|
|
928
|
+
{
|
|
929
|
+
"schema_version": 1,
|
|
930
|
+
"outcome": "RETRO_COMPLETE",
|
|
931
|
+
"result": "DOCS_UPDATED",
|
|
932
|
+
"reason": "Durable docs changed",
|
|
933
|
+
"change_paths": ["src/example.py"],
|
|
934
|
+
"documentation_paths": [invalid_path],
|
|
935
|
+
"validation": ["validate docs"],
|
|
936
|
+
}
|
|
937
|
+
),
|
|
938
|
+
encoding="utf-8",
|
|
939
|
+
)
|
|
940
|
+
|
|
941
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is False
|
|
942
|
+
|
|
943
|
+
|
|
944
|
+
@pytest.mark.parametrize(
|
|
945
|
+
"invalid_path",
|
|
946
|
+
["../escape.py", "./src/example.py", "src//example.py", "src\\example.py"],
|
|
947
|
+
)
|
|
948
|
+
def test_completion_rejects_unsafe_retrospective_change_path(tmp_path, invalid_path):
|
|
949
|
+
payload = _family_checkpoint(tmp_path)
|
|
950
|
+
retrospective = next(
|
|
951
|
+
step for step in payload["steps"]
|
|
952
|
+
if step["skill"] == "prizmkit-retrospective"
|
|
953
|
+
)
|
|
954
|
+
retrospective["required_artifacts"] = [
|
|
955
|
+
".prizmkit/specs/feature/retrospective-result.json"
|
|
956
|
+
]
|
|
957
|
+
checkpoint = _write(
|
|
958
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json", payload,
|
|
959
|
+
)
|
|
960
|
+
(checkpoint.parent / "retrospective-result.json").write_text(
|
|
961
|
+
json.dumps(
|
|
962
|
+
{
|
|
963
|
+
"schema_version": 1,
|
|
964
|
+
"outcome": "RETRO_COMPLETE",
|
|
965
|
+
"result": "NO_DOC_CHANGE",
|
|
966
|
+
"reason": "No durable change",
|
|
967
|
+
"change_paths": [invalid_path],
|
|
968
|
+
"documentation_paths": [],
|
|
969
|
+
"validation": [],
|
|
970
|
+
}
|
|
971
|
+
),
|
|
972
|
+
encoding="utf-8",
|
|
973
|
+
)
|
|
974
|
+
|
|
975
|
+
assert load_checkpoint_state(checkpoint, project_root=tmp_path).semantic_complete is False
|
|
976
|
+
|
|
977
|
+
|
|
978
|
+
def test_completion_rejects_configured_verification_after_test(tmp_path):
|
|
979
|
+
payload = _family_checkpoint(tmp_path)
|
|
980
|
+
test_index = next(
|
|
981
|
+
index for index, step in enumerate(payload["steps"])
|
|
982
|
+
if step["skill"] == "prizmkit-test"
|
|
983
|
+
)
|
|
984
|
+
test_step = payload["steps"][test_index]
|
|
985
|
+
following = payload["steps"][test_index + 1]
|
|
986
|
+
payload["steps"].insert(
|
|
987
|
+
test_index + 1,
|
|
988
|
+
{
|
|
989
|
+
"id": "S99",
|
|
990
|
+
"skill": "browser-verification",
|
|
991
|
+
"name": "Browser Verification",
|
|
992
|
+
"status": "completed",
|
|
993
|
+
"required_artifacts": [],
|
|
994
|
+
"depends_on": test_step["id"],
|
|
995
|
+
},
|
|
996
|
+
)
|
|
997
|
+
following["depends_on"] = "S99"
|
|
998
|
+
|
|
999
|
+
state = validate_checkpoint_data(payload, project_root=tmp_path)
|
|
1000
|
+
|
|
1001
|
+
assert state.semantic.sequence_valid is False
|
|
1002
|
+
assert "after review and before test" in state.semantic.error_message
|
|
1003
|
+
|
|
1004
|
+
|
|
1005
|
+
def test_configured_manual_verification_cannot_be_skipped_for_commit_readiness(tmp_path):
|
|
1006
|
+
payload = _family_checkpoint(tmp_path, semantic={}, terminal=False)
|
|
1007
|
+
payload["steps"].insert(
|
|
1008
|
+
0,
|
|
1009
|
+
{
|
|
1010
|
+
"id": "S00",
|
|
1011
|
+
"skill": "context-snapshot-and-plan",
|
|
1012
|
+
"name": "Plan",
|
|
1013
|
+
"status": "completed",
|
|
1014
|
+
"stage_result": "PLAN_READY",
|
|
1015
|
+
"required_artifacts": [],
|
|
1016
|
+
"depends_on": None,
|
|
1017
|
+
},
|
|
1018
|
+
)
|
|
1019
|
+
payload["steps"][1]["depends_on"] = "S00"
|
|
1020
|
+
review = next(
|
|
1021
|
+
step for step in payload["steps"]
|
|
1022
|
+
if step["skill"] == "prizmkit-code-review"
|
|
1023
|
+
)
|
|
1024
|
+
test = next(
|
|
1025
|
+
step for step in payload["steps"] if step["skill"] == "prizmkit-test"
|
|
1026
|
+
)
|
|
1027
|
+
test_index = payload["steps"].index(test)
|
|
1028
|
+
payload["steps"].insert(
|
|
1029
|
+
test_index,
|
|
1030
|
+
{
|
|
1031
|
+
"id": "S99",
|
|
1032
|
+
"skill": "manual-verification",
|
|
1033
|
+
"name": "Manual Verification",
|
|
1034
|
+
"status": "skipped",
|
|
1035
|
+
"required_artifacts": [],
|
|
1036
|
+
"depends_on": review["id"],
|
|
1037
|
+
},
|
|
1038
|
+
)
|
|
1039
|
+
test["depends_on"] = "S99"
|
|
1040
|
+
committer = next(
|
|
1041
|
+
step for step in payload["steps"]
|
|
1042
|
+
if step["skill"] == "prizmkit-committer"
|
|
1043
|
+
)
|
|
1044
|
+
committer["status"] = "in_progress"
|
|
1045
|
+
committer["stage_result"] = "COMMIT_PENDING"
|
|
1046
|
+
committer["required_artifacts"] = [
|
|
1047
|
+
".prizmkit/specs/feature/runtime-commit-request.json"
|
|
1048
|
+
]
|
|
1049
|
+
artifact_dir = tmp_path / ".prizmkit/specs/feature"
|
|
1050
|
+
(artifact_dir / "runtime-commit-request.json").write_text("{}", encoding="utf-8")
|
|
1051
|
+
checkpoint = _write(artifact_dir / "workflow-checkpoint.json", payload)
|
|
1052
|
+
|
|
1053
|
+
skipped = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
1054
|
+
payload["steps"][test_index]["status"] = "completed"
|
|
1055
|
+
checkpoint.write_text(json.dumps(payload), encoding="utf-8")
|
|
1056
|
+
completed = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
1057
|
+
|
|
1058
|
+
assert skipped.semantic_commit_ready is False
|
|
1059
|
+
assert completed.semantic_commit_ready is True
|
|
1060
|
+
|
|
1061
|
+
|
|
1062
|
+
def test_nonpass_test_projection_requires_exact_unresolved_item(tmp_path):
|
|
1063
|
+
artifact_dir = _write_test_artifacts(
|
|
1064
|
+
tmp_path, slug="nonpass", result="TEST_NEEDS_FIXES"
|
|
1065
|
+
)
|
|
1066
|
+
result_path = artifact_dir / "test-result.json"
|
|
1067
|
+
|
|
1068
|
+
assert load_test_result(result_path) is None
|
|
1069
|
+
projection = json.loads(result_path.read_text(encoding="utf-8"))
|
|
1070
|
+
projection["unresolved_items"] = ["Public API correction remains"]
|
|
1071
|
+
result_path.write_text(json.dumps(projection), encoding="utf-8")
|
|
1072
|
+
assert load_test_result(result_path) is not None
|
|
1073
|
+
|
|
1074
|
+
|
|
522
1075
|
def test_completion_rejects_invalid_test_result_projection(tmp_path):
|
|
523
1076
|
payload = _family_checkpoint(tmp_path)
|
|
524
1077
|
checkpoint = _write(
|
|
@@ -558,6 +1111,40 @@ def test_completion_rejects_inconsistent_independent_review_metadata(tmp_path):
|
|
|
558
1111
|
assert state.semantic.artifacts_valid is False
|
|
559
1112
|
|
|
560
1113
|
|
|
1114
|
+
def test_legacy_completed_checkpoint_accepts_historical_test_projection_read_only(tmp_path):
|
|
1115
|
+
payload = _family_checkpoint(tmp_path)
|
|
1116
|
+
by_skill = {step["skill"]: step for step in payload["steps"]}
|
|
1117
|
+
legacy_order = [
|
|
1118
|
+
"prizmkit-implement",
|
|
1119
|
+
"prizmkit-code-review",
|
|
1120
|
+
"prizmkit-test",
|
|
1121
|
+
"prizmkit-retrospective",
|
|
1122
|
+
"prizmkit-committer",
|
|
1123
|
+
"completion-summary",
|
|
1124
|
+
]
|
|
1125
|
+
payload["steps"] = [by_skill[skill] for skill in legacy_order]
|
|
1126
|
+
previous = None
|
|
1127
|
+
for index, step in enumerate(payload["steps"], start=1):
|
|
1128
|
+
step["id"] = f"S{index:02d}"
|
|
1129
|
+
step["depends_on"] = previous
|
|
1130
|
+
previous = step["id"]
|
|
1131
|
+
checkpoint = _write(
|
|
1132
|
+
tmp_path / ".prizmkit/specs/feature/workflow-checkpoint.json", payload,
|
|
1133
|
+
)
|
|
1134
|
+
result_path = checkpoint.parent / "test-result.json"
|
|
1135
|
+
projection = json.loads(result_path.read_text(encoding="utf-8"))
|
|
1136
|
+
projection["review_required"] = False
|
|
1137
|
+
projection["review_scope"] = None
|
|
1138
|
+
result_path.write_text(json.dumps(projection), encoding="utf-8")
|
|
1139
|
+
assert load_test_result(result_path) is None
|
|
1140
|
+
|
|
1141
|
+
state = load_checkpoint_state(checkpoint, project_root=tmp_path)
|
|
1142
|
+
|
|
1143
|
+
assert state.semantic_complete is True
|
|
1144
|
+
assert state.semantic.artifacts_valid is True
|
|
1145
|
+
assert state.semantic_commit_ready is False
|
|
1146
|
+
|
|
1147
|
+
|
|
561
1148
|
def test_completion_accepts_legacy_terminal_result_report(tmp_path):
|
|
562
1149
|
payload = _family_checkpoint(tmp_path)
|
|
563
1150
|
checkpoint = _write(
|