prizmkit 1.1.153 → 1.1.155

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 (178) hide show
  1. package/bundled/VERSION.json +3 -3
  2. package/bundled/adapters/codex/skill-adapter.js +1 -1
  3. package/bundled/adapters/pi/skill-adapter.js +2 -1
  4. package/bundled/dev-pipeline/.env.example +2 -6
  5. package/bundled/dev-pipeline/README.md +120 -113
  6. package/bundled/dev-pipeline/assets/skill-subagent-integration.md +1 -1
  7. package/bundled/dev-pipeline/prizmkit_runtime/checkpoint_state.py +355 -15
  8. package/bundled/dev-pipeline/prizmkit_runtime/cli.py +189 -110
  9. package/bundled/dev-pipeline/prizmkit_runtime/commands.py +146 -111
  10. package/bundled/dev-pipeline/prizmkit_runtime/daemon.py +4 -20
  11. package/bundled/dev-pipeline/prizmkit_runtime/gitops.py +48 -14
  12. package/bundled/dev-pipeline/prizmkit_runtime/interoperability.py +0 -1
  13. package/bundled/dev-pipeline/prizmkit_runtime/paths.py +0 -3
  14. package/bundled/dev-pipeline/prizmkit_runtime/reset.py +122 -15
  15. package/bundled/dev-pipeline/prizmkit_runtime/reset_preserve.py +1 -1
  16. package/bundled/dev-pipeline/prizmkit_runtime/runner_classification.py +50 -2
  17. package/bundled/dev-pipeline/prizmkit_runtime/runner_models.py +1 -10
  18. package/bundled/dev-pipeline/prizmkit_runtime/runner_prompts.py +3 -3
  19. package/bundled/dev-pipeline/prizmkit_runtime/runners.py +139 -74
  20. package/bundled/dev-pipeline/prizmkit_runtime/runtime_commit.py +482 -0
  21. package/bundled/dev-pipeline/prizmkit_runtime/status.py +106 -1
  22. package/bundled/dev-pipeline/prizmkit_runtime/test_result.py +64 -11
  23. package/bundled/dev-pipeline/scripts/generate-bootstrap-prompt.py +145 -367
  24. package/bundled/dev-pipeline/scripts/generate-bugfix-prompt.py +91 -62
  25. package/bundled/dev-pipeline/scripts/generate-refactor-prompt.py +105 -69
  26. package/bundled/dev-pipeline/scripts/init-bugfix-pipeline.py +2 -2
  27. package/bundled/dev-pipeline/scripts/prompt_framework.py +36 -41
  28. package/bundled/dev-pipeline/scripts/update-bug-status.py +197 -71
  29. package/bundled/dev-pipeline/scripts/update-checkpoint.py +77 -16
  30. package/bundled/dev-pipeline/scripts/update-feature-status.py +46 -121
  31. package/bundled/dev-pipeline/scripts/update-refactor-status.py +46 -123
  32. package/bundled/dev-pipeline/scripts/utils.py +119 -0
  33. package/bundled/dev-pipeline/templates/bootstrap-prompt.md +22 -27
  34. package/bundled/dev-pipeline/templates/bootstrap-tier3.md +71 -104
  35. package/bundled/dev-pipeline/templates/bug-fix-list-schema.json +3 -2
  36. package/bundled/dev-pipeline/templates/bugfix-bootstrap-prompt.md +59 -111
  37. package/bundled/dev-pipeline/templates/refactor-bootstrap-prompt.md +37 -72
  38. package/bundled/dev-pipeline/templates/sections/artifact-manifest.md +39 -0
  39. package/bundled/dev-pipeline/templates/sections/bugfix-critical-paths.md +3 -1
  40. package/bundled/dev-pipeline/templates/sections/bugfix-phase-commit-report.md +8 -25
  41. package/bundled/dev-pipeline/templates/sections/bugfix-phase-diagnose-plan.md +3 -3
  42. package/bundled/dev-pipeline/templates/sections/bugfix-phase-implement.md +2 -2
  43. package/bundled/dev-pipeline/templates/sections/bugfix-phase-review.md +3 -3
  44. package/bundled/dev-pipeline/templates/sections/bugfix-phase-test.md +2 -2
  45. package/bundled/dev-pipeline/templates/sections/bugfix-reminders.md +4 -4
  46. package/bundled/dev-pipeline/templates/sections/bugfix-session-context.md +0 -1
  47. package/bundled/dev-pipeline/templates/sections/checkpoint-system.md +7 -4
  48. package/bundled/dev-pipeline/templates/sections/context-budget-rules.md +14 -27
  49. package/bundled/dev-pipeline/templates/sections/feature-completion-summary.md +20 -0
  50. package/bundled/dev-pipeline/templates/sections/phase-commit-full.md +8 -71
  51. package/bundled/dev-pipeline/templates/sections/phase-implement-full.md +7 -7
  52. package/bundled/dev-pipeline/templates/sections/phase-prizmkit-test.md +3 -3
  53. package/bundled/dev-pipeline/templates/sections/phase-review-full.md +4 -4
  54. package/bundled/dev-pipeline/templates/sections/phase-specify-plan-full.md +5 -17
  55. package/bundled/dev-pipeline/templates/sections/refactor-critical-paths.md +3 -1
  56. package/bundled/dev-pipeline/templates/sections/refactor-phase-commit-report.md +10 -23
  57. package/bundled/dev-pipeline/templates/sections/refactor-phase-implement.md +2 -2
  58. package/bundled/dev-pipeline/templates/sections/refactor-phase-plan.md +2 -2
  59. package/bundled/dev-pipeline/templates/sections/refactor-phase-review.md +3 -3
  60. package/bundled/dev-pipeline/templates/sections/refactor-phase-test.md +2 -2
  61. package/bundled/dev-pipeline/templates/sections/refactor-reminders.md +7 -7
  62. package/bundled/dev-pipeline/templates/sections/refactor-session-context.md +0 -1
  63. package/bundled/dev-pipeline/templates/sections/retrospective.md +12 -0
  64. package/bundled/dev-pipeline/templates/sections/runtime-commit-handoff.md +35 -0
  65. package/bundled/dev-pipeline/templates/session-status-schema.json +45 -62
  66. package/bundled/dev-pipeline/tests/test_auto_skip.py +111 -32
  67. package/bundled/dev-pipeline/tests/test_checkpoint_state.py +617 -30
  68. package/bundled/dev-pipeline/tests/test_generate_bootstrap_prompt.py +262 -194
  69. package/bundled/dev-pipeline/tests/test_generate_bugfix_prompt.py +62 -23
  70. package/bundled/dev-pipeline/tests/test_generate_refactor_prompt.py +76 -28
  71. package/bundled/dev-pipeline/tests/test_python_runner_parity.py +281 -206
  72. package/bundled/dev-pipeline/tests/test_recovery_workflow.py +322 -0
  73. package/bundled/dev-pipeline/tests/test_reset_modes.py +935 -0
  74. package/bundled/dev-pipeline/tests/test_reset_preserve.py +5 -7
  75. package/bundled/dev-pipeline/tests/test_runtime_commit.py +608 -0
  76. package/bundled/dev-pipeline/tests/test_unified_cli.py +617 -188
  77. package/bundled/skills/_metadata.json +9 -9
  78. package/bundled/skills/app-planner/SKILL.md +3 -3
  79. package/bundled/skills/app-planner/references/generated-plan-review.md +2 -1
  80. package/bundled/skills/app-planner/references/rules/backend/derivation-rules.md +56 -64
  81. package/bundled/skills/app-planner/references/rules/backend/fixed-rules.md +85 -240
  82. package/bundled/skills/app-planner/references/rules/backend/question-bank.md +25 -25
  83. package/bundled/skills/app-planner/references/rules/backend/question-manifest.json +16 -7
  84. package/bundled/skills/app-planner/references/rules/backend/template.md +74 -92
  85. package/bundled/skills/app-planner/references/rules/database/derivation-rules.md +93 -93
  86. package/bundled/skills/app-planner/references/rules/database/fixed-rules.md +66 -173
  87. package/bundled/skills/app-planner/references/rules/database/question-bank.md +31 -26
  88. package/bundled/skills/app-planner/references/rules/database/question-manifest.json +13 -4
  89. package/bundled/skills/app-planner/references/rules/database/template.md +76 -98
  90. package/bundled/skills/app-planner/references/rules/frontend/derivation-rules.md +68 -95
  91. package/bundled/skills/app-planner/references/rules/frontend/fixed-rules.md +93 -166
  92. package/bundled/skills/app-planner/references/rules/frontend/question-bank.md +46 -36
  93. package/bundled/skills/app-planner/references/rules/frontend/question-manifest.json +11 -2
  94. package/bundled/skills/app-planner/references/rules/frontend/template.md +79 -257
  95. package/bundled/skills/app-planner/references/rules/mobile/derivation-rules.md +91 -99
  96. package/bundled/skills/app-planner/references/rules/mobile/fixed-rules.md +73 -246
  97. package/bundled/skills/app-planner/references/rules/mobile/question-bank.md +17 -17
  98. package/bundled/skills/app-planner/references/rules/mobile/question-manifest.json +17 -2
  99. package/bundled/skills/app-planner/references/rules/mobile/template.md +73 -85
  100. package/bundled/skills/app-planner/references/rules-configuration.md +133 -65
  101. package/bundled/skills/app-planner/scripts/validate-rules-configuration.py +291 -0
  102. package/bundled/skills/bug-planner/references/schema-validation.md +1 -1
  103. package/bundled/skills/bug-planner/scripts/validate-bug-list.py +1 -1
  104. package/bundled/skills/bugfix-pipeline-launcher/SKILL.md +21 -13
  105. package/bundled/skills/feature-pipeline-launcher/SKILL.md +22 -14
  106. package/bundled/skills/prizmkit/SKILL.md +17 -18
  107. package/bundled/skills/prizmkit/references/workflow-state-protocol.md +22 -18
  108. package/bundled/skills/prizmkit-code-review/SKILL.md +22 -58
  109. package/bundled/skills/prizmkit-code-review/references/independent-code-review.md +6 -6
  110. package/bundled/skills/prizmkit-code-review/references/review-report-template.md +2 -2
  111. package/bundled/skills/prizmkit-committer/SKILL.md +82 -132
  112. package/bundled/skills/prizmkit-deploy/SKILL.md +27 -11
  113. package/bundled/skills/prizmkit-deploy/assets/deploy-document-template.md +89 -0
  114. package/bundled/skills/prizmkit-deploy/references/ci-cd-workflows.md +44 -14
  115. package/bundled/skills/prizmkit-deploy/references/cloud-platform-deploy.md +19 -3
  116. package/bundled/skills/prizmkit-deploy/references/database-setup.md +22 -12
  117. package/bundled/skills/prizmkit-deploy/references/deploy-config-schema.md +3 -1
  118. package/bundled/skills/prizmkit-deploy/references/deploy-history-schema.md +22 -6
  119. package/bundled/skills/prizmkit-deploy/references/deploy-metadata-schema.json +49 -0
  120. package/bundled/skills/prizmkit-deploy/references/live-validation-notes.md +1 -1
  121. package/bundled/skills/prizmkit-deploy/references/pending-input-schema.json +164 -0
  122. package/bundled/skills/prizmkit-deploy/references/ssh-adapter-flow.md +9 -8
  123. package/bundled/skills/prizmkit-deploy/references/ssh-execution-flow.md +6 -6
  124. package/bundled/skills/prizmkit-implement/SKILL.md +30 -66
  125. package/bundled/skills/prizmkit-implement/references/implementation-subagent-procedure.md +4 -4
  126. package/bundled/skills/prizmkit-init/SKILL.md +4 -0
  127. package/bundled/skills/prizmkit-plan/SKILL.md +21 -64
  128. package/bundled/skills/prizmkit-plan/references/artifact-identity.md +38 -0
  129. package/bundled/skills/prizmkit-plan/references/independent-plan-review.md +4 -4
  130. package/bundled/skills/prizmkit-plan/references/review-plan-spec-loop.md +5 -5
  131. package/bundled/skills/prizmkit-prizm-docs/SKILL.md +11 -1
  132. package/bundled/skills/prizmkit-retrospective/SKILL.md +63 -77
  133. package/bundled/skills/prizmkit-retrospective/references/knowledge-injection-steps.md +3 -3
  134. package/bundled/skills/prizmkit-retrospective/references/retrospective-result-schema.json +138 -0
  135. package/bundled/skills/prizmkit-retrospective/references/structural-sync-steps.md +17 -23
  136. package/bundled/skills/prizmkit-test/SKILL.md +64 -51
  137. package/bundled/skills/prizmkit-test/references/boundary-contract-and-test-double-guidance.md +206 -0
  138. package/bundled/skills/prizmkit-test/references/independent-test-review.md +17 -9
  139. package/bundled/skills/prizmkit-test/references/test-coverage-model.md +45 -21
  140. package/bundled/skills/prizmkit-test/references/test-report-template.md +20 -13
  141. package/bundled/skills/prizmkit-workflow/SKILL.md +35 -37
  142. package/bundled/skills/prizmkit-workflow/references/artifact-identity.md +34 -0
  143. package/bundled/skills/prizmkit-workflow/references/workflow-state-protocol.md +22 -18
  144. package/bundled/skills/recovery-workflow/SKILL.md +9 -6
  145. package/bundled/skills/recovery-workflow/evals/evals.json +2 -2
  146. package/bundled/skills/recovery-workflow/references/detection.md +7 -6
  147. package/bundled/skills/recovery-workflow/scripts/detect-recovery-state.py +16 -6
  148. package/bundled/skills/refactor-pipeline-launcher/SKILL.md +21 -13
  149. package/bundled/templates/project-memory-template.md +19 -11
  150. package/package.json +1 -1
  151. package/src/scaffold.js +33 -22
  152. package/bundled/dev-pipeline/prizmkit_runtime/runner_recovery.py +0 -228
  153. package/bundled/dev-pipeline/scripts/generate-recovery-prompt.py +0 -767
  154. package/bundled/dev-pipeline/templates/sections/bugfix-artifacts.md +0 -16
  155. package/bundled/dev-pipeline/templates/sections/bugfix-session-status.md +0 -31
  156. package/bundled/dev-pipeline/templates/sections/critical-paths-agent.md +0 -7
  157. package/bundled/dev-pipeline/templates/sections/critical-paths-lite.md +0 -7
  158. package/bundled/dev-pipeline/templates/sections/directory-convention-agent.md +0 -9
  159. package/bundled/dev-pipeline/templates/sections/directory-convention-lite.md +0 -6
  160. package/bundled/dev-pipeline/templates/sections/headless-commit-authorization.md +0 -15
  161. package/bundled/dev-pipeline/templates/sections/phase-commit.md +0 -61
  162. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-agent-suffix.md +0 -24
  163. package/bundled/dev-pipeline/templates/sections/phase-context-snapshot-lite-suffix.md +0 -12
  164. package/bundled/dev-pipeline/templates/sections/phase-implement-agent.md +0 -44
  165. package/bundled/dev-pipeline/templates/sections/phase-implement-lite.md +0 -25
  166. package/bundled/dev-pipeline/templates/sections/phase-plan-agent.md +0 -20
  167. package/bundled/dev-pipeline/templates/sections/phase-plan-lite.md +0 -20
  168. package/bundled/dev-pipeline/templates/sections/phase-review-agent.md +0 -20
  169. package/bundled/dev-pipeline/templates/sections/refactor-artifacts.md +0 -17
  170. package/bundled/dev-pipeline/templates/sections/refactor-session-status.md +0 -28
  171. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-agent.md +0 -52
  172. package/bundled/dev-pipeline/templates/sections/test-failure-recovery-lite.md +0 -52
  173. package/bundled/skills/prizmkit-code-review/references/workflow-state-protocol.md +0 -174
  174. package/bundled/skills/prizmkit-committer/references/workflow-state-protocol.md +0 -174
  175. package/bundled/skills/prizmkit-implement/references/workflow-state-protocol.md +0 -174
  176. package/bundled/skills/prizmkit-plan/references/workflow-state-protocol.md +0 -174
  177. package/bundled/skills/prizmkit-retrospective/references/workflow-state-protocol.md +0 -174
  178. package/bundled/skills/prizmkit-test/references/external-contract-mock-guidance.md +0 -119
