prizmkit 1.1.125 → 1.1.126

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 (25) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +49 -3
  3. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +75 -14
  4. package/bundled/dev-pipeline/prizmkit_runtime/runtime_helper.py +37 -0
  5. package/bundled/dev-pipeline/prizmkit_runtime/task_checkout.py +267 -0
  6. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +3 -2
  7. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
  8. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +6 -18
  9. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +6 -15
  10. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +8 -9
  11. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +9 -9
  12. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +15 -18
  13. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +11 -17
  14. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +9 -9
  15. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +8 -5
  16. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +1 -1
  17. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +1 -1
  18. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +206 -3
  19. package/bundled/dev-pipeline/tests/test_runtime_helper.py +36 -0
  20. package/bundled/dev-pipeline/tests/test_unified_cli.py +10 -7
  21. package/bundled/skills/_metadata.json +1 -1
  22. package/bundled/skills/prizmkit-code-review/SKILL.md +128 -95
  23. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +89 -25
  24. package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +246 -114
  25. package/package.json +1 -1
@@ -180,24 +180,15 @@ After implement completes, verify:
180
180
  If `FAST_PATH=true` (≤ 2 tasks, obvious root cause), skip this phase entirely and mark checkpoint step `prizmkit-code-review` as `skipped` before continuing.
181
181
 
182
182
  Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/bugfix/{{BUG_ID}}/`:
183
- - The skill chooses a platform-supported fresh read-only Reviewer, prefers direct access to the active checkout, and supplies changed content that a platform-forced worktree cannot see
184
- - The skill runs an internal review-fix loop (Reviewer Main-Agent filter Main-Agent fix, up to ten completed rounds) and writes `review-report.md`
185
- - `review-report.md` must contain `## Verdict`
183
+ - The skill first runs up to ten Main-Agent self-review rounds, then semantic risk limits independent Reviewers to `low=0`, `medium=1`, `high=2`
184
+ - High risk starts Reviewer 2 only after an accepted high finding was repaired, tested, and reconverged; Reviewer 3 is forbidden
185
+ - The skill preserves its independent Reviewer prompt and appends progress to `review-report.md`
186
186
 
187
- **Gate Check — Review Report**:
188
- After `/prizmkit-code-review` returns, verify the review report:
187
+ **Gate Check — Final Review Result**:
189
188
  ```bash
190
- {{RUNTIME_HELPER_CMD}} text contains .prizmkit/bugfix/{{BUG_ID}}/review-report.md --pattern "## Verdict"
189
+ {{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/bugfix/{{BUG_ID}}/review-report.md
191
190
  ```
192
- If GATE:MISSING:
193
- - Do not re-run `/prizmkit-code-review` in an unbounded report-repair loop.
194
- - Perform one bounded status check; retry `/prizmkit-code-review` at most once only if the missing report appears caused by a transient team/config/lock error.
195
- - If the report is still missing after that single check/retry, write `failure-log.md` with the spawn/skill error and last observable state, then stop with a clear recovery failure. Do not replace the independent Reviewer with Main-Agent self-review.
196
-
197
- Read `review-report.md` and check the Verdict:
198
- - `PASS` → proceed
199
- - `NEEDS_FIXES` → the skill reached the ten-round safety fuse or left an accepted finding unresolved; log remaining findings and proceed according to the task failure policy
200
- - `BLOCKED` → log the infrastructure reason and stop so the review can be retried
191
+ Handle `REVIEW_PASS`, `REVIEW_NEEDS_FIXES`, and `REVIEW_BLOCKED` according to task policy. `REVIEW_INCOMPLETE` or `REVIEW_INVALID` means review did not complete: inspect progress once, write `failure-log.md`, and stop. Do not accept stale verdict text or replace required independent review with Main-Agent self-review.
201
192
 
202
193
  {{IF_BROWSER_INTERACTION}}
203
194
 
@@ -139,22 +139,21 @@ Apply the browser behavior preservation protocol where UI behavior can be affect
139
139
 
140
140
  ### Phase 4: Review — Code Review & Behavior Verification
141
141
 
142
- Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/` directly from the main orchestrator. The skill loads its skill-owned `references/reviewer-agent-prompt.md`, chooses a platform-supported fresh read-only Reviewer, prefers direct access to the active checkout, and supplies changed content that a platform-forced worktree cannot see.
142
+ Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/` directly from the main orchestrator. The skill first runs up to ten Main-Agent self-review rounds, then semantic risk limits independent Reviewers to `low=0`, `medium=1`, `high=2`. High risk starts Reviewer 2 only after an accepted high finding was repaired, tested, and reconverged; Reviewer 3 is forbidden.
143
143
 
144
144
  Review requirements:
145
145
  - verify refactor quality against spec.md and plan.md;
146
146
  - verify behavior preservation evidence;
147
147
  - run or confirm relevant tests;
148
- - write `.prizmkit/refactor/{{REFACTOR_ID}}/review-report.md` with `## Verdict`.
148
+ - preserve append-only progress in `.prizmkit/refactor/{{REFACTOR_ID}}/review-report.md`.
149
149
 
