prizmkit 1.1.130 → 1.1.134

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 (119) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/claude/command-adapter.js +0 -2
  3. package/bundled/dev-pipeline/.env.example +1 -1
  4. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +11 -6
  5. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +445 -8
  6. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +2 -9
  7. package/bundled/dev-pipeline/prizmkit_runtime/processes.py +21 -1
  8. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +87 -66
  9. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +53 -3
  10. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +55 -25
  11. package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +4 -0
  12. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +119 -18
  13. package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +3 -2
  14. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +411 -17
  15. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +57 -14
  16. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +5 -4
  17. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +55 -2
  18. package/bundled/dev-pipeline/scripts/prompt_framework.py +152 -1
  19. package/bundled/dev-pipeline/scripts/update-bug-status.py +53 -0
  20. package/bundled/dev-pipeline/scripts/update-checkpoint.py +294 -69
  21. package/bundled/dev-pipeline/scripts/update-feature-status.py +57 -0
  22. package/bundled/dev-pipeline/scripts/update-refactor-status.py +53 -0
  23. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +5 -5
  24. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +28 -22
  25. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +24 -10
  26. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +20 -2
  27. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +2 -2
  28. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
  29. package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +5 -10
  30. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +9 -19
  31. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +21 -0
  32. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +1 -1
  33. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +10 -10
  34. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
  35. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +15 -0
  36. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
  37. package/bundled/dev-pipeline/templates/sections/phase-commit.md +2 -2
  38. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +26 -23
  39. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +5 -5
  40. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +15 -11
  41. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +1 -1
  42. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +6 -6
  43. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +21 -0
  44. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
  45. package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +1 -1
  46. package/bundled/dev-pipeline/templates/sections/task-contract.md +4 -0
  47. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +362 -3
  48. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +349 -48
  49. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +129 -0
  50. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +91 -0
  51. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +567 -23
  52. package/bundled/dev-pipeline/tests/test_unified_cli.py +37 -10
  53. package/bundled/skills/_metadata.json +63 -26
  54. package/bundled/skills/app-planner/SKILL.md +4 -3
  55. package/bundled/skills/bug-fix-workflow/SKILL.md +89 -180
  56. package/bundled/skills/bug-planner/SKILL.md +12 -27
  57. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +131 -220
  58. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +36 -90
  59. package/bundled/skills/feature-pipeline-launcher/SKILL.md +129 -271
  60. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +35 -76
  61. package/bundled/skills/feature-planner/SKILL.md +27 -22
  62. package/bundled/skills/feature-planner/assets/planning-guide.md +4 -4
  63. package/bundled/skills/feature-workflow/SKILL.md +94 -121
  64. package/bundled/skills/prizmkit/SKILL.md +130 -94
  65. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +181 -0
  66. package/bundled/skills/prizmkit-code-review/SKILL.md +132 -86
  67. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +243 -0
  68. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +42 -1
  69. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +181 -0
  70. package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +93 -1
  71. package/bundled/skills/prizmkit-committer/SKILL.md +125 -48
  72. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +181 -0
  73. package/bundled/skills/prizmkit-implement/SKILL.md +99 -73
  74. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +1 -1
  75. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +181 -0
  76. package/bundled/skills/prizmkit-init/SKILL.md +5 -5
  77. package/bundled/skills/prizmkit-plan/SKILL.md +146 -105
  78. package/bundled/skills/prizmkit-plan/assets/plan-template.md +18 -0
  79. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +242 -0
  80. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +4 -4
  81. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +181 -0
  82. package/bundled/skills/prizmkit-retrospective/SKILL.md +111 -53
  83. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +7 -6
  84. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +5 -4
  85. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +181 -0
  86. package/bundled/skills/prizmkit-test/SKILL.md +52 -6
  87. package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +63 -12
  88. package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +1 -1
  89. package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +13 -0
  90. package/bundled/skills/prizmkit-test/references/evidence-protocol.md +6 -1
  91. package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +8 -1
  92. package/bundled/skills/prizmkit-test/references/examples.md +1 -1
  93. package/bundled/skills/prizmkit-test/references/test-generation-steps.md +4 -1
  94. package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
  95. package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +15 -0
  96. package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +181 -0
  97. package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +744 -23
  98. package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +194 -15
  99. package/bundled/skills/prizmkit-workflow/SKILL.md +266 -0
  100. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +181 -0
  101. package/bundled/skills/recovery-workflow/SKILL.md +62 -241
  102. package/bundled/skills/recovery-workflow/evals/evals.json +17 -33
  103. package/bundled/skills/recovery-workflow/references/detection.md +28 -52
  104. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +105 -439
  105. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +136 -275
  106. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +53 -88
  107. package/bundled/skills/refactor-planner/SKILL.md +12 -57
  108. package/bundled/skills/refactor-planner/references/behavior-preservation.md +111 -230
  109. package/bundled/skills/refactor-planner/references/error-recovery.md +37 -0
  110. package/bundled/skills/refactor-planner/references/planning-phases.md +11 -17
  111. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +1 -1
  112. package/bundled/skills/refactor-workflow/SKILL.md +98 -139
  113. package/bundled/templates/project-memory-template.md +4 -4
  114. package/package.json +1 -1
  115. package/src/index.js +4 -0
  116. package/src/prompts.js +10 -3
  117. package/src/scaffold.js +1 -1
  118. package/bundled/dev-pipeline/tests/test-deploy-safety.sh +0 -223
  119. package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
