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.
Files changed (24) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +1 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +20 -11
  4. package/bundled/dev-pipeline/scripts/update-feature-status.py +7 -0
  5. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +115 -4
  6. package/bundled/dev-pipeline/tests/test_reset_modes.py +137 -0
  7. package/bundled/dev-pipeline/tests/test_unified_cli.py +2 -1
  8. package/bundled/skills/_metadata.json +1 -1
  9. package/bundled/skills/prizmkit/SKILL.md +1 -13
  10. package/bundled/skills/prizmkit-code-review/SKILL.md +29 -10
  11. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +5 -4
  12. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +15 -0
  13. package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +61 -0
  14. package/bundled/skills/prizmkit-committer/SKILL.md +22 -17
  15. package/bundled/skills/prizmkit-init/SKILL.md +21 -25
  16. package/bundled/skills/prizmkit-init/references/config-schema.md +11 -7
  17. package/bundled/skills/prizmkit-init/references/rules/layer-detection.md +3 -3
  18. package/bundled/skills/prizmkit-plan/SKILL.md +5 -4
  19. package/bundled/skills/prizmkit-plan/assets/plan-template.md +4 -1
  20. package/bundled/skills/prizmkit-plan/references/verification-checklist.md +3 -2
  21. package/bundled/skills/prizmkit-workflow/SKILL.md +35 -3
  22. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +24 -5
  23. package/package.json +1 -1
  24. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +0 -178
@@ -1,5 +1,5 @@
1
1
  {
2
- "frameworkVersion": "1.1.156",
3
- "bundledAt": "2026-07-27T23:46:04.550Z",
4
- "bundledFrom": "6d92126"
2
+ "frameworkVersion": "1.1.159",
3
+ "bundledAt": "2026-07-28T08:35:59.394Z",
4
+ "bundledFrom": "7908918"
5
5
  }
@@ -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 (options.fresh_checkout or options.clean) and not (family.state_dir / "pipeline.json").is_file():
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 _is_terminal_success(result):
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 test_pi_length_stop_with_zero_exit_and_no_checkpoint_uses_no_completion_policy(tmp_path):
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 == "workflow_skipped"
4033
- assert classification.reason == "no_semantic_completion"
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 files" in skill_text
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
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.1.156",
2
+ "version": "1.1.159",
3
3
  "skills": {
4
4
  "prizmkit": {
5
5
  "description": "Framework introduction and navigation for the formal single-requirement lifecycle, project initialization, Prizm docs, and independent deployment.",
@@ -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 from its workflow state and current stage.
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, replace any prior report with a new execution header using `${SKILL_DIR}/references/review-report-template.md`.
50
- 3. Within that execution, append progress after every review round, repair batch, final verification, and exactly one `## Final Result`.
51
- 4. Start from the Main Agent's current requirement context and inspect the complete workspace change first: `git status --short`, the staged diff, and the unstaged diff. Include untracked, deleted, and renamed files in the review scope.
52
- 5. 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.
53
- 6. Inspect unchanged callers, dependents, contracts, or tests only when the diff changes or may violate an interface, shared behavior, or regression boundary. Do not perform an unconditional repository-wide dependency sweep.
54
- 7. For `review_scope=delta`, focus on files and behavior affected since the prior review pass and expand only across contracts implicated by that delta.
55
- 8. If no changes exist, record final verification and `PASS` only when the current requirement context and prior implementation state prove there is nothing left to review.
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 round to `review-report.md`.
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 verification and continue the review loop.
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
  ]