prizmkit 1.1.130 → 1.1.131

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 (88) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/dev-pipeline/.env.example +1 -1
  3. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +445 -8
  4. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +87 -66
  5. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +53 -3
  6. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +55 -25
  7. package/bundled/dev-pipeline/prizmkit_runtime/runner_status.py +4 -0
  8. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +15 -1
  9. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +396 -4
  10. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +54 -1
  11. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +54 -1
  12. package/bundled/dev-pipeline/scripts/prompt_framework.py +152 -1
  13. package/bundled/dev-pipeline/scripts/update-bug-status.py +53 -0
  14. package/bundled/dev-pipeline/scripts/update-checkpoint.py +294 -69
  15. package/bundled/dev-pipeline/scripts/update-feature-status.py +57 -0
  16. package/bundled/dev-pipeline/scripts/update-refactor-status.py +53 -0
  17. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +2 -2
  18. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +20 -14
  19. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +21 -4
  20. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +18 -0
  21. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +1 -1
  22. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +5 -15
  23. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +21 -0
  24. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +10 -10
  25. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +16 -0
  26. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +26 -23
  27. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +12 -8
  28. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +1 -1
  29. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +21 -0
  30. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +362 -3
  31. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +310 -22
  32. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +132 -0
  33. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +91 -0
  34. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +411 -18
  35. package/bundled/dev-pipeline/tests/test_unified_cli.py +3 -3
  36. package/bundled/skills/_metadata.json +62 -25
  37. package/bundled/skills/app-planner/SKILL.md +4 -3
  38. package/bundled/skills/bug-fix-workflow/SKILL.md +89 -180
  39. package/bundled/skills/bug-planner/SKILL.md +12 -27
  40. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +131 -220
  41. package/bundled/skills/bugfix-pipeline-launcher/references/configuration.md +36 -90
  42. package/bundled/skills/feature-pipeline-launcher/SKILL.md +129 -271
  43. package/bundled/skills/feature-pipeline-launcher/references/configuration.md +35 -76
  44. package/bundled/skills/feature-planner/SKILL.md +27 -22
  45. package/bundled/skills/feature-planner/assets/planning-guide.md +4 -4
  46. package/bundled/skills/feature-workflow/SKILL.md +94 -121
  47. package/bundled/skills/prizmkit/SKILL.md +130 -94
  48. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +181 -0
  49. package/bundled/skills/prizmkit-code-review/SKILL.md +114 -84
  50. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +1 -1
  51. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +181 -0
  52. package/bundled/skills/prizmkit-committer/SKILL.md +131 -47
  53. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +181 -0
  54. package/bundled/skills/prizmkit-implement/SKILL.md +99 -73
  55. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +1 -1
  56. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +181 -0
  57. package/bundled/skills/prizmkit-init/SKILL.md +5 -5
  58. package/bundled/skills/prizmkit-plan/SKILL.md +132 -105
  59. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +181 -0
  60. package/bundled/skills/prizmkit-retrospective/SKILL.md +111 -53
  61. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +7 -6
  62. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +5 -4
  63. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +181 -0
  64. package/bundled/skills/prizmkit-test/SKILL.md +28 -6
  65. package/bundled/skills/prizmkit-test/references/examples.md +1 -1
  66. package/bundled/skills/prizmkit-test/references/test-report-template.md +1 -1
  67. package/bundled/skills/prizmkit-test/references/workflow-state-protocol.md +181 -0
  68. package/bundled/skills/prizmkit-workflow/SKILL.md +266 -0
  69. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +181 -0
  70. package/bundled/skills/recovery-workflow/SKILL.md +62 -241
  71. package/bundled/skills/recovery-workflow/evals/evals.json +17 -33
  72. package/bundled/skills/recovery-workflow/references/detection.md +28 -52
  73. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +105 -439
  74. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +136 -275
  75. package/bundled/skills/refactor-pipeline-launcher/references/configuration.md +53 -88
  76. package/bundled/skills/refactor-planner/SKILL.md +12 -57
  77. package/bundled/skills/refactor-planner/references/behavior-preservation.md +111 -230
  78. package/bundled/skills/refactor-planner/references/error-recovery.md +37 -0
  79. package/bundled/skills/refactor-planner/references/planning-phases.md +11 -17
  80. package/bundled/skills/refactor-planner/scripts/validate-and-generate-refactor.py +1 -1
  81. package/bundled/skills/refactor-workflow/SKILL.md +98 -139
  82. package/bundled/templates/project-memory-template.md +4 -4
  83. package/package.json +1 -1
  84. package/src/index.js +4 -0
  85. package/src/prompts.js +10 -3
  86. package/src/scaffold.js +1 -1
  87. package/bundled/dev-pipeline/tests/test-deploy-safety.sh +0 -223
  88. package/bundled/skills/feature-pipeline-launcher/scripts/preflight-check.py +0 -462
@@ -1,37 +1,29 @@
1
1
  #!/usr/bin/env python3
