prizmkit 1.1.130 → 1.1.131

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 (88) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/.env.example +1 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +445 -8
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +87 -66
  5. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +53 -3
  6. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +55 -25
  7. package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +4 -0
  8. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +15 -1
  9. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +396 -4
  10. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +54 -1
  11. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +54 -1
  12. package/bundled/dev-pipeline/scripts/prompt_framework.py +152 -1
  13. package/bundled/dev-pipeline/scripts/update-bug-status.py +53 -0
  14. package/bundled/dev-pipeline/scripts/update-checkpoint.py +294 -69
  15. package/bundled/dev-pipeline/scripts/update-feature-status.py +57 -0
  16. package/bundled/dev-pipeline/scripts/update-refactor-status.py +53 -0
  17. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
  18. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +20 -14
  19. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +21 -4
  20. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +18 -0
  21. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
  22. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +5 -15
  23. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +21 -0
  24. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +10 -10
  25. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +16 -0
  26. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +26 -23
  27. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -8
  28. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
  29. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +21 -0
  30. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +362 -3
  31. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +310 -22
  32. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +132 -0
  33. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +91 -0
  34. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +411 -18
  35. package/bundled/dev-pipeline/tests/test_unified_cli.py +3 -3
  36. package/bundled/skills/_metadata.json +62 -25
  37. package/bundled/skills/app-planner/SKILL.md +4 -3
  38. package/bundled/skills/bug-fix-workflow/SKILL.md +89 -180
  39. package/bundled/skills/bug-planner/SKILL.md +12 -27
  40. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +131 -220
  41. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +36 -90
  42. package/bundled/skills/feature-pipeline-launcher/SKILL.md +129 -271
  43. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +35 -76
  44. package/bundled/skills/feature-planner/SKILL.md +27 -22
  45. package/bundled/skills/feature-planner/assets/planning-guide.md +4 -4
  46. package/bundled/skills/feature-workflow/SKILL.md +94 -121
  47. package/bundled/skills/prizmkit/SKILL.md +130 -94
  48. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +181 -0
  49. package/bundled/skills/prizmkit-code-review/SKILL.md +114 -84
  50. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -1
  51. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +181 -0
  52. package/bundled/skills/prizmkit-committer/SKILL.md +131 -47
  53. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +181 -0
  54. package/bundled/skills/prizmkit-implement/SKILL.md +99 -73
  55. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +1 -1
  56. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +181 -0
  57. package/bundled/skills/prizmkit-init/SKILL.md +5 -5
  58. package/bundled/skills/prizmkit-plan/SKILL.md +132 -105
  59. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +181 -0
  60. package/bundled/skills/prizmkit-retrospective/SKILL.md +111 -53
  61. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +7 -6
  62. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +5 -4
  63. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +181 -0
  64. package/bundled/skills/prizmkit-test/SKILL.md +28 -6
  65. package/bundled/skills/prizmkit-test/references/examples.md +1 -1
  66. package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
  67. package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +181 -0
  68. package/bundled/skills/prizmkit-workflow/SKILL.md +266 -0
  69. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +181 -0
  70. package/bundled/skills/recovery-workflow/SKILL.md +62 -241
  71. package/bundled/skills/recovery-workflow/evals/evals.json +17 -33
  72. package/bundled/skills/recovery-workflow/references/detection.md +28 -52
  73. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +105 -439
  74. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +136 -275
  75. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +53 -88
  76. package/bundled/skills/refactor-planner/SKILL.md +12 -57
  77. package/bundled/skills/refactor-planner/references/behavior-preservation.md +111 -230
  78. package/bundled/skills/refactor-planner/references/error-recovery.md +37 -0
  79. package/bundled/skills/refactor-planner/references/planning-phases.md +11 -17
  80. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +1 -1
  81. package/bundled/skills/refactor-workflow/SKILL.md +98 -139
  82. package/bundled/templates/project-memory-template.md +4 -4
  83. package/package.json +1 -1
  84. package/src/index.js +4 -0
  85. package/src/prompts.js +10 -3
  86. package/src/scaffold.js +1 -1
  87. package/bundled/dev-pipeline/tests/test-deploy-safety.sh +0 -223
  88. package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
