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.
Files changed (83) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/README.md +1 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +165 -125
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +13 -1
  5. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +0 -6
  6. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +5 -3
  7. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +160 -0
  8. package/bundled/dev-pipeline/scripts/check-session-status.py +11 -1
  9. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +23 -806
  10. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +6 -8
  11. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +1 -1
  12. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +6 -8
  13. package/bundled/dev-pipeline/scripts/prompt_framework.py +68 -56
  14. package/bundled/dev-pipeline/scripts/update-bug-status.py +21 -2
  15. package/bundled/dev-pipeline/scripts/update-checkpoint.py +52 -131
  16. package/bundled/dev-pipeline/scripts/update-feature-status.py +21 -2
  17. package/bundled/dev-pipeline/scripts/update-refactor-status.py +21 -2
  18. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +3 -3
  19. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +8 -19
  20. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +6 -6
  21. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +5 -5
  22. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +1 -1
  23. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
  24. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +1 -1
  25. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +1 -1
  26. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +13 -10
  27. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +1 -1
  28. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +18 -9
  29. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +1 -1
  30. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +1 -1
  31. package/bundled/dev-pipeline/templates/sections/phase-commit.md +1 -1
  32. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +2 -2
  33. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-base.md +1 -1
  34. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +1 -1
  35. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +1 -1
  36. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +14 -26
  37. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +2 -2
  38. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +3 -3
  39. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
  40. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +13 -10
  41. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
  42. package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
  43. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +494 -413
  44. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +144 -684
  45. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +20 -41
  46. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +24 -24
  47. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +176 -36
  48. package/bundled/skills/_metadata.json +6 -6
  49. package/bundled/skills/prizmkit/SKILL.md +13 -18
  50. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +73 -80
  51. package/bundled/skills/prizmkit-code-review/SKILL.md +7 -7
  52. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +73 -80
  53. package/bundled/skills/prizmkit-committer/SKILL.md +8 -7
  54. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +73 -80
  55. package/bundled/skills/prizmkit-implement/SKILL.md +3 -2
  56. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +73 -80
  57. package/bundled/skills/prizmkit-plan/SKILL.md +2 -1
  58. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +73 -80
  59. package/bundled/skills/prizmkit-retrospective/SKILL.md +8 -6
  60. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +73 -80
  61. package/bundled/skills/prizmkit-test/SKILL.md +217 -189
  62. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +119 -0
  63. package/bundled/skills/prizmkit-test/references/independent-test-review.md +269 -0
  64. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +193 -0
  65. package/bundled/skills/prizmkit-test/references/test-report-template.md +164 -79
  66. package/bundled/skills/prizmkit-workflow/SKILL.md +28 -36
  67. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +73 -80
  68. package/package.json +1 -1
  69. package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +0 -477
  70. package/bundled/skills/prizmkit-test/assets/behavior-risk-matrix.schema.json +0 -116
  71. package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +0 -111
  72. package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +0 -66
  73. package/bundled/skills/prizmkit-test/references/boundary-coverage-protocol.md +0 -104
  74. package/bundled/skills/prizmkit-test/references/contract-mock-protocol.md +0 -65
  75. package/bundled/skills/prizmkit-test/references/evidence-protocol.md +0 -208
  76. package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +0 -80
  77. package/bundled/skills/prizmkit-test/references/examples.md +0 -100
  78. package/bundled/skills/prizmkit-test/references/service-boundary-test-catalog.md +0 -228
  79. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +0 -118
  80. package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +0 -107
  81. package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +0 -181
  82. package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +0 -1930
  83. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +0 -1847
@@ -1,11 +1,9 @@
1
1
  """Tests for generate-bootstrap-prompt.py core functions."""
2
2
 
3
3
  import json
4
- import hashlib
5
4
  import importlib.util
6
5
  import os
7
6
  import re
8
- import time
9
7
  from argparse import Namespace
10
8
  from pathlib import Path
11
9
 
@@ -28,12 +26,12 @@ from generate_bootstrap_prompt import (
28
26
  merge_checkpoint_state,
29
27
  load_active_agent_prompts,
30
28
  validate_rendered,
31
- validate_feature_test_evidence,
32
29
  route_feature_repair,
33
30
  feature_checkpoint_metadata,
34
31
  main as generate_bootstrap_main,
35
32
  )
36
33
  from continuation import append_continuation_handoff, checkpoint_cursor
34
+ from prompt_framework import pipeline_checkpoint_metadata
37
35
  from utils import resolve_prompt_platform
38
36
 
39
37
  REMOVED_MAX_LOG_ENV = "MAX_" + "LOG_SIZE"
@@ -112,197 +110,6 @@ def test_rendered_prompt_validation_rejects_unresolved_control_placeholders():
112
110
  assert errors == ["Unreplaced placeholders: {{INIT_SCRIPT_PATH}}"]
113
111
 
114
112
 
