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,421 @@
1
+ /**
2
+ * **What the apply phase does with the agent job's result** (spec
3
+ * `ci-agent-job-without-write-token` §8 "Agent result", decision 5 of ADR
4
+ * `ci-agent-job-holds-no-write-token`, task `ci-split-agent-result-and-reruns`).
5
+ *
6
+ * The apply job receives `needs.agent.result` (`--agent-result`). It is part of
7
+ * the HOSTILE channel: `success` is only the precondition for reading the
8
+ * artifact, never evidence that its content is good. Every other result never
9
+ * reads the artifact:
10
+ *
11
+ * - `failure`: surface the item to needs-attention (the lock is released);
12
+ * - `cancelled`: GitHub reports a TIMED-OUT job as `cancelled` too (measured,
13
+ * finding `github-actions-job-timeout-reported-as-cancelled`), so the apply
14
+ * job reads the agent job from the Actions API ({@link detectAgentTimeout}).
15
+ * A timeout surfaces; a real cancel only releases the lock; anything it
16
+ * cannot read surfaces (the safe side);
17
+ * - `skipped`: accepted only when the lock job said `needsAgent: false` (the
18
+ * apply job then runs the deterministic rung itself); otherwise it is a
19
+ * failure.
20
+ *
21
+ * This module DECIDES; each path split acts on the decision (its surface and
22
+ * its release differ: a task lock, an intake label).
23
+ */
24
+
25
+ /** The values of `needs.<job>.result` GitHub Actions reports. */
26
+ export const AGENT_JOB_RESULTS = [
27
+ 'success',
28
+ 'failure',
29
+ 'cancelled',
30
+ 'skipped',
31
+ ] as const;
32
+
33
+ /** `needs.agent.result`, as the apply job receives it. */
34
+ export type AgentJobResult = (typeof AGENT_JOB_RESULTS)[number];
35
+
36
+ /** A malformed `--agent-result` or `--agent-timeout-minutes`. */
37
+ export class AgentResultUsageError extends Error {
38
+ override readonly name = 'AgentResultUsageError';
39
+ }
40
+
41
+ /** Parse `--agent-result` (exactly one of {@link AGENT_JOB_RESULTS}). */
42
+ export function parseAgentJobResult(raw: string): AgentJobResult {
43
+ if ((AGENT_JOB_RESULTS as readonly string[]).includes(raw)) {
44
+ return raw as AgentJobResult;
45
+ }
46
+ throw new AgentResultUsageError(
47
+ `--agent-result must be one of ${AGENT_JOB_RESULTS.join(', ')} ` +
48
+ `(needs.agent.result); got ${JSON.stringify(raw.slice(0, 40))}`,
49
+ );
50
+ }
51
+
52
+ /** Parse `--agent-timeout-minutes` (a positive integer, the lock output). */
53
+ export function parseAgentTimeoutMinutes(raw: string): number {
54
+ const n = /^[0-9]{1,9}$/.test(raw) ? Number(raw) : Number.NaN;
55
+ if (!Number.isInteger(n) || n < 1) {
56
+ throw new AgentResultUsageError(
57
+ '--agent-timeout-minutes must be a positive integer ' +
58
+ '(needs.lock.outputs.agentTimeoutMinutes)',
59
+ );
60
+ }
61
+ return n;
62
+ }
63
+
64
+ /**
65
+ * The fragment of GitHub's timeout annotation ("The job has exceeded the maximum
66
+ * execution time of <duration>"), matched as a substring only.
67
+ */
68
+ export const TIMEOUT_ANNOTATION_FRAGMENT =
69
+ 'exceeded the maximum execution time';
70
+
71
+ /**
72
+ * The tolerance under `agentTimeoutMinutes` at which the agent job's duration
73
+ * already counts as a timeout: the job clock may start after `started_at`.
74
+ */
75
+ export const TIMEOUT_TOLERANCE_MS = 60_000;
76
+
77
+ /** The most pages {@link detectAgentTimeout} reads from one listing. */
78
+ export const MAX_API_PAGES = 50;
79
+
80
+ /**
81
+ * One GET against the GitHub REST API: the status, the parsed JSON body and the
82
+ * `rel="next"` page URL from the `Link` header. Tests stub it.
83
+ */
84
+ export type GithubApiGet = (
85
+ url: string,
86
+ ) => Promise<{status: number; body: unknown; next?: string}>;
87
+
88
+ /** How a cancelled agent job ended, as far as the Actions API tells. */
89
+ export type CancelVerdict =
90
+ /** The job hit its `timeout-minutes` (either signal said so). */
91
+ | {kind: 'timeout'; why: string}
92
+ /** A real cancel (a human, or a newer run's concurrency). */
93
+ | {kind: 'cancel'; why: string}
94
+ /** The API could not be read, or the job not found: treated as a timeout. */
95
+ | {kind: 'unreadable'; why: string};
96
+
97
+ /** Where the Actions API reads of {@link detectAgentTimeout} go. */
98
+ export interface ActionsRunRef {
99
+ /** `GITHUB_API_URL` (default `https://api.github.com`). */
100
+ apiUrl: string;
101
+ /** `GITHUB_REPOSITORY` (`owner/repo`). */
102
+ repository: string;
103
+ /** `GITHUB_RUN_ID`. */
104
+ runId: string;
105
+ /** `GITHUB_RUN_ATTEMPT`. */
106
+ runAttempt: string;
107
+ }
108
+
109
+ /**
110
+ * The run this apply job belongs to, from the Actions environment, or
111
+ * `undefined` when any part is missing (the caller then surfaces).
112
+ */
113
+ export function actionsRunRefFromEnv(
114
+ env: NodeJS.ProcessEnv,
115
+ ): ActionsRunRef | undefined {
116
+ const repository = env.GITHUB_REPOSITORY;
117
+ const runId = env.GITHUB_RUN_ID;
118
+ const runAttempt = env.GITHUB_RUN_ATTEMPT;
119
+ if (
120
+ repository === undefined ||
121
+ !/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(repository) ||
122
+ runId === undefined ||
123
+ !/^[0-9]+$/.test(runId) ||
124
+ runAttempt === undefined ||
125
+ !/^[0-9]+$/.test(runAttempt)
126
+ ) {
127
+ return undefined;
128
+ }
129
+ const apiUrl = (env.GITHUB_API_URL ?? 'https://api.github.com').replace(
130
+ /\/+$/,
131
+ '',
132
+ );
133
+ return {apiUrl, repository, runId, runAttempt};
134
+ }
135
+
136
+ /**
137
+ * A {@link GithubApiGet} over `fetch` authenticated with `GITHUB_TOKEN` ONLY:
138
+ * the job's own token, which the apply job grants `actions: read` and
139
+ * `checks: read`. Never `GH_TOKEN` / `DORFL_GH_TOKEN` (the write credential has
140
+ * no business reading this). `undefined` when `GITHUB_TOKEN` is not set.
141
+ */
142
+ export function githubTokenApiGet(
143
+ env: NodeJS.ProcessEnv,
144
+ ): GithubApiGet | undefined {
145
+ const token = env.GITHUB_TOKEN?.trim();
146
+ if (token === undefined || token === '') return undefined;
147
+ return async (url) => {
148
+ const res = await fetch(url, {
149
+ headers: {
150
+ accept: 'application/vnd.github+json',
151
+ authorization: `Bearer ${token}`,
152
+ 'x-github-api-version': '2022-11-28',
153
+ },
154
+ });
155
+ let body: unknown;
156
+ try {
157
+ body = await res.json();
158
+ } catch {
159
+ body = undefined;
160
+ }
161
+ const link = res.headers.get('link') ?? '';
162
+ const next = /<([^>]+)>;\s*rel="next"/.exec(link)?.[1];
163
+ return {status: res.status, body, next};
164
+ };
165
+ }
166
+
167
+ /**
168
+ * GET every page of a listing (following `rel="next"`), handing each page's
169
+ * body to `onPage`. Throws on a non-200 page, on a next URL outside `apiUrl`,
170
+ * or past {@link MAX_API_PAGES}.
171
+ */
172
+ async function eachPage(
173
+ get: GithubApiGet,
174
+ apiUrl: string,
175
+ first: string,
176
+ onPage: (body: unknown) => void,
177
+ ): Promise<void> {
178
+ let url: string | undefined = first;
179
+ for (let page = 1; url !== undefined; page++) {
180
+ if (page > MAX_API_PAGES) {
181
+ throw new Error(`more than ${MAX_API_PAGES} pages`);
182
+ }
183
+ if (!url.startsWith(`${apiUrl}/`)) {
184
+ throw new Error('a next page points outside the API');
185
+ }
186
+ const r = await get(url);
187
+ if (r.status !== 200) throw new Error(`HTTP ${r.status}`);
188
+ onPage(r.body);
189
+ url = r.next;
190
+ }
191
+ }
192
+
193
+ /** One agent job, as the jobs API describes it (only the fields read). */
194
+ interface AgentJob {
195
+ id: number;
196
+ startedAt?: string;
197
+ completedAt?: string;
198
+ }
199
+
200
+ /**
201
+ * Is this job the item's agent job? Inside a called workflow the job is named
202
+ * `<caller job> / agent`; a workflow that is not called names it `agent`.
203
+ */
204
+ export function isAgentJobName(name: string): boolean {
205
+ return name === 'agent' || name.endsWith(' / agent');
206
+ }
207
+
208
+ /**
209
+ * Tell a timed-out agent job from a cancelled one (decision 5). Two signals,
210
+ * either one meaning timeout:
211
+ *
212
+ * - an annotation of the agent job's check run containing
213
+ * {@link TIMEOUT_ANNOTATION_FRAGMENT}, read across EVERY page;
214
+ * - the job's `completed_at - started_at` (written by GitHub, not the agent)
215
+ * reaching `agentTimeoutMinutes` minus {@link TIMEOUT_TOLERANCE_MS}.
216
+ *
217
+ * Anything it cannot establish (no token, an API error, no single agent job in
218
+ * this run attempt, no timestamps, no trusted timeout) is `unreadable`, which
219
+ * the caller surfaces. Annotation text is agent-controllable (a job can write
220
+ * `::error::` lines): it is only substring-matched, and NEVER copied into the
221
+ * verdict, a note or the output.
222
+ */
223
+ export async function detectAgentTimeout(params: {
224
+ get: GithubApiGet | undefined;
225
+ run: ActionsRunRef | undefined;
226
+ agentTimeoutMinutes: number | undefined;
227
+ }): Promise<CancelVerdict> {
228
+ const {get, run, agentTimeoutMinutes} = params;
229
+ if (get === undefined) {
230
+ return {
231
+ kind: 'unreadable',
232
+ why: 'GITHUB_TOKEN is not set, so the agent job could not be read',
233
+ };
234
+ }
235
+ if (run === undefined) {
236
+ return {
237
+ kind: 'unreadable',
238
+ why:
239
+ 'GITHUB_REPOSITORY, GITHUB_RUN_ID or GITHUB_RUN_ATTEMPT is not set, ' +
240
+ 'so the agent job could not be read',
241
+ };
242
+ }
243
+ const repo = `${run.apiUrl}/repos/${run.repository}`;
244
+
245
+ const agents: AgentJob[] = [];
246
+ try {
247
+ await eachPage(
248
+ get,
249
+ run.apiUrl,
250
+ `${repo}/actions/runs/${run.runId}/attempts/${run.runAttempt}/jobs?per_page=100`,
251
+ (body) => {
252
+ const jobs = (body as {jobs?: unknown} | undefined)?.jobs;
253
+ if (!Array.isArray(jobs)) throw new Error('no jobs list');
254
+ for (const job of jobs as Array<Record<string, unknown>>) {
255
+ if (typeof job.name !== 'string' || !isAgentJobName(job.name)) {
256
+ continue;
257
+ }
258
+ if (typeof job.id !== 'number' || !Number.isSafeInteger(job.id)) {
259
+ throw new Error('an agent job without an id');
260
+ }
261
+ agents.push({
262
+ id: job.id,
263
+ startedAt:
264
+ typeof job.started_at === 'string' ? job.started_at : undefined,
265
+ completedAt:
266
+ typeof job.completed_at === 'string'
267
+ ? job.completed_at
268
+ : undefined,
269
+ });
270
+ }
271
+ },
272
+ );
273
+ } catch (err) {
274
+ return {
275
+ kind: 'unreadable',
276
+ why: `the jobs of run ${run.runId} attempt ${run.runAttempt} could not be read (${errorText(err)})`,
277
+ };
278
+ }
279
+ if (agents.length !== 1) {
280
+ return {
281
+ kind: 'unreadable',
282
+ why:
283
+ `run ${run.runId} attempt ${run.runAttempt} has ${agents.length} ` +
284
+ `jobs named '/ agent', not exactly one`,
285
+ };
286
+ }
287
+ const agent = agents[0]!;
288
+
289
+ // Signal 1: the duration GitHub recorded (the agent cannot write it).
290
+ const started = Date.parse(agent.startedAt ?? '');
291
+ const completed = Date.parse(agent.completedAt ?? '');
292
+ let durationKnown = false;
293
+ if (Number.isFinite(started) && Number.isFinite(completed)) {
294
+ durationKnown = true;
295
+ if (
296
+ agentTimeoutMinutes !== undefined &&
297
+ completed - started >= agentTimeoutMinutes * 60_000 - TIMEOUT_TOLERANCE_MS
298
+ ) {
299
+ const minutes = Math.floor((completed - started) / 60_000);
300
+ return {
301
+ kind: 'timeout',
302
+ why:
303
+ `the agent job ran ${minutes} minute(s), within a minute of its ` +
304
+ `${agentTimeoutMinutes}-minute timeout`,
305
+ };
306
+ }
307
+ }
308
+
309
+ // Signal 2: GitHub's timeout annotation, on ANY page.
310
+ let annotated = false;
311
+ try {
312
+ await eachPage(
313
+ get,
314
+ run.apiUrl,
315
+ `${repo}/check-runs/${agent.id}/annotations?per_page=100`,
316
+ (body) => {
317
+ if (!Array.isArray(body)) throw new Error('no annotations list');
318
+ for (const a of body as Array<Record<string, unknown>>) {
319
+ for (const field of [a.message, a.title]) {
320
+ if (
321
+ typeof field === 'string' &&
322
+ field.includes(TIMEOUT_ANNOTATION_FRAGMENT)
323
+ ) {
324
+ annotated = true;
325
+ }
326
+ }
327
+ }
328
+ },
329
+ );
330
+ } catch (err) {
331
+ return {
332
+ kind: 'unreadable',
333
+ why: `the annotations of the agent job could not be read (${errorText(err)})`,
334
+ };
335
+ }
336
+ if (annotated) {
337
+ return {
338
+ kind: 'timeout',
339
+ why: "the agent job carries GitHub's timeout annotation",
340
+ };
341
+ }
342
+ if (!durationKnown) {
343
+ return {
344
+ kind: 'unreadable',
345
+ why: 'the agent job has no start or completion time',
346
+ };
347
+ }
348
+ if (agentTimeoutMinutes === undefined) {
349
+ return {
350
+ kind: 'unreadable',
351
+ why: 'no trusted agent timeout was given, so its duration cannot be judged',
352
+ };
353
+ }
354
+ return {
355
+ kind: 'cancel',
356
+ why: 'the agent job was cancelled before its timeout',
357
+ };
358
+ }
359
+
360
+ /** Only dorfl's own words: an error thrown by a listing above, or the status. */
361
+ function errorText(err: unknown): string {
362
+ // Our own thrown messages carry no API text; a fetch failure's message is
363
+ // the runtime's, never the annotation body.
364
+ return err instanceof Error ? err.message.slice(0, 200) : 'unknown error';
365
+ }
366
+
367
+ /** What the apply phase does, given the agent job's result. */
368
+ export type AgentResultDecision =
369
+ /** `success`: read and validate the handoff. */
370
+ | {action: 'read-handoff'}
371
+ /** `skipped` with `needsAgent: false`: run the deterministic rung itself. */
372
+ | {action: 'deterministic'}
373
+ /** Surface the item to needs-attention (and release the lock); no artifact read. */
374
+ | {action: 'surface'; reason: string}
375
+ /** Only release the lock (a real cancel); no artifact read. */
376
+ | {action: 'release'; reason: string};
377
+
378
+ /**
379
+ * Decide what the apply phase does with `needs.agent.result` (decision 5).
380
+ * `detectTimeout` is called only for `cancelled`.
381
+ */
382
+ export async function decideAgentResult(params: {
383
+ result: AgentJobResult;
384
+ needsAgent: boolean | undefined;
385
+ detectTimeout: () => Promise<CancelVerdict>;
386
+ }): Promise<AgentResultDecision> {
387
+ switch (params.result) {
388
+ case 'success':
389
+ return {action: 'read-handoff'};
390
+ case 'skipped':
391
+ if (params.needsAgent === false) return {action: 'deterministic'};
392
+ return {
393
+ action: 'surface',
394
+ reason:
395
+ 'the agent job was skipped although the lock job said the item ' +
396
+ 'needs an agent (treated as an agent failure); the handoff was not read',
397
+ };
398
+ case 'failure':
399
+ return {
400
+ action: 'surface',
401
+ reason:
402
+ 'the agent job failed (see its log in the workflow run); the handoff ' +
403
+ 'was not read',
404
+ };
405
+ case 'cancelled': {
406
+ const verdict = await params.detectTimeout();
407
+ if (verdict.kind === 'cancel') {
408
+ return {
409
+ action: 'release',
410
+ reason: `${verdict.why}; the handoff was not read`,
411
+ };
412
+ }
413
+ const what =
414
+ verdict.kind === 'timeout'
415
+ ? `the agent job timed out: ${verdict.why}`
416
+ : `the agent job was cancelled and it could not be told whether it ` +
417
+ `timed out (${verdict.why}), so it is treated as a timeout`;
418
+ return {action: 'surface', reason: `${what}; the handoff was not read`};
419
+ }
420
+ }
421
+ }