@@ -1,6 +1,7 @@
1
1
  """Tests for generate-bootstrap-prompt.py core functions."""
2
2
 
3
3
  import json
4
+ import hashlib
4
5
  import importlib.util
5
6
  import os
6
7
  import re
@@ -27,6 +28,9 @@ from generate_bootstrap_prompt import (
27
28
  merge_checkpoint_state,
28
29
  load_active_agent_prompts,
29
30
  validate_rendered,
31
+ validate_feature_test_evidence,
32
+ route_feature_repair,
33
+ feature_checkpoint_metadata,
30
34
  main as generate_bootstrap_main,
31
35
  )
32
36
  from continuation import append_continuation_handoff, checkpoint_cursor
@@ -157,7 +161,7 @@ def scoped_report_text(verdict="PASS", boundary_missing=0,
157
161
 
158
162
 
159
163
  def write_fresh_scoped_report(project_root, report_path, report_text=None):
160
- """Write marker, in-scope files, and a fresh scoped report."""
164
+ """Write marker, in-scope files, and a fresh derived report."""
161
165
  source = project_root / "src" / "feature.js"
162
166
  test = project_root / "tests" / "feature.test.js"
163
167
  marker = project_root / ".prizmkit" / "specs" / "123-scope-test" / ".prizmkit-test-started"
@@ -173,6 +177,135 @@ def write_fresh_scoped_report(project_root, report_path, report_text=None):
173
177
  return source, test, marker
174
178
 
175
179
 
180
+ def write_fresh_structured_evidence(project_root):
181
+ """Create the smallest protocol-shaped, validator-attested pass package."""
182
+ source, test, marker = write_fresh_scoped_report(
183
+ project_root,
184
+ project_root / ".prizmkit" / "test" / "unused" / "test-report.md",
185
+ )
186
+ evidence_base = project_root / ".prizmkit" / "test" / "evidence"
187
+ environment = {}
188
+ plan_inputs = {"feature": "123-scope-test"}
189
+ categories = {
190
+ "source": [{"path": "src/feature.js", "sha256": hashlib.sha256(source.read_bytes()).hexdigest()}],
191
+ "tests": [{"path": "tests/feature.test.js", "sha256": hashlib.sha256(test.read_bytes()).hexdigest()}],
192
+ "contracts": [],
193
+ "lockfiles": [],
194
+ }
195
+ target_hashes = {
196
+ category: hashlib.sha256(json.dumps(
197
+ sorted(records, key=lambda item: item["path"]),
198
+ sort_keys=True, separators=(",", ":"),
199
+ ).encode()).hexdigest()
200
+ for category, records in categories.items()
201
+ }
202
+ target_hashes["environment"] = hashlib.sha256(b"{}").hexdigest()
203
+ target_hashes["plan"] = hashlib.sha256(json.dumps(
204
+ plan_inputs, sort_keys=True, separators=(",", ":"),
205
+ ).encode()).hexdigest()
206
+ inventory = {
207
+ "categories": categories,
208
+ "plan_inputs": plan_inputs,
209
+ }
210
+ scope = {"feature": "123-scope-test"}
211
+ zero_hash = "0" * 64
212
+ identity = {
213
+ "baseline_commit": "baseline",
214
+ "working_diff_sha256": zero_hash,
215
+ "scope_sha256": hashlib.sha256(json.dumps(
216
+ scope, sort_keys=True, separators=(",", ":"),
217
+ ).encode()).hexdigest(),
218
+ }
219
+ evidence_id = hashlib.sha256(json.dumps(
220
+ identity, sort_keys=True, separators=(",", ":"),
221
+ ).encode()).hexdigest()
222
+ evidence_root = evidence_base / evidence_id
223
+ evidence_root.mkdir(parents=True, exist_ok=True)
224
+ (evidence_root / "environment.json").write_text("{}\n", encoding="utf-8")
225
+ (evidence_root / "target-inventory.json").write_text(
226
+ json.dumps(inventory), encoding="utf-8",
227
+ )
228
+ (evidence_root / "scope.json").write_text(json.dumps(scope), encoding="utf-8")
229
+ verdict = {
230
+ "verdict": "TEST_PASS",
231
+ "testing_domain_only": True,
232
+ "authorizes_commit_or_release": False,
233
+ "real_environment_validated": False,
234
+ "repairs_business_defects": False,
235
+ "code_evidence_replayable": True,
236
+ "blockers": [],
237
+ "reproduced_failures": [],
238
+ }
239
+ (evidence_root / "verdict.json").write_text(json.dumps(verdict), encoding="utf-8")
240
+ report = scoped_report_text()
241
+ (evidence_root / "test-report.md").write_text(report, encoding="utf-8")
242
+ stages = []
243
+ stage_names = [
244
+ "CHANGE_CLASSIFY", "SCOPE_DISCOVER", "CONTRACT_MODEL", "TEST_PLAN",
245
+ "INFRA_READY", "TEST_BUILD", "EXECUTE_PROVE", "EVIDENCE_PACKAGE",
246
+ "EVIDENCE_VALIDATE",
247
+ ]
248
+ for name in stage_names:
249
+ stages.append({"name": name, "status": "complete", "input_sha256": zero_hash, "outputs": []})
250
+ manifest = {
251
+ "protocol_version": "1.0",
252
+ "evidence_id": evidence_id,
253
+ "evidence_id_inputs": identity,
254
+ "sensitivity": "project-controlled",
255
+ "environment_claim": "mocked-code-level-only",
256
+ "compatibility": "legacy-test-report-interface-not-supported",
257
+ "change_class": "lightweight",
258
+ "baseline_commit": "baseline",
259
+ "working_diff_sha256": zero_hash,
260
+ "target_hashes": target_hashes,
261
+ "stages": stages,
262
+ "files": [],
263
+ "final_verdict": "TEST_PASS",
264
+ }
265
+ file_records = []
266
+ for path in sorted(evidence_root.rglob("*")):
267
+ if path.is_file():
268
+ relative = path.relative_to(evidence_root).as_posix()
269
+ file_records.append({
270
+ "path": relative,
271
+ "sha256": hashlib.sha256(path.read_bytes()).hexdigest(),
272
+ "produced_by": "EVIDENCE_PACKAGE",
273
+ })
274
+ payload = sorted(
275
+ ({"path": item["path"], "sha256": item["sha256"]}
276
+ for item in file_records
277
+ if item["path"] not in {"verdict.json", "test-report.md"}),
278
+ key=lambda item: item["path"],
279
+ )
280
+ validation = {
281
+ "validator": "validate_test_evidence.py",
282
+ "validator_sha256": zero_hash,
283
+ "payload_sha256": hashlib.sha256(json.dumps(
284
+ payload, sort_keys=True, separators=(",", ":"),
285
+ ).encode()).hexdigest(),
286
+ "result": "passed",
287
+ "verdict": "TEST_PASS",
288
+ "semantics": "integrity-and-protocol-validation-not-hostile-producer-proof",
289
+ "replay_receipt_ids": [],
290
+ }
291
+ (evidence_root / "validation.json").write_text(json.dumps(validation), encoding="utf-8")
292
+ file_records.append({
293
+ "path": "validation.json",
294
+ "sha256": hashlib.sha256((evidence_root / "validation.json").read_bytes()).hexdigest(),
295
+ "produced_by": "EVIDENCE_VALIDATE",
296
+ })
297
+ manifest["files"] = file_records
298
+ (evidence_root / "manifest.json").write_text(json.dumps(manifest), encoding="utf-8")
299
+ pointer = project_root / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
300
+ pointer.parent.mkdir(parents=True, exist_ok=True)
301
+ pointer.write_text(str(evidence_root / "test-report.md"), encoding="utf-8")
302
+ report_path = evidence_root / "test-report.md"
303
+ now = time.time()
304
+ os.utime(marker, (now, now))
305
+ os.utime(report_path, (now + 1, now + 1))
306
+ return evidence_root
307
+
308
+
176
309
  # ---------------------------------------------------------------------------
177
310
  # compute_feature_slug
178
311
  # ---------------------------------------------------------------------------
@@ -469,6 +602,13 @@ class TestProcessModeBlocks:
469
602
  # ---------------------------------------------------------------------------
470
603
 
471
604
  class TestScopedFeatureTestGate:
605
+ @staticmethod
606
+ def _trust_fixture_validator(monkeypatch):
607
+ monkeypatch.setattr(
608
+ "generate_bootstrap_prompt._evidence_validator_passes",
609
+ lambda _project_root, _evidence_root, _validation: True,
610
+ )
611
+
472
612
  def test_standard_sections_insert_prizmkit_test_after_review(self):
473
613
  sections_dir = Path(__file__).resolve().parents[1] / "templates" / "sections"
474
614
  sections = assemble_sections(
@@ -561,7 +701,9 @@ class TestScopedFeatureTestGate:
561
701
  test_step = next(step for step in checkpoint["steps"] if step["skill"] == "prizmkit-test")
562
702
  assert test_step["required_artifacts"] == [
563
703
  ".prizmkit/specs/123-scope-test/test-report-path.txt",
564
- ".prizmkit/test/*/test-report.md",
704
+ ".prizmkit/test/evidence/*/manifest.json",
705
+ ".prizmkit/test/evidence/*/verdict.json",
706
+ ".prizmkit/test/evidence/*/validation.json",
565
707
  ]
566
708
 
567
709
  def test_merge_resets_completed_prizmkit_test_without_report_artifact(self, tmp_path):
@@ -596,19 +738,19 @@ class TestScopedFeatureTestGate:
596
738
 
597
739
  assert [step["status"] for step in merged["steps"]] == ["pending", "pending"]
598
740
 
599
- def test_merge_keeps_completed_prizmkit_test_only_for_scoped_pass_report(self, tmp_path):
600
- report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
601
- write_fresh_scoped_report(tmp_path, report)
741
+ def test_merge_keeps_completed_prizmkit_test_only_for_scoped_pass_report(self, tmp_path, monkeypatch):
742
+ self._trust_fixture_validator(monkeypatch)
743
+ evidence_root = write_fresh_structured_evidence(tmp_path)
602
744
  pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
603
745
  pointer.parent.mkdir(parents=True, exist_ok=True)
604
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
746
+ pointer.write_text(str(evidence_root / "test-report.md") + "\n", encoding="utf-8")
605
747
  existing = {
606
748
  "steps": [
607
- {"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
749
+ {"id": "S01", "skill": "prizmkit-test", "status": "completed", "stage_result": "TEST_PASS", "required_artifacts": [
608
750
  ".prizmkit/specs/123-scope-test/test-report-path.txt",
609
751
  ".prizmkit/test/*/test-report.md",
610
752
  ]},
611
- {"id": "S02", "skill": "prizmkit-code-review", "status": "completed", "required_artifacts": []},
753
+ {"id": "S02", "skill": "prizmkit-code-review", "status": "completed", "stage_result": "REVIEW_PASS", "required_artifacts": []},
612
754
  ],
613
755
  }
614
756
  fresh = {
@@ -834,25 +976,171 @@ class TestScopedFeatureTestGate:
834
976
 
835
977
  assert merged["steps"][0]["status"] == "pending"
836
978
 
837
- def test_merge_resets_completed_prizmkit_test_without_in_scope_sources(self, tmp_path):
979
+ def test_structured_evidence_accepts_current_matching_package(self, tmp_path, monkeypatch):
980
+ self._trust_fixture_validator(monkeypatch)
981
+ evidence_root = write_fresh_structured_evidence(tmp_path)
982
+
983
+ evidence = validate_feature_test_evidence(
984
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
985
+ )
986
+
987
+ assert evidence is not None
988
+ assert evidence["verdict"] == "TEST_PASS"
989
+ assert evidence["manifest_path"] == str(
990
+ (evidence_root / "manifest.json").relative_to(tmp_path),
991
+ )
992
+ assert evidence["validation_path"].endswith("/validation.json")
993
+
994
+ def test_structured_evidence_requires_authoritative_validator(self, tmp_path, monkeypatch):
995
+ write_fresh_structured_evidence(tmp_path)
996
+ monkeypatch.setattr(
997
+ "generate_bootstrap_prompt._evidence_validator_passes",
998
+ lambda _project_root, _evidence_root, _validation: False,
999
+ )
1000
+
1001
+ assert validate_feature_test_evidence(
1002
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1003
+ ) is None
1004
+
1005
+ def test_structured_evidence_rejects_legacy_markdown_only_pass(self, tmp_path):
838
1006
  report = tmp_path / ".prizmkit" / "test" / "run" / "test-report.md"
839
- report_text = scoped_report_text().replace("- In-Scope Source Files:\n - src/feature.js\n", "- In-Scope Source Files:\n")
840
- write_fresh_scoped_report(tmp_path, report, report_text)
1007
+ write_fresh_scoped_report(tmp_path, report)
841
1008
  pointer = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / "test-report-path.txt"
842
- pointer.write_text(".prizmkit/test/run/test-report.md\n", encoding="utf-8")
843
- existing = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "completed", "required_artifacts": [
844
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
845
- ".prizmkit/test/*/test-report.md",
846
- ]}]}
847
- fresh = {"steps": [{"id": "S01", "skill": "prizmkit-test", "status": "pending", "required_artifacts": [
848
- ".prizmkit/specs/123-scope-test/test-report-path.txt",
849
- ".prizmkit/test/*/test-report.md",
850
- ]}]}
1009
+ pointer.write_text(str(report), encoding="utf-8")
1010
+
1011
+ assert validate_feature_test_evidence(
1012
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1013
+ ) is None
1014
+
1015
+ def test_structured_evidence_rejects_missing_validation(self, tmp_path):
1016
+ evidence_root = write_fresh_structured_evidence(tmp_path)
1017
+ (evidence_root / "validation.json").unlink()
1018
+
1019
+ assert validate_feature_test_evidence(
1020
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1021
+ ) is None
1022
+
1023
+ def test_structured_evidence_rejects_tampered_verdict(self, tmp_path, monkeypatch):
1024
+ self._trust_fixture_validator(monkeypatch)
1025
+ evidence_root = write_fresh_structured_evidence(tmp_path)
1026
+ verdict_path = evidence_root / "verdict.json"
1027
+ verdict = json.loads(verdict_path.read_text(encoding="utf-8"))
1028
+ verdict["verdict"] = "TEST_BLOCKED"
1029
+ verdict_path.write_text(json.dumps(verdict), encoding="utf-8")
1030
+
1031
+ assert validate_feature_test_evidence(
1032
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1033
+ ) is None
1034
+
1035
+ def test_structured_evidence_rejects_wrong_artifact_scope(self, tmp_path, monkeypatch):
1036
+ self._trust_fixture_validator(monkeypatch)
1037
+ evidence_root = write_fresh_structured_evidence(tmp_path)
1038
+ report_path = evidence_root / "test-report.md"
1039
+ report_path.write_text(
1040
+ scoped_report_text().replace(
1041
+ ".prizmkit/specs/123-scope-test/",
1042
+ ".prizmkit/specs/999-other/",
1043
+ ),
1044
+ encoding="utf-8",
1045
+ )
851
1046
 
852
- merged = merge_checkpoint_state(existing, fresh, str(tmp_path))
1047
+ assert validate_feature_test_evidence(
1048
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1049
+ ) is None
853
1050
 
854
- assert merged["steps"][0]["status"] == "pending"
1051
+ def test_structured_evidence_rejects_stale_report(self, tmp_path, monkeypatch):
1052
+ self._trust_fixture_validator(monkeypatch)
1053
+ evidence_root = write_fresh_structured_evidence(tmp_path)
1054
+ report = evidence_root / "test-report.md"
1055
+ marker = tmp_path / ".prizmkit" / "specs" / "123-scope-test" / ".prizmkit-test-started"
1056
+ now = time.time()
1057
+ os.utime(report, (now, now))
1058
+ os.utime(marker, (now + 10, now + 10))
1059
+
1060
+ assert validate_feature_test_evidence(
1061
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1062
+ ) is None
1063
+
1064
+ def test_structured_evidence_rejects_live_inventory_drift(self, tmp_path, monkeypatch):
1065
+ self._trust_fixture_validator(monkeypatch)
1066
+ write_fresh_structured_evidence(tmp_path)
1067
+ source = tmp_path / "src" / "feature.js"
1068
+ source.write_text("export const feature = false;\n", encoding="utf-8")
1069
+
1070
+ assert validate_feature_test_evidence(
1071
+ str(tmp_path), ".prizmkit/specs/123-scope-test",
1072
+ ) is None
1073
+
1074
+ def test_feature_repair_routing_preserves_review_and_test_boundaries(self):
1075
+ review = route_feature_repair("REVIEW_NEEDS_FIXES", repair_round=0)
1076
+ test_infra = route_feature_repair(
1077
+ "TEST_FAIL", "test-infrastructure", repair_round=1,
1078
+ )
1079
+ production = route_feature_repair("TEST_FAIL", "production", repair_round=1)
855
1080
 
1081
+ assert review["route"] == [
1082
+ "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
1083
+ ]
1084
+ assert test_infra["route"] == ["prizmkit-implement", "prizmkit-test"]
1085
+ assert production["route"] == [
1086
+ "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
1087
+ ]
1088
+ assert review["repair_round"] == 1
1089
+
1090
+ def test_feature_repair_routing_blocks_unsafe_and_exhausted_routes(self):
1091
+ unknown = route_feature_repair("TEST_FAIL", "unknown", repair_round=1)
1092
+ blocked = route_feature_repair("TEST_BLOCKED", "production", repair_round=1)
1093
+ exhausted = route_feature_repair("TEST_FAIL", "production", repair_round=3)
1094
+
1095
+ assert unknown["blocked"] is True
1096
+ assert unknown["route"] == []
1097
+ assert blocked["blocked"] is True
1098
+ assert blocked["resumable"] is False
1099
+ assert blocked["next_stage"] is None
1100
+ assert blocked["terminal_status"] == "WORKFLOW_BLOCKED"
1101
+ assert blocked["blocked_reason"] == "test_blocked"
1102
+ assert exhausted["blocked"] is True
1103
+ assert exhausted["repair_round"] == 3
1104
+
1105
+ def test_feature_checkpoint_metadata_records_l1_handoff_fields(self):
1106
+ evidence = {
1107
+ "manifest_path": ".prizmkit/test/evidence/id/manifest.json",
1108
+ "verdict_path": ".prizmkit/test/evidence/id/verdict.json",
1109
+ "validation_path": ".prizmkit/test/evidence/id/validation.json",
1110
+ }
1111
+
1112
+ metadata = feature_checkpoint_metadata(
1113
+ ".prizmkit/specs/123-scope-test",
1114
+ evidence=evidence,
1115
+ stage="test",
1116
+ status="TEST_FAIL",
1117
+ stage_result="TEST_FAIL",
1118
+ repair_scope="test-infrastructure",
1119
+ repair_round=2,
1120
+ next_stage="prizmkit-implement",
1121
+ resume_from="prizmkit-implement",
1122
+ )
1123
+
1124
+ assert metadata == {
1125
+ "schema_version": 1,
1126
+ "artifact_dir": ".prizmkit/specs/123-scope-test",
1127
+ "orchestrator": "prizmkit-l4",
1128
+ "stage": "test",
1129
+ "current_stage": "test",
1130
+ "status": "TEST_FAIL",
1131
+ "stage_result": "TEST_FAIL",
1132
+ "repair_scope": "test-infrastructure",
1133
+ "repair_round": 2,
1134
+ "authoritative_evidence_paths": {
1135
+ "manifest": ".prizmkit/test/evidence/id/manifest.json",
1136
+ "verdict": ".prizmkit/test/evidence/id/verdict.json",
1137
+ "validation": ".prizmkit/test/evidence/id/validation.json",
1138
+ },
1139
+ "next_stage": "prizmkit-implement",
1140
+ "resume_from": "prizmkit-implement",
1141
+ "blocked_reason": None,
1142
+ "terminal_status": None,
1143
+ }
856
1144
 
857
1145
  # ---------------------------------------------------------------------------
858
1146
  # Active compaction harness rollback
@@ -13,8 +13,12 @@ from generate_bugfix_prompt import (
13
13
  format_error_source_details,
14
14
  format_environment,
15
15
  generate_bugfix_checkpoint,
16
+ bugfix_checkpoint_metadata,
17
+ merge_bugfix_checkpoint_state,
18
+ route_bugfix_repair,
16
19
  process_conditional_blocks,
17
20
  render_template,
21
+ assemble_bugfix_sections,
18
22
  )
19
23
  from continuation import append_continuation_handoff
20
24
 
@@ -312,6 +316,11 @@ def test_template_source_is_clean_for_headless_prompt_contract():
312
316
  content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
313
317
  for forbidden in HEADLESS_FORBIDDEN_PROMPT_STRINGS:
314
318
  assert forbidden not in content
319
+ assert "FAST_PATH=true" not in content
320
+ assert "prizmkit-workflow" not in content
321
+ assert "/prizmkit-test scope=this-change artifact_dir=.prizmkit/bugfix/{{BUG_ID}}/" in content
322
+ assert "local_commit_authorized=true" in content
323
+ assert "push_authorized=false" in content
315
324
 
316
325
 
317
326
  def test_bugfix_prompt_keeps_fine_grained_phases_out_of_external_status_contract():
@@ -455,6 +464,129 @@ class TestBugfixBootstrapShellExtraction:
455
464
 
456
465
 
457
466
  class TestBugfixCheckpointGeneration:
467
+ def test_checkpoint_merge_preserves_family_handoff_state(self, tmp_path):
468
+ fresh = generate_bugfix_checkpoint("B-001", "new")
469
+ existing = generate_bugfix_checkpoint("B-001", "old")
470
+ existing["bugfix_state"] = bugfix_checkpoint_metadata(
471
+ ".prizmkit/bugfix/B-001",
472
+ stage="test",
473
+ status="TEST_BLOCKED",
474
+ stage_result="TEST_BLOCKED",
475
+ repair_round=2,
476
+ next_stage="prizmkit-test",
477
+ resume_from="prizmkit-test",
478
+ )
479
+
480
+ merged = merge_bugfix_checkpoint_state(existing, fresh, str(tmp_path))
481
+
482
+ assert merged["bugfix_state"]["stage_result"] == "TEST_BLOCKED"
483
+ assert merged["bugfix_state"]["resume_from"] == "prizmkit-test"
484
+
485
+ def test_default_checkpoint_has_all_atomic_gates_in_order(self):
486
+ checkpoint = generate_bugfix_checkpoint("B-001", "session-1")
487
+
488
+ assert [step["skill"] for step in checkpoint["steps"]] == [
489
+ "prizmkit-init",
490
+ "bug-diagnosis-and-plan",
491
+ "prizmkit-implement",
492
+ "prizmkit-code-review",
493
+ "prizmkit-test",
494
+ "prizmkit-retrospective",
495
+ "prizmkit-committer",
496
+ "bug-report",
497
+ ]
498
+ test_step = checkpoint["steps"][4]
499
+ assert test_step["depends_on"] == "S04"
500
+ assert test_step["required_artifacts"] == [
501
+ ".prizmkit/test/evidence/*/manifest.json",
502
+ ".prizmkit/test/evidence/*/verdict.json",
503
+ ".prizmkit/test/evidence/*/validation.json",
504
+ ]
505
+ assert checkpoint["steps"][5]["depends_on"] == "S05"
506
+
507
+ def test_bugfix_repair_routing_preserves_review_and_test_boundaries(self):
508
+ review = route_bugfix_repair("REVIEW_NEEDS_FIXES", repair_round=0)
509
+ test_infra = route_bugfix_repair(
510
+ "TEST_FAIL", "test-infrastructure", repair_round=1,
511
+ )
512
+ production = route_bugfix_repair("TEST_FAIL", "public-interface", repair_round=2)
513
+
514
+ assert review["route"] == [
515
+ "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
516
+ ]
517
+ assert test_infra["route"] == ["prizmkit-implement", "prizmkit-test"]
518
+ assert production["route"] == [
519
+ "prizmkit-implement", "prizmkit-code-review", "prizmkit-test",
520
+ ]
521
+ assert production["repair_round"] == 3
522
+
523
+ def test_bugfix_repair_routing_blocks_unsafe_blocked_and_exhausted_results(self):
524
+ unknown = route_bugfix_repair("TEST_FAIL", "unknown", repair_round=1)
525
+ blocked = route_bugfix_repair("TEST_BLOCKED", "production", repair_round=1)
526
+ exhausted = route_bugfix_repair("TEST_FAIL", "production", repair_round=3)
527
+
528
+ assert unknown["blocked"] is True
529
+ assert unknown["route"] == []
530
+ assert blocked["blocked"] is True
531
+ assert blocked["resumable"] is False
532
+ assert blocked["next_stage"] is None
533
+ assert blocked["terminal_status"] == "WORKFLOW_BLOCKED"
534
+ assert blocked["blocked_reason"] == "test_blocked"
535
+ assert blocked["route"] == []
536
+ assert "cannot safely edit production" in blocked["reason"]
537
+ assert exhausted["blocked"] is True
538
+ assert exhausted["repair_round"] == 3
539
+
540
+ def test_bugfix_checkpoint_metadata_preserves_artifact_and_evidence_handoff(self):
541
+ metadata = bugfix_checkpoint_metadata(
542
+ ".prizmkit/bugfix/B-001",
543
+ evidence={
544
+ "manifest_path": ".prizmkit/test/evidence/id/manifest.json",
545
+ "verdict_path": ".prizmkit/test/evidence/id/verdict.json",
546
+ "validation_path": ".prizmkit/test/evidence/id/validation.json",
547
+ },
548
+ stage="test",
549
+ status="TEST_FAIL",
550
+ stage_result="TEST_FAIL",
551
+ repair_scope="test-infrastructure",
552
+ repair_round=2,
553
+ )
554
+
555
+ assert metadata["artifact_dir"] == ".prizmkit/bugfix/B-001"
556
+ assert metadata["authoritative_evidence_paths"]["validation"].endswith(
557
+ "/validation.json"
558
+ )
559
+ assert metadata["repair_round"] == 2
560
+ assert metadata["resume_from"] == "prizmkit-implement"
561
+
562
+ def test_manual_bugfix_keeps_test_gate_but_omits_commit_authorization(self):
563
+ sections_dir = Path("dev-pipeline/templates/sections")
564
+ sections = assemble_bugfix_sections(
565
+ "standard", str(sections_dir), False, True,
566
+ )
567
+ names = [name for name, _ in sections]
568
+
569
+ assert "bugfix-phase-review" in names
570
+ assert "bugfix-phase-test" in names
571
+ assert "bugfix-manual-gate" in names
572
+ assert "headless-commit-authorization" not in names
573
+ assert "bugfix-phase-commit-report" not in names
574
+
575
+ def test_rendered_bugfix_prompt_is_atomic_headless_and_local_commit_only(self, tmp_path):
576
+ prompt = _render_bugfix_prompt(tmp_path)
577
+
578
+ assert prompt.index("/prizmkit-code-review") < prompt.index(
579
+ "/prizmkit-test scope=this-change artifact_dir=.prizmkit/bugfix/B-001/"
580
+ ) < prompt.index("### Headless Commit Authorization")
581
+ assert "FAST_PATH=true" not in prompt
582
+ assert "prizmkit-workflow" not in prompt
583
+ assert '"local_commit_authorized": true' in prompt
584
+ assert '"push_authorized": false' in prompt
585
+ assert "Do not ask questions, wait for user input" in prompt
586
+ assert "push_authorized\": true" not in prompt
587
+ assert "automatic push" not in prompt.lower()
588
+ assert ".prizmkit/bugfix/B-001/" in prompt
589
+
458
590
  def test_setup_section_skips_and_finalization_group_expands(self):
459
591
  checkpoint = generate_bugfix_checkpoint(
460
592
  "B-001",