115
- def scoped_report_text(verdict="PASS", boundary_missing=0,
116
- happy_path_only=0, completion_gate="yes",
117
- validator_result="passed"):
118
- """Build a scoped report with a complete boundary contract."""
119
- return "\n".join([
120
- "# Test Report",
121
- "",
122
- "## Scope",
123
- "- Mode: this-change",
124
- "- Artifact Dir: .prizmkit/specs/123-scope-test/",
125
- "- In-Scope Source Files:",
126
- " - src/feature.js",
127
- "- In-Scope Test Files:",
128
- " - tests/feature.test.js",
129
- "",
130
- "## Boundary Matrix",
131
- "| Module | Interface | Service Type | Happy Path | Request Validation | Auth / Permission / Ownership | Domain Invariants | Collection / Pagination | Date / Time | State Transitions | Dependency Failure | Response Contract | N/A Reasons | Final Status |",
132
- "|--------|-----------|--------------|------------|--------------------|-------------------------------|-------------------|-------------------------|-------------|-------------------|-------------------|-------------------|-------------|--------------|",
133
- "| feature | POST /feature | API | covered | covered | covered | covered | covered | covered | covered | covered | covered | none | boundary-covered |",
134
- "",
135
- "## Boundary Completion Gate",
136
- "- Interfaces total: 1",
137
- "- Boundary-covered: 1",
138
- "- Generated-needs-review: 0",
139
- "- Skipped-with-reason: 0",
140
- f"- Boundary-missing: {boundary_missing}",
141
- f"- Happy-path-only: {happy_path_only}",
142
- f"- Completion gate passed: {completion_gate}",
143
- "",
144
- "## Boundary Validation",
145
- "- Validator command: `python3 validate_boundary_report.py`",
146
- f"- Validator result: {validator_result}",
147
- "- Validator output: Boundary report validation: PASS",
148
- "",
149
- "## Verdict",
150
- verdict,
151
- "",
152
- "## Final Status",
153
- "- All tests passing: yes",
154
- f"- Boundary completion gate passed: {completion_gate}",
155
- "- Ready for commit: yes",
156
- "- Needs human review: None",
157
- "",
158
- ])
159
-
160
-
161
- def write_fresh_scoped_report(project_root, report_path, report_text=None):
162
- """Write marker, in-scope files, and a fresh derived report."""
163
- source = project_root / "src" / "feature.js"
164
- test = project_root / "tests" / "feature.test.js"
165
- marker = project_root / ".prizmkit" / "specs" / "123-scope-test" / ".prizmkit-test-started"
166
- source.parent.mkdir(parents=True, exist_ok=True)
167
- test.parent.mkdir(parents=True, exist_ok=True)
168
- marker.parent.mkdir(parents=True, exist_ok=True)
169
- report_path.parent.mkdir(parents=True, exist_ok=True)
170
- source.write_text("export const feature = true;\n", encoding="utf-8")
171
- test.write_text("test('feature', () => {});\n", encoding="utf-8")
172
- marker.write_text("started\n", encoding="utf-8")
173
- time.sleep(0.01)
174
- report_path.write_text(report_text or scoped_report_text(), encoding="utf-8")
175
- return source, test, marker
176
-
177
-
178
- def write_fresh_structured_evidence(project_root):
179
- """Create the smallest protocol-shaped, validator-attested pass package."""
180
- source, test, marker = write_fresh_scoped_report(
181
- project_root,
182
- project_root / ".prizmkit" / "test" / "unused" / "test-report.md",
183
- )
184
- evidence_base = project_root / ".prizmkit" / "test" / "evidence"
185
- environment = {}
186
- plan_inputs = {"feature": "123-scope-test"}
187
- categories = {
188
- "source": [{"path": "src/feature.js", "sha256": hashlib.sha256(source.read_bytes()).hexdigest()}],
189
- "tests": [{"path": "tests/feature.test.js", "sha256": hashlib.sha256(test.read_bytes()).hexdigest()}],
190
- "contracts": [],
191
- "lockfiles": [],
192
- }
193
- target_hashes = {
194
- category: hashlib.sha256(json.dumps(
195
- sorted(records, key=lambda item: item["path"]),
196
- sort_keys=True, separators=(",", ":"),
197
- ).encode()).hexdigest()
198
- for category, records in categories.items()
199
- }
200
- target_hashes["environment"] = hashlib.sha256(b"{}").hexdigest()
201
- target_hashes["plan"] = hashlib.sha256(json.dumps(
202
- plan_inputs, sort_keys=True, separators=(",", ":"),
203
- ).encode()).hexdigest()
204
- inventory = {
205
- "categories": categories,
206
- "plan_inputs": plan_inputs,
207
- }
208
- scope = {"feature": "123-scope-test"}
209
- zero_hash = "0" * 64
210
- identity = {
211
- "baseline_commit": "baseline",
212
- "working_diff_sha256": zero_hash,
213
- "scope_sha256": hashlib.sha256(json.dumps(
214
- scope, sort_keys=True, separators=(",", ":"),
215
- ).encode()).hexdigest(),
216
- }
217
- evidence_id = hashlib.sha256(json.dumps(
218
- identity, sort_keys=True, separators=(",", ":"),
219
- ).encode()).hexdigest()
220
- evidence_root = evidence_base / evidence_id
221
- evidence_root.mkdir(parents=True, exist_ok=True)
222
- (evidence_root / "environment.json").write_text("{}\n", encoding="utf-8")
223
- (evidence_root / "target-inventory.json").write_text(
224
- json.dumps(inventory), encoding="utf-8",
225
- )
226
- (evidence_root / "scope.json").write_text(json.dumps(scope), encoding="utf-8")
227
- verdict = {
228
- "verdict": "TEST_PASS",
229
- "testing_domain_only": True,
230
- "authorizes_commit_or_release": False,
231
- "real_environment_validated": False,
232
- "repairs_business_defects": False,
233
- "code_evidence_replayable": True,
234
- "blockers": [],
235
- "reproduced_failures": [],
236
- }
237
- (evidence_root / "verdict.json").write_text(json.dumps(verdict), encoding="utf-8")
238
- report = scoped_report_text()
239
- (evidence_root / "test-report.md").write_text(report, encoding="utf-8")
240
- stages = []
241
- stage_names = [
242
- "CHANGE_CLASSIFY", "SCOPE_DISCOVER", "CONTRACT_MODEL", "TEST_PLAN",
243
- "INFRA_READY", "TEST_BUILD", "EXECUTE_PROVE", "EVIDENCE_PACKAGE",
244
- "EVIDENCE_VALIDATE",
245
- ]
246
- for name in stage_names:
247
- stages.append({"name": name, "status": "complete", "outputs": []})
248
- manifest = {
249
- "protocol_version": "1.0",
250
- "evidence_id": evidence_id,
251
- "evidence_id_inputs": identity,
252
- "sensitivity": "project-controlled",
253
- "environment_claim": "mocked-code-level-only",
254
- "compatibility": "legacy-test-report-interface-not-supported",
255
- "change_class": "lightweight",
256
- "baseline_commit": "baseline",
257
- "working_diff_sha256": zero_hash,
258
- "target_hashes": target_hashes,
259
- "stages": stages,
260
- "files": [],
261
- "final_verdict": "TEST_PASS",
262
- }
263
- file_records = []
264
- for path in sorted(evidence_root.rglob("*")):
265
- if path.is_file():
266
- relative = path.relative_to(evidence_root).as_posix()
267
- file_records.append({
268
- "path": relative,
269
- "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
270
- "produced_by": "EVIDENCE_PACKAGE",
271
- })
272
- payload = sorted(
273
- ({"path": item["path"], "sha256": item["sha256"]}
274
- for item in file_records
275
- if item["path"] not in {"verdict.json", "test-report.md"}),
276
- key=lambda item: item["path"],
277
- )
278
- validation = {
279
- "validator": "validate_test_evidence.py",
280
- "validator_sha256": zero_hash,
281
- "payload_sha256": hashlib.sha256(json.dumps(
282
- payload, sort_keys=True, separators=(",", ":"),
283
- ).encode()).hexdigest(),
284
- "result": "passed",
285
- "verdict": "TEST_PASS",
286
- "semantics": "integrity-and-protocol-validation-not-hostile-producer-proof",
287
- "replay_receipt_ids": [],
288
- }
289
- (evidence_root / "validation.json").write_text(json.dumps(validation), encoding="utf-8")
290
- file_records.append({
291
- "path": "validation.json",
292
- "sha256": hashlib.sha256((evidence_root / "validation.json").read_bytes()).hexdigest(),
293
- "produced_by": "EVIDENCE_VALIDATE",
294
- })
295
- manifest["files"] = file_records
296
- (evidence_root / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
297
- pointer = project_root / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
298
- pointer.parent.mkdir(parents=True, exist_ok=True)
299
- pointer.write_text(str(evidence_root / "test-report.md"), encoding="utf-8")
300
- report_path = evidence_root / "test-report.md"
301
- now = time.time()
302
- os.utime(marker, (now, now))
303
- os.utime(report_path, (now + 1, now + 1))
304
- return evidence_root
305
-
306
113
 
307
114
  # ---------------------------------------------------------------------------
308
115
  # compute_feature_slug
@@ -601,544 +408,196 @@ class TestProcessModeBlocks:
601
408
 
602
409
  class TestScopedFeatureTestGate:
603
410
  @staticmethod
604
- def _trust_fixture_validator(monkeypatch):
605
- monkeypatch.setattr(
606
- "generate_bootstrap_prompt._evidence_validator_passes",
607
- lambda _project_root, _evidence_root, _validation: True,
411
+ def _artifacts(slug="123-scope-test"):
412
+ return [
413
+ f".prizmkit/specs/{slug}/test-report.md",
414
+ f".prizmkit/specs/{slug}/test-result.json",
415
+ ]
416
+
417
+ @staticmethod
418
+ def _write_result(root, result="TEST_PASS", *, unresolved=None,
419
+ review_required=False, review_scope=None):
420
+ artifact = root / ".prizmkit" / "specs" / "123-scope-test"
421
+ artifact.mkdir(parents=True, exist_ok=True)
422
+ (artifact / "test-report.md").write_text(
423
+ f"# Test Report\n\n## Final Result\n\n- Result: {result}\n",
424
+ encoding="utf-8",
425
+ )
426
+ payload = {
427
+ "schema_version": 1,
428
+ "result": result,
429
+ "report": "test-report.md",
430
+ "main_review_rounds": 1,
431
+ "independent_review": {
432
+ "status": "downgraded",
433
+ "responses": 0,
434
+ "downgrade_reason": "read-only continuation capability unavailable",
435
+ "final_state_rechecked": False,
436
+ },
437
+ "repair_rounds": 0,
438
+ "production_changed": False,
439
+ "review_required": review_required,
440
+ "review_scope": review_scope,
441
+ "unresolved_items": unresolved or [],
442
+ }
443
+ (artifact / "test-result.json").write_text(
444
+ json.dumps(payload), encoding="utf-8",
608
445
  )
609
446
 
610
447
  def test_standard_sections_insert_prizmkit_test_after_review(self):
611
448
  sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
612
449
  sections = assemble_sections(
613
- "standard",
614
- str(sections_dir),
615
- init_done=True,
616
- is_resume=False,
617
- browser_enabled=False,
450
+ "standard", str(sections_dir), init_done=True,
451
+ is_resume=False, browser_enabled=False,
618
452
  )
619
453
  names = [name for name, _ in sections]
454
+ rendered = "\n".join(content for _, content in sections)
620
455
 
621
- assert "phase-prizmkit-test" in names
622
456
  assert names.index("phase-implement") < names.index("phase-review") < names.index("phase-prizmkit-test")
623
-
624
- rendered = "\n".join(content for _, content in sections)
625
- assert "prizmkit-test-gate.py" not in rendered
626
- assert "{{RUNTIME_HELPER_CMD}} artifact touch .prizmkit/specs/{{FEATURE_SLUG}}/.prizmkit-test-started" in rendered
627
- assert "mkdir -p .prizmkit/specs/{{FEATURE_SLUG}}" not in rendered
628
- assert "when present" not in rendered
629
- assert "Boundary Matrix" in rendered
630
- assert "Boundary Completion Gate" in rendered
631
- assert "Boundary-missing: 0" in rendered
632
- assert "Happy-path-only: 0" in rendered
633
- assert "Validator result: passed" in rendered
634
457
  assert "/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/" in rendered
635
- assert ".prizmkit/bugfix/" in rendered # explicitly forbidden in the gate wording
636
- assert ".prizmkit/refactor/" in rendered # explicitly forbidden in the gate wording
637
- assert "latest_test_report=$(python3" not in rendered
638
- assert "boundary_columns_ok=$(awk" not in rendered
639
- assert "Runs tests using `TEST_CMD` after each task" not in rendered
640
- assert "Run the full test suite" not in rendered
641
- assert "before code review" not in rendered
642
- assert "Precondition: the `prizmkit-test` checkpoint is completed" not in rendered
458
+ assert "test-report.md" in rendered
459
+ assert "test-result.json" in rendered
460
+ assert "TEST_NEEDS_FIXES" in rendered
461
+ assert ".prizmkit/test/evidence" not in rendered
462
+ assert "TEST_FAIL" not in rendered
643
463
 
644
464
  def test_standard_sections_include_central_checkpoint_helper(self):
645
465
  sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
646
466
  sections = assemble_sections(
647
- "standard",
648
- str(sections_dir),
649
- init_done=True,
650
- is_resume=False,
651
- browser_enabled=False,
467
+ "standard", str(sections_dir), init_done=True,
468
+ is_resume=False, browser_enabled=False,
652
469
  )
653
470
  rendered = "\n".join(content for _, content in sections)
654
471
 
655
472
  assert "### Checkpoint Update Helper" in rendered
656
473
  assert "{{CHECKPOINT_PYTHON_CMD}}" in rendered
657
- assert "python3 .prizmkit/dev-pipeline/scripts/update-checkpoint.py" not in rendered
658
474
  assert "--checkpoint-path {{CHECKPOINT_PATH}}" in rendered
659
- assert "--step <step-id-or-skill-name>" in rendered
475
+ assert "--stage-result <optional-domain-result>" in rendered
476
+ assert "--status completed --stage-result PLAN_READY" in rendered
477
+ assert "--status completed --stage-result IMPLEMENTED" in rendered
478
+ assert "--status completed --stage-result REVIEW_PASS" in rendered
479
+ assert "--status completed --stage-result TEST_PASS" in rendered
480
+ assert "--status completed --stage-result RETRO_COMPLETE" in rendered
481
+ assert "--status in_progress --stage-result COMMIT_PENDING" in rendered
482
+ assert "--status completed --stage-result COMMITTED" in rendered
660
483
  assert "Do not hand-edit `workflow-checkpoint.json` directly." in rendered
661
- assert "pipeline progress mechanism" not in rendered
662
-
663
- def test_lite_sections_include_review_before_prizmkit_test_and_commit(self):
664
- sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
665
- sections = assemble_sections(
666
- "lite",
667
- str(sections_dir),
668
- init_done=True,
669
- is_resume=False,
670
- browser_enabled=False,
671
- )
672
- names = [name for name, _ in sections]
673
-
674
- assert "phase-prizmkit-test" in names
675
- assert "phase-review" in names
676
- assert names.index("phase-implement") < names.index("phase-review") < names.index("phase-prizmkit-test") < names.index("phase-commit")
484
+ assert "authoritative_evidence_paths" not in rendered
677
485
 
678
- def test_checkpoint_orders_prizmkit_test_after_review(self):
486
+ def test_checkpoint_orders_test_after_review_with_terminal_artifacts(self):
679
487
  sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
680
488
  sections = assemble_sections(
681
- "standard",
682
- str(sections_dir),
683
- init_done=True,
684
- is_resume=False,
685
- browser_enabled=False,
489
+ "standard", str(sections_dir), init_done=True,
490
+ is_resume=False, browser_enabled=False,
686
491
  )
687
492
  checkpoint = generate_checkpoint_definition(
688
- sections,
689
- pipeline_mode="standard",
690
- workflow_type="feature-pipeline",
691
- item_id="F-123",
692
- item_slug="123-scope-test",
693
- session_id="F-123-session",
694
- init_done=True,
493
+ sections, pipeline_mode="standard", workflow_type="feature-pipeline",
494
+ item_id="F-123", item_slug="123-scope-test",
495
+ session_id="F-123-session", init_done=True,
695
496
  )
696
497
  skills = [step["skill"] for step in checkpoint["steps"]]
697
-
698
- assert skills.index("prizmkit-implement") < skills.index("prizmkit-code-review") < skills.index("prizmkit-test")
699
498
  test_step = next(step for step in checkpoint["steps"] if step["skill"] == "prizmkit-test")
700
- assert test_step["required_artifacts"] == [
701
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
702
- ".prizmkit/test/evidence/*/manifest.json",
703
- ".prizmkit/test/evidence/*/verdict.json",
704
- ".prizmkit/test/evidence/*/validation.json",
705
- ]
706
-
707
- def test_merge_resets_completed_prizmkit_test_without_report_artifact(self, tmp_path):
708
- existing = {
709
- "steps": [
710
- {
711
- "id": "S01",
712
- "skill": "prizmkit-test",
713
- "status": "completed",
714
- "required_artifacts": [
715
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
716
- ".prizmkit/test/*/test-report.md",
717
- ],
718
- },
719
- {"id": "S02", "skill": "prizmkit-code-review", "status": "completed", "required_artifacts": []},
720
- ],
721
- }
722
- fresh = {
723
- "steps": [
724
- {"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
725
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
726
- ".prizmkit/test/*/test-report.md",
727
- ]},
728
- {"id": "S02", "skill": "prizmkit-code-review", "status": "pending", "required_artifacts": []},
729
- ],
730
- }
731
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
732
- pointer.parent.mkdir(parents=True, exist_ok=True)
733
- pointer.write_text(".prizmkit/test/missing/test-report.md\n", encoding="utf-8")
734
-
735
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
736
-
737
- assert [step["status"] for step in merged["steps"]] == ["pending", "pending"]
738
-
739
- def test_merge_keeps_completed_prizmkit_test_only_for_scoped_pass_report(self, tmp_path, monkeypatch):
740
- self._trust_fixture_validator(monkeypatch)
741
- evidence_root = write_fresh_structured_evidence(tmp_path)
742
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
743
- pointer.parent.mkdir(parents=True, exist_ok=True)
744
- pointer.write_text(str(evidence_root / "test-report.md") + "\n", encoding="utf-8")
745
- existing = {
746
- "steps": [
747
- {"id": "S01", "skill": "prizmkit-test", "status": "completed", "stage_result": "TEST_PASS", "required_artifacts": [
748
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
749
- ".prizmkit/test/*/test-report.md",
750
- ]},
751
- {"id": "S02", "skill": "prizmkit-code-review", "status": "completed", "stage_result": "REVIEW_PASS", "required_artifacts": []},
752
- ],
753
- }
754
- fresh = {
755
- "steps": [
756
- {"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
757
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
758
- ".prizmkit/test/*/test-report.md",
759
- ]},
760
- {"id": "S02", "skill": "prizmkit-code-review", "status": "pending", "required_artifacts": []},
761
- ],
762
- }
763
-
764
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
765
-
766
- assert [step["status"] for step in merged["steps"]] == ["completed", "completed"]
767
-
768
- def test_merge_resets_completed_prizmkit_test_for_needs_fixes_report(self, tmp_path):
769
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
770
- report.parent.mkdir(parents=True)
771
- report.write_text(scoped_report_text(verdict="NEEDS_FIXES"), encoding="utf-8")
772
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
773
- pointer.parent.mkdir(parents=True, exist_ok=True)
774
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
775
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
776
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
777
- ".prizmkit/test/*/test-report.md",
778
- ]}]}
779
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
780
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
781
- ".prizmkit/test/*/test-report.md",
782
- ]}]}
783
-
784
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
785
-
786
- assert merged["steps"][0]["status"] == "pending"
787
-
788
- def test_merge_resets_completed_prizmkit_test_for_absolute_report_outside_project(self, tmp_path):
789
- project_root = tmp_path / "project"
790
- external_root = tmp_path / "external"
791
- in_project_report = project_root / ".prizmkit" / "test" / "run" / "test-report.md"
792
- external_report = external_root / "report.md"
793
- pointer = project_root / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
794
- for path in (in_project_report.parent, external_report.parent, pointer.parent):
795
- path.mkdir(parents=True, exist_ok=True)
796
- report_text = "\n".join([
797
- "# Test Report",
798
- "",
799
- "## Scope",
800
- "- Mode: this-change",
801
- "- Artifact Dir: .prizmkit/specs/123-scope-test/",
802
- "",
803
- "## Verdict",
804
- "PASS",
805
- "",
806
- ])
807
- in_project_report.write_text(report_text, encoding="utf-8")
808
- external_report.write_text(report_text, encoding="utf-8")
809
- pointer.write_text(f"{external_report}\n", encoding="utf-8")
810
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
811
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
812
- ".prizmkit/test/*/test-report.md",
813
- ]}]}
814
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
815
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
816
- ".prizmkit/test/*/test-report.md",
817
- ]}]}
818
-
819
- merged = merge_checkpoint_state(existing, fresh, str(project_root))
820
-
821
- assert merged["steps"][0]["status"] == "pending"
822
-
823
- def test_merge_resets_completed_prizmkit_test_for_sibling_workspace_report(self, tmp_path):
824
- project_root = tmp_path / "project"
825
- sibling_root = tmp_path / "project-sibling"
826
- in_project_report = project_root / ".prizmkit" / "test" / "run" / "test-report.md"
827
- sibling_report = sibling_root / ".prizmkit" / "test" / "run" / "test-report.md"
828
- pointer = project_root / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
829
- for path in (in_project_report.parent, sibling_report.parent, pointer.parent):
830
- path.mkdir(parents=True, exist_ok=True)
831
- report_text = scoped_report_text()
832
- in_project_report.write_text(report_text, encoding="utf-8")
833
- sibling_report.write_text(report_text, encoding="utf-8")
834
- pointer.write_text(f"{sibling_report}\n", encoding="utf-8")
835
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
836
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
837
- ".prizmkit/test/*/test-report.md",
838
- ]}]}
839
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
840
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
841
- ".prizmkit/test/*/test-report.md",
842
- ]}]}
843
-
844
- merged = merge_checkpoint_state(existing, fresh, str(project_root))
845
-
846
- assert merged["steps"][0]["status"] == "pending"
847
-
848
- def test_merge_resets_completed_prizmkit_test_for_minimal_boundary_matrix(self, tmp_path):
849
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
850
- report.parent.mkdir(parents=True)
851
- report.write_text(
852
- "\n".join([
853
- "# Test Report",
854
- "",
855
- "## Scope",
856
- "- Mode: this-change",
857
- "- Artifact Dir: .prizmkit/specs/123-scope-test/",
858
- "",
859
- "## Boundary Matrix",
860
- "| Interface | Happy Path | N/A Reasons | Final Status |",
861
- "|-----------|------------|-------------|--------------|",
862
- "| POST /feature | covered | none | boundary-covered |",
863
- "",
864
- "## Boundary Completion Gate",
865
- "- Interfaces total: 1",
866
- "- Boundary-covered: 1",
867
- "- Boundary-missing: 0",
868
- "- Happy-path-only: 0",
869
- "- Completion gate passed: yes",
870
- "",
871
- "## Boundary Validation",
872
- "- Validator result: skipped",
873
- "",
874
- "## Verdict",
875
- "PASS",
876
- "",
877
- ]),
878
- encoding="utf-8",
879
- )
880
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
881
- pointer.parent.mkdir(parents=True, exist_ok=True)
882
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
883
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
884
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
885
- ".prizmkit/test/*/test-report.md",
886
- ]}]}
887
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
888
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
889
- ".prizmkit/test/*/test-report.md",
890
- ]}]}
891
-
892
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
893
-
894
- assert merged["steps"][0]["status"] == "pending"
895
-
896
- def test_merge_resets_completed_prizmkit_test_for_missing_boundary_metrics(self, tmp_path):
897
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
898
- report_text = scoped_report_text().replace("- Boundary-missing: 0\n", "").replace("- Happy-path-only: 0\n", "")
899
- write_fresh_scoped_report(tmp_path, report, report_text)
900
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
901
- pointer.parent.mkdir(parents=True, exist_ok=True)
902
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
903
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
904
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
905
- ".prizmkit/test/*/test-report.md",
906
- ]}]}
907
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
908
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
909
- ".prizmkit/test/*/test-report.md",
910
- ]}]}
911
499
 
