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.
- package/bundled/VERSION.json +3 -3
- package/bundled/dev-pipeline/prizmkit_runtime/reset.py +49 -3
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +75 -14
- package/bundled/dev-pipeline/prizmkit_runtime/runtime_helper.py +37 -0
- package/bundled/dev-pipeline/prizmkit_runtime/task_checkout.py +267 -0
- package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +3 -2
- package/bundled/dev-pipeline/templates/bootstrap-prompt.md +1 -1
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +6 -18
- package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +6 -15
- package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +8 -9
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +9 -9
- package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +15 -18
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +11 -17
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +9 -9
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +8 -5
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +1 -1
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +1 -1
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +206 -3
- package/bundled/dev-pipeline/tests/test_runtime_helper.py +36 -0
- package/bundled/dev-pipeline/tests/test_unified_cli.py +10 -7
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/prizmkit-code-review/SKILL.md +128 -95
- package/bundled/skills/prizmkit-code-review/references/review-report-template.md +89 -25
- package/bundled/skills/prizmkit-code-review/scripts/render_review_report.py +246 -114
- 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
|
|
184
|
-
-
|
|
185
|
-
- `review-report.md`
|
|
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
|
|
188
|
-
After `/prizmkit-code-review` returns, verify the review report:
|
|
187
|
+
**Gate Check — Final Review Result**:
|
|
189
188
|
```bash
|
|
190
|
-
{{RUNTIME_HELPER_CMD}} text
|
|
189
|
+
{{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/bugfix/{{BUG_ID}}/review-report.md
|
|
191
190
|
```
|
|
192
|
-
|
|
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
|
|
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
|
-
-
|
|
148
|
+
- preserve append-only progress in `.prizmkit/refactor/{{REFACTOR_ID}}/review-report.md`.
|
|
149
149
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
- `
|
|
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
|
|
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
|
|
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
|
|
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
|
|
19
|
+
**Gate Check — Final Review Result**:
|
|
20
20
|
|
|
21
21
|
```bash
|
|
22
|
-
{{RUNTIME_HELPER_CMD}} text
|
|
22
|
+
{{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/bugfix/{{BUG_ID}}/review-report.md
|
|
23
23
|
```
|
|
24
24
|
|
|
25
|
-
- `
|
|
26
|
-
- `
|
|
27
|
-
- `
|
|
28
|
-
-
|
|
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`
|
|
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
|
|
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
|
|
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
|
|
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
|
|
12
|
+
{{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/specs/{{FEATURE_SLUG}}/review-report.md
|
|
13
13
|
```
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
-
|
|
18
|
-
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
|
7
|
+
**Gate Check — Final Review Result**:
|
|
8
8
|
|
|
9
9
|
```bash
|
|
10
|
-
{{RUNTIME_HELPER_CMD}} text
|
|
10
|
+
{{RUNTIME_HELPER_CMD}} text final-verdict .prizmkit/refactor/{{REFACTOR_ID}}/review-report.md
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
-
- `
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
-
|
|
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
|
|
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 "
|
|
886
|
-
assert "
|
|
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 "
|
|
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 "
|
|
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
|
|
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
|
|
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
|
|
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[
|
|
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
|
|
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[
|
|
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):
|