dorfl 0.14.2 → 0.15.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 (418) hide show
  1. package/dist/advance-ci-template.d.ts +17 -16
  2. package/dist/advance-ci-template.d.ts.map +1 -1
  3. package/dist/advance-ci-template.js +42 -53
  4. package/dist/advance-ci-template.js.map +1 -1
  5. package/dist/advance-drivers.d.ts +8 -0
  6. package/dist/advance-drivers.d.ts.map +1 -1
  7. package/dist/advance-drivers.js +28 -4
  8. package/dist/advance-drivers.js.map +1 -1
  9. package/dist/advance-isolated.d.ts.map +1 -1
  10. package/dist/advance-isolated.js +3 -2
  11. package/dist/advance-isolated.js.map +1 -1
  12. package/dist/advance-lifecycle-template.d.ts +41 -40
  13. package/dist/advance-lifecycle-template.d.ts.map +1 -1
  14. package/dist/advance-lifecycle-template.js +309 -489
  15. package/dist/advance-lifecycle-template.js.map +1 -1
  16. package/dist/advance-loop-driver.d.ts.map +1 -1
  17. package/dist/advance-loop-driver.js +3 -2
  18. package/dist/advance-loop-driver.js.map +1 -1
  19. package/dist/advance-treeless-publish.d.ts +31 -0
  20. package/dist/advance-treeless-publish.d.ts.map +1 -1
  21. package/dist/advance-treeless-publish.js +40 -0
  22. package/dist/advance-treeless-publish.js.map +1 -1
  23. package/dist/advance.d.ts +63 -3
  24. package/dist/advance.d.ts.map +1 -1
  25. package/dist/advance.js +80 -12
  26. package/dist/advance.js.map +1 -1
  27. package/dist/advancing-lock.d.ts +9 -0
  28. package/dist/advancing-lock.d.ts.map +1 -1
  29. package/dist/advancing-lock.js +21 -1
  30. package/dist/advancing-lock.js.map +1 -1
  31. package/dist/agent-env.d.ts +61 -0
  32. package/dist/agent-env.d.ts.map +1 -0
  33. package/dist/agent-env.js +144 -0
  34. package/dist/agent-env.js.map +1 -0
  35. package/dist/agent-stop.d.ts +35 -0
  36. package/dist/agent-stop.d.ts.map +1 -1
  37. package/dist/agent-stop.js +62 -0
  38. package/dist/agent-stop.js.map +1 -1
  39. package/dist/apply-merge-action.d.ts +76 -34
  40. package/dist/apply-merge-action.d.ts.map +1 -1
  41. package/dist/apply-merge-action.js +339 -129
  42. package/dist/apply-merge-action.js.map +1 -1
  43. package/dist/apply-stuck-action.js +2 -2
  44. package/dist/apply-stuck-action.js.map +1 -1
  45. package/dist/ci-agent-result.d.ts +151 -0
  46. package/dist/ci-agent-result.d.ts.map +1 -0
  47. package/dist/ci-agent-result.js +314 -0
  48. package/dist/ci-agent-result.js.map +1 -0
  49. package/dist/ci-handoff-apply.d.ts +162 -0
  50. package/dist/ci-handoff-apply.d.ts.map +1 -0
  51. package/dist/ci-handoff-apply.js +497 -0
  52. package/dist/ci-handoff-apply.js.map +1 -0
  53. package/dist/ci-handoff-format.d.ts +369 -0
  54. package/dist/ci-handoff-format.d.ts.map +1 -0
  55. package/dist/ci-handoff-format.js +646 -0
  56. package/dist/ci-handoff-format.js.map +1 -0
  57. package/dist/ci-handoff-lfs.d.ts +146 -0
  58. package/dist/ci-handoff-lfs.d.ts.map +1 -0
  59. package/dist/ci-handoff-lfs.js +397 -0
  60. package/dist/ci-handoff-lfs.js.map +1 -0
  61. package/dist/ci-handoff.d.ts +99 -0
  62. package/dist/ci-handoff.d.ts.map +1 -0
  63. package/dist/ci-handoff.js +277 -0
  64. package/dist/ci-handoff.js.map +1 -0
  65. package/dist/ci-lock-outputs.d.ts +93 -0
  66. package/dist/ci-lock-outputs.d.ts.map +1 -0
  67. package/dist/ci-lock-outputs.js +223 -0
  68. package/dist/ci-lock-outputs.js.map +1 -0
  69. package/dist/ci-phase-build.d.ts +178 -0
  70. package/dist/ci-phase-build.d.ts.map +1 -0
  71. package/dist/ci-phase-build.js +989 -0
  72. package/dist/ci-phase-build.js.map +1 -0
  73. package/dist/ci-phase-driver.d.ts +247 -0
  74. package/dist/ci-phase-driver.d.ts.map +1 -0
  75. package/dist/ci-phase-driver.js +370 -0
  76. package/dist/ci-phase-driver.js.map +1 -0
  77. package/dist/ci-phase-intake.d.ts +172 -0
  78. package/dist/ci-phase-intake.d.ts.map +1 -0
  79. package/dist/ci-phase-intake.js +555 -0
  80. package/dist/ci-phase-intake.js.map +1 -0
  81. package/dist/ci-phase-tasking.d.ts +205 -0
  82. package/dist/ci-phase-tasking.d.ts.map +1 -0
  83. package/dist/ci-phase-tasking.js +906 -0
  84. package/dist/ci-phase-tasking.js.map +1 -0
  85. package/dist/ci-phase-treeless.d.ts +224 -0
  86. package/dist/ci-phase-treeless.d.ts.map +1 -0
  87. package/dist/ci-phase-treeless.js +1482 -0
  88. package/dist/ci-phase-treeless.js.map +1 -0
  89. package/dist/ci-read-token.d.ts +47 -0
  90. package/dist/ci-read-token.d.ts.map +1 -0
  91. package/dist/ci-read-token.js +80 -0
  92. package/dist/ci-read-token.js.map +1 -0
  93. package/dist/cli.d.ts.map +1 -1
  94. package/dist/cli.js +447 -22
  95. package/dist/cli.js.map +1 -1
  96. package/dist/close-job-template.d.ts +10 -1
  97. package/dist/close-job-template.d.ts.map +1 -1
  98. package/dist/close-job-template.js +46 -14
  99. package/dist/close-job-template.js.map +1 -1
  100. package/dist/close-job.d.ts +20 -5
  101. package/dist/close-job.d.ts.map +1 -1
  102. package/dist/close-job.js +65 -12
  103. package/dist/close-job.js.map +1 -1
  104. package/dist/complete.d.ts.map +1 -1
  105. package/dist/complete.js +20 -1
  106. package/dist/complete.js.map +1 -1
  107. package/dist/config.d.ts +41 -44
  108. package/dist/config.d.ts.map +1 -1
  109. package/dist/config.js +2 -4
  110. package/dist/config.js.map +1 -1
  111. package/dist/continue-branch.d.ts +63 -15
  112. package/dist/continue-branch.d.ts.map +1 -1
  113. package/dist/continue-branch.js +75 -8
  114. package/dist/continue-branch.js.map +1 -1
  115. package/dist/crashed-run-save.d.ts +73 -0
  116. package/dist/crashed-run-save.d.ts.map +1 -0
  117. package/dist/crashed-run-save.js +86 -0
  118. package/dist/crashed-run-save.js.map +1 -0
  119. package/dist/cwd-section.d.ts.map +1 -1
  120. package/dist/cwd-section.js +10 -2
  121. package/dist/cwd-section.js.map +1 -1
  122. package/dist/decision-engine.d.ts +10 -0
  123. package/dist/decision-engine.d.ts.map +1 -1
  124. package/dist/decision-engine.js.map +1 -1
  125. package/dist/do-config.d.ts +2 -24
  126. package/dist/do-config.d.ts.map +1 -1
  127. package/dist/do-config.js +1 -33
  128. package/dist/do-config.js.map +1 -1
  129. package/dist/do.d.ts +149 -1
  130. package/dist/do.d.ts.map +1 -1
  131. package/dist/do.js +115 -25
  132. package/dist/do.js.map +1 -1
  133. package/dist/dorfl-item-template.d.ts +98 -0
  134. package/dist/dorfl-item-template.d.ts.map +1 -0
  135. package/dist/dorfl-item-template.js +504 -0
  136. package/dist/dorfl-item-template.js.map +1 -0
  137. package/dist/env-config.d.ts.map +1 -1
  138. package/dist/env-config.js +7 -5
  139. package/dist/env-config.js.map +1 -1
  140. package/dist/frontmatter.d.ts +52 -0
  141. package/dist/frontmatter.d.ts.map +1 -1
  142. package/dist/frontmatter.js +136 -2
  143. package/dist/frontmatter.js.map +1 -1
  144. package/dist/gc.d.ts +13 -0
  145. package/dist/gc.d.ts.map +1 -1
  146. package/dist/gc.js +16 -2
  147. package/dist/gc.js.map +1 -1
  148. package/dist/git.d.ts +18 -1
  149. package/dist/git.d.ts.map +1 -1
  150. package/dist/git.js +70 -5
  151. package/dist/git.js.map +1 -1
  152. package/dist/harness.d.ts +44 -5
  153. package/dist/harness.d.ts.map +1 -1
  154. package/dist/harness.js +24 -1
  155. package/dist/harness.js.map +1 -1
  156. package/dist/index.d.ts +11 -4
  157. package/dist/index.d.ts.map +1 -1
  158. package/dist/index.js +7 -3
  159. package/dist/index.js.map +1 -1
  160. package/dist/install-ci-action-pins.d.ts +123 -0
  161. package/dist/install-ci-action-pins.d.ts.map +1 -0
  162. package/dist/install-ci-action-pins.js +190 -0
  163. package/dist/install-ci-action-pins.js.map +1 -0
  164. package/dist/install-ci-actions-pr-creation.d.ts +70 -0
  165. package/dist/install-ci-actions-pr-creation.d.ts.map +1 -0
  166. package/dist/install-ci-actions-pr-creation.js +106 -0
  167. package/dist/install-ci-actions-pr-creation.js.map +1 -0
  168. package/dist/install-ci-capabilities/dorfl-item.d.ts +12 -0
  169. package/dist/install-ci-capabilities/dorfl-item.d.ts.map +1 -0
  170. package/dist/install-ci-capabilities/dorfl-item.js +26 -0
  171. package/dist/install-ci-capabilities/dorfl-item.js.map +1 -0
  172. package/dist/install-ci-core.d.ts +155 -37
  173. package/dist/install-ci-core.d.ts.map +1 -1
  174. package/dist/install-ci-core.js +304 -200
  175. package/dist/install-ci-core.js.map +1 -1
  176. package/dist/install-ci-github.d.ts +47 -2
  177. package/dist/install-ci-github.d.ts.map +1 -1
  178. package/dist/install-ci-github.js +113 -1
  179. package/dist/install-ci-github.js.map +1 -1
  180. package/dist/install-ci.d.ts +25 -6
  181. package/dist/install-ci.d.ts.map +1 -1
  182. package/dist/install-ci.js +72 -36
  183. package/dist/install-ci.js.map +1 -1
  184. package/dist/intake-marker.d.ts +6 -1
  185. package/dist/intake-marker.d.ts.map +1 -1
  186. package/dist/intake-marker.js.map +1 -1
  187. package/dist/intake-triage.d.ts.map +1 -1
  188. package/dist/intake-triage.js +6 -2
  189. package/dist/intake-triage.js.map +1 -1
  190. package/dist/intake-trigger-template.d.ts +31 -25
  191. package/dist/intake-trigger-template.d.ts.map +1 -1
  192. package/dist/intake-trigger-template.js +107 -292
  193. package/dist/intake-trigger-template.js.map +1 -1
  194. package/dist/intake.d.ts +186 -4
  195. package/dist/intake.d.ts.map +1 -1
  196. package/dist/intake.js +533 -416
  197. package/dist/intake.js.map +1 -1
  198. package/dist/integration-core.d.ts +154 -1
  199. package/dist/integration-core.d.ts.map +1 -1
  200. package/dist/integration-core.js +759 -486
  201. package/dist/integration-core.js.map +1 -1
  202. package/dist/integrator.d.ts +12 -0
  203. package/dist/integrator.d.ts.map +1 -1
  204. package/dist/integrator.js +21 -0
  205. package/dist/integrator.js.map +1 -1
  206. package/dist/isolation.d.ts +12 -2
  207. package/dist/isolation.d.ts.map +1 -1
  208. package/dist/isolation.js +19 -8
  209. package/dist/isolation.js.map +1 -1
  210. package/dist/item-lock.d.ts +93 -11
  211. package/dist/item-lock.d.ts.map +1 -1
  212. package/dist/item-lock.js +233 -65
  213. package/dist/item-lock.js.map +1 -1
  214. package/dist/land-question-residue.d.ts +58 -0
  215. package/dist/land-question-residue.d.ts.map +1 -0
  216. package/dist/land-question-residue.js +95 -0
  217. package/dist/land-question-residue.js.map +1 -0
  218. package/dist/lifecycle-gather.d.ts +16 -0
  219. package/dist/lifecycle-gather.d.ts.map +1 -1
  220. package/dist/lifecycle-gather.js +54 -6
  221. package/dist/lifecycle-gather.js.map +1 -1
  222. package/dist/merge-answer.d.ts +41 -0
  223. package/dist/merge-answer.d.ts.map +1 -0
  224. package/dist/merge-answer.js +60 -0
  225. package/dist/merge-answer.js.map +1 -0
  226. package/dist/merge-question-surfacer.d.ts +105 -30
  227. package/dist/merge-question-surfacer.d.ts.map +1 -1
  228. package/dist/merge-question-surfacer.js +202 -91
  229. package/dist/merge-question-surfacer.js.map +1 -1
  230. package/dist/merge-question-tick.d.ts +64 -0
  231. package/dist/merge-question-tick.d.ts.map +1 -0
  232. package/dist/merge-question-tick.js +96 -0
  233. package/dist/merge-question-tick.js.map +1 -0
  234. package/dist/mint-adr.d.ts.map +1 -1
  235. package/dist/mint-adr.js +20 -2
  236. package/dist/mint-adr.js.map +1 -1
  237. package/dist/needs-attention.d.ts +72 -2
  238. package/dist/needs-attention.d.ts.map +1 -1
  239. package/dist/needs-attention.js +275 -38
  240. package/dist/needs-attention.js.map +1 -1
  241. package/dist/phase-recorder.d.ts +168 -0
  242. package/dist/phase-recorder.d.ts.map +1 -0
  243. package/dist/phase-recorder.js +218 -0
  244. package/dist/phase-recorder.js.map +1 -0
  245. package/dist/phase.d.ts +94 -0
  246. package/dist/phase.d.ts.map +1 -0
  247. package/dist/phase.js +148 -0
  248. package/dist/phase.js.map +1 -0
  249. package/dist/pi-harness.d.ts.map +1 -1
  250. package/dist/pi-harness.js +19 -6
  251. package/dist/pi-harness.js.map +1 -1
  252. package/dist/prepare.d.ts.map +1 -1
  253. package/dist/prepare.js +4 -0
  254. package/dist/prepare.js.map +1 -1
  255. package/dist/prompt.d.ts +4 -0
  256. package/dist/prompt.d.ts.map +1 -1
  257. package/dist/prompt.js +48 -1
  258. package/dist/prompt.js.map +1 -1
  259. package/dist/protocol/CLAIM-PROTOCOL.md +3 -1
  260. package/dist/protocol/WORK-CONTRACT.md +1 -1
  261. package/dist/reap-branches.d.ts +48 -0
  262. package/dist/reap-branches.d.ts.map +1 -1
  263. package/dist/reap-branches.js +64 -0
  264. package/dist/reap-branches.js.map +1 -1
  265. package/dist/ref-write.d.ts +198 -0
  266. package/dist/ref-write.d.ts.map +1 -0
  267. package/dist/ref-write.js +79 -0
  268. package/dist/ref-write.js.map +1 -0
  269. package/dist/repo-config.js +8 -8
  270. package/dist/repo-config.js.map +1 -1
  271. package/dist/repo-mirror.d.ts +36 -0
  272. package/dist/repo-mirror.d.ts.map +1 -1
  273. package/dist/repo-mirror.js +91 -1
  274. package/dist/repo-mirror.js.map +1 -1
  275. package/dist/review-verdict.d.ts +1 -1
  276. package/dist/review-verdict.js +1 -1
  277. package/dist/run.d.ts +7 -1
  278. package/dist/run.d.ts.map +1 -1
  279. package/dist/run.js +28 -4
  280. package/dist/run.js.map +1 -1
  281. package/dist/scan.d.ts +19 -1
  282. package/dist/scan.d.ts.map +1 -1
  283. package/dist/scan.js +21 -8
  284. package/dist/scan.js.map +1 -1
  285. package/dist/sidecar.d.ts +14 -0
  286. package/dist/sidecar.d.ts.map +1 -1
  287. package/dist/sidecar.js +21 -0
  288. package/dist/sidecar.js.map +1 -1
  289. package/dist/single-job-warning.d.ts +63 -0
  290. package/dist/single-job-warning.d.ts.map +1 -0
  291. package/dist/single-job-warning.js +110 -0
  292. package/dist/single-job-warning.js.map +1 -0
  293. package/dist/skills/setup/protocol/CLAIM-PROTOCOL.md +3 -1
  294. package/dist/skills/setup/protocol/WORK-CONTRACT.md +1 -1
  295. package/dist/spec-complete.d.ts +13 -6
  296. package/dist/spec-complete.d.ts.map +1 -1
  297. package/dist/spec-complete.js +23 -11
  298. package/dist/spec-complete.js.map +1 -1
  299. package/dist/start.d.ts.map +1 -1
  300. package/dist/start.js +47 -14
  301. package/dist/start.js.map +1 -1
  302. package/dist/status.d.ts +12 -0
  303. package/dist/status.d.ts.map +1 -1
  304. package/dist/status.js +11 -3
  305. package/dist/status.js.map +1 -1
  306. package/dist/tasking.d.ts +246 -1
  307. package/dist/tasking.d.ts.map +1 -1
  308. package/dist/tasking.js +452 -358
  309. package/dist/tasking.js.map +1 -1
  310. package/dist/triage-persist.d.ts.map +1 -1
  311. package/dist/triage-persist.js +21 -2
  312. package/dist/triage-persist.js.map +1 -1
  313. package/dist/verify-workflow-template.d.ts.map +1 -1
  314. package/dist/verify-workflow-template.js +8 -4
  315. package/dist/verify-workflow-template.js.map +1 -1
  316. package/dist/verify.d.ts.map +1 -1
  317. package/dist/verify.js +4 -0
  318. package/dist/verify.js.map +1 -1
  319. package/dist/watch-session.d.ts +42 -13
  320. package/dist/watch-session.d.ts.map +1 -1
  321. package/dist/watch-session.js +52 -23
  322. package/dist/watch-session.js.map +1 -1
  323. package/dist/work-layout.d.ts +10 -4
  324. package/dist/work-layout.d.ts.map +1 -1
  325. package/dist/work-layout.js +11 -3
  326. package/dist/work-layout.js.map +1 -1
  327. package/dist/workspace.d.ts +65 -4
  328. package/dist/workspace.d.ts.map +1 -1
  329. package/dist/workspace.js +159 -11
  330. package/dist/workspace.js.map +1 -1
  331. package/package.json +1 -1
  332. package/src/advance-ci-template.ts +80 -81
  333. package/src/advance-drivers.ts +45 -6
  334. package/src/advance-isolated.ts +3 -5
  335. package/src/advance-lifecycle-template.ts +375 -538
  336. package/src/advance-loop-driver.ts +3 -5
  337. package/src/advance-treeless-publish.ts +61 -0
  338. package/src/advance.ts +145 -15
  339. package/src/advancing-lock.ts +36 -1
  340. package/src/agent-env.ts +174 -0
  341. package/src/agent-stop.ts +81 -0
  342. package/src/apply-merge-action.ts +401 -154
  343. package/src/apply-stuck-action.ts +2 -2
  344. package/src/ci-agent-result.ts +421 -0
  345. package/src/ci-handoff-apply.ts +737 -0
  346. package/src/ci-handoff-format.ts +926 -0
  347. package/src/ci-handoff-lfs.ts +519 -0
  348. package/src/ci-handoff.ts +392 -0
  349. package/src/ci-lock-outputs.ts +273 -0
  350. package/src/ci-phase-build.ts +1328 -0
  351. package/src/ci-phase-driver.ts +548 -0
  352. package/src/ci-phase-intake.ts +861 -0
  353. package/src/ci-phase-tasking.ts +1255 -0
  354. package/src/ci-phase-treeless.ts +2028 -0
  355. package/src/ci-read-token.ts +90 -0
  356. package/src/cli.ts +591 -29
  357. package/src/close-job-template.ts +52 -15
  358. package/src/close-job.ts +77 -12
  359. package/src/complete.ts +19 -1
  360. package/src/config.ts +43 -48
  361. package/src/continue-branch.ts +121 -16
  362. package/src/crashed-run-save.ts +162 -0
  363. package/src/cwd-section.ts +10 -0
  364. package/src/decision-engine.ts +10 -0
  365. package/src/do-config.ts +2 -57
  366. package/src/do.ts +170 -28
  367. package/src/dorfl-item-template.ts +556 -0
  368. package/src/env-config.ts +7 -5
  369. package/src/frontmatter.ts +155 -2
  370. package/src/gc.ts +27 -2
  371. package/src/git.ts +83 -5
  372. package/src/harness.ts +58 -6
  373. package/src/index.ts +50 -1
  374. package/src/install-ci-action-pins.ts +209 -0
  375. package/src/install-ci-actions-pr-creation.ts +186 -0
  376. package/src/install-ci-capabilities/dorfl-item.ts +33 -0
  377. package/src/install-ci-core.ts +388 -233
  378. package/src/install-ci-github.ts +149 -1
  379. package/src/install-ci.ts +83 -37
  380. package/src/intake-marker.ts +6 -1
  381. package/src/intake-triage.ts +7 -3
  382. package/src/intake-trigger-template.ts +126 -327
  383. package/src/intake.ts +725 -544
  384. package/src/integration-core.ts +975 -523
  385. package/src/integrator.ts +28 -0
  386. package/src/isolation.ts +29 -10
  387. package/src/item-lock.ts +308 -92
  388. package/src/land-question-residue.ts +122 -0
  389. package/src/lifecycle-gather.ts +82 -10
  390. package/src/merge-answer.ts +82 -0
  391. package/src/merge-question-surfacer.ts +320 -112
  392. package/src/merge-question-tick.ts +182 -0
  393. package/src/mint-adr.ts +28 -2
  394. package/src/needs-attention.ts +350 -42
  395. package/src/phase-recorder.ts +350 -0
  396. package/src/phase.ts +177 -0
  397. package/src/pi-harness.ts +25 -5
  398. package/src/prepare.ts +4 -0
  399. package/src/prompt.ts +68 -1
  400. package/src/reap-branches.ts +107 -0
  401. package/src/ref-write.ts +324 -0
  402. package/src/repo-config.ts +8 -8
  403. package/src/repo-mirror.ts +107 -1
  404. package/src/review-verdict.ts +1 -1
  405. package/src/run.ts +47 -6
  406. package/src/scan.ts +37 -7
  407. package/src/sidecar.ts +36 -0
  408. package/src/single-job-warning.ts +135 -0
  409. package/src/spec-complete.ts +36 -14
  410. package/src/start.ts +55 -16
  411. package/src/status.ts +29 -3
  412. package/src/tasking.ts +593 -378
  413. package/src/triage-persist.ts +24 -2
  414. package/src/verify-workflow-template.ts +8 -4
  415. package/src/verify.ts +4 -0
  416. package/src/watch-session.ts +86 -28
  417. package/src/work-layout.ts +11 -3
  418. package/src/workspace.ts +210 -14