912
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
500
+ assert skills.index("prizmkit-code-review") < skills.index("prizmkit-test")
501
+ assert test_step["required_artifacts"] == self._artifacts()
913
502
 
914
- assert merged["steps"][0]["status"] == "pending"
915
-
916
- def test_merge_resets_completed_prizmkit_test_when_marker_newer_than_report(self, tmp_path):
917
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
918
- _source, _test, marker = write_fresh_scoped_report(tmp_path, report)
919
- now = time.time()
920
- os.utime(report, (now, now))
921
- os.utime(marker, (now + 10, now + 10))
922
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
923
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
924
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
925
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
926
- ".prizmkit/test/*/test-report.md",
927
- ]}]}
928
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
929
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
930
- ".prizmkit/test/*/test-report.md",
931
- ]}]}
503
+ def test_merge_keeps_only_consistent_test_pass_artifacts(self, tmp_path):
504
+ self._write_result(tmp_path)
505
+ existing = {"steps": [{
506
+ "id": "S01", "skill": "prizmkit-test", "status": "completed",
507
+ "stage_result": "TEST_PASS", "required_artifacts": self._artifacts(),
508
+ }]}
509
+ fresh = {"steps": [{
510
+ "id": "S01", "skill": "prizmkit-test", "status": "pending",
511
+ "required_artifacts": self._artifacts(),
512
+ }]}
932
513
 
