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
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os';
3
3
  import { join } from 'node:path';
4
4
  import { workFolderPrefix, workFolderRel, workItemRel, isWorkItemFile, } from './work-layout.js';
5
5
  import { run, runAsync } from './git.js';
6
- import { branchAheadOf, rebaseContinuedBranchOntoMain, } from './continue-branch.js';
6
+ import { branchAheadOf, rebaseContinuedBranchOntoMain, describeContinueRebaseFailure, } from './continue-branch.js';
7
7
  import { acquireItemLock, releaseItemLock, readItemLock, itemLockRef, lockEntryFor, refreshMainRef, parseLockEntry, } from './item-lock.js';
8
8
  import { ledgerWrite } from './ledger-write.js';
9
9
  import { workBranchRef } from './slug-namespace.js';
@@ -11,6 +11,7 @@ import { refreshArbiterRefs, resolveArbiterBranch } from './arbiter-refs.js';
11
11
  import { appendQuestions, isEntryAnswered, newSidecar, parseSidecar, resolveSidecarIdentity, serialiseSidecar, sidecarPathFor, sidecarPathCandidates, } from './sidecar.js';
12
12
  import { parseFrontmatter, setNeedsAnswersMarker } from './frontmatter.js';
13
13
  import { retryWithBackoff, realSleep, } from './retry-backoff.js';
