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
@@ -0,0 +1,548 @@
1
+ /**
2
+ * **The shared CI phase driver** (spec `ci-agent-job-without-write-token`, ADR
3
+ * `ci-agent-job-holds-no-write-token`, task `ci-split-build-path`).
4
+ *
5
+ * Every CI item runs as three jobs (`phase.ts`): lock, agent, apply. Each path
6
+ * split (`ci-phase-build.ts` first; intake, tasking and the tree-less rungs
7
+ * later) needs the same few steps around its own pipeline halves, and they live
8
+ * here so every split does them the same way:
9
+ *
10
+ * - the lock phase reads `<arbiter>/main` as the base (never the run's own
11
+ * commit), classifies the item in a throwaway worktree of that base, and
12
+ * publishes its trusted facts to `$GITHUB_OUTPUT` (`ci-lock-outputs.ts`);
13
+ * - the agent and apply phases read those facts back from
14
+ * {@link LOCK_OUTPUTS_ENV} and check them;
15
+ * - the lock-ownership rule (spec §8): the agent phase checks, read-only, that
16
+ * the item's lock ref still equals `lockSha` before launching anything, and
17
+ * the apply phase checks it before its FIRST write and writes nothing when
18
+ * it does not; every later lock operation is leased on the sha last observed
19
+ * ({@link releaseLockLeased});
20
+ * - the agent phase bounds the free text it hands over to the handoff limits;
21
+ * - the lock phase computes the agent job's timeout from the config at
22
+ * `baseSha` ({@link agentTimeoutMinutesAt}), and the apply phase decides what
23
+ * to do with the agent job's result before it reads any artifact
24
+ * ({@link resolveAgentResult}, `ci-agent-result.ts`): only `success` reads
25
+ * it (task `ci-split-agent-result-and-reruns`).
26
+ *
27
+ * **A handled outcome is green** (task `a-handled-surface-exits-green-in-ci`):
28
+ * the job's exit code is the only signal a CI run's colour carries, so every
29
+ * phase (build, tasking, tree-less, intake) exits 0 for an outcome it HANDLED
30
+ * and non-zero only for one it did not:
31
+ *
32
+ * - exit 0: the item was surfaced to needs-attention (including a rejected
33
+ * handoff whose surface landed: outcome `rejected` / `surfaced`); a lock
34
+ * phase that backed off because another run holds the lock (`lost`,
35
+ * `backed-off`); an agent or apply phase whose lock is no longer this run's
36
+ * (`stale-lock`, e.g. a "Re-run failed jobs" of a finished run), which
37
+ * writes nothing;
38
+ * - non-zero: a surface that did not land (`surface-unmoved`), a refused or
39
+ * failed publish or release (`stale-lease`, `publish-refused`,
40
+ * `publish-failed`, `release-refused`, `land-failed`), an agent that failed
41
+ * without a handoff (`agent-failed`), a usage or environment problem, and
42
+ * any unexpected error.
43
+ */
44
+
45
+ import {mkdtempSync, rmSync, appendFileSync} from 'node:fs';
46
+ import {tmpdir} from 'node:os';
47
+ import {join} from 'node:path';
48
+ import {
49
+ LOCK_OUTPUTS_ENV,
50
+ readLockOutputsFromEnv,
51
+ serializeLockOutputs,
52
+ type LockOutputs,
53
+ } from './ci-lock-outputs.js';
54
+ import {HandoffRejected, type HandoffRung} from './ci-handoff-format.js';
55
+ import {
56
+ actionsRunRefFromEnv,
57
+ decideAgentResult,
58
+ detectAgentTimeout,
59
+ githubTokenApiGet,
60
+ type AgentJobResult,
61
+ type AgentResultDecision,
62
+ type GithubApiGet,
63
+ } from './ci-agent-result.js';
64
+ import {DEFAULT_CONFIG, type Config} from './config.js';
65
+ import {git, run, runAsync} from './git.js';
66
+ import {pushLfsObjects} from './ci-handoff-lfs.js';
67
+ import type {HandoffLfsObject} from './ci-handoff.js';
68
+ import type {IntegrationCoreInput} from './integration-core.js';
69
+ import {workItemRel} from './work-layout.js';
70
+ import {
71
+ REPO_CONFIG_FILENAME,
72
+ REPO_CONFIG_FILENAME_LEGACY,
73
+ loadRepoConfigFromContent,
74
+ } from './repo-config.js';
75
+ import {itemLockRef, lockEntryFor} from './item-lock.js';
76
+ import {refWrite} from './ref-write.js';
77
+
78
+ export {LOCK_OUTPUTS_ENV};
79
+
80
+ /** A phase run that cannot go on: bad trusted inputs or a missing precondition. */
81
+ export class PhaseDriverError extends Error {
82
+ override readonly name = 'PhaseDriverError';
83
+ }
84
+
85
+ /**
86
+ * The instruction a phase prints when it refuses to act for a lock it no longer
87
+ * owns (spec §8, "Re-runs"): "Re-run failed jobs" replays old lock outputs, and
88
+ * the way to retry an item is a new run, which takes a fresh lock.
89
+ */
90
+ export const STALE_LOCK_RETRY_HINT =
91
+ 'The lock was released, reaped or re-taken since the lock job ran (for ' +
92
+ 'example a "Re-run failed jobs" of a finished run). Nothing was written. To ' +
93
+ 'retry the item, start a NEW run (the next tick, or a dispatch), which takes ' +
94
+ 'a fresh lock; do not re-run the old jobs.';
95
+
96
+ /** Git environment for any checkout of hostile or unverified content: never smudge LFS. */
97
+ export function noSmudgeEnv(
98
+ env: NodeJS.ProcessEnv | undefined,
99
+ ): NodeJS.ProcessEnv {
100
+ return {...(env ?? process.env), GIT_LFS_SKIP_SMUDGE: '1'};
101
+ }
102
+
103
+ /**
104
+ * Fetch the arbiter's `main` into `<arbiter>/main` and return its tip: the
105
+ * phase's `baseSha`. The lock phase classifies against THIS tip, never the
106
+ * run's `github.sha` (a queued run may start hours after its dispatch).
107
+ */
108
+ export async function fetchArbiterMain(params: {
109
+ cwd: string;
110
+ arbiter: string;
111
+ env?: NodeJS.ProcessEnv;
112
+ }): Promise<string> {
113
+ const {cwd, arbiter, env} = params;
114
+ await gitAsyncHard(
115
+ [
116
+ 'fetch',
117
+ '--quiet',
118
+ '--no-tags',
119
+ arbiter,
120
+ `+refs/heads/main:refs/remotes/${arbiter}/main`,
121
+ ],
122
+ cwd,
123
+ env,
124
+ );
125
+ return (
126
+ await gitAsyncHard(
127
+ ['rev-parse', '--verify', `refs/remotes/${arbiter}/main^{commit}`],
128
+ cwd,
129
+ env,
130
+ )
131
+ ).trim();
132
+ }
133
+
134
+ /**
135
+ * Run `fn` with a throwaway, detached worktree of `baseSha` (LFS smudging off),
136
+ * removed afterwards. The lock phase reads the ledger (the item's folder, its
137
+ * frontmatter, its sidecar) from here, so a checkout behind the arbiter cannot
138
+ * make it act on stale state. Only files are read: no repository code runs.
139
+ */
140
+ export async function withBaseWorktree<T>(
141
+ params: {cwd: string; baseSha: string; env?: NodeJS.ProcessEnv},
142
+ fn: (dir: string) => Promise<T>,
143
+ ): Promise<T> {
144
+ const {cwd, baseSha} = params;
145
+ const env = noSmudgeEnv(params.env);
146
+ const parent = mkdtempSync(join(tmpdir(), 'dorfl-base-'));
147
+ const dir = join(parent, 'tree');
148
+ await gitAsyncHard(
149
+ ['worktree', 'add', '--quiet', '--detach', dir, baseSha],
150
+ cwd,
151
+ env,
152
+ );
153
+ try {
154
+ return await fn(dir);
155
+ } finally {
156
+ await runAsync('git', ['worktree', 'remove', '--force', dir], cwd, {env});
157
+ rmSync(parent, {recursive: true, force: true});
158
+ await runAsync('git', ['worktree', 'prune'], cwd, {env});
159
+ }
160
+ }
161
+
162
+ /** The sha of `ref` on the arbiter (`git ls-remote`, read-only), or `undefined`. */
163
+ export async function arbiterRefSha(params: {
164
+ cwd: string;
165
+ arbiter: string;
166
+ ref: string;
167
+ env?: NodeJS.ProcessEnv;
168
+ }): Promise<string | undefined> {
169
+ const out = await gitAsyncHard(
170
+ ['ls-remote', params.arbiter, params.ref],
171
+ params.cwd,
172
+ params.env,
173
+ );
174
+ for (const line of out.split('\n')) {
175
+ const [sha, name] = line.split('\t');
176
+ if (name === params.ref && sha !== undefined && sha !== '') return sha;
177
+ }
178
+ return undefined;
179
+ }
180
+
181
+ /** The item's lock ref sha on the arbiter (read-only), or `undefined` when released. */
182
+ export function arbiterLockSha(params: {
183
+ cwd: string;
184
+ arbiter: string;
185
+ item: string;
186
+ env?: NodeJS.ProcessEnv;
187
+ }): Promise<string | undefined> {
188
+ return arbiterRefSha({
189
+ ...params,
190
+ ref: itemLockRef(lockEntryFor(params.item)),
191
+ });
192
+ }
193
+
194
+ /** The result of {@link checkLockOwnership}. */
195
+ export type LockOwnership =
196
+ | {owned: true}
197
+ | {owned: false; current: string | undefined; message: string};
198
+
199
+ /**
200
+ * The lock-ownership rule (spec §8): does the item's lock ref on the arbiter
201
+ * still equal `lockSha`, the sha the lock job produced? Read-only. The lock
202
+ * `holder` is the same bot for every CI run, so the sha is what tells runs
203
+ * apart. When it does not match, the message carries the retry instruction.
204
+ */
205
+ export async function checkLockOwnership(params: {
206
+ cwd: string;
207
+ arbiter: string;
208
+ item: string;
209
+ lockSha: string;
210
+ env?: NodeJS.ProcessEnv;
211
+ }): Promise<LockOwnership> {
212
+ const current = await arbiterLockSha(params);
213
+ if (current === params.lockSha) return {owned: true};
214
+ const now =
215
+ current === undefined ? 'is released' : `now points at ${current}`;
216
+ return {
217
+ owned: false,
218
+ current,
219
+ message:
220
+ `the lock of ${params.item} ${now}, not the lock job's ` +
221
+ `${params.lockSha}. ${STALE_LOCK_RETRY_HINT}`,
222
+ };
223
+ }
224
+
225
+ /**
226
+ * Release the item's lock with a LEASE on `expectedSha`, the sha this phase last
227
+ * observed, so a phase never releases a lock another run holds now. Returns
228
+ * whether the ref was deleted (a lost lease leaves it untouched).
229
+ */
230
+ export async function releaseLockLeased(params: {
231
+ cwd: string;
232
+ arbiter: string;
233
+ item: string;
234
+ expectedSha: string;
235
+ env?: NodeJS.ProcessEnv;
236
+ }): Promise<{released: boolean; message: string}> {
237
+ const ref = itemLockRef(lockEntryFor(params.item));
238
+ const del = await refWrite.deleteLockRef({
239
+ arbiter: params.arbiter,
240
+ ref,
241
+ expectedSha: params.expectedSha,
242
+ cwd: params.cwd,
243
+ env: params.env,
244
+ });
245
+ if (del.status === 0) {
246
+ await runAsync('git', ['update-ref', '-d', ref], params.cwd, {
247
+ env: params.env,
248
+ });
249
+ return {released: true, message: `released ${ref}`};
250
+ }
251
+ return {
252
+ released: false,
253
+ message: `the leased release of ${ref} was rejected: ${del.stderr.trim()}`,
254
+ };
255
+ }
256
+
257
+ /**
258
+ * Publish the lock phase's facts: append them to the `$GITHUB_OUTPUT` file when
259
+ * one is given, else print them (a local replay). The serializer refuses any
260
+ * value that is not a sha, an enum, a boolean, a bounded integer or a
261
+ * dorfl-derived name.
262
+ */
263
+ export function emitLockOutputs(
264
+ facts: LockOutputs,
265
+ params: {githubOutput?: string; note: (message: string) => void},
266
+ ): void {
267
+ const lines = serializeLockOutputs(facts);
268
+ if (params.githubOutput !== undefined && params.githubOutput !== '') {
269
+ appendFileSync(params.githubOutput, lines);
270
+ } else {
271
+ params.note(`lock outputs:\n${lines}`);
272
+ }
273
+ }
274
+
275
+ /** The lock outputs an agent or apply phase acts on, checked for its rung. */
276
+ export interface HeldLock {
277
+ baseSha: string;
278
+ lockSha: string;
279
+ rung: HandoffRung;
280
+ continueTip?: string;
281
+ handoffName?: string;
282
+ needsAgent?: boolean;
283
+ agentTimeoutMinutes?: number;
284
+ }
285
+
286
+ /**
287
+ * Read the lock outputs (given, or from {@link LOCK_OUTPUTS_ENV}) and require
288
+ * what an agent or apply phase needs: the lock was acquired, on `rung`, with a
289
+ * base and a lock sha. Throws {@link PhaseDriverError} otherwise.
290
+ */
291
+ export function requireHeldLock(params: {
292
+ lockOutputs?: LockOutputs;
293
+ env: NodeJS.ProcessEnv;
294
+ rung: HandoffRung;
295
+ }): HeldLock {
296
+ const facts = params.lockOutputs ?? readLockOutputsFromEnv(params.env);
297
+ if (facts.acquired !== true) {
298
+ throw new PhaseDriverError(
299
+ 'the lock job did not acquire the item (acquired != true); nothing to do',
300
+ );
301
+ }
302
+ if (facts.rung !== params.rung) {
303
+ throw new PhaseDriverError(
304
+ `the lock job classified the rung ${String(facts.rung)}, not ${params.rung}`,
305
+ );
306
+ }
307
+ if (facts.baseSha === undefined || facts.lockSha === undefined) {
308
+ throw new PhaseDriverError(
309
+ 'the lock outputs carry no baseSha or no lockSha',
310
+ );
311
+ }
312
+ return {
313
+ baseSha: facts.baseSha,
314
+ lockSha: facts.lockSha,
315
+ rung: facts.rung,
316
+ continueTip: facts.continueTip,
317
+ handoffName: facts.handoffName,
318
+ needsAgent: facts.needsAgent,
319
+ agentTimeoutMinutes: facts.agentTimeoutMinutes,
320
+ };
321
+ }
322
+
323
+ /** Control characters the handoff record refuses (all but tab, LF, CR). */
324
+ // eslint-disable-next-line no-control-regex
325
+ const HANDOFF_TEXT_CONTROL_RE =
326
+ /[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g;
327
+
328
+ /**
329
+ * Bound one free-text product for the handoff record: drop the control
330
+ * characters the record refuses (an agent's output may carry terminal escapes)
331
+ * and cut it to `maxChars`, marking the cut. The apply phase re-validates it;
332
+ * this only keeps a long but honest summary from failing the whole handoff.
333
+ */
334
+ export function boundHandoffText(text: string, maxChars: number): string {
335
+ const clean = text.replace(HANDOFF_TEXT_CONTROL_RE, '');
336
+ if (clean.length <= maxChars) return clean;
337
+ const marker = '\n\n[truncated by dorfl for the CI handoff]';
338
+ return clean.slice(0, Math.max(0, maxChars - marker.length)) + marker;
339
+ }
340
+
341
+ /**
342
+ * The `$RUNNER_TEMP` the apply phase reads the artifact under (the handoff
343
+ * reader refuses a directory outside it).
344
+ */
345
+ export function runnerTempFrom(
346
+ given: string | undefined,
347
+ env: NodeJS.ProcessEnv,
348
+ ): string {
349
+ const dir = given ?? env.RUNNER_TEMP;
350
+ if (dir === undefined || dir === '') {
351
+ throw new PhaseDriverError(
352
+ 'RUNNER_TEMP is not set (the apply phase reads the handoff only from under it)',
353
+ );
354
+ }
355
+ return dir;
356
+ }
357
+
358
+ /**
359
+ * The repository config committed at `baseSha` (`dorfl.json`, else the legacy
360
+ * dotfile): the trusted config a phase acts on, never the checkout's or a
361
+ * bundle's. `undefined` when neither file exists or the first one found does
362
+ * not parse.
363
+ */
364
+ export function repoConfigAt(
365
+ cwd: string,
366
+ baseSha: string,
367
+ env: NodeJS.ProcessEnv | undefined,
368
+ ): Partial<Config> | undefined {
369
+ for (const name of [REPO_CONFIG_FILENAME, REPO_CONFIG_FILENAME_LEGACY]) {
370
+ const r = run('git', ['cat-file', 'blob', `${baseSha}:${name}`], cwd, {
371
+ env,
372
+ });
373
+ if (r.status !== 0) continue;
374
+ try {
375
+ return loadRepoConfigFromContent(r.stdout, `${baseSha}:${name}`).config;
376
+ } catch {
377
+ return undefined;
378
+ }
379
+ }
380
+ return undefined;
381
+ }
382
+
383
+ /**
384
+ * The agent job's timeout in minutes, as the lock phase publishes it
385
+ * (`agentTimeoutMinutes`): today's `agentDeadlineMinutes +
386
+ * checkpointHeadroomMinutes`, each read from the config at `baseSha` and falling
387
+ * back to its default (60 and 30) when absent or outside its valid range
388
+ * ([1, 240] and [10, 60]).
389
+ */
390
+ export function agentTimeoutMinutesAt(
391
+ cwd: string,
392
+ baseSha: string,
393
+ env: NodeJS.ProcessEnv | undefined,
394
+ ): number {
395
+ const config = repoConfigAt(cwd, baseSha, env) ?? {};
396
+ const pick = (v: unknown, min: number, max: number, fallback: number) =>
397
+ typeof v === 'number' && Number.isInteger(v) && v >= min && v <= max
398
+ ? v
399
+ : fallback;
400
+ return (
401
+ pick(
402
+ config.agentDeadlineMinutes,
403
+ 1,
404
+ 240,
405
+ DEFAULT_CONFIG.agentDeadlineMinutes,
406
+ ) +
407
+ pick(
408
+ config.checkpointHeadroomMinutes,
409
+ 10,
410
+ 60,
411
+ DEFAULT_CONFIG.checkpointHeadroomMinutes,
412
+ )
413
+ );
414
+ }
415
+
416
+ /**
417
+ * Decide what the apply phase does with the agent job's result (decision 5),
418
+ * BEFORE any artifact is read. For `cancelled` it reads the agent job from the
419
+ * Actions API with `GITHUB_TOKEN` (or the injected `api`) to tell a timeout from
420
+ * a real cancel. `agentTimeoutMinutes` is the trusted lock output
421
+ * (`--agent-timeout-minutes`, else the lock outputs), never the artifact's.
422
+ * Nothing it reads from the API is ever printed.
423
+ */
424
+ export function resolveAgentResult(params: {
425
+ result: AgentJobResult;
426
+ held: HeldLock;
427
+ agentTimeoutMinutes?: number;
428
+ api?: GithubApiGet;
429
+ env: NodeJS.ProcessEnv;
430
+ }): Promise<AgentResultDecision> {
431
+ return decideAgentResult({
432
+ result: params.result,
433
+ needsAgent: params.held.needsAgent,
434
+ detectTimeout: () =>
435
+ detectAgentTimeout({
436
+ get: params.api ?? githubTokenApiGet(params.env),
437
+ run: actionsRunRefFromEnv(params.env),
438
+ agentTimeoutMinutes:
439
+ params.agentTimeoutMinutes ?? params.held.agentTimeoutMinutes,
440
+ }),
441
+ });
442
+ }
443
+
444
+ /** The result of {@link pushHandoffLfs}. */
445
+ export type HandoffLfsPush =
446
+ | {ok: true}
447
+ | {
448
+ ok: false;
449
+ /** True when an object broke a handoff rule (the handoff is rejected). */
450
+ rejected: boolean;
451
+ reason: string;
452
+ };
453
+
454
+ /**
455
+ * Run a shared lock acquire whose refusal is BOTH noted and returned as its
456
+ * `message`, which the phase returns as its result line and the CLI prints:
457
+ * its notes are held back until it returns, then every one is passed on except
458
+ * a refusal's own message, so that line appears once in the job log (task
459
+ * `ci-phase-logs-each-line-once`). An acquire is one short arbiter round trip,
460
+ * so holding its notes back does not delay the log noticeably.
461
+ */
462
+ export async function acquireNotingOnce<
463
+ R extends {exitCode: number; message: string},
464
+ >(
465
+ note: (message: string) => void,
466
+ acquire: (note: (message: string) => void) => Promise<R>,
467
+ ): Promise<R> {
468
+ const held: string[] = [];
469
+ const result = await acquire((m) => held.push(m));
470
+ for (const m of held) {
471
+ if (result.exitCode === 0 || m !== result.message) note(m);
472
+ }
473
+ return result;
474
+ }
475
+
476
+ /**
477
+ * Push the validated handoff's Git LFS objects to the arbiter (decision 6),
478
+ * the apply phase's FIRST write: before any ref of any intent (a continue push,
479
+ * the land's pushes, a WIP branch save), so a ref never lands pointing at a
480
+ * missing object. Pushed from the apply checkout of the trusted base, so
481
+ * `.lfsconfig` and the endpoint come from main. The caller surfaces the item on
482
+ * a failure (`rejected` when an object broke a handoff rule).
483
+ */
484
+ export async function pushHandoffLfs(params: {
485
+ cwd: string;
486
+ arbiter: string;
487
+ objects: readonly HandoffLfsObject[];
488
+ env: NodeJS.ProcessEnv;
489
+ note: (message: string) => void;
490
+ }): Promise<HandoffLfsPush> {
491
+ const {cwd, arbiter, objects, env, note} = params;
492
+ if (objects.length === 0) return {ok: true};
493
+ let pushed: {status: number; stderr: string};
494
+ try {
495
+ pushed = await pushLfsObjects({cwd, arbiter, objects, env});
496
+ } catch (err) {
497
+ if (!(err instanceof HandoffRejected)) throw err;
498
+ note(err.message);
499
+ return {ok: false, rejected: true, reason: err.message};
500
+ }
501
+ if (pushed.status !== 0) {
502
+ const reason =
503
+ `the ${objects.length} Git LFS object(s) of the handoff ` +
504
+ `could not be pushed to ${arbiter} (${pushed.stderr.trim().slice(0, 500)}), ` +
505
+ 'so no branch of the work was pushed';
506
+ note(reason);
507
+ return {ok: false, rejected: false, reason};
508
+ }
509
+ note(`pushed ${objects.length} Git LFS object(s) to ${arbiter}`);
510
+ return {ok: true};
511
+ }
512
+
513
+ /**
514
+ * The done-move source folder of the task AT `baseSha` (trusted), which picks
515
+ * the land half's ledger reconcile arms exactly as the agent's `complete` did.
516
+ */
517
+ export function taskSourceAtBase(
518
+ cwd: string,
519
+ baseSha: string,
520
+ slug: string,
521
+ env: NodeJS.ProcessEnv,
522
+ ): IntegrationCoreInput['source'] {
523
+ const folders = ['tasks-ready', 'tasks-backlog', 'done'] as const;
524
+ for (const folder of folders) {
525
+ const spec = `${baseSha}:${workItemRel(folder, `${slug}.md`)}`;
526
+ try {
527
+ git(['cat-file', '-e', spec], cwd, {env});
528
+ return folder;
529
+ } catch {
530
+ // not in this folder
531
+ }
532
+ }
533
+ return 'tasks-ready';
534
+ }
535
+
536
+ async function gitAsyncHard(
537
+ args: string[],
538
+ cwd: string,
539
+ env: NodeJS.ProcessEnv | undefined,
540
+ ): Promise<string> {
541
+ const r = await runAsync('git', args, cwd, {env});
542
+ if (r.status !== 0) {
543
+ throw new Error(
544
+ `git ${args.join(' ')} failed (exit ${r.status}): ${r.stderr.trim()}`,
545
+ );
546
+ }
547
+ return r.stdout;
548
+ }