933
514
  merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
934
515
 
935
- assert merged["steps"][0]["status"] == "pending"
936
-
937
- def test_merge_resets_completed_prizmkit_test_when_in_scope_file_newer_than_report(self, tmp_path):
938
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
939
- source, _test, _marker = write_fresh_scoped_report(tmp_path, report)
940
- now = time.time()
941
- os.utime(report, (now, now))
942
- os.utime(source, (now + 10, now + 10))
943
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
944
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
945
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
946
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
947
- ".prizmkit/test/*/test-report.md",
948
- ]}]}
949
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
950
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
951
- ".prizmkit/test/*/test-report.md",
952
- ]}]}
516
+ assert merged["steps"][0]["status"] == "completed"
517
+ assert merged["steps"][0]["stage_result"] == "TEST_PASS"
518
+
519
+ def test_merge_resets_missing_or_non_pass_test_artifacts(self, tmp_path):
520
+ existing = {"steps": [{
521
+ "id": "S01", "skill": "prizmkit-test", "status": "completed",
522
+ "stage_result": "TEST_PASS", "required_artifacts": self._artifacts(),
523
+ }]}
524
+ fresh = {"steps": [{
525
+ "id": "S01", "skill": "prizmkit-test", "status": "pending",
526
+ "required_artifacts": self._artifacts(),
527
+ }]}
528
+ missing = merge_checkpoint_state(existing, fresh, str(tmp_path))
529
+ assert missing["steps"][0]["status"] == "pending"
530
+
531
+ self._write_result(tmp_path, "TEST_NEEDS_FIXES", unresolved=["known correction"])
532
+ needs_fixes = merge_checkpoint_state(existing, fresh, str(tmp_path))
533
+ assert needs_fixes["steps"][0]["status"] == "pending"
534
+
535
+ def test_merge_rejects_malformed_or_inconsistent_terminal_projection(self, tmp_path):
536
+ self._write_result(tmp_path)
537
+ result_path = tmp_path / ".prizmkit/specs/123-scope-test/test-result.json"
538
+ payload = json.loads(result_path.read_text(encoding="utf-8"))
539
+ payload["review_required"] = True
540
+ payload["review_scope"] = None
541
+ result_path.write_text(json.dumps(payload), encoding="utf-8")
542
+ existing = {"steps": [{
543
+ "id": "S01", "skill": "prizmkit-test", "status": "completed",
544
+ "stage_result": "TEST_PASS", "required_artifacts": self._artifacts(),
545
+ }]}
546
+ fresh = {"steps": [{
547
+ "id": "S01", "skill": "prizmkit-test", "status": "pending",
548
+ "required_artifacts": self._artifacts(),
549
+ }]}
953
550
 