14
+ import { saveCrashedRunWork, } from './crashed-run-save.js';
14
15
  /**
15
16
  * The **needs-attention mechanism** (ADR `ledger-status-on-per-item-lock-refs`;
16
17
  * spec `ledger-status-per-item-lock-refs`; ADR §12 for the original folder model).
@@ -91,18 +92,7 @@ export async function routeToNeedsAttention(options) {
91
92
  // tip. Skip when the tree is clean (no aborted work to save). NOTE this no
92
93
  // longer needs a folder source — the body rests in `backlog/` (task 9a) and
93
94
  // never moves on a bounce.
94
- gitHard(['add', '-A'], cwd, env);
95
- const hadWip = !nothingStaged(cwd, env);
96
- if (hadWip) {
97
- // Save the agent's uncommitted work as a plain wip commit on the work-branch
98
- // tip (the RECOVERABLE half — a `requeue` continues from it). No bookkeeping
99
- // trailer: after the per-item-lock cut-over (tasks 9a–9d) NO transient status
100
- // (no `needs-attention/` move-only commit) lands on a branch, so a branch cut
101
- // from `main` rebases PLAINLY with nothing to drop — the `drop-bookkeeping-rebase`
102
- // machinery and its `Dorfl-Bookkeeping` trailer are gone (9d).
103
- const wipBody = `chore(${slug}): save aborted work (wip)`;
104
- gitHard(['commit', '-q', '-m', wipBody], cwd, env);
105
- }
95
+ const hadWip = commitAbortedWork({ cwd, slug, env });
106
96
  const commitMessage = `chore(${slug}): bounce to stuck; ${options.reason}`;
107
97
  const moveCommit = hadWip ? revParseHead(cwd, env) : undefined;
108
98
  note(`Bounced '${slug}' to stuck (lock): ${options.reason}`);
@@ -123,6 +113,9 @@ export async function routeToNeedsAttention(options) {
123
113
  if (branchAheadOf(cwd, branch, 'main', env)) {
124
114
  const arbiter = options.arbiter;
125
115
  const result = await retryWithBackoff(async () => {
116
+ // Write-seam implementation: `routeToNeedsAttention` is reached only
117
+ // through `ledgerWrite.applyNeedsAttentionTransition` or
118
+ // `refWrite.saveWorkBranch` (guard: `write-sites-through-seams.test.ts`).
126
119
  const r = gitSoftRun(['push', arbiter, `${branch}:${branch}`], cwd, env);
127
120
  return r.status === 0
128
121
  ? { ok: true, value: undefined }
@@ -147,6 +140,31 @@ export async function routeToNeedsAttention(options) {
147
140
  }
148
141
  return { moved: true, commitMessage, moveCommit, branchPush, pushError };
149
142
  }
143
+ /**
144
+ * The LOCAL half of {@link routeToNeedsAttention}'s save: commit whatever the
145
+ * agent left uncommitted (`git add -A`) as a plain wip commit on the current
146
+ * branch tip. Returns whether a commit was made (a clean tree makes none).
147
+ *
148
+ * No bookkeeping trailer: after the per-item-lock cut-over (tasks 9a–9d) NO
149
+ * transient status (no `needs-attention/` move-only commit) lands on a branch,
150
+ * so a branch cut from `main` rebases PLAINLY with nothing to drop: the
151
+ * `drop-bookkeeping-rebase` machinery and its `Dorfl-Bookkeeping` trailer are
152
+ * gone (9d).
153
+ *
154
+ * The CI agent phase (task `ci-split-build-path-non-integrate-intents`) runs
155
+ * this on its own when its pipeline halts at a needs-attention bounce, so the
156
+ * wip travels in the handoff bundle; the apply phase then re-runs the whole
157
+ * route on the bundle's tip, where this finds a clean tree and commits nothing.
158
+ */
159
+ export function commitAbortedWork(params) {
160
+ const { cwd, slug, env } = params;
161
+ gitHard(['add', '-A'], cwd, env);
162
+ if (nothingStaged(cwd, env)) {
163
+ return false;
164
+ }
165
+ gitHard(['commit', '-q', '-m', `chore(${slug}): save aborted work (wip)`], cwd, env);
166
+ return true;
167
+ }
150
168
  /**
151
169
  * The clean re-queue (ADR §12 / WORK-CONTRACT return path): once the human has
152
170
  * resolved the cause, move the stuck item back to `work/tasks/ready/<slug>.md` and
@@ -185,9 +203,10 @@ export async function routeToNeedsAttention(options) {
185
203
  * closes the claim-race window; a FAILED delete ABORTS (no backlog move)
186
204
  * so the item stays in needs-attention. The next claim then finds NO
187
205
  * arbiter branch and cuts fresh — no special claim-time logic.
188
- * - **`-m "<note>"` = HANDOFF NOTE.** When `message` is set, APPEND a dated
189
- * `## Requeue YYYY-MM-DD` section to the item BODY (append-only; accumulates
190
- * over repeated requeues) for the next agent. Applies to BOTH modes.
206
+ * - **`-m "<note>"` = HANDOFF NOTE.** When `message` is set, ADD a dated
207
+ * `## Requeue YYYY-MM-DD` section to the item BODY, just before
208
+ * `## Acceptance criteria` (additive; accumulates over repeated requeues,
209
+ * oldest first) for the next agent. Applies to BOTH modes.
191
210
  *
192
211
  * Like the move, NEVER throws for the expected "not in needs-attention" case.
193
212
  */