@@ -6,15 +6,19 @@ import copy
6
6
  import json
7
7
  import re
8
8
  from dataclasses import dataclass, field
9
- from pathlib import Path
9
+ from pathlib import Path, PurePosixPath
10
10
  from typing import Any, Mapping
11
11
 
12
- from .test_result import valid_test_artifact_pair
12
+ from .test_result import (
13
+ valid_legacy_test_artifact_pair,
14
+ valid_test_artifact_pair,
15
+ )
13
16
 
14
17
  VALID_CHECKPOINT_STATUSES = {"pending", "in_progress", "completed", "skipped", "failed"}
15
18
  TERMINAL_STEP_STATUSES = {"completed", "skipped"}
16
19
  STEP_ID_PATTERN = re.compile(r"^S[0-9]+$")
17
20
  MAX_OUTER_REPAIR_ROUNDS = 3
21
+ _COMMIT_HASH_PATTERN = re.compile(r"^[0-9a-fA-F]{40,64}$")
18
22
 
19
23
  REVIEW_RESULTS = {"REVIEW_PASS", "REVIEW_NEEDS_FIXES"}
20
24
  TEST_RESULTS = {"TEST_PASS", "TEST_NEEDS_FIXES", "TEST_BLOCKED"}
@@ -32,6 +36,8 @@ VALID_STAGE_RESULTS = (
32
36
  )