2
- """Safely update a workflow-checkpoint.json step status.
3
-
4
- Instead of having the AI hand-write JSON, this script reads the checkpoint,
5
- validates the update, and writes it back atomically.
6
-
7
- Usage:
8
- python3 update-checkpoint.py --checkpoint-path <path> --step <skill-or-id> --status <status>
9
- python3 update-checkpoint.py --checkpoint-path <path> --step prizmkit-implement --status completed
10
- python3 update-checkpoint.py --checkpoint-path <path> --step S04 --status in_progress
11
- python3 update-checkpoint.py --checkpoint-path <path> --step prizmkit-code-review --status failed --note "max rounds exhausted"
12
-
13
- Exit codes:
14
- 0 = success
15
- 1 = validation error (step not found, invalid status, ambiguous skill, etc.)
16
- 2 = file error (not found, corrupted JSON)
17
-
18
- Output (stdout): JSON with {ok: true, step_id, skill, old_status, new_status} or {ok: false, error}
19
- """
2
+ """Atomically update one semantic L4 workflow checkpoint."""
20
3
 
21
4
  import argparse
22
5
  import json
23
6
  import os
24
7
  import sys
25
8
  import tempfile
9
+ from pathlib import Path
26
10
 
27
11
  _PIPELINE_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), ".."))
28
12
  if _PIPELINE_ROOT not in sys.path:
29
13
  sys.path.insert(0, _PIPELINE_ROOT)
30
14
 
31
15
  from prizmkit_runtime.checkpoint_state import ( # noqa: E402
16
+ MAX_OUTER_REPAIR_ROUNDS,
17
+ REVIEW_RESULTS,
32
18
  STEP_ID_PATTERN,
19
+ TERMINAL_STEP_STATUSES,
20
+ TEST_RESULTS,
33
21
  VALID_CHECKPOINT_STATUSES,
22
+ VALID_REPAIR_SCOPES,
23
+ VALID_TERMINAL_STATUSES,
34
24
  load_checkpoint_state,
25
+ semantic_state_key,
26
+ validate_checkpoint_data,
35
27
  )
36
28
 
37
29
  VALID_STATUSES = VALID_CHECKPOINT_STATUSES
@@ -48,15 +40,15 @@ def _load_checkpoint(path):
48
40
  try:
49
41
  with open(path, "r", encoding="utf-8") as f:
50
42
  data = json.load(f)
51
- except json.JSONDecodeError as e:
52
- return None, "Corrupted JSON: {}".format(e)
53
- except IOError as e:
54
- return None, "Cannot read file: {}".format(e)
43
+ except json.JSONDecodeError as exc:
44
+ return None, "Corrupted JSON: {}".format(exc)
45
+ except IOError as exc:
46
+ return None, "Cannot read file: {}".format(exc)
55
47
  return data, None
56
48
 
57
49
 
58
50
  def _write_checkpoint(path, data):
59
- """Write checkpoint JSON atomically using a temp file + rename."""
51
+ """Write checkpoint JSON atomically using a temp file and rename."""
60
52
  parent = os.path.dirname(os.path.abspath(path))
61
53
  if parent and not os.path.isdir(parent):
62
54
  os.makedirs(parent, exist_ok=True)
@@ -70,24 +62,24 @@ def _write_checkpoint(path, data):
70
62
  json.dump(data, f, indent=2, ensure_ascii=False)
71
63
  f.write("\n")
72
64
  os.replace(tmp_path, os.path.abspath(path))
73
- except (IOError, OSError) as e:
65
+ except (IOError, OSError) as exc:
74
66
  if tmp_path is not None and os.path.exists(tmp_path):
75
67
  os.unlink(tmp_path)
76
- return "Cannot write file: {}".format(e)
68
+ return "Cannot write file: {}".format(exc)
77
69
  return None
78
70
 
79
71
 
80
72
  def _find_step(steps, identifier):
81
73
  """Find a step by exact ID or unambiguous skill name."""
82
74
  if STEP_ID_PATTERN.match(identifier):
83
- for i, step in enumerate(steps):
75
+ for index, step in enumerate(steps):
84
76
  if step.get("id") == identifier:
85
- return i, step, None
77
+ return index, step, None
86
78
  return None, None, "Step '{}' not found.".format(identifier)
87
79
 
88
80
  matches = [
89
- (i, step)
90
- for i, step in enumerate(steps)
81
+ (index, step)
82
+ for index, step in enumerate(steps)
91
83
  if step.get("skill") == identifier
92
84
  ]
93
85
  if len(matches) == 1:
@@ -103,11 +95,161 @@ def _find_step(steps, identifier):
103
95
  return None, None, "Step '{}' not found.".format(identifier)
104
96
 
105
97
 