@@ -233,6 +252,10 @@ export async function deleteRemoteWorkBranchIfPresent(options) {
233
252
  const branch = workBranchRef('task', slug);
234
253
  await gitSoftAsync(['update-ref', '-d', `refs/remotes/${arbiter}/${branch}`], cwd, env);
235
254
  await gitSoftAsync(['branch', '-D', branch], cwd, env);
255
+ // Write-seam implementation: callers reach this through
256
+ // `refWrite.deleteRemoteWorkBranch`, or through `returnToBacklog` behind
257
+ // `ledgerWrite.applyReturnToBacklogTransition` (guard:
258
+ // `write-sites-through-seams.test.ts`).
236
259
  const del = await gitSoftAsync(['push', arbiter, '--delete', branch], cwd, env);
237
260
  if (del.status === 0) {
238
261
  return { branch, status: 'deleted', stderr: '' };
@@ -270,6 +293,17 @@ export async function returnToBacklog(options) {
270
293
  reasonNotMoved: `no git remote named '${arbiter}' (set one, or pass --arbiter).`,
271
294
  };
272
295
  }
296
+ // Destructive vs non-destructive are exclusive verbs on the same escalation.
297
+ // Checked up front (before any lock is read or taken) so the refusal is the
298
+ // same whether the item's lock is held or already released by a surface.
299
+ if (options.reconcile && options.reset) {
300
+ return {
301
+ moved: false,
302
+ reasonNotMoved: `requeue for '${slug}': --reconcile and --reset are mutually ` +
303
+ 'exclusive (non-destructive recovery vs destructive last resort). ' +
304
+ 'Pick one.',
305
+ };
306
+ }
273
307
  // Refresh the remote-tracking refs so every check below (the item's residence,
274
308
  // the continue-branch guard, the CAS base) sees the arbiter's TRUTH, not a stale
275
309
  // local copy. This is a fetch, not a checkout — the working tree is untouched.
@@ -310,7 +344,53 @@ export async function returnToBacklog(options) {
310
344
  catch {
311
345
  held = await readLocalItemLock(slug, cwd, env);
312
346
  }
347
+ // RELEASED-LOCK RECOVERY (task
348
+ // `requeue-reset-and-reconcile-work-after-the-lock-was-released`). A surface
349
+ // (`surfaceStuckToNeedsAttention`) RELEASES the per-item lock after landing the
350
+ // question sidecar, so a surfaced item with a KEPT work branch has no held lock,
351
+ // yet it is exactly what `--reconcile` (re-sync + retry) and `--reset` (discard,
352
+ // start fresh) exist for. When the item's body rests in the pool/staging on
353
+ // `<arbiter>/main` AND its `work/task-<slug>` branch is on the arbiter, take a
354
+ // SHORT per-item lock (create-only, like claim) so no claim can continue from
355
+ // the branch while it is being rebased or deleted, run the ordinary held-lock
356
+ // path, and give the short lock back on every outcome. Plain `requeue` stays a
357
+ // no-op (the next claim already continues from the kept branch).
358
+ let shortLock = false;
359
+ let releasedRecoverable;
313
360
  if (!held) {
361
+ releasedRecoverable = await probeReleasedRecoverableItem({
362
+ cwd,
363
+ slug,
364
+ arbiter,
365
+ branch: continueBranchName,
366
+ env,
367
+ });
368
+ if (releasedRecoverable !== undefined &&
369
+ (options.reset || options.reconcile)) {
370
+ const verb = options.reset ? '--reset' : '--reconcile';
371
+ const acquired = await acquireItemLock({
372
+ item: `task:${slug}`,
373
+ action: 'implement',
374
+ cwd,
375
+ arbiter,
376
+ env,
377
+ });
378
+ if (acquired.outcome !== 'acquired') {
379
+ const message = `requeue ${verb} for '${slug}': its lock was already released, ` +
380
+ 'but the short lock this recovery needs could not be taken ' +
381
+ (acquired.outcome === 'lost'
382
+ ? '(another run claimed the item meanwhile); nothing was changed.'
383
+ : `(${acquired.message}); nothing was changed. Try again shortly.`);
384
+ note(message);
385
+ return { moved: false, reasonNotMoved: message };
386
+ }
387
+ shortLock = true;
388
+ note(`'${slug}' has no held lock (a surface or earlier requeue already ` +
389
+ `released it); took a short lock to ${options.reset ? 'discard' : 'reconcile'} ` +
390
+ `the kept ${continueBranchName} (${verb}).`);
391
+ }
392
+ }
393
+ if (!held && !shortLock) {
314
394
  // CROSS-NAMESPACE HINT (observation
315
395
  // `crashed-do-spec-strands-a-tasking-lock-no-verb-releases`). `requeue` is a
316
396
  // TASK-only verb, so a bare `<slug>` resolves to `task:<slug>` and finds no
@@ -351,15 +431,139 @@ export async function returnToBacklog(options) {
351
431
  catch {
352
432
  // Best-effort hint only: fall through to the plain refusal.
353
433
  }
434
+ // A released item that DOES rest in the pool with a kept branch (plain
435
+ // requeue only; `--reset`/`--reconcile` took the short-lock path above):
436
+ // nothing to release, and the next claim already continues from the branch.
437
+ // Say so, and name the two verbs that DO act on a released item.
438
+ const keptHint = releasedRecoverable !== undefined
439
+ ? ` NOTE: '${slug}' rests in the pool (${releasedRecoverable.bodyRel}) ` +
440
+ `with a kept work branch ${continueBranchName} on ${arbiter}; the ` +
441
+ 'next claim continues from it, so plain requeue has nothing to do. ' +
442
+ 'To act on the kept branch: `requeue --reconcile` (non-destructive ' +
443
+ 're-sync + rebase retry) or `requeue --reset` (DESTRUCTIVELY discard ' +
444
+ 'it and start fresh).'
445
+ : '';
354
446
  return {
355
447
  moved: false,
356
448
  reasonNotMoved: `'${slug}' has no held per-item lock on ${arbiter} — nothing to requeue ` +
357
449
  '(wrong slug, or already at rest in backlog/done?). requeue recovers a ' +
358
450
  'task whose lock is held stuck (needs-attention) or active (a killed ' +
359
451
  'in-progress run).' +
360
- specHint,
452
+ specHint +
453
+ keptHint,
361
454
  };
362
455
  }
456
+ // CRASHED-RUN SAVE (task
457
+ // `a-crashed-runs-local-work-is-saved-before-its-lock-is-released`). A run
458
+ // killed mid-agent leaves its lock held and its job worktree retained, possibly
459
+ // with commits (or a wip residue) the arbiter's work branch lacks. Releasing
460
+ // the lock over them hands the item to a next claim (or a `gc --force`) that
461
+ // cuts that worktree away, so SAVE them first: wip-commit + push the work
462
+ // branch. A failed save KEEPS the lock (and the worktree) and says so. Only on
463
+ // a HELD lock (a short recovery lock means the run already surfaced, its lock
464
+ // released) and never on `--reset` (which discards the branch by design).
465
+ let crashedRunSave;
466
+ if (held && !options.reset && options.workspacesDir !== undefined) {
467
+ crashedRunSave = await saveCrashedRunWork({
468
+ cwd,
469
+ arbiter,
470
+ slug,
471
+ workspacesDir: options.workspacesDir,
472
+ env,
473
+ note,
474
+ ...(options.sleep !== undefined ? { sleep: options.sleep } : {}),
475
+ });
476
+ if (crashedRunSave.kind === 'not-saved') {
477
+ const message = `requeue for '${slug}': its crashed run left work in ` +
478
+ `${crashedRunSave.dir} that the arbiter's ${crashedRunSave.branch} ` +
479
+ `lacks, and it could not be saved: ${crashedRunSave.detail}. The lock ` +
480
+ 'is KEPT (not released) and the worktree left in place, so nothing is ' +
481
+ `lost. Push ${crashedRunSave.branch} from that worktree by hand, then ` +
482
+ '`requeue` again.';
483
+ note(message);
484
+ return { moved: false, reasonNotMoved: message, crashedRunSave };
485
+ }
486
+ if (crashedRunSave.kind === 'saved') {
487
+ // The guard below reads the arbiter's branch from THIS clone: bring the
488
+ // just-pushed tip (and its objects) in.
489
+ await refreshArbiterRefs({
490
+ cwd,
491
+ arbiter,
492
+ branches: ['main', continueBranchName],
493
+ env,
494
+ });
495
+ }
496
+ }
497
+ const heldResult = await requeueHeldItem({
498
+ options,
499
+ arbiter,
500
+ continueBranchName,
501
+ note,
502
+ });
503
+ const result = crashedRunSave !== undefined ? { ...heldResult, crashedRunSave } : heldResult;
504
+ if (shortLock && !result.moved) {
505
+ // Give the SHORT lock back: the item returns to exactly the released,
506
+ // surfaced state it was in (its question sidecar untouched). A failed
507
+ // release here is reported, never thrown.
508
+ const back = await releaseItemLock({
509
+ item: `task:${slug}`,
510
+ cwd,
511
+ arbiter,
512
+ env,
513
+ });
514
+ const tail = back.outcome === 'error'
515
+ ? ` WARNING: could not release the short recovery lock this requeue ` +
516
+ `took (${back.message}); clear it with \`dorfl release-lock ` +
517
+ `task:${slug}\`.`
518
+ : ' (The short recovery lock this requeue took was released again; ' +
519
+ 'the item stays as it was before this requeue.)';
520
+ note(tail.trim());
521
+ return { ...result, reasonNotMoved: (result.reasonNotMoved ?? '') + tail };
522
+ }
523
+ return result;
524
+ }
525
+ /**
526
+ * Is `<slug>` a RELEASED-lock task that `requeue --reset`/`--reconcile` can act
527
+ * on? True iff its body rests in `tasks/ready/` or `tasks/backlog/` on
528
+ * `<arbiter>/main` (the D1 probe a surface uses) AND the arbiter HAS its
529
+ * `work/task-<slug>` branch (arbiter-authoritative `ls-remote`). A surfaced item
530
+ * (question sidecar + `needsAnswers:true`, lock released) is the motivating
531
+ * case, but the sidecar is NOT required: a plain requeue that already released
532
+ * the lock leaves the same recoverable shape. An unknown slug (no body) or a
533
+ * pooled item with no kept branch answers `undefined`, so the refusal for those
534
+ * is unchanged. Best-effort: an unreachable arbiter answers `undefined`.
535
+ */
536
+ async function probeReleasedRecoverableItem(params) {
537
+ const { cwd, slug, arbiter, branch, env } = params;
538
+ try {
539
+ const bodyRel = await resolveBounceItemBodyPathOnMain({
540
+ cwd,
541
+ item: `task:${slug}`,
542
+ arbiter,
543
+ env,
544
+ });
545
+ if (bodyRel === undefined) {
546
+ return undefined;
547
+ }
548
+ const resolved = await resolveArbiterBranch({ cwd, arbiter, branch, env });
549
+ if (!resolved.trustworthy || resolved.sha === undefined) {
550
+ return undefined;
551
+ }
552
+ return { bodyRel, branchSha: resolved.sha };
553
+ }
554
+ catch {
555
+ return undefined;
556
+ }
557
+ }
558
+ /**
559
+ * The body of {@link returnToBacklog} once the item's per-item lock is HELD
560
+ * (either a pre-existing stuck/active hold, or the short lock a released-lock
561
+ * `--reset`/`--reconcile` just took): the reconcile / reset / keep+continue
562
+ * guard, the optional handoff note, and the lock release.
563
+ */
564
+ async function requeueHeldItem(params) {
565
+ const { options, arbiter, continueBranchName, note } = params;
566
+ const { cwd, slug, env } = options;
363
567
  // `--reconcile`: the NON-DESTRUCTIVE recovery rung (task
364
568
  // `requeue-reconcile-nondestructive-recovery-verb`, parent observation
365
569
  // `rebase-conflict-on-continue-needs-nondestructive-recovery-not-reset`).
@@ -377,14 +581,6 @@ export async function returnToBacklog(options) {
377
581
  // deletes the remote branch — this verb's contract is "keep the work".
378
582
  let reconciled;
379
583
  if (options.reconcile) {
380
- if (options.reset) {
381
- return {
382
- moved: false,
383
- reasonNotMoved: `requeue for '${slug}': --reconcile and --reset are mutually ` +
384
- 'exclusive (non-destructive recovery vs destructive last resort). ' +
385
- 'Pick one.',
386
- };
387
- }
388
584
  const attempt = await attemptReconcile({
389
585
  cwd,
390
586
  slug,
@@ -396,7 +592,9 @@ export async function returnToBacklog(options) {
396
592
  const branch = workBranchRef('task', slug);
397
593
  const message = `requeue --reconcile for '${slug}': re-synced the ${arbiter} mirror ` +
398
594
  `and RETRIED the rebase of ${branch} onto latest ${arbiter}/main, but ` +
399
- `the rebase still conflicts on genuine content (${attempt.detail}). The ` +
595
+ (attempt.genuineConflict === false
596
+ ? `the rebase still fails (${attempt.detail}). The `
597
+ : `the rebase still conflicts on genuine content (${attempt.detail}). The `) +
400
598
  'kept branch is left UNTOUCHED on the arbiter (nothing deleted) and the ' +
401
599
  'item is left stuck. A supported mirror-side "resolve against latest ' +
402
600
  'main" command that fetches the kept branch into a scratch worktree, ' +
@@ -532,8 +730,9 @@ export async function returnToBacklog(options) {
532
730
  'tasks-ready',
533
731
  'tasks-backlog',
534
732
  ];
535
- // `-m "<note>"` (the handoff steer): APPEND a dated `## Requeue YYYY-MM-DD`
536
- // section to the item BODY where it already rests (pool or staging), via the SAME
733
+ // `-m "<note>"` (the handoff steer): ADD a dated `## Requeue YYYY-MM-DD`
734
+ // section (before `## Acceptance criteria`, clear of a kept branch's done-move
735
+ // tail) to the item BODY where it already rests (pool or staging), via the SAME
537
736
  // tree-less CAS move (same-folder rewrite with the body transform) — it NEVER
538
737
  // stages/commits in the cwd tree. The handoff is OPTIONAL and NON-FATAL: a failed
539
738
  // append degrades to a WARNING and the lock release below STILL runs, because the
@@ -564,7 +763,7 @@ export async function returnToBacklog(options) {
564
763
  base,
565
764
  sourceRel: bodyRel,
566
765
  destRel: bodyRel,
567
- transformBody: (body) => appendRequeueNoteText(body, handoff),
766
+ transformBody: (body) => insertRequeueNoteText(body, handoff),
568
767
  commitMessage: `chore(${slug}): requeue handoff note`,
569
768
  refNamespace: 'requeue',
570
769
  env,
@@ -663,12 +862,20 @@ async function attemptReconcile(params) {
663
862
  }
664
863
  try {
665
864
  const rebase = rebaseContinuedBranchOntoMain(worktree, arbMainRef, env);
666
- if (rebase.kind === 'conflict') {
667
- return { kind: 'conflict', detail: 'rebase conflicted after re-sync' };
865
+ if (rebase.kind !== 'clean') {
866
+ return {
867
+ kind: 'conflict',
868
+ detail: `rebase after re-sync ${describeContinueRebaseFailure(rebase)}`,
869
+ genuineConflict: rebase.kind === 'conflict',
870
+ };
668
871
  }
669
872
  // 4. Push the reconciled tip back (`--force-with-lease`, WORK branch only).
670
873
  const observedTip = gitSoftRun(['rev-parse', '--verify', '--quiet', `${arbBranchRef}^{commit}`], cwd, env).stdout.trim();
671
874
  const lease = observedTip === '' ? `${branch}:` : `${branch}:${observedTip}`;
875
+ // WRITE-SEAM EXEMPT: `attemptReconcile` backs the human-only
876
+ // `requeue --reconcile` verb, never a CI path, so this reconciled
877
+ // kept-branch push stays direct (task
878
+ // `ci-split-route-direct-writes-through-seams`).
672
879
  const push = gitSoftRun([
673
880
  'push',
674
881
  arbiter,
@@ -2055,20 +2262,50 @@ const BOUNCE_BODY_PROBE_ORDER = {
2055
2262
  spec: ['specs-ready', 'specs-proposed'],
2056
2263
  observation: ['observations'],
2057
2264
  };
2265
+ /** The body headings a requeue handoff note is inserted BEFORE, in preference
2266
+ * order (see {@link insertRequeueNoteText}). `## Prompt` is the fallback anchor:
2267
+ * every BUILDABLE task has one (`resolveTask` refuses a body without it), so a
2268
+ * buildable body never falls through to the tail. */
2269
+ const REQUEUE_NOTE_ANCHORS = [
2270
+ /^##\s+Acceptance criteria\s*$/m,
2271
+ /^##\s+Prompt\s*$/m,
2272
+ ];
2058
2273
  /**
2059
- * Append a dated `## Requeue YYYY-MM-DD` handoff section to an item body's TEXT
2060
- * (append-only — never overwrites; repeated requeues accumulate a handoff log).
2061
- * Body prose only (never a frontmatter field — WORK-CONTRACT rule 3). The date is
2062
- * UTC `YYYY-MM-DD`; multiple notes on the same day are distinct appended blocks.
2274
+ * Add a dated `## Requeue YYYY-MM-DD` handoff section to an item body's TEXT
2275
+ * (additive — never overwrites; repeated requeues accumulate a handoff log,
2276
+ * oldest first). Body prose only (never a frontmatter field — WORK-CONTRACT
2277
+ * rule 3). The date is UTC `YYYY-MM-DD`; multiple notes on the same day are
2278
+ * distinct blocks.
2279
+ *
2280
+ * PLACEMENT (task `requeue-handoff-note-does-not-conflict-with-the-kept-done-move`):
2281
+ * the section is inserted immediately BEFORE the `## Acceptance criteria`
2282
+ * heading, NOT at the end of the body. A kept work branch the next claim
2283
+ * CONTINUES from may already have done-moved this body AND appended its
2284
+ * `## Decisions` block at the END; a tail-appended note on `main` then collided
2285
+ * with that tail in the continue rebase (which never auto-resolves, ADR §10) and
2286
+ * bounced the item. Mid-body, the two edits are disjoint hunks and the rebase
2287
+ * merges them cleanly. A body with no `## Acceptance criteria` heading anchors on
2288
+ * `## Prompt` instead (a chore task written without acceptance criteria used to
2289
+ * fall back to the tail and still collide); only a body with NEITHER heading
2290
+ * (which is not buildable anyway) falls back to the end. The continue prompt reads every `## Requeue`
2291
+ * section wherever it sits (`extractRequeueNotes`), so the note still reaches
2292
+ * the continuing agent.
2063
2293
  *
2064
2294
  * A PURE string transform (it operates on the body CONTENT, not a file path) so
2065
2295
  * the tree-less requeue can apply it to the blob read from `<arbiter>/main`
2066
2296
  * without touching the cwd working tree.
2067
2297
  */
2068
- function appendRequeueNoteText(content, message) {
2298
+ export function insertRequeueNoteText(content, message) {
2069
2299
  const date = new Date().toISOString().slice(0, 10);
2070
- const base = content.replace(/\s*$/, '');
2071
- return [base, '', `${REQUEUE_HEADING_PREFIX} ${date}`, '', message, ''].join('\n');
2300
+ const section = `${REQUEUE_HEADING_PREFIX} ${date}\n\n${message}\n`;
2301
+ const anchor = REQUEUE_NOTE_ANCHORS.map((re) => re.exec(content)).find((m) => m !== null);
2302
+ if (anchor === undefined || anchor === null) {
2303
+ const base = content.replace(/\s*$/, '');
2304
+ return `${base}\n\n${section}`;
2305
+ }
2306
+ const before = content.slice(0, anchor.index).replace(/\s*$/, '');
2307
+ const after = content.slice(anchor.index);
2308
+ return `${before}\n\n${section}\n${after}`;
2072
2309
  }
2073
2310
  /**
2074
2311
  * Extract the prose written under the `## Needs attention` heading from an item