954
551
  merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
955
552
 
956
553
  assert merged["steps"][0]["status"] == "pending"
957
554
 
958
- def test_merge_resets_completed_prizmkit_test_without_in_scope_tests(self, tmp_path):
959
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
960
- report_text = scoped_report_text().replace("- In-Scope Test Files:\n - tests/feature.test.js\n", "- In-Scope Test Files:\n")
961
- write_fresh_scoped_report(tmp_path, report, report_text)
962
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
963
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
964
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
965
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
966
- ".prizmkit/test/*/test-report.md",
967
- ]}]}
968
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
969
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
970
- ".prizmkit/test/*/test-report.md",
971
- ]}]}
972
-
973
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
974
-
975
- assert merged["steps"][0]["status"] == "pending"
976
-
977
- def test_structured_evidence_accepts_current_matching_package(self, tmp_path, monkeypatch):
978
- self._trust_fixture_validator(monkeypatch)
979
- evidence_root = write_fresh_structured_evidence(tmp_path)
980
-
981
- evidence = validate_feature_test_evidence(
982
- str(tmp_path), ".prizmkit/specs/123-scope-test",
983
- )
984
-
985
- assert evidence is not None
986
- assert evidence["verdict"] == "TEST_PASS"
987
- assert evidence["manifest_path"] == str(
988
- (evidence_root / "manifest.json").relative_to(tmp_path),
989
- )
990
- assert evidence["validation_path"].endswith("/validation.json")
991
-
992
- def test_structured_evidence_requires_authoritative_validator(self, tmp_path, monkeypatch):
993
- write_fresh_structured_evidence(tmp_path)
994
- monkeypatch.setattr(
995
- "generate_bootstrap_prompt._evidence_validator_passes",
996
- lambda _project_root, _evidence_root, _validation: False,
997
- )
998
-
999
- assert validate_feature_test_evidence(
1000
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1001
- ) is None
1002
-
1003
- def test_structured_evidence_rejects_legacy_markdown_only_pass(self, tmp_path):
1004
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
1005
- write_fresh_scoped_report(tmp_path, report)
1006
- pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
1007
- pointer.write_text(str(report), encoding="utf-8")
1008
-
1009
- assert validate_feature_test_evidence(
1010
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1011
- ) is None
1012
-
1013
- def test_structured_evidence_rejects_missing_validation(self, tmp_path):
1014
- evidence_root = write_fresh_structured_evidence(tmp_path)
1015
- (evidence_root / "validation.json").unlink()
1016
-
1017
- assert validate_feature_test_evidence(
1018
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1019
- ) is None
1020
-
1021
- def test_structured_evidence_rejects_tampered_verdict(self, tmp_path, monkeypatch):
1022
- self._trust_fixture_validator(monkeypatch)
1023
- evidence_root = write_fresh_structured_evidence(tmp_path)
1024
- verdict_path = evidence_root / "verdict.json"
1025
- verdict = json.loads(verdict_path.read_text(encoding="utf-8"))
1026
- verdict["verdict"] = "TEST_BLOCKED"
1027
- verdict_path.write_text(json.dumps(verdict), encoding="utf-8")
1028
-
1029
- assert validate_feature_test_evidence(
1030
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1031
- ) is None
1032
-
1033
- def test_structured_evidence_rejects_wrong_artifact_scope(self, tmp_path, monkeypatch):
1034
- self._trust_fixture_validator(monkeypatch)
1035
- evidence_root = write_fresh_structured_evidence(tmp_path)
1036
- report_path = evidence_root / "test-report.md"
1037
- report_path.write_text(
1038
- scoped_report_text().replace(
1039
- ".prizmkit/specs/123-scope-test/",
1040
- ".prizmkit/specs/999-other/",
1041
- ),
1042
- encoding="utf-8",
1043
- )
1044
-
1045
- assert validate_feature_test_evidence(
1046
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1047
- ) is None
1048
-
1049
- def test_structured_evidence_rejects_stale_report(self, tmp_path, monkeypatch):
1050
- self._trust_fixture_validator(monkeypatch)
1051
- evidence_root = write_fresh_structured_evidence(tmp_path)
1052
- report = evidence_root / "test-report.md"
1053
- marker = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / ".prizmkit-test-started"
1054
- now = time.time()
1055
- os.utime(report, (now, now))
1056
- os.utime(marker, (now + 10, now + 10))
1057
-
1058
- assert validate_feature_test_evidence(
1059
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1060
- ) is None
1061
-
1062
- def test_structured_evidence_rejects_live_inventory_drift(self, tmp_path, monkeypatch):
1063
- self._trust_fixture_validator(monkeypatch)
1064
- write_fresh_structured_evidence(tmp_path)
1065
- source = tmp_path / "src" / "feature.js"
1066
- source.write_text("export const feature = false;\n", encoding="utf-8")
1067
-
1068
- assert validate_feature_test_evidence(
1069
- str(tmp_path), ".prizmkit/specs/123-scope-test",
1070
- ) is None
1071
-
1072
- def test_feature_repair_routing_preserves_review_and_test_boundaries(self):
555
+ def test_feature_result_routing_stops_test_non_pass_without_outer_repair(self):
1073
556
  review = route_feature_repair("REVIEW_NEEDS_FIXES", repair_round=0)
