prizmkit 1.1.141 → 1.1.143
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/README.md +1 -1
- package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +124 -4
- package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +13 -1
- package/bundled/dev-pipeline/prizmkit_runtime/runners.py +4 -1
- package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +20 -10
- package/bundled/dev-pipeline/scripts/check-session-status.py +11 -1
- package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +2 -2
- package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +2 -2
- package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +2 -2
- package/bundled/dev-pipeline/scripts/prompt_framework.py +12 -1
- package/bundled/dev-pipeline/scripts/update-bug-status.py +20 -0
- package/bundled/dev-pipeline/scripts/update-checkpoint.py +38 -9
- package/bundled/dev-pipeline/scripts/update-feature-status.py +20 -0
- package/bundled/dev-pipeline/scripts/update-refactor-status.py +20 -0
- package/bundled/dev-pipeline/templates/bootstrap-tier3.md +1 -1
- package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +1 -1
- package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +5 -1
- package/bundled/dev-pipeline/templates/sections/phase-review-full.md +2 -2
- package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
- package/bundled/dev-pipeline/templates/session-status-schema.json +1 -1
- package/bundled/dev-pipeline/tests/test_checkpoint_state.py +231 -0
- package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +26 -1
- package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +4 -2
- package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +5 -2
- package/bundled/dev-pipeline/tests/test_python_runner_parity.py +147 -4
- package/bundled/skills/_metadata.json +1 -1
- package/bundled/skills/prizmkit/SKILL.md +6 -6
- package/bundled/skills/prizmkit/references/workflow-state-protocol.md +23 -11
- package/bundled/skills/prizmkit-code-review/SKILL.md +4 -4
- package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +23 -11
- package/bundled/skills/prizmkit-committer/SKILL.md +5 -4
- package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +23 -11
- package/bundled/skills/prizmkit-implement/SKILL.md +3 -2
- package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +23 -11
- package/bundled/skills/prizmkit-plan/SKILL.md +2 -1
- package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +23 -11
- package/bundled/skills/prizmkit-retrospective/SKILL.md +6 -4
- package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +23 -11
- package/bundled/skills/prizmkit-test/SKILL.md +1 -1
- package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
- package/bundled/skills/prizmkit-workflow/SKILL.md +13 -13
- package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +23 -11
- package/bundled/templates/hooks/commit-intent-status.py +50 -0
- package/bundled/templates/hooks/commit-intent.json +2 -2
- package/bundled/templates/hooks/post-command-prizm-drift.py +44 -0
- package/package.json +1 -1
- package/src/scaffold.js +2 -0
package/bundled/VERSION.json
CHANGED
|
@@ -359,7 +359,7 @@ python3 scripts/update-feature-status.py \
|
|
|
359
359
|
| `complete` | `--feature-id` | Shortcut for manually marking completed |
|
|
360
360
|
|
|
361
361
|
**Session status values (for `--session-status`):**
|
|
362
|
-
`success`, `partial_resumable`, `partial_not_resumable`, `failed`, `crashed`, `commit_missing`, `docs_missing`, `merge_conflict`
|
|
362
|
+
`success`, `partial_resumable`, `partial_not_resumable`, `failed`, `crashed`, `timed_out`, `infra_error`, `context_overflow`, `stalled_context_continuation`, `workflow_blocked`, `workflow_skipped`, `commit_missing`, `docs_missing`, `merge_conflict`
|
|
363
363
|
|
|
364
364
|
**Output (JSON to stdout):**
|
|
365
365
|
|
|
@@ -17,7 +17,37 @@ MAX_OUTER_REPAIR_ROUNDS = 3
|
|
|
17
17
|
|
|
18
18
|
REVIEW_RESULTS = {"REVIEW_PASS", "REVIEW_NEEDS_FIXES"}
|
|
19
19
|
TEST_RESULTS = {"TEST_PASS", "TEST_NEEDS_FIXES", "TEST_BLOCKED"}
|
|
20
|
-
|
|
20
|
+
PLAN_RESULTS = {"PLAN_READY", "PLAN_BLOCKED"}
|
|
21
|
+
IMPLEMENT_RESULTS = {"IMPLEMENTED", "IMPLEMENT_REPAIR", "IMPLEMENT_BLOCKED"}
|
|
22
|
+
RETROSPECTIVE_RESULTS = {"RETRO_COMPLETE", "RETRO_BLOCKED"}
|
|
23
|
+
COMMIT_RESULTS = {"COMMIT_PENDING", "COMMITTED", "COMMIT_BLOCKED"}
|
|
24
|
+
VALID_STAGE_RESULTS = (
|
|
25
|
+
REVIEW_RESULTS
|
|
26
|
+
| TEST_RESULTS
|
|
27
|
+
| PLAN_RESULTS
|
|
28
|
+
| IMPLEMENT_RESULTS
|
|
29
|
+
| RETROSPECTIVE_RESULTS
|
|
30
|
+
| COMMIT_RESULTS
|
|
31
|
+
)
|
|
32
|
+
STAGE_RESULT_OWNERS = {
|
|
33
|
+
"prizmkit-plan": PLAN_RESULTS,
|
|
34
|
+
"prizmkit-implement": IMPLEMENT_RESULTS,
|
|
35
|
+
"prizmkit-code-review": REVIEW_RESULTS,
|
|
36
|
+
"prizmkit-test": TEST_RESULTS,
|
|
37
|
+
"prizmkit-retrospective": RETROSPECTIVE_RESULTS,
|
|
38
|
+
"prizmkit-committer": COMMIT_RESULTS,
|
|
39
|
+
}
|
|
40
|
+
WORKFLOW_STAGE_SKILLS = {
|
|
41
|
+
"plan": "prizmkit-plan",
|
|
42
|
+
"implement": "prizmkit-implement",
|
|
43
|
+
"code-review": "prizmkit-code-review",
|
|
44
|
+
"test": "prizmkit-test",
|
|
45
|
+
"retrospective": "prizmkit-retrospective",
|
|
46
|
+
"committer": "prizmkit-committer",
|
|
47
|
+
}
|
|
48
|
+
SKILL_WORKFLOW_STAGES = {
|
|
49
|
+
skill: stage for stage, skill in WORKFLOW_STAGE_SKILLS.items()
|
|
50
|
+
}
|
|
21
51
|
VALID_TERMINAL_STATUSES = {"WORKFLOW_COMPLETED", "WORKFLOW_BLOCKED"}
|
|
22
52
|
VALID_REPAIR_SCOPES = {
|
|
23
53
|
"production",
|
|
@@ -270,6 +300,23 @@ def validate_checkpoint_data(
|
|
|
270
300
|
stage_result = raw_step.get("stage_result")
|
|
271
301
|
if stage_result is not None and stage_result not in VALID_STAGE_RESULTS:
|
|
272
302
|
return _invalid(checkpoint_path, exists, "invalid_stage_result", f"Step {step_id} has invalid stage_result", data)
|
|
303
|
+
if stage_result is not None and not stage_result_allowed_for_skill(skill, stage_result):
|
|
304
|
+
return _invalid(
|
|
305
|
+
checkpoint_path,
|
|
306
|
+
exists,
|
|
307
|
+
"stage_result_owner_mismatch",
|
|
308
|
+
f"Step {step_id} cannot record {stage_result} for {skill}",
|
|
309
|
+
data,
|
|
310
|
+
)
|
|
311
|
+
expected_status = _expected_status_for_result(stage_result)
|
|
312
|
+
if expected_status is not None and status != expected_status:
|
|
313
|
+
return _invalid(
|
|
314
|
+
checkpoint_path,
|
|
315
|
+
exists,
|
|
316
|
+
"stage_result_status_mismatch",
|
|
317
|
+
f"Step {step_id} requires status={expected_status} for {stage_result}",
|
|
318
|
+
data,
|
|
319
|
+
)
|
|
273
320
|
|
|
274
321
|
step = CheckpointStep(
|
|
275
322
|
id=step_id,
|
|
@@ -401,6 +448,47 @@ def semantic_state_key(workflow_type: str) -> str:
|
|
|
401
448
|
return SEMANTIC_STATE_KEYS.get(workflow_type, "l4_state")
|
|
402
449
|
|
|
403
450
|
|
|
451
|
+
def expected_status_for_stage_result(stage_result: str | None) -> str | None:
|
|
452
|
+
"""Return the lifecycle status required by one domain stage result."""
|
|
453
|
+
return _expected_status_for_result(stage_result)
|
|
454
|
+
|
|
455
|
+
|
|
456
|
+
def stage_result_allowed_for_skill(skill: str, stage_result: str | None) -> bool:
|
|
457
|
+
"""Return whether one stage owns the supplied domain result."""
|
|
458
|
+
if stage_result is None:
|
|
459
|
+
return True
|
|
460
|
+
allowed_results = STAGE_RESULT_OWNERS.get(skill)
|
|
461
|
+
return allowed_results is not None and stage_result in allowed_results
|
|
462
|
+
|
|
463
|
+
|
|
464
|
+
def _expected_status_for_result(stage_result: str | None) -> str | None:
|
|
465
|
+
if stage_result is None:
|
|
466
|
+
return None
|
|
467
|
+
if stage_result == "COMMIT_PENDING":
|
|
468
|
+
return "in_progress"
|
|
469
|
+
if stage_result in {
|
|
470
|
+
"REVIEW_PASS",
|
|
471
|
+
"TEST_PASS",
|
|
472
|
+
"PLAN_READY",
|
|
473
|
+
"IMPLEMENTED",
|
|
474
|
+
"RETRO_COMPLETE",
|
|
475
|
+
"COMMITTED",
|
|
476
|
+
}:
|
|
477
|
+
return "completed"
|
|
478
|
+
if stage_result in {
|
|
479
|
+
"REVIEW_NEEDS_FIXES",
|
|
480
|
+
"TEST_NEEDS_FIXES",
|
|
481
|
+
"TEST_BLOCKED",
|
|
482
|
+
"PLAN_BLOCKED",
|
|
483
|
+
"IMPLEMENT_REPAIR",
|
|
484
|
+
"IMPLEMENT_BLOCKED",
|
|
485
|
+
"RETRO_BLOCKED",
|
|
486
|
+
"COMMIT_BLOCKED",
|
|
487
|
+
}:
|
|
488
|
+
return "failed"
|
|
489
|
+
return None
|
|
490
|
+
|
|
491
|
+
|
|
404
492
|
def _progress_error(steps: list[CheckpointStep]) -> str:
|
|
405
493
|
active_seen = False
|
|
406
494
|
for step in steps:
|
|
@@ -435,6 +523,32 @@ def _derive_semantic_state(
|
|
|
435
523
|
repair_scope = raw_state.get("repair_scope")
|
|
436
524
|
if repair_scope is not None and repair_scope not in VALID_REPAIR_SCOPES:
|
|
437
525
|
return _semantic_error(workflow_type, current_step, "invalid_repair_scope", "repair_scope is not a safe shared scope")
|
|
526
|
+
semantic_status = raw_state.get("status")
|
|
527
|
+
if semantic_status is not None and semantic_status not in VALID_CHECKPOINT_STATUSES:
|
|
528
|
+
return _semantic_error(workflow_type, current_step, "invalid_semantic_status", "semantic status is not a lifecycle status")
|
|
529
|
+
semantic_stage_result = raw_state.get("stage_result")
|
|
530
|
+
if semantic_stage_result is not None and semantic_stage_result not in VALID_STAGE_RESULTS:
|
|
531
|
+
return _semantic_error(workflow_type, current_step, "invalid_semantic_stage_result", "semantic stage_result is not recognized")
|
|
532
|
+
expected_semantic_status = _expected_status_for_result(semantic_stage_result)
|
|
533
|
+
if semantic_status is not None and expected_semantic_status is not None and semantic_status != expected_semantic_status:
|
|
534
|
+
return _semantic_error(
|
|
535
|
+
workflow_type,
|
|
536
|
+
current_step,
|
|
537
|
+
"semantic_stage_result_status_mismatch",
|
|
538
|
+
f"Semantic {semantic_stage_result} requires status={expected_semantic_status}",
|
|
539
|
+
)
|
|
540
|
+
semantic_stage = raw_state.get("stage")
|
|
541
|
+
if semantic_stage is not None and (not isinstance(semantic_stage, str) or not semantic_stage.strip()):
|
|
542
|
+
return _semantic_error(workflow_type, current_step, "invalid_semantic_stage", "semantic stage must be a non-empty string")
|
|
543
|
+
if semantic_stage_result is not None:
|
|
544
|
+
semantic_skill = WORKFLOW_STAGE_SKILLS.get(semantic_stage, semantic_stage)
|
|
545
|
+
if not stage_result_allowed_for_skill(semantic_skill, semantic_stage_result):
|
|
546
|
+
return _semantic_error(
|
|
547
|
+
workflow_type,
|
|
548
|
+
current_step,
|
|
549
|
+
"semantic_stage_result_owner_mismatch",
|
|
550
|
+
f"Semantic stage {semantic_stage} cannot record {semantic_stage_result}",
|
|
551
|
+
)
|
|
438
552
|
terminal_status = raw_state.get("terminal_status")
|
|
439
553
|
if terminal_status is not None and terminal_status not in VALID_TERMINAL_STATUSES:
|
|
440
554
|
return _semantic_error(workflow_type, current_step, "invalid_terminal_status", "terminal_status is not recognized")
|
|
@@ -475,9 +589,15 @@ def _derive_semantic_state(
|
|
|
475
589
|
current_stage = current_step.skill if current_step is not None else None
|
|
476
590
|
next_stage = current_stage
|
|
477
591
|
if current_step is not None and current_step.status == "in_progress":
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
592
|
+
next_stage = current_stage
|
|
593
|
+
if (
|
|
594
|
+
current_step is not None
|
|
595
|
+
and current_step.status == "failed"
|
|
596
|
+
and current_step.stage_result == "REVIEW_NEEDS_FIXES"
|
|
597
|
+
and raw_state.get("stage_result") == "REVIEW_NEEDS_FIXES"
|
|
598
|
+
and raw_state.get("next_stage") == "prizmkit-implement"
|
|
599
|
+
):
|
|
600
|
+
next_stage = "prizmkit-implement"
|
|
481
601
|
if terminal_status is not None:
|
|
482
602
|
current_stage = None
|
|
483
603
|
next_stage = None
|
|
@@ -13,6 +13,7 @@ from .sessions import AISessionResult
|
|
|
13
13
|
|
|
14
14
|
CONTEXT_OVERFLOW = "context_overflow"
|
|
15
15
|
WORKFLOW_BLOCKED = "workflow_blocked"
|
|
16
|
+
WORKFLOW_SKIPPED = "workflow_skipped"
|
|
16
17
|
STALL_REASON = "context_overflow_without_git_checkpoint_or_artifact_progress"
|
|
17
18
|
CONTEXT_MARKERS = (
|
|
18
19
|
"context_overflow",
|
|
@@ -50,8 +51,9 @@ def classify_session(
|
|
|
50
51
|
fingerprint = progress_fingerprint(project_root, prompt)
|
|
51
52
|
checkpoint = _workflow_checkpoint(prompt, project_root)
|
|
52
53
|
if checkpoint is not None and not checkpoint.valid:
|
|
54
|
+
session_status = WORKFLOW_SKIPPED if _is_terminal_success(result) else "crashed"
|
|
53
55
|
return SessionClassification(
|
|
54
|
-
|
|
56
|
+
session_status,
|
|
55
57
|
reason=f"invalid_workflow_checkpoint:{checkpoint.error_code}",
|
|
56
58
|
progress_fingerprint=fingerprint,
|
|
57
59
|
checkpoint_state=checkpoint.semantic_snapshot,
|
|
@@ -79,6 +81,16 @@ def classify_session(
|
|
|
79
81
|
)
|
|
80
82
|
|
|
81
83
|
semantic = checkpoint.semantic_snapshot if checkpoint is not None else {}
|
|
84
|
+
if _is_terminal_success(result):
|
|
85
|
+
reason = "no_semantic_completion"
|
|
86
|
+
if checkpoint is not None and checkpoint.semantic.error_code:
|
|
87
|
+
reason = f"{reason}:{checkpoint.semantic.error_code}"
|
|
88
|
+
return SessionClassification(
|
|
89
|
+
WORKFLOW_SKIPPED,
|
|
90
|
+
reason=reason,
|
|
91
|
+
progress_fingerprint=fingerprint,
|
|
92
|
+
checkpoint_state=semantic,
|
|
93
|
+
)
|
|
82
94
|
if _is_context_overflow(result):
|
|
83
95
|
no_progress_count = _next_no_progress_count(previous_fingerprint, fingerprint, previous_no_progress_count)
|
|
84
96
|
if no_progress_count >= 2:
|
|
@@ -862,7 +862,10 @@ def _emit_interruption_cleanup_failure(result, message: str, recovery: str) -> N
|
|
|
862
862
|
|
|
863
863
|
def _is_retryable_ai_outcome(session_status: str, new_status: str) -> bool:
|
|
864
864
|
"""Return whether a persisted post-launch outcome should continue in-process."""
|
|
865
|
-
if session_status in {
|
|
865
|
+
if session_status in {
|
|
866
|
+
"success", "stalled_context_continuation", "workflow_blocked",
|
|
867
|
+
"workflow_skipped",
|
|
868
|
+
}:
|
|
866
869
|
return False
|
|
867
870
|
return new_status in {"in_progress", "pending"} and session_status in {
|
|
868
871
|
"context_overflow",
|
|
@@ -9,6 +9,12 @@ from typing import Any
|
|
|
9
9
|
|
|
10
10
|
TEST_RESULTS = {"TEST_PASS", "TEST_NEEDS_FIXES", "TEST_BLOCKED"}
|
|
11
11
|
INDEPENDENT_REVIEW_STATUSES = {"completed", "downgraded", "not_applicable"}
|
|
12
|
+
_RESULT_LINE_PATTERN = re.compile(
|
|
13
|
+
r"(?m)^- Result:\s*(TEST_PASS|TEST_NEEDS_FIXES|TEST_BLOCKED)\s*$"
|
|
14
|
+
)
|
|
15
|
+
_BARE_RESULT_LINE_PATTERN = re.compile(
|
|
16
|
+
r"(?m)^(TEST_PASS|TEST_NEEDS_FIXES|TEST_BLOCKED)\s*$"
|
|
17
|
+
)
|
|
12
18
|
_RESULT_KEYS = {
|
|
13
19
|
"schema_version",
|
|
14
20
|
"result",
|
|
@@ -128,18 +134,22 @@ def valid_test_artifact_pair(
|
|
|
128
134
|
|
|
129
135
|
|
|
130
136
|
def _report_final_result(report: str) -> str | None:
|
|
131
|
-
"""Extract
|
|
132
|
-
|
|
133
|
-
|
|
137
|
+
"""Extract one unambiguous result from a supported terminal section."""
|
|
138
|
+
declared_results: list[str] = []
|
|
139
|
+
for match in re.finditer(
|
|
140
|
+
r"(?ms)^## (?P<title>Final Result|Terminal Result|Final Decision)\s*$\n"
|
|
141
|
+
r"(?P<body>.*?)(?=^## |\Z)",
|
|
134
142
|
report,
|
|
135
|
-
)
|
|
136
|
-
|
|
143
|
+
):
|
|
144
|
+
body = match.group("body")
|
|
145
|
+
result_lines = _RESULT_LINE_PATTERN.findall(body)
|
|
146
|
+
if match.group("title") == "Final Decision":
|
|
147
|
+
result_lines.extend(_BARE_RESULT_LINE_PATTERN.findall(body))
|
|
148
|
+
declared_results.extend(result_lines)
|
|
149
|
+
|
|
150
|
+
if not declared_results or len(set(declared_results)) != 1:
|
|
137
151
|
return None
|
|
138
|
-
|
|
139
|
-
r"(?m)^- Result:\s*(TEST_PASS|TEST_NEEDS_FIXES|TEST_BLOCKED)\s*$",
|
|
140
|
-
match.group("body"),
|
|
141
|
-
)
|
|
142
|
-
return result.group(1) if result else None
|
|
152
|
+
return declared_results[0]
|
|
143
153
|
|
|
144
154
|
|
|
145
155
|
def _integer_in_range(value: object, minimum: int, maximum: int) -> bool:
|
|
@@ -150,7 +150,9 @@ def determine_status(data):
|
|
|
150
150
|
"""Determine the single-line status string from the parsed data.
|
|
151
151
|
|
|
152
152
|
Returns one of: success, partial_resumable, partial_not_resumable,
|
|
153
|
-
failed,
|
|
153
|
+
failed, crashed, timed_out, infra_error, commit_missing, docs_missing,
|
|
154
|
+
merge_conflict, context_overflow, stalled_context_continuation,
|
|
155
|
+
workflow_blocked, workflow_skipped.
|
|
154
156
|
"""
|
|
155
157
|
status = data.get("status", "")
|
|
156
158
|
|
|
@@ -174,12 +176,20 @@ def determine_status(data):
|
|
|
174
176
|
return "crashed"
|
|
175
177
|
elif status == "timed_out":
|
|
176
178
|
return "timed_out"
|
|
179
|
+
elif status == "infra_error":
|
|
180
|
+
return "infra_error"
|
|
181
|
+
elif status == "stalled_context_continuation":
|
|
182
|
+
return "stalled_context_continuation"
|
|
177
183
|
elif status == "commit_missing":
|
|
178
184
|
return "commit_missing"
|
|
179
185
|
elif status == "docs_missing":
|
|
180
186
|
return "docs_missing"
|
|
181
187
|
elif status == "merge_conflict":
|
|
182
188
|
return "merge_conflict"
|
|
189
|
+
elif status == "workflow_blocked":
|
|
190
|
+
return "workflow_blocked"
|
|
191
|
+
elif status == "workflow_skipped":
|
|
192
|
+
return "workflow_skipped"
|
|
183
193
|
else:
|
|
184
194
|
# Unknown status value — treat as crashed
|
|
185
195
|
return "crashed"
|
|
@@ -717,7 +717,7 @@ def route_feature_repair(stage_result, repair_scope=None, repair_round=0):
|
|
|
717
717
|
|
|
718
718
|
|
|
719
719
|
def feature_checkpoint_metadata(artifact_dir, stage="plan",
|
|
720
|
-
status="
|
|
720
|
+
status="pending", stage_result=None,
|
|
721
721
|
repair_scope=None, repair_round=0,
|
|
722
722
|
next_stage="prizmkit-implement",
|
|
723
723
|
resume_from="prizmkit-implement"):
|
|
@@ -1478,7 +1478,7 @@ def main():
|
|
|
1478
1478
|
checkpoint["feature_state"] = feature_checkpoint_metadata(
|
|
1479
1479
|
os.path.join(".prizmkit", "specs", feature_slug),
|
|
1480
1480
|
stage="plan",
|
|
1481
|
-
status="
|
|
1481
|
+
status="pending",
|
|
1482
1482
|
stage_result=None,
|
|
1483
1483
|
next_stage="prizmkit-implement",
|
|
1484
1484
|
resume_from="prizmkit-implement",
|
|
@@ -41,7 +41,7 @@ def route_bugfix_repair(stage_result, repair_scope=None, repair_round=0):
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def bugfix_checkpoint_metadata(artifact_dir, stage="plan",
|
|
44
|
-
status="
|
|
44
|
+
status="pending", stage_result=None,
|
|
45
45
|
repair_scope=None, repair_round=0,
|
|
46
46
|
next_stage="prizmkit-implement",
|
|
47
47
|
resume_from="prizmkit-implement"):
|
|
@@ -536,7 +536,7 @@ def main():
|
|
|
536
536
|
checkpoint["bugfix_state"] = bugfix_checkpoint_metadata(
|
|
537
537
|
replacements["{{ARTIFACT_DIR}}"],
|
|
538
538
|
stage="plan",
|
|
539
|
-
status="
|
|
539
|
+
status="pending",
|
|
540
540
|
stage_result=None,
|
|
541
541
|
repair_scope=None,
|
|
542
542
|
repair_round=0,
|
|
@@ -41,7 +41,7 @@ def route_refactor_repair(stage_result, repair_scope=None, repair_round=0):
|
|
|
41
41
|
|
|
42
42
|
|
|
43
43
|
def refactor_checkpoint_metadata(artifact_dir, stage="plan",
|
|
44
|
-
status="
|
|
44
|
+
status="pending", stage_result=None,
|
|
45
45
|
repair_scope=None, repair_round=0,
|
|
46
46
|
next_stage="prizmkit-implement",
|
|
47
47
|
resume_from="prizmkit-implement"):
|
|
@@ -554,7 +554,7 @@ def main():
|
|
|
554
554
|
checkpoint["refactor_state"] = refactor_checkpoint_metadata(
|
|
555
555
|
replacements["{{ARTIFACT_DIR}}"],
|
|
556
556
|
stage="plan",
|
|
557
|
-
status="
|
|
557
|
+
status="pending",
|
|
558
558
|
stage_result=None,
|
|
559
559
|
repair_scope=None,
|
|
560
560
|
repair_round=0,
|
|
@@ -14,6 +14,10 @@ _PIPELINE_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
|
|
|
14
14
|
if _PIPELINE_ROOT not in sys.path:
|
|
15
15
|
sys.path.insert(0, _PIPELINE_ROOT)
|
|
16
16
|
|
|
17
|
+
from prizmkit_runtime.checkpoint_state import ( # noqa: E402
|
|
18
|
+
VALID_CHECKPOINT_STATUSES,
|
|
19
|
+
expected_status_for_stage_result,
|
|
20
|
+
)
|
|
17
21
|
from prizmkit_runtime.test_result import valid_test_artifact_pair # noqa: E402
|
|
18
22
|
|
|
19
23
|
|
|
@@ -95,12 +99,19 @@ def route_pipeline_repair(stage_result, repair_scope=None, repair_round=0):
|
|
|
95
99
|
|
|
96
100
|
|
|
97
101
|
def pipeline_checkpoint_metadata(artifact_dir, stage="plan",
|
|
98
|
-
status="
|
|
102
|
+
status="pending", stage_result=None,
|
|
99
103
|
repair_scope=None, repair_round=0,
|
|
100
104
|
next_stage="prizmkit-implement",
|
|
101
105
|
resume_from="prizmkit-implement",
|
|
102
106
|
blocked_reason=None, terminal_status=None):
|
|
103
107
|
"""Build family-neutral pipeline metadata without replacing skill state."""
|
|
108
|
+
if status not in VALID_CHECKPOINT_STATUSES:
|
|
109
|
+
raise ValueError("Invalid checkpoint lifecycle status: {}".format(status))
|
|
110
|
+
expected_status = expected_status_for_stage_result(stage_result)
|
|
111
|
+
if expected_status is None and stage_result is not None:
|
|
112
|
+
raise ValueError("Invalid checkpoint stage result: {}".format(stage_result))
|
|
113
|
+
if expected_status is not None and status != expected_status:
|
|
114
|
+
raise ValueError("{} requires status={}".format(stage_result, expected_status))
|
|
104
115
|
current_stage = None if terminal_status else stage
|
|
105
116
|
semantic_next_stage = None if terminal_status else next_stage
|
|
106
117
|
return {
|
|
@@ -47,6 +47,7 @@ SESSION_STATUS_VALUES = [
|
|
|
47
47
|
"context_overflow",
|
|
48
48
|
"stalled_context_continuation",
|
|
49
49
|
"workflow_blocked",
|
|
50
|
+
"workflow_skipped",
|
|
50
51
|
"commit_missing",
|
|
51
52
|
"docs_missing",
|
|
52
53
|
"merge_conflict",
|
|
@@ -593,6 +594,20 @@ def action_update(args, bug_list_path, state_dir):
|
|
|
593
594
|
if err:
|
|
594
595
|
error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
|
|
595
596
|
return
|
|
597
|
+
elif session_status == "workflow_skipped":
|
|
598
|
+
# A completed AI CLI session without authoritative workflow evidence is
|
|
599
|
+
# skipped, not crashed. Preserve diagnostics and spend no retry budget.
|
|
600
|
+
new_status = "skipped"
|
|
601
|
+
bs["continuation_pending"] = False
|
|
602
|
+
bs.pop("continuation_reason", None)
|
|
603
|
+
bs.pop("terminal_status", None)
|
|
604
|
+
bs.pop("blocked_reason", None)
|
|
605
|
+
bs["needs_attention"] = True
|
|
606
|
+
bs["skipped_reason"] = "workflow_completion_evidence_invalid"
|
|
607
|
+
err = update_bug_in_list(bug_list_path, bug_id, new_status)
|
|
608
|
+
if err:
|
|
609
|
+
error_out("Failed to update .prizmkit/plans/bug-fix-list.json: {}".format(err))
|
|
610
|
+
return
|
|
596
611
|
elif session_status == "workflow_blocked":
|
|
597
612
|
# Semantic terminal state: preserve partial work without consuming any retry budget.
|
|
598
613
|
new_status = "needs_info"
|
|
@@ -677,6 +692,11 @@ def action_update(args, bug_list_path, state_dir):
|
|
|
677
692
|
summary["restart_policy"] = "context_overflow_continuation"
|
|
678
693
|
summary.update(continuation_metadata_summary(bs))
|
|
679
694
|
summary["artifacts_preserved"] = True
|
|
695
|
+
elif session_status == "workflow_skipped":
|
|
696
|
+
summary["restart_policy"] = "terminal_skipped"
|
|
697
|
+
summary.update(continuation_metadata_summary(bs))
|
|
698
|
+
summary["checkpoint_state"] = semantic_checkpoint
|
|
699
|
+
summary["artifacts_preserved"] = True
|
|
680
700
|
elif session_status == "workflow_blocked":
|
|
681
701
|
summary["restart_policy"] = "terminal_blocked"
|
|
682
702
|
summary.update(continuation_metadata_summary(bs))
|
|
@@ -15,12 +15,16 @@ if _PIPELINE_ROOT not in sys.path:
|
|
|
15
15
|
from prizmkit_runtime.checkpoint_state import ( # noqa: E402
|
|
16
16
|
MAX_OUTER_REPAIR_ROUNDS,
|
|
17
17
|
REVIEW_RESULTS,
|
|
18
|
+
SKILL_WORKFLOW_STAGES,
|
|
18
19
|
STEP_ID_PATTERN,
|
|
19
20
|
TERMINAL_STEP_STATUSES,
|
|
20
21
|
TEST_RESULTS,
|
|
21
22
|
VALID_CHECKPOINT_STATUSES,
|
|
23
|
+
VALID_STAGE_RESULTS,
|
|
22
24
|
VALID_REPAIR_SCOPES,
|
|
23
25
|
VALID_TERMINAL_STATUSES,
|
|
26
|
+
expected_status_for_stage_result,
|
|
27
|
+
stage_result_allowed_for_skill,
|
|
24
28
|
load_checkpoint_state,
|
|
25
29
|
semantic_state_key,
|
|
26
30
|
validate_checkpoint_data,
|
|
@@ -134,6 +138,12 @@ def _validate_semantic_update(step, semantic_update):
|
|
|
134
138
|
return "Review verdicts may only be recorded on prizmkit-code-review"
|
|
135
139
|
if stage_result in TEST_RESULTS and skill != "prizmkit-test":
|
|
136
140
|
return "Test verdicts may only be recorded on prizmkit-test"
|
|
141
|
+
if not stage_result_allowed_for_skill(skill, stage_result):
|
|
142
|
+
return "{} cannot record stage result {}".format(skill, stage_result)
|
|
143
|
+
expected_status = expected_status_for_stage_result(stage_result)
|
|
144
|
+
if expected_status is not None and semantic_update.get("status") is not None:
|
|
145
|
+
if semantic_update["status"] != expected_status:
|
|
146
|
+
return "{} requires status={}".format(stage_result, expected_status)
|
|
137
147
|
repair_scope = semantic_update.get("repair_scope")
|
|
138
148
|
if repair_scope is not None and repair_scope not in VALID_REPAIR_SCOPES:
|
|
139
149
|
return "Invalid repair_scope '{}'".format(repair_scope)
|
|
@@ -150,7 +160,7 @@ def _validate_semantic_update(step, semantic_update):
|
|
|
150
160
|
|
|
151
161
|
def _apply_repair_policy(l4_state, stage_result, repair_scope):
|
|
152
162
|
if stage_result not in {"REVIEW_NEEDS_FIXES", "TEST_NEEDS_FIXES", "TEST_BLOCKED"}:
|
|
153
|
-
return
|
|
163
|
+
return False
|
|
154
164
|
current_round = l4_state.get("repair_round", 0)
|
|
155
165
|
if not isinstance(current_round, int) or isinstance(current_round, bool):
|
|
156
166
|
current_round = MAX_OUTER_REPAIR_ROUNDS
|
|
@@ -165,7 +175,7 @@ def _apply_repair_policy(l4_state, stage_result, repair_scope):
|
|
|
165
175
|
current_stage=None,
|
|
166
176
|
next_stage=None,
|
|
167
177
|
)
|
|
168
|
-
return
|
|
178
|
+
return False
|
|
169
179
|
if current_round >= MAX_OUTER_REPAIR_ROUNDS:
|
|
170
180
|
l4_state.update(
|
|
171
181
|
repair_round=MAX_OUTER_REPAIR_ROUNDS,
|
|
@@ -174,7 +184,7 @@ def _apply_repair_policy(l4_state, stage_result, repair_scope):
|
|
|
174
184
|
current_stage=None,
|
|
175
185
|
next_stage=None,
|
|
176
186
|
)
|
|
177
|
-
return
|
|
187
|
+
return False
|
|
178
188
|
if repair_scope not in VALID_REPAIR_SCOPES:
|
|
179
189
|
l4_state.update(
|
|
180
190
|
terminal_status="WORKFLOW_BLOCKED",
|
|
@@ -182,14 +192,15 @@ def _apply_repair_policy(l4_state, stage_result, repair_scope):
|
|
|
182
192
|
current_stage=None,
|
|
183
193
|
next_stage=None,
|
|
184
194
|
)
|
|
185
|
-
return
|
|
195
|
+
return False
|
|
186
196
|
l4_state["repair_round"] = current_round + 1
|
|
187
197
|
l4_state["terminal_status"] = None
|
|
188
198
|
l4_state["blocked_reason"] = None
|
|
189
199
|
l4_state["next_stage"] = "prizmkit-implement"
|
|
200
|
+
return True
|
|
190
201
|
|
|
191
202
|
|
|
192
|
-
def _sync_cursor(data, l4_state):
|
|
203
|
+
def _sync_cursor(data, l4_state, preserve_next_stage=False):
|
|
193
204
|
active = next(
|
|
194
205
|
(step for step in data["steps"] if step.get("status") not in TERMINAL_STEP_STATUSES),
|
|
195
206
|
None,
|
|
@@ -198,8 +209,9 @@ def _sync_cursor(data, l4_state):
|
|
|
198
209
|
l4_state["current_stage"] = None
|
|
199
210
|
l4_state["next_stage"] = None
|
|
200
211
|
return
|
|
212
|
+
routed_stage = l4_state.get("next_stage") if preserve_next_stage else None
|
|
201
213
|
l4_state["current_stage"] = active.get("skill") if active else None
|
|
202
|
-
l4_state["next_stage"] = active.get("skill") if active else None
|
|
214
|
+
l4_state["next_stage"] = routed_stage or (active.get("skill") if active else None)
|
|
203
215
|
|
|
204
216
|
def _checkpoint_project_root(checkpoint_path):
|
|
205
217
|
"""Resolve the project root from a conventional .prizmkit checkpoint path."""
|
|
@@ -296,6 +308,12 @@ def update_checkpoint(
|
|
|
296
308
|
stage_result = stage_result or step.get("stage_result")
|
|
297
309
|
if stage_result != "TEST_PASS":
|
|
298
310
|
return {"ok": False, "error": "Test completion requires TEST_PASS"}
|
|
311
|
+
expected_status = expected_status_for_stage_result(stage_result)
|
|
312
|
+
if expected_status is not None and new_status != expected_status:
|
|
313
|
+
return {
|
|
314
|
+
"ok": False,
|
|
315
|
+
"error": "{} requires status={}".format(stage_result, expected_status),
|
|
316
|
+
}
|
|
299
317
|
|
|
300
318
|
old_status = step.get("status", "unknown")
|
|
301
319
|
step["status"] = new_status
|
|
@@ -303,6 +321,8 @@ def update_checkpoint(
|
|
|
303
321
|
step["note"] = note
|
|
304
322
|
if stage_result:
|
|
305
323
|
step["stage_result"] = stage_result
|
|
324
|
+
else:
|
|
325
|
+
step.pop("stage_result", None)
|
|
306
326
|
|
|
307
327
|
workflow_type = str(data.get("workflow_type") or "")
|
|
308
328
|
state_key = semantic_state_key(workflow_type)
|
|
@@ -320,10 +340,19 @@ def update_checkpoint(
|
|
|
320
340
|
l4_state[key] = semantic_update[key]
|
|
321
341
|
l4_state["schema_version"] = 1
|
|
322
342
|
l4_state["orchestrator"] = "prizmkit-l4"
|
|
343
|
+
skill = step.get("skill")
|
|
344
|
+
l4_state["stage"] = SKILL_WORKFLOW_STAGES.get(skill, skill)
|
|
345
|
+
l4_state["status"] = new_status
|
|
323
346
|
if stage_result:
|
|
324
347
|
l4_state["stage_result"] = stage_result
|
|
325
|
-
|
|
326
|
-
|
|
348
|
+
else:
|
|
349
|
+
l4_state.pop("stage_result", None)
|
|
350
|
+
repair_route_selected = _apply_repair_policy(
|
|
351
|
+
l4_state,
|
|
352
|
+
stage_result,
|
|
353
|
+
semantic_update.get("repair_scope") or l4_state.get("repair_scope"),
|
|
354
|
+
)
|
|
355
|
+
_sync_cursor(data, l4_state, preserve_next_stage=repair_route_selected)
|
|
327
356
|
_finalize_semantics(checkpoint_path, data, state_key)
|
|
328
357
|
|
|
329
358
|
final_state = validate_checkpoint_data(
|
|
@@ -362,7 +391,7 @@ def main():
|
|
|
362
391
|
parser.add_argument("--step", required=True)
|
|
363
392
|
parser.add_argument("--status", required=True, choices=sorted(VALID_STATUSES))
|
|
364
393
|
parser.add_argument("--note", default=None)
|
|
365
|
-
parser.add_argument("--stage-result", choices=sorted(
|
|
394
|
+
parser.add_argument("--stage-result", choices=sorted(VALID_STAGE_RESULTS), default=None)
|
|
366
395
|
parser.add_argument("--repair-scope", choices=sorted(VALID_REPAIR_SCOPES), default=None)
|
|
367
396
|
parser.add_argument("--repair-round", type=int, default=None)
|
|
368
397
|
parser.add_argument("--blocked-reason", default=None)
|
|
@@ -51,6 +51,7 @@ SESSION_STATUS_VALUES = [
|
|
|
51
51
|
"context_overflow",
|
|
52
52
|
"stalled_context_continuation",
|
|
53
53
|
"workflow_blocked",
|
|
54
|
+
"workflow_skipped",
|
|
54
55
|
"commit_missing",
|
|
55
56
|
"docs_missing",
|
|
56
57
|
"merge_conflict",
|
|
@@ -1021,6 +1022,20 @@ def action_update(args, feature_list_path, state_dir):
|
|
|
1021
1022
|
if err:
|
|
1022
1023
|
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1023
1024
|
return
|
|
1025
|
+
elif session_status == "workflow_skipped":
|
|
1026
|
+
# A completed AI CLI session without authoritative workflow evidence is
|
|
1027
|
+
# skipped, not crashed. Preserve diagnostics and spend no retry budget.
|
|
1028
|
+
new_status = "skipped"
|
|
1029
|
+
fs["continuation_pending"] = False
|
|
1030
|
+
fs.pop("continuation_reason", None)
|
|
1031
|
+
fs.pop("terminal_status", None)
|
|
1032
|
+
fs.pop("blocked_reason", None)
|
|
1033
|
+
fs["needs_attention"] = True
|
|
1034
|
+
fs["skipped_reason"] = "workflow_completion_evidence_invalid"
|
|
1035
|
+
err = update_feature_in_list(feature_list_path, feature_id, new_status)
|
|
1036
|
+
if err:
|
|
1037
|
+
error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
|
|
1038
|
+
return
|
|
1024
1039
|
elif session_status == "workflow_blocked":
|
|
1025
1040
|
# Semantic terminal state: preserve partial work without consuming any retry budget.
|
|
1026
1041
|
new_status = "failed"
|
|
@@ -1119,6 +1134,11 @@ def action_update(args, feature_list_path, state_dir):
|
|
|
1119
1134
|
summary["restart_policy"] = "context_overflow_continuation"
|
|
1120
1135
|
summary.update(continuation_metadata_summary(fs))
|
|
1121
1136
|
summary["artifacts_preserved"] = True
|
|
1137
|
+
elif session_status == "workflow_skipped":
|
|
1138
|
+
summary["restart_policy"] = "terminal_skipped"
|
|
1139
|
+
summary.update(continuation_metadata_summary(fs))
|
|
1140
|
+
summary["checkpoint_state"] = semantic_checkpoint
|
|
1141
|
+
summary["artifacts_preserved"] = True
|
|
1122
1142
|
elif session_status == "workflow_blocked":
|
|
1123
1143
|
summary["restart_policy"] = "terminal_blocked"
|
|
1124
1144
|
summary.update(continuation_metadata_summary(fs))
|
|
@@ -48,6 +48,7 @@ SESSION_STATUS_VALUES = [
|
|
|
48
48
|
"context_overflow",
|
|
49
49
|
"stalled_context_continuation",
|
|
50
50
|
"workflow_blocked",
|
|
51
|
+
"workflow_skipped",
|
|
51
52
|
"commit_missing",
|
|
52
53
|
"docs_missing",
|
|
53
54
|
"merge_conflict",
|
|
@@ -597,6 +598,20 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
597
598
|
if err:
|
|
598
599
|
error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
599
600
|
return
|
|
601
|
+
elif session_status == "workflow_skipped":
|
|
602
|
+
# A completed AI CLI session without authoritative workflow evidence is
|
|
603
|
+
# skipped, not crashed. Preserve diagnostics and spend no retry budget.
|
|
604
|
+
new_status = "skipped"
|
|
605
|
+
rs["continuation_pending"] = False
|
|
606
|
+
rs.pop("continuation_reason", None)
|
|
607
|
+
rs.pop("terminal_status", None)
|
|
608
|
+
rs.pop("blocked_reason", None)
|
|
609
|
+
rs["needs_attention"] = True
|
|
610
|
+
rs["skipped_reason"] = "workflow_completion_evidence_invalid"
|
|
611
|
+
err = update_refactor_in_list(refactor_list_path, refactor_id, new_status)
|
|
612
|
+
if err:
|
|
613
|
+
error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
|
|
614
|
+
return
|
|
600
615
|
elif session_status == "workflow_blocked":
|
|
601
616
|
# Semantic terminal state: preserve partial work without consuming any retry budget.
|
|
602
617
|
new_status = "failed"
|
|
@@ -690,6 +705,11 @@ def action_update(args, refactor_list_path, state_dir):
|
|
|
690
705
|
summary["restart_policy"] = "context_overflow_continuation"
|
|
691
706
|
summary.update(continuation_metadata_summary(rs))
|
|
692
707
|
summary["artifacts_preserved"] = True
|
|
708
|
+
elif session_status == "workflow_skipped":
|
|
709
|
+
summary["restart_policy"] = "terminal_skipped"
|
|
710
|
+
summary.update(continuation_metadata_summary(rs))
|
|
711
|
+
summary["checkpoint_state"] = semantic_checkpoint
|
|
712
|
+
summary["artifacts_preserved"] = True
|
|
693
713
|
elif session_status == "workflow_blocked":
|
|
694
714
|
summary["restart_policy"] = "terminal_blocked"
|
|
695
715
|
summary.update(continuation_metadata_summary(rs))
|