33
37
  STAGE_RESULT_OWNERS = {
34
38
  "prizmkit-plan": PLAN_RESULTS,
39
+ "context-snapshot-and-plan": PLAN_RESULTS,
40
+ "bug-diagnosis-and-plan": PLAN_RESULTS,
35
41
  "prizmkit-implement": IMPLEMENT_RESULTS,
36
42
  "prizmkit-code-review": REVIEW_RESULTS,
37
43
  "prizmkit-test": TEST_RESULTS,
@@ -49,6 +55,15 @@ WORKFLOW_STAGE_SKILLS = {
49
55
  SKILL_WORKFLOW_STAGES = {
50
56
  skill: stage for stage, skill in WORKFLOW_STAGE_SKILLS.items()
51
57
  }
58
+ SKILL_WORKFLOW_STAGES.update({
59
+ "context-snapshot-and-plan": "plan",
60
+ "bug-diagnosis-and-plan": "plan",
61
+ })
62
+ FAMILY_PLAN_SKILLS = {
63
+ "feature-pipeline": "context-snapshot-and-plan",
64
+ "bugfix-pipeline": "bug-diagnosis-and-plan",
65
+ "refactor-pipeline": "prizmkit-plan",
66
+ }
52
67
  VALID_TERMINAL_STATUSES = {"WORKFLOW_COMPLETED", "WORKFLOW_BLOCKED"}
53
68
  VALID_REPAIR_SCOPES = {
54
69
  "production",
@@ -63,7 +78,7 @@ SEMANTIC_STATE_KEYS = {
63
78
  "bugfix-pipeline": "bugfix_state",
64
79
  "refactor-pipeline": "refactor_state",
65
80
  }
66
- FAMILY_REQUIRED_STAGES = {
81
+ LEGACY_FAMILY_REQUIRED_STAGES = {
67
82
  "feature-pipeline": (
68
83
  "prizmkit-implement",
69
84
  "prizmkit-code-review",
@@ -91,6 +106,34 @@ FAMILY_REQUIRED_STAGES = {
91
106
  ),
92
107
  }
93
108
 
109
+ FAMILY_REQUIRED_STAGES = {
110
+ "feature-pipeline": (
111
+ "prizmkit-implement",
112
+ "prizmkit-code-review",
113
+ "prizmkit-test",
114
+ "completion-summary",
115
+ "prizmkit-retrospective",
116
+ "prizmkit-committer",
117
+ ),
118
+ "bugfix-pipeline": (
119
+ "prizmkit-implement",
120
+ "prizmkit-code-review",
121
+ "prizmkit-test",
122
+ "bug-report",
123
+ "prizmkit-retrospective",
124
+ "prizmkit-committer",
125
+ ),
126
+ "refactor-pipeline": (
127
+ "prizmkit-implement",
128
+ "prizmkit-code-review",
129
+ "prizmkit-test",
130
+ "refactor-report",
131
+ "completion-summary",
132
+ "prizmkit-retrospective",
133
+ "prizmkit-committer",
134
+ ),
135
+ }
136
+
94
137
 
95
138
  @dataclass(frozen=True)
96
139
  class CheckpointStep:
@@ -128,10 +171,13 @@ class SemanticCheckpoint:
128
171
  repair_round: int = 0
129
172
  blocked_reason: str | None = None
130
173
  terminal_status: str | None = None
174
+ runtime_base_head: str = ""
175
+ runtime_commit_hash: str = ""
131
176
  sequence_valid: bool = False
132
177
  test_result_valid: bool = False
133
178
  artifacts_valid: bool = False
134
179
  completion_ready: bool = False
180
+ commit_ready: bool = False
135
181
  complete: bool = False
136
182
  blocked: bool = False
137
183
  error_code: str = ""
@@ -147,6 +193,9 @@ class SemanticCheckpoint:
147
193
  "repair_round": self.repair_round,
148
194
  "blocked_reason": self.blocked_reason,
149
195
  "terminal_status": self.terminal_status,
196
+ "runtime_base_head": self.runtime_base_head,
197
+ "runtime_commit_hash": self.runtime_commit_hash,
198
+ "commit_ready": self.commit_ready,
150
199
  }
151
200
 
152
201
 
@@ -200,6 +249,11 @@ class CheckpointState:
200
249
  """Return whether this checkpoint authoritatively proves task completion."""
201
250
  return self.valid and self.semantic.complete
202
251
 
252
+ @property
253
+ def semantic_commit_ready(self) -> bool:
254
+ """Return whether the runtime may execute the prepared local commit."""
255
+ return self.valid and self.semantic.commit_ready
256
+
203
257
  @property
204
258
  def semantic_blocked(self) -> bool:
205
259
  """Return whether this checkpoint has a deterministic blocked terminal state."""
@@ -591,8 +645,21 @@ def _derive_semantic_state(
591
645
  return _semantic_error(workflow_type, current_step, "invalid_semantic_state", f"{state_key} must be an object")
592
646
 
593
647
  required = FAMILY_REQUIRED_STAGES.get(workflow_type)
594
- sequence_error = _required_sequence_error(steps, required) if required else "unsupported workflow family"
648
+ canonical_sequence_error = (
649
+ _required_sequence_error(steps, required)
650
+ if required else "unsupported workflow family"
651
+ )
652
+ if not canonical_sequence_error:
653
+ canonical_sequence_error = _configured_verification_sequence_error(steps)
654
+ legacy_required = LEGACY_FAMILY_REQUIRED_STAGES.get(workflow_type)
655
+ legacy_sequence_valid = bool(
656
+ canonical_sequence_error
657
+ and legacy_required
658
+ and not _required_sequence_error(steps, legacy_required)
659
+ )
660
+ sequence_error = "" if legacy_sequence_valid else canonical_sequence_error
595
661
  sequence_valid = not sequence_error
662
+ canonical_sequence_valid = not canonical_sequence_error
596
663
 
597
664
  repair_round = raw_state.get("repair_round", 0)
598
665
  if isinstance(repair_round, bool) or not isinstance(repair_round, int) or not 0 <= repair_round <= MAX_OUTER_REPAIR_ROUNDS:
@@ -629,14 +696,61 @@ def _derive_semantic_state(
629
696
  terminal_status = raw_state.get("terminal_status")
630
697
  if terminal_status is not None and terminal_status not in VALID_TERMINAL_STATUSES:
631
698
  return _semantic_error(workflow_type, current_step, "invalid_terminal_status", "terminal_status is not recognized")
699
+ runtime_base_head = str(raw_state.get("runtime_base_head") or "").strip()
700
+ runtime_commit_hash = str(raw_state.get("runtime_commit_hash") or "").strip()
701
+ runtime_receipt_valid = bool(
702
+ _COMMIT_HASH_PATTERN.fullmatch(runtime_base_head)
703
+ and _COMMIT_HASH_PATTERN.fullmatch(runtime_commit_hash)
704
+ and runtime_base_head.lower() != runtime_commit_hash.lower()
705
+ )
706
+ if (runtime_base_head or runtime_commit_hash) and not runtime_receipt_valid:
707
+ return _semantic_error(
708
+ workflow_type,
709
+ current_step,
710
+ "invalid_runtime_commit_receipt",
711
+ "Runtime commit receipt requires distinct valid base and commit hashes",
712
+ )
632
713
  blocked_reason = raw_state.get("blocked_reason")
633
714
  if blocked_reason is not None and (not isinstance(blocked_reason, str) or not blocked_reason.strip()):
634
715
  return _semantic_error(workflow_type, current_step, "invalid_blocked_reason", "blocked_reason must be a non-empty string")
635
716
  if terminal_status == "WORKFLOW_BLOCKED" and not blocked_reason:
636
717
  return _semantic_error(workflow_type, current_step, "missing_blocked_reason", "WORKFLOW_BLOCKED requires blocked_reason")
718
+ if blocked_reason and terminal_status == "WORKFLOW_COMPLETED":
719
+ return _semantic_error(
720
+ workflow_type,
721
+ current_step,
722
+ "completed_workflow_has_blocked_reason",
723
+ "WORKFLOW_COMPLETED cannot retain blocked_reason",
724
+ )
725
+ if blocked_reason and terminal_status != "WORKFLOW_BLOCKED":
726
+ return _semantic_error(
727
+ workflow_type,
728
+ current_step,
729
+ "blocked_reason_without_blocked_terminal",
730
+ "blocked_reason requires terminal_status=WORKFLOW_BLOCKED",
731
+ )
637
732
 
638
733
  review_step = _single_step(steps, "prizmkit-code-review")
639
734
  test_step = _single_step(steps, "prizmkit-test")
735
+ plan_step = _single_step(steps, FAMILY_PLAN_SKILLS.get(workflow_type, ""))
736
+ implement_step = _single_step(steps, "prizmkit-implement")
737
+ plan_valid = bool(
738
+ plan_step
739
+ and implement_step
740
+ and plan_step.index < implement_step.index
741
+ and plan_step.status == "completed"
742
+ and plan_step.stage_result == "PLAN_READY"
743
+ )
744
+ configured_manual_valid = all(
745
+ step.status == "completed"
746
+ for step in steps
747
+ if step.skill == "manual-verification"
748
+ )
749
+ implementation_valid = bool(
750
+ implement_step
751
+ and implement_step.status == "completed"
752
+ and implement_step.stage_result == "IMPLEMENTED"
753
+ )
640
754
  review_valid = bool(
641
755
  review_step
642
756
  and review_step.status == "completed"
@@ -647,18 +761,65 @@ def _derive_semantic_state(
647
761
  and test_step.status == "completed"
648
762
  and test_step.stage_result == "TEST_PASS"
649
763
  )
650
- all_required_completed = bool(required) and all(
764
+ effective_required = legacy_required if legacy_sequence_valid else required
765
+ all_required_completed = bool(effective_required) and all(
651
766
  (step := _single_step(steps, skill)) is not None and step.status == "completed"
652
- for skill in required
767
+ for skill in effective_required
653
768
  )
654
769
  artifacts_valid = all(
655
770
  _required_step_artifacts_valid(step, checkpoint_path, project_root)
656
771
  for step in steps
657
772
  if step.status == "completed"
658
773
  )
774
+ completion_artifacts_valid = artifacts_valid or (
775
+ legacy_sequence_valid
776
+ and all(
777
+ _required_step_artifacts_valid(
778
+ step,
779
+ checkpoint_path,
780
+ project_root,
781
+ allow_legacy_test=True,
782
+ )
783
+ for step in steps
784
+ if step.status == "completed"
785
+ )
786
+ )
787
+ retrospective_step = _single_step(steps, "prizmkit-retrospective")
788
+ retrospective_valid = bool(
789
+ retrospective_step
790
+ and retrospective_step.status == "completed"
791
+ and retrospective_step.stage_result == "RETRO_COMPLETE"
792
+ )
793
+ committer_step = _single_step(steps, "prizmkit-committer")
794
+ committed_valid = bool(
795
+ committer_step
796
+ and committer_step.status == "completed"
797
+ and committer_step.stage_result == "COMMITTED"
798
+ )
799
+ precommit_required = tuple(
800
+ skill for skill in (required or ()) if skill != "prizmkit-committer"
801
+ )
802
+ all_precommit_completed = bool(precommit_required) and all(
803
+ (step := _single_step(steps, skill)) is not None and step.status == "completed"
804
+ for skill in precommit_required
805
+ )
806
+ commit_request_valid = bool(
807
+ committer_step
808
+ and committer_step.status == "in_progress"
809
+ and committer_step.stage_result == "COMMIT_PENDING"
810
+ and _required_step_artifacts_valid(committer_step, checkpoint_path, project_root)
811
+ )
812
+ commit_ready = (
813
+ canonical_sequence_valid and plan_valid and configured_manual_valid
814
+ and all_precommit_completed and implementation_valid and review_valid
815
+ and test_result_valid and retrospective_valid and artifacts_valid
816
+ and commit_request_valid and terminal_status is None
817
+ )
659
818
  completion_ready = (
660
- sequence_valid and all_required_completed and review_valid
661
- and test_result_valid and artifacts_valid
819
+ sequence_valid and all_required_completed and implementation_valid
820
+ and review_valid and test_result_valid and retrospective_valid
821
+ and committed_valid and completion_artifacts_valid
822
+ and (legacy_sequence_valid or runtime_receipt_valid)
662
823
  )
663
824
  blocked = terminal_status == "WORKFLOW_BLOCKED" and bool(blocked_reason)
664
825
  complete = completion_ready and terminal_status == "WORKFLOW_COMPLETED" and not blocked
@@ -705,10 +866,13 @@ def _derive_semantic_state(
705
866
  repair_round=repair_round,
706
867
  blocked_reason=blocked_reason,
707
868
  terminal_status=terminal_status,
869
+ runtime_base_head=runtime_base_head,
870
+ runtime_commit_hash=runtime_commit_hash,
708
871
  sequence_valid=sequence_valid,
709
872
  test_result_valid=test_result_valid,
710
- artifacts_valid=artifacts_valid,
873
+ artifacts_valid=completion_artifacts_valid,
711
874
  completion_ready=completion_ready,
875
+ commit_ready=commit_ready,
712
876
  complete=complete,
713
877
  blocked=blocked,
714
878
  error_code=error_code,
@@ -733,16 +897,34 @@ def _required_sequence_error(
733
897
  return ""
734
898
 
735
899
 
900
+ def _configured_verification_sequence_error(
901
+ steps: tuple[CheckpointStep, ...],
902
+ ) -> str:
903
+ """Require configured Browser/Manual checks between Review and Test."""
904
+ review = _single_step(steps, "prizmkit-code-review")
905
+ test = _single_step(steps, "prizmkit-test")
906
+ if review is None or test is None:
907
+ return ""
908
+ for skill in ("browser-verification", "manual-verification"):
909
+ matches = [step for step in steps if step.skill == skill]
910
+ if len(matches) > 1:
911
+ return f"Expected at most one configured stage {skill}"
912
+ if matches and not (review.index < matches[0].index < test.index):
913
+ return f"Configured stage {skill} must run after review and before test"
914
+ return ""
915
+
916
+
736
917
  def _single_step(steps: tuple[CheckpointStep, ...], skill: str) -> CheckpointStep | None:
737
918
  matches = [step for step in steps if step.skill == skill]
738
919
  return matches[0] if len(matches) == 1 else None
739
920
 
740
921
 
741
-
742
922
  def _required_step_artifacts_valid(
743
923
  step: CheckpointStep,
744
924
  checkpoint_path: Path | None,
745
925
  project_root: Path | None,
926
+ *,
927
+ allow_legacy_test: bool = False,
746
928
  ) -> bool:
747
929
  """Validate declared artifacts, including semantic test terminal output."""
748
930
  if step.skill == "prizmkit-test":
@@ -755,11 +937,38 @@ def _required_step_artifacts_valid(
755
937
  result_path = (root / results[0]).resolve()
756
938
  if not _is_within_root(report_path, root) or not _is_within_root(result_path, root):
757
939
  return False
758
- return valid_test_artifact_pair(
940
+ validator = (
941
+ valid_legacy_test_artifact_pair
942
+ if allow_legacy_test
943
+ else valid_test_artifact_pair
944
+ )
945
+ return validator(
759
946
  report_path,
760
947
  result_path,
761
948
  required_result="TEST_PASS",
762
949
  )
950
+ if step.skill == "prizmkit-code-review":
951
+ return _valid_review_artifact(step, checkpoint_path, project_root)
952
+ if step.skill == "prizmkit-retrospective":
953
+ return _valid_retrospective_artifact(step, checkpoint_path, project_root)
954
+ if (
955
+ step.skill == "prizmkit-committer"
956
+ and step.status == "completed"
957
+ and step.stage_result == "COMMITTED"
958
+ ):
959
+ requests = tuple(
960
+ artifact
961
+ for artifact in step.required_artifacts
962
+ if artifact.endswith("/runtime-commit-request.json")
963
+ )
964
+ if not step.required_artifacts:
965
+ return True
966
+ if len(requests) != 1:
967
+ return False
968
+ durable = tuple(
969
+ artifact for artifact in step.required_artifacts if artifact not in requests
970
+ )
971
+ return _required_artifact_paths_exist(durable, checkpoint_path, project_root)
763
972
  return _required_artifacts_exist(step, checkpoint_path, project_root)
764
973
 
765
974
 
@@ -768,23 +977,154 @@ def _required_artifacts_exist(
768
977
  checkpoint_path: Path | None,
769
978
  project_root: Path | None,
770
979
  ) -> bool:
771
- if not step.required_artifacts:
980
+ return _required_artifact_paths_exist(
981
+ step.required_artifacts,
982
+ checkpoint_path,
983
+ project_root,
984
+ )
985
+
986
+
987
+ def _required_artifact_paths_exist(
988
+ artifacts: tuple[str, ...],
989
+ checkpoint_path: Path | None,
990
+ project_root: Path | None,
991
+ ) -> bool:
992
+ if not artifacts:
772
993
  return True
773
994
  import glob
774
995
 
775
996
  root = (project_root or _infer_project_root(checkpoint_path)).resolve()
776
- for artifact in step.required_artifacts:
997
+ for artifact in artifacts:
777
998
  candidate = (root / artifact).resolve()
778
999
  if not _is_within_root(candidate, root):
779
1000
  return False
780
1001
  if glob.has_magic(str(candidate)):
781
1002
  if not glob.glob(str(candidate)):
782
1003
  return False
783
- elif not candidate.exists():
784
- return False
1004
+ else:
1005
+ try:
1006
+ if not candidate.exists():
1007
+ return False
1008
+ if candidate.is_file() and candidate.stat().st_size == 0:
1009
+ return False
1010
+ except OSError:
1011
+ return False
785
1012
  return True
786
1013
 
787
1014
 
1015
+ def _valid_review_artifact(
1016
+ step: CheckpointStep,
1017
+ checkpoint_path: Path | None,
1018
+ project_root: Path | None,
1019
+ ) -> bool:
1020
+ reports = [item for item in step.required_artifacts if item.endswith("/review-report.md")]
1021
+ if not reports:
1022
+ return _required_artifacts_exist(step, checkpoint_path, project_root)
1023
+ if len(reports) != 1 or not _required_artifacts_exist(step, checkpoint_path, project_root):
1024
+ return False
1025
+ root = (project_root or _infer_project_root(checkpoint_path)).resolve()
1026
+ report = (root / reports[0]).resolve()
1027
+ if not _is_within_root(report, root):
1028
+ return False
1029
+ try:
1030
+ text = report.read_text(encoding="utf-8")
1031
+ except (OSError, UnicodeError):
1032
+ return False
1033
+ final_sections = re.findall(
1034
+ r"(?ms)^## Final Result\s*$\n(?P<body>.*?)(?=^## |\Z)",
1035
+ text,
1036
+ )
1037
+ if len(final_sections) != 1:
1038
+ return False
1039
+ verdicts = re.findall(r"(?m)^- Verdict:\s*(PASS|NEEDS_FIXES)\s*$", final_sections[0])
1040
+ return verdicts == ["PASS"]
1041
+
1042
+
1043
+ def _valid_retrospective_artifact(
1044
+ step: CheckpointStep,
1045
+ checkpoint_path: Path | None,
1046
+ project_root: Path | None,
1047
+ ) -> bool:
1048
+ results = [
1049
+ item for item in step.required_artifacts
1050
+ if item.endswith("/retrospective-result.json")
1051
+ ]
1052
+ if not results:
1053
+ return _required_artifacts_exist(step, checkpoint_path, project_root)
1054
+ if len(results) != 1 or not _required_artifacts_exist(step, checkpoint_path, project_root):
1055
+ return False
1056
+ root = (project_root or _infer_project_root(checkpoint_path)).resolve()
1057
+ result_path = (root / results[0]).resolve()
1058
+ if not _is_within_root(result_path, root):
1059
+ return False
1060
+ try:
1061
+ value = json.loads(result_path.read_text(encoding="utf-8"))
1062
+ except (OSError, UnicodeError, json.JSONDecodeError):
1063
+ return False
1064
+ expected_keys = {
1065
+ "schema_version",
1066
+ "outcome",
1067
+ "result",
1068
+ "reason",
1069
+ "change_paths",
1070
+ "documentation_paths",
1071
+ "validation",
1072
+ }
1073
+ return bool(
1074
+ isinstance(value, dict)
1075
+ and set(value) == expected_keys
1076
+ and value.get("schema_version") == 1
1077
+ and value.get("outcome") == "RETRO_COMPLETE"
1078
+ and value.get("result") in {"DOCS_UPDATED", "NO_DOC_CHANGE"}
1079
+ and isinstance(value.get("reason"), str)
1080
+ and value["reason"].strip()
1081
+ and isinstance(value.get("change_paths"), list)
1082
+ and all(_valid_retrospective_change_path(path) for path in value["change_paths"])
1083
+ and isinstance(value.get("documentation_paths"), list)
1084
+ and all(
1085
+ _valid_retrospective_documentation_path(path)
1086
+ for path in value["documentation_paths"]
1087
+ )
1088
+ and isinstance(value.get("validation"), list)
1089
+ and all(isinstance(item, str) and item.strip() for item in value["validation"])
1090
+ and (
1091
+ value.get("result") != "DOCS_UPDATED"
1092
+ or (
1093
+ bool(value["documentation_paths"])
1094
+ and bool(value["validation"])
1095
+ )
1096
+ )
1097
+ )
1098
+
1099
+
1100
+ def _valid_retrospective_documentation_path(value: object) -> bool:
1101
+ if not isinstance(value, str) or not value.strip() or "\\" in value:
1102
+ return False
1103
+ raw_parts = value.split("/")
1104
+ if any(part in {"", ".", ".."} for part in raw_parts):
1105
+ return False
1106
+ path = PurePosixPath(value)
1107
+ return bool(
1108
+ not path.is_absolute()
1109
+ and len(raw_parts) >= 3
1110
+ and raw_parts[:2] == [".prizmkit", "prizm-docs"]
1111
+ and path.suffix == ".prizm"
1112
+ )
1113
+
1114
+
1115
+ def _valid_retrospective_change_path(value: object) -> bool:
1116
+ if not isinstance(value, str) or not value.strip() or "\\" in value:
1117
+ return False
1118
+ raw_parts = value.split("/")
1119
+ if any(part in {"", ".", ".."} for part in raw_parts):
1120
+ return False
1121
+ path = PurePosixPath(value)
1122
+ return bool(
1123
+ not path.is_absolute()
1124
+ and raw_parts[0] != ".prizmkit"
1125
+ )
1126
+
1127
+
788
1128
  def _is_within_root(path: Path, root: Path) -> bool:
789
1129
  try:
790
1130
  path.relative_to(root)