106
- def update_checkpoint(checkpoint_path, step_identifier, new_status, note=None):
107
- """Update a single step's status in the checkpoint file.
98
+ def _predecessors_terminal(steps, index):
99
+ return all(step.get("status") in TERMINAL_STEP_STATUSES for step in steps[:index])
100
+
101
+
102
+
103
+ def _mandatory_predecessor_verdict_error(steps, index, workflow_type):
104
+ if workflow_type not in {"feature-pipeline", "bugfix-pipeline", "refactor-pipeline"}:
105
+ return None
106
+ preceding = steps[:index]
107
+ review = next((item for item in preceding if item.get("skill") == "prizmkit-code-review"), None)
108
+ test = next((item for item in preceding if item.get("skill") == "prizmkit-test"), None)
109
+ if review is None and test is None:
110
+ return None
111
+ skill = steps[index].get("skill")
112
+ if skill == "prizmkit-test":
113
+ if not review or review.get("status") != "completed" or review.get("stage_result") != "REVIEW_PASS":
114
+ return "Test stage requires a completed REVIEW_PASS predecessor"
115
+ if skill in {"prizmkit-retrospective", "prizmkit-committer", "completion-summary", "bug-report", "refactor-report"}:
116
+ if not review or review.get("status") != "completed" or review.get("stage_result") != "REVIEW_PASS":
117
+ return "Finalization requires a completed REVIEW_PASS predecessor"
118
+ if not test or test.get("status") != "completed" or test.get("stage_result") != "TEST_PASS":
119
+ return "Finalization requires a completed TEST_PASS predecessor"
120
+ return None
121
+
122
+ def _normalize_semantic_update(semantic_update):
123
+ values = dict(semantic_update or {})
124
+ evidence = values.pop("authoritative_evidence_paths", None)
125
+ if evidence is not None:
126
+ evidence = {key: value for key, value in evidence.items() if value}
127
+ if evidence:
128
+ values["authoritative_evidence_paths"] = evidence
129
+ return {key: value for key, value in values.items() if value is not None}
130
+
131
+
132
+ def _validate_semantic_update(step, semantic_update):
133
+ stage_result = semantic_update.get("stage_result")
134
+ skill = step.get("skill")
135
+ if stage_result in REVIEW_RESULTS and skill != "prizmkit-code-review":
136
+ return "Review verdicts may only be recorded on prizmkit-code-review"
137
+ if stage_result in TEST_RESULTS and skill != "prizmkit-test":
138
+ return "Test verdicts may only be recorded on prizmkit-test"
139
+ repair_scope = semantic_update.get("repair_scope")
140
+ if repair_scope is not None and repair_scope not in VALID_REPAIR_SCOPES:
141
+ return "Invalid repair_scope '{}'".format(repair_scope)
142
+ repair_round = semantic_update.get("repair_round")
143
+ if repair_round is not None and not 0 <= repair_round <= MAX_OUTER_REPAIR_ROUNDS:
144
+ return "repair_round must be between 0 and {}".format(MAX_OUTER_REPAIR_ROUNDS)
145
+ terminal_status = semantic_update.get("terminal_status")
146
+ if terminal_status is not None and terminal_status not in VALID_TERMINAL_STATUSES:
147
+ return "Invalid terminal_status '{}'".format(terminal_status)
148
+ evidence = semantic_update.get("authoritative_evidence_paths")
149
+ if evidence is not None and set(evidence) != {"manifest", "verdict", "validation"}:
150
+ return "Authoritative test evidence requires manifest, verdict, and validation paths"
151
+ if terminal_status == "WORKFLOW_BLOCKED" and not semantic_update.get("blocked_reason"):
152
+ return "WORKFLOW_BLOCKED requires blocked_reason"
153
+ return None
154
+
108
155
 
109
- Returns a result dict: {ok, step_id, skill, old_status, new_status} or {ok, error}.
110
- """
156
+ def _apply_repair_policy(l4_state, stage_result, repair_scope):
157
+ if stage_result not in {"REVIEW_NEEDS_FIXES", "TEST_FAIL", "TEST_BLOCKED"}:
158
+ return
159
+ current_round = l4_state.get("repair_round", 0)
160
+ if not isinstance(current_round, int) or isinstance(current_round, bool):
161
+ current_round = MAX_OUTER_REPAIR_ROUNDS
162
+ if stage_result == "TEST_BLOCKED":
163
+ l4_state.update(
164
+ terminal_status="WORKFLOW_BLOCKED",
165
+ blocked_reason="test_blocked",
166
+ current_stage=None,
167
+ next_stage=None,
168
+ )
169
+ return
170
+ if current_round >= MAX_OUTER_REPAIR_ROUNDS:
171
+ l4_state.update(
172
+ repair_round=MAX_OUTER_REPAIR_ROUNDS,
173
+ terminal_status="WORKFLOW_BLOCKED",
174
+ blocked_reason="repair_round_limit",
175
+ current_stage=None,
176
+ next_stage=None,
177
+ )
178
+ return
179
+ if repair_scope not in VALID_REPAIR_SCOPES:
180
+ l4_state.update(
181
+ terminal_status="WORKFLOW_BLOCKED",
182
+ blocked_reason="unsafe_repair_scope",
183
+ current_stage=None,
184
+ next_stage=None,
185
+ )
186
+ return
187
+ l4_state["repair_round"] = current_round + 1
188
+ l4_state["terminal_status"] = None
189
+ l4_state["blocked_reason"] = None
190
+ l4_state["next_stage"] = "prizmkit-implement"
191
+
192
+
193
+ def _sync_cursor(data, l4_state):
194
+ active = next(
195
+ (step for step in data["steps"] if step.get("status") not in TERMINAL_STEP_STATUSES),
196
+ None,
197
+ )
198
+ if l4_state.get("terminal_status"):
199
+ l4_state["current_stage"] = None
200
+ l4_state["next_stage"] = None
201
+ return
202
+ l4_state["current_stage"] = active.get("skill") if active else None
203
+ l4_state["next_stage"] = active.get("skill") if active else None
204
+
205
+
206
+
207
+
208
+ def _checkpoint_project_root(checkpoint_path):
209
+ """Resolve the project root from a conventional .prizmkit checkpoint path."""
210
+ path = Path(os.path.abspath(checkpoint_path))
211
+ for parent in (path.parent, *path.parents):
212
+ if parent.name == ".prizmkit":
213
+ return str(parent.parent)
214
+ return str(path.parent)
215
+
216
+ def _finalize_semantics(checkpoint_path, data, state_key):
217
+ """Derive a deterministic terminal status after an atomic mutation."""
218
+ candidate = validate_checkpoint_data(
219
+ data,
220
+ path=checkpoint_path,
221
+ exists=True,
222
+ project_root=_checkpoint_project_root(checkpoint_path),
223
+ )
224
+ l4_state = data[state_key]
225
+ if candidate.valid and candidate.semantic.completion_ready:
226
+ l4_state.update(
227
+ terminal_status="WORKFLOW_COMPLETED",
228
+ blocked_reason=None,
229
+ current_stage=None,
230
+ next_stage=None,
231
+ )
232
+ return
233
+ all_terminal = all(
234
+ step.get("status") in TERMINAL_STEP_STATUSES for step in data["steps"]
235
+ )
236
+ if all_terminal and not l4_state.get("terminal_status"):
237
+ l4_state.update(
238
+ terminal_status="WORKFLOW_BLOCKED",
239
+ blocked_reason=candidate.semantic.error_code or "incomplete_semantic_evidence",
240
+ current_stage=None,
241
+ next_stage=None,
242
+ )
243
+
244
+
245
+ def update_checkpoint(
246
+ checkpoint_path,
247
+ step_identifier,
248
+ new_status,
249
+ note=None,
250
+ semantic_update=None,
251
+ ):
252
+ """Update one step and its cohesive semantic L4 state atomically."""
111
253
  if new_status not in VALID_STATUSES:
