devlyn-cli 3.2.1 → 3.3.0

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 (423) hide show
  1. package/AGENTS.md +2 -2
  2. package/CLAUDE.md +2 -2
  3. package/README.md +12 -102
  4. package/bin/devlyn.js +13 -225
  5. package/bin/instruction-templates.json +6 -0
  6. package/config/skills/_shared/adapters/grok.md +1 -2
  7. package/config/skills/_shared/archive_run.py +15 -3
  8. package/config/skills/_shared/judge-role-evidence.py +21 -1
  9. package/config/skills/_shared/runtime-principles.md +1 -1
  10. package/config/skills/_shared/spec-verify-check.py +0 -653
  11. package/config/skills/_shared/state-phase-write.py +351 -25
  12. package/config/skills/_shared/task-complete.py +635 -143
  13. package/config/skills/_shared/terminal-claim-check.py +229 -4
  14. package/config/skills/_shared/verify-merge-findings.py +93 -2
  15. package/config/skills/devlyn:ideate/SKILL.md +3 -4
  16. package/config/skills/devlyn:ideate/references/elicitation.md +2 -35
  17. package/config/skills/devlyn:ideate/references/from-spec-mode.md +3 -18
  18. package/config/skills/devlyn:ideate/references/project-mode.md +1 -16
  19. package/config/skills/devlyn:ideate/references/spec-template.md +0 -2
  20. package/config/skills/devlyn:queue/SKILL.md +6 -4
  21. package/config/skills/devlyn:resolve/SKILL.md +16 -25
  22. package/config/skills/devlyn:resolve/references/free-form-mode.md +0 -14
  23. package/config/skills/devlyn:resolve/references/outer-loop.md +1 -1
  24. package/config/skills/devlyn:resolve/references/phases/build-gate.md +1 -1
  25. package/config/skills/devlyn:resolve/references/phases/cleanup.md +3 -3
  26. package/config/skills/devlyn:resolve/references/phases/implement.md +1 -1
  27. package/config/skills/devlyn:resolve/references/phases/probe-derive.md +1 -16
  28. package/config/skills/devlyn:resolve/references/phases/verify.md +4 -9
  29. package/config/skills/devlyn:resolve/references/state-schema.md +9 -7
  30. package/config/skills/devlyn:resolve/references/task-completion.md +46 -37
  31. package/{config/skills → optional-skills}/code-health-standards/SKILL.md +4 -13
  32. package/{config/skills → optional-skills}/code-review-standards/SKILL.md +4 -12
  33. package/{config/skills → optional-skills}/root-cause-analysis/SKILL.md +4 -12
  34. package/{config/skills → optional-skills}/ui-implementation-standards/SKILL.md +4 -14
  35. package/package.json +1 -57
  36. package/benchmark/auto-resolve/BENCHMARK-DESIGN.md +0 -289
  37. package/benchmark/auto-resolve/BENCHMARK-RESULTS.md +0 -341
  38. package/benchmark/auto-resolve/README.md +0 -696
  39. package/benchmark/auto-resolve/RUBRIC.md +0 -177
  40. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/NOTES.md +0 -34
  41. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/expected.json +0 -68
  42. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/metadata.json +0 -10
  43. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/setup.sh +0 -4
  44. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/spec.md +0 -44
  45. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.ko.txt +0 -8
  46. package/benchmark/auto-resolve/fixtures/F1-cli-trivial-flag/task.txt +0 -8
  47. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/NOTES.md +0 -68
  48. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/expected.json +0 -60
  49. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/metadata.json +0 -10
  50. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/setup.sh +0 -17
  51. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/spec.md +0 -51
  52. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/task.txt +0 -9
  53. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/verifiers/invalid.js +0 -29
  54. package/benchmark/auto-resolve/fixtures/F10-persist-write-collision/verifiers/parallel.js +0 -50
  55. package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/NOTES.md +0 -75
  56. package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/expected.json +0 -52
  57. package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/metadata.json +0 -10
  58. package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/setup.sh +0 -171
  59. package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/spec.md +0 -50
  60. package/benchmark/auto-resolve/fixtures/F11-batch-import-all-or-nothing/task.txt +0 -9
  61. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/NOTES.md +0 -89
  62. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/expected.json +0 -74
  63. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/metadata.json +0 -10
  64. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/setup.sh +0 -251
  65. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/spec.md +0 -57
  66. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/task.txt +0 -13
  67. package/benchmark/auto-resolve/fixtures/F12-webhook-raw-body-signature/verifiers/replay-malformed-body.js +0 -64
  68. package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/NOTES.md +0 -104
  69. package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/expected.json +0 -46
  70. package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/metadata.json +0 -10
  71. package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/setup.sh +0 -336
  72. package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/spec.md +0 -51
  73. package/benchmark/auto-resolve/fixtures/F15-frozen-diff-race-review/task.txt +0 -9
  74. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/NOTES.md +0 -38
  75. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/expected.json +0 -64
  76. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/metadata.json +0 -10
  77. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/setup.sh +0 -32
  78. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/spec.md +0 -63
  79. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/task.txt +0 -7
  80. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/verifiers/exact-success.js +0 -54
  81. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/verifiers/no-hardcoded-pricing.js +0 -47
  82. package/benchmark/auto-resolve/fixtures/F16-cli-quote-tax-rules/verifiers/stock-error.js +0 -45
  83. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/NOTES.md +0 -57
  84. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/expected-pair-plan-registry.json +0 -170
  85. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/expected.json +0 -84
  86. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/metadata.json +0 -21
  87. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/pair-plan.sample-fail.json +0 -214
  88. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/pair-plan.sample-pass.json +0 -223
  89. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/setup.sh +0 -5
  90. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/spec.md +0 -55
  91. package/benchmark/auto-resolve/fixtures/F2-cli-medium-subcommand/task.txt +0 -14
  92. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/NOTES.md +0 -39
  93. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/expected.json +0 -62
  94. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/metadata.json +0 -10
  95. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/setup.sh +0 -2
  96. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/spec.md +0 -67
  97. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/task.txt +0 -7
  98. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/verifiers/error-order.js +0 -55
  99. package/benchmark/auto-resolve/fixtures/F21-cli-scheduler-priority/verifiers/priority-blocked.js +0 -48
  100. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/NOTES.md +0 -35
  101. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/expected.json +0 -56
  102. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/metadata.json +0 -10
  103. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/setup.sh +0 -2
  104. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/spec.md +0 -64
  105. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/task.txt +0 -7
  106. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/verifiers/conflicting-duplicate.js +0 -34
  107. package/benchmark/auto-resolve/fixtures/F22-cli-ledger-close/verifiers/idempotent-close.js +0 -41
  108. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/NOTES.md +0 -39
  109. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/expected.json +0 -56
  110. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/metadata.json +0 -10
  111. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/setup.sh +0 -2
  112. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/spec.md +0 -76
  113. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/task.txt +0 -7
  114. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/verifiers/priority-rollback.js +0 -64
  115. package/benchmark/auto-resolve/fixtures/F23-cli-fulfillment-wave/verifiers/single-warehouse-fefo.js +0 -66
  116. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/NOTES.md +0 -40
  117. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/expected.json +0 -66
  118. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/metadata.json +0 -10
  119. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/setup.sh +0 -36
  120. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/spec.md +0 -71
  121. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/task.txt +0 -7
  122. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/verifiers/catalog-source.js +0 -57
  123. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/verifiers/exact-success.js +0 -63
  124. package/benchmark/auto-resolve/fixtures/F25-cli-cart-promotion-rules/verifiers/stock-error.js +0 -34
  125. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/NOTES.md +0 -31
  126. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/expected.json +0 -68
  127. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/metadata.json +0 -10
  128. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/setup.sh +0 -17
  129. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/spec.md +0 -68
  130. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/task.txt +0 -7
  131. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/verifiers/conflicting-duplicate.js +0 -29
  132. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/verifiers/exact-payout.js +0 -58
  133. package/benchmark/auto-resolve/fixtures/F26-cli-payout-ledger-rules/verifiers/rules-source.js +0 -56
  134. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/NOTES.md +0 -35
  135. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/expected-pair-plan-registry.json +0 -162
  136. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/expected.json +0 -67
  137. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/metadata.json +0 -19
  138. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/setup.sh +0 -4
  139. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/spec.md +0 -55
  140. package/benchmark/auto-resolve/fixtures/F3-backend-contract-risk/task.txt +0 -9
  141. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/NOTES.md +0 -34
  142. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/expected.json +0 -57
  143. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/metadata.json +0 -10
  144. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/setup.sh +0 -2
  145. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/spec.md +0 -67
  146. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/task.txt +0 -7
  147. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/verifiers/duplicate-event-error.js +0 -35
  148. package/benchmark/auto-resolve/fixtures/F31-cli-seat-rebalance/verifiers/priority-transfer-rollback.js +0 -53
  149. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/NOTES.md +0 -38
  150. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/expected.json +0 -57
  151. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/metadata.json +0 -10
  152. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/setup.sh +0 -2
  153. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/spec.md +0 -70
  154. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/task.txt +0 -3
  155. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/verifiers/duplicate-renewal-error.js +0 -42
  156. package/benchmark/auto-resolve/fixtures/F32-cli-subscription-renewal/verifiers/priority-credit-rollback.js +0 -70
  157. package/benchmark/auto-resolve/fixtures/F4-web-browser-design/NOTES.md +0 -47
  158. package/benchmark/auto-resolve/fixtures/F4-web-browser-design/expected.json +0 -57
  159. package/benchmark/auto-resolve/fixtures/F4-web-browser-design/metadata.json +0 -10
  160. package/benchmark/auto-resolve/fixtures/F4-web-browser-design/setup.sh +0 -6
  161. package/benchmark/auto-resolve/fixtures/F4-web-browser-design/spec.md +0 -48
  162. package/benchmark/auto-resolve/fixtures/F4-web-browser-design/task.txt +0 -9
  163. package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/NOTES.md +0 -45
  164. package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/expected.json +0 -65
  165. package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/metadata.json +0 -10
  166. package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/setup.sh +0 -55
  167. package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/spec.md +0 -48
  168. package/benchmark/auto-resolve/fixtures/F5-fix-loop-red-green/task.txt +0 -7
  169. package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/NOTES.md +0 -43
  170. package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/expected.json +0 -77
  171. package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/metadata.json +0 -10
  172. package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/setup.sh +0 -4
  173. package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/spec.md +0 -48
  174. package/benchmark/auto-resolve/fixtures/F6-dep-audit-native-module/task.txt +0 -10
  175. package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/NOTES.md +0 -57
  176. package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/expected.json +0 -76
  177. package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/metadata.json +0 -10
  178. package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/setup.sh +0 -36
  179. package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/spec.md +0 -45
  180. package/benchmark/auto-resolve/fixtures/F7-out-of-scope-trap/task.txt +0 -7
  181. package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/NOTES.md +0 -53
  182. package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/expected.json +0 -63
  183. package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/metadata.json +0 -10
  184. package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/setup.sh +0 -4
  185. package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/spec.md +0 -48
  186. package/benchmark/auto-resolve/fixtures/F8-known-limit-ambiguous/task.txt +0 -1
  187. package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/NOTES.md +0 -105
  188. package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/expected.json +0 -74
  189. package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/metadata.json +0 -10
  190. package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/setup.sh +0 -28
  191. package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/spec.md +0 -59
  192. package/benchmark/auto-resolve/fixtures/F9-e2e-ideate-to-resolve/task.txt +0 -5
  193. package/benchmark/auto-resolve/fixtures/SCHEMA.md +0 -188
  194. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/NOTES.md +0 -37
  195. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/RETIRED.md +0 -13
  196. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/expected.json +0 -56
  197. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/metadata.json +0 -10
  198. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/setup.sh +0 -18
  199. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/spec.md +0 -69
  200. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/task.txt +0 -7
  201. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/verifiers/exact-proration.js +0 -48
  202. package/benchmark/auto-resolve/fixtures/retired/F27-cli-subscription-proration/verifiers/rules-source-and-conflict.js +0 -79
  203. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/NOTES.md +0 -54
  204. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/RETIRED.md +0 -7
  205. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/expected.json +0 -67
  206. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/metadata.json +0 -10
  207. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/setup.sh +0 -2
  208. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/spec.md +0 -67
  209. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/task.txt +0 -5
  210. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/verifiers/policy-precedence.js +0 -72
  211. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/verifiers/validation-and-immutability.js +0 -43
  212. package/benchmark/auto-resolve/fixtures/retired/F28-cli-return-authorization/verifiers/validation-boundary.js +0 -116
  213. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/NOTES.md +0 -35
  214. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/RETIRED.md +0 -12
  215. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/expected.json +0 -58
  216. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/metadata.json +0 -10
  217. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/setup.sh +0 -2
  218. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/spec.md +0 -73
  219. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/task.txt +0 -17
  220. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/verifiers/mixed-idempotent-settlement.js +0 -53
  221. package/benchmark/auto-resolve/fixtures/retired/F30-cli-credit-hold-settlement/verifiers/rejection-boundaries.js +0 -74
  222. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/NOTES.md +0 -28
  223. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/RETIRED.md +0 -15
  224. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/expected.json +0 -57
  225. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/metadata.json +0 -10
  226. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/setup.sh +0 -50
  227. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/spec.md +0 -59
  228. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/task.txt +0 -9
  229. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/ordered-settlement.js +0 -88
  230. package/benchmark/auto-resolve/fixtures/retired/F34-cli-settle-inflight/verifiers/reject-invalid.js +0 -45
  231. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/NOTES.md +0 -30
  232. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/RETIRED.md +0 -16
  233. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/expected.json +0 -64
  234. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/metadata.json +0 -10
  235. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/setup.sh +0 -21
  236. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/spec.md +0 -58
  237. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/task.txt +0 -9
  238. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/dup-op.js +0 -54
  239. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/priority-apply.js +0 -82
  240. package/benchmark/auto-resolve/fixtures/retired/F35-cli-apply-journal/verifiers/rollback.js +0 -77
  241. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/NOTES.md +0 -99
  242. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/RETIRED.md +0 -18
  243. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/expected.json +0 -64
  244. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/metadata.json +0 -10
  245. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/setup.sh +0 -4
  246. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/spec.md +0 -61
  247. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/task.txt +0 -9
  248. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/correctness-small.js +0 -135
  249. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/scale.js +0 -100
  250. package/benchmark/auto-resolve/fixtures/retired/F36-cli-session-admission/verifiers/validation.js +0 -75
  251. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/NOTES.md +0 -93
  252. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/RETIRED.md +0 -16
  253. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/expected.json +0 -65
  254. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/metadata.json +0 -10
  255. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/setup.sh +0 -18
  256. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/spec.md +0 -66
  257. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/task.txt +0 -9
  258. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/correctness-small.js +0 -84
  259. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/scale.js +0 -184
  260. package/benchmark/auto-resolve/fixtures/retired/F37-cli-rule-lookup/verifiers/validation.js +0 -74
  261. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/NOTES.md +0 -60
  262. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/RETIRED.md +0 -29
  263. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/expected.json +0 -73
  264. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/metadata.json +0 -10
  265. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/setup.sh +0 -28
  266. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/spec.md +0 -58
  267. package/benchmark/auto-resolve/fixtures/retired/F9-e2e-ideate-to-preflight/task.txt +0 -5
  268. package/benchmark/auto-resolve/fixtures/test-repo/README.md +0 -12
  269. package/benchmark/auto-resolve/fixtures/test-repo/bin/cli.js +0 -63
  270. package/benchmark/auto-resolve/fixtures/test-repo/package-lock.json +0 -823
  271. package/benchmark/auto-resolve/fixtures/test-repo/package.json +0 -22
  272. package/benchmark/auto-resolve/fixtures/test-repo/playwright.config.js +0 -17
  273. package/benchmark/auto-resolve/fixtures/test-repo/server/index.js +0 -37
  274. package/benchmark/auto-resolve/fixtures/test-repo/tests/cli.test.js +0 -25
  275. package/benchmark/auto-resolve/fixtures/test-repo/tests/server.test.js +0 -58
  276. package/benchmark/auto-resolve/fixtures/test-repo/web/index.html +0 -37
  277. package/benchmark/auto-resolve/results/20260507-f10-f11-tier1-full-pipeline/headroom-gate.json +0 -25
  278. package/benchmark/auto-resolve/results/20260507-f10-f11-tier1-full-pipeline/headroom-gate.md +0 -10
  279. package/benchmark/auto-resolve/results/20260508-f22-exact-error-headroom/headroom-gate.json +0 -18
  280. package/benchmark/auto-resolve/results/20260508-f22-exact-error-headroom/headroom-gate.md +0 -9
  281. package/benchmark/auto-resolve/results/20260508-f26-headroom/headroom-gate.json +0 -18
  282. package/benchmark/auto-resolve/results/20260508-f26-headroom/headroom-gate.md +0 -9
  283. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/F16-cli-quote-tax-rules/l2_risk_probes/result.json +0 -28
  284. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/F23-cli-fulfillment-wave/l2_risk_probes/result.json +0 -28
  285. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/F25-cli-cart-promotion-rules/l2_risk_probes/result.json +0 -30
  286. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/full-pipeline-pair-gate.json +0 -82
  287. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/full-pipeline-pair-gate.md +0 -18
  288. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/headroom-gate.json +0 -46
  289. package/benchmark/auto-resolve/results/20260510-f16-f23-f25-combined-proof/headroom-gate.md +0 -17
  290. package/benchmark/auto-resolve/results/20260511-f12-webhook-headroom/headroom-gate.json +0 -28
  291. package/benchmark/auto-resolve/results/20260511-f12-webhook-headroom/headroom-gate.md +0 -15
  292. package/benchmark/auto-resolve/results/20260511-f15-concurrency-headroom/headroom-gate.json +0 -28
  293. package/benchmark/auto-resolve/results/20260511-f15-concurrency-headroom/headroom-gate.md +0 -15
  294. package/benchmark/auto-resolve/results/20260511-f21-current-riskprobes-v1/F21-cli-scheduler-priority/l2_risk_probes/result.json +0 -32
  295. package/benchmark/auto-resolve/results/20260511-f21-current-riskprobes-v1/full-pipeline-pair-gate.json +0 -44
  296. package/benchmark/auto-resolve/results/20260511-f21-current-riskprobes-v1/full-pipeline-pair-gate.md +0 -16
  297. package/benchmark/auto-resolve/results/20260511-f21-current-riskprobes-v1/headroom-gate.json +0 -18
  298. package/benchmark/auto-resolve/results/20260511-f21-current-riskprobes-v1/headroom-gate.md +0 -9
  299. package/benchmark/auto-resolve/results/20260511-f3-http-error-headroom/headroom-gate.json +0 -28
  300. package/benchmark/auto-resolve/results/20260511-f3-http-error-headroom/headroom-gate.md +0 -15
  301. package/benchmark/auto-resolve/results/20260512-f2-medium-headroom/headroom-gate.json +0 -28
  302. package/benchmark/auto-resolve/results/20260512-f2-medium-headroom/headroom-gate.md +0 -15
  303. package/benchmark/auto-resolve/results/20260512-f31-seat-rebalance-headroom/headroom-gate.json +0 -28
  304. package/benchmark/auto-resolve/results/20260512-f31-seat-rebalance-headroom/headroom-gate.md +0 -15
  305. package/benchmark/auto-resolve/results/20260512-f32-subscription-renewal-headroom/headroom-gate.json +0 -28
  306. package/benchmark/auto-resolve/results/20260512-f32-subscription-renewal-headroom/headroom-gate.md +0 -15
  307. package/benchmark/auto-resolve/results/20260512-f4-web-headroom/headroom-gate.json +0 -28
  308. package/benchmark/auto-resolve/results/20260512-f4-web-headroom/headroom-gate.md +0 -15
  309. package/benchmark/auto-resolve/results/20260512-f5-fixloop-headroom/headroom-gate.json +0 -28
  310. package/benchmark/auto-resolve/results/20260512-f5-fixloop-headroom/headroom-gate.md +0 -15
  311. package/benchmark/auto-resolve/results/20260512-f6-checksum-headroom/headroom-gate.json +0 -28
  312. package/benchmark/auto-resolve/results/20260512-f6-checksum-headroom/headroom-gate.md +0 -15
  313. package/benchmark/auto-resolve/results/20260512-f7-scope-headroom/headroom-gate.json +0 -28
  314. package/benchmark/auto-resolve/results/20260512-f7-scope-headroom/headroom-gate.md +0 -15
  315. package/benchmark/auto-resolve/results/20260512-f9-e2e-headroom/headroom-gate.json +0 -28
  316. package/benchmark/auto-resolve/results/20260512-f9-e2e-headroom/headroom-gate.md +0 -15
  317. package/benchmark/auto-resolve/run-real-benchmark.md +0 -303
  318. package/benchmark/auto-resolve/scripts/audit-headroom-rejections.py +0 -441
  319. package/benchmark/auto-resolve/scripts/audit-pair-evidence.py +0 -1256
  320. package/benchmark/auto-resolve/scripts/build-pair-eligible-manifest.py +0 -306
  321. package/benchmark/auto-resolve/scripts/check-f9-artifacts.py +0 -268
  322. package/benchmark/auto-resolve/scripts/collect-swebench-predictions.py +0 -108
  323. package/benchmark/auto-resolve/scripts/compile-report.py +0 -493
  324. package/benchmark/auto-resolve/scripts/fetch-swebench-instances.py +0 -129
  325. package/benchmark/auto-resolve/scripts/frozen-verify-gate.py +0 -434
  326. package/benchmark/auto-resolve/scripts/full-pipeline-pair-gate.py +0 -612
  327. package/benchmark/auto-resolve/scripts/headroom-gate.py +0 -378
  328. package/benchmark/auto-resolve/scripts/iter-0033c-compare.py +0 -683
  329. package/benchmark/auto-resolve/scripts/iter-0033c-l1-summary.py +0 -97
  330. package/benchmark/auto-resolve/scripts/judge-opus-pass.sh +0 -542
  331. package/benchmark/auto-resolve/scripts/judge.sh +0 -565
  332. package/benchmark/auto-resolve/scripts/oracle-scope-tier-a.py +0 -256
  333. package/benchmark/auto-resolve/scripts/oracle-scope-tier-b.py +0 -287
  334. package/benchmark/auto-resolve/scripts/oracle-test-fidelity.py +0 -328
  335. package/benchmark/auto-resolve/scripts/pair-candidate-frontier.py +0 -469
  336. package/benchmark/auto-resolve/scripts/pair-plan-idgen.py +0 -401
  337. package/benchmark/auto-resolve/scripts/pair-plan-lint.py +0 -475
  338. package/benchmark/auto-resolve/scripts/pair-rejected-fixtures.sh +0 -103
  339. package/benchmark/auto-resolve/scripts/pair_evidence_contract.py +0 -270
  340. package/benchmark/auto-resolve/scripts/prepare-swebench-frozen-case.py +0 -273
  341. package/benchmark/auto-resolve/scripts/prepare-swebench-frozen-corpus.py +0 -148
  342. package/benchmark/auto-resolve/scripts/prepare-swebench-solver-worktree.py +0 -210
  343. package/benchmark/auto-resolve/scripts/recent-benchmark-summary.py +0 -232
  344. package/benchmark/auto-resolve/scripts/run-fixture.sh +0 -1024
  345. package/benchmark/auto-resolve/scripts/run-frozen-verify-pair.sh +0 -683
  346. package/benchmark/auto-resolve/scripts/run-full-pipeline-pair-candidate.sh +0 -458
  347. package/benchmark/auto-resolve/scripts/run-headroom-candidate.sh +0 -336
  348. package/benchmark/auto-resolve/scripts/run-iter-0033c.sh +0 -208
  349. package/benchmark/auto-resolve/scripts/run-suite.sh +0 -251
  350. package/benchmark/auto-resolve/scripts/run-swebench-frozen-corpus.sh +0 -310
  351. package/benchmark/auto-resolve/scripts/run-swebench-solver-batch.sh +0 -257
  352. package/benchmark/auto-resolve/scripts/ship-gate.py +0 -391
  353. package/benchmark/auto-resolve/scripts/solo-ceiling-avoidance.py +0 -53
  354. package/benchmark/auto-resolve/scripts/solo-headroom-hypothesis.py +0 -77
  355. package/benchmark/auto-resolve/scripts/swebench-frozen-matrix.py +0 -478
  356. package/benchmark/auto-resolve/scripts/test-audit-headroom-rejections.sh +0 -288
  357. package/benchmark/auto-resolve/scripts/test-audit-pair-evidence.sh +0 -1672
  358. package/benchmark/auto-resolve/scripts/test-benchmark-arg-parsing.sh +0 -1015
  359. package/benchmark/auto-resolve/scripts/test-build-pair-eligible-manifest.sh +0 -491
  360. package/benchmark/auto-resolve/scripts/test-check-f9-artifacts.sh +0 -91
  361. package/benchmark/auto-resolve/scripts/test-frozen-verify-gate.sh +0 -517
  362. package/benchmark/auto-resolve/scripts/test-full-pipeline-pair-gate.sh +0 -610
  363. package/benchmark/auto-resolve/scripts/test-headroom-gate.sh +0 -401
  364. package/benchmark/auto-resolve/scripts/test-iter-0033c-compare.sh +0 -525
  365. package/benchmark/auto-resolve/scripts/test-iter-0033c-l1-summary.sh +0 -254
  366. package/benchmark/auto-resolve/scripts/test-lint-fixtures.sh +0 -580
  367. package/benchmark/auto-resolve/scripts/test-pair-candidate-frontier.sh +0 -591
  368. package/benchmark/auto-resolve/scripts/test-run-full-pipeline-pair-candidate.sh +0 -497
  369. package/benchmark/auto-resolve/scripts/test-run-headroom-candidate.sh +0 -401
  370. package/benchmark/auto-resolve/scripts/test-run-swebench-solver-batch.sh +0 -111
  371. package/benchmark/auto-resolve/scripts/test-ship-gate.sh +0 -1189
  372. package/benchmark/auto-resolve/scripts/test-swebench-frozen-case.sh +0 -1221
  373. package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/NOTES.md +0 -28
  374. package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/expected.json +0 -63
  375. package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/metadata.json +0 -10
  376. package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/setup.sh +0 -3
  377. package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/spec.md +0 -47
  378. package/benchmark/auto-resolve/shadow-fixtures/S1-cli-lang-flag/task.txt +0 -1
  379. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/NOTES.md +0 -34
  380. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/expected.json +0 -53
  381. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/metadata.json +0 -10
  382. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/setup.sh +0 -3
  383. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/spec.md +0 -50
  384. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/task.txt +0 -1
  385. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/verifiers/duplicate-order-error.js +0 -27
  386. package/benchmark/auto-resolve/shadow-fixtures/S2-cli-inventory-reservation/verifiers/priority-stock-reservation.js +0 -44
  387. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/NOTES.md +0 -34
  388. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/expected.json +0 -55
  389. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/metadata.json +0 -10
  390. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/setup.sh +0 -3
  391. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/spec.md +0 -52
  392. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/task.txt +0 -1
  393. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/verifiers/duplicate-ticket-error.js +0 -29
  394. package/benchmark/auto-resolve/shadow-fixtures/S3-cli-ticket-assignment/verifiers/priority-agent-assignment.js +0 -48
  395. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/NOTES.md +0 -34
  396. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/expected.json +0 -55
  397. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/metadata.json +0 -10
  398. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/setup.sh +0 -3
  399. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/spec.md +0 -55
  400. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/task.txt +0 -1
  401. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/verifiers/duplicate-return-error.js +0 -43
  402. package/benchmark/auto-resolve/shadow-fixtures/S4-cli-return-routing/verifiers/priority-return-routing.js +0 -70
  403. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/NOTES.md +0 -37
  404. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/expected.json +0 -54
  405. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/metadata.json +0 -10
  406. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/setup.sh +0 -3
  407. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/spec.md +0 -59
  408. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/task.txt +0 -1
  409. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/verifiers/credit-ledger-priority.js +0 -98
  410. package/benchmark/auto-resolve/shadow-fixtures/S5-cli-credit-grant-ledger/verifiers/duplicate-charge-error.js +0 -38
  411. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/NOTES.md +0 -36
  412. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/expected.json +0 -56
  413. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/metadata.json +0 -10
  414. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/setup.sh +0 -3
  415. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/spec.md +0 -59
  416. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/task.txt +0 -1
  417. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/verifiers/duplicate-refund-error.js +0 -41
  418. package/benchmark/auto-resolve/shadow-fixtures/S6-cli-refund-window-ledger/verifiers/priority-refund-ledger.js +0 -65
  419. package/config/skills/_shared/pair-plan-schema.md +0 -298
  420. package/scripts/lint-fixtures.sh +0 -352
  421. package/scripts/lint-shadow-fixtures.sh +0 -58
  422. package/scripts/lint-skills.sh +0 -4265
  423. package/scripts/test-owner-phases.py +0 -238