150
- If the report is missing after one bounded status check, write `failure-log.md` and stop with a clear recovery failure. Do not replace the independent Reviewer with Main-Agent self-review.
151
-
152
- Read `review-report.md` and check the Verdict:
153
- - `PASS` → proceed.
154
- - `NEEDS_FIXES` log unresolved accepted findings and proceed only when they do not block behavior preservation.
155
- - `BLOCKED` → log the infrastructure reason and stop so the review can be retried.
150
+ **Gate Check Final Review Result**:
151
+ ```bash
152
+ {{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/refactor/{{REFACTOR_ID}}/review-report.md
153
+ ```
154
+ Handle `REVIEW_PASS`, `REVIEW_NEEDS_FIXES`, and `REVIEW_BLOCKED` according to behavior-preservation policy. `REVIEW_INCOMPLETE` or `REVIEW_INVALID` means review did not complete: inspect progress once, write `failure-log.md`, and stop. Do not accept stale verdict text or replace required independent review with Main-Agent self-review.
156
155
 
157
- **Checkpoint update**: set step `prizmkit-code-review` to `completed` only after `PASS`, or after an explicitly accepted non-blocking `NEEDS_FIXES` result. Do not complete the checkpoint for `BLOCKED`.
156
+ **Checkpoint update**: set step `prizmkit-code-review` to `completed` only for an accepted valid Final Result. Do not complete it for blocked, incomplete, or invalid reports.
158
157
 
159
158
  ---
160
159
 
@@ -12,19 +12,19 @@ If `FAST_PATH=true`, skip this phase intentionally:
12
12
 
13
13
  Then continue to the next checkpoint step.
14
14
 
15
- Otherwise, run `/prizmkit-code-review` with `artifact_dir=.prizmkit/bugfix/{{BUG_ID}}/`. The skill chooses a platform-supported fresh read-only Reviewer, prefers direct access to the active checkout, and supplies changed content that a platform-forced worktree cannot see.
15
+ Otherwise, run `/prizmkit-code-review` with `artifact_dir=.prizmkit/bugfix/{{BUG_ID}}/`. The skill first runs a Main-Agent self-review loop for up to ten completed rounds, then semantic risk permits independent Reviewers at limits `low=0`, `medium=1`, `high=2`. High risk starts Reviewer 2 only after Reviewer 1 produced an accepted high finding that was repaired, tested, and reconverged. Reviewer 3 is forbidden.
16
16
 
17
- The skill runs its internal fresh Reviewer Main-Agent filter Main-Agent fix loop for up to ten completed rounds and writes `review-report.md`. Do not prescribe Agent launch parameters from this pipeline template.
17
+ The skill owns its unchanged independent Reviewer prompt, current-checkout preference, forced-worktree changed-content fallback, one bounded infrastructure retry per required Reviewer position, and append-only progress in `review-report.md`.
18
18
 
19
- **Gate Check — Review Report**:
19
+ **Gate Check — Final Review Result**:
20
20
 
21
21
  ```bash
22
- {{RUNTIME_HELPER_CMD}} text contains .prizmkit/bugfix/{{BUG_ID}}/review-report.md --pattern "## Verdict"
22
+ {{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/bugfix/{{BUG_ID}}/review-report.md
23
23
  ```
24
24
 
25
- - `PASS` → proceed.
26
- - `NEEDS_FIXES` → log unresolved accepted findings and proceed only when they do not block acceptance criteria.
27
- - `BLOCKED` → log the infrastructure reason and stop so the review can be retried.
28
- - Missing reportone bounded retry, then write `failure-log.md` and stop if independent review remains blocked; do not replace it with Main-Agent self-review.
25
+ - `REVIEW_PASS` → proceed.
26
+ - `REVIEW_NEEDS_FIXES` → log unresolved findings and proceed only when explicit task policy allows them.
27
+ - `REVIEW_BLOCKED` → log infrastructure evidence and stop so recovery can retry.
28
+ - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` inspect progress once, write `failure-log.md`, and stop. Do not accept old or incidental verdict text and do not replace required independent review with Main-Agent self-review.
29
29
 
30
- **Checkpoint update**: Set step `prizmkit-code-review` to `completed` when review is complete, or `skipped` for FAST_PATH.
30
+ **Checkpoint update**: Set step `prizmkit-code-review` to `completed` only for an accepted valid Final Result, or `skipped` for FAST_PATH.
@@ -1,26 +1,23 @@
1
1
  ### Review — Code Review
2
2
 
3
- Review the completed implementation before the scoped feature test gate runs. Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so the code-review skill can read spec, plan, context snapshot, implementation log, and current diff artifacts. Do not require or read a prior `/prizmkit-test` report here; test generation and test-fix looping happen in the next phase.
3
+ Review the completed implementation before the scoped feature test gate runs. Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so the code-review skill can read spec, plan, context snapshot, implementation log, and current diff artifacts. Do not require a prior `/prizmkit-test` report here; the next phase owns the formal test gate.
4
4
 
5
- Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. The skill chooses a platform-supported fresh read-only Reviewer, prefers direct access to the active checkout, and supplies changed content that a platform-forced worktree cannot see. Do not prescribe Agent launch parameters from this pipeline template.
5
+ Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. The skill first runs a Main-Agent self-review loop for up to ten completed rounds, then semantic risk permits independent Reviewers at limits `low=0`, `medium=1`, `high=2`. High risk starts Reviewer 2 only after Reviewer 1 produced an accepted high finding that was repaired, tested, and reconverged. Reviewer 3 is forbidden.
6
6
 
7
- The skill runs an internal review-fix loop (fresh Reviewer → Main-Agent filter Main-Agent fix, up to ten completed rounds) and writes `review-report.md` to the artifact directory.
7
+ The skill owns Reviewer launch parameters, current-checkout preference, forced-worktree changed-content fallback, one bounded infrastructure retry per required Reviewer position, and append-only progress in `review-report.md`.
8
+
9
+ **Gate Check — Final Review Result**:
8
10
 
9
- **Gate Check — Review Report**:
10
- After `/prizmkit-code-review` returns, verify the review report:
11
11
  ```bash