112
254
  return {
113
255
  "ok": False,
@@ -120,23 +262,106 @@ def update_checkpoint(checkpoint_path, step_identifier, new_status, note=None):
120
262
  if err:
121
263
  return {"ok": False, "error": err}
122
264
 
123
- idx, step, find_error = _find_step(data["steps"], step_identifier)
265
+ index, step, find_error = _find_step(data["steps"], step_identifier)
124
266
  if step is None:
125
267
  available = [
126
- "{} ({})".format(s.get("id", "?"), s.get("skill", "?"))
127
- for s in data["steps"]
268
+ "{} ({})".format(item.get("id", "?"), item.get("skill", "?"))
269
+ for item in data["steps"]
128
270
  ]
129
271
  suffix = " Available: {}".format(", ".join(available)) if available else ""
272
+ return {"ok": False, "error": "{}{}".format(find_error or "Step not found.", suffix)}
273
+
274
+ if new_status in {"in_progress", "completed", "skipped"} and not _predecessors_terminal(data["steps"], index):
130
275
  return {
131
276
  "ok": False,
132
- "error": "{}{}".format(find_error or "Step not found.", suffix),
277
+ "error": "Step '{}' cannot become {} before all predecessors are completed or skipped".format(
278
+ step.get("id"), new_status
279
+ ),
133
280
  }
281
+ if new_status in {"in_progress", "completed"}:
282
+ verdict_error = _mandatory_predecessor_verdict_error(
283
+ data["steps"], index, str(data.get("workflow_type") or "")
284
+ )
285
+ if verdict_error:
286
+ return {"ok": False, "error": verdict_error}
287
+
288
+ semantic_update = _normalize_semantic_update(semantic_update)
289
+ semantic_error = _validate_semantic_update(step, semantic_update)
290
+ if semantic_error:
291
+ return {"ok": False, "error": semantic_error}
292
+
293
+ stage_result = semantic_update.get("stage_result")
294
+ if new_status == "completed" and step.get("skill") == "prizmkit-code-review":
295
+ stage_result = stage_result or step.get("stage_result")
296
+ if stage_result != "REVIEW_PASS":
297
+ return {"ok": False, "error": "Code Review completion requires REVIEW_PASS"}
298
+ if new_status == "completed" and step.get("skill") == "prizmkit-test":
299
+ stage_result = stage_result or step.get("stage_result")
300
+ if stage_result != "TEST_PASS":
301
+ return {"ok": False, "error": "Test completion requires TEST_PASS"}
302
+ evidence = semantic_update.get("authoritative_evidence_paths") or step.get("authoritative_evidence_paths")
303
+ if not isinstance(evidence, dict) or set(evidence) != {"manifest", "verdict", "validation"}:
304
+ return {"ok": False, "error": "Test completion requires all authoritative evidence paths"}
134
305
 
135
306
  old_status = step.get("status", "unknown")
136
307
  step["status"] = new_status
137
-
138
308
  if note:
139
309
  step["note"] = note
310
+ if stage_result:
311
+ step["stage_result"] = stage_result
312
+ if semantic_update.get("authoritative_evidence_paths"):
313
+ step["authoritative_evidence_paths"] = semantic_update["authoritative_evidence_paths"]
314
+ if new_status == "completed" and step.get("skill") == "prizmkit-test":
315
+ candidate = validate_checkpoint_data(
316
+ data,
317
+ path=checkpoint_path,
318
+ exists=True,
319
+ project_root=_checkpoint_project_root(checkpoint_path),
320
+ )
321
+ if not candidate.valid or not candidate.semantic.evidence_valid:
322
+ return {
323
+ "ok": False,
324
+ "error": "Test completion requires validator-attested authoritative evidence",
325
+ }
326
+
327
+ workflow_type = str(data.get("workflow_type") or "")
328
+ state_key = semantic_state_key(workflow_type)
329
+ l4_state = data.setdefault(state_key, {})
330
+ if not isinstance(l4_state, dict):
331
+ return {"ok": False, "error": "Invalid semantic state object '{}'".format(state_key)}
332
+ for key in (
333
+ "stage_result",
334
+ "repair_scope",
335
+ "repair_round",
336
+ "authoritative_evidence_paths",
337
+ "blocked_reason",
338
+ "terminal_status",
339
+ ):
340
+ if key in semantic_update:
341
+ l4_state[key] = semantic_update[key]
342
+ l4_state["schema_version"] = 1
343
+ l4_state["orchestrator"] = "prizmkit-l4"
344
+ if stage_result:
345
+ l4_state["stage_result"] = stage_result
346
+ if semantic_update.get("authoritative_evidence_paths"):
347
+ l4_state["authoritative_evidence_paths"] = semantic_update["authoritative_evidence_paths"]
348
+ _apply_repair_policy(l4_state, stage_result, semantic_update.get("repair_scope") or l4_state.get("repair_scope"))
349
+ _sync_cursor(data, l4_state)
350
+ _finalize_semantics(checkpoint_path, data, state_key)
351
+
352
+ final_state = validate_checkpoint_data(
353
+ data,
354
+ path=checkpoint_path,
355
+ exists=True,
356
+ project_root=_checkpoint_project_root(checkpoint_path),
357
+ )
358
+ if not final_state.valid:
359
+ return {
360
+ "ok": False,
361
+ "error": "Update would create invalid checkpoint ({}): {}".format(
362
+ final_state.error_code, final_state.error_message
363
+ ),
364
+ }
140
365
 
141
366
  err = _write_checkpoint(checkpoint_path, data)
142
367
  if err:
@@ -148,46 +373,46 @@ def update_checkpoint(checkpoint_path, step_identifier, new_status, note=None):
148
373
  "skill": step.get("skill"),
149
374
  "old_status": old_status,
150
375
  "new_status": new_status,
376
+ "semantic": final_state.semantic_snapshot,
151
377
  }