1074
- test_infra = route_feature_repair(
1075
- "TEST_FAIL", "test-infrastructure", repair_round=1,
1076
- )
1077
- production = route_feature_repair("TEST_FAIL", "production", repair_round=1)
557
+ needs_fixes = route_feature_repair("TEST_NEEDS_FIXES", repair_round=1)
558
+ blocked = route_feature_repair("TEST_BLOCKED", repair_round=1)
1078
559
 
1079
560
  assert review["route"] == [
1080
561
  "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
1081
562
  ]
1082
- assert test_infra["route"] == ["prizmkit-implement", "prizmkit-test"]
1083
- assert production["route"] == [
1084
- "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
1085
- ]
1086
563
  assert review["repair_round"] == 1
1087
-
1088
- def test_feature_repair_routing_blocks_unsafe_and_exhausted_routes(self):
1089
- unknown = route_feature_repair("TEST_FAIL", "unknown", repair_round=1)
1090
- blocked = route_feature_repair("TEST_BLOCKED", "production", repair_round=1)
1091
- exhausted = route_feature_repair("TEST_FAIL", "production", repair_round=3)
1092
-
1093
- assert unknown["blocked"] is True
1094
- assert unknown["route"] == []
564
+ assert needs_fixes["blocked"] is True
565
+ assert needs_fixes["route"] == []
566
+ assert needs_fixes["blocked_reason"] == "test_needs_fixes"
1095
567
  assert blocked["blocked"] is True
