prizmkit 1.1.156 → 1.1.159
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/prizmkit_runtime/reset.py +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +20 -11
- package/bundled/dev-pipeline/scripts/update-feature-status.py +7 -0
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +115 -4
- package/bundled/dev-pipeline/tests/test_reset_modes.py +137 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +2 -1
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/prizmkit/SKILL.md +1 -13
- package/bundled/skills/prizmkit-code-review/SKILL.md +29 -10
- package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +5 -4
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +15 -0
- package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +61 -0
- package/bundled/skills/prizmkit-committer/SKILL.md +22 -17
- package/bundled/skills/prizmkit-init/SKILL.md +21 -25
- package/bundled/skills/prizmkit-init/references/config-schema.md +11 -7
- package/bundled/skills/prizmkit-init/references/rules/layer-detection.md +3 -3
- package/bundled/skills/prizmkit-plan/SKILL.md +5 -4
- package/bundled/skills/prizmkit-plan/assets/plan-template.md +4 -1
- package/bundled/skills/prizmkit-plan/references/verification-checklist.md +3 -2
- package/bundled/skills/prizmkit-workflow/SKILL.md +35 -3
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +24 -5
- package/package.json +1 -1
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +0 -178
package/bundled/VERSION.json
CHANGED
|
@@ -115,7 +115,7 @@ def run_reset_command(kind: str, legacy_args: tuple[str, ...], paths) -> Command
|
|
|
115
115
|
list_path = _resolve_list_path(options.list_path, paths.project_root)
|
|
116
116
|
if not list_path.is_file():
|
|
117
117
|
return CommandResult(1, f"Python {kind} reset", f"{kind} list not found: {list_path}")
|
|
118
|
-
if
|
|
118
|
+
if options.fresh_checkout and not (family.state_dir / "pipeline.json").is_file():
|
|
119
119
|
return CommandResult(1, f"Python {kind} reset", f"No pipeline state found. Run the {kind} pipeline first to initialize.")
|
|
120
120
|
|
|
121
121
|
options = ResetOptions(
|
|
@@ -92,17 +92,7 @@ def classify_session(
|
|
|
92
92
|
)
|
|
93
93
|
|
|
94
94
|
semantic = checkpoint.semantic_snapshot if checkpoint is not None else {}
|
|
95
|
-
if
|
|
96
|
-
reason = "no_semantic_completion"
|
|
97
|
-
if checkpoint is not None and checkpoint.semantic.error_code:
|
|
98
|
-
reason = f"{reason}:{checkpoint.semantic.error_code}"
|
|
99
|
-
return SessionClassification(
|
|
100
|
-
WORKFLOW_SKIPPED,
|
|
101
|
-
reason=reason,
|
|
102
|
-
progress_fingerprint=fingerprint,
|
|
103
|
-
checkpoint_state=semantic,
|
|
104
|
-
)
|
|
105
|
-
if _is_context_overflow(result):
|
|
95
|
+
if _is_recoverable_context_interruption(result):
|
|
106
96
|
no_progress_count = _next_no_progress_count(previous_fingerprint, fingerprint, previous_no_progress_count)
|
|
107
97
|
if no_progress_count >= 2:
|
|
108
98
|
return SessionClassification(
|
|
@@ -121,6 +111,16 @@ def classify_session(
|
|
|
121
111
|
no_progress_count=no_progress_count,
|
|
122
112
|
checkpoint_state=semantic,
|
|
123
113
|
)
|
|
114
|
+
if _is_terminal_success(result):
|
|
115
|
+
reason = "no_semantic_completion"
|
|
116
|
+
if checkpoint is not None and checkpoint.semantic.error_code:
|
|
117
|
+
reason = f"{reason}:{checkpoint.semantic.error_code}"
|
|
118
|
+
return SessionClassification(
|
|
119
|
+
WORKFLOW_SKIPPED,
|
|
120
|
+
reason=reason,
|
|
121
|
+
progress_fingerprint=fingerprint,
|
|
122
|
+
checkpoint_state=semantic,
|
|
123
|
+
)
|
|
124
124
|
|
|
125
125
|
if result.termination_reason == "stale_session":
|
|
126
126
|
return SessionClassification(
|
|
@@ -275,6 +275,15 @@ def _next_no_progress_count(previous: object | None, current: object, count: int
|
|
|
275
275
|
return 0
|
|
276
276
|
|
|
277
277
|
|
|
278
|
+
def _is_recoverable_context_interruption(result: AISessionResult) -> bool:
|
|
279
|
+
"""Recognize provider limits that should continue an incomplete workflow."""
|
|
280
|
+
if _is_terminal_success(result):
|
|
281
|
+
# Pi reports a provider token/length limit as a successful result event.
|
|
282
|
+
# Semantic completion was already handled before this helper is called.
|
|
283
|
+
return result.progress_summary.stop_reason.strip().lower() == "length"
|
|
284
|
+
return _is_context_overflow(result)
|
|
285
|
+
|
|
286
|
+
|
|
278
287
|
def _is_context_overflow(result: AISessionResult) -> bool:
|
|
279
288
|
if (result.fatal_error_code or "").lower() == CONTEXT_OVERFLOW:
|
|
280
289
|
return True
|
|
@@ -1726,6 +1726,13 @@ def action_clean(args, feature_list_path, state_dir):
|
|
|
1726
1726
|
fs["last_session_id"] = None
|
|
1727
1727
|
fs["resume_from_phase"] = None
|
|
1728
1728
|
reset_continuation_metadata(fs)
|
|
1729
|
+
for stale_field in (
|
|
1730
|
+
"repair_scope",
|
|
1731
|
+
"repair_round",
|
|
1732
|
+
"blocked_reason",
|
|
1733
|
+
"terminal_status",
|
|
1734
|
+
):
|
|
1735
|
+
fs.pop(stale_field, None)
|
|
1729
1736
|
fs["updated_at"] = now_iso()
|
|
1730
1737
|
|
|
1731
1738
|
err = save_feature_status(state_dir, feature_id, fs)
|
|
@@ -4015,7 +4015,7 @@ def test_pi_length_stop_with_nonzero_exit_uses_bounded_crash_path(tmp_path):
|
|
|
4015
4015
|
assert classification.fatal_error_code == ""
|
|
4016
4016
|
|
|
4017
4017
|
|
|
4018
|
-
def
|
|
4018
|
+
def test_pi_length_stop_with_zero_exit_and_no_checkpoint_uses_recoverable_continuation(tmp_path):
|
|
4019
4019
|
from dataclasses import replace
|
|
4020
4020
|
|
|
4021
4021
|
from prizmkit_runtime.runner_classification import classify_session
|
|
@@ -4029,9 +4029,120 @@ def test_pi_length_stop_with_zero_exit_and_no_checkpoint_uses_no_completion_poli
|
|
|
4029
4029
|
|
|
4030
4030
|
classification = classify_session(raw, project_root=tmp_path, base_head=base_head)
|
|
4031
4031
|
|
|
4032
|
-
assert classification.session_status == "
|
|
4033
|
-
assert classification.reason == "
|
|
4034
|
-
assert classification.fatal_error_code == ""
|
|
4032
|
+
assert classification.session_status == "context_overflow"
|
|
4033
|
+
assert classification.reason == "context_overflow"
|
|
4034
|
+
assert classification.fatal_error_code == "context_overflow"
|
|
4035
|
+
|
|
4036
|
+
|
|
4037
|
+
def test_pi_length_stop_with_in_progress_checkpoint_uses_recoverable_continuation(tmp_path):
|
|
4038
|
+
from dataclasses import replace
|
|
4039
|
+
|
|
4040
|
+
from prizmkit_runtime.runner_classification import classify_session
|
|
4041
|
+
from prizmkit_runtime.runner_models import PromptGenerationResult
|
|
4042
|
+
from prizmkit_runtime.status import ProgressSummary
|
|
4043
|
+
|
|
4044
|
+
base_head = _init_git_repo_for_invalid_checkpoint_test(tmp_path)
|
|
4045
|
+
artifact_dir = tmp_path / ".prizmkit" / "specs" / "001-length-in-progress"
|
|
4046
|
+
artifact_dir.mkdir(parents=True)
|
|
4047
|
+
checkpoint = artifact_dir / "workflow-checkpoint.json"
|
|
4048
|
+
checkpoint.write_text(
|
|
4049
|
+
json.dumps({
|
|
4050
|
+
"version": 1,
|
|
4051
|
+
"workflow_type": "feature-pipeline",
|
|
4052
|
+
"steps": [
|
|
4053
|
+
{
|
|
4054
|
+
"id": "S01",
|
|
4055
|
+
"skill": "context-snapshot-and-plan",
|
|
4056
|
+
"name": "Specify & Plan",
|
|
4057
|
+
"status": "completed",
|
|
4058
|
+
"required_artifacts": [],
|
|
4059
|
+
"depends_on": None,
|
|
4060
|
+
"stage_result": "PLAN_READY",
|
|
4061
|
+
},
|
|
4062
|
+
{
|
|
4063
|
+
"id": "S02",
|
|
4064
|
+
"skill": "prizmkit-implement",
|
|
4065
|
+
"name": "Implement",
|
|
4066
|
+
"status": "completed",
|
|
4067
|
+
"required_artifacts": [],
|
|
4068
|
+
"depends_on": "S01",
|
|
4069
|
+
"stage_result": "IMPLEMENTED",
|
|
4070
|
+
},
|
|
4071
|
+
{
|
|
4072
|
+
"id": "S03",
|
|
4073
|
+
"skill": "prizmkit-code-review",
|
|
4074
|
+
"name": "Code Review",
|
|
4075
|
+
"status": "in_progress",
|
|
4076
|
+
"required_artifacts": [],
|
|
4077
|
+
"depends_on": "S02",
|
|
4078
|
+
},
|
|
4079
|
+
{
|
|
4080
|
+
"id": "S04",
|
|
4081
|
+
"skill": "prizmkit-test",
|
|
4082
|
+
"name": "Test",
|
|
4083
|
+
"status": "pending",
|
|
4084
|
+
"required_artifacts": [],
|
|
4085
|
+
"depends_on": "S03",
|
|
4086
|
+
},
|
|
4087
|
+
{
|
|
4088
|
+
"id": "S05",
|
|
4089
|
+
"skill": "completion-summary",
|
|
4090
|
+
"name": "Completion Summary",
|
|
4091
|
+
"status": "pending",
|
|
4092
|
+
"required_artifacts": [],
|
|
4093
|
+
"depends_on": "S04",
|
|
4094
|
+
},
|
|
4095
|
+
{
|
|
4096
|
+
"id": "S06",
|
|
4097
|
+
"skill": "prizmkit-retrospective",
|
|
4098
|
+
"name": "Retrospective",
|
|
4099
|
+
"status": "pending",
|
|
4100
|
+
"required_artifacts": [],
|
|
4101
|
+
"depends_on": "S05",
|
|
4102
|
+
},
|
|
4103
|
+
{
|
|
4104
|
+
"id": "S07",
|
|
4105
|
+
"skill": "prizmkit-committer",
|
|
4106
|
+
"name": "Runtime Commit Handoff",
|
|
4107
|
+
"status": "pending",
|
|
4108
|
+
"required_artifacts": [],
|
|
4109
|
+
"depends_on": "S06",
|
|
4110
|
+
},
|
|
4111
|
+
],
|
|
4112
|
+
"feature_state": {
|
|
4113
|
+
"stage": "code-review",
|
|
4114
|
+
"current_stage": "prizmkit-code-review",
|
|
4115
|
+
"status": "in_progress",
|
|
4116
|
+
"repair_scope": None,
|
|
4117
|
+
"repair_round": 0,
|
|
4118
|
+
"next_stage": "prizmkit-code-review",
|
|
4119
|
+
"blocked_reason": None,
|
|
4120
|
+
"terminal_status": None,
|
|
4121
|
+
},
|
|
4122
|
+
}),
|
|
4123
|
+
encoding="utf-8",
|
|
4124
|
+
)
|
|
4125
|
+
prompt = PromptGenerationResult(
|
|
4126
|
+
output_path=tmp_path / "prompt.md",
|
|
4127
|
+
checkpoint_path=checkpoint,
|
|
4128
|
+
artifact_path=artifact_dir,
|
|
4129
|
+
)
|
|
4130
|
+
raw = replace(
|
|
4131
|
+
_terminal_success_result(tmp_path),
|
|
4132
|
+
progress_summary=ProgressSummary(result_subtype="success", stop_reason="length"),
|
|
4133
|
+
)
|
|
4134
|
+
|
|
4135
|
+
classification = classify_session(
|
|
4136
|
+
raw,
|
|
4137
|
+
project_root=tmp_path,
|
|
4138
|
+
base_head=base_head,
|
|
4139
|
+
prompt=prompt,
|
|
4140
|
+
)
|
|
4141
|
+
|
|
4142
|
+
assert classification.session_status == "context_overflow"
|
|
4143
|
+
assert classification.reason == "context_overflow"
|
|
4144
|
+
assert classification.fatal_error_code == "context_overflow"
|
|
4145
|
+
assert classification.checkpoint_state["current_stage"] == "prizmkit-code-review"
|
|
4035
4146
|
|
|
4036
4147
|
|
|
4037
4148
|
def test_pi_length_stop_does_not_override_completed_checkpoint(tmp_path):
|
|
@@ -670,6 +670,143 @@ def test_fresh_checkout_and_clean_keep_id_range_and_filter_selection(
|
|
|
670
670
|
assert all(entry[3] is (mode == "--clean") for entry in captured)
|
|
671
671
|
|
|
672
672
|
|
|
673
|
+
@pytest.mark.parametrize("kind", ("feature", "bugfix", "refactor"))
|
|
674
|
+
def test_clean_uses_item_state_without_pipeline_state_but_fresh_checkout_still_requires_it(
|
|
675
|
+
monkeypatch,
|
|
676
|
+
tmp_path,
|
|
677
|
+
kind,
|
|
678
|
+
):
|
|
679
|
+
from prizmkit_runtime import reset as reset_runtime
|
|
680
|
+
from prizmkit_runtime.reset import run_reset_command
|
|
681
|
+
from prizmkit_runtime.task_checkout import TaskCheckout, persist_task_checkout
|
|
682
|
+
|
|
683
|
+
paths = _make_paths(tmp_path / kind)
|
|
684
|
+
contract = FAMILY_CONTRACTS[kind]
|
|
685
|
+
item_id = f"{contract['prefix']}001"
|
|
686
|
+
family, plan_path = _family_and_plan(paths, kind, [_item(item_id, "in_progress")])
|
|
687
|
+
status_path, _ = _write_runtime_status(
|
|
688
|
+
family,
|
|
689
|
+
item_id,
|
|
690
|
+
active_dev_branch=f"{contract['branch_prefix']}/{item_id}-clean",
|
|
691
|
+
base_branch="main",
|
|
692
|
+
)
|
|
693
|
+
checkout_path = persist_task_checkout(
|
|
694
|
+
family.state_dir,
|
|
695
|
+
TaskCheckout.active(
|
|
696
|
+
family.task_type,
|
|
697
|
+
item_id,
|
|
698
|
+
f"{contract['branch_prefix']}/{item_id}-clean",
|
|
699
|
+
"main",
|
|
700
|
+
),
|
|
701
|
+
)
|
|
702
|
+
captured = []
|
|
703
|
+
|
|
704
|
+
def fake_reset_one(received_family, item, options, project_root, return_branch, lines):
|
|
705
|
+
captured.append((received_family.kind, item.item_id, options.clean))
|
|
706
|
+
return True
|
|
707
|
+
|
|
708
|
+
monkeypatch.setattr(reset_runtime, "_reset_one", fake_reset_one)
|
|
709
|
+
monkeypatch.setattr(reset_runtime, "default_branch", lambda _root: "main")
|
|
710
|
+
|
|
711
|
+
fresh_result = run_reset_command(
|
|
712
|
+
kind,
|
|
713
|
+
(item_id, "--fresh-checkout", str(plan_path)),
|
|
714
|
+
paths,
|
|
715
|
+
)
|
|
716
|
+
|
|
717
|
+
assert fresh_result.exit_code != 0
|
|
718
|
+
assert "No pipeline state found" in fresh_result.render()
|
|
719
|
+
assert captured == []
|
|
720
|
+
assert status_path.is_file()
|
|
721
|
+
assert checkout_path.is_file()
|
|
722
|
+
assert not (family.state_dir / "pipeline.json").exists()
|
|
723
|
+
|
|
724
|
+
clean_result = run_reset_command(
|
|
725
|
+
kind,
|
|
726
|
+
(item_id, "--clean", str(plan_path)),
|
|
727
|
+
paths,
|
|
728
|
+
)
|
|
729
|
+
|
|
730
|
+
assert clean_result.exit_code == 0, clean_result.render()
|
|
731
|
+
assert captured == [(kind, item_id, True)]
|
|
732
|
+
|
|
733
|
+
|
|
734
|
+
def test_feature_clean_removes_stale_workflow_fields_and_preserves_diagnostics(tmp_path):
|
|
735
|
+
paths = _make_paths(tmp_path)
|
|
736
|
+
family, plan_path = _family_and_plan(
|
|
737
|
+
paths,
|
|
738
|
+
"feature",
|
|
739
|
+
[_item("F-001", "failed")],
|
|
740
|
+
)
|
|
741
|
+
status_path, _ = _write_runtime_status(
|
|
742
|
+
family,
|
|
743
|
+
"F-001",
|
|
744
|
+
repair_scope="production",
|
|
745
|
+
repair_round=2,
|
|
746
|
+
blocked_reason="test_blocked",
|
|
747
|
+
terminal_status="WORKFLOW_BLOCKED",
|
|
748
|
+
)
|
|
749
|
+
session_dir = family.state_dir / "F-001" / "sessions" / "old-session"
|
|
750
|
+
session_dir.mkdir(parents=True)
|
|
751
|
+
(session_dir / "session.log").write_text("old session\n", encoding="utf-8")
|
|
752
|
+
feature_slug = "001-f-001"
|
|
753
|
+
specs_dir = paths.project_root / ".prizmkit" / "specs" / feature_slug
|
|
754
|
+
specs_dir.mkdir(parents=True)
|
|
755
|
+
(specs_dir / "spec.md").write_text("old spec\n", encoding="utf-8")
|
|
756
|
+
(specs_dir / "continuation-summary.md").write_text(
|
|
757
|
+
"old continuation\n",
|
|
758
|
+
encoding="utf-8",
|
|
759
|
+
)
|
|
760
|
+
|
|
761
|
+
result = subprocess.run(
|
|
762
|
+
[
|
|
763
|
+
sys.executable,
|
|
764
|
+
str(PIPELINE_ROOT / "scripts" / "update-feature-status.py"),
|
|
765
|
+
"--feature-list",
|
|
766
|
+
str(plan_path),
|
|
767
|
+
"--state-dir",
|
|
768
|
+
str(family.state_dir),
|
|
769
|
+
"--feature-id",
|
|
770
|
+
"F-001",
|
|
771
|
+
"--feature-slug",
|
|
772
|
+
feature_slug,
|
|
773
|
+
"--project-root",
|
|
774
|
+
str(paths.project_root),
|
|
775
|
+
"--action",
|
|
776
|
+
"clean",
|
|
777
|
+
],
|
|
778
|
+
cwd=paths.project_root,
|
|
779
|
+
text=True,
|
|
780
|
+
stdout=subprocess.PIPE,
|
|
781
|
+
stderr=subprocess.PIPE,
|
|
782
|
+
check=False,
|
|
783
|
+
)
|
|
784
|
+
|
|
785
|
+
assert result.returncode == 0, (result.stdout, result.stderr)
|
|
786
|
+
payload = json.loads(result.stdout)
|
|
787
|
+
assert payload["action"] == "clean"
|
|
788
|
+
assert payload["new_status"] == "pending"
|
|
789
|
+
updated_list = json.loads(plan_path.read_text(encoding="utf-8"))
|
|
790
|
+
assert updated_list["features"][0]["status"] == "pending"
|
|
791
|
+
updated_status = json.loads(status_path.read_text(encoding="utf-8"))
|
|
792
|
+
for stale_field in (
|
|
793
|
+
"repair_scope",
|
|
794
|
+
"repair_round",
|
|
795
|
+
"blocked_reason",
|
|
796
|
+
"terminal_status",
|
|
797
|
+
):
|
|
798
|
+
assert stale_field not in updated_status
|
|
799
|
+
assert updated_status["retry_count"] == 0
|
|
800
|
+
assert updated_status["infra_error_count"] == 0
|
|
801
|
+
assert updated_status["sessions"] == []
|
|
802
|
+
assert updated_status["last_session_id"] is None
|
|
803
|
+
assert updated_status["resume_from_phase"] is None
|
|
804
|
+
assert updated_status["custom_diagnostic"] == {"keep": True}
|
|
805
|
+
assert "continuation_pending" not in updated_status
|
|
806
|
+
assert not session_dir.exists()
|
|
807
|
+
assert not specs_dir.exists()
|
|
808
|
+
|
|
809
|
+
|
|
673
810
|
def _valid_bug(bug_id, status="pending", dependencies=None):
|
|
674
811
|
return {
|
|
675
812
|
"id": bug_id,
|
|
@@ -1313,7 +1313,8 @@ def test_headless_review_guidance_maps_stage_local_skill_result():
|
|
|
1313
1313
|
|
|
1314
1314
|
skill_text = skill_path.read_text(encoding="utf-8")
|
|
1315
1315
|
assert "the staged diff, and the unstaged diff" in skill_text
|
|
1316
|
-
assert "untracked, deleted, and renamed
|
|
1316
|
+
assert "untracked, deleted, and renamed paths" in skill_text
|
|
1317
|
+
assert "default-excluded changed paths" in skill_text
|
|
1317
1318
|
assert "accepted = 0" in skill_text
|
|
1318
1319
|
assert "ten completed review rounds" in skill_text
|
|
1319
1320
|
assert "## Phase 3: Independent Code Review" in skill_text
|
|
@@ -18,7 +18,7 @@ description: "PrizmKit framework introduction and lifecycle navigator. Explains
|
|
|
18
18
|
## When NOT to Use
|
|
19
19
|
|
|
20
20
|
- User already knows the required skill; invoke that skill directly.
|
|
21
|
-
- A formal requirement is already in progress; continue
|
|
21
|
+
- A formal requirement is already in progress; continue through its owning coordinator or current atomic stage.
|
|
22
22
|
- User wants an immediate low-risk direct edit; perform the edit and its specific verification.
|
|
23
23
|
|
|
24
24
|
## Responsibility Map
|
|
@@ -125,18 +125,6 @@ The active coordinator validates all required evidence before invoking `prizmkit
|
|
|
125
125
|
|
|
126
126
|
Pushing to a remote is a separate explicit action. Deployment is always a separate invocation of `/prizmkit-deploy`.
|
|
127
127
|
|
|
128
|
-
## Workflow State
|
|
129
|
-
|
|
130
|
-
An active requirement may use:
|
|
131
|
-
|
|
132
|
-
```text
|
|
133
|
-
.prizmkit/state/workflows/<requirement-identity>.json
|
|
134
|
-
```
|
|
135
|
-
|
|
136
|
-
`<requirement-identity>` is the validated exact basename of the active artifact directory. An existing state file that records a different artifact directory is a blocking collision and is never silently overwritten or suffixed. This coordinator-owned state records current stage, terminal status, repair scope, repair round, and resume entry. Atomic Skills do not read or write it. The target project controls whether the file is committed, ignored, or shared.
|
|
137
|
-
|
|
138
|
-
Read `${SKILL_DIR}/references/workflow-state-protocol.md` for the state protocol. If the state file is missing or stale, use `spec.md`, `plan.md`, the review report, `test-report.md`, and `test-result.json` to reconstruct the safest recoverable stage and report that reconstruction.
|
|
139
|
-
|
|
140
128
|
## Quick Start
|
|
141
129
|
|
|
142
130
|
Install the skills through the host's skills installer, for example:
|
|
@@ -18,6 +18,18 @@ description: "Review one supplied complete change with a bounded Main-Agent revi
|
|
|
18
18
|
- Review repairs use targeted verification appropriate to each accepted finding.
|
|
19
19
|
- `{artifact_dir}/review-report.md` is the only persisted review artifact for this execution.
|
|
20
20
|
|
|
21
|
+
## Report Renderer Contract
|
|
22
|
+
|
|
23
|
+
Use `${SKILL_DIR}/scripts/render_review_report.py` as the canonical executable writer for report initialization, validated progress append, and finalization:
|
|
24
|
+
|
|
25
|
+
```text
|
|
26
|
+
resolved Python 3 interpreter + render_review_report.py init <report>
|
|
27
|
+
resolved Python 3 interpreter + render_review_report.py append <report> ← validated event JSON on standard input
|
|
28
|
+
resolved Python 3 interpreter + render_review_report.py finalize <report> ← validated final JSON on standard input
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Resolve a compatible Python 3 interpreter through the current Host's executable capability; do not require one operating-system-specific command name. If no compatible interpreter is available, follow `${SKILL_DIR}/references/review-report-template.md` directly, record the renderer fallback in Final Verification evidence, and apply every equivalent count, ordering, field, and terminal-shape check before returning a verdict. Never silently omit renderer-owned fields or claim executable validation occurred during fallback.
|
|
32
|
+
|
|
21
33
|
## Atomic Stage Boundary
|
|
22
34
|
|
|
23
35
|
`prizmkit-code-review` owns the complete review, accepted-correction repairs, targeted verification, and `review-report.md` for one supplied change. It returns `PASS` or `NEEDS_FIXES` and stops.
|
|
@@ -43,16 +55,23 @@ description: "Review one supplied complete change with a bounded Main-Agent revi
|
|
|
43
55
|
|
|
44
56
|
Review only the caller-supplied `artifact_dir`; never discover a different recent artifact. Missing or inconsistent stage input produces `NEEDS_FIXES` with the exact input blocker.
|
|
45
57
|
|
|
58
|
+
## Default Correctness Scope
|
|
59
|
+
|
|
60
|
+
The current requirement change outside `.prizmkit/**` is the ordinary correctness scope. Every `.prizmkit/**` path is excluded by default from correctness review, including correctness diffs, candidate findings, repairs, and verification scope. The Skill may read exact caller-supplied `.prizmkit` artifacts such as `spec.md` and `plan.md` as requirement evidence and may write its own `review-report.md`; neither action makes `.prizmkit` content reviewable production change.
|
|
61
|
+
|
|
62
|
+
Generated host-support paths are also excluded by default, including `AGENTS.md`, `skills-lock.json`, installed platform directories, and local platform settings. An explicitly owned support change requires a separate explicit support-artifact validation contract; do not pull it into ordinary Code Review through workspace expansion. Record excluded changed paths in the report so exclusion is visible rather than mistaken for reviewed content.
|
|
63
|
+
|
|
46
64
|
## Phase 0: Initialize Report and Reuse Current Context
|
|
47
65
|
|
|
48
66
|
1. Resolve `{artifact_dir}` and `{artifact_dir}/review-report.md` from the active requirement context.
|
|
49
|
-
2. At the start of each execution,
|
|
50
|
-
3. Within that execution, append
|
|
51
|
-
4. Start from the Main Agent's current requirement context and inspect the complete workspace
|
|
52
|
-
5.
|
|
53
|
-
6.
|
|
54
|
-
7.
|
|
55
|
-
8.
|
|
67
|
+
2. At the start of each execution, initialize a replacement report through the Report Renderer Contract and `${SKILL_DIR}/references/review-report-template.md`.
|
|
68
|
+
3. Within that execution, append every review round, repair batch, independent-review event, final verification, and exactly one `## Final Result` through the renderer or its visible equivalent fallback.
|
|
69
|
+
4. Start from the Main Agent's current requirement context and inspect the complete workspace inventory first: `git status --short`, the staged diff, and the unstaged diff. Include untracked, deleted, and renamed paths in classification, then remove default-excluded `.prizmkit/**` and host-support paths from the correctness scope before producing findings.
|
|
70
|
+
5. Append one validated `scope-classification` renderer event containing exact in-scope paths and exact default-excluded changed paths; do not describe exclusions as reviewed or silently repair them.
|
|
71
|
+
6. Reuse current context and load only missing or potentially stale material required to resolve a concrete ambiguity, verify an acceptance criterion, or understand a changed contract.
|
|
72
|
+
7. Inspect unchanged callers, dependents, contracts, or tests only when the in-scope diff changes or may violate an interface, shared behavior, or regression boundary. Do not perform an unconditional repository-wide dependency sweep.
|
|
73
|
+
8. For `review_scope=delta`, focus on in-scope files and behavior affected since the prior review pass and expand only across contracts implicated by that delta.
|
|
74
|
+
9. If no in-scope changes exist, record final verification and `PASS` only when the current requirement context and prior implementation state prove there is nothing left to review.
|
|
56
75
|
|
|
57
76
|
## Phase 1: Main-Agent Review Loop
|
|
58
77
|
|
|
@@ -77,7 +96,7 @@ For every candidate finding:
|
|
|
77
96
|
- `unresolved`: correctness or safe repair cannot be established.
|
|
78
97
|
3. Treat Missing tools, permissions, environment, or required evidence as an unresolved finding when they prevent review verification.
|
|
79
98
|
4. If a repair cannot be completed safely, record an unresolved finding and return `NEEDS_FIXES`.
|
|
80
|
-
5. Append the review
|
|
99
|
+
5. Append the validated review-round event through the Report Renderer Contract.
|
|
81
100
|
|
|
82
101
|
Round behavior:
|
|
83
102
|
|
|
@@ -105,7 +124,7 @@ For accepted findings while the round limit remains:
|
|
|
105
124
|
1. Repair directly in the active workspace.
|
|
106
125
|
2. Run targeted tests, static checks, or other verification appropriate to each repair.
|
|
107
126
|
3. Inspect the complete resulting diff for unrelated changes and regressions.
|
|
108
|
-
4. Append repair
|
|
127
|
+
4. Append validated repair-verification evidence through the Report Renderer Contract and continue the review loop.
|
|
109
128
|
|
|
110
129
|
Do not turn review verification into a broad unrelated test campaign; use targeted checks that establish each repair.
|
|
111
130
|
|
|
@@ -131,7 +150,7 @@ Before completing:
|
|
|
131
150
|
|
|
132
151
|
1. Confirm the final workspace is the complete reviewed change.
|
|
133
152
|
2. Confirm all accepted findings are fixed and no unresolved finding remains for `PASS`.
|
|
134
|
-
3. Append final verification and exactly one final result.
|
|
153
|
+
3. Append final verification and exactly one final result through the Report Renderer Contract or its visibly recorded equivalent fallback.
|
|
135
154
|
|
|
136
155
|
Valid results:
|
|
137
156
|
|
|
@@ -62,14 +62,15 @@ The Main Agent supplies:
|
|
|
62
62
|
|
|
63
63
|
- original requirement and confirmed clarifications;
|
|
64
64
|
- exact artifact, spec, and plan paths plus current contents;
|
|
65
|
-
- current workspace status;
|
|
66
|
-
- staged and unstaged tracked changes;
|
|
67
|
-
- relevant untracked, deleted, and renamed content;
|
|
65
|
+
- current workspace status after the Main Agent classifies default exclusions;
|
|
66
|
+
- staged and unstaged tracked changes in the ordinary correctness scope;
|
|
67
|
+
- relevant in-scope untracked, deleted, and renamed content;
|
|
68
|
+
- exact default-excluded changed paths as visible exclusion metadata, never as reviewed content;
|
|
68
69
|
- implementation task completion and targeted verification results;
|
|
69
70
|
- response number and total budget;
|
|
70
71
|
- prior adjudication, actual repairs, and repair verification on continuation or replacement.
|
|
71
72
|
|
|
72
|
-
The Main Agent runs Git and captures authoritative current-change state. The Reviewer may use structurally read-only checkout access to inspect unchanged callers, consumers, contracts, schemas, types, configurations, fixtures, or tests only when concrete coupling to this requirement justifies it. It must not run commands or perform an unconditional repository-wide scan. Missing or inconsistent required input produces `REVIEW_BLOCKED`, never partial success.
|
|
73
|
+
The Main Agent runs Git and captures authoritative current-change state, then applies the Skill's default `.prizmkit/**` and generated host-support exclusions before supplying the correctness change. The Reviewer may read the exact supplied spec/plan artifacts but must not inspect other `.prizmkit` content. It may use structurally read-only checkout access to inspect unchanged callers, consumers, contracts, schemas, types, configurations, fixtures, or tests only when concrete coupling to this requirement justifies it. It must not run commands or perform an unconditional repository-wide scan. Missing or inconsistent required input produces `REVIEW_BLOCKED`, never partial success.
|
|
73
74
|
|
|
74
75
|
## Initial Reviewer Prompt
|
|
75
76
|
|
|
@@ -16,6 +16,21 @@ Within that execution, append sections only. Never edit or replace an earlier pr
|
|
|
16
16
|
|
|
17
17
|
## Progress Sections
|
|
18
18
|
|
|
19
|
+
### Review Scope
|
|
20
|
+
|
|
21
|
+
Append once after workspace inventory classification and before the first review round:
|
|
22
|
+
|
|
23
|
+
```markdown
|
|
24
|
+
## Review Scope
|
|
25
|
+
|
|
26
|
+
- In-Scope Paths:
|
|
27
|
+
- <exact requirement-owned path or None.>
|
|
28
|
+
- Default-Excluded Changed Paths:
|
|
29
|
+
- <exact .prizmkit/support path or None.>
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
Paths under `.prizmkit/**` and generated host-support paths are visible exclusions, not reviewed content.
|
|
33
|
+
|
|
19
34
|
### Main-Agent Review Round
|
|
20
35
|
|
|
21
36
|
```markdown
|
|
@@ -11,6 +11,7 @@ from typing import Any
|
|
|
11
11
|
|
|
12
12
|
VERDICTS = {"PASS", "NEEDS_FIXES"}
|
|
13
13
|
EVENTS = {
|
|
14
|
+
"scope-classification",
|
|
14
15
|
"main-review-round",
|
|
15
16
|
"repair-verification",
|
|
16
17
|
"independent-review-round",
|
|
@@ -25,6 +26,8 @@ REVIEWER_RESULTS = {
|
|
|
25
26
|
}
|
|
26
27
|
ADJUDICATION_DECISIONS = {"accepted", "rejected", "unresolved"}
|
|
27
28
|
RECHECK_STATES = {"yes", "no", "not applicable"}
|
|
29
|
+
ROUND_CONTINUATION_MODES = {"native", "replacement"}
|
|
30
|
+
DOWNGRADE_CONTINUATION_MODES = {"native", "replacement", "mixed", "not-applicable"}
|
|
28
31
|
FINAL_HEADING = "## Final Result"
|
|
29
32
|
|
|
30
33
|
|
|
@@ -54,6 +57,25 @@ def _require_count(data: dict[str, Any], name: str, *, maximum: int | None = Non
|
|
|
54
57
|
return value
|
|
55
58
|
|
|
56
59
|
|
|
60
|
+
def _require_text_list(data: dict[str, Any], name: str) -> list[str]:
|
|
61
|
+
value = data.get(name)
|
|
62
|
+
if not isinstance(value, list):
|
|
63
|
+
raise ReportStateError(f"{name} must be an array of strings")
|
|
64
|
+
normalized: list[str] = []
|
|
65
|
+
for item in value:
|
|
66
|
+
if not isinstance(item, str) or not item.strip():
|
|
67
|
+
raise ReportStateError(f"{name} must contain only non-empty strings")
|
|
68
|
+
text = item.strip()
|
|
69
|
+
if text in normalized:
|
|
70
|
+
raise ReportStateError(f"{name} must not contain duplicates")
|
|
71
|
+
normalized.append(text)
|
|
72
|
+
return normalized
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def _render_path_list(paths: list[str]) -> list[str]:
|
|
76
|
+
return [f" - {path}" for path in paths] if paths else [" - None."]
|
|
77
|
+
|
|
78
|
+
|
|
57
79
|
def _report_text(path: Path) -> str:
|
|
58
80
|
try:
|
|
59
81
|
return path.read_text(encoding="utf-8")
|
|
@@ -91,6 +113,20 @@ def _render_event(data: dict[str, Any]) -> str:
|
|
|
91
113
|
if event not in EVENTS:
|
|
92
114
|
raise ReportStateError(f"event has invalid value: {event!r}")
|
|
93
115
|
|
|
116
|
+
if event == "scope-classification":
|
|
117
|
+
in_scope = _require_text_list(data, "in_scope_paths")
|
|
118
|
+
excluded = _require_text_list(data, "excluded_paths")
|
|
119
|
+
return "\n".join(
|
|
120
|
+
[
|
|
121
|
+
"## Review Scope",
|
|
122
|
+
"",
|
|
123
|
+
"- In-Scope Paths:",
|
|
124
|
+
*_render_path_list(in_scope),
|
|
125
|
+
"- Default-Excluded Changed Paths:",
|
|
126
|
+
*_render_path_list(excluded),
|
|
127
|
+
]
|
|
128
|
+
)
|
|
129
|
+
|
|
94
130
|
if event == "main-review-round":
|
|
95
131
|
round_number = _require_count(data, "round", maximum=10)
|
|
96
132
|
if round_number < 1:
|
|
@@ -131,6 +167,15 @@ def _render_event(data: dict[str, Any]) -> str:
|
|
|
131
167
|
response = _require_count(data, "response", maximum=5)
|
|
132
168
|
if response < 1:
|
|
133
169
|
raise ReportStateError("response must be at least 1")
|
|
170
|
+
capability_basis = _require_text(data, "capability_basis")
|
|
171
|
+
continuation_mode = _require_text(data, "continuation_mode")
|
|
172
|
+
if continuation_mode not in ROUND_CONTINUATION_MODES:
|
|
173
|
+
raise ReportStateError(
|
|
174
|
+
f"continuation_mode has invalid value: {continuation_mode!r}"
|
|
175
|
+
)
|
|
176
|
+
reviewer_replacements = _require_count(
|
|
177
|
+
data, "reviewer_replacements", maximum=1
|
|
178
|
+
)
|
|
134
179
|
result = _require_text(data, "result")
|
|
135
180
|
if result not in REVIEWER_RESULTS:
|
|
136
181
|
raise ReportStateError(f"result has invalid value: {result!r}")
|
|
@@ -156,6 +201,10 @@ def _render_event(data: dict[str, Any]) -> str:
|
|
|
156
201
|
[
|
|
157
202
|
f"## Independent Review Round {response}",
|
|
158
203
|
"",
|
|
204
|
+
"- Capability Gate: ENABLED",
|
|
205
|
+
f"- Capability Basis: {capability_basis}",
|
|
206
|
+
f"- Continuation Mode: {continuation_mode}",
|
|
207
|
+
f"- Reviewer Replacements: {reviewer_replacements}",
|
|
159
208
|
f"- Result: {result}",
|
|
160
209
|
f"- Corrections: {corrections}",
|
|
161
210
|
f"- Accepted: {accepted}",
|
|
@@ -181,6 +230,15 @@ def _render_event(data: dict[str, Any]) -> str:
|
|
|
181
230
|
)
|
|
182
231
|
|
|
183
232
|
if event == "independent-review-downgrade":
|
|
233
|
+
capability_basis = _require_text(data, "capability_basis")
|
|
234
|
+
continuation_mode = _require_text(data, "continuation_mode")
|
|
235
|
+
if continuation_mode not in DOWNGRADE_CONTINUATION_MODES:
|
|
236
|
+
raise ReportStateError(
|
|
237
|
+
f"continuation_mode has invalid value: {continuation_mode!r}"
|
|
238
|
+
)
|
|
239
|
+
reviewer_replacements = _require_count(
|
|
240
|
+
data, "reviewer_replacements", maximum=1
|
|
241
|
+
)
|
|
184
242
|
rechecked = _require_text(data, "final_state_independently_rechecked")
|
|
185
243
|
if rechecked not in RECHECK_STATES:
|
|
186
244
|
raise ReportStateError(
|
|
@@ -192,6 +250,9 @@ def _render_event(data: dict[str, Any]) -> str:
|
|
|
192
250
|
"## Independent Review Downgrade",
|
|
193
251
|
"",
|
|
194
252
|
f"- Reason: {_require_text(data, 'reason')}",
|
|
253
|
+
f"- Capability Basis: {capability_basis}",
|
|
254
|
+
f"- Continuation Mode: {continuation_mode}",
|
|
255
|
+
f"- Reviewer Replacements: {reviewer_replacements}",
|
|
195
256
|
f"- Fallback: {_require_text(data, 'fallback')}",
|
|
196
257
|
f"- Final State Independently Rechecked: {rechecked}",
|
|
197
258
|
]
|