dorfl 0.0.0 → 0.1.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 (619) hide show
  1. package/dist/advance-ci-template.d.ts +73 -0
  2. package/dist/advance-ci-template.d.ts.map +1 -0
  3. package/dist/advance-ci-template.js +104 -0
  4. package/dist/advance-ci-template.js.map +1 -0
  5. package/dist/advance-classify.d.ts +132 -0
  6. package/dist/advance-classify.d.ts.map +1 -0
  7. package/dist/advance-classify.js +120 -0
  8. package/dist/advance-classify.js.map +1 -0
  9. package/dist/advance-drivers.d.ts +182 -0
  10. package/dist/advance-drivers.d.ts.map +1 -0
  11. package/dist/advance-drivers.js +231 -0
  12. package/dist/advance-drivers.js.map +1 -0
  13. package/dist/advance-isolated.d.ts +156 -0
  14. package/dist/advance-isolated.d.ts.map +1 -0
  15. package/dist/advance-isolated.js +256 -0
  16. package/dist/advance-isolated.js.map +1 -0
  17. package/dist/advance-lifecycle-template.d.ts +107 -0
  18. package/dist/advance-lifecycle-template.d.ts.map +1 -0
  19. package/dist/advance-lifecycle-template.js +668 -0
  20. package/dist/advance-lifecycle-template.js.map +1 -0
  21. package/dist/advance-loop-driver.d.ts +325 -0
  22. package/dist/advance-loop-driver.d.ts.map +1 -0
  23. package/dist/advance-loop-driver.js +437 -0
  24. package/dist/advance-loop-driver.js.map +1 -0
  25. package/dist/advance-treeless-publish.d.ts +108 -0
  26. package/dist/advance-treeless-publish.d.ts.map +1 -0
  27. package/dist/advance-treeless-publish.js +71 -0
  28. package/dist/advance-treeless-publish.js.map +1 -0
  29. package/dist/advance.d.ts +340 -0
  30. package/dist/advance.d.ts.map +1 -0
  31. package/dist/advance.js +1122 -0
  32. package/dist/advance.js.map +1 -0
  33. package/dist/advancing-lock.d.ts +294 -0
  34. package/dist/advancing-lock.d.ts.map +1 -0
  35. package/dist/advancing-lock.js +594 -0
  36. package/dist/advancing-lock.js.map +1 -0
  37. package/dist/agent-launch.d.ts +79 -0
  38. package/dist/agent-launch.d.ts.map +1 -0
  39. package/dist/agent-launch.js +61 -0
  40. package/dist/agent-launch.js.map +1 -0
  41. package/dist/agent-stop.d.ts +149 -0
  42. package/dist/agent-stop.d.ts.map +1 -0
  43. package/dist/agent-stop.js +307 -0
  44. package/dist/agent-stop.js.map +1 -0
  45. package/dist/apply-decide.d.ts +127 -0
  46. package/dist/apply-decide.d.ts.map +1 -0
  47. package/dist/apply-decide.js +176 -0
  48. package/dist/apply-decide.js.map +1 -0
  49. package/dist/apply-merge-action.d.ts +206 -0
  50. package/dist/apply-merge-action.d.ts.map +1 -0
  51. package/dist/apply-merge-action.js +307 -0
  52. package/dist/apply-merge-action.js.map +1 -0
  53. package/dist/apply-persist.d.ts +174 -0
  54. package/dist/apply-persist.d.ts.map +1 -0
  55. package/dist/apply-persist.js +359 -0
  56. package/dist/apply-persist.js.map +1 -0
  57. package/dist/arbiter.d.ts +120 -0
  58. package/dist/arbiter.d.ts.map +1 -0
  59. package/dist/arbiter.js +255 -0
  60. package/dist/arbiter.js.map +1 -0
  61. package/dist/brand.d.ts +70 -0
  62. package/dist/brand.d.ts.map +1 -0
  63. package/dist/brand.js +84 -0
  64. package/dist/brand.js.map +1 -0
  65. package/dist/buildable-body.d.ts +132 -0
  66. package/dist/buildable-body.d.ts.map +1 -0
  67. package/dist/buildable-body.js +131 -0
  68. package/dist/buildable-body.js.map +1 -0
  69. package/dist/categorise.d.ts +66 -0
  70. package/dist/categorise.d.ts.map +1 -0
  71. package/dist/categorise.js +106 -0
  72. package/dist/categorise.js.map +1 -0
  73. package/dist/claim-cas.d.ts +117 -0
  74. package/dist/claim-cas.d.ts.map +1 -0
  75. package/dist/claim-cas.js +312 -0
  76. package/dist/claim-cas.js.map +1 -0
  77. package/dist/cli-spinner.d.ts +112 -0
  78. package/dist/cli-spinner.d.ts.map +1 -0
  79. package/dist/cli-spinner.js +157 -0
  80. package/dist/cli-spinner.js.map +1 -0
  81. package/dist/cli.d.ts +11 -0
  82. package/dist/cli.d.ts.map +1 -0
  83. package/dist/cli.js +3094 -0
  84. package/dist/cli.js.map +1 -0
  85. package/dist/close-job-template.d.ts +70 -0
  86. package/dist/close-job-template.d.ts.map +1 -0
  87. package/dist/close-job-template.js +180 -0
  88. package/dist/close-job-template.js.map +1 -0
  89. package/dist/close-job.d.ts +95 -0
  90. package/dist/close-job.d.ts.map +1 -0
  91. package/dist/close-job.js +226 -0
  92. package/dist/close-job.js.map +1 -0
  93. package/dist/complete.d.ts +361 -0
  94. package/dist/complete.d.ts.map +1 -0
  95. package/dist/complete.js +885 -0
  96. package/dist/complete.js.map +1 -0
  97. package/dist/concurrency.d.ts +68 -0
  98. package/dist/concurrency.d.ts.map +1 -0
  99. package/dist/concurrency.js +112 -0
  100. package/dist/concurrency.js.map +1 -0
  101. package/dist/config-override.d.ts +76 -0
  102. package/dist/config-override.d.ts.map +1 -0
  103. package/dist/config-override.js +50 -0
  104. package/dist/config-override.js.map +1 -0
  105. package/dist/config.d.ts +668 -0
  106. package/dist/config.d.ts.map +1 -0
  107. package/dist/config.js +241 -0
  108. package/dist/config.js.map +1 -0
  109. package/dist/continue-branch.d.ts +249 -0
  110. package/dist/continue-branch.d.ts.map +1 -0
  111. package/dist/continue-branch.js +389 -0
  112. package/dist/continue-branch.js.map +1 -0
  113. package/dist/cwd-section.d.ts +186 -0
  114. package/dist/cwd-section.d.ts.map +1 -0
  115. package/dist/cwd-section.js +209 -0
  116. package/dist/cwd-section.js.map +1 -0
  117. package/dist/decision-engine.d.ts +170 -0
  118. package/dist/decision-engine.d.ts.map +1 -0
  119. package/dist/decision-engine.js +136 -0
  120. package/dist/decision-engine.js.map +1 -0
  121. package/dist/detect.d.ts +17 -0
  122. package/dist/detect.d.ts.map +1 -0
  123. package/dist/detect.js +118 -0
  124. package/dist/detect.js.map +1 -0
  125. package/dist/do-autopick.d.ts +85 -0
  126. package/dist/do-autopick.d.ts.map +1 -0
  127. package/dist/do-autopick.js +112 -0
  128. package/dist/do-autopick.js.map +1 -0
  129. package/dist/do-config.d.ts +312 -0
  130. package/dist/do-config.d.ts.map +1 -0
  131. package/dist/do-config.js +358 -0
  132. package/dist/do-config.js.map +1 -0
  133. package/dist/do-remote-auto.d.ts +75 -0
  134. package/dist/do-remote-auto.d.ts.map +1 -0
  135. package/dist/do-remote-auto.js +111 -0
  136. package/dist/do-remote-auto.js.map +1 -0
  137. package/dist/do.d.ts +621 -0
  138. package/dist/do.d.ts.map +1 -0
  139. package/dist/do.js +1882 -0
  140. package/dist/do.js.map +1 -0
  141. package/dist/drop-source.d.ts +96 -0
  142. package/dist/drop-source.d.ts.map +1 -0
  143. package/dist/drop-source.js +91 -0
  144. package/dist/drop-source.js.map +1 -0
  145. package/dist/eligibility.d.ts +46 -0
  146. package/dist/eligibility.d.ts.map +1 -0
  147. package/dist/eligibility.js +34 -0
  148. package/dist/eligibility.js.map +1 -0
  149. package/dist/env-config.d.ts +51 -0
  150. package/dist/env-config.d.ts.map +1 -0
  151. package/dist/env-config.js +272 -0
  152. package/dist/env-config.js.map +1 -0
  153. package/dist/failure-cause.d.ts +70 -0
  154. package/dist/failure-cause.d.ts.map +1 -0
  155. package/dist/failure-cause.js +126 -0
  156. package/dist/failure-cause.js.map +1 -0
  157. package/dist/format.d.ts +43 -0
  158. package/dist/format.d.ts.map +1 -0
  159. package/dist/format.js +256 -0
  160. package/dist/format.js.map +1 -0
  161. package/dist/frontmatter.d.ts +215 -0
  162. package/dist/frontmatter.d.ts.map +1 -0
  163. package/dist/frontmatter.js +345 -0
  164. package/dist/frontmatter.js.map +1 -0
  165. package/dist/gate-readiness.d.ts +84 -0
  166. package/dist/gate-readiness.d.ts.map +1 -0
  167. package/dist/gate-readiness.js +103 -0
  168. package/dist/gate-readiness.js.map +1 -0
  169. package/dist/gc.d.ts +165 -0
  170. package/dist/gc.d.ts.map +1 -0
  171. package/dist/gc.js +313 -0
  172. package/dist/gc.js.map +1 -0
  173. package/dist/gh-failure.d.ts +42 -0
  174. package/dist/gh-failure.d.ts.map +1 -0
  175. package/dist/gh-failure.js +49 -0
  176. package/dist/gh-failure.js.map +1 -0
  177. package/dist/git.d.ts +75 -0
  178. package/dist/git.d.ts.map +1 -0
  179. package/dist/git.js +130 -0
  180. package/dist/git.js.map +1 -0
  181. package/dist/github.d.ts +187 -0
  182. package/dist/github.d.ts.map +1 -0
  183. package/dist/github.js +343 -0
  184. package/dist/github.js.map +1 -0
  185. package/dist/harness.d.ts +242 -0
  186. package/dist/harness.d.ts.map +1 -0
  187. package/dist/harness.js +157 -0
  188. package/dist/harness.js.map +1 -0
  189. package/dist/identity.d.ts +167 -0
  190. package/dist/identity.d.ts.map +1 -0
  191. package/dist/identity.js +231 -0
  192. package/dist/identity.js.map +1 -0
  193. package/dist/index.d.ts +147 -0
  194. package/dist/index.d.ts.map +1 -0
  195. package/dist/index.js +76 -0
  196. package/dist/index.js.map +1 -0
  197. package/dist/install-ci-branch-protection.d.ts +147 -0
  198. package/dist/install-ci-branch-protection.d.ts.map +1 -0
  199. package/dist/install-ci-branch-protection.js +166 -0
  200. package/dist/install-ci-branch-protection.js.map +1 -0
  201. package/dist/install-ci-capabilities/advance-lifecycle.d.ts +15 -0
  202. package/dist/install-ci-capabilities/advance-lifecycle.d.ts.map +1 -0
  203. package/dist/install-ci-capabilities/advance-lifecycle.js +28 -0
  204. package/dist/install-ci-capabilities/advance-lifecycle.js.map +1 -0
  205. package/dist/install-ci-capabilities/close-job.d.ts +13 -0
  206. package/dist/install-ci-capabilities/close-job.d.ts.map +1 -0
  207. package/dist/install-ci-capabilities/close-job.js +26 -0
  208. package/dist/install-ci-capabilities/close-job.js.map +1 -0
  209. package/dist/install-ci-capabilities/example-noop.d.ts +16 -0
  210. package/dist/install-ci-capabilities/example-noop.d.ts.map +1 -0
  211. package/dist/install-ci-capabilities/example-noop.js +23 -0
  212. package/dist/install-ci-capabilities/example-noop.js.map +1 -0
  213. package/dist/install-ci-capabilities/intake.d.ts +15 -0
  214. package/dist/install-ci-capabilities/intake.d.ts.map +1 -0
  215. package/dist/install-ci-capabilities/intake.js +28 -0
  216. package/dist/install-ci-capabilities/intake.js.map +1 -0
  217. package/dist/install-ci-capabilities/verify.d.ts +14 -0
  218. package/dist/install-ci-capabilities/verify.d.ts.map +1 -0
  219. package/dist/install-ci-capabilities/verify.js +27 -0
  220. package/dist/install-ci-capabilities/verify.js.map +1 -0
  221. package/dist/install-ci-core.d.ts +446 -0
  222. package/dist/install-ci-core.d.ts.map +1 -0
  223. package/dist/install-ci-core.js +760 -0
  224. package/dist/install-ci-core.js.map +1 -0
  225. package/dist/install-ci-github.d.ts +167 -0
  226. package/dist/install-ci-github.d.ts.map +1 -0
  227. package/dist/install-ci-github.js +315 -0
  228. package/dist/install-ci-github.js.map +1 -0
  229. package/dist/install-ci.d.ts +105 -0
  230. package/dist/install-ci.d.ts.map +1 -0
  231. package/dist/install-ci.js +363 -0
  232. package/dist/install-ci.js.map +1 -0
  233. package/dist/intake-event.d.ts +88 -0
  234. package/dist/intake-event.d.ts.map +1 -0
  235. package/dist/intake-event.js +66 -0
  236. package/dist/intake-event.js.map +1 -0
  237. package/dist/intake-marker.d.ts +95 -0
  238. package/dist/intake-marker.d.ts.map +1 -0
  239. package/dist/intake-marker.js +127 -0
  240. package/dist/intake-marker.js.map +1 -0
  241. package/dist/intake-triage.d.ts +48 -0
  242. package/dist/intake-triage.d.ts.map +1 -0
  243. package/dist/intake-triage.js +95 -0
  244. package/dist/intake-triage.js.map +1 -0
  245. package/dist/intake-trigger-template.d.ts +185 -0
  246. package/dist/intake-trigger-template.d.ts.map +1 -0
  247. package/dist/intake-trigger-template.js +449 -0
  248. package/dist/intake-trigger-template.js.map +1 -0
  249. package/dist/intake.d.ts +569 -0
  250. package/dist/intake.d.ts.map +1 -0
  251. package/dist/intake.js +1628 -0
  252. package/dist/intake.js.map +1 -0
  253. package/dist/integration-core.d.ts +539 -0
  254. package/dist/integration-core.d.ts.map +1 -0
  255. package/dist/integration-core.js +2195 -0
  256. package/dist/integration-core.js.map +1 -0
  257. package/dist/integrator.d.ts +343 -0
  258. package/dist/integrator.d.ts.map +1 -0
  259. package/dist/integrator.js +400 -0
  260. package/dist/integrator.js.map +1 -0
  261. package/dist/isolation.d.ts +219 -0
  262. package/dist/isolation.d.ts.map +1 -0
  263. package/dist/isolation.js +261 -0
  264. package/dist/isolation.js.map +1 -0
  265. package/dist/issue-provider.d.ts +349 -0
  266. package/dist/issue-provider.d.ts.map +1 -0
  267. package/dist/issue-provider.js +360 -0
  268. package/dist/issue-provider.js.map +1 -0
  269. package/dist/item-lock.d.ts +626 -0
  270. package/dist/item-lock.d.ts.map +1 -0
  271. package/dist/item-lock.js +1381 -0
  272. package/dist/item-lock.js.map +1 -0
  273. package/dist/item-path.d.ts +49 -0
  274. package/dist/item-path.d.ts.map +1 -0
  275. package/dist/item-path.js +66 -0
  276. package/dist/item-path.js.map +1 -0
  277. package/dist/ledger-lint.d.ts +129 -0
  278. package/dist/ledger-lint.d.ts.map +1 -0
  279. package/dist/ledger-lint.js +249 -0
  280. package/dist/ledger-lint.js.map +1 -0
  281. package/dist/ledger-read.d.ts +357 -0
  282. package/dist/ledger-read.d.ts.map +1 -0
  283. package/dist/ledger-read.js +442 -0
  284. package/dist/ledger-read.js.map +1 -0
  285. package/dist/ledger-write.d.ts +330 -0
  286. package/dist/ledger-write.d.ts.map +1 -0
  287. package/dist/ledger-write.js +411 -0
  288. package/dist/ledger-write.js.map +1 -0
  289. package/dist/lifecycle-gather.d.ts +30 -0
  290. package/dist/lifecycle-gather.d.ts.map +1 -0
  291. package/dist/lifecycle-gather.js +205 -0
  292. package/dist/lifecycle-gather.js.map +1 -0
  293. package/dist/lifecycle-pools.d.ts +180 -0
  294. package/dist/lifecycle-pools.d.ts.map +1 -0
  295. package/dist/lifecycle-pools.js +78 -0
  296. package/dist/lifecycle-pools.js.map +1 -0
  297. package/dist/merge-question-surfacer.d.ts +166 -0
  298. package/dist/merge-question-surfacer.d.ts.map +1 -0
  299. package/dist/merge-question-surfacer.js +297 -0
  300. package/dist/merge-question-surfacer.js.map +1 -0
  301. package/dist/mint-adr.d.ts +126 -0
  302. package/dist/mint-adr.d.ts.map +1 -0
  303. package/dist/mint-adr.js +257 -0
  304. package/dist/mint-adr.js.map +1 -0
  305. package/dist/mirror-pool-scan.d.ts +125 -0
  306. package/dist/mirror-pool-scan.d.ts.map +1 -0
  307. package/dist/mirror-pool-scan.js +104 -0
  308. package/dist/mirror-pool-scan.js.map +1 -0
  309. package/dist/needs-attention.d.ts +341 -0
  310. package/dist/needs-attention.d.ts.map +1 -0
  311. package/dist/needs-attention.js +900 -0
  312. package/dist/needs-attention.js.map +1 -0
  313. package/dist/orphan-sidecar.d.ts +79 -0
  314. package/dist/orphan-sidecar.d.ts.map +1 -0
  315. package/dist/orphan-sidecar.js +71 -0
  316. package/dist/orphan-sidecar.js.map +1 -0
  317. package/dist/output.d.ts +48 -0
  318. package/dist/output.d.ts.map +1 -0
  319. package/dist/output.js +66 -0
  320. package/dist/output.js.map +1 -0
  321. package/dist/pi-harness.d.ts +179 -0
  322. package/dist/pi-harness.d.ts.map +1 -0
  323. package/dist/pi-harness.js +342 -0
  324. package/dist/pi-harness.js.map +1 -0
  325. package/dist/placement.d.ts +99 -0
  326. package/dist/placement.d.ts.map +1 -0
  327. package/dist/placement.js +67 -0
  328. package/dist/placement.js.map +1 -0
  329. package/dist/prd-to-spec.d.ts +315 -0
  330. package/dist/prd-to-spec.d.ts.map +1 -0
  331. package/dist/prd-to-spec.js +684 -0
  332. package/dist/prd-to-spec.js.map +1 -0
  333. package/dist/prepare.d.ts +121 -0
  334. package/dist/prepare.d.ts.map +1 -0
  335. package/dist/prepare.js +140 -0
  336. package/dist/prepare.js.map +1 -0
  337. package/dist/prompt.d.ts +360 -0
  338. package/dist/prompt.d.ts.map +1 -0
  339. package/dist/prompt.js +499 -0
  340. package/dist/prompt.js.map +1 -0
  341. package/dist/protocol/ADR-FORMAT.md +47 -0
  342. package/dist/protocol/CLAIM-PROTOCOL.md +217 -0
  343. package/dist/protocol/REVIEW-PROTOCOL.md +119 -0
  344. package/dist/protocol/SURFACE-PROTOCOL.md +121 -0
  345. package/dist/protocol/TASKING-PROTOCOL.md +122 -0
  346. package/dist/protocol/WORK-CONTRACT.md +276 -0
  347. package/dist/protocol/spec-template.md +71 -0
  348. package/dist/protocol/task-template.md +65 -0
  349. package/dist/readiness.d.ts +66 -0
  350. package/dist/readiness.d.ts.map +1 -0
  351. package/dist/readiness.js +36 -0
  352. package/dist/readiness.js.map +1 -0
  353. package/dist/reap-branches.d.ts +102 -0
  354. package/dist/reap-branches.d.ts.map +1 -0
  355. package/dist/reap-branches.js +149 -0
  356. package/dist/reap-branches.js.map +1 -0
  357. package/dist/recover-isolated.d.ts +72 -0
  358. package/dist/recover-isolated.d.ts.map +1 -0
  359. package/dist/recover-isolated.js +188 -0
  360. package/dist/recover-isolated.js.map +1 -0
  361. package/dist/registry.d.ts +172 -0
  362. package/dist/registry.d.ts.map +1 -0
  363. package/dist/registry.js +296 -0
  364. package/dist/registry.js.map +1 -0
  365. package/dist/repo-config.d.ts +201 -0
  366. package/dist/repo-config.d.ts.map +1 -0
  367. package/dist/repo-config.js +414 -0
  368. package/dist/repo-config.js.map +1 -0
  369. package/dist/repo-key.d.ts +20 -0
  370. package/dist/repo-key.d.ts.map +1 -0
  371. package/dist/repo-key.js +68 -0
  372. package/dist/repo-key.js.map +1 -0
  373. package/dist/repo-mirror.d.ts +177 -0
  374. package/dist/repo-mirror.d.ts.map +1 -0
  375. package/dist/repo-mirror.js +271 -0
  376. package/dist/repo-mirror.js.map +1 -0
  377. package/dist/retry-backoff.d.ts +90 -0
  378. package/dist/retry-backoff.d.ts.map +1 -0
  379. package/dist/retry-backoff.js +98 -0
  380. package/dist/retry-backoff.js.map +1 -0
  381. package/dist/review-gate.d.ts +173 -0
  382. package/dist/review-gate.d.ts.map +1 -0
  383. package/dist/review-gate.js +261 -0
  384. package/dist/review-gate.js.map +1 -0
  385. package/dist/review-verdict.d.ts +149 -0
  386. package/dist/review-verdict.d.ts.map +1 -0
  387. package/dist/review-verdict.js +332 -0
  388. package/dist/review-verdict.js.map +1 -0
  389. package/dist/run.d.ts +221 -0
  390. package/dist/run.d.ts.map +1 -0
  391. package/dist/run.js +963 -0
  392. package/dist/run.js.map +1 -0
  393. package/dist/scan.d.ts +308 -0
  394. package/dist/scan.d.ts.map +1 -0
  395. package/dist/scan.js +374 -0
  396. package/dist/scan.js.map +1 -0
  397. package/dist/select-order.d.ts +75 -0
  398. package/dist/select-order.d.ts.map +1 -0
  399. package/dist/select-order.js +108 -0
  400. package/dist/select-order.js.map +1 -0
  401. package/dist/select-priority.d.ts +188 -0
  402. package/dist/select-priority.d.ts.map +1 -0
  403. package/dist/select-priority.js +80 -0
  404. package/dist/select-priority.js.map +1 -0
  405. package/dist/select.d.ts +25 -0
  406. package/dist/select.d.ts.map +1 -0
  407. package/dist/select.js +43 -0
  408. package/dist/select.js.map +1 -0
  409. package/dist/session-path.d.ts +36 -0
  410. package/dist/session-path.d.ts.map +1 -0
  411. package/dist/session-path.js +129 -0
  412. package/dist/session-path.js.map +1 -0
  413. package/dist/sidecar-apply.d.ts +83 -0
  414. package/dist/sidecar-apply.d.ts.map +1 -0
  415. package/dist/sidecar-apply.js +111 -0
  416. package/dist/sidecar-apply.js.map +1 -0
  417. package/dist/sidecar.d.ts +245 -0
  418. package/dist/sidecar.d.ts.map +1 -0
  419. package/dist/sidecar.js +481 -0
  420. package/dist/sidecar.js.map +1 -0
  421. package/dist/slug-namespace.d.ts +204 -0
  422. package/dist/slug-namespace.d.ts.map +1 -0
  423. package/dist/slug-namespace.js +229 -0
  424. package/dist/slug-namespace.js.map +1 -0
  425. package/dist/spec-complete.d.ts +44 -0
  426. package/dist/spec-complete.d.ts.map +1 -0
  427. package/dist/spec-complete.js +69 -0
  428. package/dist/spec-complete.js.map +1 -0
  429. package/dist/start.d.ts +97 -0
  430. package/dist/start.d.ts.map +1 -0
  431. package/dist/start.js +633 -0
  432. package/dist/start.js.map +1 -0
  433. package/dist/status.d.ts +199 -0
  434. package/dist/status.d.ts.map +1 -0
  435. package/dist/status.js +228 -0
  436. package/dist/status.js.map +1 -0
  437. package/dist/surface-gate.d.ts +162 -0
  438. package/dist/surface-gate.d.ts.map +1 -0
  439. package/dist/surface-gate.js +206 -0
  440. package/dist/surface-gate.js.map +1 -0
  441. package/dist/surface-persist.d.ts +86 -0
  442. package/dist/surface-persist.d.ts.map +1 -0
  443. package/dist/surface-persist.js +129 -0
  444. package/dist/surface-persist.js.map +1 -0
  445. package/dist/tasker-review-loop.d.ts +249 -0
  446. package/dist/tasker-review-loop.d.ts.map +1 -0
  447. package/dist/tasker-review-loop.js +369 -0
  448. package/dist/tasker-review-loop.js.map +1 -0
  449. package/dist/tasking-eligibility.d.ts +74 -0
  450. package/dist/tasking-eligibility.d.ts.map +1 -0
  451. package/dist/tasking-eligibility.js +52 -0
  452. package/dist/tasking-eligibility.js.map +1 -0
  453. package/dist/tasking-lock.d.ts +111 -0
  454. package/dist/tasking-lock.d.ts.map +1 -0
  455. package/dist/tasking-lock.js +256 -0
  456. package/dist/tasking-lock.js.map +1 -0
  457. package/dist/tasking.d.ts +275 -0
  458. package/dist/tasking.d.ts.map +1 -0
  459. package/dist/tasking.js +952 -0
  460. package/dist/tasking.js.map +1 -0
  461. package/dist/triage-gate.d.ts +127 -0
  462. package/dist/triage-gate.d.ts.map +1 -0
  463. package/dist/triage-gate.js +139 -0
  464. package/dist/triage-gate.js.map +1 -0
  465. package/dist/triage-persist.d.ts +163 -0
  466. package/dist/triage-persist.d.ts.map +1 -0
  467. package/dist/triage-persist.js +387 -0
  468. package/dist/triage-persist.js.map +1 -0
  469. package/dist/verdict-json.d.ts +32 -0
  470. package/dist/verdict-json.d.ts.map +1 -0
  471. package/dist/verdict-json.js +74 -0
  472. package/dist/verdict-json.js.map +1 -0
  473. package/dist/verify-workflow-template.d.ts +60 -0
  474. package/dist/verify-workflow-template.d.ts.map +1 -0
  475. package/dist/verify-workflow-template.js +126 -0
  476. package/dist/verify-workflow-template.js.map +1 -0
  477. package/dist/verify.d.ts +60 -0
  478. package/dist/verify.d.ts.map +1 -0
  479. package/dist/verify.js +62 -0
  480. package/dist/verify.js.map +1 -0
  481. package/dist/watch-session.d.ts +112 -0
  482. package/dist/watch-session.d.ts.map +1 -0
  483. package/dist/watch-session.js +347 -0
  484. package/dist/watch-session.js.map +1 -0
  485. package/dist/work-layout.d.ts +198 -0
  486. package/dist/work-layout.d.ts.map +1 -0
  487. package/dist/work-layout.js +217 -0
  488. package/dist/work-layout.js.map +1 -0
  489. package/dist/work-on.d.ts +154 -0
  490. package/dist/work-on.d.ts.map +1 -0
  491. package/dist/work-on.js +387 -0
  492. package/dist/work-on.js.map +1 -0
  493. package/dist/workspace.d.ts +224 -0
  494. package/dist/workspace.d.ts.map +1 -0
  495. package/dist/workspace.js +325 -0
  496. package/dist/workspace.js.map +1 -0
  497. package/package.json +46 -2
  498. package/src/advance-ci-template.ts +203 -0
  499. package/src/advance-classify.ts +197 -0
  500. package/src/advance-drivers.ts +414 -0
  501. package/src/advance-isolated.ts +432 -0
  502. package/src/advance-lifecycle-template.ts +791 -0
  503. package/src/advance-loop-driver.ts +745 -0
  504. package/src/advance-treeless-publish.ts +177 -0
  505. package/src/advance.ts +1564 -0
  506. package/src/advancing-lock.ts +988 -0
  507. package/src/agent-launch.ts +137 -0
  508. package/src/agent-stop.ts +361 -0
  509. package/src/apply-decide.ts +242 -0
  510. package/src/apply-merge-action.ts +502 -0
  511. package/src/apply-persist.ts +518 -0
  512. package/src/arbiter.ts +372 -0
  513. package/src/brand.ts +111 -0
  514. package/src/buildable-body.ts +196 -0
  515. package/src/categorise.ts +158 -0
  516. package/src/claim-cas.ts +513 -0
  517. package/src/cli-spinner.ts +225 -0
  518. package/src/cli.ts +4369 -0
  519. package/src/close-job-template.ts +236 -0
  520. package/src/close-job.ts +319 -0
  521. package/src/complete.ts +1379 -0
  522. package/src/concurrency.ts +151 -0
  523. package/src/config-override.ts +116 -0
  524. package/src/config.ts +883 -0
  525. package/src/continue-branch.ts +542 -0
  526. package/src/cwd-section.ts +392 -0
  527. package/src/decision-engine.ts +272 -0
  528. package/src/detect.ts +124 -0
  529. package/src/do-autopick.ts +223 -0
  530. package/src/do-config.ts +589 -0
  531. package/src/do-remote-auto.ts +197 -0
  532. package/src/do.ts +2623 -0
  533. package/src/drop-source.ts +194 -0
  534. package/src/eligibility.ts +79 -0
  535. package/src/env-config.ts +305 -0
  536. package/src/failure-cause.ts +142 -0
  537. package/src/format.ts +313 -0
  538. package/src/frontmatter.ts +485 -0
  539. package/src/gate-readiness.ts +147 -0
  540. package/src/gc.ts +510 -0
  541. package/src/gh-failure.ts +53 -0
  542. package/src/git.ts +186 -0
  543. package/src/github.ts +468 -0
  544. package/src/harness.ts +355 -0
  545. package/src/identity.ts +322 -0
  546. package/src/index.ts +785 -0
  547. package/src/install-ci-branch-protection.ts +255 -0
  548. package/src/install-ci-capabilities/advance-lifecycle.ts +34 -0
  549. package/src/install-ci-capabilities/close-job.ts +32 -0
  550. package/src/install-ci-capabilities/example-noop.ts +24 -0
  551. package/src/install-ci-capabilities/intake.ts +34 -0
  552. package/src/install-ci-capabilities/verify.ts +33 -0
  553. package/src/install-ci-core.ts +1088 -0
  554. package/src/install-ci-github.ts +376 -0
  555. package/src/install-ci.ts +552 -0
  556. package/src/intake-event.ts +102 -0
  557. package/src/intake-marker.ts +195 -0
  558. package/src/intake-triage.ts +138 -0
  559. package/src/intake-trigger-template.ts +591 -0
  560. package/src/intake.ts +2445 -0
  561. package/src/integration-core.ts +3065 -0
  562. package/src/integrator.ts +771 -0
  563. package/src/isolation.ts +484 -0
  564. package/src/issue-provider.ts +733 -0
  565. package/src/item-lock.ts +1858 -0
  566. package/src/item-path.ts +75 -0
  567. package/src/ledger-lint.ts +332 -0
  568. package/src/ledger-read.ts +924 -0
  569. package/src/ledger-write.ts +865 -0
  570. package/src/lifecycle-gather.ts +298 -0
  571. package/src/lifecycle-pools.ts +250 -0
  572. package/src/merge-question-surfacer.ts +496 -0
  573. package/src/mint-adr.ts +362 -0
  574. package/src/mirror-pool-scan.ts +240 -0
  575. package/src/needs-attention.ts +1506 -0
  576. package/src/orphan-sidecar.ts +150 -0
  577. package/src/output.ts +89 -0
  578. package/src/pi-harness.ts +403 -0
  579. package/src/placement.ts +131 -0
  580. package/src/prd-to-spec.ts +1023 -0
  581. package/src/prepare.ts +230 -0
  582. package/src/prompt.ts +760 -0
  583. package/src/readiness.ts +98 -0
  584. package/src/reap-branches.ts +278 -0
  585. package/src/recover-isolated.ts +276 -0
  586. package/src/registry.ts +475 -0
  587. package/src/repo-config.ts +550 -0
  588. package/src/repo-key.ts +74 -0
  589. package/src/repo-mirror.ts +367 -0
  590. package/src/retry-backoff.ts +130 -0
  591. package/src/review-gate.ts +389 -0
  592. package/src/review-verdict.ts +422 -0
  593. package/src/run.ts +1430 -0
  594. package/src/scan.ts +611 -0
  595. package/src/select-order.ts +143 -0
  596. package/src/select-priority.ts +266 -0
  597. package/src/select.ts +62 -0
  598. package/src/session-path.ts +153 -0
  599. package/src/sidecar-apply.ts +216 -0
  600. package/src/sidecar.ts +700 -0
  601. package/src/slug-namespace.ts +367 -0
  602. package/src/spec-complete.ts +118 -0
  603. package/src/start.ts +974 -0
  604. package/src/status.ts +441 -0
  605. package/src/surface-gate.ts +337 -0
  606. package/src/surface-persist.ts +241 -0
  607. package/src/tasker-review-loop.ts +671 -0
  608. package/src/tasking-eligibility.ts +114 -0
  609. package/src/tasking-lock.ts +416 -0
  610. package/src/tasking.ts +1438 -0
  611. package/src/triage-gate.ts +248 -0
  612. package/src/triage-persist.ts +570 -0
  613. package/src/verdict-json.ts +73 -0
  614. package/src/verify-workflow-template.ts +159 -0
  615. package/src/verify.ts +123 -0
  616. package/src/watch-session.ts +397 -0
  617. package/src/work-layout.ts +262 -0
  618. package/src/work-on.ts +660 -0
  619. package/src/workspace.ts +502 -0