@@ -12,6 +12,7 @@ import {run, runAsync, type RunResult} from './git.js';
12
12
  import {
13
13
  branchAheadOf,
14
14
  rebaseContinuedBranchOntoMain,
15
+ describeContinueRebaseFailure,
15
16
  } from './continue-branch.js';
16
17
  import {
17
18
  acquireItemLock,
@@ -45,6 +46,10 @@ import {
45
46
  type BackoffOptions,
46
47
  type Sleep,
47
48
  } from './retry-backoff.js';
49
+ import {
50
+ saveCrashedRunWork,
51
+ type CrashedRunSaveOutcome,
52
+ } from './crashed-run-save.js';
48
53
 
49
54
  /**
50
55
  * The **needs-attention mechanism** (ADR `ledger-status-on-per-item-lock-refs`;
@@ -221,13 +226,29 @@ export interface ReturnToBacklogOptions {
221
226
  reconcile?: boolean;
222
227
  /**
223
228
  * `requeue -m "<note>"` (the handoff note): an optional human steer for the
224
- * NEXT agent. APPENDED (never overwritten) as a dated `## Requeue YYYY-MM-DD`
225
- * section to the item BODY before the move — the ledger file is the durable,
229
+ * NEXT agent. ADDED (never overwritten) as a dated `## Requeue YYYY-MM-DD`
230
+ * section to the item BODY, just before `## Acceptance criteria` (so it never
231
+ * collides with a kept branch's done-move tail; see `insertRequeueNoteText`),
232
+ * before the move — the ledger file is the durable,
226
233
  * conflict-safe, cross-machine home (same place the needs-attention reason
227
234
  * lives). Repeated requeues ACCUMULATE a handoff log. Applies to BOTH modes
228
235
  * (a steer is relevant even on `--reset`).
229
236
  */
230
237
  message?: string;
238
+ /**
239
+ * The execution working area (config `workspacesDir`) where `do --isolated`
240
+ * retains job worktrees. When given, a HELD lock's retained job worktree is
241
+ * checked BEFORE the release (task
242
+ * `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`): a crashed
243
+ * run's local-only commits / uncommitted residue are committed + pushed to
244
+ * `work/task-<slug>` first (see {@link saveCrashedRunWork}), and a failed save
245
+ * KEEPS the lock. Not consulted on `--reset` (which discards the work by
246
+ * design). Omitted ⇒ no worktree is looked at (the in-process callers, e.g.
247
+ * the deadline checkpoint, which saved its own branch already).
248
+ */
249
+ workspacesDir?: string;
250
+ /** No-wait sleep for the crashed-run save push's backoff (tests). */
251
+ sleep?: Sleep;
231
252
  /** Environment for child git processes. */
232
253
  env?: NodeJS.ProcessEnv;
233
254
  /** Sink for human-readable progress notes. */
@@ -251,6 +272,12 @@ export interface ReturnToBacklogResult {
251
272
  reconciled?: boolean;
252
273
  /** When NOT moved, why (e.g. the slug held no recoverable per-item lock on the arbiter, or a failed --reset delete). */
253
274
  reasonNotMoved?: string;
275
+ /**
276
+ * What the crashed-run save found/did in the slug's retained job worktree
277
+ * (only when {@link ReturnToBacklogOptions.workspacesDir} was given and a lock
278
+ * was held; absent otherwise).
279
+ */
280
+ crashedRunSave?: CrashedRunSaveOutcome;
254
281
  /**
255
282
  * **The ONE resolved continue-branch state** this requeue decided from — so a
256
283
  * CALLER reports the same reality the requeue acted on instead of running its
@@ -383,18 +410,7 @@ export async function routeToNeedsAttention(
383
410
  // tip. Skip when the tree is clean (no aborted work to save). NOTE this no
384
411
  // longer needs a folder source — the body rests in `backlog/` (task 9a) and
385
412
  // never moves on a bounce.
386
- gitHard(['add', '-A'], cwd, env);
387
- const hadWip = !nothingStaged(cwd, env);
388
- if (hadWip) {
389
- // Save the agent's uncommitted work as a plain wip commit on the work-branch
390
- // tip (the RECOVERABLE half — a `requeue` continues from it). No bookkeeping
391
- // trailer: after the per-item-lock cut-over (tasks 9a–9d) NO transient status
392
- // (no `needs-attention/` move-only commit) lands on a branch, so a branch cut
393
- // from `main` rebases PLAINLY with nothing to drop — the `drop-bookkeeping-rebase`
394
- // machinery and its `Dorfl-Bookkeeping` trailer are gone (9d).
395
- const wipBody = `chore(${slug}): save aborted work (wip)`;
396
- gitHard(['commit', '-q', '-m', wipBody], cwd, env);
397
- }
413
+ const hadWip = commitAbortedWork({cwd, slug, env});
398
414
  const commitMessage = `chore(${slug}): bounce to stuck; ${options.reason}`;
399
415
  const moveCommit = hadWip ? revParseHead(cwd, env) : undefined;
400
416
  note(`Bounced '${slug}' to stuck (lock): ${options.reason}`);
@@ -417,6 +433,9 @@ export async function routeToNeedsAttention(
417
433
  const arbiter = options.arbiter;
418
434
  const result = await retryWithBackoff(
419
435
  async () => {
436
+ // Write-seam implementation: `routeToNeedsAttention` is reached only
437
+ // through `ledgerWrite.applyNeedsAttentionTransition` or
438
+ // `refWrite.saveWorkBranch` (guard: `write-sites-through-seams.test.ts`).
420
439
  const r = gitSoftRun(
421
440
  ['push', arbiter, `${branch}:${branch}`],
422
441
  cwd,
@@ -451,6 +470,40 @@ export async function routeToNeedsAttention(
451
470
  return {moved: true, commitMessage, moveCommit, branchPush, pushError};
452
471
  }
453
472
 
473
+ /**
474
+ * The LOCAL half of {@link routeToNeedsAttention}'s save: commit whatever the
475
+ * agent left uncommitted (`git add -A`) as a plain wip commit on the current
476
+ * branch tip. Returns whether a commit was made (a clean tree makes none).
477
+ *
478
+ * No bookkeeping trailer: after the per-item-lock cut-over (tasks 9a–9d) NO
479
+ * transient status (no `needs-attention/` move-only commit) lands on a branch,
480
+ * so a branch cut from `main` rebases PLAINLY with nothing to drop: the
481
+ * `drop-bookkeeping-rebase` machinery and its `Dorfl-Bookkeeping` trailer are
482
+ * gone (9d).
483
+ *
484
+ * The CI agent phase (task `ci-split-build-path-non-integrate-intents`) runs
485
+ * this on its own when its pipeline halts at a needs-attention bounce, so the
486
+ * wip travels in the handoff bundle; the apply phase then re-runs the whole
487
+ * route on the bundle's tip, where this finds a clean tree and commits nothing.
488
+ */
489
+ export function commitAbortedWork(params: {
490
+ cwd: string;
491
+ slug: string;
492
+ env?: NodeJS.ProcessEnv;
493
+ }): boolean {
494
+ const {cwd, slug, env} = params;
495
+ gitHard(['add', '-A'], cwd, env);
496
+ if (nothingStaged(cwd, env)) {
497
+ return false;
498
+ }
499
+ gitHard(
500
+ ['commit', '-q', '-m', `chore(${slug}): save aborted work (wip)`],
501
+ cwd,
502
+ env,
503
+ );
504
+ return true;
505
+ }
506
+
454
507
  /**
455
508
  * The clean re-queue (ADR §12 / WORK-CONTRACT return path): once the human has
456
509
  * resolved the cause, move the stuck item back to `work/tasks/ready/<slug>.md` and
@@ -489,9 +542,10 @@ export async function routeToNeedsAttention(
489
542
  * closes the claim-race window; a FAILED delete ABORTS (no backlog move)
490
543
  * so the item stays in needs-attention. The next claim then finds NO
491
544
  * arbiter branch and cuts fresh — no special claim-time logic.
492
- * - **`-m "<note>"` = HANDOFF NOTE.** When `message` is set, APPEND a dated
493
- * `## Requeue YYYY-MM-DD` section to the item BODY (append-only; accumulates
494
- * over repeated requeues) for the next agent. Applies to BOTH modes.
545
+ * - **`-m "<note>"` = HANDOFF NOTE.** When `message` is set, ADD a dated
546
+ * `## Requeue YYYY-MM-DD` section to the item BODY, just before
547
+ * `## Acceptance criteria` (additive; accumulates over repeated requeues,
548
+ * oldest first) for the next agent. Applies to BOTH modes.
495
549
  *
496
550
  * Like the move, NEVER throws for the expected "not in needs-attention" case.
497
551
  */
@@ -586,6 +640,10 @@ export async function deleteRemoteWorkBranchIfPresent(
586
640
  env,
587
641
  );
588
642
  await gitSoftAsync(['branch', '-D', branch], cwd, env);
643
+ // Write-seam implementation: callers reach this through
644
+ // `refWrite.deleteRemoteWorkBranch`, or through `returnToBacklog` behind
645
+ // `ledgerWrite.applyReturnToBacklogTransition` (guard:
646
+ // `write-sites-through-seams.test.ts`).
589
647
  const del = await gitSoftAsync(
590
648
  ['push', arbiter, '--delete', branch],
591
649
  cwd,
@@ -636,6 +694,19 @@ export async function returnToBacklog(
636
694
  };
637
695
  }
638
696
 
697
+ // Destructive vs non-destructive are exclusive verbs on the same escalation.
698
+ // Checked up front (before any lock is read or taken) so the refusal is the
699
+ // same whether the item's lock is held or already released by a surface.
700
+ if (options.reconcile && options.reset) {
701
+ return {
702
+ moved: false,
703
+ reasonNotMoved:
704
+ `requeue for '${slug}': --reconcile and --reset are mutually ` +
705
+ 'exclusive (non-destructive recovery vs destructive last resort). ' +
706
+ 'Pick one.',
707
+ };
708
+ }
709
+
639
710
  // Refresh the remote-tracking refs so every check below (the item's residence,
640
711
  // the continue-branch guard, the CAS base) sees the arbiter's TRUTH, not a stale
641
712
  // local copy. This is a fetch, not a checkout — the working tree is untouched.
@@ -676,7 +747,58 @@ export async function returnToBacklog(
676
747
  } catch {
677
748
  held = await readLocalItemLock(slug, cwd, env);
678
749
  }
750
+ // RELEASED-LOCK RECOVERY (task
751
+ // `requeue-reset-and-reconcile-work-after-the-lock-was-released`). A surface
752
+ // (`surfaceStuckToNeedsAttention`) RELEASES the per-item lock after landing the
753
+ // question sidecar, so a surfaced item with a KEPT work branch has no held lock,
754
+ // yet it is exactly what `--reconcile` (re-sync + retry) and `--reset` (discard,
755
+ // start fresh) exist for. When the item's body rests in the pool/staging on
756
+ // `<arbiter>/main` AND its `work/task-<slug>` branch is on the arbiter, take a
757
+ // SHORT per-item lock (create-only, like claim) so no claim can continue from
758
+ // the branch while it is being rebased or deleted, run the ordinary held-lock
759
+ // path, and give the short lock back on every outcome. Plain `requeue` stays a
760
+ // no-op (the next claim already continues from the kept branch).
761
+ let shortLock = false;
762
+ let releasedRecoverable: ReleasedRecoverableItem | undefined;
679
763
  if (!held) {
764
+ releasedRecoverable = await probeReleasedRecoverableItem({
765
+ cwd,
766
+ slug,
767
+ arbiter,
768
+ branch: continueBranchName,
769
+ env,
770
+ });
771
+ if (
772
+ releasedRecoverable !== undefined &&
773
+ (options.reset || options.reconcile)
774
+ ) {
775
+ const verb = options.reset ? '--reset' : '--reconcile';
776
+ const acquired = await acquireItemLock({
777
+ item: `task:${slug}`,
778
+ action: 'implement',
779
+ cwd,
780
+ arbiter,
781
+ env,
782
+ });
783
+ if (acquired.outcome !== 'acquired') {
784
+ const message =
785
+ `requeue ${verb} for '${slug}': its lock was already released, ` +
786
+ 'but the short lock this recovery needs could not be taken ' +
787
+ (acquired.outcome === 'lost'
788
+ ? '(another run claimed the item meanwhile); nothing was changed.'
789
+ : `(${acquired.message}); nothing was changed. Try again shortly.`);
790
+ note(message);
791
+ return {moved: false, reasonNotMoved: message};
792
+ }
793
+ shortLock = true;
794
+ note(
795
+ `'${slug}' has no held lock (a surface or earlier requeue already ` +
796
+ `released it); took a short lock to ${options.reset ? 'discard' : 'reconcile'} ` +
797
+ `the kept ${continueBranchName} (${verb}).`,
798
+ );
799
+ }
800
+ }
801
+ if (!held && !shortLock) {
680
802
  // CROSS-NAMESPACE HINT (observation
681
803
  // `crashed-do-spec-strands-a-tasking-lock-no-verb-releases`). `requeue` is a
682
804
  // TASK-only verb, so a bare `<slug>` resolves to `task:<slug>` and finds no
@@ -716,6 +838,19 @@ export async function returnToBacklog(
716
838
  } catch {
717
839
  // Best-effort hint only: fall through to the plain refusal.
718
840
  }
841
+ // A released item that DOES rest in the pool with a kept branch (plain
842
+ // requeue only; `--reset`/`--reconcile` took the short-lock path above):
843
+ // nothing to release, and the next claim already continues from the branch.
844
+ // Say so, and name the two verbs that DO act on a released item.
845
+ const keptHint =
846
+ releasedRecoverable !== undefined
847
+ ? ` NOTE: '${slug}' rests in the pool (${releasedRecoverable.bodyRel}) ` +
848
+ `with a kept work branch ${continueBranchName} on ${arbiter}; the ` +
849
+ 'next claim continues from it, so plain requeue has nothing to do. ' +
850
+ 'To act on the kept branch: `requeue --reconcile` (non-destructive ' +
851
+ 're-sync + rebase retry) or `requeue --reset` (DESTRUCTIVELY discard ' +
852
+ 'it and start fresh).'
853
+ : '';
719
854
  return {
720
855
  moved: false,
721
856
  reasonNotMoved:
@@ -723,10 +858,147 @@ export async function returnToBacklog(
723
858
  '(wrong slug, or already at rest in backlog/done?). requeue recovers a ' +
724
859
  'task whose lock is held stuck (needs-attention) or active (a killed ' +
725
860
  'in-progress run).' +
726
- specHint,
861
+ specHint +
862
+ keptHint,
727
863
  };
728
864
  }
729
865
 
866
+ // CRASHED-RUN SAVE (task
867
+ // `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`). A run
868
+ // killed mid-agent leaves its lock held and its job worktree retained, possibly
869
+ // with commits (or a wip residue) the arbiter's work branch lacks. Releasing
870
+ // the lock over them hands the item to a next claim (or a `gc --force`) that
871
+ // cuts that worktree away, so SAVE them first: wip-commit + push the work
872
+ // branch. A failed save KEEPS the lock (and the worktree) and says so. Only on
873
+ // a HELD lock (a short recovery lock means the run already surfaced, its lock
874
+ // released) and never on `--reset` (which discards the branch by design).
875
+ let crashedRunSave: CrashedRunSaveOutcome | undefined;
876
+ if (held && !options.reset && options.workspacesDir !== undefined) {
877
+ crashedRunSave = await saveCrashedRunWork({
878
+ cwd,
879
+ arbiter,
880
+ slug,
881
+ workspacesDir: options.workspacesDir,
882
+ env,
883
+ note,
884
+ ...(options.sleep !== undefined ? {sleep: options.sleep} : {}),
885
+ });
886
+ if (crashedRunSave.kind === 'not-saved') {
887
+ const message =
888
+ `requeue for '${slug}': its crashed run left work in ` +
889
+ `${crashedRunSave.dir} that the arbiter's ${crashedRunSave.branch} ` +
890
+ `lacks, and it could not be saved: ${crashedRunSave.detail}. The lock ` +
891
+ 'is KEPT (not released) and the worktree left in place, so nothing is ' +
892
+ `lost. Push ${crashedRunSave.branch} from that worktree by hand, then ` +
893
+ '`requeue` again.';
894
+ note(message);
895
+ return {moved: false, reasonNotMoved: message, crashedRunSave};
896
+ }
897
+ if (crashedRunSave.kind === 'saved') {
898
+ // The guard below reads the arbiter's branch from THIS clone: bring the
899
+ // just-pushed tip (and its objects) in.
900
+ await refreshArbiterRefs({
901
+ cwd,
902
+ arbiter,
903
+ branches: ['main', continueBranchName],
904
+ env,
905
+ });
906
+ }
907
+ }
908
+
909
+ const heldResult = await requeueHeldItem({
910
+ options,
911
+ arbiter,
912
+ continueBranchName,
913
+ note,
914
+ });
915
+ const result =
916
+ crashedRunSave !== undefined ? {...heldResult, crashedRunSave} : heldResult;
917
+ if (shortLock && !result.moved) {
918
+ // Give the SHORT lock back: the item returns to exactly the released,
919
+ // surfaced state it was in (its question sidecar untouched). A failed
920
+ // release here is reported, never thrown.
921
+ const back = await releaseItemLock({
922
+ item: `task:${slug}`,
923
+ cwd,
924
+ arbiter,
925
+ env,
926
+ });
927
+ const tail =
928
+ back.outcome === 'error'
929
+ ? ` WARNING: could not release the short recovery lock this requeue ` +
930
+ `took (${back.message}); clear it with \`dorfl release-lock ` +
931
+ `task:${slug}\`.`
932
+ : ' (The short recovery lock this requeue took was released again; ' +
933
+ 'the item stays as it was before this requeue.)';
934
+ note(tail.trim());
935
+ return {...result, reasonNotMoved: (result.reasonNotMoved ?? '') + tail};
936
+ }
937
+ return result;
938
+ }
939
+
940
+ /** A task whose lock is RELEASED but which rests in the pool with a kept branch. */
941
+ interface ReleasedRecoverableItem {
942
+ /** The body's on-`main` path (`work/tasks/ready/<slug>.md` or staging). */
943
+ bodyRel: string;
944
+ /** The kept branch's tip on the arbiter. */
945
+ branchSha: string;
946
+ }
947
+
948
+ /**
949
+ * Is `<slug>` a RELEASED-lock task that `requeue --reset`/`--reconcile` can act
950
+ * on? True iff its body rests in `tasks/ready/` or `tasks/backlog/` on
951
+ * `<arbiter>/main` (the D1 probe a surface uses) AND the arbiter HAS its
952
+ * `work/task-<slug>` branch (arbiter-authoritative `ls-remote`). A surfaced item
953
+ * (question sidecar + `needsAnswers:true`, lock released) is the motivating
954
+ * case, but the sidecar is NOT required: a plain requeue that already released
955
+ * the lock leaves the same recoverable shape. An unknown slug (no body) or a
956
+ * pooled item with no kept branch answers `undefined`, so the refusal for those
957
+ * is unchanged. Best-effort: an unreachable arbiter answers `undefined`.
958
+ */
959
+ async function probeReleasedRecoverableItem(params: {
960
+ cwd: string;
961
+ slug: string;
962
+ arbiter: string;
963
+ branch: string;
964
+ env: NodeJS.ProcessEnv | undefined;
965
+ }): Promise<ReleasedRecoverableItem | undefined> {
966
+ const {cwd, slug, arbiter, branch, env} = params;
967
+ try {
968
+ const bodyRel = await resolveBounceItemBodyPathOnMain({
969
+ cwd,
970
+ item: `task:${slug}`,
971
+ arbiter,
972
+ env,
973
+ });
974
+ if (bodyRel === undefined) {
975
+ return undefined;
976
+ }
977
+ const resolved = await resolveArbiterBranch({cwd, arbiter, branch, env});
978
+ if (!resolved.trustworthy || resolved.sha === undefined) {
979
+ return undefined;
980
+ }
981
+ return {bodyRel, branchSha: resolved.sha};
982
+ } catch {
983
+ return undefined;
984
+ }
985
+ }
986
+
987
+ /**
988
+ * The body of {@link returnToBacklog} once the item's per-item lock is HELD
989
+ * (either a pre-existing stuck/active hold, or the short lock a released-lock
990
+ * `--reset`/`--reconcile` just took): the reconcile / reset / keep+continue
991
+ * guard, the optional handoff note, and the lock release.
992
+ */
993
+ async function requeueHeldItem(params: {
994
+ options: ReturnToBacklogOptions;
995
+ arbiter: string;
996
+ continueBranchName: string;
997
+ note: (message: string) => void;
998
+ }): Promise<ReturnToBacklogResult> {
999
+ const {options, arbiter, continueBranchName, note} = params;
1000
+ const {cwd, slug, env} = options;
1001
+
730
1002
  // `--reconcile`: the NON-DESTRUCTIVE recovery rung (task
731
1003
  // `requeue-reconcile-nondestructive-recovery-verb`, parent observation
732
1004
  // `rebase-conflict-on-continue-needs-nondestructive-recovery-not-reset`).
@@ -744,15 +1016,6 @@ export async function returnToBacklog(
744
1016
  // deletes the remote branch — this verb's contract is "keep the work".
745
1017
  let reconciled: boolean | undefined;
746
1018
  if (options.reconcile) {
747
- if (options.reset) {
748
- return {
749
- moved: false,
750
- reasonNotMoved:
751
- `requeue for '${slug}': --reconcile and --reset are mutually ` +
752
- 'exclusive (non-destructive recovery vs destructive last resort). ' +
753
- 'Pick one.',
754
- };
755
- }
756
1019
  const attempt = await attemptReconcile({
757
1020
  cwd,
758
1021
  slug,
@@ -765,7 +1028,9 @@ export async function returnToBacklog(
765
1028
  const message =
766
1029
  `requeue --reconcile for '${slug}': re-synced the ${arbiter} mirror ` +
767
1030
  `and RETRIED the rebase of ${branch} onto latest ${arbiter}/main, but ` +
768
- `the rebase still conflicts on genuine content (${attempt.detail}). The ` +
1031
+ (attempt.genuineConflict === false
1032
+ ? `the rebase still fails (${attempt.detail}). The `
1033
+ : `the rebase still conflicts on genuine content (${attempt.detail}). The `) +
769
1034
  'kept branch is left UNTOUCHED on the arbiter (nothing deleted) and the ' +
770
1035
  'item is left stuck. A supported mirror-side "resolve against latest ' +
771
1036
  'main" command that fetches the kept branch into a scratch worktree, ' +
@@ -914,8 +1179,9 @@ export async function returnToBacklog(
914
1179
  'tasks-backlog',
915
1180
  ];
916
1181
 
917
- // `-m "<note>"` (the handoff steer): APPEND a dated `## Requeue YYYY-MM-DD`
918
- // section to the item BODY where it already rests (pool or staging), via the SAME
1182
+ // `-m "<note>"` (the handoff steer): ADD a dated `## Requeue YYYY-MM-DD`
1183
+ // section (before `## Acceptance criteria`, clear of a kept branch's done-move
1184
+ // tail) to the item BODY where it already rests (pool or staging), via the SAME
919
1185
  // tree-less CAS move (same-folder rewrite with the body transform) — it NEVER
920
1186
  // stages/commits in the cwd tree. The handoff is OPTIONAL and NON-FATAL: a failed
921
1187
  // append degrades to a WARNING and the lock release below STILL runs, because the
@@ -946,7 +1212,7 @@ export async function returnToBacklog(
946
1212
  base,
947
1213
  sourceRel: bodyRel,
948
1214
  destRel: bodyRel,
949
- transformBody: (body) => appendRequeueNoteText(body, handoff),
1215
+ transformBody: (body) => insertRequeueNoteText(body, handoff),
950
1216
  commitMessage: `chore(${slug}): requeue handoff note`,
951
1217
  refNamespace: 'requeue',
952
1218
  env,
@@ -1019,7 +1285,7 @@ export async function returnToBacklog(
1019
1285
  type ReconcileAttempt =
1020
1286
  | {kind: 'reconciled'}
1021
1287
  | {kind: 'no-branch'}
1022
- | {kind: 'conflict'; detail: string};
1288
+ | {kind: 'conflict'; detail: string; genuineConflict?: boolean};
1023
1289
 
1024
1290
  /**
1025
1291
  * The `--reconcile` recovery attempt — the non-destructive middle rung of the
@@ -1088,8 +1354,12 @@ async function attemptReconcile(params: {
1088
1354
  }
1089
1355
  try {
1090
1356
  const rebase = rebaseContinuedBranchOntoMain(worktree, arbMainRef, env);
1091
- if (rebase.kind === 'conflict') {
1092
- return {kind: 'conflict', detail: 'rebase conflicted after re-sync'};
1357
+ if (rebase.kind !== 'clean') {
1358
+ return {
1359
+ kind: 'conflict',
1360
+ detail: `rebase after re-sync ${describeContinueRebaseFailure(rebase)}`,
1361
+ genuineConflict: rebase.kind === 'conflict',
1362
+ };
1093
1363
  }
1094
1364
  // 4. Push the reconciled tip back (`--force-with-lease`, WORK branch only).
1095
1365
  const observedTip = gitSoftRun(
@@ -1099,6 +1369,10 @@ async function attemptReconcile(params: {
1099
1369
  ).stdout.trim();
1100
1370
  const lease =
1101
1371
  observedTip === '' ? `${branch}:` : `${branch}:${observedTip}`;
1372
+ // WRITE-SEAM EXEMPT: `attemptReconcile` backs the human-only
1373
+ // `requeue --reconcile` verb, never a CI path, so this reconciled
1374
+ // kept-branch push stays direct (task
1375
+ // `ci-split-route-direct-writes-through-seams`).
1102
1376
  const push = gitSoftRun(
1103
1377
  [
1104
1378
  'push',
@@ -3160,22 +3434,56 @@ const BOUNCE_BODY_PROBE_ORDER: Record<SidecarType, readonly WorkFolderKey[]> = {
3160
3434
  observation: ['observations'],
3161
3435
  };
3162
3436
 
3437
+ /** The body headings a requeue handoff note is inserted BEFORE, in preference
3438
+ * order (see {@link insertRequeueNoteText}). `## Prompt` is the fallback anchor:
3439
+ * every BUILDABLE task has one (`resolveTask` refuses a body without it), so a
3440
+ * buildable body never falls through to the tail. */
3441
+ const REQUEUE_NOTE_ANCHORS: readonly RegExp[] = [
3442
+ /^##\s+Acceptance criteria\s*$/m,
3443
+ /^##\s+Prompt\s*$/m,
3444
+ ];
3445
+
3163
3446
  /**
3164
- * Append a dated `## Requeue YYYY-MM-DD` handoff section to an item body's TEXT
3165
- * (append-only — never overwrites; repeated requeues accumulate a handoff log).
3166
- * Body prose only (never a frontmatter field — WORK-CONTRACT rule 3). The date is
3167
- * UTC `YYYY-MM-DD`; multiple notes on the same day are distinct appended blocks.
3447
+ * Add a dated `## Requeue YYYY-MM-DD` handoff section to an item body's TEXT
3448
+ * (additive — never overwrites; repeated requeues accumulate a handoff log,
3449
+ * oldest first). Body prose only (never a frontmatter field — WORK-CONTRACT
3450
+ * rule 3). The date is UTC `YYYY-MM-DD`; multiple notes on the same day are
3451
+ * distinct blocks.
3452
+ *
3453
+ * PLACEMENT (task `requeue-handoff-note-does-not-conflict-with-the-kept-done-move`):
3454
+ * the section is inserted immediately BEFORE the `## Acceptance criteria`
3455
+ * heading, NOT at the end of the body. A kept work branch the next claim
3456
+ * CONTINUES from may already have done-moved this body AND appended its
3457
+ * `## Decisions` block at the END; a tail-appended note on `main` then collided
3458
+ * with that tail in the continue rebase (which never auto-resolves, ADR §10) and
3459
+ * bounced the item. Mid-body, the two edits are disjoint hunks and the rebase
3460
+ * merges them cleanly. A body with no `## Acceptance criteria` heading anchors on
3461
+ * `## Prompt` instead (a chore task written without acceptance criteria used to
3462
+ * fall back to the tail and still collide); only a body with NEITHER heading
3463
+ * (which is not buildable anyway) falls back to the end. The continue prompt reads every `## Requeue`
3464
+ * section wherever it sits (`extractRequeueNotes`), so the note still reaches
3465
+ * the continuing agent.
3168
3466
  *
3169
3467
  * A PURE string transform (it operates on the body CONTENT, not a file path) so
3170
3468
  * the tree-less requeue can apply it to the blob read from `<arbiter>/main`
3171
3469
  * without touching the cwd working tree.
3172
3470
  */
3173
- function appendRequeueNoteText(content: string, message: string): string {
3471
+ export function insertRequeueNoteText(
3472
+ content: string,
3473
+ message: string,
3474
+ ): string {
3174
3475
  const date = new Date().toISOString().slice(0, 10);
3175
- const base = content.replace(/\s*$/, '');
3176
- return [base, '', `${REQUEUE_HEADING_PREFIX} ${date}`, '', message, ''].join(
3177
- '\n',
3476
+ const section = `${REQUEUE_HEADING_PREFIX} ${date}\n\n${message}\n`;
3477
+ const anchor = REQUEUE_NOTE_ANCHORS.map((re) => re.exec(content)).find(
3478
+ (m) => m !== null,
3178
3479
  );
3480
+ if (anchor === undefined || anchor === null) {
3481
+ const base = content.replace(/\s*$/, '');
3482
+ return `${base}\n\n${section}`;
3483
+ }
3484
+ const before = content.slice(0, anchor.index).replace(/\s*$/, '');
3485
+ const after = content.slice(anchor.index);
3486
+ return `${before}\n\n${section}\n${after}`;
3179
3487
  }
3180
3488
 
3181
3489
  /**