@@ -39,6 +39,7 @@ import sys
39
39
  import tempfile
40
40
 
41
41
  VALID_VERDICTS = {"PASS", "PASS_WITH_ISSUES", "FAIL", "NEEDS_WORK", "BLOCKED"}
42
+ FINAL_VERDICTS = {"PASS", "PASS_WITH_ISSUES", "NEEDS_WORK", "BLOCKED"}
42
43
  VALID_TRIGGERS = {"build_gate", "cleanup", "verify"}
43
44
  SPAWN_TRIGGERS = VALID_TRIGGERS | {"plan"}
44
45
  PHASE_NAMES = {"plan", "probe_derive", "implement", "surface_close", "build_gate", "cleanup", "verify", "final_report"}
@@ -1682,8 +1683,109 @@ def freeze_roles(state: dict, work: pathlib.Path, default_engine: str) -> dict:
1682
1683
  return resolved
1683
1684
 
1684
1685
 
1686
+ REPAIR_PHASES = ("implement", "build_gate", "cleanup", "verify")
1687
+
1688
+
1689
+ def valid_phase_gate_progress(progress: object) -> bool:
1690
+ if not isinstance(progress, dict):
1691
+ return False
1692
+ total, current, statuses = progress.get("total"), progress.get("current"), progress.get("statuses")
1693
+ return (type(total) is int and total > 1 and type(current) is int and 1 <= current <= total
1694
+ and isinstance(statuses, list) and len(statuses) == total
1695
+ and all(status in ("PASS", "FAIL", None) for status in statuses)
1696
+ and all(status == "PASS" for status in statuses[:current - 1]))
1697
+
1698
+
1699
+ def repair_predecessor(state: dict) -> tuple[str, dict] | None:
1700
+ """Select the most recent started repair boundary by invocation identity."""
1701
+ phases = state.get("phases", {})
1702
+ selected = None
1703
+ for order, name in enumerate(REPAIR_PHASES):
1704
+ entry = phases.get(name) if isinstance(phases, dict) else None
1705
+ if not isinstance(entry, dict) or entry.get("started_at") is None:
1706
+ continue
1707
+ round_ = entry.get("round")
1708
+ if type(round_) is not int or round_ < 0:
1709
+ raise SystemExit("BLOCKED:repair-edge-invalid")
1710
+ if selected is None or (round_, order) > selected[0]:
1711
+ selected = ((round_, order), name, entry)
1712
+ return None if selected is None else (selected[1], selected[2])
1713
+
1714
+
1715
+ def repair_admission(state: dict, phase: str, round_: int,
1716
+ triggered_by: str | None, source_phase: str | None = None) -> None:
1717
+ if phase not in REPAIR_PHASES:
1718
+ return
1719
+ predecessor = repair_predecessor(state)
1720
+ if phase == "implement":
1721
+ if predecessor is None:
1722
+ if triggered_by not in {None, "plan"}:
1723
+ raise SystemExit("BLOCKED:repair-trigger-mismatch: expected=null supplied=" + triggered_by)
1724
+ expected_round, origin = 0, None
1725
+ else:
1726
+ prior_name, prior = predecessor
1727
+ if prior.get("completed_at") is None or (source_phase is not None and prior_name != source_phase):
1728
+ raise SystemExit("BLOCKED:repair-edge-invalid")
1729
+ verdict = prior.get("verdict")
1730
+ origin = None
1731
+ if prior_name in {"build_gate", "cleanup", "verify"}:
1732
+ valid = (verdict == "NEEDS_WORK" and isinstance(prior.get("merged"), dict)
1733
+ and prior["merged"].get("verdict") == "NEEDS_WORK") if prior_name == "verify" else verdict == "FAIL"
1734
+ if prior_name == "cleanup":
1735
+ valid = valid and orchestrator_phase(prior, "cleanup")
1736
+ if not valid:
1737
+ raise SystemExit("BLOCKED:repair-edge-invalid")
1738
+ origin = prior_name
1739
+ elif prior_name == "implement":
1740
+ progress = prior.get("exec")
1741
+ if not valid_phase_gate_progress(progress):
1742
+ raise SystemExit("BLOCKED:repair-edge-invalid")
1743
+ current, statuses = progress["current"], progress["statuses"]
1744
+ status = statuses[current - 1]
1745
+ if verdict == "FAIL" and status == "FAIL":
1746
+ origin = "phase_gate"
1747
+ elif verdict not in {"PASS", "PASS_WITH_ISSUES"} or current < 2 or status is not None:
1748
+ raise SystemExit("BLOCKED:repair-edge-invalid")
1749
+ else:
1750
+ raise SystemExit("BLOCKED:repair-edge-invalid")
1751
+ expected_round = prior["round"] + 1
1752
+ expected_trigger = origin if origin in VALID_TRIGGERS else None
1753
+ if triggered_by != expected_trigger:
1754
+ supplied = triggered_by if triggered_by is not None else "null"
1755
+ expected = expected_trigger if expected_trigger is not None else "null"
1756
+ raise SystemExit(f"BLOCKED:repair-trigger-mismatch: expected={expected} supplied={supplied}")
1757
+ if round_ != expected_round:
1758
+ raise SystemExit(f"BLOCKED:implement-round-nonmonotonic: expected={expected_round} supplied={round_}")
1759
+ if origin is not None:
1760
+ rounds = state.get("rounds")
1761
+ if (not isinstance(rounds, dict) or type(rounds.get("global")) is not int
1762
+ or rounds["global"] < 0 or type(rounds.get("max_rounds")) is not int
1763
+ or rounds["max_rounds"] < 1):
1764
+ raise SystemExit("BLOCKED:rounds-malformed")
1765
+ if rounds["global"] >= rounds["max_rounds"]:
1766
+ raise SystemExit(
1767
+ f"BLOCKED:repair-budget-exhausted: global={rounds['global']} "
1768
+ f"max_rounds={rounds['max_rounds']} origin={origin}"
1769
+ )
1770
+ rounds["global"] += 1
1771
+ elif phase in {"build_gate", "cleanup", "verify"}:
1772
+ implement = (state.get("phases") or {}).get("implement")
1773
+ expected_round = implement.get("round", 0) if isinstance(implement, dict) and implement.get("started_at") else 0
1774
+ if round_ != expected_round:
1775
+ raise SystemExit(f"BLOCKED:phase-round-mismatch: phase={phase} expected={expected_round} supplied={round_}")
1776
+
1777
+
1778
+ def validate_verdict(phase: str, verdict: str | None) -> None:
1779
+ allowed = (verdict in FINAL_VERDICTS or (
1780
+ isinstance(verdict, str) and verdict.startswith("BLOCKED:") and len(verdict) > 8
1781
+ )) if phase == "final_report" else verdict in VALID_VERDICTS
1782
+ if verdict is not None and not allowed:
1783
+ raise SystemExit(f"error: invalid verdict for phases.{phase}: {verdict}")
1784
+
1785
+
1685
1786
  def do_spawn(state: dict, phase: str, round_: int, triggered_by: str | None,
1686
1787
  pre_sha: str | None, engine: str | None, model: str | None, *,
1788
+ source_phase: str | None = None,
1687
1789
  input_patch_sha256: str | None = None,
1688
1790
  prompt_sha256: str | None = None,
1689
1791
  untracked_before: list[str] | None = None,
@@ -1804,6 +1906,7 @@ def do_spawn(state: dict, phase: str, round_: int, triggered_by: str | None,
1804
1906
  raise SystemExit(
1805
1907
  f"error: phases.{phase} has an open span — complete it before respawn"
1806
1908
  )
1909
+ repair_admission(state, phase, round_, triggered_by, source_phase)
1807
1910
  append_phase_history(entry, phase)
1808
1911
  if phase in WORKER_SESSION_ARTIFACT_PHASES:
1809
1912
  entry.pop("invocation_receipt", None)
@@ -1812,7 +1915,10 @@ def do_spawn(state: dict, phase: str, round_: int, triggered_by: str | None,
1812
1915
  entry["completed_at"] = None
1813
1916
  entry["duration_ms"] = None
1814
1917
  entry["round"] = round_
1815
- entry["triggered_by"] = triggered_by
1918
+ entry["triggered_by"] = (
1919
+ "cleanup" if phase == "implement" and triggered_by is None
1920
+ and entry.get("triggered_by") == "cleanup" else triggered_by
1921
+ )
1816
1922
  entry["verdict"] = None
1817
1923
  entry["artifacts"] = {"findings_file": None, "log_file": None}
1818
1924
  entry["sub_verdicts"] = None
@@ -1881,6 +1987,7 @@ def do_complete(state: dict, phase: str, verdict: str | None,
1881
1987
  entry = phases.get(phase)
1882
1988
  if not isinstance(entry, dict) or not entry.get("started_at"):
1883
1989
  raise SystemExit(f"error: phases.{phase} was never spawned (no started_at) — cannot complete")
1990
+ validate_verdict(phase, verdict)
1884
1991
  if phase == "verify" and verdict is not None:
1885
1992
  raise SystemExit(
1886
1993
  "error: phases.verify.verdict is owned by verify-merge-findings.py "
@@ -1954,6 +2061,13 @@ def do_complete(state: dict, phase: str, verdict: str | None,
1954
2061
  entry["verdict"] = verdict
1955
2062
  else:
1956
2063
  raise SystemExit(f"error: --verdict is required to complete phases.{phase}")
2064
+ if phase == "implement" and verdict == "FAIL" and isinstance(entry.get("exec"), dict):
2065
+ progress = entry["exec"]
2066
+ total, current, statuses = progress.get("total"), progress.get("current"), progress.get("statuses")
2067
+ if (type(total) is not int or total <= 1 or type(current) is not int
2068
+ or not 1 <= current <= total or not isinstance(statuses, list) or len(statuses) != total):
2069
+ raise SystemExit("BLOCKED:repair-edge-invalid")
2070
+ statuses[current - 1] = "FAIL"
1957
2071
  if phase == "plan":
1958
2072
  if state.get("version") == "3.0":
1959
2073
  bind_plan_output(state, devlyn)
@@ -2104,7 +2218,9 @@ def do_transition(
2104
2218
  """
2105
2219
  if next_phase not in LEGAL_TRANSITIONS.get(phase, set()):
2106
2220
  raise SystemExit(f"error: illegal phase transition: {phase} -> {next_phase}")
2107
- if phase == "implement" and state.get("phases", {}).get(phase, {}).get("triggered_by") == "cleanup":
2221
+ validate_verdict(phase, verdict)
2222
+ if (phase == "implement" and next_phase != "implement"
2223
+ and state.get("phases", {}).get(phase, {}).get("triggered_by") == "cleanup"):
2108
2224
  bypasses = state.get("bypasses", [])
2109
2225
  required = "build_gate" if "build-gate" not in bypasses else (
2110
2226
  "cleanup" if "cleanup" not in bypasses else "verify"
@@ -2131,6 +2247,7 @@ def do_transition(
2131
2247
  do_spawn(
2132
2248
  candidate, next_phase, next_round, next_triggered_by,
2133
2249
  next_pre_sha, next_engine, next_model,
2250
+ source_phase=phase,
2134
2251
  input_patch_sha256=next_input_patch_sha256,
2135
2252
  prompt_sha256=next_prompt_sha256,
2136
2253
  untracked_before=next_untracked_before,
@@ -2209,6 +2326,14 @@ def final_report_self_test() -> None:
2209
2326
  assert result.returncode == 1 and "BLOCKED:final-report-invalid" in result.stderr, (case, result)
2210
2327
  assert state_path.read_bytes() == before, case
2211
2328
  report.write_bytes(valid)
2329
+ for invalid in ("FAIL", "BLOCKED:", "unknown"):
2330
+ result = subprocess.run(command + ["complete", "--verdict", invalid,
2331
+ "--log-file", ".devlyn/final-report.md"],
2332
+ cwd=work, capture_output=True, text=True, encoding="utf-8")
2333
+ assert result.returncode == 1 and result.stderr.strip() == (
2334
+ f"error: invalid verdict for phases.final_report: {invalid}"
2335
+ ), result.stderr
2336
+ assert state_path.read_bytes() == before
2212
2337
  result = subprocess.run(command + ["complete", "--verdict", "PASS_WITH_ISSUES", "--log-file", ".devlyn/final-report.md"],
2213
2338
  cwd=work, capture_output=True, text=True, encoding="utf-8")
2214
2339
  assert result.returncode == 0, result.stderr
@@ -2254,12 +2379,142 @@ def final_report_self_test() -> None:
2254
2379
  assert (target / "pipeline.state.json").read_bytes() == completed_bytes
2255
2380
  assert not state_path.exists() and not report.exists()
2256
2381
  assert outside.read_bytes() == valid
2257
- print("PASS iter-0126 final report: 11 invalid CLI completions preserve state; exact binding/archive; 6 archive refusals preserve artifacts")
2382
+ print("PASS iter-0126 final report: 14 invalid CLI completions preserve state; exact binding/archive; 6 archive refusals preserve artifacts")
2383
+
2384
+
2385
+ def repair_admission_self_test() -> None:
2386
+ stamp = "2026-01-01T00:00:00.000Z"
2387
+ origins = {
2388
+ "build_gate": ({"started_at": stamp, "completed_at": stamp, "round": 0, "verdict": "FAIL"}, "build_gate"),
2389
+ "cleanup": ({"started_at": stamp, "completed_at": stamp, "round": 0, "verdict": "FAIL",
2390
+ "execution_kind": "orchestrator_commands"}, "cleanup"),
2391
+ "verify": ({"started_at": stamp, "completed_at": stamp, "round": 0,
2392
+ "verdict": "NEEDS_WORK", "merged": {"verdict": "NEEDS_WORK"}}, "verify"),
2393
+ "phase_gate": ({"started_at": stamp, "completed_at": stamp, "round": 0, "verdict": "FAIL",
2394
+ "exec": {"total": 2, "current": 1, "statuses": ["FAIL", None]}}, None),
2395
+ }
2396
+ for origin, (entry, trigger) in origins.items():
2397
+ source = "implement" if origin == "phase_gate" else origin
2398
+ initial = {"phases": {source: entry}, "rounds": {"global": 0, "max_rounds": 1}}
2399
+ for wrong in ((None, "plan", "verify") if trigger else ("plan", "verify")):
2400
+ if wrong == trigger:
2401
+ continue
2402
+ candidate = copy.deepcopy(initial)
2403
+ try:
2404
+ do_spawn(candidate, "implement", 1, wrong, None, None, None)
2405
+ except SystemExit as exc:
2406
+ assert "repair-trigger-mismatch" in str(exc), (origin, wrong, exc)
2407
+ else:
2408
+ raise AssertionError((origin, wrong))
2409
+ for malformed in ({"global": True, "max_rounds": 1}, {"global": -1, "max_rounds": 1},
2410
+ {"global": 0, "max_rounds": True}, {"global": 0, "max_rounds": 0}, None):
2411
+ candidate = copy.deepcopy(initial)
2412
+ candidate["rounds"] = malformed
2413
+ try:
2414
+ do_spawn(candidate, "implement", 1, trigger, None, None, None)
2415
+ except SystemExit as exc:
2416
+ assert str(exc) == "BLOCKED:rounds-malformed"
2417
+ else:
2418
+ raise AssertionError((origin, malformed))
2419
+ candidate = copy.deepcopy(initial)
2420
+ try:
2421
+ do_spawn(candidate, "implement", 2, trigger, None, None, None)
2422
+ except SystemExit as exc:
2423
+ assert "implement-round-nonmonotonic" in str(exc)
2424
+ else:
2425
+ raise AssertionError((origin, "skipped round"))
2426
+ candidate = copy.deepcopy(initial)
2427
+ do_spawn(candidate, "implement", 1, trigger, None, None, None)
2428
+ assert candidate["rounds"]["global"] == 1, origin
2429
+ try:
2430
+ do_spawn(candidate, "implement", 1, trigger, None, None, None)
2431
+ except SystemExit:
2432
+ pass
2433
+ else:
2434
+ raise AssertionError((origin, "duplicate admission"))
2435
+ exhausted = copy.deepcopy(initial)
2436
+ exhausted["rounds"]["global"] = 1
2437
+ try:
2438
+ do_spawn(exhausted, "implement", 1, trigger, None, None, None)
2439
+ except SystemExit as exc:
2440
+ assert str(exc) == f"BLOCKED:repair-budget-exhausted: global=1 max_rounds=1 origin={origin}"
2441
+ else:
2442
+ raise AssertionError((origin, "shared exhaustion"))
2443
+ phased = {"rounds": {"global": 0, "max_rounds": 1}, "phases": {}}
2444
+ do_spawn(phased, "implement", 0, None, None, None, None)
2445
+ phased["phases"]["implement"].update({"completed_at": stamp, "verdict": "PASS",
2446
+ "exec": {"total": 2, "current": 2, "statuses": ["PASS", None]}})
2447
+ do_spawn(phased, "implement", 1, None, None, None, None)
2448
+ assert phased["rounds"]["global"] == 0
2449
+ do_complete(phased, "implement", "FAIL", None, None, None, None, None)
2450
+ assert phased["phases"]["implement"]["exec"]["statuses"] == ["PASS", "FAIL"]
2451
+ do_spawn(phased, "implement", 2, None, None, None, None)
2452
+ assert phased["rounds"]["global"] == 1
2453
+ do_complete(phased, "implement", "PASS", None, None, None, None, None)
2454
+ do_spawn(phased, "verify", 2, None, None, None, None)
2455
+ with tempfile.TemporaryDirectory() as tmp:
2456
+ transition_state = {"rounds": {"global": 0, "max_rounds": 1}, "phases": {}}
2457
+ do_spawn(transition_state, "implement", 0, None, None, None, None)
2458
+ transition_state["phases"]["implement"]["exec"] = {
2459
+ "total": 2, "current": 1, "statuses": [None, None],
2460
+ }
2461
+ transitioned = do_transition(
2462
+ transition_state, "implement", "implement", "FAIL", None, None, None,
2463
+ None, None, None, pathlib.Path(tmp), 1, None, None, None, None,
2464
+ )
2465
+ assert transition_state["rounds"]["global"] == 0
2466
+ assert transitioned["rounds"]["global"] == 1
2467
+ assert transitioned["phases"]["implement"]["exec"]["statuses"][0] == "FAIL"
2468
+ print("PASS repair admission: four origins, trigger/counter/round refusal, phased invocation and last repair")
2469
+
2470
+
2471
+ def cleanup_origin_phase_gate_reentry_self_test() -> None:
2472
+ with tempfile.TemporaryDirectory() as tmp:
2473
+ stamp = "2026-01-01T00:00:00.000Z"
2474
+ state = {"version": "3.0", "rounds": {"global": 1, "max_rounds": 4}, "phases": {
2475
+ "cleanup": {"started_at": stamp, "completed_at": stamp, "round": 0,
2476
+ "verdict": "FAIL", "execution_kind": "orchestrator_commands"},
2477
+ "implement": {"started_at": stamp, "completed_at": None, "round": 1,
2478
+ "triggered_by": "cleanup", "verdict": None,
2479
+ "exec": {"total": 2, "current": 1, "statuses": [None, None]}},
2480
+ }}
2481
+ try:
2482
+ do_transition(
2483
+ state, "implement", "verify", "PASS", None, None, None,
2484
+ None, None, None, pathlib.Path(tmp), 1, None, None, None, None,
2485
+ )
2486
+ except SystemExit as exc:
2487
+ assert str(exc) == "BLOCKED:owner-cleanup-repair-route: next phase must be build_gate", exc
2488
+ else:
2489
+ raise AssertionError("cleanup repair bypassed BUILD_GATE")
2490
+ transitioned = do_transition(
2491
+ state, "implement", "implement", "FAIL", None, None, None,
2492
+ None, None, None, pathlib.Path(tmp), 2, None, None, None, None,
2493
+ )
2494
+ assert transitioned["rounds"]["global"] == 2
2495
+ assert transitioned["phases"]["implement"]["exec"]["statuses"] == ["FAIL", None]
2496
+ assert transitioned["phases"]["implement"]["triggered_by"] == "cleanup"
2497
+ try:
2498
+ do_transition(
2499
+ transitioned, "implement", "verify", "PASS", None, None, None,
2500
+ None, None, None, pathlib.Path(tmp), 2, None, None, None, None,
2501
+ )
2502
+ except SystemExit as exc:
2503
+ assert str(exc) == "BLOCKED:owner-cleanup-repair-route: next phase must be build_gate", exc
2504
+ else:
2505
+ raise AssertionError("phase-gate retry bypassed BUILD_GATE")
2506
+ spawned = copy.deepcopy(state)
2507
+ do_complete(spawned, "implement", "FAIL", None, None, None, None, None)
2508
+ do_spawn(spawned, "implement", 2, None, None, None, None)
2509
+ assert spawned["rounds"]["global"] == 2
2510
+ assert spawned["phases"]["implement"]["triggered_by"] == "cleanup"
2258
2511
 
2259
2512
 
2260
2513
  def self_test() -> int:
2261
2514
  import time
2262
2515
 
2516
+ repair_admission_self_test()
2517
+ cleanup_origin_phase_gate_reentry_self_test()
2263
2518
  final_report_self_test()
2264
2519
  with tempfile.TemporaryDirectory() as tmp:
2265
2520
  work = pathlib.Path(tmp); devlyn = work / ".devlyn"; devlyn.mkdir()
@@ -2535,7 +2790,7 @@ def self_test() -> int:
2535
2790
  assert archived_legacy == old_receipt
2536
2791
  print("PASS iter-0089 PLAN ledger: P-0089-1/2/6")
2537
2792
 
2538
- write_state(state_path, {"phases": {}})
2793
+ write_state(state_path, {"phases": {}, "rounds": {"global": 0, "max_rounds": 4}})
2539
2794
 
2540
2795
  # Round 0: spawn -> complete.
2541
2796
  state = read_state(state_path)
@@ -2555,9 +2810,14 @@ def self_test() -> int:
2555
2810
  expected_ms = round((parse_iso(entry["completed_at"]) - parse_iso(entry["started_at"])).total_seconds() * 1000)
2556
2811
  assert entry["duration_ms"] == expected_ms, (entry["duration_ms"], expected_ms)
2557
2812
 
2558
- # Round 1: fix-loop respawn — this is the literal iter-0042 regression.
2813
+ # Round 1: VERIFY finding admits a fix-loop respawn.
2559
2814
  time.sleep(0.05)
2560
2815
  state = read_state(state_path)
2816
+ do_spawn(state, "verify", 0, None, None, None, None)
2817
+ state["phases"]["verify"].update({
2818
+ "completed_at": now_iso(), "verdict": "NEEDS_WORK",
2819
+ "merged": {"verdict": "NEEDS_WORK"},
2820
+ })
2561
2821
  do_spawn(state, "implement", 1, "verify", None, None, None)
2562
2822
  write_state(state_path, state)
2563
2823
  respawned = read_state(state_path)["phases"]["implement"]
@@ -2596,6 +2856,10 @@ def self_test() -> int:
2596
2856
  evidence_spec_dir = evidence_work / "docs" / "evidence"
2597
2857
  evidence_devlyn.mkdir(parents=True)
2598
2858
  evidence_spec_dir.mkdir(parents=True)
2859
+ subprocess.run(["git", "init", "-q"], cwd=evidence_work, check=True)
2860
+ subprocess.run(["git", "config", "user.name", "Evidence test"], cwd=evidence_work, check=True)
2861
+ subprocess.run(["git", "config", "user.email", "evidence@example.invalid"], cwd=evidence_work, check=True)
2862
+ (evidence_work / ".gitignore").write_text(".devlyn/\n", encoding="utf-8")
2599
2863
  (evidence_spec_dir / "spec.md").write_text("# Evidence fixture\n", encoding="utf-8")
2600
2864
  (evidence_spec_dir / "spec.expected.json").write_text(json.dumps({
2601
2865
  "verification_commands": [{"cmd": "printf build-gate"}],
@@ -2607,9 +2871,12 @@ def self_test() -> int:
2607
2871
  "stdout_contains": ["red-before-fix"],
2608
2872
  }],
2609
2873
  }) + "\n", encoding="utf-8")
2874
+ subprocess.run(["git", "add", "."], cwd=evidence_work, check=True)
2875
+ subprocess.run(["git", "commit", "-qm", "fixture"], cwd=evidence_work, check=True)
2610
2876
  evidence_state = {
2611
2877
  "run_id": "rs-state-evidence",
2612
2878
  "source": {"type": "spec", "spec_path": "docs/evidence/spec.md"},
2879
+ "rounds": {"global": 0, "max_rounds": 4},
2613
2880
  "phases": {"implement": None, "build_gate": None},
2614
2881
  }
2615
2882
  do_spawn(evidence_state, "implement", 0, None, None, "codex", None)
@@ -2736,11 +3003,28 @@ def self_test() -> int:
2736
3003
  return build_carrier
2737
3004
 
2738
3005
  build_carrier_0 = write_build_gate_results()
2739
- completed = build_gate_state_cli("complete", "--verdict", "PASS")
3006
+ completed = build_gate_state_cli("complete", "--verdict", "FAIL")
2740
3007
  assert completed.returncode == 0, completed.stderr
2741
3008
  completed_state = read_state(evidence_state_path)
2742
3009
  assert completed_state["process_evidence"] == [carrier, build_carrier_0]
2743
3010
 
3011
+ repaired = evidence_state_cli("spawn", "--round", "1", "--triggered-by", "build_gate")
3012
+ assert repaired.returncode == 0, repaired.stderr
3013
+ captured = subprocess.run(
3014
+ [sys.executable, runner_script, "--devlyn-dir", ".devlyn", "run",
3015
+ "--phase", "implement", "--id", "red-first"],
3016
+ cwd=evidence_work, capture_output=True, text=True, encoding="utf-8",
3017
+ )
3018
+ assert captured.returncode == 0, captured.stderr
3019
+ completed = evidence_state_cli("complete", "--verdict", "PASS")
3020
+ assert completed.returncode == 0, completed.stderr
3021
+ implement_carrier_1 = read_state(evidence_state_path)["process_evidence"][-1]
3022
+ subprocess.run(["git", "commit", "--allow-empty", "-qm", "chore(pipeline): implement fix round 1"],
3023
+ cwd=evidence_work, check=True)
3024
+ (evidence_devlyn / "build_gate.findings.jsonl").write_text("", encoding="utf-8")
3025
+ durable = evidence_state_cli("durability-enforce", "--round", "1", "--origin-phase", "build_gate")
3026
+ assert durable.returncode == 0, durable.stderr
3027
+
2744
3028
  spawned = build_gate_state_cli(
2745
3029
  "spawn", "--round", "1", "--triggered-by", "build_gate",
2746
3030
  )
@@ -2748,12 +3032,12 @@ def self_test() -> int:
2748
3032
  build_carrier_1 = write_build_gate_results()
2749
3033
  transitioned = build_gate_state_cli(
2750
3034
  "transition", "--verdict", "PASS", "--next-phase", "cleanup",
2751
- "--next-round", "0", "--next-pre-sha", "test-pre-sha",
3035
+ "--next-round", "1", "--next-pre-sha", "test-pre-sha",
2752
3036
  )
2753
3037
  assert transitioned.returncode == 0, transitioned.stderr
2754
3038
  build_bound_state = read_state(evidence_state_path)
2755
3039
  assert build_bound_state["process_evidence"] == [
2756
- carrier, build_carrier_0, build_carrier_1,
3040
+ carrier, build_carrier_0, implement_carrier_1, build_carrier_1,
2757
3041
  ]
2758
3042
  assert build_bound_state["phases"]["cleanup"]["started_at"] is not None
2759
3043
  print("PASS iter-0111 BUILD_GATE evidence: completion/transition state binding")
@@ -2762,6 +3046,7 @@ def self_test() -> int:
2762
3046
  # bytes only, so a failed entry or capability denial could still be completed
2763
3047
  # with caller-supplied PASS.
2764
3048
  build_bound_state["phases"]["build_gate"] = None
3049
+ build_bound_state["phases"]["implement"]["round"] = 2
2765
3050
  do_spawn(build_bound_state, "build_gate", 2, "build_gate", None, None, None)
2766
3051
  write_state(evidence_state_path, build_bound_state)
2767
3052
  mismatch = runner.normalize_obligation({
@@ -2807,6 +3092,7 @@ def self_test() -> int:
2807
3092
  assert failed.returncode == 0, failed.stderr
2808
3093
 
2809
3094
  failed_state = read_state(evidence_state_path)
3095
+ failed_state["phases"]["implement"]["round"] = 3
2810
3096
  do_spawn(failed_state, "build_gate", 3, "build_gate", None, None, None)
2811
3097
  write_state(evidence_state_path, failed_state)
2812
3098
  denied = runner.normalize_obligation({
@@ -2919,7 +3205,7 @@ def self_test() -> int:
2919
3205
  completed = read_state(state_file)["phases"]["build_gate"]
2920
3206
  command_identity(completed)
2921
3207
  assert completed["verdict"] == "PASS" and completed["completed_at"]
2922
- result = cli("build_gate", "spawn", "--round", "1")
3208
+ result = cli("build_gate", "spawn", "--round", "0")
2923
3209
  assert result.returncode == 0, result.stderr
2924
3210
  reentered = read_state(state_file)["phases"]["build_gate"]
2925
3211
  command_identity(reentered)
@@ -2968,6 +3254,11 @@ def self_test() -> int:
2968
3254
  old = legacy_done["phases"]["build_gate"]
2969
3255
  assert "execution_kind" not in old and old["model_effective"] is None
2970
3256
  assert old["invocation_receipt"]["sandbox_network_access"] is True
3257
+ legacy_done["phases"]["implement"] = {
3258
+ "started_at": now_iso(), "completed_at": now_iso(), "round": 1,
3259
+ "verdict": "PASS",
3260
+ }
3261
+ write_state(state_file, legacy_done)
2971
3262
  result = cli("build_gate", "spawn", "--round", "1")
2972
3263
  assert result.returncode == 0, result.stderr
2973
3264
  fresh = read_state(state_file)["phases"]["build_gate"]
@@ -3251,6 +3542,10 @@ def self_test() -> int:
3251
3542
  print("PASS iter-0119 nonzero receipt persists BLOCKED before exit1; transition stays atomic")
3252
3543
 
3253
3544
  candidate = read_state(state_file)
3545
+ candidate["phases"]["implement"] = {
3546
+ "started_at": now_iso(), "completed_at": now_iso(), "round": 1,
3547
+ "verdict": "PASS",
3548
+ }
3254
3549
  do_spawn(candidate, "build_gate", 1, None, None, None, None)
3255
3550
  write_state(state_file, candidate)
3256
3551
  next_relative = runner.manifest_relative_path(candidate, "build_gate")
@@ -3272,6 +3567,10 @@ def self_test() -> int:
3272
3567
  # A post-spec denial refresh must preserve earlier failed checks.
3273
3568
  refresh_state = copy.deepcopy(fixture)
3274
3569
  refresh_state["phases"]["build_gate"] = None
3570
+ refresh_state["phases"]["implement"] = {
3571
+ "started_at": now_iso(), "completed_at": now_iso(), "round": 2,
3572
+ "verdict": "PASS",
3573
+ }
3275
3574
  do_spawn(refresh_state, "build_gate", 2, None, None, None, None, devlyn=active)
3276
3575
  write_state(state_file, refresh_state)
3277
3576
  refresh_relative = runner.manifest_relative_path(refresh_state, "build_gate")
@@ -3321,7 +3620,7 @@ def self_test() -> int:
3321
3620
  active.mkdir(parents=True)
3322
3621
  state_file = active / "pipeline.state.json"
3323
3622
  results_file = active / "spec-verify.results.json"
3324
- checker = pathlib.Path(__file__).with_name("spec-verify-check.py")
3623
+ checker = pathlib.Path(__file__).resolve().with_name("spec-verify-check.py")
3325
3624
  env = {key: value for key, value in os.environ.items() if key not in {
3326
3625
  "BENCH_WORKDIR", "SPEC_VERIFY_PHASE", "SPEC_VERIFY_FINDINGS_FILE",
3327
3626
  "SPEC_VERIFY_FINDING_PREFIX",
@@ -3341,7 +3640,10 @@ def self_test() -> int:
3341
3640
  fixture = {
3342
3641
  "version": "3.0", "run_id": "rs-preflight-build-gate",
3343
3642
  "source": {"type": "generated", "criteria_path": ".devlyn/criteria.generated.md"},
3344
- "phases": {"build_gate": None}, "process_evidence": copy.deepcopy(history) or None,
3643
+ "phases": {"build_gate": None, "implement": {
3644
+ "started_at": now_iso(), "completed_at": now_iso(),
3645
+ "round": round_, "verdict": "PASS",
3646
+ }}, "process_evidence": copy.deepcopy(history) or None,
3345
3647
  }
3346
3648
  do_spawn(fixture, "build_gate", round_, None, None, None, None)
3347
3649
  write_state(state_file, fixture)
@@ -3623,7 +3925,7 @@ def self_test() -> int:
3623
3925
  assert state_path.read_bytes() == open_next_before
3624
3926
  print("PASS self-test transition open-span guard: rejected without mutation")
3625
3927
 
3626
- # A completed FAIL round must be retained before a fix-loop respawn
3928
+ # A completed FAIL span must be retained before a same-invocation respawn
3627
3929
  # resets the live record.
3628
3930
  write_state(state_path, {"phases": {}})
3629
3931
  state = read_state(state_path)
@@ -3635,7 +3937,7 @@ def self_test() -> int:
3635
3937
  write_state(state_path, state)
3636
3938
  failed_round = read_state(state_path)["phases"]["build_gate"]
3637
3939
  state = read_state(state_path)
3638
- do_spawn(state, "build_gate", 1, "build_gate", None, None, None)
3940
+ do_spawn(state, "build_gate", 0, None, None, None, None)
3639
3941
  write_state(state_path, state)
3640
3942
  respawned_fail = read_state(state_path)["phases"]["build_gate"]
3641
3943
  assert respawned_fail["verdict"] is None
@@ -3671,7 +3973,7 @@ def self_test() -> int:
3671
3973
  assert "owned by verify-merge-findings.py" in str(e)
3672
3974
  else:
3673
3975
  raise AssertionError("complete() must reject an explicit --verdict for VERIFY")
3674
- do_spawn(state, "verify", 1, "verify", None, None, None)
3976
+ do_spawn(state, "verify", 0, None, None, None, None)
3675
3977
  assert state["phases"]["verify"]["judge_durations_ms"] is None
3676
3978
 
3677
3979
  # Non-VERIFY phases require --verdict explicitly; complete() must not
@@ -3740,21 +4042,21 @@ def self_test() -> int:
3740
4042
  assert (devlyn / "codex-primary-judge.prompt.md").exists(), "judge prompts must survive VERIFY spawn"
3741
4043
  assert (devlyn / "spec-verify.json").exists(), "non-VERIFY-round files must survive"
3742
4044
 
3743
- # Fix-loop respawn of phase-gated IMPLEMENT must preserve `exec`
4045
+ # Normal phase-gated IMPLEMENT advancement must preserve `exec`
3744
4046
  # (routing truth for large runs, state-schema.md line 55) — spawn
3745
4047
  # merges into the existing entry rather than replacing it wholesale.
3746
4048
  write_state(state_path, {"phases": {}})
3747
4049
  state = read_state(state_path)
3748
4050
  do_spawn(state, "implement", 0, None, None, "claude", None)
3749
4051
  state["phases"]["implement"]["exec"] = {
3750
- "total": 3, "current": 3, "statuses": ["PASS", "PASS", "PASS"], "commits": ["a", "b", "c"],
4052
+ "total": 3, "current": 3, "statuses": ["PASS", "PASS", None], "commits": ["a", "b", "c"],
3751
4053
  }
3752
4054
  write_state(state_path, state)
3753
4055
  state = read_state(state_path)
3754
4056
  do_complete(state, "implement", "PASS", None, None, None, None, None)
3755
4057
  write_state(state_path, state)
3756
4058
  state = read_state(state_path)
3757
- do_spawn(state, "implement", 1, "verify", None, None, None)
4059
+ do_spawn(state, "implement", 1, None, None, None, None)
3758
4060
  write_state(state_path, state)
3759
4061
  respawned_exec = read_state(state_path)["phases"]["implement"]
3760
4062
  assert respawned_exec["exec"]["current"] == 3, "spawn must not clobber unowned fields like exec"
@@ -3765,6 +4067,7 @@ def self_test() -> int:
3765
4067
  # Existing history is append-only; a respawn must not clobber prior
3766
4068
  # entries that were already preserved from older rounds.
3767
4069
  write_state(state_path, {
4070
+ "rounds": {"global": 0, "max_rounds": 4},
3768
4071
  "phases": {
3769
4072
  "implement": {
3770
4073
  "started_at": "2026-01-01T00:00:02.000Z",
@@ -3773,6 +4076,7 @@ def self_test() -> int:
3773
4076
  "round": 2,
3774
4077
  "triggered_by": "verify",
3775
4078
  "verdict": "FAIL",
4079
+ "exec": {"total": 2, "current": 2, "statuses": ["PASS", "FAIL"]},
3776
4080
  "engine": "codex",
3777
4081
  "history": [{
3778
4082
  "started_at": "2026-01-01T00:00:00.000Z",
@@ -3784,7 +4088,7 @@ def self_test() -> int:
3784
4088
  }
3785
4089
  })
3786
4090
  state = read_state(state_path)
3787
- do_spawn(state, "implement", 3, "verify", None, None, None)
4091
+ do_spawn(state, "implement", 3, None, None, None, None)
3788
4092
  write_state(state_path, state)
3789
4093
  history_preserved = read_state(state_path)["phases"]["implement"]["history"]
3790
4094
  assert len(history_preserved) == 2
@@ -4670,9 +4974,13 @@ def self_test() -> int:
4670
4974
  )
4671
4975
  assert reroute_error and "model reroute" in reroute_error
4672
4976
  assert rerouted_state["phases"]["implement"]["verdict"] == "BLOCKED"
4673
- do_spawn(rerouted_state, "implement", 1, None, None, "codex", receipt_model,
4674
- prompt_sha256=receipt_prompt_sha, devlyn=receipt_devlyn)
4675
- assert rerouted_state["phases"]["implement"]["history"][-1]["verdict"] == "BLOCKED"
4977
+ try:
4978
+ do_spawn(rerouted_state, "implement", 1, None, None, "codex", receipt_model,
4979
+ prompt_sha256=receipt_prompt_sha, devlyn=receipt_devlyn)
4980
+ except SystemExit as exc:
4981
+ assert str(exc) == "BLOCKED:repair-edge-invalid"
4982
+ else:
4983
+ raise AssertionError("BLOCKED IMPLEMENT was admitted as a product repair")
4676
4984
 
4677
4985
  plan_prompt = receipt_devlyn / "plan.prompt.0"
4678
4986
  plan_prompt.write_text("plan exactly\n", encoding="utf-8")
@@ -5158,7 +5466,10 @@ def self_test() -> int:
5158
5466
  {"a.txt": "old\n"}, "build_gate",
5159
5467
  [{"id": "BG", "file": "a.txt", "line": 8}],
5160
5468
  )
5161
- build_state["phases"]["implement"] = {"round": 1, "triggered_by": "build_gate"}
5469
+ build_state["phases"]["implement"] = {
5470
+ "started_at": now_iso(), "completed_at": now_iso(), "round": 1,
5471
+ "triggered_by": "build_gate", "verdict": "PASS",
5472
+ }
5162
5473
  write_state(build_devlyn / "pipeline.state.json", build_state)
5163
5474
  subprocess.run(
5164
5475
  [sys.executable, str(pathlib.Path(__file__).resolve()), "--devlyn-dir", ".devlyn",
@@ -5415,7 +5726,7 @@ def self_test() -> int:
5415
5726
  return 0
5416
5727
 
5417
5728
 
5418
- def main() -> int:
5729
+ def _main_unlocked() -> int:
5419
5730
  ap = argparse.ArgumentParser(description=__doc__.splitlines()[0])
5420
5731
  ap.add_argument("--devlyn-dir", default=".devlyn")
5421
5732
  ap.add_argument("--phase", choices=sorted(PHASE_NAMES))
@@ -5435,7 +5746,7 @@ def main() -> int:
5435
5746
  spawn_p.add_argument("--model", default=None)
5436
5747
 
5437
5748
  complete_p = sub.add_parser("complete")
5438
- complete_p.add_argument("--verdict", choices=sorted(VALID_VERDICTS), default=None)
5749
+ complete_p.add_argument("--verdict", default=None)
5439
5750
  complete_p.add_argument("--post-sha", default=None)
5440
5751
  complete_p.add_argument("--findings-file", default=None)
5441
5752
  complete_p.add_argument("--log-file", default=None)
@@ -5444,7 +5755,7 @@ def main() -> int:
5444
5755
  complete_p.add_argument("--engine-session-log", default=None)
5445
5756
 
5446
5757
  transition_p = sub.add_parser("transition")
5447
- transition_p.add_argument("--verdict", choices=sorted(VALID_VERDICTS), default=None)
5758
+ transition_p.add_argument("--verdict", default=None)
5448
5759
  transition_p.add_argument("--post-sha", default=None)
5449
5760
  transition_p.add_argument("--findings-file", default=None)
5450
5761
  transition_p.add_argument("--log-file", default=None)
@@ -5658,6 +5969,21 @@ def main() -> int:
5658
5969
  return 0
5659
5970
 
5660
5971
 
5972
+ def main() -> int:
5973
+ if "--self-test" in sys.argv[1:]:
5974
+ return _main_unlocked()
5975
+ selector = argparse.ArgumentParser(add_help=False)
5976
+ selector.add_argument("--devlyn-dir", default=".devlyn")
5977
+ known, _ = selector.parse_known_args()
5978
+ devlyn = pathlib.Path(known.devlyn_dir).resolve()
5979
+ if not devlyn.is_dir():
5980
+ sys.stderr.write(f"error: {devlyn} is not a directory\n")
5981
+ return 1
5982
+ lock = runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["file_lock"]
5983
+ with lock(devlyn / "pipeline.state.lock", blocking=True):
5984
+ return _main_unlocked()
5985
+
5986
+
5661
5987
  if __name__ == "__main__":
5662
5988
  runpy.run_path(str(pathlib.Path(__file__).with_name("platform-support.py")))["configure_utf8"]()
5663
5989
  raise SystemExit(main())