152
378
 
153
379
 
154
380
  def main():
155
381
  parser = argparse.ArgumentParser(
156
- description="Safely update a workflow-checkpoint.json step status.",
157
- formatter_class=argparse.RawDescriptionHelpFormatter,
158
- epilog=(
159
- "Examples:\n"
160
- " %(prog)s --checkpoint-path .prizmkit/specs/my-feature/workflow-checkpoint.json "
161
- "--step prizmkit-implement --status completed\n"
162
- " %(prog)s --checkpoint-path .prizmkit/bugfix/B-001/workflow-checkpoint.json "
163
- "--step S04 --status in_progress\n"
164
- " %(prog)s --checkpoint-path .prizmkit/refactor/R-001/workflow-checkpoint.json "
165
- "--step prizmkit-code-review --status failed --note 'max rounds exhausted'"
166
- ),
167
- )
168
- parser.add_argument(
169
- "--checkpoint-path", required=True, help="Path to workflow-checkpoint.json"
170
- )
171
- parser.add_argument(
172
- "--step",
173
- required=True,
174
- help="Step identifier (skill name like 'prizmkit-implement' or step ID like 'S04')",
175
- )
176
- parser.add_argument(
177
- "--status",
178
- required=True,
179
- choices=sorted(VALID_STATUSES),
180
- help="New status for the step",
181
- )
182
- parser.add_argument(
183
- "--note",
184
- default=None,
185
- help="Optional note to attach to the step (e.g., failure reason)",
382
+ description="Safely update a semantic workflow-checkpoint.json step status."
186
383
  )