1096
- assert blocked["resumable"] is False
1097
- assert blocked["next_stage"] is None
1098
- assert blocked["terminal_status"] == "WORKFLOW_BLOCKED"
1099
568
  assert blocked["blocked_reason"] == "test_blocked"
1100
- assert exhausted["blocked"] is True
1101
- assert exhausted["repair_round"] == 3
1102
-
1103
- def test_feature_checkpoint_metadata_records_l1_handoff_fields(self):
1104
- evidence = {
1105
- "manifest_path": ".prizmkit/test/evidence/id/manifest.json",
1106
- "verdict_path": ".prizmkit/test/evidence/id/verdict.json",
1107
- "validation_path": ".prizmkit/test/evidence/id/validation.json",
1108
- }
569
+ assert blocked["terminal_status"] == "WORKFLOW_BLOCKED"
1109
570
 
571
+ def test_pipeline_checkpoint_metadata_rejects_legacy_or_mismatched_status(self):
572
+ invalid = [
573
+ {"status": "TEST_PASS", "stage_result": "TEST_PASS"},
574
+ {"status": "failed", "stage_result": "TEST_PASS"},
575
+ {"status": "completed", "stage_result": "UNKNOWN_RESULT"},
576
+ ]
577
+
578
+ for values in invalid:
579
+ try:
580
+ pipeline_checkpoint_metadata(
581
+ ".prizmkit/specs/contract-test",
582
+ stage="test",
583
+ **values,
584
+ )
585
+ except ValueError:
586
+ continue
587
+ raise AssertionError("invalid lifecycle metadata was accepted: {}".format(values))
588
+
589
+ def test_feature_checkpoint_metadata_has_no_evidence_paths(self):
1110
590
  metadata = feature_checkpoint_metadata(
1111
591
  ".prizmkit/specs/123-scope-test",
1112
- evidence=evidence,
1113
- stage="test",
1114
- status="TEST_FAIL",
1115
- stage_result="TEST_FAIL",
1116
- repair_scope="test-infrastructure",
1117
- repair_round=2,
1118
- next_stage="prizmkit-implement",
1119
- resume_from="prizmkit-implement",
592
+ stage="test", status="failed",
593
+ stage_result="TEST_NEEDS_FIXES", repair_round=0,
594
+ next_stage=None, resume_from=None,
1120
595
  )
