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.
- package/bundled/VERSION.json +3 -3
- package/bundled/adapters/claude/command-adapter.js +0 -2
- package/bundled/dev-pipeline/.env.example +1 -1
- package/bundled/dev-pipeline/assets/skill-subagent-integration.md +11 -6
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +445 -8
- package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +2 -9
- package/bundled/dev-pipeline/prizmkit_runtime/processes.py +21 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +87 -66
- package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +53 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +55 -25
- package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +4 -0
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +119 -18
- package/bundled/dev-pipeline/prizmkit_runtime/sessions.py +3 -2
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +411 -17
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +57 -14
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +5 -4
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +55 -2
- package/bundled/dev-pipeline/scripts/prompt_framework.py +152 -1
- package/bundled/dev-pipeline/scripts/update-bug-status.py +53 -0
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +294 -69
- package/bundled/dev-pipeline/scripts/update-feature-status.py +57 -0
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +53 -0
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +5 -5
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +28 -22
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +24 -10
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +20 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +2 -2
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-manual-verification.md +5 -10
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +9 -19
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +21 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +1 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +10 -10
- package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +1 -1
- package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +15 -0
- package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-commit.md +2 -2
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +26 -23
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +5 -5
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +15 -11
- package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +6 -6
- package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +21 -0
- package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +1 -1
- package/bundled/dev-pipeline/templates/sections/subagent-timeout-recovery.md +1 -1
- package/bundled/dev-pipeline/templates/sections/task-contract.md +4 -0
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +362 -3
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +349 -48
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +129 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +91 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +567 -23
- package/bundled/dev-pipeline/tests/test_unified_cli.py +37 -10
- package/bundled/skills/_metadata.json +63 -26
- package/bundled/skills/app-planner/SKILL.md +4 -3
- package/bundled/skills/bug-fix-workflow/SKILL.md +89 -180
- package/bundled/skills/bug-planner/SKILL.md +12 -27
- package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +131 -220
- package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +36 -90
- package/bundled/skills/feature-pipeline-launcher/SKILL.md +129 -271
- package/bundled/skills/feature-pipeline-launcher/references/configuration.md +35 -76
- package/bundled/skills/feature-planner/SKILL.md +27 -22
- package/bundled/skills/feature-planner/assets/planning-guide.md +4 -4
- package/bundled/skills/feature-workflow/SKILL.md +94 -121
- package/bundled/skills/prizmkit/SKILL.md +130 -94
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-code-review/SKILL.md +132 -86
- package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +243 -0
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +42 -1
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +93 -1
- package/bundled/skills/prizmkit-committer/SKILL.md +125 -48
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-implement/SKILL.md +99 -73
- package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +1 -1
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-init/SKILL.md +5 -5
- package/bundled/skills/prizmkit-plan/SKILL.md +146 -105
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +18 -0
- package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +242 -0
- package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +4 -4
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-retrospective/SKILL.md +111 -53
- package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +7 -6
- package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +5 -4
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-test/SKILL.md +52 -6
- package/bundled/skills/prizmkit-test/assets/authoritative-records.schema.json +63 -12
- package/bundled/skills/prizmkit-test/assets/evidence-manifest.schema.json +1 -1
- package/bundled/skills/prizmkit-test/assets/evidence-package-template.json +13 -0
- package/bundled/skills/prizmkit-test/references/evidence-protocol.md +6 -1
- package/bundled/skills/prizmkit-test/references/evidence-request-protocol.md +8 -1
- package/bundled/skills/prizmkit-test/references/examples.md +1 -1
- package/bundled/skills/prizmkit-test/references/test-generation-steps.md +4 -1
- package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
- package/bundled/skills/prizmkit-test/references/trusted-evidence-execution.md +15 -0
- package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-test/scripts/build_test_evidence.py +744 -23
- package/bundled/skills/prizmkit-test/scripts/validate_test_evidence.py +194 -15
- package/bundled/skills/prizmkit-workflow/SKILL.md +266 -0
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/recovery-workflow/SKILL.md +62 -241
- package/bundled/skills/recovery-workflow/evals/evals.json +17 -33
- package/bundled/skills/recovery-workflow/references/detection.md +28 -52
- package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +105 -439
- package/bundled/skills/refactor-pipeline-launcher/SKILL.md +136 -275
- package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +53 -88
- package/bundled/skills/refactor-planner/SKILL.md +12 -57
- package/bundled/skills/refactor-planner/references/behavior-preservation.md +111 -230
- package/bundled/skills/refactor-planner/references/error-recovery.md +37 -0
- package/bundled/skills/refactor-planner/references/planning-phases.md +11 -17
- package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +1 -1
- package/bundled/skills/refactor-workflow/SKILL.md +98 -139
- package/bundled/templates/project-memory-template.md +4 -4
- package/package.json +1 -1
- package/src/index.js +4 -0
- package/src/prompts.js +10 -3
- package/src/scaffold.js +1 -1
- package/bundled/dev-pipeline/tests/test-deploy-safety.sh +0 -223
- package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
|
@@ -24,17 +24,23 @@ Usage:
|
|
|
24
24
|
|
|
25
25
|
import argparse
|
|
26
26
|
import glob
|
|
27
|
+
import hashlib
|
|
27
28
|
import json
|
|
28
29
|
import os
|
|
29
30
|
import re
|
|
31
|
+
import subprocess
|
|
30
32
|
import sys
|
|
31
33
|
|
|
32
34
|
from utils import enrich_global_context, helper_replacements, load_json_file, resolve_prompt_platform, setup_logging
|
|
33
35
|
from continuation import add_continuation_args, append_continuation_handoff, is_continuation
|
|
34
|
-
from prompt_framework import browser_enabled_from_item, checkpoint_group, checkpoint_skip, checkpoint_step, detect_dev_server_port, generate_checkpoint_from_sections, merge_checkpoint_state as merge_shared_checkpoint_state
|
|
36
|
+
from prompt_framework import browser_enabled_from_item, checkpoint_group, checkpoint_skip, checkpoint_step, detect_dev_server_port, generate_checkpoint_from_sections, merge_checkpoint_state as merge_shared_checkpoint_state, pipeline_checkpoint_metadata, route_pipeline_repair
|
|
35
37
|
|
|
36
38
|
|
|
37
39
|
DEFAULT_MAX_RETRIES = 3
|
|
40
|
+
MAX_OUTER_REPAIR_ROUNDS = 3
|
|
41
|
+
EVIDENCE_FINAL_RECORDS = {"validation.json", "verdict.json", "test-report.md"}
|
|
42
|
+
EVIDENCE_SHA256_RE = re.compile(r"^[a-f0-9]{64}$")
|
|
43
|
+
EVIDENCE_TARGET_HASH_KEYS = ("source", "tests", "contracts", "lockfiles", "environment", "plan")
|
|
38
44
|
|
|
39
45
|
LOGGER = setup_logging("generate-bootstrap-prompt")
|
|
40
46
|
|
|
@@ -675,11 +681,13 @@ SECTION_TO_SKILL = {
|
|
|
675
681
|
"prizmkit-plan", "Plan & Tasks",
|
|
676
682
|
[".prizmkit/specs/{slug}/plan.md"],
|
|
677
683
|
),
|
|
678
|
-
"phase-implement": checkpoint_step("prizmkit-implement", "Implement
|
|
684
|
+
"phase-implement": checkpoint_step("prizmkit-implement", "Implement", []),
|
|
679
685
|
"phase-prizmkit-test": checkpoint_step(
|
|
680
686
|
"prizmkit-test", "Scoped Feature Test Gate",
|
|
681
687
|
[".prizmkit/specs/{slug}/test-report-path.txt",
|
|
682
|
-
".prizmkit/test/*/
|
|
688
|
+
".prizmkit/test/evidence/*/manifest.json",
|
|
689
|
+
".prizmkit/test/evidence/*/verdict.json",
|
|
690
|
+
".prizmkit/test/evidence/*/validation.json"],
|
|
683
691
|
),
|
|
684
692
|
"phase-review": checkpoint_step("prizmkit-code-review", "Code Review", []),
|
|
685
693
|
"phase-browser": checkpoint_step("browser-verification", "Browser Verification", []),
|
|
@@ -716,6 +724,366 @@ def _read_text(path):
|
|
|
716
724
|
return ""
|
|
717
725
|
|
|
718
726
|
|
|
727
|
+
def _canonical_sha256(value):
|
|
728
|
+
"""Return the protocol's compact, key-sorted JSON SHA-256."""
|
|
729
|
+
payload = json.dumps(
|
|
730
|
+
value, ensure_ascii=False, sort_keys=True, separators=(",", ":"),
|
|
731
|
+
)
|
|
732
|
+
return hashlib.sha256(payload.encode("utf-8")).hexdigest()
|
|
733
|
+
|
|
734
|
+
|
|
735
|
+
def _file_sha256(path):
|
|
736
|
+
"""Hash a file without loading large evidence records into memory."""
|
|
737
|
+
digest = hashlib.sha256()
|
|
738
|
+
try:
|
|
739
|
+
with open(path, "rb") as f:
|
|
740
|
+
for chunk in iter(lambda: f.read(1024 * 1024), b""):
|
|
741
|
+
digest.update(chunk)
|
|
742
|
+
except OSError:
|
|
743
|
+
return ""
|
|
744
|
+
return digest.hexdigest()
|
|
745
|
+
|
|
746
|
+
|
|
747
|
+
def _load_strict_json(path):
|
|
748
|
+
"""Load JSON while rejecting duplicate keys and non-JSON constants."""
|
|
749
|
+
def reject_duplicates(pairs):
|
|
750
|
+
result = {}
|
|
751
|
+
for key, value in pairs:
|
|
752
|
+
if key in result:
|
|
753
|
+
raise ValueError("duplicate object key: {}".format(key))
|
|
754
|
+
result[key] = value
|
|
755
|
+
return result
|
|
756
|
+
|
|
757
|
+
def reject_constant(value):
|
|
758
|
+
raise ValueError("non-JSON numeric constant: {}".format(value))
|
|
759
|
+
|
|
760
|
+
try:
|
|
761
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
762
|
+
return json.load(
|
|
763
|
+
f, object_pairs_hook=reject_duplicates,
|
|
764
|
+
parse_constant=reject_constant,
|
|
765
|
+
)
|
|
766
|
+
except (OSError, ValueError, TypeError):
|
|
767
|
+
return None
|
|
768
|
+
|
|
769
|
+
|
|
770
|
+
def _is_sha256(value):
|
|
771
|
+
return isinstance(value, str) and bool(EVIDENCE_SHA256_RE.fullmatch(value))
|
|
772
|
+
|
|
773
|
+
|
|
774
|
+
def _path_is_within(root, candidate):
|
|
775
|
+
"""Return whether candidate resolves below root without escaping it."""
|
|
776
|
+
try:
|
|
777
|
+
return os.path.commonpath([
|
|
778
|
+
os.path.realpath(root), os.path.realpath(candidate),
|
|
779
|
+
]) == os.path.realpath(root)
|
|
780
|
+
except ValueError:
|
|
781
|
+
return False
|
|
782
|
+
|
|
783
|
+
|
|
784
|
+
def _resolve_test_evidence_validator(project_root):
|
|
785
|
+
"""Return the installed validator path used by the current project."""
|
|
786
|
+
candidates = [
|
|
787
|
+
os.path.join(
|
|
788
|
+
project_root, ".claude", "command-assets", "prizmkit-test",
|
|
789
|
+
"scripts", "validate_test_evidence.py",
|
|
790
|
+
),
|
|
791
|
+
os.path.join(
|
|
792
|
+
project_root, ".agents", "skills", "prizmkit-test", "scripts",
|
|
793
|
+
"validate_test_evidence.py",
|
|
794
|
+
),
|
|
795
|
+
os.path.join(
|
|
796
|
+
project_root, ".codebuddy", "skills", "prizmkit-test", "scripts",
|
|
797
|
+
"validate_test_evidence.py",
|
|
798
|
+
),
|
|
799
|
+
]
|
|
800
|
+
return next((path for path in candidates if os.path.isfile(path)), "")
|
|
801
|
+
|
|
802
|
+
|
|
803
|
+
def _evidence_validator_passes(project_root, evidence_root, validation):
|
|
804
|
+
"""Re-run the authoritative L1 validator before accepting any verdict."""
|
|
805
|
+
validator_path = _resolve_test_evidence_validator(project_root)
|
|
806
|
+
if (not validator_path or
|
|
807
|
+
validation.get("validator_sha256") != _file_sha256(validator_path)):
|
|
808
|
+
return False
|
|
809
|
+
try:
|
|
810
|
+
result = subprocess.run(
|
|
811
|
+
[sys.executable, validator_path,
|
|
812
|
+
"--evidence-dir", evidence_root,
|
|
813
|
+
"--project-root", project_root],
|
|
814
|
+
stdout=subprocess.DEVNULL,
|
|
815
|
+
stderr=subprocess.DEVNULL,
|
|
816
|
+
timeout=120,
|
|
817
|
+
check=False,
|
|
818
|
+
)
|
|
819
|
+
except (OSError, subprocess.SubprocessError):
|
|
820
|
+
return False
|
|
821
|
+
return result.returncode == 0
|
|
822
|
+
|
|
823
|
+
|
|
824
|
+
def _evidence_package_from_pointer(project_root, pointer_artifact):
|
|
825
|
+
"""Resolve a Feature report pointer to the structured evidence package."""
|
|
826
|
+
pointer_path = os.path.join(project_root, pointer_artifact)
|
|
827
|
+
pointed = _read_text(pointer_path).strip().splitlines()
|
|
828
|
+
if not pointed:
|
|
829
|
+
return None, None
|
|
830
|
+
|
|
831
|
+
report_path = pointed[0].strip()
|
|
832
|
+
if not os.path.isabs(report_path):
|
|
833
|
+
report_path = os.path.join(project_root, report_path)
|
|
834
|
+
evidence_root = os.path.dirname(report_path)
|
|
835
|
+
expected_root = os.path.join(project_root, ".prizmkit", "test", "evidence")
|
|
836
|
+
if not _path_is_within(expected_root, evidence_root):
|
|
837
|
+
return None, None
|
|
838
|
+
relative_root = os.path.relpath(evidence_root, expected_root)
|
|
839
|
+
if (not relative_root or relative_root.startswith("..") or
|
|
840
|
+
os.sep in relative_root):
|
|
841
|
+
return None, None
|
|
842
|
+
if os.path.basename(report_path) != "test-report.md":
|
|
843
|
+
return None, None
|
|
844
|
+
if not os.path.isfile(report_path):
|
|
845
|
+
return None, None
|
|
846
|
+
return os.path.realpath(evidence_root), os.path.realpath(report_path)
|
|
847
|
+
|
|
848
|
+
|
|
849
|
+
def _manifest_entries(evidence_root, manifest):
|
|
850
|
+
"""Validate the manifest file list and return path -> entry."""
|
|
851
|
+
entries = manifest.get("files")
|
|
852
|
+
if not isinstance(entries, list):
|
|
853
|
+
return None
|
|
854
|
+
result = {}
|
|
855
|
+
for entry in entries:
|
|
856
|
+
if not isinstance(entry, dict):
|
|
857
|
+
return None
|
|
858
|
+
relative = entry.get("path")
|
|
859
|
+
if (not isinstance(relative, str) or not relative or
|
|
860
|
+
os.path.isabs(relative) or relative in result or
|
|
861
|
+
".." in relative.split("/") or "\\" in relative):
|
|
862
|
+
return None
|
|
863
|
+
path = os.path.join(evidence_root, relative)
|
|
864
|
+
if not _path_is_within(evidence_root, path) or not os.path.isfile(path):
|
|
865
|
+
return None
|
|
866
|
+
if not _is_sha256(entry.get("sha256")):
|
|
867
|
+
return None
|
|
868
|
+
if not isinstance(entry.get("produced_by"), str) or not entry["produced_by"].strip():
|
|
869
|
+
return None
|
|
870
|
+
if _file_sha256(path) != entry["sha256"]:
|
|
871
|
+
return None
|
|
872
|
+
result[relative] = entry
|
|
873
|
+
|
|
874
|
+
actual = set()
|
|
875
|
+
for root, _dirs, files in os.walk(evidence_root):
|
|
876
|
+
for filename in files:
|
|
877
|
+
relative = os.path.relpath(os.path.join(root, filename), evidence_root)
|
|
878
|
+
if relative != "manifest.json":
|
|
879
|
+
actual.add(relative)
|
|
880
|
+
return result if set(result) == actual else None
|
|
881
|
+
|
|
882
|
+
|
|
883
|
+
def _extract_report_scope(report_text):
|
|
884
|
+
"""Read only the derived report's artifact identity, never its verdict."""
|
|
885
|
+
in_scope = False
|
|
886
|
+
for raw_line in report_text.splitlines():
|
|
887
|
+
line = raw_line.strip()
|
|
888
|
+
if line == "## Scope":
|
|
889
|
+
in_scope = True
|
|
890
|
+
continue
|
|
891
|
+
if in_scope and line.startswith("## "):
|
|
892
|
+
break
|
|
893
|
+
if in_scope and line.startswith("- Artifact Dir:"):
|
|
894
|
+
return line.split(":", 1)[1].strip()
|
|
895
|
+
return ""
|
|
896
|
+
|
|
897
|
+
|
|
898
|
+
def _validate_target_hashes(evidence_root, project_root, manifest, entry_map):
|
|
899
|
+
"""Validate live inventoried files against the manifest target hashes."""
|
|
900
|
+
inventory_path = os.path.join(evidence_root, "target-inventory.json")
|
|
901
|
+
environment_path = os.path.join(evidence_root, "environment.json")
|
|
902
|
+
inventory = _load_strict_json(inventory_path)
|
|
903
|
+
environment = _load_strict_json(environment_path)
|
|
904
|
+
if not isinstance(inventory, dict) or not isinstance(environment, dict):
|
|
905
|
+
return False
|
|
906
|
+
if "target-inventory.json" not in entry_map or "environment.json" not in entry_map:
|
|
907
|
+
return False
|
|
908
|
+
|
|
909
|
+
categories = inventory.get("categories")
|
|
910
|
+
expected = set(EVIDENCE_TARGET_HASH_KEYS[:4])
|
|
911
|
+
if not isinstance(categories, dict) or set(categories) != expected:
|
|
912
|
+
return False
|
|
913
|
+
aggregates = {}
|
|
914
|
+
for category in sorted(expected):
|
|
915
|
+
records = categories.get(category)
|
|
916
|
+
if not isinstance(records, list):
|
|
917
|
+
return False
|
|
918
|
+
normalized = []
|
|
919
|
+
for record in records:
|
|
920
|
+
if not isinstance(record, dict):
|
|
921
|
+
return False
|
|
922
|
+
relative = record.get("path")
|
|
923
|
+
expected_hash = record.get("sha256")
|
|
924
|
+
if (not isinstance(relative, str) or os.path.isabs(relative) or
|
|
925
|
+
not _is_sha256(expected_hash)):
|
|
926
|
+
return False
|
|
927
|
+
live_path = os.path.join(project_root, relative)
|
|
928
|
+
if (not _path_is_within(project_root, live_path) or
|
|
929
|
+
not os.path.isfile(live_path) or
|
|
930
|
+
_file_sha256(live_path) != expected_hash):
|
|
931
|
+
return False
|
|
932
|
+
normalized.append({"path": relative, "sha256": expected_hash})
|
|
933
|
+
aggregates[category] = _canonical_sha256(
|
|
934
|
+
sorted(normalized, key=lambda item: item["path"]),
|
|
935
|
+
)
|
|
936
|
+
|
|
937
|
+
aggregates["environment"] = _canonical_sha256(environment)
|
|
938
|
+
plan_inputs = inventory.get("plan_inputs")
|
|
939
|
+
if not isinstance(plan_inputs, dict) or not plan_inputs:
|
|
940
|
+
return False
|
|
941
|
+
aggregates["plan"] = _canonical_sha256(plan_inputs)
|
|
942
|
+
target_hashes = manifest.get("target_hashes")
|
|
943
|
+
return target_hashes == aggregates
|
|
944
|
+
|
|
945
|
+
|
|
946
|
+
def validate_feature_test_evidence(project_root, artifact_dir):
|
|
947
|
+
"""Return structured Feature test evidence, or None when it is unsafe."""
|
|
948
|
+
pointer_artifact = os.path.join(artifact_dir, "test-report-path.txt")
|
|
949
|
+
evidence_root, report_path = _evidence_package_from_pointer(
|
|
950
|
+
project_root, pointer_artifact,
|
|
951
|
+
)
|
|
952
|
+
if not evidence_root or not report_path:
|
|
953
|
+
return None
|
|
954
|
+
|
|
955
|
+
marker_path = os.path.join(project_root, artifact_dir, ".prizmkit-test-started")
|
|
956
|
+
if not os.path.isfile(marker_path):
|
|
957
|
+
return None
|
|
958
|
+
try:
|
|
959
|
+
if os.path.getmtime(report_path) < os.path.getmtime(marker_path):
|
|
960
|
+
return None
|
|
961
|
+
except OSError:
|
|
962
|
+
return None
|
|
963
|
+
|
|
964
|
+
manifest = _load_strict_json(os.path.join(evidence_root, "manifest.json"))
|
|
965
|
+
verdict = _load_strict_json(os.path.join(evidence_root, "verdict.json"))
|
|
966
|
+
validation = _load_strict_json(os.path.join(evidence_root, "validation.json"))
|
|
967
|
+
if not all(isinstance(record, dict) for record in (manifest, verdict, validation)):
|
|
968
|
+
return None
|
|
969
|
+
entry_map = _manifest_entries(evidence_root, manifest)
|
|
970
|
+
if entry_map is None or not {"verdict.json", "validation.json"}.issubset(entry_map):
|
|
971
|
+
return None
|
|
972
|
+
|
|
973
|
+
identity = manifest.get("evidence_id_inputs")
|
|
974
|
+
evidence_id = manifest.get("evidence_id")
|
|
975
|
+
if (not isinstance(identity, dict) or not _is_sha256(identity.get("working_diff_sha256")) or
|
|
976
|
+
not _is_sha256(identity.get("scope_sha256")) or
|
|
977
|
+
not isinstance(identity.get("baseline_commit"), str) or
|
|
978
|
+
evidence_id != _canonical_sha256(identity) or
|
|
979
|
+
os.path.basename(evidence_root) != evidence_id):
|
|
980
|
+
return None
|
|
981
|
+
if (manifest.get("protocol_version") != "1.0" or
|
|
982
|
+
manifest.get("sensitivity") != "project-controlled" or
|
|
983
|
+
manifest.get("environment_claim") != "mocked-code-level-only" or
|
|
984
|
+
manifest.get("compatibility") != "legacy-test-report-interface-not-supported" or
|
|
985
|
+
manifest.get("final_verdict") not in {"TEST_PASS", "TEST_FAIL", "TEST_BLOCKED"}):
|
|
986
|
+
return None
|
|
987
|
+
stages = manifest.get("stages")
|
|
988
|
+
stage_names = [stage.get("name") for stage in stages] if isinstance(stages, list) else []
|
|
989
|
+
if stage_names != [
|
|
990
|
+
"CHANGE_CLASSIFY", "SCOPE_DISCOVER", "CONTRACT_MODEL", "TEST_PLAN",
|
|
991
|
+
"INFRA_READY", "TEST_BUILD", "EXECUTE_PROVE", "EVIDENCE_PACKAGE",
|
|
992
|
+
"EVIDENCE_VALIDATE"]:
|
|
993
|
+
return None
|
|
994
|
+
for stage in stages:
|
|
995
|
+
if (not isinstance(stage, dict) or stage.get("status") not in {"complete", "not_applicable"} or
|
|
996
|
+
not _is_sha256(stage.get("input_sha256")) or
|
|
997
|
+
not isinstance(stage.get("outputs"), list)):
|
|
998
|
+
return None
|
|
999
|
+
if (not _is_sha256(manifest.get("working_diff_sha256")) or
|
|
1000
|
+
manifest.get("working_diff_sha256") != identity.get("working_diff_sha256")):
|
|
1001
|
+
return None
|
|
1002
|
+
|
|
1003
|
+
final_verdict = manifest["final_verdict"]
|
|
1004
|
+
if (verdict.get("verdict") != final_verdict or
|
|
1005
|
+
verdict.get("testing_domain_only") is not True or
|
|
1006
|
+
verdict.get("authorizes_commit_or_release") is not False or
|
|
1007
|
+
verdict.get("real_environment_validated") is not False or
|
|
1008
|
+
verdict.get("repairs_business_defects") is not False or
|
|
1009
|
+
not isinstance(verdict.get("blockers"), list) or
|
|
1010
|
+
not isinstance(verdict.get("reproduced_failures"), list)):
|
|
1011
|
+
return None
|
|
1012
|
+
if final_verdict == "TEST_PASS" and (
|
|
1013
|
+
verdict.get("code_evidence_replayable") is not True or
|
|
1014
|
+
verdict.get("blockers") or verdict.get("reproduced_failures")):
|
|
1015
|
+
return None
|
|
1016
|
+
if final_verdict == "TEST_FAIL" and not verdict.get("reproduced_failures"):
|
|
1017
|
+
return None
|
|
1018
|
+
if final_verdict == "TEST_BLOCKED" and not verdict.get("blockers"):
|
|
1019
|
+
return None
|
|
1020
|
+
|
|
1021
|
+
payload = sorted(
|
|
1022
|
+
({"path": path, "sha256": entry["sha256"]}
|
|
1023
|
+
for path, entry in entry_map.items()
|
|
1024
|
+
if path not in EVIDENCE_FINAL_RECORDS),
|
|
1025
|
+
key=lambda item: item["path"],
|
|
1026
|
+
)
|
|
1027
|
+
if (validation.get("validator") != "validate_test_evidence.py" or
|
|
1028
|
+
not _is_sha256(validation.get("validator_sha256")) or
|
|
1029
|
+
validation.get("payload_sha256") != _canonical_sha256(payload) or
|
|
1030
|
+
validation.get("result") != "passed" or
|
|
1031
|
+
validation.get("verdict") != final_verdict or
|
|
1032
|
+
validation.get("semantics") != "integrity-and-protocol-validation-not-hostile-producer-proof" or
|
|
1033
|
+
not isinstance(validation.get("replay_receipt_ids"), list)):
|
|
1034
|
+
return None
|
|
1035
|
+
if not _evidence_validator_passes(project_root, evidence_root, validation):
|
|
1036
|
+
return None
|
|
1037
|
+
|
|
1038
|
+
report_scope = _normalize_artifact_dir(_extract_report_scope(_read_text(report_path)))
|
|
1039
|
+
expected_scope = _normalize_artifact_dir(artifact_dir)
|
|
1040
|
+
if report_scope != expected_scope:
|
|
1041
|
+
return None
|
|
1042
|
+
if not _validate_target_hashes(evidence_root, project_root, manifest, entry_map):
|
|
1043
|
+
return None
|
|
1044
|
+
scope = _load_strict_json(os.path.join(evidence_root, "scope.json"))
|
|
1045
|
+
if not isinstance(scope, dict) or identity.get("scope_sha256") != _canonical_sha256(scope):
|
|
1046
|
+
return None
|
|
1047
|
+
|
|
1048
|
+
state_path = os.path.join(
|
|
1049
|
+
project_root, ".prizmkit", "state", "workflows",
|
|
1050
|
+
os.path.basename(artifact_dir) + ".json",
|
|
1051
|
+
)
|
|
1052
|
+
state = _load_strict_json(state_path) if os.path.isfile(state_path) else {}
|
|
1053
|
+
if state and (not isinstance(state, dict) or state.get("stage_result") not in {None, final_verdict, "TEST_PASS", "TEST_FAIL", "TEST_BLOCKED"}):
|
|
1054
|
+
return None
|
|
1055
|
+
return {
|
|
1056
|
+
"evidence_id": evidence_id,
|
|
1057
|
+
"evidence_dir": os.path.relpath(evidence_root, project_root),
|
|
1058
|
+
"manifest_path": os.path.relpath(os.path.join(evidence_root, "manifest.json"), project_root),
|
|
1059
|
+
"verdict_path": os.path.relpath(os.path.join(evidence_root, "verdict.json"), project_root),
|
|
1060
|
+
"validation_path": os.path.relpath(os.path.join(evidence_root, "validation.json"), project_root),
|
|
1061
|
+
"verdict": final_verdict,
|
|
1062
|
+
"repair_scope": state.get("repair_scope") if isinstance(state, dict) else None,
|
|
1063
|
+
"repair_round": state.get("repair_round", 0) if isinstance(state, dict) else 0,
|
|
1064
|
+
"target_hashes": manifest.get("target_hashes"),
|
|
1065
|
+
}
|
|
1066
|
+
|
|
1067
|
+
|
|
1068
|
+
def route_feature_repair(stage_result, repair_scope=None, repair_round=0):
|
|
1069
|
+
"""Map Feature review/test outcomes to the shared safe route contract."""
|
|
1070
|
+
return route_pipeline_repair(stage_result, repair_scope, repair_round)
|
|
1071
|
+
|
|
1072
|
+
|
|
1073
|
+
def feature_checkpoint_metadata(artifact_dir, evidence=None, stage="plan",
|
|
1074
|
+
status="PLAN_READY", stage_result=None,
|
|
1075
|
+
repair_scope=None, repair_round=0,
|
|
1076
|
+
next_stage="prizmkit-implement",
|
|
1077
|
+
resume_from="prizmkit-implement"):
|
|
1078
|
+
"""Build Feature L4 handoff metadata through the shared semantic contract."""
|
|
1079
|
+
return pipeline_checkpoint_metadata(
|
|
1080
|
+
artifact_dir, evidence=evidence, stage=stage, status=status,
|
|
1081
|
+
stage_result=stage_result, repair_scope=repair_scope,
|
|
1082
|
+
repair_round=repair_round, next_stage=next_stage,
|
|
1083
|
+
resume_from=resume_from,
|
|
1084
|
+
)
|
|
1085
|
+
|
|
1086
|
+
|
|
719
1087
|
def _normalize_artifact_dir(path):
|
|
720
1088
|
"""Normalize report artifact_dir values for comparison."""
|
|
721
1089
|
return path.strip().replace("\\", "/").rstrip("/")
|
|
@@ -1091,8 +1459,12 @@ def _scoped_report_fresh(project_root, pointer_artifact, report_path, report_tex
|
|
|
1091
1459
|
return True
|
|
1092
1460
|
|
|
1093
1461
|
|
|
1094
|
-
def
|
|
1095
|
-
"""Validate the
|
|
1462
|
+
def _validate_legacy_scoped_test_report(project_root, artifacts):
|
|
1463
|
+
"""Validate the legacy Markdown report for old callers only.
|
|
1464
|
+
|
|
1465
|
+
Feature checkpoints must use ``_validate_scoped_test_report`` below; this
|
|
1466
|
+
retained helper is intentionally not used by Feature prompt generation.
|
|
1467
|
+
"""
|
|
1096
1468
|
pointer_artifacts = [a for a in artifacts
|
|
1097
1469
|
if a.endswith("/test-report-path.txt")]
|
|
1098
1470
|
if not pointer_artifacts:
|
|
@@ -1130,6 +1502,16 @@ def _validate_scoped_test_report(project_root, artifacts):
|
|
|
1130
1502
|
report_path, report_text))
|
|
1131
1503
|
|
|
1132
1504
|
|
|
1505
|
+
def _validate_scoped_test_report(project_root, artifacts):
|
|
1506
|
+
"""Validate current Feature structured evidence, never Markdown authority."""
|
|
1507
|
+
pointer_artifacts = [a for a in artifacts if a.endswith("/test-report-path.txt")]
|
|
1508
|
+
if not pointer_artifacts:
|
|
1509
|
+
return False
|
|
1510
|
+
return validate_feature_test_evidence(
|
|
1511
|
+
project_root, os.path.dirname(pointer_artifacts[0]),
|
|
1512
|
+
) is not None
|
|
1513
|
+
|
|
1514
|
+
|
|
1133
1515
|
def _completed_step_artifacts_valid(project_root, skill_key, artifacts):
|
|
1134
1516
|
"""Return whether a completed checkpoint step can be preserved."""
|
|
1135
1517
|
if not all(_artifact_exists(project_root, a) for a in artifacts):
|
|
@@ -1237,10 +1619,10 @@ def _tier_header(pipeline_mode):
|
|
|
1237
1619
|
"# Dev-Pipeline Session Bootstrap — Tier 3 "
|
|
1238
1620
|
"(Main-Agent Full Guardrails)\n",
|
|
1239
1621
|
"**Tier 3 — Main-Agent Full Guardrails**: For complex "
|
|
1240
|
-
"features, the main orchestrator handles context, planning, "
|
|
1241
|
-
"implementation,
|
|
1242
|
-
"
|
|
1243
|
-
"
|
|
1622
|
+
"features, the main orchestrator handles context, planning, and "
|
|
1623
|
+
"implementation directly, then runs mandatory Main-Agent review. "
|
|
1624
|
+
"After convergence, the review skill may use only its strict-gated "
|
|
1625
|
+
"skill-owned independent Reviewer.\n",
|
|
1244
1626
|
),
|
|
1245
1627
|
}
|
|
1246
1628
|
return headers.get(pipeline_mode, headers["lite"])
|
|
@@ -1276,8 +1658,8 @@ def _tier_reminders(pipeline_mode):
|
|
|
1276
1658
|
"Code Review writes only review-report.md",
|
|
1277
1659
|
"- Gate checks require the Implementation Log (with Gate Evidence) "
|
|
1278
1660
|
"and a valid review-report.md Final Result before proceeding",
|
|
1279
|
-
"- Code Review
|
|
1280
|
-
"
|
|
1661
|
+
"- Code Review keeps mandatory Main-Agent ownership; after convergence "
|
|
1662
|
+
"it may use only its strict-gated skill-owned independent Reviewer",
|
|
1281
1663
|
"- On timeout outside Code Review: check snapshot + git diff HEAD "
|
|
1282
1664
|
"→ model:lite → remaining steps only → max 2 retries per phase → "
|
|
1283
1665
|
"Main Agent fallback",
|
|
@@ -1285,13 +1667,13 @@ def _tier_reminders(pipeline_mode):
|
|
|
1285
1667
|
else: # full
|
|
1286
1668
|
specific = [
|
|
1287
1669
|
"- Tier 3: full orchestration — the Main Agent implements directly "
|
|
1288
|
-
"and owns the
|
|
1670
|
+
"and owns the mandatory Code Review loop",
|
|
1289
1671
|
"- context-snapshot.md is the implementation knowledge base; "
|
|
1290
1672
|
"Code Review writes only review-report.md",
|
|
1291
1673
|
"- Gate checks require the Implementation Log and a valid "
|
|
1292
1674
|
"review-report.md Final Result before proceeding",
|
|
1293
|
-
"- Code Review
|
|
1294
|
-
"
|
|
1675
|
+
"- Code Review excludes ordinary work delegation and permits only "
|
|
1676
|
+
"the skill-owned independent Reviewer under its strict structural gate",
|
|
1295
1677
|
"- Do NOT use `run_in_background=true` when spawning normal work "
|
|
1296
1678
|
"agents outside Code Review",
|
|
1297
1679
|
"- If a normal work Agent call fails with team/config/lock errors, "
|
|
@@ -1483,11 +1865,13 @@ def assemble_sections(pipeline_mode, sections_dir, init_done, is_resume,
|
|
|
1483
1865
|
load_section(sections_dir,
|
|
1484
1866
|
browser_section_file)))
|
|
1485
1867
|
|
|
1868
|
+
# --- Headless commit authorization ---
|
|
1869
|
+
sections.append(("headless-commit-authorization",
|
|
1870
|
+
load_section(sections_dir, "headless-commit-authorization.md")))
|
|
1871
|
+
|
|
1486
1872
|
# --- Commit (tier-dependent) ---
|
|
1487
1873
|
if pipeline_mode == "full":
|
|
1488
|
-
sections.append(("phase-commit",
|
|
1489
|
-
load_section(sections_dir,
|
|
1490
|
-
"phase-commit-full.md")))
|
|
1874
|
+
sections.append(("phase-commit", load_section(sections_dir, "phase-commit-full.md")))
|
|
1491
1875
|
else:
|
|
1492
1876
|
sections.append(("phase-commit",
|
|
1493
1877
|
load_section(sections_dir,
|
|
@@ -1886,6 +2270,16 @@ def main():
|
|
|
1886
2270
|
checkpoint_dir, "workflow-checkpoint.json",
|
|
1887
2271
|
)
|
|
1888
2272
|
|
|
2273
|
+
# L4 metadata remains separate from the L1 workflow-state contract.
|
|
2274
|
+
checkpoint["feature_state"] = feature_checkpoint_metadata(
|
|
2275
|
+
os.path.join(".prizmkit", "specs", feature_slug),
|
|
2276
|
+
stage="plan",
|
|
2277
|
+
status="PLAN_READY",
|
|
2278
|
+
stage_result=None,
|
|
2279
|
+
next_stage="prizmkit-implement",
|
|
2280
|
+
resume_from="prizmkit-implement",
|
|
2281
|
+
)
|
|
2282
|
+
|
|
1889
2283
|
# On resume, merge existing completed state (with artifact validation)
|
|
1890
2284
|
if (is_resume or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
1891
2285
|
try:
|
|
@@ -22,9 +22,11 @@ from prompt_framework import (
|
|
|
22
22
|
merge_checkpoint_state,
|
|
23
23
|
mode_agent_count,
|
|
24
24
|
normalize_mode,
|
|
25
|
+
pipeline_checkpoint_metadata,
|
|
25
26
|
read_text_file,
|
|
26
27
|
render_from_sections,
|
|
27
28
|
replace_placeholders,
|
|
29
|
+
route_pipeline_repair,
|
|
28
30
|
validate_rendered,
|
|
29
31
|
write_output,
|
|
30
32
|
)
|
|
@@ -34,6 +36,30 @@ from utils import enrich_global_context, helper_replacements, load_json_file, re
|
|
|
34
36
|
LOGGER = setup_logging("generate-bugfix-prompt")
|
|
35
37
|
|
|
36
38
|
|
|
39
|
+
def route_bugfix_repair(stage_result, repair_scope=None, repair_round=0):
|
|
40
|
+
"""Map Bugfix review/test outcomes to the shared safe route contract."""
|
|
41
|
+
return route_pipeline_repair(stage_result, repair_scope, repair_round)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def bugfix_checkpoint_metadata(artifact_dir, evidence=None, stage="plan",
|
|
45
|
+
status="PLAN_READY", stage_result=None,
|
|
46
|
+
repair_scope=None, repair_round=0,
|
|
47
|
+
next_stage="prizmkit-implement",
|
|
48
|
+
resume_from="prizmkit-implement"):
|
|
49
|
+
"""Build Bugfix L4 handoff metadata while preserving Bugfix L1 state."""
|
|
50
|
+
return pipeline_checkpoint_metadata(
|
|
51
|
+
artifact_dir,
|
|
52
|
+
evidence=evidence,
|
|
53
|
+
stage=stage,
|
|
54
|
+
status=status,
|
|
55
|
+
stage_result=stage_result,
|
|
56
|
+
repair_scope=repair_scope,
|
|
57
|
+
repair_round=repair_round,
|
|
58
|
+
next_stage=next_stage,
|
|
59
|
+
resume_from=resume_from,
|
|
60
|
+
)
|
|
61
|
+
|
|
62
|
+
|
|
37
63
|
# Section name -> checkpoint contract.
|
|
38
64
|
BUGFIX_SECTION_TO_SKILL = {
|
|
39
65
|
"bugfix-phase-init": checkpoint_step("prizmkit-init", "Initialize", [".prizmkit/bugfix/{slug}"]),
|
|
@@ -43,6 +69,14 @@ BUGFIX_SECTION_TO_SKILL = {
|
|
|
43
69
|
),
|
|
44
70
|
"bugfix-phase-implement": checkpoint_step("prizmkit-implement", "Implement Fix", []),
|
|
45
71
|
"bugfix-phase-review": checkpoint_step("prizmkit-code-review", "Code Review", []),
|
|
72
|
+
"bugfix-phase-test": checkpoint_step(
|
|
73
|
+
"prizmkit-test", "Post-Review Test Gate",
|
|
74
|
+
[
|
|
75
|
+
".prizmkit/test/evidence/*/manifest.json",
|
|
76
|
+
".prizmkit/test/evidence/*/verdict.json",
|
|
77
|
+
".prizmkit/test/evidence/*/validation.json",
|
|
78
|
+
],
|
|
79
|
+
),
|
|
46
80
|
"phase-browser": checkpoint_step("browser-verification", "Browser Verification", []),
|
|
47
81
|
"bugfix-phase-commit-report": checkpoint_group([
|
|
48
82
|
("prizmkit-retrospective", "Retrospective", []),
|
|
@@ -266,12 +300,9 @@ def build_replacements(args, bug, global_context, script_dir):
|
|
|
266
300
|
dev_url = "http://localhost:{}".format(dev_port) if dev_port.isdigit() else "<UNKNOWN_DETECT_FROM_PROJECT_CONFIG>"
|
|
267
301
|
|
|
268
302
|
pipeline_mode = determine_bugfix_mode(args, bug)
|
|
269
|
-
recovery_like = is_recovery_like(args)
|
|
270
303
|
manual_or_hybrid = verification_type in ("manual", "hybrid")
|
|
271
304
|
manual_policy = (
|
|
272
|
-
"
|
|
273
|
-
if manual_or_hybrid and recovery_like
|
|
274
|
-
else "Fresh manual/hybrid bugfix: stop as partial before commit after automated checks; manual UAT is required."
|
|
305
|
+
"Manual/hybrid verification is executed automatically in headless mode: use available browser, CLI, API, fixture, and test evidence; document the verification evidence and proceed through review, test, retrospective, and commit."
|
|
275
306
|
if manual_or_hybrid
|
|
276
307
|
else "Automated verification: complete tests/review and proceed to commit when gates pass."
|
|
277
308
|
)
|
|
@@ -356,7 +387,7 @@ def _bugfix_header(pipeline_mode):
|
|
|
356
387
|
title = "# Dev-Pipeline Bug Fix Session Bootstrap — {}\n".format(pipeline_mode.title())
|
|
357
388
|
desc = {
|
|
358
389
|
"lite": "**Tier 1 — Single Agent**: direct root-cause fix by the main orchestrator.",
|
|
359
|
-
"standard": "**Tier 2 — Main-Agent Review Gates**: direct implementation with Main-Agent-
|
|
390
|
+
"standard": "**Tier 2 — Main-Agent Review Gates**: direct implementation with mandatory Main-Agent review and optional strict capability-gated independent correctness review.",
|
|
360
391
|
"full": "**Tier 3 — Full Bugfix Guardrails**: direct implementation with stronger diagnosis and review gates.",
|
|
361
392
|
}.get(pipeline_mode, "**Bugfix pipeline**")
|
|
362
393
|
return title + "\n" + desc + "\n"
|
|
@@ -378,6 +409,7 @@ def assemble_bugfix_sections(pipeline_mode, sections_dir, browser_enabled, manua
|
|
|
378
409
|
sections.extend([
|
|
379
410
|
("bugfix-phase-implement", load_section(sections_dir, "bugfix-phase-implement.md")),
|
|
380
411
|
("bugfix-phase-review", load_section(sections_dir, "bugfix-phase-review.md")),
|
|
412
|
+
("bugfix-phase-test", load_section(sections_dir, "bugfix-phase-test.md")),
|
|
381
413
|
])
|
|
382
414
|
if browser_enabled:
|
|
383
415
|
browser_section_file = {
|
|
@@ -387,14 +419,8 @@ def assemble_bugfix_sections(pipeline_mode, sections_dir, browser_enabled, manua
|
|
|
387
419
|
sections.append(("phase-browser", load_section(sections_dir, browser_section_file)))
|
|
388
420
|
if manual_gate:
|
|
389
421
|
sections.append(("bugfix-manual-gate", load_section(sections_dir, "bugfix-phase-manual-verification.md")))
|
|
390
|
-
sections.extend([
|
|
391
|
-
("bugfix-session-status", load_section(sections_dir, "bugfix-session-status.md")),
|
|
392
|
-
("bugfix-critical-paths", load_section(sections_dir, "bugfix-critical-paths.md")),
|
|
393
|
-
("failure-capture", load_section(sections_dir, "failure-capture.md")),
|
|
394
|
-
("bugfix-reminders", load_section(sections_dir, "bugfix-reminders.md")),
|
|
395
|
-
])
|
|
396
|
-
return sections
|
|
397
422
|
sections.extend([
|
|
423
|
+
("headless-commit-authorization", load_section(sections_dir, "headless-commit-authorization.md")),
|
|
398
424
|
("bugfix-phase-commit-report", load_section(sections_dir, "bugfix-phase-commit-report.md")),
|
|
399
425
|
("bugfix-session-status", load_section(sections_dir, "bugfix-session-status.md")),
|
|
400
426
|
("bugfix-critical-paths", load_section(sections_dir, "bugfix-critical-paths.md")),
|
|
@@ -412,6 +438,7 @@ def generate_bugfix_checkpoint(bug_id, session_id, pipeline_mode="standard", sec
|
|
|
412
438
|
("bugfix-phase-diagnose-plan", ""),
|
|
413
439
|
("bugfix-phase-implement", ""),
|
|
414
440
|
("bugfix-phase-review", ""),
|
|
441
|
+
("bugfix-phase-test", ""),
|
|
415
442
|
("bugfix-phase-commit-report", ""),
|
|
416
443
|
]
|
|
417
444
|
return generate_checkpoint_from_sections(
|
|
@@ -428,7 +455,11 @@ def generate_bugfix_checkpoint(bug_id, session_id, pipeline_mode="standard", sec
|
|
|
428
455
|
|
|
429
456
|
def merge_bugfix_checkpoint_state(existing, fresh, project_root):
|
|
430
457
|
"""Merge existing bugfix checkpoint state into fresh definition."""
|
|
431
|
-
|
|
458
|
+
merged = merge_checkpoint_state(existing, fresh, project_root, logger=LOGGER)
|
|
459
|
+
existing_state = existing.get("bugfix_state")
|
|
460
|
+
if isinstance(existing_state, dict):
|
|
461
|
+
merged["bugfix_state"] = existing_state
|
|
462
|
+
return merged
|
|
432
463
|
|
|
433
464
|
|
|
434
465
|
def emit_failure(message):
|
|
@@ -459,7 +490,7 @@ def main():
|
|
|
459
490
|
replacements = build_replacements(args, bug, global_context, script_dir)
|
|
460
491
|
pipeline_mode = replacements["{{PIPELINE_MODE}}"]
|
|
461
492
|
browser_enabled = replacements["{{BROWSER_ENABLED}}"] == "true"
|
|
462
|
-
manual_gate = bug.get("verification_type", "automated") in ("manual", "hybrid")
|
|
493
|
+
manual_gate = bug.get("verification_type", "automated") in ("manual", "hybrid")
|
|
463
494
|
|
|
464
495
|
use_sections = os.path.isdir(sections_dir) and not args.template
|
|
465
496
|
sections = []
|
|
@@ -503,6 +534,18 @@ def main():
|
|
|
503
534
|
checkpoint = generate_bugfix_checkpoint(
|
|
504
535
|
args.bug_id, args.session_id, pipeline_mode, sections if use_sections else None,
|
|
505
536
|
)
|
|
537
|
+
if not any(step.get("skill") == "prizmkit-test" for step in checkpoint["steps"]):
|
|
538
|
+
emit_failure("Bugfix checkpoint is missing the mandatory prizmkit-test stage")
|
|
539
|
+
checkpoint["bugfix_state"] = bugfix_checkpoint_metadata(
|
|
540
|
+
replacements["{{ARTIFACT_DIR}}"],
|
|
541
|
+
stage="plan",
|
|
542
|
+
status="PLAN_READY",
|
|
543
|
+
stage_result=None,
|
|
544
|
+
repair_scope=None,
|
|
545
|
+
repair_round=0,
|
|
546
|
+
next_stage="prizmkit-implement",
|
|
547
|
+
resume_from="prizmkit-implement",
|
|
548
|
+
)
|
|
506
549
|
|
|
507
550
|
if (args.resume_phase != "null" or is_continuation(args)) and os.path.exists(checkpoint_path):
|
|
508
551
|
try:
|