384
+ parser.add_argument("--checkpoint-path", required=True)
385
+ parser.add_argument("--step", required=True)
386
+ parser.add_argument("--status", required=True, choices=sorted(VALID_STATUSES))
387
+ parser.add_argument("--note", default=None)
388
+ parser.add_argument("--stage-result", choices=sorted(REVIEW_RESULTS | TEST_RESULTS), default=None)
389
+ parser.add_argument("--repair-scope", choices=sorted(VALID_REPAIR_SCOPES), default=None)
390
+ parser.add_argument("--repair-round", type=int, default=None)
391
+ parser.add_argument("--manifest-path", default=None)
392
+ parser.add_argument("--verdict-path", default=None)
393
+ parser.add_argument("--validation-path", default=None)
394
+ parser.add_argument("--blocked-reason", default=None)
395
+ parser.add_argument("--terminal-status", choices=sorted(VALID_TERMINAL_STATUSES), default=None)
187
396
 
188
397
  args = parser.parse_args()
398
+ semantic_update = {
399
+ "stage_result": args.stage_result,
400
+ "repair_scope": args.repair_scope,
401
+ "repair_round": args.repair_round,
402
+ "authoritative_evidence_paths": {
403
+ "manifest": args.manifest_path,
404
+ "verdict": args.verdict_path,
405
+ "validation": args.validation_path,
406
+ },
407
+ "blocked_reason": args.blocked_reason,
408
+ "terminal_status": args.terminal_status,
409
+ }
189
410
  result = update_checkpoint(
190
- args.checkpoint_path, args.step, args.status, args.note
411
+ args.checkpoint_path,
412
+ args.step,
413
+ args.status,
414
+ args.note,
415
+ semantic_update,
191
416
  )
192
417
  print(json.dumps(result, indent=2, ensure_ascii=False))
193
418
  error = result.get("error", "")
@@ -50,6 +50,7 @@ SESSION_STATUS_VALUES = [
50
50
  "infra_error",
51
51
  "context_overflow",
52
52
  "stalled_context_continuation",
53
+ "workflow_blocked",
53
54
  "commit_missing",
54
55
  "docs_missing",
55
56
  "merge_conflict",
@@ -152,6 +153,11 @@ def parse_args():
152
153
  default=None,
153
154
  help="JSON progress fingerprint captured after the session.",
154
155
  )
156
+ parser.add_argument(
157
+ "--checkpoint-state",
158
+ default=None,
159
+ help="JSON semantic L4 checkpoint snapshot captured after the session.",
160
+ )
155
161
  return parser.parse_args()
156
162
 
157
163
 
@@ -358,6 +364,37 @@ def _load_progress_fingerprint(raw):
358
364
  return {"unparsed": str(raw)}
359
365
 
360
366
 
367
+ SEMANTIC_CHECKPOINT_FIELDS = (
368
+ "current_stage",
369
+ "next_stage",
370
+ "stage_result",
371
+ "repair_scope",
372
+ "repair_round",
373
+ "authoritative_evidence_paths",
374
+ "blocked_reason",
375
+ "terminal_status",
376
+ )
377
+
378
+
379
+ def _load_checkpoint_state(raw):
380
+ if not raw:
381
+ return {}
382
+ try:
383
+ data = json.loads(raw)
384
+ except (TypeError, ValueError):
385
+ return {}
386
+ if not isinstance(data, dict):
387
+ return {}
388
+ return {key: data.get(key) for key in SEMANTIC_CHECKPOINT_FIELDS}
389
+
390
+
391
+ def _sync_semantic_checkpoint(status_data, args):
392
+ semantic = _load_checkpoint_state(getattr(args, "checkpoint_state", None))
393
+ if semantic:
394
+ status_data.update(semantic)
395
+ return {key: status_data.get(key) for key in SEMANTIC_CHECKPOINT_FIELDS}
396
+
397
+
361
398
  def _set_continuation_common(status_data, args, session_id):
362
399
  if args.active_dev_branch:
363
400
  status_data["active_dev_branch"] = args.active_dev_branch
@@ -886,6 +923,7 @@ def action_update(args, feature_list_path, state_dir):
886
923
  fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
887
924
  if fingerprint is not None:
888
925
  fs["last_progress_fingerprint"] = fingerprint
926
+ semantic_checkpoint = _sync_semantic_checkpoint(fs, args)
889
927
 
