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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/.env.example +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +445 -8
- 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 +15 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +396 -4
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +54 -1
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +54 -1
- 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 +2 -2
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +20 -14
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +21 -4
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +18 -0
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +5 -15
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +21 -0
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +10 -10
- package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +16 -0
- package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +26 -23
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -8
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
- package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +21 -0
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +362 -3
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +310 -22
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +132 -0
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +91 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +411 -18
- package/bundled/dev-pipeline/tests/test_unified_cli.py +3 -3
- package/bundled/skills/_metadata.json +62 -25
- 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 +114 -84
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -1
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +181 -0
- package/bundled/skills/prizmkit-committer/SKILL.md +131 -47
- 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 +132 -105
- 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 +28 -6
- package/bundled/skills/prizmkit-test/references/examples.md +1 -1
- package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
- package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +181 -0
- 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
|
@@ -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
|
|
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/*/
|
|
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
|
-
|
|
601
|
-
|
|
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("
|
|
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
|
|
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
|
-
|
|
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(
|
|
843
|
-
|
|
844
|
-
|
|
845
|
-
".prizmkit/test
|
|
846
|
-
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
|
|
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
|
-
|
|
1047
|
+
assert validate_feature_test_evidence(
|
|
1048
|
+
str(tmp_path), ".prizmkit/specs/123-scope-test",
|
|
1049
|
+
) is None
|
|
853
1050
|
|
|
854
|
-
|
|
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",
|