package/dist/do.js ADDED
@@ -0,0 +1,1882 @@
1
+ import { existsSync, mkdirSync, readFileSync, rmSync } from 'node:fs';
2
+ import { dirname, join } from 'node:path';
3
+ import { performStart } from './start.js';
4
+ import { performComplete } from './complete.js';
5
+ import { performClaim } from './claim-cas.js';
6
+ import { resolveSlug, SlugResolutionError, workBranchRef, } from './slug-namespace.js';
7
+ import { performTask } from './tasking.js';
8
+ import { resolveTask, buildAgentPrompt, resolveContinueContext, resolvePromptGuidanceForItem, PromptError, } from './prompt.js';
9
+ import { NullHarness } from './harness.js';
10
+ import { PiHarness } from './pi-harness.js';
11
+ import { launchWithOptionalWatch } from './agent-launch.js';
12
+ import { ledgerRead } from './ledger-read.js';
13
+ import { ledgerWrite, } from './ledger-write.js';
14
+ import { jobWorktreeStrategy, selectIsolationStrategy, } from './isolation.js';
15
+ import { ensureMirror, encodeRepoKey, mirrorPath } from './repo-mirror.js';
16
+ import { jobWorktreePath } from './workspace.js';
17
+ import { reapJob } from './gc.js';
18
+ import { isGitHubArbiterUrl, GitHubProvider } from './github.js';
19
+ import { arbiterUrl } from './integration-core.js';
20
+ import { shouldFailProposePrIntent, PROPOSE_PR_INTENT_GH_UNAVAILABLE_MESSAGE, } from './do-config.js';
21
+ import { checkGatePreconditions, detectLockfileOnDisk, detectLockfileOnMirrorMain, } from './gate-readiness.js';
22
+ import { git, run, runAsync, localMainAheadCount } from './git.js';
23
+ import { identityEnv, assertTransportAllowed, } from './identity.js';
24
+ import { parseStopSentinel, isWorkBranchDiffEmpty, emptyDiffStopReason, } from './agent-stop.js';
25
+ import { classifyFailureCause, failureCauseLabel, } from './failure-cause.js';
26
+ const DEFAULT_ARBITER = 'origin';
27
+ /**
28
+ * Map a `do prd:<slug>` {@link TaskResult} onto the `do` {@link DoResult}
29
+ * contract: outcomes pass through (tasked / gate-refused / stale / agent-failed /
30
+ * usage-error), the lock-lost outcome splits into `lost` (exit 2) vs `contended`
31
+ * (exit 3) by its exit code, and the tasking-only exit 4 (stale) is reported on
32
+ * the `do` exit contract (`0|1|2|3`) as exit 1 — the needs-attention-class
33
+ * failure code, same as a stuck build.
34
+ */
35
+ function taskResultToDoResult(tasked) {
36
+ let outcome;
37
+ let exitCode;
38
+ switch (tasked.outcome) {
39
+ case 'tasked':
40
+ outcome = 'tasked';
41
+ exitCode = 0;
42
+ break;
43
+ case 'gate-refused':
44
+ outcome = 'gate-refused';
45
+ exitCode = 1;
46
+ break;
47
+ case 'lock-lost':
48
+ if (tasked.exitCode === 3) {
49
+ outcome = 'contended';
50
+ exitCode = 3;
51
+ }
52
+ else {
53
+ outcome = 'lost';
54
+ exitCode = 2;
55
+ }
56
+ break;
57
+ case 'stale':
58
+ outcome = 'stale';
59
+ exitCode = 1;
60
+ break;
61
+ case 'needs-attention':
62
+ // The tasker review→edit loop found the decomposition unclear and routed the
63
+ // prd to needs-attention (no guessed tasks). Same exit class as a stuck
64
+ // build (1).
65
+ outcome = 'needs-attention';
66
+ exitCode = 1;
67
+ break;
68
+ case 'agent-failed':
69
+ outcome = 'agent-failed';
70
+ exitCode = 1;
71
+ break;
72
+ default:
73
+ outcome = 'usage-error';
74
+ exitCode = 1;
75
+ }
76
+ return { exitCode, outcome, slug: tasked.slug, message: tasked.message };
77
+ }
78
+ /**
79
+ * Run the in-place `do` ritual end-to-end. Never throws for the expected
80
+ * lost/contended/refused/needs-attention cases — those are returned with the
81
+ * appropriate exit code and outcome. The runner owns all git; the agent only
82
+ * edits code.
83
+ */
84
+ export async function performDo(options) {
85
+ const note = options.note ?? (() => { });
86
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
87
+ const cwd = options.cwd;
88
+ // `env` here is the runner's GIT/provider env, scoped to the configured
89
+ // identity (claim, push, integrate, `gh`). The AGENT launch is the ONE thing
90
+ // that must NOT be the identity — it stays on the ambient `options.env`
91
+ // (`runDoAgent` reads `options.env` directly, never this local `env`), so the
92
+ // agent never commits as the bot; only the runner's own transitions do. Absent
93
+ // identity ⇒ `options.env` unchanged (byte-for-byte ambient). A configured
94
+ // identity that cannot be resolved (e.g. `tokenEnv` names an unset env var) is
95
+ // a clean usage error here, never a crash or a silent ambient fallback.
96
+ let env;
97
+ try {
98
+ env = identityEnv(options.identity, options.env ?? process.env);
99
+ }
100
+ catch (err) {
101
+ const message = err instanceof Error ? err.message : String(err);
102
+ note(message);
103
+ return { exitCode: 1, outcome: 'usage-error', message };
104
+ }
105
+ // 0. `--watch` REQUIRES the pi harness (task `do-watch`): only the pi adapter
106
+ // writes a session `.jsonl` event log to tail. The null/shell adapter has no
107
+ // session log / event taxonomy, so there is nothing to observe — ERROR
108
+ // CLEARLY here, BEFORE any git transition (no claim, no branch), rather than
109
+ // silently running without the view. The injected `dorfl` (tests /
110
+ // custom embeddings) is its own launch path and is exempt.
111
+ if (options.watch === true &&
112
+ options.dorfl === undefined &&
113
+ !(options.harness instanceof PiHarness)) {
114
+ return {
115
+ exitCode: 1,
116
+ outcome: 'usage-error',
117
+ message: '`do --watch` requires the pi harness; configure `harness: pi` or drop ' +
118
+ '`--watch`.',
119
+ };
120
+ }
121
+ // 1. Resolve the slug across BOTH namespaces — `do` is the ONE command that
122
+ // spans them (ADR §3a): bare → task (after a no-prd-collision check;
123
+ // ERROR on collision), `task:`/`prd:` explicit. A collision / resolution
124
+ // failure is a loud usage error (exit 1).
125
+ let resolved;
126
+ try {
127
+ resolved = resolveSlug({
128
+ arg: options.arg,
129
+ repoPath: options.repoPath ?? cwd,
130
+ read: options.read ?? ledgerRead,
131
+ });
132
+ }
133
+ catch (err) {
134
+ if (err instanceof SlugResolutionError) {
135
+ return { exitCode: 1, outcome: 'usage-error', message: err.message };
136
+ }
137
+ const message = err instanceof Error ? err.message : String(err);
138
+ return { exitCode: 1, outcome: 'usage-error', message };
139
+ }
140
+ // 2. `do spec:<slug>` → the spec-TASKING path (`autoslice-command`): the in-place
141
+ // `do` worker is AUTONOMOUS, so it tasks as the AGENT (gate-bound + lock).
142
+ // The orchestration (gate → lock → to-task harness → runner-owned commit)
143
+ // lives in `tasking.ts`; `do` dispatches `spec:` here. The agent only writes
144
+ // task files — the runner owns every git transition (same boundary as the
145
+ // build path). It does NOT run the task-build pipeline below.
146
+ // `resolveSlug` returns `{namespace:'spec'}` for a `spec:<slug>` arg, so
147
+ // `do spec:<slug>` routes here to tasking.
148
+ if (resolved.namespace === 'spec') {
149
+ const tasked = await performTask({
150
+ slug: resolved.slug,
151
+ cwd,
152
+ arbiter,
153
+ doer: 'agent',
154
+ autoTask: options.autoTask,
155
+ // EXPLICIT dispatch: a `do prd:<slug>` target was NAMED (the operator typed
156
+ // it, or the auto-pick POOL already filtered it on `autoTask` before
157
+ // dispatching here — the single policy-enforcement point). So the tasking gate
158
+ // drops the `autoTask` policy term and binds only the prd's own readiness
159
+ // (`humanOnly`/`needsAnswers`) + `taskedAfter`, EXACTLY as `do <task>` builds a
160
+ // named task regardless of `autoBuild` (the pool gates the policy, not the
161
+ // explicit claim).
162
+ explicit: true,
163
+ // The injected dorfl (tests) writes task files directly. The
164
+ // DoDorfl shape is a structural superset of TaskDorfl (its
165
+ // extra `output` is ignored by the tasking path), so it threads straight in.
166
+ dorfl: options.dorfl,
167
+ harness: options.harness,
168
+ agentCmd: options.agentCmd,
169
+ model: options.model,
170
+ sessionsDir: options.sessionsDir,
171
+ // The integrate-time args (task `slice-output-through-integration`): the
172
+ // `provider` is the SAME the task-build path threads (arg parity), but the
173
+ // MODE is the per-TRANSITION TASKING resolution (`per-transition-integration-
174
+ // mode-tasking-vs-build`): `taskingIntegration ?? integration`. Unset override ⇒
175
+ // falls back to `integration` (today's behaviour); a repo with
176
+ // `integration:'propose'` + `taskingIntegration:'merge'` lands the task FILES
177
+ // on main here while the BUILD path below still threads plain `integration`.
178
+ integration: options.taskingIntegration ?? options.integration,
179
+ // The per-repo TASK-PLACEMENT default + the operator's explicit
180
+ // override (task `runner-deterministic-slice-placement-policy-and-
181
+ // precedence`). The tasker reads them as the configured-default + the
182
+ // top rung of the runner-deterministic placement resolver; the
183
+ // `originTrust: untrusted` force is read inside the tasker from the
184
+ // prd's stamped frontmatter.
185
+ tasksLandIn: options.tasksLandIn,
186
+ explicitTasksLandIn: options.explicitTasksLandIn,
187
+ noPR: options.noPR,
188
+ providerInstance: options.providerInstance,
189
+ // The tasker review→edit→converge loop (slicer-review-edit-loop): improves the
190
+ // candidate tasks in place + routes the verdict through the needsAnswers /
191
+ // needs-attention sink. Threaded only on the `do prd:` path; omitted ⇒ no loop.
192
+ reviewLoop: options.reviewLoop,
193
+ taskerLoopMax: options.taskerLoopMax,
194
+ reviewExecutions: options.reviewExecutions,
195
+ taskerLoopModel: options.taskerLoopModel,
196
+ // The task-SET ACCEPTANCE GATE (slice-acceptance-gate): rides the BUILD
197
+ // `--review`/`--review-model` family — a fresh-context review of the produced
198
+ // SET before it integrates, ONE-SHOT, independent of the improver loop above.
199
+ review: options.review,
200
+ reviewGate: options.taskReviewGate,
201
+ acceptanceReviewModel: options.reviewModel,
202
+ env,
203
+ // The tasker + review AGENTS launch AMBIENT, never the identity env.
204
+ agentEnv: options.env,
205
+ note,
206
+ });
207
+ return taskResultToDoResult(tasked);
208
+ }
209
+ const slug = resolved.slug;
210
+ // 3. Refuse on a DIRTY working tree (ar-run.sh's first guard). `do` runs in a
211
+ // REAL checkout (the human's clone / the CI container); it must NOT
212
+ // entangle unrelated work or run over uncommitted changes. (Mirrors the
213
+ // bash driver: "error: working tree is dirty — commit/stash before
214
+ // running a task.")
215
+ if (await isDirtyTree(cwd, env)) {
216
+ const message = `working tree is dirty — commit or stash before running '${slug}' ` +
217
+ '(do runs in-place in this checkout and will not entangle unrelated ' +
218
+ 'changes).';
219
+ return { exitCode: 1, outcome: 'refused', slug, message };
220
+ }
221
+ // 3b. Refuse on a DIVERGED local `main` (MERGE MODE ONLY — mirrors `complete`'s
222
+ // guard). A local `main` AHEAD of `<arbiter>/main` (unpushed commits) breaks
223
+ // ONLY the paths that fast-forward local `main`, and only merge mode ff's it:
224
+ // the task builds off `<arbiter>/main`, so a merge-back ff cannot apply over
225
+ // a diverged main. Propose mode never ff's local `main` (it pushes the work
226
+ // branch + opens a PR; completion only `switch`es to main, no ff), so the
227
+ // guard is irrelevant there and must NOT fire. Catch it UP FRONT — before the
228
+ // claim + agent run — so a whole build is not wasted. Resolve the mode the
229
+ // SAME way the rest of the flow does (the `options.integration` we thread into
230
+ // `complete`), then fetch (as the onboarding flow does) and compare.
231
+ // `--ignore-diverged-main` overrides (mirrors `--ignore-not-ready`); when
232
+ // overridden, Part 1's non-fatal sync handles the persisting divergence
233
+ // honestly at complete-time.
234
+ if ((options.integration ?? 'propose') === 'merge' &&
235
+ options.ignoreDivergedMain !== true) {
236
+ await runAsync('git', ['fetch', '--quiet', arbiter], cwd, { env });
237
+ const ahead = await localMainAheadCount(cwd, arbiter, env);
238
+ if (ahead > 0) {
239
+ const message = `local main is ahead of ${arbiter}/main by ${ahead} commit` +
240
+ `${ahead === 1 ? '' : 's'} (unpushed); the task builds off ${arbiter}/main ` +
241
+ "and the merge-back can't fast-forward — push or reconcile main first " +
242
+ '(or re-run with --ignore-diverged-main to proceed anyway).';
243
+ return { exitCode: 1, outcome: 'refused', slug, message };
244
+ }
245
+ }
246
+ // 3c. PR-INTENT pre-flight guard (the honest-failure value of the `noPR` axis).
247
+ // When this run is `propose` on a GITHUB arbiter and the operator INTENDS a PR
248
+ // (`noPR` unset), run a `gh` AUTH/AVAILABILITY PROBE UP FRONT — BEFORE the
249
+ // claim + agent run — and FAIL FAST if `gh` genuinely cannot open one, instead
250
+ // of letting integration silently degrade to manual-PR instructions. This sits
251
+ // alongside the dirty-tree / diverged-main guards (and mirrors the shared
252
+ // `doNeedsAgentCmd`/`NO_AGENT_CMD_MESSAGE` up-front refusal) so no build work
253
+ // is wasted. CRITICAL: the PROBE is the signal, NOT "is a `providers.github`
254
+ // identity present" — an absent identity falls back to AMBIENT `gh` auth (the
255
+ // common local-dev case), which the probe correctly reports as available, so a
256
+ // working ambient setup still PROCEEDS. A genuinely transient mid-run `gh`
257
+ // outage (probe passes here, the API fails later) is left to the runtime
258
+ // degrade. `noPR: true` skips the guard entirely (no PR is intended).
259
+ {
260
+ const url = await arbiterUrl(cwd, arbiter, env);
261
+ const probe = options.ghCanOpenPr ??
262
+ ((probeCwd, probeEnv) => new GitHubProvider().available(probeCwd, probeEnv));
263
+ if (shouldFailProposePrIntent({
264
+ mode: options.integration ?? 'propose',
265
+ arbiterIsGitHub: url !== undefined && isGitHubArbiterUrl(url),
266
+ noPR: options.noPR,
267
+ ghCanOpenPr: () => probe(cwd, env),
268
+ })) {
269
+ return {
270
+ exitCode: 1,
271
+ outcome: 'refused',
272
+ slug,
273
+ message: PROPOSE_PR_INTENT_GH_UNAVAILABLE_MESSAGE,
274
+ };
275
+ }
276
+ }
277
+ // 3d. STATIC fresh-worktree-gate readiness guard (task
278
+ // `do-fails-fast-when-acceptance-gate-statically-unrunnable`). When the
279
+ // fresh-worktree gate is ON AND `prepare` resolves to no commands AND a
280
+ // lockfile is present, the throwaway worktree the gate runs in will have no
281
+ // `node_modules` and `verify`'s tools (`prettier`/`tsc`/`vitest`) will be
282
+ // "command not found" — fail fast HERE, BEFORE the claim and BEFORE spawning
283
+ // the build agent, instead of wasting a whole `do` run and (worse) routing
284
+ // correct work to needs-attention as if the task were at fault. A repo with
285
+ // NO lockfile is the intentional dep-free case (the design point preserved)
286
+ // and proceeds. There is NO verify-unset case — `resolveVerifyCommands`
287
+ // substitutes the default gate when verify is unset/all-blank, so verify is
288
+ // never statically unrunnable-because-unset (the guard is deps-only).
289
+ {
290
+ const guard = checkGatePreconditions({
291
+ freshWorktreeGate: options.freshWorktreeGate,
292
+ prepare: options.prepare,
293
+ lockfile: detectLockfileOnDisk(cwd),
294
+ });
295
+ if (guard !== undefined) {
296
+ return { exitCode: 1, outcome: 'refused', slug, message: guard.message };
297
+ }
298
+ }
299
+ // 4. Onboard via the ISOLATION SEAM (`selectIsolationStrategy`/`inPlaceStrategy`)
300
+ // with the SAME claim-first composition `do --remote` (and `run`) use — this
301
+ // is the consumer that finally puts in-place `do` on the seam, so all THREE
302
+ // `do`/`run` forms share the one `IsolatedTree`-handle post-claim shape:
303
+ //
304
+ // a. CLAIM explicitly via the CAS (the claim is the `do` driver's job,
305
+ // BEFORE prepare). A lost/contended/usage claim is propagated verbatim
306
+ // and NOTHING is onboarded — the same clean skip `run`/`do --remote` do.
307
+ // An already-in-progress / done / absent item is NOT claimable, so the
308
+ // CAS returns `lost` (exit 2): `do` (the autonomous CI worker) never
309
+ // re-claims an item someone else holds, exactly like its siblings.
310
+ // b. `selectIsolationStrategy({checkout})` → `inPlaceStrategy`, whose
311
+ // `prepare` does the ONBOARDING half (fetch + continue-detection +
312
+ // fresh-main `work/<slug>` switch, incl. the §14 continue/rebase path)
313
+ // WITHOUT re-claiming — the split the seam mandates (claim → driver,
314
+ // onboarding → strategy).
315
+ const claim = await performClaim({
316
+ slug,
317
+ cwd,
318
+ arbiter,
319
+ // `--allow-backlog`: widen the claimable predicate to ALSO accept a
320
+ // `tasks/backlog/`-resident body. Claim stays a pure lock (writes nothing to
321
+ // `main`, moves nothing); default off ⇒ pool-only, exactly as today.
322
+ allowBacklog: options.allowBacklog === true,
323
+ env,
324
+ note,
325
+ });
326
+ if (claim.outcome === 'lost') {
327
+ return { exitCode: 2, outcome: 'lost', slug, message: claim.message };
328
+ }
329
+ if (claim.outcome === 'contended') {
330
+ return { exitCode: 3, outcome: 'contended', slug, message: claim.message };
331
+ }
332
+ if (claim.exitCode !== 0) {
333
+ // usage/environment error (not inside a repo, no arbiter remote, dirty
334
+ // index, …): surface verbatim. NOTHING was onboarded.
335
+ return { exitCode: 1, outcome: 'usage-error', slug, message: claim.message };
336
+ }
337
+ // The claim landed (the item is now in-progress on the arbiter). Onboard the
338
+ // checkout onto its work branch THROUGH the seam — the in-place strategy puts
339
+ // `cwd` on `work/<slug>` off the freshly-fetched `<arbiter>/main` (or continues
340
+ // a kept requeue branch + rebases it, §14/§10). `prepare` can throw on a genuine
341
+ // plumbing failure (unreachable arbiter, …) — surface that as a usage error,
342
+ // never a false success.
343
+ let tree;
344
+ try {
345
+ tree = selectIsolationStrategy({ checkout: cwd, arbiter }).prepare({
346
+ slug,
347
+ // The task-build path: namespace the branch `work/task-<slug>`, and
348
+ // branch it from the EXACT claim commit (the defensive guard) so a stale
349
+ // same-named branch (e.g. one `intake` left) is re-pointed, never reused.
350
+ type: 'task',
351
+ claimCommit: claim.claimCommit,
352
+ env,
353
+ });
354
+ }
355
+ catch (err) {
356
+ const message = err instanceof Error ? err.message : String(err);
357
+ return { exitCode: 1, outcome: 'usage-error', slug, message };
358
+ }
359
+ const branch = tree.branch;
360
+ // 4a. CONTINUE rebase conflict (ADR §14 + §10): a requeue kept a `work/<slug>`
361
+ // that did not replay onto the current main at onboard-time (aborted, never
362
+ // auto-resolved). Surface to needs-attention TREE-LESSLY via the SAME `#89`
363
+ // mechanism `requeue` uses for the reverse direction — the rebase was
364
+ // ABORTED, so the kept `work/<slug>` tip == the arbiter tip (already on the
365
+ // arbiter, after-commit, recoverable). The surface is purely the one-file
366
+ // `in-progress/ → needs-attention/` ledger move + reason (no branch push, no
367
+ // worktree mutation) instead of running the agent — the §10 path. The work
368
+ // did NOT onboard; the runner owns the bounce.
369
+ if (tree.continueRebaseConflict) {
370
+ const reason = `continuing the kept ${tree.branch}: rebase onto the latest main ` +
371
+ 'conflicted (aborted, never auto-resolved) — resolve against the latest ' +
372
+ 'main, or `requeue --reset` to discard and start fresh';
373
+ const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
374
+ cwd: tree.dir,
375
+ slug,
376
+ reason,
377
+ arbiter: tree.arbiterRemote,
378
+ env,
379
+ note,
380
+ });
381
+ if (!surfaced.moved) {
382
+ return surfaceUnmovedDoResult({ slug, branch, reason, surfaced });
383
+ }
384
+ return {
385
+ exitCode: 1,
386
+ outcome: 'needs-attention',
387
+ slug,
388
+ branch,
389
+ message: reason,
390
+ };
391
+ }
392
+ // 4b. CONTINUE reconcile-push TERMINAL failure (the stale-lease-strand bug):
393
+ // the onboard reconcile push of the kept (already-committed) work branch
394
+ // FAILED terminally (stale-lease cap exhausted, or a non-stale-lease
395
+ // rejection / unreachable arbiter). The push helper THROWS; the strategy
396
+ // CATCHES it and flags `continuePushFailure` so the run does NOT crash
397
+ // leaving the task silently in-progress. Surface to needs-attention
398
+ // TREE-LESSLY via the SAME `#89` mechanism `requeue` uses — the kept branch
399
+ // is already on the arbiter (after-commit, recoverable), so the surface is
400
+ // purely the one-file ledger move + reason (no branch push, no worktree).
401
+ if (tree.continuePushFailure !== undefined) {
402
+ const reason = `continuing the kept ${tree.branch}: publishing the rebased work branch ` +
403
+ `to the arbiter failed terminally (${tree.continuePushFailure}) — the kept ` +
404
+ 'branch is left intact on the arbiter (recoverable); `requeue` to retry ' +
405
+ 'once the churn settles, or `requeue --reset` to discard and start fresh';
406
+ const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
407
+ cwd: tree.dir,
408
+ slug,
409
+ reason,
410
+ arbiter: tree.arbiterRemote,
411
+ env,
412
+ note,
413
+ });
414
+ if (!surfaced.moved) {
415
+ return surfaceUnmovedDoResult({ slug, branch, reason, surfaced });
416
+ }
417
+ return {
418
+ exitCode: 1,
419
+ outcome: 'needs-attention',
420
+ slug,
421
+ branch,
422
+ message: reason,
423
+ };
424
+ }
425
+ // 5. Run the agent autonomously in the checkout, ON the work branch — the
426
+ // SAME prompt assembly `dorfl prompt` emits (canonical wrapper +
427
+ // source prd + the task's ## Prompt). The agent only edits code (it does
428
+ // no git). This is the one NEW middle step `ar-run.sh` shelled out for
429
+ // (`prompt | pi`).
430
+ // The post-claim pipeline reads the uniform `IsolatedTree` handle (`tree.dir`)
431
+ // — in-place that IS `cwd`, but reading the handle keeps the shared shape the
432
+ // future advance-loop tick wraps (no in-place-only special case).
433
+ let prompt;
434
+ try {
435
+ // CONTINUE-aware resolution: on a continue (the arbiter holds a kept
436
+ // `work/<slug>` whose tip is STRANDED off main) the task may already be in
437
+ // `work/done/`; admit `done/` ONLY behind the tip-vs-arbiter stranded gate
438
+ // (story 5), reusing the SAME refs the continue-detection uses.
439
+ const task = resolveTask(tree.dir, slug, {
440
+ cwd: tree.dir,
441
+ branchRef: `${tree.arbiterRemote}/${tree.branch}`,
442
+ mainRef: `${tree.arbiterRemote}/main`,
443
+ env,
444
+ },
445
+ // `--allow-backlog`: also resolve a `tasks/backlog/`-resident body
446
+ // (lowest priority). Default off ⇒ unchanged.
447
+ { allowBacklog: options.allowBacklog === true });
448
+ // CONTINUE-mode (the `agent-prompt-continue-context` task): if the arbiter
449
+ // holds a kept `work/<slug>` ahead of main (a requeue) the checkout was
450
+ // CONTINUED onto it — inject the continue block (prior diff + reason + note).
451
+ // REUSE the SAME continue-detection the onboarding path used (in-place clone
452
+ // refs: `<arbiter>/work/<slug>` vs `<arbiter>/main`).
453
+ const continueContext = resolveContinueContext({
454
+ cwd: tree.dir,
455
+ slug,
456
+ arbiter: tree.arbiterRemote,
457
+ branchRef: `${tree.arbiterRemote}/${tree.branch}`,
458
+ mainRef: `${tree.arbiterRemote}/main`,
459
+ content: readFileSync(task.path, 'utf8'),
460
+ env,
461
+ });
462
+ // Per-item override layer: a task or prd may pin `promptGuidance.testFirst`
463
+ // in its frontmatter, superseding the resolved repo policy for THIS item.
464
+ const itemGuidance = resolvePromptGuidanceForItem({
465
+ cwd: tree.dir,
466
+ repoResolved: { testFirst: options.promptGuidance?.testFirst === true },
467
+ taskContent: readFileSync(task.path, 'utf8'),
468
+ });
469
+ prompt = buildAgentPrompt(task.slug, task.prd, task.taskPrompt, {
470
+ cwd: tree.dir,
471
+ continueContext,
472
+ promptGuidance: itemGuidance,
473
+ });
474
+ }
475
+ catch (err) {
476
+ if (err instanceof PromptError) {
477
+ return await saveAgentFailure({
478
+ slug,
479
+ branch,
480
+ cwd: tree.dir,
481
+ arbiter: tree.arbiterRemote,
482
+ detail: err.message,
483
+ env,
484
+ note,
485
+ });
486
+ }
487
+ throw err;
488
+ }
489
+ let agent;
490
+ try {
491
+ agent = await runDoAgent(options, tree.dir, prompt, slug);
492
+ }
493
+ catch (err) {
494
+ const message = err instanceof Error ? err.message : String(err);
495
+ return await saveAgentFailure({
496
+ slug,
497
+ branch,
498
+ cwd: tree.dir,
499
+ arbiter: tree.arbiterRemote,
500
+ detail: message,
501
+ env,
502
+ note,
503
+ });
504
+ }
505
+ if (!agent.ok) {
506
+ const detail = agent.detail ?? `the agent failed to build '${slug}'.`;
507
+ return await saveAgentFailure({
508
+ slug,
509
+ branch,
510
+ cwd: tree.dir,
511
+ arbiter: tree.arbiterRemote,
512
+ detail,
513
+ env,
514
+ note,
515
+ });
516
+ }
517
+ // 5b. HONOR a deliberate STOP (task `agent-stop-signal`). The agent exited
518
+ // cleanly (`agent.ok`), but the CLAIM-PROTOCOL wrapper tells it to STOP and
519
+ // report on a DRIFTED/ambiguous/stale-premise task WITHOUT building. Detect
520
+ // that BEFORE the gate via the in-band sentinel (the agent's reason is the
521
+ // needs-attention reason VERBATIM); a clean STOP with no source change is the
522
+ // deterministic empty-diff backstop. Either routes to needs-attention and
523
+ // SKIPS the acceptance gate AND Gate-2 — a clean STOP is NOT "a build that
524
+ // changed nothing".
525
+ const stopReason = await resolveStopReason({
526
+ output: agent.output,
527
+ slug,
528
+ cwd: tree.dir,
529
+ arbiter: tree.arbiterRemote,
530
+ env,
531
+ });
532
+ if (stopReason !== undefined) {
533
+ return await saveAgentStop({
534
+ slug,
535
+ branch,
536
+ cwd: tree.dir,
537
+ arbiter: tree.arbiterRemote,
538
+ reason: stopReason,
539
+ env,
540
+ note,
541
+ });
542
+ }
543
+ // 6. Gate + done-move + commit + rebase + integrate + branch-tidy LIKE
544
+ // `complete` — but with the AUTONOMOUS needs-attention surfacing (pass
545
+ // `surfaceArbiter` so a red gate / rebase conflict surfaces on the
546
+ // arbiter's main, cross-machine visible — a stuck CI `do` that only routed
547
+ // locally would be invisible). The success path reuses `complete`'s
548
+ // machinery unchanged.
549
+ //
550
+ // Push-time transport-coherence guard (identity): refuse a forbidden transport
551
+ // for THIS arbiter's actual URL rather than silently pushing under an ambient
552
+ // credential. A no-op when no identity is configured.
553
+ try {
554
+ assertTransportAllowed(options.identity, tree.arbiterUrl);
555
+ }
556
+ catch (err) {
557
+ const message = err instanceof Error ? err.message : String(err);
558
+ return await saveAgentFailure({
559
+ slug,
560
+ branch,
561
+ cwd: tree.dir,
562
+ arbiter: tree.arbiterRemote,
563
+ detail: message,
564
+ env,
565
+ note,
566
+ });
567
+ }
568
+ const completed = await performComplete({
569
+ slug,
570
+ cwd: tree.dir,
571
+ arbiter: tree.arbiterRemote,
572
+ integration: options.integration,
573
+ // An explicit `--merge` overrides the untrusted-origin build-propose rule (task
574
+ // `untrusted-origin-forces-build-propose`); the autonomous path leaves it
575
+ // unset so untrusted-origin reliably forces propose.
576
+ explicitMerge: options.explicitMerge,
577
+ // `do` already ran the pre-flight divergence guard UP FRONT (step 3b), before
578
+ // the claim + agent; skip `complete`'s redundant re-check. When `do` was run
579
+ // with --ignore-diverged-main the guard was bypassed there too, so either way
580
+ // the (now non-fatal) local-main sync handles any persisting divergence.
581
+ ignoreDivergedMain: true,
582
+ // `--allow-backlog`: treat a `tasks/backlog/`-resident body as a valid build
583
+ // source so the done-move goes `tasks/backlog/ → tasks/done/` directly. Gated
584
+ // on the flag (without it `complete` refuses a staged strand). Default off.
585
+ allowBacklog: options.allowBacklog === true,
586
+ prepare: options.prepare,
587
+ verify: options.verify,
588
+ freshWorktreeGate: options.freshWorktreeGate,
589
+ // The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) — the
590
+ // git-alone FLOOR of the cross-job land queue (prd `land-time-reverify-and-
591
+ // parallel-merge-ceiling` Story 5 / Applied Answer q1 (a)). Threaded so a
592
+ // wide-matrix CI's raised cap actually reaches the merge loop.
593
+ mergeRetries: options.mergeRetries,
594
+ noPR: options.noPR,
595
+ // The resolved provider INSTANCE seam (tests/embeddings inject a stubbed
596
+ // GitHubProvider to drive the propose pipeline offline). Unset ⇒ the core
597
+ // selects from the arbiter URL.
598
+ providerInstance: options.providerInstance,
599
+ // Half B (propose-mode PR body): the build agent's FINAL SUMMARY, captured
600
+ // from the harness seam's `LaunchResult.output` (surfaced by `runDoAgent`
601
+ // below) and threaded as the PR description. `complete` scaffolds a
602
+ // deterministic header (task pointer) above it. Undefined ⇒ no body ⇒ the
603
+ // provider degrades to `gh ... --fill` (no regression).
604
+ body: agent.output,
605
+ // Gate 2 (PR/code review) rides INSIDE `complete`: run the `review` SKILL as a
606
+ // fresh-context agent after the green `verify` (the non-skippable floor) and
607
+ // before the done-move. A `block` re-uses the same needs-attention surfacing
608
+ // (`surfaceArbiter`) the red gate does; mapped to `needs-attention` below.
609
+ review: options.review,
610
+ reviewModel: options.reviewModel,
611
+ reviewMaxRounds: options.reviewMaxRounds,
612
+ reviewGate: options.reviewGate,
613
+ // `--watch` (task `watch-review-session`): tail the Gate-2 review agent's
614
+ // session live too, AFTER the build stream the `runDoAgent` watch surfaced
615
+ // (the gate prints a build→review boundary). Threaded into the gate launch via
616
+ // `complete`; OFF ⇒ the review path is byte-identical (sync launch, no tailer).
617
+ watch: options.watch,
618
+ watchSink: options.watchSink,
619
+ sessionsDir: options.sessionsDir,
620
+ // The autonomous failure-surfacing: route stuck items to the arbiter's
621
+ // main (the `run` semantics), NOT local-only (the human `complete`).
622
+ surfaceArbiter: tree.arbiterRemote,
623
+ color: options.color,
624
+ note,
625
+ noteBlock: options.noteBlock,
626
+ env,
627
+ // The review AGENT (Gate 2) launches AMBIENT — never the identity env (an
628
+ // agent must not act as the bot; only the runner's git ops carry identity).
629
+ agentEnv: options.env,
630
+ });
631
+ if (completed.outcome === 'completed' ||
632
+ completed.outcome === 'already-integrated') {
633
+ // `already-integrated` is the stranded-done auto-recover's clean no-op (the
634
+ // kept tip was already on `<arbiter>/main` — e.g. the prior PR merged
635
+ // out-of-band before the re-claim). It is a SUCCESSFUL terminal state — the
636
+ // work is integrated — so it folds into `DoOutcome 'completed'` (exit 0),
637
+ // not a new outcome value: the autonomous caller just needs to know the
638
+ // integrate path ended cleanly. The distinct `CompleteOutcome` value
639
+ // preserves the no-op signal for tests + the `complete` surface.
640
+ return {
641
+ exitCode: 0,
642
+ outcome: 'completed',
643
+ slug,
644
+ branch,
645
+ message: completed.message,
646
+ };
647
+ }
648
+ if (completed.outcome === 'review-unparseable') {
649
+ // Gate 2 RAN but its verdict was UNPARSEABLE (malformed JSON). The core already
650
+ // routed it work-preservingly (branch pushed + surfaced). This is the FAILURE-
651
+ // CAUSE axis, NOT a reviewer block: classify the parse-failure phrase the core
652
+ // recorded → `transient-infra` (re-run the SAME work: the gate output is
653
+ // stochastic + the parser now repairs the control-char class, so a re-run is far
654
+ // more likely to parse). Same `classifyFailureCause` `run` uses, so `do`/`run`
655
+ // agree on the label. (`classifyFailureCause` only EVER returns config/transient/
656
+ // agent-failed; the malformed-JSON signature lands transient-infra, never a
657
+ // silent success.)
658
+ const cause = classifyFailureCause(completed.message);
659
+ return {
660
+ exitCode: 1,
661
+ outcome: failureCauseToDoOutcome(cause),
662
+ slug,
663
+ branch,
664
+ message: completed.message,
665
+ };
666
+ }
667
+ if (completed.outcome === 'prepare-failed' ||
668
+ completed.outcome === 'gate-failed' ||
669
+ completed.outcome === 'review-blocked' ||
670
+ completed.outcome === 'rebase-conflict' ||
671
+ completed.outcome === 'strand-surfaced') {
672
+ // Failed env-prep / red gate / Gate-2 review block / rebase conflict — routed
673
+ // to needs-attention (surfaced on the arbiter). A `prepare-failed` (the env
674
+ // could not be made ready, so verify was NOT run) and a `review-blocked` are
675
+ // mapped HERE the SAME way `gate-failed` is. The work did NOT complete; the
676
+ // runner owns the bounce.
677
+ //
678
+ // `strand-surfaced` is the autonomous-strand parity (the SHARED `complete.ts`
679
+ // seam already surfaced the source-strand / empty-staged refusal to
680
+ // needs-attention on the arbiter) — in-place `performDo` inherits the fix
681
+ // here, mapped to the SAME `needs-attention` outcome shape `do --remote`
682
+ // (`runRemotePipeline`) uses, so `advance task:<slug>` (via the default
683
+ // `doDriver = performDo`) agrees with the remote path on the caller-visible
684
+ // label.
685
+ return {
686
+ exitCode: 1,
687
+ outcome: 'needs-attention',
688
+ slug,
689
+ branch,
690
+ message: completed.message,
691
+ };
692
+ }
693
+ if (completed.outcome === 'surface-unmoved') {
694
+ // Strand-surface could not land on the arbiter (CAS contention exhausted /
695
+ // no arbiter) — HONESTLY still in-progress on the arbiter. Mirror
696
+ // `runRemotePipeline`'s `surface-unmoved` mapping so in-place `performDo`
697
+ // (and `advance task:<slug>` via the default `doDriver`) agrees with
698
+ // `do --remote` on the same signal, never a fake success.
699
+ return {
700
+ exitCode: 1,
701
+ outcome: 'surface-unmoved',
702
+ slug,
703
+ branch,
704
+ message: completed.message,
705
+ };
706
+ }
707
+ // refused (nothing to commit, wrong folder) / usage-error: surface verbatim —
708
+ // BUT first reclassify a thrown CORE wiring/config error (which `performComplete`
709
+ // swallows into `usage-error`) onto the SAME `config-error` cause `run` records,
710
+ // closing the cross-path divergence (`do`: usage-error vs `run`: agent-failed for
711
+ // the identical thrown core error). Best-effort: a non-config usage-error stays
712
+ // `usage-error` (the conservative default — the classifier only re-labels what it
713
+ // recognises).
714
+ if (completed.outcome === 'usage-error') {
715
+ const cause = classifyFailureCause(completed.message);
716
+ if (cause === 'config-error') {
717
+ return {
718
+ exitCode: 1,
719
+ outcome: 'config-error',
720
+ slug,
721
+ branch,
722
+ message: completed.message,
723
+ };
724
+ }
725
+ }
726
+ const outcome = completed.outcome === 'refused' ? 'refused' : 'usage-error';
727
+ return { exitCode: 1, outcome, slug, branch, message: completed.message };
728
+ }
729
+ /**
730
+ * The EXACT recovery one-liner handed to the operator when an isolated/remote `do`
731
+ * integration fails terminally AFTER the work was committed + done-moved (the
732
+ * stale-lease-strand class surfaced by Part B #97), leaving the job worktree
733
+ * RETAINED. It points them straight at the recover-already-committed path
734
+ * (`complete --isolated <slug>`) so they need not reverse-engineer the encoded
735
+ * worktree path \u2014 the FINISH half of try-to-finish / else-surface. Detection is
736
+ * unspoofable (an already-integrated task is a clean no-op), so re-running it is
737
+ * always safe.
738
+ *
739
+ * `complete --isolated` recovers the retained WORKTREE, so it ONLY works ON THE
740
+ * MACHINE that ran the job (the worktree is local + reaped when that runner ends).
741
+ * From a DIFFERENT checkout (e.g. a CI-stranded job finished on your laptop),
742
+ * `--isolated` finds no local worktree and silently no-ops; there, check out the
743
+ * already-pushed work branch off the arbiter and run plain `complete`. Both named.
744
+ */
745
+ export function recoverIsolatedOneLiner(slug) {
746
+ return (`To FINISH the stranded branch once the cause clears: ON THE SAME MACHINE that ` +
747
+ `ran the job, run \`dorfl complete --isolated ${slug}\` (integrates the ` +
748
+ `kept commit from the retained worktree; a no-op if already integrated). From ` +
749
+ `ANOTHER checkout (e.g. a CI-stranded job finished on your laptop), check out ` +
750
+ `the pushed work branch off the arbiter and run plain \`dorfl complete ` +
751
+ `${slug}\` instead.`);
752
+ }
753
+ /**
754
+ * Build the HONEST per-op fragment describing WHAT actually reached the arbiter
755
+ * after a needs-attention route — reading the seam's captured per-op outcome
756
+ * (`branchPush`) rather than ASSUMING "pushed" off the local move. Shared by
757
+ * every save-failure / save-stop message site so they can never drift from
758
+ * reality (the observed bug: the report claimed "pushed" when the branch push
759
+ * was skipped-empty or failed). A PUSH failure (HIGH severity: work-at-risk /
760
+ * breaks cross-machine recovery) flips the fragment to a loud "saved LOCALLY
761
+ * only" with recovery guidance. The OBSERVABLE half (the stuck state) now rides
762
+ * on the per-item lock `state: stuck` amend — there is no separate on-`main`
763
+ * surface to report.
764
+ */
765
+ function routeReport(routed, branch) {
766
+ const branchPush = routed.branchPush ?? 'not-attempted';
767
+ const branchFailed = branchPush === 'failed';
768
+ if (branchFailed) {
769
+ // HIGH severity: the recoverable branch push did not reach the arbiter — say
770
+ // so loudly, the work is saved LOCALLY only, and how to recover.
771
+ return {
772
+ fragment: `push of ${branch} FAILED — the work is saved LOCALLY only; push it ` +
773
+ 'when online, then `requeue` (continue), or `requeue --reset` to discard',
774
+ pushFailed: true,
775
+ };
776
+ }
777
+ // The push that was attempted succeeded (or was honestly skipped). Report
778
+ // the branch state truthfully.
779
+ let landed;
780
+ if (branchPush === 'pushed') {
781
+ landed = `pushed ${branch}`;
782
+ }
783
+ else if (branchPush === 'skipped-empty') {
784
+ landed = `branch ${branch} skipped (nothing to recover yet)`;
785
+ }
786
+ else {
787
+ landed = 'saved locally';
788
+ }
789
+ return {
790
+ fragment: `${landed}. Recover via \`requeue\` (continue) or \`requeue --reset\` to discard`,
791
+ pushFailed: false,
792
+ };
793
+ }
794
+ /**
795
+ * SAVE the partial work of a FAILED agent instead of dropping it (the keystone of
796
+ * the `agent-fail-saves-work` task). An agent failure (`runDoAgent` returned
797
+ * `ok:false`, threw, or the prompt could not be assembled) used to BARE-RETURN
798
+ * `agent-failed`, leaving whatever the agent edited only on the local work branch
799
+ * in the (disposable, possibly remote) job worktree — silently lost.
800
+ *
801
+ * This routes it through the SAME work-preserving machinery a RED GATE uses: the
802
+ * ledger write seam's needs-attention transition (`git add -A` + a wip commit
803
+ * capturing the agent's work + the `git mv → needs-attention/` move-only commit
804
+ * with the failure detail recorded as the reason in the body), surfaced on the
805
+ * arbiter's `main` (the autonomous, cross-machine-visible mode-M surfacing `do`
806
+ * already uses for the gate-fail path) so `scan`/`status`/another machine see it.
807
+ *
808
+ * It ALSO pushes the `work/<slug>` branch to the arbiter so the saved partial
809
+ * commits travel cross-machine and the item is RECOVERABLE via `requeue`
810
+ * (continue): the continue-detection in `continue-branch.ts` looks for an arbiter
811
+ * `work/<slug>` ahead of main. That push now lives IN the ledger write seam (the
812
+ * RECOVERABLE half of the needs-attention transition — fired when an `arbiter` is
813
+ * given, best-effort, emptiness-guarded), consolidated there by
814
+ * `centralise-bounce-branch-push` so it cannot drift from the OBSERVABLE surface;
815
+ * this function no longer pushes separately.
816
+ *
817
+ * The EMPTY-failure case (the agent made NO commits / no changes) is handled
818
+ * without crashing on an empty commit: `routeToNeedsAttention` (under the seam)
819
+ * skips the wip commit when the tree is clean, and the move-only commit (reason +
820
+ * the `git mv`) is always non-empty, so the failure reason is still surfaced.
821
+ *
822
+ * The OUTCOME is the classified failure CAUSE (the FAILURE-CAUSE axis): the
823
+ * genuinely-new `transient-infra` / `config-error` where the surfaced detail makes
824
+ * the cause knowable, else the conservative generic `agent-failed` (still distinct
825
+ * from a clean success and from a red `gate-failed`/`needs-attention` — `do`'s exit
826
+ * contract stays coherent). Only the WORK-PRESERVING side-effect (unchanged here)
827
+ * matches the gate-failure path. We do NOT validate or "fix" the partial work — a broken
828
+ * tree committed + surfaced (with the reason) is recoverable; the human chooses
829
+ * `requeue` (continue) vs `requeue --reset` (discard).
830
+ */
831
+ async function saveAgentFailure(params) {
832
+ const { slug, cwd, arbiter, detail, env, note } = params;
833
+ // The work branch is the namespaced build branch (`work/task-<slug>`; the
834
+ // onboarding switched the checkout to it before the agent ran); derive it from
835
+ // the slug so the push target is always defined even when the caller's `branch`
836
+ // was not narrowed.
837
+ const branch = params.branch ?? workBranchRef('task', slug);
838
+ // Classify the failure CAUSE (best-effort + conservative) from the surfaced
839
+ // detail — the SAME `classifyFailureCause` `run` uses, so `do`/`run` agree on the
840
+ // same error. The cause LABEL prefixes the recorded reason so the cause is legible
841
+ // on the needs-attention route without a second naming scheme; `agent-failed`
842
+ // keeps the historical "agent failed:" prefix (no reason-prose regression).
843
+ const cause = classifyFailureCause(detail);
844
+ const reason = `${failureCauseLabel(cause)}: ${detail}`;
845
+ // Route through the SAME seam the gate-fail path uses: save the agent's work as
846
+ // a wip commit (skipped when the tree is clean — the empty-failure case), amend
847
+ // the item's per-item lock ref to `state: stuck` with the reason in the body,
848
+ // surface that lock amend on the arbiter (OBSERVABLE, mode-M, cross-machine
849
+ // visible) AND push the `work/<slug>` branch (RECOVERABLE — so a requeue-continue
850
+ // reading <arbiter>/work/<slug> lands on the saved wip). Both halves fire from
851
+ // the single `arbiter` here; no separate push to forget.
852
+ const routed = await ledgerWrite.applyNeedsAttentionTransition({
853
+ cwd,
854
+ slug,
855
+ reason,
856
+ arbiter,
857
+ env,
858
+ note,
859
+ });
860
+ const report = routed.moved ? routeReport(routed, branch) : undefined;
861
+ const message = routed.moved
862
+ ? `Agent run failed building '${slug}' [${cause}] (${detail}); SAVED the ` +
863
+ `partial work and marked '${slug}' stuck on its lock (${report.fragment}).`
864
+ : `Agent run failed building '${slug}' [${cause}] (${detail}); could not ` +
865
+ `mark '${slug}' stuck (${routed.reasonNotMoved ?? 'unknown'}).`;
866
+ note(message);
867
+ return {
868
+ exitCode: 1,
869
+ outcome: failureCauseToDoOutcome(cause),
870
+ slug,
871
+ branch,
872
+ routedToNeedsAttention: routed.moved,
873
+ message,
874
+ };
875
+ }
876
+ /**
877
+ * Map a {@link FailureCause} onto the `do` {@link DoOutcome}. The cause names ARE
878
+ * the outcome names (the FAILURE-CAUSE axis reuses the terminal vocabulary), so
879
+ * this is identity — a single helper documents the mapping + keeps the `do`/`run`
880
+ * sites symmetric (`run` has the twin {@link failureCauseToItemStatus}).
881
+ */
882
+ function failureCauseToDoOutcome(cause) {
883
+ return cause;
884
+ }
885
+ /**
886
+ * Build the HONEST result for a CONTINUE-site surface that did NOT land on the
887
+ * arbiter (`{moved: false}`). The tree-less `in-progress/ → needs-attention/` move
888
+ * lost the CAS race against a busy arbiter (its contention-retry cap exhausted) or
889
+ * had no arbiter to publish to, so the item is STILL in-progress on the arbiter —
890
+ * a clean `needs-attention` would mislead (it claims the surface landed). Distinct
891
+ * `surface-unmoved` outcome, carrying `reasonNotMoved`, so the caller/human can
892
+ * tell it from a successful surface and retry/resolve. The `moved: true` path is
893
+ * left byte-for-byte unchanged (this branch is only reached on `!moved`).
894
+ */
895
+ function surfaceUnmovedDoResult(params) {
896
+ const { slug, branch, reason, surfaced } = params;
897
+ const message = `'${slug}' could NOT be surfaced to needs-attention — the surface did not ` +
898
+ `reach the arbiter's main; the item is still IN-PROGRESS on the arbiter ` +
899
+ `(retry/resolve). ${surfaced.reasonNotMoved ?? reason}`;
900
+ return { exitCode: 1, outcome: 'surface-unmoved', slug, branch, message };
901
+ }
902
+ /**
903
+ * Resolve the STOP reason for a clean (`agent.ok`) run, or `undefined` when the
904
+ * run is a genuine build that should proceed to the gate (task
905
+ * `agent-stop-signal`). TWO independent triggers, the sentinel winning:
906
+ *
907
+ * 1. The IN-BAND STOP sentinel in the agent's output ({@link parseStopSentinel})
908
+ * — the principled case: the agent declared the task drifted/ambiguous and
909
+ * reported WHY. Its reason is used VERBATIM (a non-empty diff with a sentinel
910
+ * is still a STOP — the agent may have left scratch; the sentinel wins).
911
+ * 2. The DETERMINISTIC empty-diff backstop ({@link isWorkBranchDiffEmpty}) — the
912
+ * observable safety net for when the agent stopped WITHOUT (or with a
913
+ * malformed) sentinel: `agent.ok` but no source change vs `<arbiter>/main` is
914
+ * never a successful build.
915
+ *
916
+ * Shared by `performDo` and `runRemotePipeline` so the in-place and remote forms
917
+ * detect a STOP identically.
918
+ */
919
+ async function resolveStopReason(params) {
920
+ const { output, slug, cwd, arbiter, env } = params;
921
+ const sentinel = parseStopSentinel(output);
922
+ if (sentinel !== undefined) {
923
+ return sentinel.reason;
924
+ }
925
+ if (await isWorkBranchDiffEmpty({ cwd, arbiter, env })) {
926
+ return emptyDiffStopReason(slug);
927
+ }
928
+ return undefined;
929
+ }
930
+ /**
931
+ * Route a DELIBERATE agent STOP (task `agent-stop-signal`) to needs-attention
932
+ * through the SAME work-preserving seam `saveAgentFailure` uses (save the branch,
933
+ * surface on the arbiter) — but as the DISTINCT `agent-stopped` outcome, NOT
934
+ * `agent-failed` (the agent did not error) nor `needs-attention` (no red gate /
935
+ * rebase conflict). The agent's STOP reason is recorded VERBATIM as the
936
+ * needs-attention reason. The acceptance gate AND Gate-2 are NEVER reached.
937
+ */
938
+ async function saveAgentStop(params) {
939
+ const { slug, cwd, arbiter, reason, env, note } = params;
940
+ const branch = params.branch ?? workBranchRef('task', slug);
941
+ const routed = await ledgerWrite.applyNeedsAttentionTransition({
942
+ cwd,
943
+ slug,
944
+ reason,
945
+ arbiter,
946
+ env,
947
+ note,
948
+ });
949
+ const report = routed.moved ? routeReport(routed, branch) : undefined;
950
+ const message = routed.moved
951
+ ? `The agent STOPPED building '${slug}' (the task drifted / is ambiguous / ` +
952
+ `produced no change); marked '${slug}' stuck on its lock (${report.fragment}) ` +
953
+ `WITHOUT running the gate or Gate-2 review. Reason: ${reason}`
954
+ : `The agent STOPPED building '${slug}' but it could not be marked stuck ` +
955
+ `(${routed.reasonNotMoved ?? 'unknown'}). Reason: ${reason}`;
956
+ note(message);
957
+ return {
958
+ exitCode: 1,
959
+ outcome: 'agent-stopped',
960
+ slug,
961
+ branch,
962
+ routedToNeedsAttention: routed.moved,
963
+ message,
964
+ };
965
+ }
966
+ /**
967
+ * Run the agent against the checkout. Prefers the injected `dorfl` (tests
968
+ * / custom embeddings); otherwise launches `agentCmd` through the SHARED
969
+ * {@link launchWithOptionalWatch} helper (the null adapter by default),
970
+ * forwarding the model routing intent.
971
+ *
972
+ * With `--watch` (pi harness only, validated earlier), the helper launches the
973
+ * agent NON-BLOCKING (`PiHarness.launchAsync` — `spawn`, not `spawnSync`) so a
974
+ * `SessionTailer` can READ the growing session `.jsonl` concurrently and surface
975
+ * the high-signal events live. The tailer is a pure observer: the launch result
976
+ * is IDENTICAL to the non-watch path, so outcome / gate / git / exit code are
977
+ * unchanged — only a concurrent log-tail is added.
978
+ *
979
+ * The build session-id is the SLUG (in-place `do` has no work-id), which the
980
+ * helper makes unique per launch; the Gate-2 REVIEW launch uses the SAME helper
981
+ * with a DISTINCT id (`<slug>-review`) so the two sessions never collide — one
982
+ * watch implementation, two callers (task `watch-review-session`).
983
+ */
984
+ async function runDoAgent(options, cwd, prompt, slug) {
985
+ if (options.dorfl) {
986
+ return options.dorfl({ cwd, prompt, slug, env: options.env });
987
+ }
988
+ const harness = options.harness ?? new NullHarness();
989
+ const launched = await launchWithOptionalWatch({
990
+ harness,
991
+ dir: cwd,
992
+ slug,
993
+ command: options.agentCmd ?? '',
994
+ prompt,
995
+ model: options.model,
996
+ // In-place `do` has NO work-id, so the build session id is the slug.
997
+ sessionId: slug,
998
+ sessionsDir: options.sessionsDir,
999
+ watch: options.watch,
1000
+ watchSink: options.watchSink,
1001
+ color: options.color,
1002
+ env: options.env,
1003
+ });
1004
+ // Surface the agent's FINAL SUMMARY (`LaunchResult.output`) — the source channel
1005
+ // for the propose-mode PR body — instead of dropping it. Absent (no parseable
1006
+ // assistant text) ⇒ undefined ⇒ the body degrades to `--fill` (no regression).
1007
+ return { ok: launched.ok, detail: launched.detail, output: launched.output };
1008
+ }
1009
+ /**
1010
+ * True when the working tree has uncommitted (unstaged OR staged) changes — the
1011
+ * dirty-tree refusal predicate (ar-run.sh: `git diff --quiet` AND
1012
+ * `git diff --cached --quiet`). Read-only.
1013
+ */
1014
+ async function isDirtyTree(cwd, env) {
1015
+ const unstaged = await runAsync('git', ['diff', '--quiet'], cwd, { env });
1016
+ if (unstaged.status !== 0) {
1017
+ return true;
1018
+ }
1019
+ const staged = await runAsync('git', ['diff', '--cached', '--quiet'], cwd, {
1020
+ env,
1021
+ });
1022
+ return staged.status !== 0;
1023
+ }
1024
+ /**
1025
+ * `dorfl do --remote <r> <arg>` — the per-repo `do` WORKER run against a
1026
+ * REGISTERED repo with NO checkout (`docs/adr/command-surface-and-journeys.md`
1027
+ * §3). Where the in-place {@link performDo} uses the CURRENT checkout AS its
1028
+ * isolation, this form materialises a **hub mirror + job worktree in the AGENTS'
1029
+ * area** (`workspacesDir`, the SAME isolation `run` uses — NEVER the human area),
1030
+ * runs the existing `do` pipeline against that worktree, then tears it down per
1031
+ * the §4 provably-safe deletion predicate.
1032
+ *
1033
+ * **Option A — materialise-then-reuse** (the drift correction; the full
1034
+ * IsolatedTree-seam unification is the SEPARATE `do-run-share-isolation-seam`
1035
+ * task). `performDo` composes the human verbs against a literal `cwd`; this
1036
+ * function does the same — it just points that `cwd` at a freshly-cut job
1037
+ * worktree instead of a checkout. It reuses (does NOT reimplement) the pipeline:
1038
+ * `performStart` (resume) → agent → `performComplete`.
1039
+ *
1040
+ * **The claim ↔ worktree ↔ start composition.** Both `createJob` (cuts the
1041
+ * `work/<slug>` branch off the mirror's fresh main) and `performStart` (claims +
1042
+ * switches to `work/<slug>`) overlap. To compose without double-claiming or
1043
+ * fighting over the branch we order them as the task mandates:
1044
+ *
1045
+ * 1. **CLAIM FIRST** — the CAS push to the arbiter, run in a throwaway clone of
1046
+ * the mirror (the CAS needs a non-bare checkout with an `origin/main`
1047
+ * tracking ref + a worktree to commit in, which a bare mirror does not
1048
+ * provide — exactly `work-on`'s remote-form claim context).
1049
+ * 2. **MATERIALISE the worktree** off the POST-CLAIM fresh main via the EXISTING
1050
+ * job-worktree machinery (`jobWorktreeStrategy`/`createJob`): `createJob`
1051
+ * re-`ensureMirror`s (fetching the claim move) then cuts `work/<slug>` off
1052
+ * the freshly-fetched mirror main.
1053
+ * 3. **RUN start/agent/complete** against the worktree dir as `cwd`. `start` is
1054
+ * driven with `resume: true` so it PLAIN-SWITCHES the branch `createJob`
1055
+ * already created (the item is in-progress on the arbiter after the claim;
1056
+ * `performStart`'s resume path switches without re-claiming) — no
1057
+ * double-claim, no branch fight. The worktree's arbiter remote is `origin`
1058
+ * (the bare mirror's clone remote), so both `start` and `complete` use
1059
+ * `origin`.
1060
+ *
1061
+ * **Teardown** re-applies the §4 predicate via the strategy handle's `teardown`
1062
+ * (`reapJob`): reap the worktree iff clean AND on the arbiter, retain otherwise
1063
+ * (the never-lose-work signal). NEVER `--force`.
1064
+ *
1065
+ * **Recovery contract.** The worktree is disposable; the durable artifact is the
1066
+ * `work/<slug>` BRANCH (pushed by the autonomous needs-attention surfacing on a
1067
+ * stuck/failed run). A human recovers via the human face (`requeue` + re-claim,
1068
+ * or `work-on`), NEVER by editing the agents'-area worktree.
1069
+ */
1070
+ /**
1071
+ * Resolve the URL of a CHECKOUT's arbiter remote — the primitive `do --isolated`
1072
+ * needs to point the job-worktree pipeline ({@link performDoRemote}) at MY OWN
1073
+ * arbiter (`git -C <cwd> remote get-url <arbiter>`). Returns the URL, or
1074
+ * `undefined` when the cwd is not a git repo or has no such remote (the
1075
+ * "isolated against what?" case the CLI turns into a clear error naming
1076
+ * `--remote <url>`). Does NOT fork the isolation/integrate path — it only feeds
1077
+ * the EXISTING `performDoRemote` its `remote` URL.
1078
+ */
1079
+ export function resolveArbiterUrlFromCheckout(cwd, arbiter, env) {
1080
+ const res = run('git', ['remote', 'get-url', arbiter], cwd, { env });
1081
+ if (res.status !== 0) {
1082
+ return undefined;
1083
+ }
1084
+ const url = res.stdout.trim();
1085
+ return url === '' ? undefined : url;
1086
+ }
1087
+ export async function performDoRemote(options) {
1088
+ const note = options.note ?? (() => { });
1089
+ // The runner's GIT/provider env, scoped to the configured identity (claim,
1090
+ // push, integrate, `gh`). The AGENT launch stays ambient via `options.env`
1091
+ // (`runDoAgent` reads it directly) — the agent must not commit as the bot.
1092
+ // Absent identity ⇒ `options.env` unchanged (byte-for-byte ambient).
1093
+ const workspacesDir = options.workspacesDir;
1094
+ // Resolve the identity env AND run the push-time transport-coherence guard:
1095
+ // refuse a forbidden transport for THIS remote's URL (the registered remote),
1096
+ // and fail cleanly on an unresolvable identity (e.g. `tokenEnv` unset) — never
1097
+ // a crash or a silent ambient fallback. Both are no-ops without an identity.
1098
+ let env;
1099
+ try {
1100
+ env = identityEnv(options.identity, options.env ?? process.env);
1101
+ assertTransportAllowed(options.identity, options.remote);
1102
+ }
1103
+ catch (err) {
1104
+ return {
1105
+ exitCode: 1,
1106
+ outcome: 'usage-error',
1107
+ message: err instanceof Error ? err.message : String(err),
1108
+ };
1109
+ }
1110
+ // 0. `--watch` REQUIRES the pi harness (same guard as in-place `do`): only the
1111
+ // pi adapter writes a session `.jsonl` to tail. Error CLEARLY here, BEFORE
1112
+ // any mirror/claim/worktree side-effect. The injected `dorfl` (tests)
1113
+ // is its own launch path and is exempt.
1114
+ if (options.watch === true &&
1115
+ options.dorfl === undefined &&
1116
+ !(options.harness instanceof PiHarness)) {
1117
+ return {
1118
+ exitCode: 1,
1119
+ outcome: 'usage-error',
1120
+ message: '`do --watch` requires the pi harness; configure `harness: pi` or drop ' +
1121
+ '`--watch`.',
1122
+ };
1123
+ }
1124
+ // 1. Resolve / auto-create the hub mirror for `<r>` (the `registry-remote` /
1125
+ // `work-on`-remote precedent: an unregistered remote is auto-mirrored
1126
+ // before use). `ensureMirror` creates it (`git clone --bare`) when absent
1127
+ // or fetches it when present, under `workspacesDir/repos/` — the agents'
1128
+ // area, NEVER the human area.
1129
+ let mirror;
1130
+ try {
1131
+ mirror = ensureMirror({ url: options.remote, workspacesDir, env });
1132
+ }
1133
+ catch (err) {
1134
+ const message = err instanceof Error ? err.message : String(err);
1135
+ return { exitCode: 1, outcome: 'usage-error', message };
1136
+ }
1137
+ note(mirror.created
1138
+ ? `Auto-registered hub mirror for ${mirror.url} at ${mirror.path}.`
1139
+ : `Using hub mirror for ${mirror.url} at ${mirror.path}.`);
1140
+ // 1b. PR-INTENT pre-flight guard — the AUTONOMOUS mirror of in-place `performDo`
1141
+ // step 3c. The SAME predicate, run UP FRONT here (mirror resolved, BEFORE the
1142
+ // claim clone / worktree) so a `propose` run on a GitHub arbiter that INTENDS a
1143
+ // PR (`noPR` unset) fails fast when `gh` genuinely cannot open one, instead of
1144
+ // letting integration silently degrade to manual-PR instructions. NO claim/
1145
+ // build side-effect: it precedes even the throwaway claim CLONE below. The probe
1146
+ // runs in the bare mirror dir (`mirror.path`), which carries the arbiter remote
1147
+ // as `origin`, NOT a bare cwd. CRITICAL: the PROBE is the signal, NOT a config
1148
+ // check — an absent `providers.github` identity falls back to AMBIENT `gh` auth
1149
+ // (the probe reports it available), so a working ambient setup still PROCEEDS.
1150
+ // `arbiterIsGitHub` is derived from `mirror.url` (the resolved arbiter URL).
1151
+ // REUSES the predicate + message so the in-place + autonomous paths cannot
1152
+ // drift.
1153
+ {
1154
+ const probe = options.ghCanOpenPr ??
1155
+ ((probeCwd, probeEnv) => new GitHubProvider().available(probeCwd, probeEnv));
1156
+ if (shouldFailProposePrIntent({
1157
+ mode: options.integration ?? 'propose',
1158
+ arbiterIsGitHub: isGitHubArbiterUrl(mirror.url),
1159
+ noPR: options.noPR,
1160
+ ghCanOpenPr: () => probe(mirror.path, env),
1161
+ })) {
1162
+ return {
1163
+ exitCode: 1,
1164
+ outcome: 'refused',
1165
+ message: PROPOSE_PR_INTENT_GH_UNAVAILABLE_MESSAGE,
1166
+ };
1167
+ }
1168
+ }
1169
+ // 1c. STATIC fresh-worktree-gate readiness guard — the AUTONOMOUS mirror of
1170
+ // `performDo` step 3d. When the fresh-worktree gate is ON AND `prepare`
1171
+ // resolves to no commands AND a lockfile is present IN THE MIRROR, the
1172
+ // throwaway worktree the gate runs in will have no installed deps, so the
1173
+ // gate cannot run. Probe the bare mirror's main tree (`git ls-tree main`)
1174
+ // so the guard fires BEFORE the throwaway claim clone is even cut. A repo
1175
+ // with NO lockfile is the intentional dep-free case and proceeds. Deps-only
1176
+ // (verify-unset is impossible — `resolveVerifyCommands` defaults the gate).
1177
+ {
1178
+ const guard = checkGatePreconditions({
1179
+ freshWorktreeGate: options.freshWorktreeGate,
1180
+ prepare: options.prepare,
1181
+ lockfile: detectLockfileOnMirrorMain(mirror.path, env),
1182
+ });
1183
+ if (guard !== undefined) {
1184
+ return { exitCode: 1, outcome: 'refused', message: guard.message };
1185
+ }
1186
+ }
1187
+ // 2. A throwaway claim clone of the mirror (the CAS context). Slug resolution
1188
+ // + the claim both run here against `origin` (the arbiter URL). Keyed PER
1189
+ // ARG (not a single fixed `__remote` path per mirror) so two CONCURRENT
1190
+ // `do --remote` calls on the SAME mirror — the registry-set advance batch's
1191
+ // per-mirror concurrency (`perRepoMax > 1`) — get DISTINCT claim clones rather
1192
+ // than racing one shared dir (the SAME per-job-clone keying `run`'s
1193
+ // `claimAgainstRepo` uses; the prior single-shot caller never raced, so this
1194
+ // is a pure concurrency hardening, not a behaviour change for it).
1195
+ const claimKey = options.arg.replace(/[^a-zA-Z0-9._-]/g, '_');
1196
+ const claimDir = join(workspacesDir, 'claim', `${encodeRepoKey(mirror.url).split('/').join('__')}__${claimKey}`);
1197
+ rmSync(claimDir, { recursive: true, force: true });
1198
+ mkdirSync(dirname(claimDir), { recursive: true });
1199
+ git(['clone', '--quiet', mirror.url, claimDir], dirname(claimDir), { env });
1200
+ try {
1201
+ // 2a. Resolve the slug across BOTH namespaces against the claim clone (it
1202
+ // carries `work/` from the mirror's main). A collision / resolution
1203
+ // failure is a loud usage error; a `prd:` arg reaches the not-yet-wired
1204
+ // stub — identical behaviour to in-place `do`.
1205
+ let resolved;
1206
+ try {
1207
+ resolved = resolveSlug({
1208
+ arg: options.arg,
1209
+ repoPath: claimDir,
1210
+ read: options.read ?? ledgerRead,
1211
+ });
1212
+ }
1213
+ catch (err) {
1214
+ if (err instanceof SlugResolutionError) {
1215
+ return { exitCode: 1, outcome: 'usage-error', message: err.message };
1216
+ }
1217
+ const message = err instanceof Error ? err.message : String(err);
1218
+ return { exitCode: 1, outcome: 'usage-error', message };
1219
+ }
1220
+ // `do --remote spec:<slug>` routes to tasking (the `spec` namespace).
1221
+ if (resolved.namespace === 'spec') {
1222
+ // `do --remote spec:<slug>`: task the spec as the AGENT, against the claim
1223
+ // clone (its `origin` IS the arbiter URL + it carries a working tree from the
1224
+ // mirror's main). No job worktree is needed — the tasking transition is a
1225
+ // runner-owned `spec → tasking → spec` move + emit-backlog on the arbiter, not
1226
+ // a build pipeline. The agent only writes task files; the runner does all git.
1227
+ const tasked = await performTask({
1228
+ slug: resolved.slug,
1229
+ cwd: claimDir,
1230
+ arbiter: 'origin',
1231
+ doer: 'agent',
1232
+ autoTask: options.autoTask,
1233
+ // EXPLICIT dispatch (same as the in-place path above): the `prd:<slug>` was
1234
+ // NAMED (typed, or pool-filtered on `autoTask` before reaching here), so the
1235
+ // tasking gate drops the policy term — only the prd's own readiness +
1236
+ // `taskedAfter` bind, mirroring the build path vs `autoBuild`.
1237
+ explicit: true,
1238
+ dorfl: options.dorfl,
1239
+ harness: options.harness,
1240
+ agentCmd: options.agentCmd,
1241
+ model: options.model,
1242
+ sessionsDir: options.sessionsDir,
1243
+ // The integrate-time args (task `slice-output-through-integration`): the
1244
+ // `provider` is the SAME the task-build path threads (arg parity), but the
1245
+ // MODE is the per-TRANSITION TASKING resolution
1246
+ // (`per-transition-integration-mode-slicing-vs-build`):
1247
+ // `taskingIntegration ?? integration`, so the `--remote prd:` output ALSO
1248
+ // routes through the shared core with the tasking-resolved mode.
1249
+ integration: options.taskingIntegration ?? options.integration,
1250
+ // The per-repo TASK-PLACEMENT default + the operator's explicit
1251
+ // override (task `runner-deterministic-slice-placement-policy-and-
1252
+ // precedence`). Same threading as the in-place `do prd:` path.
1253
+ tasksLandIn: options.tasksLandIn,
1254
+ explicitTasksLandIn: options.explicitTasksLandIn,
1255
+ noPR: options.noPR,
1256
+ providerInstance: options.providerInstance,
1257
+ // The tasker review→edit→converge loop on the `do --remote prd:` path too.
1258
+ reviewLoop: options.reviewLoop,
1259
+ taskerLoopMax: options.taskerLoopMax,
1260
+ reviewExecutions: options.reviewExecutions,
1261
+ taskerLoopModel: options.taskerLoopModel,
1262
+ // The task-SET ACCEPTANCE GATE on the `do --remote prd:` path too.
1263
+ review: options.review,
1264
+ reviewGate: options.taskReviewGate,
1265
+ acceptanceReviewModel: options.reviewModel,
1266
+ env,
1267
+ // The tasker + review AGENTS launch AMBIENT, never the identity env.
1268
+ agentEnv: options.env,
1269
+ note,
1270
+ });
1271
+ return taskResultToDoResult(tasked);
1272
+ }
1273
+ const slug = resolved.slug;
1274
+ // 3. CLAIM FIRST (the CAS push to the arbiter), in the throwaway clone.
1275
+ // `origin` there IS the arbiter URL. A lost/contended/usage claim is
1276
+ // propagated verbatim — NO worktree is materialised (clean failure, like
1277
+ // `run`'s `runOneItem`).
1278
+ const claim = await performClaim({
1279
+ slug,
1280
+ cwd: claimDir,
1281
+ arbiter: 'origin',
1282
+ // `--allow-backlog`: accept a `tasks/backlog/`-resident body (staging). The
1283
+ // claim stays a pure lock. Default off ⇒ pool-only.
1284
+ allowBacklog: options.allowBacklog === true,
1285
+ env,
1286
+ note,
1287
+ });
1288
+ if (claim.outcome === 'lost') {
1289
+ return { exitCode: 2, outcome: 'lost', slug, message: claim.message };
1290
+ }
1291
+ if (claim.outcome === 'contended') {
1292
+ return { exitCode: 3, outcome: 'contended', slug, message: claim.message };
1293
+ }
1294
+ if (claim.exitCode !== 0) {
1295
+ return {
1296
+ exitCode: 1,
1297
+ outcome: 'usage-error',
1298
+ slug,
1299
+ message: claim.message,
1300
+ };
1301
+ }
1302
+ // 4. MATERIALISE the job worktree off the POST-CLAIM fresh main via the
1303
+ // EXISTING job-worktree machinery (the SAME path `run` uses). `createJob`
1304
+ // re-`ensureMirror`s (fetching the claim move) then cuts `work/<slug>`
1305
+ // off the freshly-fetched mirror main, in the agents' area.
1306
+ const strategy = jobWorktreeStrategy({
1307
+ fromRepo: claimDir,
1308
+ arbiter: 'origin',
1309
+ workspacesDir,
1310
+ });
1311
+ let tree;
1312
+ let result;
1313
+ try {
1314
+ try {
1315
+ tree = strategy.prepare({ slug, type: 'task', env });
1316
+ }
1317
+ catch (err) {
1318
+ // `prepare()`/`createJob` THREW before returning the handle (e.g. an
1319
+ // onboard reconcile/stale-lease push surfaced as a throw). `tree` is
1320
+ // undefined, so the normal teardown below would be skipped and a
1321
+ // partially-created worktree could LEAK with no teardown attempt at all —
1322
+ // and its checked-out `work/<slug>` branch would then poison the next
1323
+ // build's fetch. Best-effort reap the deterministic worktree path for this
1324
+ // slug (it is reaped ONLY if its branch is reachable on the arbiter —
1325
+ // never lose work), then re-throw so the failure is still reported.
1326
+ reapPreparedWorktreeLeak(mirror.url, slug, workspacesDir, env, note);
1327
+ throw err;
1328
+ }
1329
+ result = await runRemotePipeline(options, tree, slug, note, env);
1330
+ return result;
1331
+ }
1332
+ finally {
1333
+ // 7. Teardown via the strategy handle. On a CLEAN completion: reap iff clean
1334
+ // AND on the arbiter (the standard §4 predicate). On a FAILURE return
1335
+ // (needs-attention / config-error / refused etc. — the seam already
1336
+ // surfaced the item + pushed the branch): reap on REACHABILITY ALONE, so a
1337
+ // churn-dirty-but-arbiter-safe worktree does not linger to poison the next
1338
+ // build's config-read/materialisation fetch. A worktree whose work is NOT
1339
+ // yet on the arbiter is RETAINED either way (never lose work). NEVER
1340
+ // --force.
1341
+ if (tree) {
1342
+ const reachableOnly = result !== undefined && result.outcome !== 'completed';
1343
+ tree.teardown({ reachableOnly });
1344
+ }
1345
+ }
1346
+ }
1347
+ finally {
1348
+ // Remove the throwaway claim clone either way.
1349
+ rmSync(claimDir, { recursive: true, force: true });
1350
+ }
1351
+ }
1352
+ /**
1353
+ * Best-effort reap of a worktree that {@link IsolationStrategy.prepare}/`createJob`
1354
+ * may have created at the deterministic per-job path BEFORE it threw (so the
1355
+ * handle was never returned and the normal teardown is skipped). Reaps it ONLY
1356
+ * if its branch is provably reachable on the arbiter — a `prepare` throw can
1357
+ * happen AFTER a clean continue-push (work is safe ⇒ reap so it can't poison the
1358
+ * next build) or BEFORE any push (work not safe ⇒ retain; never lose work). All
1359
+ * git ops are swallowed: this runs on an already-failing path and must never mask
1360
+ * the original throw.
1361
+ */
1362
+ function reapPreparedWorktreeLeak(mirrorUrl, slug, workspacesDir, env, note) {
1363
+ try {
1364
+ const dir = jobWorktreePath(workspacesDir, mirrorUrl, slug);
1365
+ if (!existsSync(dir)) {
1366
+ return;
1367
+ }
1368
+ const mirrorDir = mirrorPath(workspacesDir, mirrorUrl);
1369
+ const result = reapJob({
1370
+ dir,
1371
+ branch: workBranchRef('task', slug),
1372
+ mirrorPath: mirrorDir,
1373
+ // Same failure-path stance as the normal teardown: reachable-on-arbiter is
1374
+ // enough (don't let incidental churn retain a worktree whose branch is
1375
+ // already safe). Reachability still gates — unsaved work is retained.
1376
+ reachableOnly: true,
1377
+ env,
1378
+ });
1379
+ if (result.removed) {
1380
+ note(`Reaped leaked worktree for ${slug} after prepare() threw ` +
1381
+ `(branch safe on the arbiter).`);
1382
+ }
1383
+ }
1384
+ catch {
1385
+ // best-effort — never mask the original prepare() throw
1386
+ }
1387
+ }
1388
+ /**
1389
+ * Run the existing `do` pipeline (start[resume] → agent → complete) against an
1390
+ * already-materialised job worktree. Mirrors {@link performDo}'s middle/back —
1391
+ * but `cwd` is the worktree and the arbiter remote inside it is `origin`. The
1392
+ * needs-attention surfacing is the AUTONOMOUS, arbiter-passed variant (like
1393
+ * `run`/in-place `do`): a stuck remote `do` must be cross-machine visible.
1394
+ */
1395
+ async function runRemotePipeline(options, tree, slug, note, env) {
1396
+ const cwd = tree.dir;
1397
+ const arbiterRemote = tree.arbiterRemote; // `origin` (the bare mirror's clone).
1398
+ // 4a. CONTINUE rebase conflict (ADR §14 + §10): a requeue kept a `work/<slug>`
1399
+ // that did not replay onto the current main at onboard-time (aborted, never
1400
+ // auto-resolved). Route to needs-attention via the seam (surfaced on the
1401
+ // arbiter + the kept branch already on the arbiter) instead of running the
1402
+ // agent — the §10 path `run` uses.
1403
+ if (tree.continueRebaseConflict) {
1404
+ const reason = `continuing the kept ${tree.branch}: rebase onto the latest main ` +
1405
+ 'conflicted (aborted, never auto-resolved) — resolve against the latest ' +
1406
+ 'main, or `requeue --reset` to discard and start fresh';
1407
+ await ledgerWrite.applyNeedsAttentionTransition({
1408
+ cwd,
1409
+ slug,
1410
+ reason,
1411
+ arbiter: arbiterRemote,
1412
+ env,
1413
+ note,
1414
+ });
1415
+ return {
1416
+ exitCode: 1,
1417
+ outcome: 'needs-attention',
1418
+ slug,
1419
+ branch: tree.branch,
1420
+ message: reason,
1421
+ };
1422
+ }
1423
+ // 4b. CONTINUE reconcile-push TERMINAL failure (the stale-lease-strand bug this
1424
+ // fix kills): the onboard reconcile push of the kept (already-committed)
1425
+ // work branch to the arbiter FAILED terminally (the stale-lease retry cap
1426
+ // exhausted, or a non-stale-lease rejection / unreachable arbiter). The push
1427
+ // helper THROWS; `createJob` CATCHES it and flags `continuePushFailure`
1428
+ // rather than letting the throw escape (which crashed the run and left the
1429
+ // task silently in `work/in-progress/` on the arbiter, the work stranded in
1430
+ // the worktree). Route to needs-attention via the SAME seam the conflict path
1431
+ // uses — surfaced on the arbiter, the kept branch already on the arbiter from
1432
+ // the prior requeue (recoverable) — instead of running the agent.
1433
+ if (tree.continuePushFailure !== undefined) {
1434
+ const reason = `continuing the kept ${tree.branch}: publishing the rebased work branch ` +
1435
+ `to the arbiter failed terminally (${tree.continuePushFailure}) — the kept ` +
1436
+ 'branch is left intact on the arbiter (recoverable); `requeue` to retry ' +
1437
+ 'once the churn settles, or `requeue --reset` to discard and start fresh';
1438
+ await ledgerWrite.applyNeedsAttentionTransition({
1439
+ cwd,
1440
+ slug,
1441
+ reason,
1442
+ arbiter: arbiterRemote,
1443
+ env,
1444
+ note,
1445
+ });
1446
+ return {
1447
+ exitCode: 1,
1448
+ outcome: 'needs-attention',
1449
+ slug,
1450
+ branch: tree.branch,
1451
+ message: reason,
1452
+ };
1453
+ }
1454
+ // 5. HARDEN start to work against a job-worktree `cwd` (the load-bearing work).
1455
+ // `performStart` reads/switches via the `<arbiter>/main` REMOTE-TRACKING ref
1456
+ // (`origin/main` here). A job worktree is cut from a BARE hub mirror whose
1457
+ // `origin` remote has NO fetch refspec, so `origin/main` /
1458
+ // `origin/work/<slug>` would not otherwise resolve — and `start` would
1459
+ // wrongly read the slug as "absent". Prime those two remote-tracking refs
1460
+ // EXPLICITLY (the SAME technique `integrator.rebaseOntoArbiterMain` /
1461
+ // `gc.fetchTracking` use against a bare-mirror worktree). After this the
1462
+ // existing `performStart` runs UNCHANGED against the worktree.
1463
+ await primeWorktreeTrackingRef(cwd, arbiterRemote, 'main', env);
1464
+ await primeWorktreeTrackingRef(cwd, arbiterRemote, workBranchRef('task', slug), env);
1465
+ // Onboard onto the work branch like in-place `do` — but the item is ALREADY
1466
+ // CLAIMED (step 3), so `performStart` runs with `resume: true`: it
1467
+ // PLAIN-SWITCHES the `work/<slug>` branch `createJob` already created WITHOUT
1468
+ // re-claiming (no double-claim, no branch fight). This proves `start` works
1469
+ // against a job-worktree `cwd`.
1470
+ const started = await performStart({
1471
+ slug,
1472
+ cwd,
1473
+ arbiter: arbiterRemote,
1474
+ resume: true,
1475
+ // `--allow-backlog`: a staged body (claimed, still in `tasks/backlog/`) is a
1476
+ // valid onboard residence; without this `start` would read it as `absent`.
1477
+ allowBacklog: options.allowBacklog === true,
1478
+ env,
1479
+ note,
1480
+ });
1481
+ if (started.outcome === 'needs-attention') {
1482
+ return {
1483
+ exitCode: 1,
1484
+ outcome: 'needs-attention',
1485
+ slug,
1486
+ branch: started.branch,
1487
+ message: started.message,
1488
+ };
1489
+ }
1490
+ // FORWARD the honest un-moved signal end-to-end: a continue-site surface that
1491
+ // did NOT land on the arbiter (start's `surface-unmoved`) must NOT degrade to
1492
+ // `usage-error` here — the item is still in-progress on the arbiter (retry/resolve).
1493
+ if (started.outcome === 'surface-unmoved') {
1494
+ return {
1495
+ exitCode: 1,
1496
+ outcome: 'surface-unmoved',
1497
+ slug,
1498
+ branch: started.branch,
1499
+ message: started.message,
1500
+ };
1501
+ }
1502
+ if (started.exitCode !== 0) {
1503
+ const outcome = started.outcome === 'refused' ? 'refused' : 'usage-error';
1504
+ return { exitCode: 1, outcome, slug, message: started.message };
1505
+ }
1506
+ const branch = started.branch;
1507
+ // 6. Build the prompt + run the agent autonomously in the worktree (the SAME
1508
+ // assembly in-place `do` uses). The agent only edits code.
1509
+ let prompt;
1510
+ try {
1511
+ // CONTINUE-aware resolution (job-worktree path): admit `work/done/` ONLY
1512
+ // behind the tip-vs-arbiter stranded gate (story 5), using the worktree's
1513
+ // primed `<origin>/work/<slug>` vs `<origin>/main` tracking refs.
1514
+ const task = resolveTask(cwd, slug, {
1515
+ cwd,
1516
+ branchRef: `${arbiterRemote}/${branch}`,
1517
+ mainRef: `${arbiterRemote}/main`,
1518
+ env,
1519
+ },
1520
+ // `--allow-backlog`: also resolve a `tasks/backlog/`-resident body.
1521
+ { allowBacklog: options.allowBacklog === true });
1522
+ // CONTINUE-mode (job-worktree path): the worktree's tracking refs were primed
1523
+ // above (`primeWorktreeTrackingRef`), so reuse the SAME continue-detection with
1524
+ // the worktree's `<origin>/work/<slug>` vs `<origin>/main` refs.
1525
+ const continueContext = resolveContinueContext({
1526
+ cwd,
1527
+ slug,
1528
+ arbiter: arbiterRemote,
1529
+ branchRef: `${arbiterRemote}/${branch}`,
1530
+ mainRef: `${arbiterRemote}/main`,
1531
+ content: readFileSync(task.path, 'utf8'),
1532
+ env,
1533
+ });
1534
+ // Per-item override layer (mirrors in-place `do`): the task/prd frontmatter
1535
+ // may override the resolved repo `promptGuidance.testFirst` for THIS item.
1536
+ const itemGuidance = resolvePromptGuidanceForItem({
1537
+ cwd,
1538
+ repoResolved: { testFirst: options.promptGuidance?.testFirst === true },
1539
+ taskContent: readFileSync(task.path, 'utf8'),
1540
+ });
1541
+ prompt = buildAgentPrompt(task.slug, task.prd, task.taskPrompt, {
1542
+ cwd,
1543
+ continueContext,
1544
+ promptGuidance: itemGuidance,
1545
+ });
1546
+ }
1547
+ catch (err) {
1548
+ if (err instanceof PromptError) {
1549
+ return await saveRemoteAgentFailure({
1550
+ slug,
1551
+ branch,
1552
+ cwd,
1553
+ arbiterRemote,
1554
+ detail: err.message,
1555
+ env,
1556
+ note,
1557
+ });
1558
+ }
1559
+ throw err;
1560
+ }
1561
+ let agent;
1562
+ try {
1563
+ agent = await runDoAgent(options, cwd, prompt, slug);
1564
+ }
1565
+ catch (err) {
1566
+ const message = err instanceof Error ? err.message : String(err);
1567
+ return await saveRemoteAgentFailure({
1568
+ slug,
1569
+ branch,
1570
+ cwd,
1571
+ arbiterRemote,
1572
+ detail: message,
1573
+ env,
1574
+ note,
1575
+ });
1576
+ }
1577
+ if (!agent.ok) {
1578
+ const detail = agent.detail ?? `the agent failed to build '${slug}'.`;
1579
+ return await saveRemoteAgentFailure({
1580
+ slug,
1581
+ branch,
1582
+ cwd,
1583
+ arbiterRemote,
1584
+ detail,
1585
+ env,
1586
+ note,
1587
+ });
1588
+ }
1589
+ // 6b. HONOR a deliberate STOP (task `agent-stop-signal`) — the SAME detection
1590
+ // in-place `do` runs, shared via `resolveStopReason`/`saveAgentStop`. A
1591
+ // sentinel STOP (verbatim reason) or an empty work-branch diff routes to
1592
+ // needs-attention (surfaced on the arbiter) and SKIPS the gate + Gate-2. The
1593
+ // diff base is the worktree's `origin` (arbiterRemote).
1594
+ const stopReason = await resolveStopReason({
1595
+ output: agent.output,
1596
+ slug,
1597
+ cwd,
1598
+ arbiter: arbiterRemote,
1599
+ env,
1600
+ });
1601
+ if (stopReason !== undefined) {
1602
+ return await saveAgentStop({
1603
+ slug,
1604
+ branch,
1605
+ cwd,
1606
+ arbiter: arbiterRemote,
1607
+ reason: stopReason,
1608
+ env,
1609
+ note,
1610
+ });
1611
+ }
1612
+ // 7. Gate + done-move + commit + rebase + integrate LIKE in-place `do`: the
1613
+ // AUTONOMOUS surfacing (`surfaceArbiter: origin`) so a red gate / rebase
1614
+ // conflict / review block surfaces on the arbiter's main (cross-machine
1615
+ // visible). The success path reuses `complete`'s machinery unchanged.
1616
+ const completed = await performComplete({
1617
+ slug,
1618
+ cwd,
1619
+ arbiter: arbiterRemote,
1620
+ integration: options.integration,
1621
+ // An explicit `--merge` overrides the untrusted-origin build-propose rule (task
1622
+ // `untrusted-origin-forces-build-propose`); unset on the autonomous path so
1623
+ // untrusted-origin reliably forces propose.
1624
+ explicitMerge: options.explicitMerge,
1625
+ // SCOPE: the divergence guard is in-place only. A job worktree is cut fresh off
1626
+ // the bare mirror and never ff's the operator's local main, so the guard does
1627
+ // not apply here — opt out explicitly (the task: do NOT touch do --remote/run).
1628
+ ignoreDivergedMain: true,
1629
+ // `--allow-backlog`: a staged build sources from `tasks/backlog/` (gated on
1630
+ // the flag; otherwise a staged strand still refuses). Default off.
1631
+ allowBacklog: options.allowBacklog === true,
1632
+ prepare: options.prepare,
1633
+ verify: options.verify,
1634
+ freshWorktreeGate: options.freshWorktreeGate,
1635
+ // The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) — see
1636
+ // the in-place site above for the rationale; mirrored here so the no-checkout
1637
+ // `do --remote`/`--isolated` path threads the same resolved cap.
1638
+ mergeRetries: options.mergeRetries,
1639
+ noPR: options.noPR,
1640
+ providerInstance: options.providerInstance,
1641
+ body: agent.output,
1642
+ review: options.review,
1643
+ reviewModel: options.reviewModel,
1644
+ reviewMaxRounds: options.reviewMaxRounds,
1645
+ reviewGate: options.reviewGate,
1646
+ watch: options.watch,
1647
+ watchSink: options.watchSink,
1648
+ sessionsDir: options.sessionsDir,
1649
+ surfaceArbiter: arbiterRemote,
1650
+ color: options.color,
1651
+ note,
1652
+ noteBlock: options.noteBlock,
1653
+ env,
1654
+ // The review AGENT (Gate 2) launches AMBIENT — never the identity env (an
1655
+ // agent must not act as the bot; only the runner's git ops carry identity).
1656
+ agentEnv: options.env,
1657
+ });
1658
+ if (completed.outcome === 'completed' ||
1659
+ completed.outcome === 'already-integrated') {
1660
+ // Stranded-done auto-recover's clean no-op folds into `completed` here too
1661
+ // (see the in-place performDo handler for the rationale): same SHARED
1662
+ // `complete.ts` seam — so `do --remote` (this `performDoRemote`) inherits
1663
+ // the auto-recover without per-caller duplication.
1664
+ return {
1665
+ exitCode: 0,
1666
+ outcome: 'completed',
1667
+ slug,
1668
+ branch,
1669
+ message: completed.message,
1670
+ };
1671
+ }
1672
+ if (completed.outcome === 'review-unparseable') {
1673
+ // Gate 2 RAN but its verdict was UNPARSEABLE (malformed JSON). The core already
1674
+ // routed it work-preservingly (branch pushed + surfaced). FAILURE-CAUSE axis, NOT
1675
+ // a reviewer block: classify the parse-failure phrase → `transient-infra` (re-run
1676
+ // the SAME work), the SAME convergence in-place `do`/`run` apply, so `do --remote`
1677
+ // agrees on the label. The job worktree is RETAINED by the §4 reap (a
1678
+ // not-provably-safe tree); hand the operator the recover-one-liner like the other
1679
+ // retained-tree failures.
1680
+ note(recoverIsolatedOneLiner(slug));
1681
+ const cause = classifyFailureCause(completed.message);
1682
+ return {
1683
+ exitCode: 1,
1684
+ outcome: failureCauseToDoOutcome(cause),
1685
+ slug,
1686
+ branch,
1687
+ message: completed.message,
1688
+ };
1689
+ }
1690
+ if (completed.outcome === 'prepare-failed' ||
1691
+ completed.outcome === 'gate-failed' ||
1692
+ completed.outcome === 'review-blocked' ||
1693
+ completed.outcome === 'rebase-conflict' ||
1694
+ completed.outcome === 'strand-surfaced') {
1695
+ // The job worktree is RETAINED (the §4 reap keeps a not-provably-safe tree).
1696
+ // When the work was committed + done-moved but the integrate failed terminally
1697
+ // (the stale-lease-strand class Part B #97 surfaces), the operator FINISHES the
1698
+ // stranded branch with the recover-already-committed path \u2014 hand them the EXACT
1699
+ // one-liner so they need not reverse-engineer the encoded worktree path.
1700
+ //
1701
+ // `strand-surfaced` is the autonomous-strand parity (the SHARED `complete.ts`
1702
+ // seam already surfaced the source-strand / empty-staged refusal to
1703
+ // needs-attention on the arbiter) — `do --remote` inherits the fix here
1704
+ // without per-caller duplication; mapped to the SAME `needs-attention`
1705
+ // outcome shape the in-place `performDo` uses.
1706
+ note(recoverIsolatedOneLiner(slug));
1707
+ return {
1708
+ exitCode: 1,
1709
+ outcome: 'needs-attention',
1710
+ slug,
1711
+ branch,
1712
+ message: completed.message,
1713
+ };
1714
+ }
1715
+ if (completed.outcome === 'surface-unmoved') {
1716
+ // Strand-surface could not land on the arbiter (CAS contention exhausted) —
1717
+ // HONESTLY still in-progress on the arbiter. Mirror in-place `performDo`'s
1718
+ // `surface-unmoved` mapping so `do --remote` agrees on the same signal.
1719
+ return {
1720
+ exitCode: 1,
1721
+ outcome: 'surface-unmoved',
1722
+ slug,
1723
+ branch,
1724
+ message: completed.message,
1725
+ };
1726
+ }
1727
+ // Reclassify a thrown CORE wiring/config error (swallowed into `usage-error` by
1728
+ // `performComplete`) onto `config-error` — the SAME convergence in-place `do`
1729
+ // applies, so `do --remote` agrees with `do`/`run` on the same error too.
1730
+ if (completed.outcome === 'usage-error') {
1731
+ const cause = classifyFailureCause(completed.message);
1732
+ if (cause === 'config-error') {
1733
+ return {
1734
+ exitCode: 1,
1735
+ outcome: 'config-error',
1736
+ slug,
1737
+ branch,
1738
+ message: completed.message,
1739
+ };
1740
+ }
1741
+ }
1742
+ const outcome = completed.outcome === 'refused' ? 'refused' : 'usage-error';
1743
+ return { exitCode: 1, outcome, slug, branch, message: completed.message };
1744
+ }
1745
+ /**
1746
+ * SAVE the partial work of a FAILED agent in a remote `do` worktree — the same
1747
+ * work-preserving routing in-place `do`'s {@link saveAgentFailure} uses, but
1748
+ * against the worktree's `origin` arbiter remote. The agent's edits + the failure
1749
+ * reason are committed + surfaced on the arbiter's main AND the `work/<slug>`
1750
+ * branch is pushed (the RECOVERABLE durable artifact — the disposable worktree is
1751
+ * NOT the recovery surface). The outcome is the classified failure CAUSE
1752
+ * (`transient-infra` / `config-error` / the generic `agent-failed`), same as the
1753
+ * in-place form.
1754
+ */
1755
+ async function saveRemoteAgentFailure(params) {
1756
+ const { slug, cwd, arbiterRemote, detail, env, note } = params;
1757
+ const branch = params.branch ?? workBranchRef('task', slug);
1758
+ // Same best-effort cause classification as in-place `do`'s `saveAgentFailure`
1759
+ // (shared `classifyFailureCause`), so the remote form labels the SAME error the
1760
+ // SAME way too.
1761
+ const cause = classifyFailureCause(detail);
1762
+ const reason = `${failureCauseLabel(cause)}: ${detail}`;
1763
+ const routed = await ledgerWrite.applyNeedsAttentionTransition({
1764
+ cwd,
1765
+ slug,
1766
+ reason,
1767
+ arbiter: arbiterRemote,
1768
+ env,
1769
+ note,
1770
+ });
1771
+ const report = routed.moved ? routeReport(routed, branch) : undefined;
1772
+ const message = routed.moved
1773
+ ? `Agent run failed building '${slug}' [${cause}] (${detail}); SAVED the ` +
1774
+ `partial work and marked '${slug}' stuck on its lock (${report.fragment}).`
1775
+ : `Agent run failed building '${slug}' [${cause}] (${detail}); could not ` +
1776
+ `mark '${slug}' stuck (${routed.reasonNotMoved ?? 'unknown'}).`;
1777
+ note(message);
1778
+ return {
1779
+ exitCode: 1,
1780
+ outcome: failureCauseToDoOutcome(cause),
1781
+ slug,
1782
+ branch,
1783
+ routedToNeedsAttention: routed.moved,
1784
+ message,
1785
+ };
1786
+ }
1787
+ /**
1788
+ * Prime ONE arbiter head into its remote-tracking ref inside a job worktree
1789
+ * (`+refs/heads/<head>:refs/remotes/<arbiter>/<head>`). A job worktree is cut
1790
+ * from a BARE hub mirror whose `origin` remote has no fetch refspec, so
1791
+ * `<arbiter>/main` / `<arbiter>/work/<slug>` would not otherwise resolve — which
1792
+ * makes the (otherwise unchanged) `performStart` read the slug as absent. This is
1793
+ * the EXACT technique `integrator.rebaseOntoArbiterMain` + `gc.fetchTracking`
1794
+ * already use for the same bare-mirror-worktree reason. Best-effort: an
1795
+ * unreachable arbiter / a missing head leaves the local ref absent, which `start`
1796
+ * then handles (the FRESH branch already exists locally from `createJob`).
1797
+ */
1798
+ async function primeWorktreeTrackingRef(cwd, arbiter, head, env) {
1799
+ // SOFT (no throw): the head may not exist on the arbiter (e.g. a FRESH cut has
1800
+ // no pushed `work/<slug>` yet) — that is the common, expected case, and `start`
1801
+ // handles a missing remote-tracking ref (the local branch `createJob` created
1802
+ // is plain-switched). A genuine fetch error likewise leaves the ref absent →
1803
+ // the safe direction.
1804
+ await runAsync('git', [
1805
+ 'fetch',
1806
+ '--quiet',
1807
+ arbiter,
1808
+ `+refs/heads/${head}:refs/remotes/${arbiter}/${head}`,
1809
+ ], cwd, { env });
1810
+ }
1811
+ /**
1812
+ * Adapt the IN-PLACE {@link DoOptions} build/task driver onto the WORKTREE-
1813
+ * ISOLATED {@link performDoRemote} pipeline (task
1814
+ * `advance-loop-driver-registry-set-job-worktrees`) — the per-mirror job-worktree
1815
+ * `doDriver` the registry-set advance driver threads into the advance tick.
1816
+ *
1817
+ * The advance tick's build/task rung ORCHESTRATES `do` by calling the
1818
+ * {@link AdvanceContext.doDriver} seam with a resolved {@link DoOptions} (whose
1819
+ * `cwd` is the per-mirror in-place checkout). The DEFAULT driver is
1820
+ * {@link performDo} (in-place, the cwd checkout IS the isolation — the human-local
1821
+ * one-shot `advance` + today's `run --advance`). The DAEMON/CI registry-set path
1822
+ * wants the SAME per-job-worktree isolation `run`'s build tick gives `runOneItem`,
1823
+ * so it injects THIS driver instead: it re-routes the orchestration onto
1824
+ * {@link performDoRemote}, which materialises a hub mirror + job worktree off the
1825
+ * mirror's arbiter via the EXISTING `jobWorktreeStrategy` (no second isolation
1826
+ * mechanism), runs the SAME `do` pipeline there, and reaps per the §4 predicate.
1827
+ * The cwd checkout is NEVER touched (the equivalence the task asserts vs plain
1828
+ * `run`).
1829
+ *
1830
+ * The pipeline knobs (verify / integration / review family / agent launch /
1831
+ * identity) ride VERBATIM from the threaded `DoOptions` onto the structurally-
1832
+ * matching {@link DoRemoteOptions} fields; `cwd` is DROPPED (the worktree replaces
1833
+ * it) and `remote` + `workspacesDir` are supplied from this driver's closure (the
1834
+ * mirror's arbiter URL + the agents' execution area). A `prd:` arg flows through
1835
+ * unchanged — `performDoRemote` tasks it against the claim clone with NO build
1836
+ * worktree (the tasking/surface/triage/apply rungs are tree-less ledger moves, the
1837
+ * substrate the task's criterion 4 preserves).
1838
+ */
1839
+ export function jobWorktreeDoDriver(closure) {
1840
+ return (options) => {
1841
+ // Map the in-place DoOptions onto the worktree DoRemoteOptions. `cwd` is
1842
+ // intentionally DROPPED (the worktree is the isolation, not the checkout);
1843
+ // every other pipeline knob rides verbatim onto its structural twin.
1844
+ const remoteOptions = {
1845
+ arg: options.arg,
1846
+ remote: closure.remote,
1847
+ workspacesDir: closure.workspacesDir,
1848
+ arbiter: options.arbiter,
1849
+ identity: options.identity,
1850
+ autoTask: options.autoTask,
1851
+ reviewLoop: options.reviewLoop,
1852
+ taskerLoopMax: options.taskerLoopMax,
1853
+ taskerLoopModel: options.taskerLoopModel,
1854
+ reviewExecutions: options.reviewExecutions,
1855
+ integration: options.integration,
1856
+ // The explicit `--merge` override for the untrusted-origin build-propose rule.
1857
+ explicitMerge: options.explicitMerge,
1858
+ prepare: options.prepare,
1859
+ verify: options.verify,
1860
+ noPR: options.noPR,
1861
+ review: options.review,
1862
+ reviewModel: options.reviewModel,
1863
+ reviewMaxRounds: options.reviewMaxRounds,
1864
+ reviewGate: options.reviewGate,
1865
+ taskReviewGate: options.taskReviewGate,
1866
+ dorfl: options.dorfl,
1867
+ harness: options.harness,
1868
+ agentCmd: options.agentCmd,
1869
+ model: options.model,
1870
+ sessionsDir: options.sessionsDir,
1871
+ watch: options.watch,
1872
+ watchSink: options.watchSink,
1873
+ color: options.color,
1874
+ read: options.read,
1875
+ env: options.env,
1876
+ note: options.note,
1877
+ noteBlock: options.noteBlock,
1878
+ };
1879
+ return performDoRemote(remoteOptions);
1880
+ };
1881
+ }
1882
+ //# sourceMappingURL=do.js.map