1121
596
 
1122
- assert metadata == {
1123
- "schema_version": 1,
1124
- "artifact_dir": ".prizmkit/specs/123-scope-test",
1125
- "orchestrator": "prizmkit-l4",
1126
- "stage": "test",
1127
- "current_stage": "test",
1128
- "status": "TEST_FAIL",
1129
- "stage_result": "TEST_FAIL",
1130
- "repair_scope": "test-infrastructure",
1131
- "repair_round": 2,
1132
- "authoritative_evidence_paths": {
1133
- "manifest": ".prizmkit/test/evidence/id/manifest.json",
1134
- "verdict": ".prizmkit/test/evidence/id/verdict.json",
1135
- "validation": ".prizmkit/test/evidence/id/validation.json",
1136
- },
1137
- "next_stage": "prizmkit-implement",
1138
- "resume_from": "prizmkit-implement",
1139
- "blocked_reason": None,
1140
- "terminal_status": None,
1141
- }
597
+ assert metadata["status"] == "failed"
598
+ assert metadata["stage_result"] == "TEST_NEEDS_FIXES"
599
+ assert "authoritative_evidence_paths" not in metadata
600
+ assert metadata["artifact_dir"] == ".prizmkit/specs/123-scope-test"
1142
601
 
1143
602
  # ---------------------------------------------------------------------------
1144
603
  # Active compaction harness rollback
@@ -1605,7 +1064,8 @@ class TestFeaturePromptUnifiedFullGuidance:
1605
1064
  assert "### Plan Self-Check — Orchestrator Direct" in prompt
1606
1065
  assert "### Scoped Feature Test Gate — PrizmKit Test" in prompt
1607
1066
  assert "## Test Failure Recovery Protocol" in prompt
1608
- assert "Boundary Matrix" in prompt
1067
+ assert "test-report.md" in prompt
1068
+ assert "test-result.json" in prompt
1609
1069
  assert "### Browser Verification — MANDATORY" not in prompt
1610
1070
  assert "### TERMINAL STOP — Commit Completed" in prompt
1611
1071
  assert "# Dev-Pipeline Session Bootstrap — Tier 1" not in prompt
@@ -1847,7 +1307,8 @@ class TestHeadlessPromptCleanupF033:
1847
1307
  assert forbidden not in prompt
1848
1308
  if "Feature ID" in prompt:
1849
1309
  assert "/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/123-prompt-cleanup/" in prompt
1850
- assert ".prizmkit-test-started" in prompt
1310
+ assert "test-report.md" in prompt
1311
+ assert "test-result.json" in prompt
1851
1312
  assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1852
1313
  assert "prizmkit-runtime-helper.py" in prompt
1853
1314
  # Anti-Bash guardrails for bugfix/refactor branches (F-039)
@@ -1874,7 +1335,7 @@ class TestHeadlessPromptCleanupF033:
1874
1335
  assert "which playwright-cli" not in prompt
1875
1336
  assert "lsof -ti" not in prompt
1876
1337
  assert "sed -nE" not in prompt
1877
- assert ".prizmkit-test-started" in prompt
1338
+ assert ".prizmkit-test-started" not in prompt
1878
1339
  assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1879
1340
  assert "prizmkit-runtime-helper.py" in prompt
1880
1341
 
@@ -1991,7 +1452,6 @@ class TestFeatureBootstrapShellExtraction:
1991
1452
  assert "{{RUNTIME_HELPER_CMD}}" not in prompt
1992
1453
  assert "python" in prompt.lower()
1993
1454
  assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
1994
- assert "artifact touch .prizmkit/specs/123-prompt-cleanup/.prizmkit-test-started" in prompt
1995
1455
  assert "text final-verdict .prizmkit/specs/123-prompt-cleanup/review-report.md" in prompt
1996
1456
 
1997
1457
  def test_rendered_section_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):