12
- {{RUNTIME_HELPER_CMD}} text contains .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md --pattern "## Verdict"
12
+ {{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md
13
13
  ```
14
- If GATE:MISSING:
15
- - Do not re-run `/prizmkit-code-review` in an unbounded report-repair loop.
16
- - Perform one bounded status check; retry at most once: inspect the skill output, `review-report.md` path, and any Reviewer agent spawn messages.
17
- - If the missing report is caused by team/config/lock errors from the Reviewer agent spawn or review skill error, retry `/prizmkit-code-review` at most once only if it appears transient.
18
- - If the report is still missing after that single check/retry, write `failure-log.md` with the spawn/skill error and last observable state, then stop with a clear recovery failure. Do not synthesize a passing report through Main-Agent self-review.
19
-
20
- Read `review-report.md` and check the Verdict:
21
- - `PASS` → proceed to next phase
22
- - `NEEDS_FIXES` log the unresolved accepted findings and proceed according to the task failure policy (do not retry externally)
23
- - `BLOCKED` → log the infrastructure reason and stop so the review can be retried
24
-
25
- **CP-3**: Review complete, report written.
14
+
15
+ Handle the marker:
16
+
17
+ - `REVIEW_PASS` proceed to next phase.
18
+ - `REVIEW_NEEDS_FIXES` log unresolved findings and follow the task failure policy; do not externally restart the review loop.
19
+ - `REVIEW_BLOCKED` → log infrastructure evidence and stop so recovery can retry.
20
+ - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` → inspect the append-only progress once, write `failure-log.md`, and stop. Do not treat an old or incidental verdict as completion and do not synthesize a passing result through Main-Agent self-review.
21
+
22
+ **CP-3**: Review has a valid Final Result and `review-report.md` preserves this execution's progress.
26
23
 
@@ -1,29 +1,23 @@
1
1
  ### Review — Code Review
2
2
 
3
- Review the completed implementation before the scoped feature test gate runs. Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so the code-review skill can read spec, plan, context snapshot, implementation log, and current diff artifacts. Do not require or read a prior `/prizmkit-test` report here; test generation and test-fix looping happen in the next phase.
3
+ Review the completed implementation before the scoped feature test gate runs. Use `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` so the code-review skill can read spec, plan, context snapshot, implementation log, and current diff artifacts. Do not require a prior `/prizmkit-test` report here; the next phase owns the formal test gate.
4
4
 
5
- Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. The skill chooses a platform-supported fresh read-only Reviewer, prefers direct access to the active checkout, and supplies changed content that a platform-forced worktree cannot see. Do not prescribe Agent launch parameters from this bootstrap prompt.
5
+ Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. The skill first runs a Main-Agent self-review loop for up to ten completed rounds, then semantic risk permits independent Reviewers at limits `low=0`, `medium=1`, `high=2`. High risk starts Reviewer 2 only after Reviewer 1 produced an accepted high finding that was repaired, tested, and reconverged. Reviewer 3 is forbidden.
6
6
 
7
- If the independent Reviewer cannot start or return usable output because of a transient platform error, retry at most once. If the second attempt fails, do not keep spawning variants or enter report-polling loops; write `failure-log.md` with the observable error and let the skill report `BLOCKED`. Do not replace the independent Reviewer with Main-Agent self-review.
7
+ The skill owns Reviewer launch parameters, current-checkout preference, forced-worktree changed-content fallback, one bounded infrastructure retry per required Reviewer position, and append-only progress in `review-report.md`.
8
8
 
9
- The skill runs an internal review-fix loop (fresh Reviewer → Main-Agent filter → Main-Agent fix, up to ten completed rounds) and writes `review-report.md` to the artifact directory. The review skill owns strategy selection and forced-worktree content fallback.
9
+ **Gate Check Final Review Result**:
10
10
 
11
- **Gate Check — Review Report**:
12
- After `/prizmkit-code-review` returns, verify the review report:
13
11
  ```bash
14
- {{RUNTIME_HELPER_CMD}} text contains .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md --pattern "## Verdict"
12
+ {{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md
15
13
  ```
16
- If GATE:MISSING:
17
- - Do not re-run `/prizmkit-code-review` in an unbounded report-repair loop.
18
- - Perform one bounded status check; retry at most once: inspect the skill output, `review-report.md` path, and any Reviewer agent spawn messages.
19
- - If the missing report is caused by team/config/lock errors from the Reviewer agent spawn or review skill error, retry `/prizmkit-code-review` at most once only if it appears transient.
20
- - If the report is still missing after that single check/retry, write `failure-log.md` with the spawn/skill error and last observable state, then stop with a clear recovery failure. Do not synthesize a passing report through Main-Agent self-review.
21
14
 
22
- Read `review-report.md` and check the Verdict:
23
- - `PASS` → proceed to next phase
24
- - `NEEDS_FIXES` → log the unresolved accepted findings and proceed according to the task failure policy (do not retry externally)
25
- - `BLOCKED` → log the infrastructure reason and stop so the review can be retried
15
+ Handle the marker:
26
16
 
27
- **CP-3**: Review complete, report written.
17
+ - `REVIEW_PASS` proceed to next phase.
18
+ - `REVIEW_NEEDS_FIXES` → log unresolved findings and follow the task failure policy; do not externally restart the review loop.
19
+ - `REVIEW_BLOCKED` → log infrastructure evidence and stop so recovery can retry.
20
+ - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` → inspect the append-only progress once, write `failure-log.md`, and stop. Do not treat an old or incidental verdict as completion and do not synthesize a passing result through Main-Agent self-review.
28
21
 
22
+ **CP-3**: Review has a valid Final Result and `review-report.md` preserves this execution's progress.
29
23
 
@@ -1,20 +1,20 @@
1
1
  ### Phase 3: Review — Code Review & Behavior Verification
2
2
 
3
- Run `/prizmkit-code-review` directly with `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/`. The skill loads its skill-owned `references/reviewer-agent-prompt.md`, chooses a platform-supported fresh read-only Reviewer, prefers direct access to the active checkout, and supplies changed content that a platform-forced worktree cannot see.
3
+ Run `/prizmkit-code-review` directly with `artifact_dir=.prizmkit/refactor/{{REFACTOR_ID}}/`. The skill first runs a Main-Agent self-review loop for up to ten completed rounds, then semantic risk permits independent Reviewers at limits `low=0`, `medium=1`, `high=2`. High risk starts Reviewer 2 only after Reviewer 1 produced an accepted high finding that was repaired, tested, and reconverged. Reviewer 3 is forbidden.
4
4
 
5
- Do not spawn a top-level Reviewer subagent. The code-review skill owns its internal fresh Reviewer Main-Agent filter Main-Agent fix loop for up to ten completed rounds. Do not prescribe Agent launch parameters from this pipeline template.
5
+ Do not spawn a top-level Reviewer subagent. The code-review skill owns its unchanged independent Reviewer prompt, current-checkout preference, forced-worktree changed-content fallback, one bounded infrastructure retry per required Reviewer position, and append-only report progress.
6
6
 
7
- **Gate Check — Review Report**:
7
+ **Gate Check — Final Review Result**:
8
8
 
9
9
  ```bash
10
- {{RUNTIME_HELPER_CMD}} text contains .prizmkit/refactor/{{REFACTOR_ID}}/review-report.md --pattern "## Verdict"
10
+ {{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/refactor/{{REFACTOR_ID}}/review-report.md
11
11
  ```
12
12
 
13
- - `PASS` → proceed.
14
- - `NEEDS_FIXES` → log unresolved accepted findings and proceed only when they do not block behavior preservation.
15
- - `BLOCKED` → log the infrastructure reason and stop so the review can be retried.
16
- - Missing reportone bounded retry, then write `failure-log.md` and stop if independent review remains blocked; do not replace it with Main-Agent self-review.
13
+ - `REVIEW_PASS` → proceed.
14
+ - `REVIEW_NEEDS_FIXES` → log unresolved findings and proceed only when they do not block behavior preservation under explicit task policy.
15
+ - `REVIEW_BLOCKED` → log infrastructure evidence and stop so recovery can retry.
16
+ - `REVIEW_INCOMPLETE` or `REVIEW_INVALID` inspect progress once, write `failure-log.md`, and stop. Do not accept stale verdict text or replace required independent review with Main-Agent self-review.
17
17
 
18
18
  Verify the review explicitly checks behavior preservation, public API compatibility, imports, tests, and rollback assumptions.
19
19
 
20
- **Checkpoint update**: Set step `prizmkit-code-review` to `completed` only after `PASS`, or after an explicitly accepted non-blocking `NEEDS_FIXES` result. Do not complete the checkpoint for `BLOCKED`.
20
+ **Checkpoint update**: Set step `prizmkit-code-review` to `completed` only for an accepted valid Final Result. Do not complete the checkpoint for blocked, incomplete, or invalid reports.
@@ -882,8 +882,9 @@ class TestDirectOrchestratorImplementationPrompts:
882
882
  assert FORBIDDEN_TEST_CMD_PLACEHOLDER not in rendered
883
883
  assert FORBIDDEN_THREE_STRIKE not in rendered
884
884
  assert FORBIDDEN_TWENTY_STEP not in rendered
885
- assert "fresh Reviewer → Main-Agent filter → Main-Agent fix" in rendered
886
- assert "up to ten completed rounds" in rendered
885
+ assert "Main-Agent self-review loop" in rendered
886
+ assert "low=0" in rendered and "medium=1" in rendered and "high=2" in rendered
887
+ assert "Reviewer 3 is forbidden" in rendered
887
888
 
888
889
  def test_retained_tier3_fallback_template_uses_direct_orchestrator_implementation(self):
889
890
  rendered = Path("dev-pipeline/templates/bootstrap-tier3.md").read_text(encoding="utf-8")
@@ -1442,7 +1443,8 @@ class TestHeadlessPromptCleanupF033:
1442
1443
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-mixed", platform="claude")
1443
1444
 
1444
1445
  assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
1445
- assert "reviewer-agent-prompt.md" in refactor_prompt
1446
+ assert "Main-Agent self-review" in refactor_prompt
1447
+ assert "Reviewer 3 is forbidden" in refactor_prompt
1446
1448
 
1447
1449
  def test_bugfix_refactor_explicit_codex_override(self, tmp_path, monkeypatch):
1448
1450
  from generate_bugfix_prompt import build_replacements as bugfix_build_replacements
@@ -1463,7 +1465,8 @@ class TestHeadlessPromptCleanupF033:
1463
1465
  refactor_prompt = _render_refactor_prompt(tmp_path / "refactor-codex", platform="claude", explicit_platform="codex")
1464
1466
 
1465
1467
  assert "{{" + "REVIEWER_" + "SUBAGENT_PATH" + "}}" not in replacements
1466
- assert "reviewer-agent-prompt.md" in refactor_prompt
1468
+ assert "Main-Agent self-review" in refactor_prompt
1469
+ assert "Reviewer 3 is forbidden" in refactor_prompt
1467
1470
 
1468
1471
 
1469
1472
  class TestFeatureBootstrapShellExtraction:
@@ -1503,7 +1506,7 @@ class TestFeatureBootstrapShellExtraction:
1503
1506
  assert "python" in prompt.lower()
1504
1507
  assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
1505
1508
  assert "artifact touch .prizmkit/specs/123-prompt-cleanup/.prizmkit-test-started" in prompt
1506
- assert "text contains .prizmkit/specs/123-prompt-cleanup/review-report.md --pattern \"## Verdict\"" in prompt
1509
+ assert "text final-verdict .prizmkit/specs/123-prompt-cleanup/review-report.md" in prompt
1507
1510
 
1508
1511
  def test_rendered_section_feature_prompts_use_runtime_helper_not_extracted_bash(self, tmp_path):
1509
1512
  for mode in ("lite", "standard", "full"):
@@ -436,7 +436,7 @@ class TestBugfixBootstrapShellExtraction:
436
436
  assert "python" in prompt.lower()
437
437
  assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
438
438
  assert "artifact ensure-dir .prizmkit/bugfix/B-001" in prompt
439
- assert "text contains .prizmkit/bugfix/B-001/review-report.md --pattern \"## Verdict\"" in prompt
439
+ assert "text final-verdict .prizmkit/bugfix/B-001/review-report.md" in prompt
440
440
 
441
441
  def test_bugfix_template_source_is_clean_of_helper_covered_snippets(self):
442
442
  content = Path("dev-pipeline/templates/bugfix-bootstrap-prompt.md").read_text(encoding="utf-8")
@@ -296,7 +296,7 @@ class TestRefactorBootstrapShellExtraction:
296
296
  assert "python" in prompt.lower()
297
297
  assert ".prizmkit/dev-pipeline/scripts/prizmkit-runtime-helper.py" in prompt
298
298
  assert "artifact ensure-dir .prizmkit/refactor/R-001" in prompt
299
- assert "text contains .prizmkit/refactor/R-001/review-report.md --pattern \"## Verdict\"" in prompt
299
+ assert "text final-verdict .prizmkit/refactor/R-001/review-report.md" in prompt
300
300
 
301
301
  def test_refactor_template_source_is_clean_of_helper_covered_snippets(self):
302
302
  content = Path("dev-pipeline/templates/refactor-bootstrap-prompt.md").read_text(encoding="utf-8")
@@ -1173,6 +1173,47 @@ def test_feature_and_bugfix_explicit_worktree_launches_from_worktree_and_renders
1173
1173
  assert observed.prompt_calls[0]["execution_root"] == expected_worktree.resolve()
1174
1174
 
1175
1175
 
1176
+ def test_worktree_checkout_mode_and_path_survive_environment_changes(monkeypatch, tmp_path):
1177
+ from prizmkit_runtime import runners
1178
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
1179
+ from prizmkit_runtime.task_checkout import CHECKOUT_MODE_WORKTREE, TaskCheckout, persist_task_checkout
1180
+
1181
+ paths = _make_paths(tmp_path)
1182
+ family = family_for("feature", paths)
1183
+ invocation = parse_invocation(family, "run", ())
1184
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
1185
+ branch = "dev/F-001-worktree-resume"
1186
+ worktree_path = (paths.state_dir / "worktrees" / "dev-F-001-worktree-resume").resolve()
1187
+ persist_task_checkout(
1188
+ family.state_dir,
1189
+ TaskCheckout.active(
1190
+ "feature",
1191
+ "F-001",
1192
+ branch,
1193
+ "main",
1194
+ checkout_mode=CHECKOUT_MODE_WORKTREE,
1195
+ worktree_path=str(worktree_path),
1196
+ ),
1197
+ )
1198
+ observed = _install_runner_session_fakes(monkeypatch, runners)
1199
+ setup_calls = []
1200
+
1201
+ def fake_setup(runtime):
1202
+ setup_calls.append(runtime)
1203
+ runtime.worktree_path.mkdir(parents=True, exist_ok=True)
1204
+ return SimpleNamespace(ok=True, runtime=runtime, reason="")
1205
+
1206
+ monkeypatch.delenv("USE_WORKTREE", raising=False)
1207
+ monkeypatch.delenv("DEV_BRANCH", raising=False)
1208
+ monkeypatch.setattr(runners, "ensure_linked_worktree", fake_setup)
1209
+
1210
+ status = runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
1211
+
1212
+ assert status == "completed"
1213
+ assert setup_calls[0].worktree_path == worktree_path
1214
+ assert observed.launch_cwds == [worktree_path]
1215
+
1216
+
1176
1217
  def test_successful_worktree_session_merges_cleans_branch_and_honors_auto_push(monkeypatch, tmp_path):
1177
1218
  from prizmkit_runtime import runners
1178
1219
  from prizmkit_runtime.runner_models import family_for, parse_invocation
@@ -1192,6 +1233,9 @@ def test_successful_worktree_session_merges_cleans_branch_and_honors_auto_push(m
1192
1233
  assert status == "completed"
1193
1234
  assert observed.merge_calls and observed.merge_calls[0][1] is True
1194
1235
  assert observed.cleanup_calls and observed.cleanup_calls[0][1] is True
1236
+ checkout = json.loads((paths.feature_state_dir / "F-001" / "checkout.json").read_text(encoding="utf-8"))
1237
+ assert checkout["state"] == "completed"
1238
+ assert checkout["active_dev_branch"] == "dev/F-001-test"
1195
1239
 
1196
1240
 
1197
1241
  def test_worktree_merge_conflict_writes_session_status_and_updates_merge_conflict(monkeypatch, tmp_path):
@@ -1212,7 +1256,10 @@ def test_worktree_merge_conflict_writes_session_status_and_updates_merge_conflic
1212
1256
 
1213
1257
  assert status == "completed"
1214
1258
  assert observed.updates[-1][0] == "merge_conflict"
1215
- assert observed.cleanup_calls[-1][1] is False
1259
+ assert observed.cleanup_calls == []
1260
+ checkout = json.loads((paths.feature_state_dir / "F-001" / "checkout.json").read_text(encoding="utf-8"))
1261
+ assert checkout["state"] == "active"
1262
+ assert checkout["checkout_mode"] == "worktree"
1216
1263
  session_status = json.loads((paths.feature_state_dir / "F-001" / "sessions" / "F-001-session-1" / "session-status.json").read_text(encoding="utf-8"))
1217
1264
  assert session_status["status"] == "merge_conflict"
1218
1265
  assert session_status["task_id"] == "F-001"
@@ -1222,7 +1269,7 @@ def test_worktree_merge_conflict_writes_session_status_and_updates_merge_conflic
1222
1269
  assert session_status["worktree_path"].endswith("dev-F-001-test")
1223
1270
 
1224
1271
 
1225
- def test_terminal_worktree_failure_saves_wip_and_removes_worktree_without_deleting_branch(monkeypatch, tmp_path):
1272
+ def test_terminal_worktree_failure_saves_wip_and_preserves_checkout(monkeypatch, tmp_path):
1226
1273
  from prizmkit_runtime import runners
1227
1274
  from prizmkit_runtime.runner_models import family_for, parse_invocation
1228
1275
 
@@ -1239,7 +1286,10 @@ def test_terminal_worktree_failure_saves_wip_and_removes_worktree_without_deleti
1239
1286
 
1240
1287
  assert status == "failed"
1241
1288
  assert observed.wip_calls
1242
- assert observed.cleanup_calls[-1][1] is False
1289
+ assert observed.cleanup_calls == []
1290
+ checkout = json.loads((paths.feature_state_dir / "F-001" / "checkout.json").read_text(encoding="utf-8"))
1291
+ assert checkout["state"] == "active"
1292
+ assert checkout["checkout_mode"] == "worktree"
1243
1293
  assert observed.updates[-1][0] == "crashed"
1244
1294
 
1245
1295
 
@@ -1708,9 +1758,54 @@ def test_foreground_runner_emits_structured_feature_progress(monkeypatch, tmp_pa
1708
1758
  assert "Session result: success" in captured.err
1709
1759
 
1710
1760
 
1761
+ def test_durable_checkout_reservation_is_first_writer_wins_and_conflicts_fail(tmp_path):
1762
+ from prizmkit_runtime.task_checkout import TaskCheckout, TaskCheckoutError, reserve_task_checkout
1763
+
1764
+ state_dir = tmp_path / "state"
1765
+ first = TaskCheckout.active("feature", "F-001", "dev/F-001-first", "main")
1766
+ conflicting = TaskCheckout.active("feature", "F-001", "dev/F-001-second", "main")
1767
+
1768
+ assert reserve_task_checkout(state_dir, first) == first
1769
+ assert reserve_task_checkout(state_dir, first) == first
1770
+ with pytest.raises(TaskCheckoutError, match="already owns checkout"):
1771
+ reserve_task_checkout(state_dir, conflicting)
1772
+
1773
+
1774
+ def test_task_checkout_guard_rejects_concurrent_start_and_reset(tmp_path):
1775
+ from prizmkit_runtime.task_checkout import TaskCheckoutError, task_checkout_guard
1776
+
1777
+ state_dir = tmp_path / "state"
1778
+ with task_checkout_guard(state_dir, "F-001"):
1779
+ with pytest.raises(TaskCheckoutError, match="already running or being reset"):
1780
+ with task_checkout_guard(state_dir, "F-001"):
1781
+ pass
1782
+
1783
+
1784
+ def test_completed_checkout_blocks_rerun_until_explicit_reset(monkeypatch, tmp_path):
1785
+ from prizmkit_runtime import runners
1786
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
1787
+ from prizmkit_runtime.task_checkout import TaskCheckout, persist_task_checkout
1788
+
1789
+ paths = _make_paths(tmp_path)
1790
+ family = family_for("feature", paths)
1791
+ invocation = parse_invocation(family, "run", ())
1792
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": paths.feature_plan})
1793
+ completed = TaskCheckout.active("feature", "F-001", "dev/F-001-completed", "main").completed()
1794
+ persist_task_checkout(family.state_dir, completed)
1795
+ monkeypatch.setattr(
1796
+ runners,
1797
+ "load_runtime_config",
1798
+ lambda paths: SimpleNamespace(ai_client=SimpleNamespace(command="fake-ai", platform="codebuddy"), model="", effort=None),
1799
+ )
1800
+
1801
+ with pytest.raises(RuntimeError, match="completed; run explicit reset or clean"):
1802
+ runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
1803
+
1804
+
1711
1805
  def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_path):
1712
1806
  from prizmkit_runtime import runners
1713
1807
  from prizmkit_runtime.runner_models import family_for, parse_invocation
1808
+ from prizmkit_runtime.task_checkout import load_task_checkout
1714
1809
 
1715
1810
  monkeypatch.setenv("DEV_BRANCH", "dev/F-001-interrupt")
1716
1811
  paths = _make_paths(tmp_path)
@@ -1734,9 +1829,50 @@ def test_no_worktree_interrupt_saves_wip_and_returns_to_base(monkeypatch, tmp_pa
1734
1829
  with pytest.raises(KeyboardInterrupt):
1735
1830
  runners._process_item(family, invocation, "F-001", paths, initial_metadata={})
1736
1831
 
1832
+ checkout = load_task_checkout(family.state_dir, family.task_type, "F-001")
1833
+ assert checkout is not None
1834
+ assert checkout.is_active
1835
+ assert checkout.active_dev_branch == "dev/F-001-interrupt"
1836
+ assert checkout.base_branch == "main"
1737
1837
  assert ensure_calls == [("main", "dev/F-001-interrupt")]
1738
1838
 
1739
1839
 
1840
+ @pytest.mark.parametrize(
1841
+ ("kind", "item_id", "branch"),
1842
+ [
1843
+ ("feature", "F-001", "dev/F-001-interrupt"),
1844
+ ("bugfix", "B-001", "bugfix/B-001-interrupt"),
1845
+ ("refactor", "R-001", "refactor/R-001-interrupt"),
1846
+ ],
1847
+ )
1848
+ def test_interrupted_task_reuses_durable_checkout_on_next_process(monkeypatch, tmp_path, kind, item_id, branch):
1849
+ from prizmkit_runtime import runners
1850
+ from prizmkit_runtime.runner_models import family_for, parse_invocation
1851
+ from prizmkit_runtime.task_checkout import TaskCheckout, persist_task_checkout
1852
+
1853
+ paths = _make_paths(tmp_path / kind)
1854
+ family = family_for(kind, paths)
1855
+ plan_path = {"feature": paths.feature_plan, "bugfix": paths.bugfix_plan, "refactor": paths.refactor_plan}[kind]
1856
+ invocation = parse_invocation(family, "run", ())
1857
+ invocation = invocation.__class__(**{**invocation.__dict__, "list_path": plan_path})
1858
+ persist_task_checkout(family.state_dir, TaskCheckout.active(kind, item_id, branch, "main"))
1859
+ observed = _install_runner_session_fakes(monkeypatch, runners)
1860
+ branch_contexts = []
1861
+
1862
+ def fake_git_plan(project_root, plan):
1863
+ branch_contexts.append((plan.commands[1].args[-2], plan.commands[1].args[-1]))
1864
+ return SimpleNamespace(ok=True, plan=plan, results=())
1865
+
1866
+ monkeypatch.delenv("DEV_BRANCH", raising=False)
1867
+ monkeypatch.setattr(runners, "run_git_plan", fake_git_plan)
1868
+
1869
+ status = runners._process_item(family, invocation, item_id, paths, initial_metadata={})
1870
+
1871
+ assert status == "completed"
1872
+ assert branch_contexts[0] == (branch, "main")
1873
+ assert observed.launch_cwds == [paths.project_root]
1874
+
1875
+
1740
1876
  def _init_bookkeeping_repo(project_root, *, configure_identity=True):
1741
1877
  subprocess.run(["git", "init", "-b", "main"], cwd=project_root, check=True, stdout=subprocess.DEVNULL)
1742
1878
  if configure_identity:
@@ -2641,6 +2777,8 @@ def _write_family_case(paths, kind, item_id=""):
2641
2777
 
2642
2778
  def test_python_reset_warns_before_mutation_and_matches_snapshot_cleanup(tmp_path):
2643
2779
  from prizmkit_runtime.reset import run_reset_command
2780
+ from prizmkit_runtime.runner_models import family_for
2781
+ from prizmkit_runtime.task_checkout import load_task_checkout
2644
2782
 
2645
2783
  snapshot = json.loads((REPO_ROOT / "tests" / "fixtures" / "dev-pipeline-behavior-snapshots" / "unix-runtime.json").read_text(encoding="utf-8"))
2646
2784
  for kind, item_id in [("feature", "F-001"), ("bugfix", "B-001"), ("refactor", "R-001")]:
@@ -2659,6 +2797,71 @@ def test_python_reset_warns_before_mutation_and_matches_snapshot_cleanup(tmp_pat
2659
2797
  for key, value in expected_runtime.items():
2660
2798
  assert runtime.get(key) == value
2661
2799
  assert "continuation_pending" not in runtime
2800
+ family = family_for(kind, paths)
2801
+ checkout = load_task_checkout(family.state_dir, family.task_type, item_id)
2802
+ assert checkout is not None
2803
+ assert checkout.is_reset
2804
+ assert checkout.active_dev_branch == ""
2805
+ assert checkout.base_branch == ""
2806
+
2807
+
2808
+ def test_python_reset_cleans_authoritative_custom_branch_before_reset_marker(monkeypatch, tmp_path):
2809
+ from prizmkit_runtime import reset as reset_runtime
2810
+ from prizmkit_runtime.reset import run_reset_command
2811
+ from prizmkit_runtime.runner_models import family_for
2812
+ from prizmkit_runtime.task_checkout import TaskCheckout, load_task_checkout, persist_task_checkout
2813
+
2814
+ paths = _make_paths(tmp_path)
2815
+ _write_family_case(paths, "feature", "F-001")
2816
+ family = family_for("feature", paths)
2817
+ custom_branch = "custom/feature-checkout"
2818
+ persist_task_checkout(family.state_dir, TaskCheckout.active("feature", "F-001", custom_branch, "main"))
2819
+ cleanup_calls = []
2820
+
2821
+ monkeypatch.setattr(reset_runtime, "list_reset_branches", lambda *args: ())
2822
+ monkeypatch.setattr(
2823
+ reset_runtime,
2824
+ "reset_cleanup_branch",
2825
+ lambda project_root, branch, *, return_branch, delete: cleanup_calls.append((branch, delete))
2826
+ or SimpleNamespace(branch=branch, cleaned=True, deleted=False, returned_to=return_branch, warnings=()),
2827
+ )
2828
+
2829
+ result = run_reset_command("feature", ("F-001",), paths)
2830
+
2831
+ assert result.exit_code == 0
2832
+ assert cleanup_calls == [(custom_branch, False)]
2833
+ checkout = load_task_checkout(family.state_dir, family.task_type, "F-001")
2834
+ assert checkout is not None and checkout.is_reset
2835
+
2836
+
2837
+ def test_python_reset_failure_keeps_authoritative_checkout_active(monkeypatch, tmp_path):
2838
+ from prizmkit_runtime import reset as reset_runtime
2839
+ from prizmkit_runtime.reset import run_reset_command
2840
+ from prizmkit_runtime.runner_models import family_for
2841
+ from prizmkit_runtime.task_checkout import TaskCheckout, load_task_checkout, persist_task_checkout
2842
+
2843
+ paths = _make_paths(tmp_path)
2844
+ _write_family_case(paths, "feature", "F-001")
2845
+ family = family_for("feature", paths)
2846
+ checkout = TaskCheckout.active("feature", "F-001", "custom/feature-checkout", "main")
2847
+ persist_task_checkout(family.state_dir, checkout)
2848
+ monkeypatch.setattr(reset_runtime, "list_reset_branches", lambda *args: ())
2849
+ monkeypatch.setattr(
2850
+ reset_runtime,
2851
+ "reset_cleanup_branch",
2852
+ lambda *args, **kwargs: SimpleNamespace(
2853
+ branch="custom/feature-checkout",
2854
+ cleaned=False,
2855
+ deleted=False,
2856
+ returned_to="main",
2857
+ warnings=("could not checkout custom/feature-checkout",),
2858
+ ),
2859
+ )
2860
+
2861
+ result = run_reset_command("feature", ("F-001",), paths)
2862
+
2863
+ assert result.exit_code == 1
2864
+ assert load_task_checkout(family.state_dir, family.task_type, "F-001") == checkout
2662
2865
 
2663
2866
 
2664
2867
  def test_python_clean_reset_deletes_continuation_artifacts_for_all_families(tmp_path):
@@ -75,6 +75,42 @@ class TestArtifactAndTextHelpers:
75
75
  missing = run_helper("text", "contains", str(target), "--pattern", "gamma")
76
76
  assert first_line(missing) == "GATE:MISSING"
77
77
 
78
+ def test_final_verdict_requires_last_final_result_section(self, tmp_path):
79
+ report = tmp_path / "review-report.md"
80
+ report.write_text(
81
+ "# Review Report\n\n## Status: IN_PROGRESS\n\n"
82
+ "## Main Review Round 1\n\n- Note: incidental PASS text\n",
83
+ encoding="utf-8",
84
+ )
85
+ incomplete = run_helper("text", "final-verdict", str(report))
86
+ assert incomplete.returncode != 0
87
+ assert first_line(incomplete) == "REVIEW_INCOMPLETE"
88
+
89
+ report.write_text(
90
+ report.read_text(encoding="utf-8")
91
+ + "\n## Final Result\n\n- Verdict: PASS\n- Summary: done\n",
92
+ encoding="utf-8",
93
+ )
94
+ complete = run_helper("text", "final-verdict", str(report))
95
+ assert complete.returncode == 0
96
+ assert first_line(complete) == "REVIEW_PASS"
97
+ assert "verdict: PASS" in complete.stdout
98
+
99
+ def test_final_verdict_uses_last_final_result_and_rejects_unknown_value(self, tmp_path):
100
+ report = tmp_path / "review-report.md"
101
+ report.write_text(
102
+ "## Final Result\n- Verdict: PASS\n\n"
103
+ "## Final Result\n- Verdict: BLOCKED\n",
104
+ encoding="utf-8",
105
+ )
106
+ blocked = run_helper("text", "final-verdict", str(report))
107
+ assert first_line(blocked) == "REVIEW_BLOCKED"
108
+
109
+ report.write_text("## Final Result\n- Verdict: UNKNOWN\n", encoding="utf-8")
110
+ invalid = run_helper("text", "final-verdict", str(report))
111
+ assert invalid.returncode != 0
112
+ assert first_line(invalid) == "REVIEW_INVALID"
113
+
78
114
 
79
115
  class TestPlatformExecutableAndPortHelpers:
80
116
  def test_platform_detection_and_skill_lookup_follow_config(self, tmp_path, monkeypatch):