@@ -38,6 +38,7 @@ LAYER_ORDER = {
38
38
  "regression-ring": 4,
39
39
  }
40
40
  FINAL_RECORDS = {"validation.json", "verdict.json", "test-report.md"}
41
+ ATTESTATION_MUTABLE = {"validation.json"}
41
42
  COMMON_RECORDS = {
42
43
  "change-classification.json", "scope.json", "target-inventory.json",
43
44
  "environment.json", "executions.json", "verdict.json", "validation.json",
@@ -54,9 +55,13 @@ BLOCKED_EXTERNAL_CLASSES = {"production", "unknown"}
54
55
  SNAPSHOT_VOLATILE_ROOTS = (
55
56
  ".prizmkit/state",
56
57
  ".prizmkit/test/evidence",
58
+ ".prizmkit/" + "dev-" + "pipeline",
57
59
  ".claude/worktrees",
60
+ ".agents/worktrees",
61
+ ".codebuddy/worktrees",
62
+ ".codex/worktrees",
58
63
  )
59
- SNAPSHOT_VOLATILE_NAMES = {".git", "__pycache__"}
64
+ SNAPSHOT_VOLATILE_NAMES = {".git", "__pycache__", ".pytest_cache", ".mypy_cache"}
60
65
  RISK_CONFLICT_PATTERNS = {
61
66
  "permission": re.compile(r"auth|tenant|role|permission|access|acl|rbac", re.I),
62
67
  "concurrency": re.compile(r"lock|shared[ _-]?state|worker|queue|concurr|parallel|race", re.I),
@@ -219,6 +224,18 @@ def validate_schema(
219
224
  """Validate the strict JSON-Schema subset used by skill-owned schemas."""
220
225
  schema = resolve_ref(schema, root_schema)
221
226
 
227
+ one_of = schema.get("oneOf")
228
+ if isinstance(one_of, list):
229
+ matching = 0
230
+ for branch in one_of:
231
+ if not isinstance(branch, dict):
232
+ continue
233
+ branch_errors: list[str] = []
234
+ validate_schema(value, branch, root_schema, location, branch_errors)
235
+ if not branch_errors:
236
+ matching += 1
237
+ require(matching == 1, f"{location}: value must match exactly one schema branch", errors)
238
+
222
239
  for child in schema.get("allOf", []):
223
240
  if isinstance(child, dict):
224
241
  validate_schema(value, child, root_schema, location, errors)
@@ -341,6 +358,31 @@ def require_manifest_entry(
341
358
  return path
342
359
 
343
360
 
361
+ def validate_differential_na(
362
+ value: Any,
363
+ location: str,
364
+ errors: list[str],
365
+ ) -> None:
366
+ """Validate the differential-only N/A shape without weakening risk-cell N/A."""
367
+ if not isinstance(value, dict):
368
+ errors.append(f"{location}: differential N/A must be an object")
369
+ return
370
+ required = {"reason", "rationale", "evidence", "considered_signals", "conflicts"}
371
+ require(set(value) == required, f"{location}: differential N/A fields are incomplete", errors)
372
+ require(value.get("reason") in {"new-behavior", "textual-contract"}, f"{location}: invalid differential N/A reason", errors)
373
+ rationale = value.get("rationale")
374
+ require(isinstance(rationale, str) and len(rationale.strip()) >= 16, f"{location}: differential N/A rationale is too short", errors)
375
+ if value.get("reason") == "new-behavior" and isinstance(rationale, str):
376
+ require("no stable pre-change observable" in rationale.lower(), f"{location}: new behavior N/A lacks baseline rationale", errors)
377
+ if value.get("reason") == "textual-contract" and isinstance(rationale, str):
378
+ require("textual" in rationale.lower(), f"{location}: textual contract N/A lacks textual rationale", errors)
379
+ evidence = value.get("evidence")
380
+ require(isinstance(evidence, list) and bool(evidence) and all(isinstance(item, str) and item.strip() for item in evidence), f"{location}: differential N/A needs evidence strings", errors)
381
+ signals = value.get("considered_signals")
382
+ require(isinstance(signals, list) and bool(signals) and all(isinstance(item, str) and item.strip() for item in signals), f"{location}: differential N/A needs considered signals", errors)
383
+ require(isinstance(value.get("conflicts"), list), f"{location}: differential N/A conflicts must be an array", errors)
384
+
385
+
344
386
  def structured_na(
345
387
  value: Any,
346
388
  records_schema: dict[str, Any],
@@ -503,6 +545,25 @@ def validate_manifest(
503
545
  return entry_map
504
546
 
505
547
 
548
+ def get_captured_deleted_paths(root: Path) -> set[str]:
549
+ capture_path = root / "change-capture.json"
550
+ if not capture_path.is_file():
551
+ return set()
552
+ try:
553
+ capture = load_json(capture_path)
554
+ except EvidenceError:
555
+ return set()
556
+ if not isinstance(capture, dict):
557
+ return set()
558
+ return {
559
+ item["path"]
560
+ for item in capture.get("changed_files", [])
561
+ if isinstance(item, dict)
562
+ and isinstance(item.get("path"), str)
563
+ and (item.get("status", "").strip() == "D" or item.get("role") == "source")
564
+ }
565
+
566
+
506
567
  def validate_inventory(
507
568
  root: Path,
508
569
  project_root: Path,
@@ -569,6 +630,7 @@ def validate_inventory(
569
630
  require(plan.get("plan_inputs") == plan_inputs, "test plan inputs do not match target inventory", errors)
570
631
 
571
632
  patch = require_manifest_entry("source-change.patch", entry_map, root, errors, "source-change patch")
633
+ captured_deleted_paths = get_captured_deleted_paths(root)
572
634
  if patch is not None:
573
635
  require(manifest.get("working_diff_sha256") == file_sha256(patch), "working_diff_sha256 does not match source-change.patch", errors)
574
636
 
@@ -579,7 +641,19 @@ def validate_inventory(
579
641
  request = load_json(request_path)
580
642
  if isinstance(request, dict):
581
643
  require(request.get("changed_files") == inventory.get("changed_files"), "inventory request changed_files mismatch", errors)
582
- require(request.get("module_roots") == inventory.get("module_roots"), "inventory request module_roots mismatch", errors)
644
+ request_roots = request.get("module_roots")
645
+ normalized_request_roots = []
646
+ if isinstance(request_roots, list):
647
+ for item in request_roots:
648
+ if isinstance(item, str):
649
+ normalized_request_roots.append(item)
650
+ elif isinstance(item, dict) and isinstance(item.get("path"), str):
651
+ normalized_request_roots.append(item["path"])
652
+ require(
653
+ normalized_request_roots == inventory.get("module_roots"),
654
+ "inventory request module_roots mismatch",
655
+ errors,
656
+ )
583
657
  require(request.get("exclusions") == scope.get("exclusions"), "inventory request exclusions mismatch", errors)
584
658
  require(request.get("plan_inputs") == plan_inputs, "inventory request plan_inputs mismatch", errors)
585
659
  else:
@@ -594,6 +668,7 @@ def validate_scope(
594
668
  inventory_by_category: dict[str, dict[str, str]],
595
669
  project_root: Path,
596
670
  evidence_root: Path,
671
+ captured_deleted_paths: set[str],
597
672
  errors: list[str],
598
673
  ) -> None:
599
674
  changed_files = scope.get("changed_files")
@@ -614,8 +689,14 @@ def validate_scope(
614
689
  if relative in exclusion_paths:
615
690
  require(relative not in inventory_paths, f"excluded changed file is also inventoried: {relative}", errors)
616
691
  else:
617
- require(candidate is not None and candidate.is_file(), f"changed file is not live: {relative}", errors)
618
- require(relative in inventory_paths, f"changed file is neither inventoried nor excluded: {relative}", errors)
692
+ candidate = safe_path(project_root, relative, errors, "changed file")
693
+ deleted = relative in captured_deleted_paths
694
+ require(
695
+ deleted or (candidate is not None and candidate.is_file()),
696
+ f"changed file is not live: {relative}",
697
+ errors,
698
+ )
699
+ require(relative in inventory_paths or deleted, f"changed file is neither inventoried nor excluded: {relative}", errors)
619
700
 
620
701
  roots = scope.get("module_roots")
621
702
  require(isinstance(roots, list) and bool(roots), "scope module_roots is empty", errors)
@@ -695,7 +776,13 @@ def validate_requests_and_receipts(
695
776
  if not isinstance(request, dict):
696
777
  errors.append(f"request must be an object: {relative}")
697
778
  continue
698
- if "execution_request" in request:
779
+ if request.get("method") == "not-applicable":
780
+ validate_definition(request, "differentialRequest", records_schema, relative, errors)
781
+ not_applicable = request.get("not_applicable")
782
+ if isinstance(not_applicable, dict):
783
+ validate_differential_na(not_applicable, f"{relative}.not_applicable", errors)
784
+ differential_requests[relative] = request
785
+ elif "execution_request" in request:
699
786
  validate_definition(request, "differentialRequest", records_schema, relative, errors)
700
787
  nested = request.get("execution_request")
701
788
  if isinstance(nested, dict):
@@ -707,6 +794,17 @@ def validate_requests_and_receipts(
707
794
  validate_execution_request_semantics(request, relative, errors)
708
795
  validate_external_targets(request.get("external_targets"), f"{relative}.external_targets", errors)
709
796
  execution_requests[relative] = request
797
+ elif set(request) == {
798
+ "request_version", "change_class", "artifact_dir",
799
+ } or (
800
+ "request_version" in request
801
+ and "change_class" in request
802
+ and set(request) <= {
803
+ "request_version", "change_class", "baseline_commit", "scope_path",
804
+ "environment_path", "artifact_dir", "blockers", "repair_scope",
805
+ }
806
+ ):
807
+ require(request.get("request_version", "1.0") == "1.0", f"unsupported finalize request version: {relative}", errors)
710
808
  elif set(request) == {
711
809
  "request_version", "categories", "changed_files", "module_roots",
712
810
  "exclusions", "discovery_evidence", "plan_inputs",
@@ -922,12 +1020,18 @@ def validate_plan_and_scope_bindings(
922
1020
  project_relative = test.get("project_path")
923
1021
  inventory_relative = test.get("inventory_path")
924
1022
  snapshot_relative = test.get("snapshot_path")
1023
+ change_status = test.get("change_status", "modified" if isinstance(snapshot_relative, str) else "existing")
1024
+ require(change_status in {"existing", "added", "modified"}, f"planned test has invalid change_status: {test_id}", errors)
925
1025
  project_path = safe_path(project_root, project_relative, errors, f"planned test {test_id} project_path")
926
1026
  require(project_path is not None and project_path.is_file(), f"planned test live path is missing: {test_id}", errors)
927
1027
  require(inventory_relative == project_relative, f"planned test inventory path differs from live project path: {test_id}", errors)
928
1028
  require(inventory_relative in inventory_by_category.get("tests", {}), f"planned test is not in the test inventory: {test_id}", errors)
929
- snapshot_path = require_manifest_entry(snapshot_relative, entry_map, root, errors, f"planned test snapshot {test_id}") if isinstance(snapshot_relative, str) else None
930
- require(isinstance(snapshot_relative, str) and path_under(snapshot_relative, "generated-tests"), f"planned test snapshot must be under generated-tests/: {test_id}", errors)
1029
+ if change_status == "existing":
1030
+ require(snapshot_relative is None, f"existing test must not have a generated snapshot: {test_id}", errors)
1031
+ snapshot_path = None
1032
+ else:
1033
+ snapshot_path = require_manifest_entry(snapshot_relative, entry_map, root, errors, f"planned test snapshot {test_id}") if isinstance(snapshot_relative, str) else None
1034
+ require(isinstance(snapshot_relative, str) and path_under(snapshot_relative, "generated-tests"), f"planned test snapshot must be under generated-tests/: {test_id}", errors)
931
1035
  if project_path is not None and project_path.is_file() and snapshot_path is not None and snapshot_path.is_file():
932
1036
  require(file_sha256(project_path) == file_sha256(snapshot_path), f"generated test snapshot differs from live test: {test_id}", errors)
933
1037
  test_layers = test.get("layers")
@@ -1082,7 +1186,7 @@ def validate_differential_proofs(
1082
1186
  proof_by_behavior[behavior_id] = item
1083
1187
  classification = item.get("classification")
1084
1188
  if classification == "NOT_APPLICABLE":
1085
- structured_na(item.get("not_applicable"), records_schema, f"differential proof {behavior_id} not_applicable", errors)
1189
+ validate_differential_na(item.get("not_applicable"), f"differential proof {behavior_id} not_applicable", errors)
1086
1190
  for key in (
1087
1191
  "method", "differential_request_path", "differential_request_sha256", "baseline_commit",
1088
1192
  "current_tree_sha256", "baseline_execution_id", "mutation_execution_id", "current_execution_id",
@@ -1245,6 +1349,13 @@ def validate_verdict_and_attestation(
1245
1349
  require(bool(verdict_record.get("blockers")), "TEST_BLOCKED lacks blocker reasons", errors)
1246
1350
  elif verdict == "TEST_FAIL":
1247
1351
  require(bool(verdict_record.get("reproduced_failures")), "TEST_FAIL lacks reproduced failures", errors)
1352
+ require(
1353
+ verdict_record.get("repair_scope") in {
1354
+ "test-infrastructure", "production", "runtime", "schema", "dependency", "public-interface",
1355
+ },
1356
+ "TEST_FAIL lacks a valid evidence-backed repair_scope",
1357
+ errors,
1358
+ )
1248
1359
 
1249
1360
  if verify_attestation:
1250
1361
  validate_definition(validation, "validation", records_schema, "validation", errors)
@@ -1264,6 +1375,10 @@ def validate_verdict_and_attestation(
1264
1375
  require(validation.get("semantics") == "integrity-and-protocol-validation-not-hostile-producer-proof", "validation attestation semantics exceed integrity/protocol validation", errors)
1265
1376
  require(validation.get("replay_receipt_ids") == replayed_ids, "validation replay receipt IDs do not match actual replay executions", errors)
1266
1377
 
1378
+ if root.joinpath("lifecycle.json").is_file():
1379
+ expected_report = render_report_text(root, manifest, verdict_record, validation)
1380
+ require(report == expected_report, "derived report does not match canonical rendering", errors)
1381
+
1267
1382
  fragments = [
1268
1383
  f"Verdict: {verdict}", f"Evidence ID: {manifest.get('evidence_id')}",
1269
1384
  "Derived from structured evidence", "sensitivity=project-controlled",
@@ -1299,7 +1414,10 @@ def validate_evidence(
1299
1414
 
1300
1415
  entry_map = validate_manifest(root, manifest, manifest_schema, records_schema, errors, ignored_hash_paths)
1301
1416
  change_class = manifest.get("change_class")
1302
- required = COMMON_RECORDS | (BEHAVIOR_RECORDS if change_class == "behavior" else LIGHTWEIGHT_RECORDS)
1417
+ final_verdict = manifest.get("final_verdict")
1418
+ required = COMMON_RECORDS
1419
+ if final_verdict != "TEST_BLOCKED":
1420
+ required = required | (BEHAVIOR_RECORDS if change_class == "behavior" else LIGHTWEIGHT_RECORDS)
1303
1421
  for relative in required:
1304
1422
  require(relative in entry_map, f"required evidence record not hashed: {relative}", errors)
1305
1423
  validate_definition(classification, "changeClassification", records_schema, "change-classification", errors)
@@ -1322,7 +1440,10 @@ def validate_evidence(
1322
1440
 
1323
1441
  inventory_by_category, _ = validate_inventory(root, project_root, manifest, inventory, scope, plan, entry_map, errors)
1324
1442
  require(manifest.get("evidence_id_inputs", {}).get("scope_sha256") == canonical_sha256(scope), "scope identity hash mismatch", errors)
1325
- validate_scope(scope, inventory, inventory_by_category, project_root, root, errors)
1443
+ validate_scope(
1444
+ scope, inventory, inventory_by_category, project_root, root,
1445
+ get_captured_deleted_paths(root), errors,
1446
+ )
1326
1447
  execution_requests, differential_requests, executions = validate_requests_and_receipts(root, entry_map, records_schema, errors)
1327
1448
 
1328
1449
  if change_class == "behavior":
@@ -1373,6 +1494,64 @@ def validate_evidence(
1373
1494
  return errors
1374
1495
 
1375
1496
 
1497
+ def atomic_write_text(path: Path, value: str) -> None:
1498
+ path.parent.mkdir(parents=True, exist_ok=True)
1499
+ descriptor, temporary_name = tempfile.mkstemp(prefix=f".{path.name}.", suffix=".tmp", dir=path.parent)
1500
+ try:
1501
+ with os.fdopen(descriptor, "w", encoding="utf-8") as handle:
1502
+ handle.write(value)
1503
+ os.replace(temporary_name, path)
1504
+ except BaseException:
1505
+ try:
1506
+ os.unlink(temporary_name)
1507
+ except FileNotFoundError:
1508
+ pass
1509
+ raise
1510
+
1511
+
1512
+ def render_report_text(
1513
+ root: Path,
1514
+ manifest: dict[str, Any],
1515
+ verdict: dict[str, Any],
1516
+ validation: dict[str, Any],
1517
+ ) -> str:
1518
+ lifecycle_path = root / "lifecycle.json"
1519
+ lifecycle = load_json(lifecycle_path) if lifecycle_path.exists() else {}
1520
+ proof_path = root / "differential-proof.json"
1521
+ proof_record = load_json(proof_path) if proof_path.exists() else {}
1522
+ executions_record = load_json(root / "executions.json")
1523
+ proofs = proof_record.get("proofs", []) if isinstance(proof_record, dict) else []
1524
+ executions = executions_record if isinstance(executions_record, list) else []
1525
+ artifact_dir = lifecycle.get("artifact_dir") if isinstance(lifecycle, dict) else None
1526
+ lines = [
1527
+ "# Test Evidence Report",
1528
+ "",
1529
+ "Derived from structured evidence",
1530
+ f"Evidence ID: {manifest.get('evidence_id')}",
1531
+ f"Verdict: {verdict.get('verdict')}",
1532
+ f"Validator result: {validation.get('result', 'pending')}",
1533
+ "sensitivity=project-controlled",
1534
+ "The project owns access control, retention, and upload policy.",
1535
+ "Mocked code-level evidence does not verify a real deployed environment.",
1536
+ "",
1537
+ "## Scope",
1538
+ f"- Artifact Dir: {artifact_dir or 'not provided'}",
1539
+ "",
1540
+ f"Execution receipts: {len(executions)}",
1541
+ f"Differential proofs: {len(proofs) if isinstance(proofs, list) else 0}",
1542
+ "The legacy test-report interface is not supported.",
1543
+ "",
1544
+ ]
1545
+ return "\n".join(lines)
1546
+
1547
+
1548
+ def refresh_final_record_hashes(root: Path, manifest: dict[str, Any]) -> None:
1549
+ for entry in manifest.get("files", []):
1550
+ if isinstance(entry, dict) and entry.get("path") in FINAL_RECORDS:
1551
+ entry["sha256"] = file_sha256(root / entry["path"])
1552
+ atomic_write_json(root / "manifest.json", manifest)
1553
+
1554
+
1376
1555
  def main() -> int:
1377
1556
  parser = argparse.ArgumentParser(description="Validate a PrizmKit test evidence package")
1378
1557
  parser.add_argument("--evidence-dir", required=True, help="Path to .prizmkit/test/evidence/<evidence-id>")
@@ -1388,7 +1567,7 @@ def main() -> int:
1388
1567
  except IndexError:
1389
1568
  print("Test evidence validation: FAIL\n- cannot infer project root from evidence directory")
1390
1569
  return 1
1391
- errors = validate_evidence(root, project_root, FINAL_RECORDS if args.attest else None)
1570
+ errors = validate_evidence(root, project_root, ATTESTATION_MUTABLE if args.attest else None)
1392
1571
  if errors:
1393
1572
  print("Test evidence validation: FAIL")
1394
1573
  for error in errors:
@@ -1396,6 +1575,7 @@ def main() -> int:
1396
1575
  return 1
1397
1576
 
1398
1577
  manifest = load_json(root / "manifest.json")
1578
+ verdict_record = load_json(root / "verdict.json")
1399
1579
  if args.attest:
1400
1580
  executions = load_json(root / "executions.json")
1401
1581
  replayed_ids = [
@@ -1421,10 +1601,9 @@ def main() -> int:
1421
1601
  "replay_receipt_ids": replayed_ids,
1422
1602
  }
1423
1603
  atomic_write_json(root / "validation.json", validation)
1424
- for entry in manifest["files"]:
1425
- if entry["path"] in FINAL_RECORDS:
1426
- entry["sha256"] = file_sha256(root / entry["path"])
1427
- atomic_write_json(root / "manifest.json", manifest)
1604
+ report_text = render_report_text(root, manifest, verdict_record, validation)
1605
+ atomic_write_text(root / "test-report.md", report_text)
1606
+ refresh_final_record_hashes(root, manifest)
1428
1607
  errors = validate_evidence(root, project_root)
1429
1608
  if errors:
1430
1609
  print("Test evidence validation: FAIL")
@@ -0,0 +1,266 @@
1
+ ---
2
+ name: "prizmkit-workflow"
3
+ description: "Coordinate one explicitly requested formal software requirement through the complete PrizmKit lifecycle from plan to confirmed commit. Use when the user invokes prizmkit-workflow or explicitly asks for the full six-stage lifecycle, a plan-to-commit workflow, or one formal requirement completed end to end. Do not trigger on generic build, implement, feature, bug, or refactor requests; those belong to the applicable individual skill or an external integration. (project)"
4
+ ---
5
+
6
+ # PrizmKit Workflow
7
+
8
+ `/prizmkit-workflow` is the optional composite entry point for one interactive formal software requirement. The user describes the requirement once; this skill coordinates the six atomic lifecycle skills in order and preserves the same requirement context throughout. It does not replace or reimplement any atomic stage.
9
+
10
+ ```text
11
+ prizmkit-plan
12
+ → prizmkit-implement
13
+ → prizmkit-code-review
14
+ → prizmkit-test
15
+ → prizmkit-retrospective
16
+ → prizmkit-committer
17
+ ```
18
+
19
+ The six stages are mandatory for a formal requirement. The order is not a suggestion and no stage is silently optional.
20
+
21
+ ## When to Use
22
+
23
+ Use this composite entry point when the user:
24
+
25
+ - explicitly invokes `/prizmkit-workflow`;
26
+ - explicitly asks for the full six-stage PrizmKit lifecycle;
27
+ - asks for one formal requirement to be coordinated from plan through commit;
28
+ - asks for a plan-to-commit or single-requirement full lifecycle;
29
+ - wants the workflow to continue automatically after each successful stage.
30
+
31
+ Generic "implement", "build", "add feature", "fix bug", or "refactor" requests do not select this workflow by themselves. Use the applicable individual skill unless the user explicitly asks for this complete lifecycle; external integrations may also invoke individual skills through their published contracts.
32
+
33
+ Use an individual atomic stage skill when the user explicitly wants only planning, implementation, review, testing, documentation maintenance, or committing.
34
+
35
+ Do not use this skill for:
36
+
37
+ - a typo, pure formatting change, or explicitly low-risk direct edit;
38
+ - first-time project initialization only; recommend `/prizmkit-init`;
39
+ - standalone Prizm documentation repair; use `/prizmkit-prizm-docs`;
40
+ - deployment or operations; use `/prizmkit-deploy` separately;
41
+ - multiple requirements in one invocation; this composite handles one requirement only.
42
+
43
+ ## Inputs
44
+
45
+ Accept:
46
+
47
+ - `description`: the natural-language formal requirement;
48
+ - `artifact_dir`: optional explicit requirement artifact root;
49
+ - `execution_mode`: `interactive` by default; headless only when a trusted host explicitly authorizes it;
50
+ - `resume`: optional workflow state path or requirement slug for recovery.
51
+
52
+ If `description` is missing and no resumable workflow is supplied, ask for the requirement before invoking `prizmkit-plan`. External automation must invoke atomic stages directly with its own execution checkpoint rather than nesting this composite workflow.
53
+
54
+ ## Core Orchestration Rules
55
+
56
+ ### 1. Start with Plan
57
+
58
+ Invoke `/prizmkit-plan` with the requirement and any explicit `artifact_dir`. Do not write a second plan in this orchestrator.
59
+
60
+ If initialization context is missing, allow `prizmkit-plan` to recommend initialization and continue with source fallback when the user chooses to proceed. Initialization is a soft prerequisite, not a hidden lifecycle stage.
61
+
62
+ ### 2. Preserve Requirement Identity
63
+
64
+ Once `prizmkit-plan` resolves an `artifact_dir`, capture it and pass the exact same value to every later stage. The artifact root is generic and is not restricted to one directory family:
65
+
66
+ ```text
67
+ .prizmkit/specs/<requirement-slug>/
68
+ .prizmkit/bugfix/<bug-id>/
69
+ .prizmkit/refactor/<refactor-id>/
70
+ ```
71
+
72
+ Never select a different most-recent plan when resuming or handing off. The workflow state path is:
73
+
74
+ ```text
75
+ .prizmkit/state/workflows/<requirement-slug>.json
76
+ ```
77
+
78
+ Read `${SKILL_DIR}/references/workflow-state-protocol.md` for the shared state contract. This workflow state remains distinct from any external host execution checkpoint. The target project controls whether generated `.prizmkit/` files are committed, ignored, or shared; do not modify its Git policy.
79
+
80
+ ### 3. Advance Only on Truthful Success
81
+
82
+ After each atomic stage:
83
+
84
+ 1. Read the stage's actual output and terminal status.
85
+ 2. Validate the expected authoritative artifact or evidence and workflow-state transition.
86
+ 3. Preserve the same `artifact_dir`.
87
+ 4. Continue only on the permitted success status.
88
+ 5. Because this composite is the active orchestrator, atomic skills return terminal state and `next_stage` to it; they must not invoke the next stage recursively.
89
+ 6. If the host cannot invoke another skill automatically, stop with exactly one deterministic next skill, its `artifact_dir`, and the workflow-state path.
90
+
91
+ Expected transitions:
92
+
93
+ | Stage | Required success | Next stage |
94
+ |---|---|---|
95
+ | `prizmkit-plan` | `PLAN_READY` | `prizmkit-implement` |
96
+ | `prizmkit-implement` | `IMPLEMENTED` | `prizmkit-code-review` |
97
+ | `prizmkit-code-review` | `REVIEW_PASS` | `prizmkit-test` |
98
+ | `prizmkit-test` | `TEST_PASS` | `prizmkit-retrospective` |
99
+ | `prizmkit-retrospective` | `status=RETRO_COMPLETE` with result `DOCS_UPDATED` or `NO_DOC_CHANGE` | `prizmkit-committer` |
100
+ | `prizmkit-committer` | explicit interactive confirmation, then `COMMITTED` | end |
101
+
102
+ `TEST_NOT_APPLICABLE` is not a valid lifecycle success. Lightweight changes must execute deterministic verification and return `TEST_PASS`.
103
+
104
+ ### 4. Do Not Duplicate Stage Responsibilities
105
+
106
+ The composite must not:
107
+
108
+ - reinterpret a plan as implementation;
109
+ - repair production code outside `prizmkit-implement` or the Main-Agent review loop;
110
+ - claim tests passed without validated authoritative test evidence;
111
+ - treat a derived Markdown report as the test source of truth;
112
+ - perform retrospective documentation changes itself;
113
+ - stage or commit before `prizmkit-committer` applies the current execution's authorization boundary;
114
+ - invoke `prizmkit-deploy` as a hidden seventh stage.
115
+
116
+ ## Failure and Repair Routing
117
+
118
+ Use the shared workflow state and authoritative stage evidence to determine routing. Do not blindly retry every failure.
119
+
120
+ ### Review Failure
121
+
122
+ `REVIEW_NEEDS_FIXES` maps from the final review report result `NEEDS_FIXES`. Route to:
123
+
124
+ ```text
125
+ prizmkit-implement
126
+ → prizmkit-code-review
127
+ → prizmkit-test
128
+ ```
129
+
130
+ The Main-Agent review skill owns its internal review repairs and its internal ten-round limit before returning its terminal result. The outer workflow repair counter is separate.
131
+
132
+ ### Test Failure
133
+
134
+ `TEST_FAIL` must come from the validated authoritative evidence verdict and include `repair_scope`:
135
+
136
+ TEST_FAIL affecting only tests, fixtures, test-runner configuration, or evidence infrastructure uses `repair_scope=test-infrastructure`:
137
+
138
+ ```text
139
+ TEST_FAIL with repair_scope=test-infrastructure
140
+ → prizmkit-implement
141
+ → prizmkit-test
142
+ ```
143
+
144
+ Test infrastructure includes tests, fixtures, test-runner configuration, and evidence infrastructure only.
145
+
146
+ TEST_FAIL affecting production code, runtime configuration, schema, dependencies, or public interfaces uses the matching production-family scope:
147
+
148
+ ```text
149
+ TEST_FAIL with repair_scope=production | runtime | schema | dependency | public-interface
150
+ → prizmkit-implement
151
+ → prizmkit-code-review
152
+ → prizmkit-test
153
+ ```
154
+
155
+ Production-affecting repair after `REVIEW_PASS` must receive another review. A test-infrastructure-only repair may return directly to testing. Unknown scope uses the conservative production route only when safe; otherwise the workflow is blocked.
156
+
157
+ ### Environment Block
158
+
159
+ `TEST_BLOCKED` means trustworthy evidence is unavailable because of environment, permission, dependency, external-service, scope, evidence, reliability, cleanup, or budget problems.
160
+
161
+ Interactive behavior:
162
+
163
+ ```text
164
+ TEST_BLOCKED
165
+ → persist the blocker
166
+ → do not make speculative production edits
167
+ → stop with a deterministic prizmkit-test resume entry
168
+ ```
169
+
170
+ A trusted headless host performs its own bounded automatic environment recovery when invoking atomic stages. It does not invoke this composite workflow or silently turn a blocked result into success.
171
+
172
+ ### Repair Limit
173
+
174
+ The outer orchestrator allows at most three automatic repair rounds. These are cross-stage rounds: one repair route from `implement` through all gates required by the repair scope.
175
+
176
+ ```text
177
+ repair_round: 0 → 1 → 2 → 3
178
+ ```
179
+
180
+ When the limit is reached:
181
+
182
+ - set workflow status to `WORKFLOW_BLOCKED`;
183
+ - preserve the latest authoritative failure evidence;
184
+ - report completed rounds and unresolved cause;
185
+ - report the exact skill, `artifact_dir`, and state path from which a user may resume after resolving the cause or explicitly authorizing another attempt;
186
+ - do not claim the requirement is complete.
187
+
188
+ The internal `prizmkit-code-review` limit of ten completed review rounds remains separate and does not increment `repair_round`.
189
+
190
+ ## Commit Authorization Boundary
191
+
192
+ The composite may automatically reach `/prizmkit-committer`, but it must not silently create a Git commit.
193
+
194
+ Interactive execution requires the committer to:
195
+
196
+ 1. verify all five preceding stage results for the same `artifact_dir`;
197
+ 2. inspect the final workspace;
198
+ 3. present intended files, diff summary, sensitive-file warnings, and the proposed Conventional Commit message;
199
+ 4. wait for explicit user confirmation from the current user;
200
+ 5. create and verify the local commit only after confirmation.
201
+
202
+ Trusted headless execution is a separate atomic-stage path. It requires a host-defined non-interactive `mode`, a trusted `owner` identifier, and `local_commit_authorized=true`; it does not ask or wait. Unknown headless contexts are blocked. Remote publication is a separate host-runtime operation and is never decided by this composite workflow.
203
+
204
+ ## Automatic Handoff and Manual Fallback
205
+
206
+ When the host supports semantic skill-to-skill invocation, the active composite invokes the next atomic stage after each permitted success transition.
207
+
208
+ When it does not:
209
+
210
+ 1. update or verify workflow state;
211
+ 2. stop without claiming the next stage ran;
212
+ 3. print one exact recovery instruction:
213
+
214
+ ```text
215
+ Next stage: /prizmkit-<skill>
216
+ artifact_dir: <same resolved artifact_dir>
217
+ workflow_state: .prizmkit/state/workflows/<requirement-slug>.json
218
+ ```
219
+
220
+ The user can invoke that one atomic skill and this composite can resume with `resume` later.
221
+
222
+ ## Resume Protocol
223
+
224
+ On resume:
225
+
226
+ 1. Read the workflow state specified by `resume` or discover the target project's active workflow state.
227
+ 2. Read `${SKILL_DIR}/references/workflow-state-protocol.md`.
228
+ 3. Verify `spec.md`, `plan.md`, review report, authoritative test evidence, retrospective result, and current workspace against state.
229
+ 4. If state is missing or stale, reconstruct the safest recoverable predecessor and report the reconstruction.
230
+ 5. Continue from the first incomplete stage; never bypass a required gate based only on stale state.
231
+ 6. Preserve the same `artifact_dir` and repair-round count.
232
+ 7. When an external host is involved, let it validate its own checkpoint independently; never merge that checkpoint into workflow state.
233
+
234
+ ## Completion Report
235
+
236
+ At successful completion, report:
237
+
238
+ ```text
239
+ WORKFLOW_COMPLETE
240
+ artifact_dir: <path>
241
+ stages:
242
+ - PLAN_READY
243
+ - IMPLEMENTED
244
+ - REVIEW_PASS
245
+ - TEST_PASS
246
+ - RETRO_COMPLETE (DOCS_UPDATED | NO_DOC_CHANGE)
247
+ - COMMITTED
248
+ commit: <hash>
249
+ push: not performed automatically
250
+ next_action: invoke /prizmkit-deploy separately if deployment is needed
251
+ ```
252
+
253
+ If the user declines interactive commit confirmation, report `COMMIT_PENDING` rather than `WORKFLOW_COMPLETE` and provide the exact `/prizmkit-committer` resume entry.
254
+
255
+ If blocked, report:
256
+
257
+ ```text
258
+ WORKFLOW_BLOCKED
259
+ stage: <stage>
260
+ reason: <evidence-backed reason>
261
+ repair_round: <0..3>
262
+ resume_from: <skill>
263
+ artifact_dir: <same path>
264
+ ```
265
+
266
+ Never report success for a stage that did not produce its required terminal result.