890
928
 
891
929
  if session_status == "success":
@@ -984,6 +1022,19 @@ def action_update(args, feature_list_path, state_dir):
984
1022
  if err:
985
1023
  error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
986
1024
  return
1025
+ elif session_status == "workflow_blocked":
1026
+ # Semantic terminal state: preserve partial work without consuming any retry budget.
1027
+ new_status = "failed"
1028
+ fs["continuation_pending"] = False
1029
+ fs["needs_attention"] = True
1030
+ fs["terminal_status"] = "WORKFLOW_BLOCKED"
1031
+ fs["blocked_reason"] = (
1032
+ semantic_checkpoint.get("blocked_reason") or "workflow_blocked"
1033
+ )
1034
+ err = update_feature_in_list(feature_list_path, feature_id, new_status)
1035
+ if err:
1036
+ error_out("Failed to update .prizmkit/plans/feature-list.json: {}".format(err))
1037
+ return
987
1038
  elif session_status == "stalled_context_continuation":
988
1039
  # No-progress context-overflow stall pauses automation without consuming retry budgets.
989
1040
  set_stalled_context_continuation_metadata(fs, args, session_id)
@@ -1052,6 +1103,7 @@ def action_update(args, feature_list_path, state_dir):
1052
1103
  "infra_error_count": fs.get("infra_error_count", 0),
1053
1104
  "max_infra_retries": max_infra_retries,
1054
1105
  "resume_from_phase": fs.get("resume_from_phase"),
1106
+ "checkpoint_state": semantic_checkpoint,
1055
1107
  "updated_at": fs["updated_at"],
1056
1108
  }
1057
1109
  if auto_skipped_features:
@@ -1068,6 +1120,11 @@ def action_update(args, feature_list_path, state_dir):
1068
1120
  summary["restart_policy"] = "context_overflow_continuation"
1069
1121
  summary.update(continuation_metadata_summary(fs))
1070
1122
  summary["artifacts_preserved"] = True
1123
+ elif session_status == "workflow_blocked":
1124
+ summary["restart_policy"] = "terminal_blocked"
1125
+ summary.update(continuation_metadata_summary(fs))
1126
+ summary["checkpoint_state"] = semantic_checkpoint
1127
+ summary["artifacts_preserved"] = True
1071
1128
  elif session_status == "stalled_context_continuation":
1072
1129
  summary["restart_policy"] = "needs_attention"
1073
1130
  summary.update(continuation_metadata_summary(fs))
@@ -47,6 +47,7 @@ SESSION_STATUS_VALUES = [
47
47
  "infra_error",
48
48
  "context_overflow",
49
49
  "stalled_context_continuation",
50
+ "workflow_blocked",
50
51
  "commit_missing",
51
52
  "docs_missing",
52
53
  "merge_conflict",
@@ -100,6 +101,7 @@ def parse_args():
100
101
  parser.add_argument("--continuation-summary-path", default=None, help="Optional continuation summary artifact path.")
101
102
  parser.add_argument("--no-progress-count", type=int, default=None, help="Consecutive context-overflow sessions without objective progress.")
102
103
  parser.add_argument("--progress-fingerprint", default=None, help="JSON progress fingerprint captured after the session.")
104
+ parser.add_argument("--checkpoint-state", default=None, help="JSON semantic L4 checkpoint snapshot captured after the session.")
103
105
  return parser.parse_args()
104
106
 
105
107
 
@@ -252,6 +254,37 @@ def _load_progress_fingerprint(raw):
252
254
  return {"unparsed": str(raw)}
253
255
 
254
256
 
257
+ SEMANTIC_CHECKPOINT_FIELDS = (
258
+ "current_stage",
259
+ "next_stage",
260
+ "stage_result",
261
+ "repair_scope",
262
+ "repair_round",
263
+ "authoritative_evidence_paths",
264
+ "blocked_reason",
265
+ "terminal_status",
266
+ )
267
+
268
+
269
+ def _load_checkpoint_state(raw):
270
+ if not raw:
271
+ return {}
272
+ try:
273
+ data = json.loads(raw)
274
+ except (TypeError, ValueError):
275
+ return {}
276
+ if not isinstance(data, dict):
277
+ return {}
278
+ return {key: data.get(key) for key in SEMANTIC_CHECKPOINT_FIELDS}
279
+
280
+
281
+ def _sync_semantic_checkpoint(status_data, args):
282
+ semantic = _load_checkpoint_state(getattr(args, "checkpoint_state", None))
283
+ if semantic:
284
+ status_data.update(semantic)
285
+ return {key: status_data.get(key) for key in SEMANTIC_CHECKPOINT_FIELDS}
286
+
287
+
255
288
  def _set_continuation_common(status_data, args, session_id):
256
289
  if args.active_dev_branch:
257
290
  status_data["active_dev_branch"] = args.active_dev_branch
@@ -498,6 +531,7 @@ def action_update(args, refactor_list_path, state_dir):
498
531
  fingerprint = _load_progress_fingerprint(args.progress_fingerprint)
499
532
  if fingerprint is not None:
500
533
  rs["last_progress_fingerprint"] = fingerprint
534
+ semantic_checkpoint = _sync_semantic_checkpoint(rs, args)
501
535
 
502
536
 
503
537
  if session_status == "success":
@@ -564,6 +598,19 @@ def action_update(args, refactor_list_path, state_dir):
564
598
  if err:
565
599
  error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
566
600
  return
601
+ elif session_status == "workflow_blocked":
602
+ # Semantic terminal state: preserve partial work without consuming any retry budget.
603
+ new_status = "failed"
604
+ rs["continuation_pending"] = False
605
+ rs["needs_attention"] = True
606
+ rs["terminal_status"] = "WORKFLOW_BLOCKED"
607
+ rs["blocked_reason"] = (
608
+ semantic_checkpoint.get("blocked_reason") or "workflow_blocked"
609
+ )
610
+ err = update_refactor_in_list(refactor_list_path, refactor_id, new_status)
611
+ if err:
612
+ error_out("Failed to update .prizmkit/plans/refactor-list.json: {}".format(err))
613
+ return
567
614
  elif session_status == "stalled_context_continuation":
568
615
  # No-progress context-overflow stall pauses automation without consuming retry budgets.
569
616
  set_stalled_context_continuation_metadata(rs, args, session_id)
@@ -627,6 +674,7 @@ def action_update(args, refactor_list_path, state_dir):
627
674
  "infra_error_count": rs.get("infra_error_count", 0),
628
675
  "max_infra_retries": max_infra_retries,
629
676
  "resume_from_phase": rs.get("resume_from_phase"),
677
+ "checkpoint_state": semantic_checkpoint,
630
678
  "updated_at": rs["updated_at"],
631
679
  }
632
680
  if auto_skipped_refactors:
@@ -643,6 +691,11 @@ def action_update(args, refactor_list_path, state_dir):
643
691
  summary["restart_policy"] = "context_overflow_continuation"
644
692
  summary.update(continuation_metadata_summary(rs))
645
693
  summary["artifacts_preserved"] = True
694
+ elif session_status == "workflow_blocked":
695
+ summary["restart_policy"] = "terminal_blocked"
696
+ summary.update(continuation_metadata_summary(rs))
697
+ summary["checkpoint_state"] = semantic_checkpoint
698
+ summary["artifacts_preserved"] = True
646
699
  elif session_status == "stalled_context_continuation":
647
700
  summary["restart_policy"] = "needs_attention"
648
701
  summary.update(continuation_metadata_summary(rs))
@@ -30,9 +30,9 @@ Infer what needs to be done from the feature context above and follow the standa
30
30
 
31
31
  3. **Implement**: Run `/prizmkit-implement` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to execute the plan using TDD (write tests first, then implement).
32
32
 
33
- 4. **Review**: Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. The current Main Agent is the only Code Review executor. It reviews the complete current change for up to ten rounds, directly repairs accepted findings, verifies repairs, and converges when accepted and unresolved findings are both zero. Do not delegate review directly or indirectly, invoke another review skill/workflow, or relabel delegated work as a finder, verifier, audit, compatibility review, verification, or gap sweep. `review-report.md` is the only persisted review artifact. Require the last `## Final Result` verdict before continuing.
33
+ 4. **Review**: Run `/prizmkit-code-review` with `artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. The current Main Agent is the only Code Review executor. It reviews the complete current change for up to ten internal rounds, directly repairs accepted findings, and requires the last `## Final Result`. `REVIEW_PASS` proceeds to test; `REVIEW_NEEDS_FIXES` increments the shared outer repair round and routes implement code-review test. Do not delegate review or use another review entry point.
34
34
 
35
- 5. **Test**: Run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/` to generate and verify tests only for this feature's changed scope after review. Do not use bugfix/refactor artifact directories for this gate.
35
+ 5. **Test**: After review passes, run `/prizmkit-test scope=this-change artifact_dir=.prizmkit/specs/{{FEATURE_SLUG}}/`. Treat the Markdown report only as a derived view. Consume `.prizmkit/test/evidence/<evidence-id>/manifest.json`, `verdict.json`, and `validation.json`; verify identity/scope, every manifest hash, verdict/validator agreement, and current target hashes before accepting `TEST_PASS`. `TEST_FAIL/test-infrastructure` routes implement → test; production/runtime/schema/dependency/public-interface routes implement → code-review → test; unknown unsafe scope blocks. `TEST_BLOCKED` allows bounded environment recovery or a test-stage resume but never speculative production edits. Stop outer repairs after three rounds; preserve evidence and truthful recovery metadata.
36
36
 
37
37
  6. **Retrospective**: Run `/prizmkit-retrospective` to sync `.prizmkit/prizm-docs/` with code changes.
38
38