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
@@ -0,0 +1,885 @@
1
+ import { existsSync, readdirSync } from 'node:fs';
2
+ import { basename, join } from 'node:path';
3
+ import { workItemPath, WORK_ROOT } from './work-layout.js';
4
+ import { performIntegration, IntegrationNothingStaged, synthesiseProposeTitle, composeProposeBody, PR_TITLE_MAX, } from './integration-core.js';
5
+ import { ledgerWrite } from './ledger-write.js';
6
+ import { releaseItemLock } from './item-lock.js';
7
+ import { workBranchRef, parseWorkBranchRef } from './slug-namespace.js';
8
+ import { runAsync, localMainAheadCount } from './git.js';
9
+ import { hasUncommittedSourceChanges } from './agent-stop.js';
10
+ import { formatProposeNextStep, shouldUseColor } from './output.js';
11
+ import { checkGatePreconditions, detectLockfileOnDisk, } from './gate-readiness.js';
12
+ // Re-export the propose-mode title/body helpers from their new home (the shared
13
+ // integration core) so existing importers (`test/propose-pr-body.test.ts`) keep
14
+ // working unchanged after the gate→integrate band moved out of this file.
15
+ export { synthesiseProposeTitle, composeProposeBody, PR_TITLE_MAX };
16
+ const DEFAULT_ARBITER = 'origin';
17
+ const DEFAULT_INTEGRATION = 'propose';
18
+ /**
19
+ * Resolve the integration mode the human asked for ON THIS invocation from the
20
+ * mutually-exclusive `--merge` / `--propose` flags. Returns the explicit mode,
21
+ * or `undefined` when neither flag was given (per-repo > global > default then
22
+ * decides). Throws when BOTH are given (they are mutually exclusive). This is
23
+ * the TOP of the complete-time precedence chain (flag > per-repo > global >
24
+ * default); the autonomous runner uses no flag and so resolves the same
25
+ * underlying order.
26
+ */
27
+ export function integrationFromFlags(flags) {
28
+ if (flags.merge && flags.propose) {
29
+ throw new Error('--merge and --propose are mutually exclusive; pass at most one.');
30
+ }
31
+ if (flags.merge) {
32
+ return 'merge';
33
+ }
34
+ if (flags.propose) {
35
+ return 'propose';
36
+ }
37
+ return undefined;
38
+ }
39
+ /** Raised for usage/environment errors (exit 1, outcome 'usage-error'). */
40
+ class CompleteUsageError extends Error {
41
+ }
42
+ /** Raised for a deliberate REFUSAL (exit 1, outcome 'refused'). */
43
+ class CompleteRefusal extends Error {
44
+ kind;
45
+ slug;
46
+ constructor(message, kind, slug) {
47
+ super(message);
48
+ this.kind = kind;
49
+ this.slug = slug;
50
+ }
51
+ }
52
+ /**
53
+ * Run the complete ritual. Never throws for the expected gate-failed /
54
+ * rebase-conflict / refused cases — those are returned with exit 1 and a
55
+ * specific outcome. Usage/environment problems also surface as exit 1.
56
+ */
57
+ export async function performComplete(options) {
58
+ const note = options.note ?? (() => { });
59
+ try {
60
+ return await runComplete(options, note);
61
+ }
62
+ catch (err) {
63
+ // AUTONOMOUS-STRAND SURFACE (task
64
+ // `autonomous-integration-refusal-surfaces-not-strands-in-progress`, prd
65
+ // `ledger-integrity` story 7). On the AUTONOMOUS path (`surfaceArbiter` set
66
+ // — the human-vs-autonomous gate the core's other failures use), a SOURCE-
67
+ // STRAND `CompleteRefusal` (the slug-is-stuck `nothing to complete`) and the
68
+ // core's empty-commit `IntegrationNothingStaged` would otherwise return a
69
+ // bare `refused` and silently strand the item in `work/in-progress/` on the
70
+ // arbiter — so the next autonomous tick re-claims it and re-crashes the
71
+ // SAME way forever. Mirror `run`'s posture (an autonomous failure NEVER
72
+ // silently strands the item in in-progress/) by surfacing it to
73
+ // `needs-attention/` via the TREE-LESS arbiter-truth seam
74
+ // (`applyTreelessNeedsAttentionTransition`, the same mechanism `requeue`
75
+ // uses in reverse): there is no working-tree `.md` to `git mv` here (the
76
+ // strand IS that the source file is missing on the branch), so the
77
+ // cwd-bound `applyNeedsAttentionTransition` cannot be used — the tree-less
78
+ // seam resolves the source folder on the arbiter and CAS-publishes the
79
+ // move-only `.md` relocation. The DIVERGED-MAIN refusal is an env/operator
80
+ // condition (NOT a stuck task), so it is excluded — leave it `refused`.
81
+ // When the tree-less surface cannot land (CAS contention exhausted, or no
82
+ // arbiter), report the HONEST still-in-progress signal
83
+ // (`outcome: 'surface-unmoved'`) the gate-fail path's `moved:false` mirrors
84
+ // in `do.ts`/`run.ts` — never a fake success.
85
+ const strandSlug = strandRefusalSlug(err);
86
+ if (strandSlug && options.surfaceArbiter) {
87
+ return await surfaceAutonomousStrand({
88
+ cwd: options.cwd,
89
+ slug: strandSlug,
90
+ reason: err.message,
91
+ arbiter: options.surfaceArbiter,
92
+ env: options.env,
93
+ note,
94
+ });
95
+ }
96
+ if (err instanceof CompleteRefusal ||
97
+ err instanceof IntegrationNothingStaged) {
98
+ // `IntegrationNothingStaged` is the core's empty-commit refusal — the same
99
+ // `refused` outcome the inline band raised before the extraction. The
100
+ // diverged-main `CompleteRefusal` (env/operator condition) also lands here
101
+ // on the autonomous path, deliberately UNCHANGED: it is NOT a stuck task.
102
+ return { exitCode: 1, outcome: 'refused', message: err.message };
103
+ }
104
+ if (err instanceof CompleteUsageError) {
105
+ return { exitCode: 1, outcome: 'usage-error', message: err.message };
106
+ }
107
+ const message = err instanceof Error ? err.message : String(err);
108
+ return { exitCode: 1, outcome: 'usage-error', message };
109
+ }
110
+ }
111
+ /**
112
+ * The SLUG of an autonomous-bounceable strand refusal, or `undefined` when the
113
+ * caught error is NOT a strand class (a `diverged-main` refusal, a usage error,
114
+ * a usual throw). The two strand classes — a `source-strand` {@link
115
+ * CompleteRefusal} and the core's {@link IntegrationNothingStaged} — BOTH leave
116
+ * the slug stuck in `work/in-progress/` on the arbiter (the claim landed and
117
+ * nothing has moved it since), so the autonomous bounce-set is exactly those
118
+ * two. The `diverged-main` refusal is deliberately EXCLUDED: it is an
119
+ * env/operator condition (local main ahead of `<arbiter>/main`), not a stuck
120
+ * task; bouncing it would mis-attribute an env problem to the work.
121
+ */
122
+ function strandRefusalSlug(err) {
123
+ if (err instanceof CompleteRefusal && err.kind === 'source-strand') {
124
+ return err.slug;
125
+ }
126
+ if (err instanceof IntegrationNothingStaged) {
127
+ return err.slug;
128
+ }
129
+ return undefined;
130
+ }
131
+ /**
132
+ * Surface an autonomous-path strand refusal to `needs-attention/` on the
133
+ * arbiter via the TREE-LESS seam ({@link
134
+ * ledgerWrite.applyTreelessNeedsAttentionTransition}). The mechanism is the
135
+ * one `requeue`/`continue` already use in reverse: fetch `<arbiter>/main`,
136
+ * resolve the slug's actual current folder ON THE ARBITER (arbiter-is-truth),
137
+ * build the one-file `work/<src>/<slug>.md → work/needs-attention/<slug>.md`
138
+ * move on a scratch index, and CAS-publish it. It NEVER touches the caller's
139
+ * working tree / HEAD / index — important here because the refusal was raised
140
+ * precisely because the working tree does NOT hold an `.md` to `git mv`. It is
141
+ * idempotent (a re-surface of an already-surfaced slug is a no-op) and reports
142
+ * `moved: false` honestly when it cannot land (CAS contention exhausted, or no
143
+ * arbiter). The HONEST still-in-progress signal is then `outcome:
144
+ * 'surface-unmoved'` — the SAME vocabulary `do`/`run`/`start` expose for the
145
+ * after-commit tree-less surface that did not land; never a fake success.
146
+ */
147
+ async function surfaceAutonomousStrand(params) {
148
+ const { cwd, slug, reason, arbiter, env, note } = params;
149
+ const surfaced = await ledgerWrite.applyTreelessNeedsAttentionTransition({
150
+ cwd,
151
+ slug,
152
+ reason,
153
+ arbiter,
154
+ env,
155
+ note,
156
+ });
157
+ if (surfaced.moved) {
158
+ const message = `'${slug}' refused (${reason}); surfaced to work/needs-attention/ on ` +
159
+ `${arbiter}/main so the next autonomous tick does NOT re-claim it.`;
160
+ note(message);
161
+ return {
162
+ exitCode: 1,
163
+ outcome: 'strand-surfaced',
164
+ routedToNeedsAttention: true,
165
+ message,
166
+ };
167
+ }
168
+ const message = `'${slug}' refused (${reason}); the tree-less surface to needs-attention ` +
169
+ `did NOT reach ${arbiter}/main — the item is HONESTLY still IN-PROGRESS on ` +
170
+ `the arbiter (retry/resolve). ${surfaced.reasonNotMoved ?? ''}`.trim();
171
+ note(message);
172
+ return {
173
+ exitCode: 1,
174
+ outcome: 'surface-unmoved',
175
+ routedToNeedsAttention: false,
176
+ message,
177
+ };
178
+ }
179
+ /**
180
+ * Locate the slug's ledger record at a RENAMED done-position on the branch tree, to
181
+ * survive a SELF-RENAMING FOLDER task (the `folder-taxonomy-reorg-and-rename`
182
+ * migration).
183
+ *
184
+ * The trap: a task whose job is to `git mv` the ledger folders themselves (e.g.
185
+ * `done/ -> tasks/done/`) runs through the runner's INSTALLED (pre-rename) binary,
186
+ * whose compiled-in {@link workItemPath} still resolves `done` to `work/done/`.
187
+ * When the agent has placed its OWN record at the NEW done-position
188
+ * (`work/tasks/done/<slug>.md`) as part of the migration, the binary-known
189
+ * `work/done/<slug>.md` does not exist, and the resolver below would crash with
190
+ * `nothing to complete`, reaping the build.
191
+ *
192
+ * This scan is the layout-agnostic backstop: when the record is at NONE of the
193
+ * binary-known ledger folders, walk `work/` and return true iff `<slug>.md` exists
194
+ * directly inside a folder whose LEAF name is `done` (covering BOTH `work/done/`
195
+ * and a renamed `work/tasks/done/`). It deliberately matches ONLY a `done` leaf:
196
+ * a record left in a renamed POOL (`tasks/ready/`) is NOT a finished task and must
197
+ * still refuse, not be mis-integrated as done. When this fires, the slug is treated
198
+ * as already-done-moved by the agent into its terminal position, so the runner
199
+ * SKIPS its own `git mv` (the existing `source: 'done'` / stranded-done path) and
200
+ * just integrates the agent's tree as-is — no binary-vs-branch folder-name
201
+ * reconciliation is needed.
202
+ */
203
+ function recordAtRenamedDonePosition(cwd, slug) {
204
+ const file = `${slug}.md`;
205
+ const workRoot = join(cwd, WORK_ROOT);
206
+ const found = [];
207
+ const walk = (dir) => {
208
+ let entries;
209
+ try {
210
+ entries = readdirSync(dir, { withFileTypes: true });
211
+ }
212
+ catch {
213
+ return;
214
+ }
215
+ for (const entry of entries) {
216
+ const full = join(dir, entry.name);
217
+ if (entry.isDirectory()) {
218
+ walk(full);
219
+ }
220
+ else if (entry.name === file && basename(dir) === 'done') {
221
+ found.push(full);
222
+ }
223
+ }
224
+ };
225
+ walk(workRoot);
226
+ return found.length > 0;
227
+ }
228
+ async function runComplete(options, note) {
229
+ const cwd = options.cwd;
230
+ const env = options.env;
231
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
232
+ // The integration mode. The shared core integrates a `merge` automatically on a
233
+ // green gate (and an `approve` when review is on) and leaves a `propose` to a
234
+ // human; there is no downgrade, so the mode it resolves always equals this one.
235
+ // The tail below still reads the resolved mode from `core.integration.mode`.
236
+ const requestedMode = options.integration ?? DEFAULT_INTEGRATION;
237
+ if ((await gitSoft(['rev-parse', '--git-dir'], cwd, env)).status !== 0) {
238
+ throw new CompleteUsageError('not inside a git repository');
239
+ }
240
+ if ((await gitSoft(['remote', 'get-url', arbiter], cwd, env)).status !== 0) {
241
+ throw new CompleteUsageError(`no git remote named '${arbiter}' (set one, or pass --arbiter)`);
242
+ }
243
+ // Resolve the slug + work branch. `complete` runs ON the work branch (it
244
+ // rebases + pushes it), and the branch carries the namespaced `work/<type>-
245
+ // <slug>` identity. So PREFER the branch HEAD is on (recovering BOTH the type
246
+ // and the slug from it); only when an explicit slug is given AND HEAD is not on
247
+ // a work branch do we synthesise the task-namespaced branch (`complete` is a
248
+ // task command).
249
+ const headBranch = await currentBranch(cwd, env);
250
+ const headParsed = parseWorkBranchRef(headBranch);
251
+ const slug = options.slug || headParsed?.slug || '';
252
+ if (!slug) {
253
+ throw new CompleteUsageError('missing <slug> and the current branch is not a work/<type>-<slug> ' +
254
+ 'branch. usage: dorfl complete [<slug>] [--skip-verify] ' +
255
+ '[--type t] [--message s] [--arbiter remote]');
256
+ }
257
+ // The branch HEAD is on when it IS a work branch for this slug (so an explicit
258
+ // `prd:`-style recovery still completes the branch it is standing on); else
259
+ // synthesise the task branch.
260
+ const branch = headParsed && headParsed.slug === slug
261
+ ? headBranch
262
+ : workBranchRef('task', slug);
263
+ // We must be ON the work branch — that is where the agent's work lives and
264
+ // what we rebase + push. (Unlike `start`, `complete` mutates the checkout.)
265
+ const head = headBranch;
266
+ if (head !== branch) {
267
+ throw new CompleteUsageError(`not on ${branch} (HEAD is '${head}'). ` +
268
+ `Check out the work branch before completing '${slug}'.`);
269
+ }
270
+ // The task is normally in `tasks/ready/` (claim leaves the body there — task
271
+ // `cutover-claim-body-stays-and-complete-sources-from-backlog`). The legacy
272
+ // runner-owned recovery path used to also probe `work/needs-attention/<slug>.md`,
273
+ // but the per-item-lock cutover (task
274
+ // `cutover-needs-attention-becomes-lock-stuck-recovery-surface`) retired that
275
+ // folder: a stuck item is now the lock `state: stuck`, the body stays in
276
+ // `tasks/ready/` while claimed, and a recovery `complete` sources from there
277
+ // like any other build. The `needs-attention/` folder probe + the `recovering`
278
+ // re-gate branch are GONE (this task,
279
+ // `finish-needs-attention-folder-cutover-remove-legacy-recovery-readers`):
280
+ // nothing writes that folder anymore so the arm could not fire.
281
+ //
282
+ // This LOCAL source resolution is now only a PRE-FLIGHT (which folder the
283
+ // checkout holds) and the content FALLBACK: the integration core's done-move
284
+ // RESOLVES THE ACTUAL SOURCE FOLDER FROM THE ARBITER (arbiter-is-truth) and
285
+ // removes it from THERE, so the move can never disagree with what the arbiter
286
+ // holds even when the local tree diverges (ledger-integrity defect 1). The
287
+ // `source` we pass is the arbiter-content fallback for the degenerate "arbiter
288
+ // holds nothing" case, not the authority. `in-progress/` is RETAINED below for
289
+ // the legacy/bounce surfaces that may still source from it until its folder
290
+ // removal (9c, OUT of this task's scope).
291
+ const backlog = workItemPath(cwd, 'tasks-ready', slug);
292
+ const staged = workItemPath(cwd, 'tasks-backlog', slug);
293
+ const inProgress = workItemPath(cwd, 'in-progress', slug);
294
+ const done = workItemPath(cwd, 'done', slug);
295
+ const onBacklog = existsSync(backlog);
296
+ // `--allow-backlog` drive (prd
297
+ // `do-allow-backlog-drive-staged-tasks-without-promotion`): a staged task driven
298
+ // in place RESTS in `tasks/backlog/` (claim never moved it). Detect it as a build
299
+ // source so the done-move goes `tasks/backlog/ → tasks/done/` DIRECTLY (the
300
+ // human's explicit drive IS the promotion; it never bounces through the pool).
301
+ // LOWER priority than `tasks/ready/` so a same-slug pool copy wins the resolution.
302
+ // GATED on the explicit flag: WITHOUT it, a body in staging is a corrupt/wip
303
+ // strand `complete` must REFUSE (the honest "not finished, not done" position),
304
+ // NOT silently integrate — only the deliberate drive promotes from staging.
305
+ const onPreBacklog = options.allowBacklog === true && existsSync(staged);
306
+ const onInProgress = existsSync(inProgress);
307
+ // SELF-RENAMING FOLDER task backstop: the binary-known `work/done/<slug>.md`,
308
+ // OR — when none of the binary-known ledger folders hold the record — the slug
309
+ // at a RENAMED done-position the migration agent placed it in (e.g.
310
+ // `work/tasks/done/<slug>.md`). See {@link recordAtRenamedDonePosition}. The
311
+ // scan runs ONLY in the all-binary-folders-empty case, so a normal task's
312
+ // resolution is byte-for-byte unchanged.
313
+ const onDone = existsSync(done) ||
314
+ (!onBacklog &&
315
+ !onPreBacklog &&
316
+ !onInProgress &&
317
+ recordAtRenamedDonePosition(cwd, slug));
318
+ // STRANDED-DONE AUTO-RECOVER (prd `ledger-integrity` story 6, the autonomous
319
+ // half of `finish-already-committed-branch`). When neither in-progress/ nor
320
+ // needs-attention/ holds the slug on the BRANCH tree BUT done/ does, the work
321
+ // branch was already built + done-moved + committed by a prior run that never
322
+ // landed on the arbiter (a terminal push failed, or the PR never merged). The
323
+ // autonomous `do`/`advance`/plain-`complete` path used to refuse this with
324
+ // `nothing to complete`; instead route into the SHARED recover tail (the SAME
325
+ // `committedRecovery: true` path `complete --isolated` drives), reusing the
326
+ // existing capability. Detection here is the FRONT-GATE only — folder shape;
327
+ // `recoverAlreadyCommitted` owns the unspoofable tip-vs-arbiter `isAncestor`
328
+ // decision (one reachability check, not two), so a tip ALREADY on
329
+ // `<arbiter>/main` returns `already-integrated` (clean no-op, NEVER a
330
+ // re-push/double-integrate). Mutually exclusive with the build-path source +
331
+ // `recovering` flags — the core ignores them when `committedRecovery` is set.
332
+ const folderShapeStranded = !onBacklog && !onPreBacklog && !onInProgress && onDone;
333
+ // DIRTY-CONTINUE GATE (task `recover-autodetect-gated-on-nothing-to-commit`).
334
+ // The folder-shape stranded-done auto-detect is necessary but NOT sufficient on
335
+ // a CONTINUE: a requeued task whose prior attempt already done-moved the slug
336
+ // into `done/` on the kept branch looks IDENTICAL by folder shape to a genuine
337
+ // finished strand, even when THIS run's agent produced NEW uncommitted edits.
338
+ // The recover path skips the build/commit/done-move steps and only rebases the
339
+ // ALREADY-committed kept tip, so firing the recover on a dirty continue would
340
+ // SILENTLY DISCARD the new work. Disambiguate on the WORKING-TREE-DIRTY check
341
+ // (the porcelain half of `isWorkBranchDiffEmpty`, extracted as
342
+ // `hasUncommittedSourceChanges`). NOT the core's `nothingStaged`: that is
343
+ // INDEX-only and reads empty BEFORE the core's later `git add -A`, so it would
344
+ // miss the agent's UNSTAGED edits. NOT the FULL `isWorkBranchDiffEmpty`: its
345
+ // commits-ahead half is true for a GENUINE STRAND too (the kept tip carries
346
+ // source commits ahead of main), so it would WRONGLY BLOCK the legitimate
347
+ // recover and break the task-1 / finished-strand behaviour.
348
+ const dirtyContinue = folderShapeStranded && (await hasUncommittedSourceChanges({ cwd, env }));
349
+ const committedRecovery = folderShapeStranded && !dirtyContinue;
350
+ // CONTINUE-BUILD (task `complete-builds-on-already-done-moved-continue`,
351
+ // scope option A — the explicit `source: 'done'` contract). On a DIRTY
352
+ // continue whose kept branch already holds the slug in `work/done/`, route
353
+ // through the build path's NEW continue-build state: the integration core
354
+ // SKIPS the step-2 `git mv` (the slug is already in done/) and is exempted
355
+ // from the originTrust read + the divergent-done-move reconcile (there is no
356
+ // first-time move on this commit), but still runs prepare → gate → `git add
357
+ // -A` → commit → rebase → integrate on the NEW work. Replaces the blocker
358
+ // task's needs-attention BOUNCE (the dirty continue now AUTO-LANDS instead
359
+ // of surfacing). See `docs/adr/continue-build-already-done-moved.md`.
360
+ // Default branch (post `needs-attention/`-folder cutover, this task): when
361
+ // NONE of the recognised source folders holds the slug, the build cannot
362
+ // proceed — refuse via the `existsSync(sourcePath)` check below (the explicit
363
+ // "nothing to complete" CompleteRefusal). We default `source` to `'tasks-ready'`
364
+ // so `sourcePath` resolves to the canonical pool path the refusal message
365
+ // names; the refusal fires because that file does not exist on disk.
366
+ const source = dirtyContinue
367
+ ? 'done'
368
+ : onBacklog
369
+ ? 'tasks-ready'
370
+ : onInProgress
371
+ ? 'in-progress'
372
+ : onPreBacklog
373
+ ? 'tasks-backlog'
374
+ : 'tasks-ready';
375
+ const sourcePath = source === 'tasks-ready'
376
+ ? backlog
377
+ : source === 'tasks-backlog'
378
+ ? staged
379
+ : source === 'in-progress'
380
+ ? inProgress
381
+ : done;
382
+ if (dirtyContinue) {
383
+ // Announce LOUDLY (parallel to the `committedRecovery` recovery note above):
384
+ // the autonomous integrate path took the CONTINUE-BUILD branch, not the
385
+ // clean-strand recover and not a refusal — the CI/job log must record that
386
+ // the new work was built + integrated on top of the kept already-done-moved
387
+ // tip (no second `git mv`).
388
+ note(`>> continue-build on '${slug}': the task is already in work/done/ on the ` +
389
+ 'kept branch (a prior attempt done-moved it); building + integrating the ' +
390
+ 'new uncommitted work on top of the kept tip (no second git mv).');
391
+ }
392
+ // The presence check guards the genuine-strand case (no record anywhere). It is
393
+ // DELIBERATELY skipped for `source === 'done'` (the continue-build / stranded-done
394
+ // state): there the record is ALREADY in its terminal done-position — presence
395
+ // was established by `folderShapeStranded`/`onDone`, which is LAYOUT-AWARE (it
396
+ // accepts a renamed `work/tasks/done/<slug>.md` via `recordAtRenamedDonePosition`).
397
+ // The binary's `sourcePath` (= `done` = `work/done/<slug>.md`) would NOT exist for
398
+ // a self-renaming-folder task, so re-checking it here would wrongly refuse a
399
+ // build the done-detection already accepted. `source: 'done'` skips the `git mv`
400
+ // anyway, so there is nothing this check needs to protect for that branch.
401
+ if (!committedRecovery && source !== 'done' && !existsSync(sourcePath)) {
402
+ throw new CompleteRefusal(`work/tasks/ready/${slug}.md not found — ` +
403
+ 'nothing to complete (already done, or wrong slug?).', 'source-strand', slug);
404
+ }
405
+ // `source: 'done'` is the CONTINUE-BUILD state — NOT a recover (the agent
406
+ // produced new work this run) and NOT the clean-strand recover
407
+ // (`committedRecovery` is mutually exclusive with `dirtyContinue` by
408
+ // construction above). It falls through to `performIntegration` like a
409
+ // normal build, where the core skips the step-2 `git mv` for this source.
410
+ // Post `needs-attention/`-folder cutover (task
411
+ // `finish-needs-attention-folder-cutover-remove-legacy-recovery-readers`):
412
+ // `source === 'needs-attention'` is no longer reachable here (the folder is
413
+ // retired; a stuck item is the lock `state: stuck` and the body rests in
414
+ // `tasks/ready/`). A recovery `complete` of a stuck item now sources from
415
+ // `tasks/ready/` like a normal build, so `recovering` is always `false`. The
416
+ // integration core still accepts the flag (vestigial, ignored) — every caller
417
+ // passes `false` and the re-gate branches that read it are dead.
418
+ const recovering = false;
419
+ if (committedRecovery) {
420
+ // Announce LOUDLY: a stranded already-complete branch signals an EARLIER
421
+ // un-merged PR — the CI/job log must record that the autonomous path took
422
+ // the recovery branch, not a normal completion. The `already-integrated`
423
+ // no-op gets its own clear note from the core itself.
424
+ note(`>> recovered a stranded already-complete branch for '${slug}' — ` +
425
+ 'integrating the kept commit (no rebuild). This signals an earlier ' +
426
+ 'un-merged PR.');
427
+ }
428
+ // Pre-flight DIVERGENCE GUARD (merge mode only) — the SAME class of refusal `do`
429
+ // raises up front: a local `main` AHEAD of `<arbiter>/main` (unpushed commits)
430
+ // cannot be fast-forwarded by the merge-back, so refuse BEFORE the gate so no
431
+ // work is wasted. Only merge mode ff's local `main`; propose only switches to it
432
+ // (no ff), so the guard is irrelevant there. `--ignore-diverged-main` overrides
433
+ // (mirrors `--ignore-not-ready`); when overridden, the now-NON-FATAL
434
+ // `syncLocalMain` handles the persisting divergence honestly at integrate-time.
435
+ if (requestedMode === 'merge' && options.ignoreDivergedMain !== true) {
436
+ await gitHard(['fetch', '--quiet', arbiter], cwd, env);
437
+ const ahead = await localMainAheadCount(cwd, arbiter, env);
438
+ if (ahead > 0) {
439
+ throw new CompleteRefusal(`local main is ahead of ${arbiter}/main by ${ahead} commit` +
440
+ `${ahead === 1 ? '' : 's'} (unpushed); a merge completion ff's local ` +
441
+ "main, which can't fast-forward against a diverged main — push or " +
442
+ 'reconcile main first (or re-run with --ignore-diverged-main to ' +
443
+ 'proceed anyway).', 'diverged-main', slug);
444
+ }
445
+ }
446
+ // STATIC fresh-worktree-gate readiness guard — the human/recovery mirror of
447
+ // `performDo`'s step 3d (task
448
+ // `do-fails-fast-when-acceptance-gate-statically-unrunnable`). When the fresh-
449
+ // worktree gate is ON for THIS invocation AND `prepare` resolves to no commands
450
+ // AND a lockfile is present, the throwaway worktree the gate runs in will have
451
+ // no installed deps, so the gate cannot run. Surface the precise error BEFORE
452
+ // `performIntegration` does any work (skip-verify bypasses the gate entirely
453
+ // and so bypasses this guard too — if there is no gate to run, there is no
454
+ // precondition to check). Gated on `freshWorktreeGate === true` for THIS
455
+ // invocation: when OFF, the gate runs in the CURRENT checkout (which carries
456
+ // its deps), so the throwaway-worktree reasoning does not apply.
457
+ if (options.skipVerify !== true) {
458
+ const guard = checkGatePreconditions({
459
+ freshWorktreeGate: options.freshWorktreeGate,
460
+ prepare: options.prepare,
461
+ lockfile: detectLockfileOnDisk(cwd),
462
+ });
463
+ if (guard !== undefined) {
464
+ throw new CompleteRefusal(guard.message, 'gate-unrunnable');
465
+ }
466
+ }
467
+ // CORE: run the SHARED gate→integrate band (verify → review → effective-mode
468
+ // decision → done-move → commit → rebase → integrate → needs-attention routing).
469
+ // It returns DATA (the routing + the effective-mode decision already happened
470
+ // inside it); the TAIL below does only `complete`'s caller-specific post-step
471
+ // (switch-to-main / ff / delete-branch / `--no-switch` / the propose next-step
472
+ // block). The human-vs-autonomous difference rides on `surfaceArbiter` (DATA).
473
+ // `IntegrationNothingStaged` is the one refusal the core raises (empty commit);
474
+ // `performComplete`'s try/catch maps it to `refused`, unchanged.
475
+ const core = await performIntegration({
476
+ cwd,
477
+ arbiter,
478
+ slug,
479
+ // The namespaced work branch HEAD is on (resolved above) — pass it through
480
+ // so the integrate core pushes the EXACT branch, not a re-synthesised default.
481
+ branch,
482
+ source,
483
+ recovering,
484
+ // Route stranded-done into the shared recover tail (front-gate detection;
485
+ // the core does the unspoofable tip-vs-arbiter ancestry check).
486
+ committedRecovery,
487
+ prepare: options.prepare,
488
+ verify: options.verify,
489
+ freshWorktreeGate: options.freshWorktreeGate,
490
+ // The cross-job merge-serialiser CAS-retry cap (config `mergeRetries`) — the
491
+ // git-alone FLOOR of the cross-job land-queue, resolved through the gate-family
492
+ // precedence chain (prd `land-time-reverify-and-parallel-merge-ceiling` Story
493
+ // 5). Threaded so a wide-matrix CI's raised cap actually reaches the merge loop.
494
+ mergeRetries: options.mergeRetries,
495
+ skipVerify: options.skipVerify,
496
+ // The untrusted-origin build-propose rule's override (task
497
+ // `untrusted-origin-forces-build-propose`): an explicit `--merge` lets the
498
+ // operator land an untrusted-origin task on main; the autonomous path leaves
499
+ // it unset so untrusted-origin reliably forces propose.
500
+ explicitMerge: options.explicitMerge,
501
+ review: options.review,
502
+ reviewGate: options.reviewGate,
503
+ reviewModel: options.reviewModel,
504
+ reviewMaxRounds: options.reviewMaxRounds,
505
+ mode: requestedMode,
506
+ noPR: options.noPR,
507
+ providerInstance: options.providerInstance,
508
+ openPr: options.openPr,
509
+ body: options.body,
510
+ type: options.type,
511
+ message: options.message,
512
+ surfaceArbiter: options.surfaceArbiter,
513
+ watch: options.watch,
514
+ watchSink: options.watchSink,
515
+ color: options.color,
516
+ sessionsDir: options.sessionsDir,
517
+ env,
518
+ agentEnv: options.agentEnv,
519
+ note,
520
+ });
521
+ // The FAILURE outcomes map 1:1 onto `complete`'s — the core already note()'d
522
+ // the reason and did any routing; the tail never runs for them.
523
+ if (core.outcome === 'prepare-failed') {
524
+ return {
525
+ exitCode: 1,
526
+ outcome: 'prepare-failed',
527
+ routedToNeedsAttention: core.routedToNeedsAttention,
528
+ branch: core.branch,
529
+ message: core.reason ?? '',
530
+ };
531
+ }
532
+ if (core.outcome === 'gate-failed') {
533
+ return {
534
+ exitCode: 1,
535
+ outcome: 'gate-failed',
536
+ routedToNeedsAttention: core.routedToNeedsAttention,
537
+ branch: core.branch,
538
+ message: core.reason ?? '',
539
+ };
540
+ }
541
+ if (core.outcome === 'review-blocked') {
542
+ return {
543
+ exitCode: 1,
544
+ outcome: 'review-blocked',
545
+ routedToNeedsAttention: core.routedToNeedsAttention,
546
+ branch: core.branch,
547
+ message: core.reason ?? '',
548
+ };
549
+ }
550
+ if (core.outcome === 'review-unparseable') {
551
+ // Gate 2 RAN but its verdict was UNPARSEABLE (malformed JSON). The core already
552
+ // routed it work-preservingly via `applyNeedsAttentionTransition` (branch pushed +
553
+ // surfaced on the autonomous path). Map it 1:1 like the other routed failures; the
554
+ // `do`/`run` tail re-labels its cause `transient-infra` (NOT a reviewer block) off
555
+ // the parse-failure phrase in `core.reason`.
556
+ return {
557
+ exitCode: 1,
558
+ outcome: 'review-unparseable',
559
+ routedToNeedsAttention: core.routedToNeedsAttention,
560
+ branch: core.branch,
561
+ message: core.reason ?? '',
562
+ };
563
+ }
564
+ if (core.outcome === 'rebase-conflict') {
565
+ return {
566
+ exitCode: 1,
567
+ outcome: 'rebase-conflict',
568
+ routedToNeedsAttention: core.routedToNeedsAttention,
569
+ branch: core.branch,
570
+ commitMessage: core.commitMessage,
571
+ message: core.reason ?? '',
572
+ };
573
+ }
574
+ if (core.outcome === 'already-integrated') {
575
+ // Stranded-done auto-recover: the kept tip is ALREADY on `<arbiter>/main`
576
+ // (the PR merged out-of-band before the re-run). A clean, successful no-op
577
+ // — NEVER a re-push / second PR. The core already emitted its honest note;
578
+ // the caller's tail (switch-to-main / ff / delete-branch) is irrelevant
579
+ // because the work is already integrated and the branch may or may not
580
+ // still exist locally.
581
+ //
582
+ // CROSS-SUBSTRATE RELEASE (prd `ledger-status-per-item-lock-refs` US #9/#10;
583
+ // task `complete-lock-then-durable-main-move-crash-safe`): the durable
584
+ // `main` record is already terminal (the kept tip is provably on
585
+ // `<arbiter>/main`), so the per-item lock claim took is now stale: release it
586
+ // SECOND (the move already landed FIRST). `durablyOnMain: true` unconditionally
587
+ // here (unlike the propose build path, this branch is reached ONLY when the
588
+ // work is already integrated on `main`). Best-effort + idempotent (`not-held`
589
+ // is fine when a prior reconcile/release cleared it).
590
+ await releaseClaimLockAfterDurableMove(slug, cwd, arbiter, env, true, note);
591
+ return {
592
+ exitCode: 0,
593
+ outcome: 'already-integrated',
594
+ branch: core.branch,
595
+ message: core.reason ?? '',
596
+ };
597
+ }
598
+ if (core.outcome === 'invariant-violation') {
599
+ // The one-slug-one-folder guard FAILED LOUD: the arbiter already holds the
600
+ // slug in >1 status folder (a corrupt ledger). Nothing was committed/moved;
601
+ // refuse rather than publish corruption (the human resolves the duplicate —
602
+ // `scan`/`gc` surfaces it — then re-runs). Exit 1, never routed.
603
+ return {
604
+ exitCode: 1,
605
+ outcome: 'invariant-violation',
606
+ routedToNeedsAttention: false,
607
+ branch: core.branch,
608
+ message: core.reason ?? '',
609
+ };
610
+ }
611
+ // SUCCESS: the core integrated. `result` is its integration result; `mode` is
612
+ // the mode the core resolved, read from the result (it always equals the
613
+ // requested mode now that there is no downgrade). The tail switches/ffs per the
614
+ // mode that integrated.
615
+ const result = core.integration;
616
+ const commitMessage = core.commitMessage;
617
+ const mode = result.mode;
618
+ // CROSS-SUBSTRATE RELEASE, gated on the work being DURABLY ON `main` (prd
619
+ // `ledger-status-per-item-lock-refs` US #9/#10; ADR
620
+ // `ledger-status-on-per-item-lock-refs`; the trail's Amendment 6; task
621
+ // `complete-lock-then-durable-main-move-crash-safe`). ORDER MATTERS: the
622
+ // per-item lock that `claim` acquired (`action: implement`, keyed
623
+ // `task:<slug>`) is released SECOND, AFTER the durable `main` move landed FIRST
624
+ // inside `performIntegration`.
625
+ //
626
+ // CRITICAL (task `propose-keep-lock-until-pr-merge`): in PROPOSE mode the
627
+ // done-move did NOT land on `main` here. The `git mv tasks/ready -> done` is
628
+ // committed on the WORK BRANCH and pushed as the PR; `<arbiter>/main` still
629
+ // shows the body in `tasks/ready/` until a human MERGES the PR. So releasing
630
+ // the lock now would leave the task BOTH unlocked AND in `tasks/ready/` on
631
+ // `main`, i.e. fully eligible again, for the whole review window: the next
632
+ // advance tick re-claims it (lock absent), rebuilds, the PR meanwhile merges,
633
+ // the diff is empty, and the item is mis-marked `stuck`. So we release ONLY
634
+ // when the work is provably on `main`: merge mode (`mergedToMain`) or an
635
+ // already-landed propose (`alreadyLanded`, the benign already-on-main race
636
+ // tail). A plain open propose PR KEEPS the lock held (the open PR IS the
637
+ // in-flight state); it is released when the work lands on `main`, reconciled by
638
+ // `reconcileItemLockAgainstMain` when the PR merges out-of-band.
639
+ //
640
+ // A crash between the move and the release leaves a `done`-on-`main` item with
641
+ // a still-held lock; `reconcileItemLockAgainstMain` recovers it (the `main`
642
+ // record is authoritative over the stale lock). The release is best-effort +
643
+ // idempotent (`not-held` when the body predates the lock or a reconcile already
644
+ // cleared it).
645
+ const durablyOnMain = result.mergedToMain === true || result.alreadyLanded === true;
646
+ await releaseClaimLockAfterDurableMove(slug, cwd, arbiter, env, durablyOnMain, note);
647
+ // Land back on `main` by default in BOTH modes (the move differs per mode),
648
+ // then delete the local work branch iff its work is provably on the arbiter.
649
+ // `--no-switch` keeps the human on the work branch AND keeps the branch.
650
+ let switchedTo = branch;
651
+ let deletedLocalBranch = false;
652
+ // Merge mode only: whether the local `main` ff'd (the courtesy after the
653
+ // authoritative push). `true` by default; flipped to `false` by `syncLocalMain`
654
+ // on a NON-FATAL diverged-`main` skip. Undefined in propose / `--no-switch`.
655
+ let localMainSynced;
656
+ if (!options.noSwitch) {
657
+ if (mode === 'merge') {
658
+ // merge: the work landed on <arbiter>/main, so switch to main AND ff it.
659
+ // The ff is a COURTESY — if local `main` diverged it cannot apply, which is
660
+ // NON-FATAL (the push already defined success); `syncLocalMain` reports it.
661
+ localMainSynced = await syncLocalMain(cwd, arbiter, env, note);
662
+ }
663
+ else {
664
+ // propose: the work is on a pushed branch awaiting review, NOT on main,
665
+ // so JUST switch to main — do NOT ff (arbiter main has not moved).
666
+ await gitHard(['switch', '--quiet', 'main'], cwd, env);
667
+ }
668
+ switchedTo = 'main';
669
+ // Delete the LOCAL work branch when provably on the arbiter (same predicate
670
+ // as worktree deletion, ADR §4, mode-agnostic). NEVER delete the remote.
671
+ deletedLocalBranch = await deleteLocalBranchIfProvablyOnArbiter(cwd, arbiter, branch, env);
672
+ }
673
+ if (mode === 'merge') {
674
+ // When the local ff was SKIPPED (diverged main, non-fatal), say so honestly —
675
+ // the work IS on the arbiter; only the local courtesy ff was left undone.
676
+ const localState = localMainSynced === false
677
+ ? 'local main left diverged (run `git rebase origin/main` to sync)'
678
+ : 'local main updated';
679
+ const landed = options.noSwitch
680
+ ? `merged to ${arbiter}/main; left on ${branch} (--no-switch).`
681
+ : `merged to ${arbiter}/main; ${localState}` +
682
+ `${deletedLocalBranch ? ` and ${branch} deleted` : ''}.`;
683
+ const message = `Completed '${slug}': ${landed}`;
684
+ note(message);
685
+ return {
686
+ exitCode: 0,
687
+ outcome: 'completed',
688
+ branch,
689
+ commitMessage,
690
+ mergedToMain: result.mergedToMain,
691
+ localMainSynced,
692
+ switchedTo,
693
+ deletedLocalBranch,
694
+ message,
695
+ };
696
+ }
697
+ // propose: the branch is pushed; report the next step. The next step is the
698
+ // ONE thing the human must act on, so it is emitted as a visually-distinct
699
+ // block (blank lines + heading + TTY-aware color) ON TOP of the plain summary
700
+ // note below — cosmetic only; the structured result is unchanged.
701
+ const color = options.color ?? shouldUseColor(process.stdout);
702
+ const noteBlock = options.noteBlock ?? note;
703
+ noteBlock(formatProposeNextStep({
704
+ branch,
705
+ arbiter,
706
+ requestOpened: result.requestOpened,
707
+ color,
708
+ }));
709
+ const next = result.requestOpened
710
+ ? result.url
711
+ ? `pushed ${branch} and opened a review (${result.url})`
712
+ : `pushed ${branch} and opened a review`
713
+ : `pushed ${branch} to ${arbiter}. ` + 'Open a PR/MR to land it on main';
714
+ const tail = options.noSwitch
715
+ ? `; left on ${branch} (--no-switch).`
716
+ : deletedLocalBranch
717
+ ? `; switched to main and deleted ${branch}.`
718
+ : '; switched to main.';
719
+ const message = `Completed '${slug}': ${next}${tail}`;
720
+ note(message);
721
+ return {
722
+ exitCode: 0,
723
+ outcome: 'completed',
724
+ branch,
725
+ commitMessage,
726
+ mergedToMain: false,
727
+ switchedTo,
728
+ deletedLocalBranch,
729
+ prUrl: result.url,
730
+ message,
731
+ };
732
+ }
733
+ /**
734
+ * Release the per-item lock `claim` ALSO acquired (`action: implement`, keyed
735
+ * `task:<slug>`) AFTER the durable `main` move has landed — the SECOND, lock-
736
+ * release half of complete's cross-substrate ordering (prd
737
+ * `ledger-status-per-item-lock-refs` US #9/#10; task
738
+ * `complete-lock-then-durable-main-move-crash-safe`).
739
+ *
740
+ * Called ONLY on the SUCCESS paths (`completed` / `already-integrated`). The lock
741
+ * is released ONLY when the work is DURABLY ON `<arbiter>/main` (`durablyOnMain`):
742
+ * merge mode (`mergedToMain`), an already-landed propose race tail
743
+ * (`alreadyLanded`), or the `already-integrated` recovery (provably on main). In
744
+ * those cases the lock is no longer holding anything in flight and is cleanly
745
+ * released SECOND.
746
+ *
747
+ * When `durablyOnMain` is FALSE (a plain open propose PR: the done-move is on the
748
+ * PR branch, NOT on `main`), the lock is KEPT HELD: the open PR IS the in-flight
749
+ * state, and the held lock is what keeps the still-`tasks/ready/`-on-`main` task
750
+ * out of the eligible pool until the PR merges (task
751
+ * `propose-keep-lock-until-pr-merge`). Releasing here would let the next advance
752
+ * tick re-claim the in-flight task and mis-mark it `stuck` on an empty diff. The
753
+ * lock is released when the work lands on `main`, reconciled by
754
+ * `reconcileItemLockAgainstMain` when the PR merges out-of-band.
755
+ *
756
+ * The FAILURE paths (gate-failed / review-blocked / rebase-conflict /
757
+ * prepare-failed) deliberately do NOT call this: those route to needs-attention
758
+ * (the lock is marked `stuck`, not released) and the item is still in flight.
759
+ *
760
+ * Best-effort + idempotent (a `not-held` is fine: the body may predate the lock,
761
+ * or a crash-recovery `reconcileItemLockAgainstMain` may already have cleared it).
762
+ * A release fault never fails an already-landed completion: the durable `main`
763
+ * record is authoritative, and a stranded lock is exactly what recovery clears.
764
+ */
765
+ async function releaseClaimLockAfterDurableMove(slug, cwd, arbiter, env, durablyOnMain, note) {
766
+ if (!durablyOnMain) {
767
+ // PROPOSE, PR open: the done-move is on the PR branch, not on `main`. KEEP the
768
+ // lock held so the in-flight task is not re-claimed during the review window.
769
+ note(`'${slug}': keeping the per-item lock HELD (propose PR open; the work is ` +
770
+ 'not yet on main). It is released when the PR merges (reconciled against ' +
771
+ 'main).');
772
+ return;
773
+ }
774
+ try {
775
+ await releaseItemLock({ item: `task:${slug}`, cwd, arbiter, env });
776
+ }
777
+ catch {
778
+ // Best-effort: a release fault leaves a stale lock that recovery
779
+ // (`reconcileItemLockAgainstMain`) clears: the durable move already defined
780
+ // success. Never fail an already-landed completion on the lock release.
781
+ }
782
+ }
783
+ /**
784
+ * Sync the local `main` to the just-pushed `<arbiter>/main`: switch to main and
785
+ * fast-forward it. We fetch the arbiter's main (we just pushed our rebased work
786
+ * there) then `merge --ff-only` so the user lands on an up-to-date local main
787
+ * without merge noise.
788
+ *
789
+ * The ff is a COURTESY, NOT the safety-bearing step — the authoritative push to
790
+ * `<arbiter>/main` already defined `complete`'s success. So the ff is NON-FATAL:
791
+ * if local `main` has DIVERGED (unpushed commits the arbiter lacks) the ff cannot
792
+ * apply; rather than throw (which would make `complete` exit non-zero even though
793
+ * the merge ALREADY LANDED on the arbiter), we print a clear "rebase to sync"
794
+ * message and return `false` (the caller records `localMainSynced: false` and
795
+ * keeps `outcome: completed` / exit 0). Returns `true` on a normal ff.
796
+ *
797
+ * Only the diverged / ff-cannot-apply case is softened — the `fetch` and `switch`
798
+ * stay `gitHard` (a genuinely different failure is NOT masked, per the task).
799
+ * Exported for direct testing of the softened-vs-fatal boundary in isolation.
800
+ */
801
+ export async function syncLocalMain(cwd, arbiter, env, note) {
802
+ await gitHard(['fetch', '--quiet', arbiter], cwd, env);
803
+ await gitHard(['switch', '--quiet', 'main'], cwd, env);
804
+ // SOFT: a non-zero here is (almost always) "not possible to fast-forward" —
805
+ // local `main` diverged. That is the one case we make non-fatal.
806
+ const ff = await gitSoft(['merge', '--ff-only', '--quiet', `${arbiter}/main`], cwd, env);
807
+ if (ff.status === 0) {
808
+ return true;
809
+ }
810
+ note(`work landed on ${arbiter}/main; your local main couldn't fast-forward (it ` +
811
+ 'has diverged) — run `git rebase origin/main` to sync.');
812
+ return false;
813
+ }
814
+ /**
815
+ * Delete the LOCAL `work/<slug>` branch iff its work is PROVABLY on the arbiter
816
+ * — the SAME predicate as worktree deletion (ADR §4), mode-agnostic. Must be
817
+ * called AFTER we have switched off the branch (you cannot delete the branch you
818
+ * are on). Returns whether the local branch was deleted.
819
+ *
820
+ * Provably-on-arbiter ⇔ EITHER:
821
+ * - merged: the branch tip is an ancestor of `<arbiter>/main`, OR
822
+ * - pushed & up-to-date: `<arbiter>/<branch>` exists AND its tip == the local
823
+ * branch tip (so a later un-pushed amend is NEVER lost — we verify the
824
+ * remote tip equals the local tip, not merely that "a branch was pushed").
825
+ *
826
+ * Otherwise the branch is KEPT (unmerged / unpushed / diverged = not safe). The
827
+ * REMOTE branch is NEVER touched (a propose PR is built from it). A `git fetch`
828
+ * refreshes the remote-tracking refs so reachability is read against the LIVE
829
+ * arbiter; an unreachable arbiter simply reads as not-provable → keep (safe).
830
+ */
831
+ async function deleteLocalBranchIfProvablyOnArbiter(cwd, arbiter, branch, env) {
832
+ if (!(await isLocalBranchProvablyOnArbiter(cwd, arbiter, branch, env))) {
833
+ return false; // not provably on the arbiter → KEEP the local branch
834
+ }
835
+ // Provably on the arbiter → delete ONLY the local branch (force-delete: the
836
+ // `-d` safety check uses the upstream/HEAD, which we have already proven via
837
+ // the predicate above; `-D` avoids a false "not fully merged" in propose mode).
838
+ await gitHard(['branch', '-D', branch], cwd, env);
839
+ return true;
840
+ }
841
+ /**
842
+ * The provably-safe predicate (ADR §4), mode-agnostic, applied to a LOCAL
843
+ * `branch` in `cwd`: true iff its tip is an ancestor of `<arbiter>/main` (merged)
844
+ * OR `<arbiter>/<branch>` exists with its tip == the local tip (pushed &
845
+ * up-to-date). False when the local branch is absent, or its work is unmerged /
846
+ * unpushed / a diverged un-pushed amend (remote tip != local tip). A `git fetch`
847
+ * refreshes the remote-tracking refs; an unreachable arbiter reads as not-
848
+ * provable → false (the safe direction). Exported for direct testing of the
849
+ * kept-vs-deleted decision in isolation.
850
+ */
851
+ export async function isLocalBranchProvablyOnArbiter(cwd, arbiter, branch, env) {
852
+ const localTip = (await gitSoft(['rev-parse', '--verify', '--quiet', branch], cwd, env)).stdout.trim();
853
+ if (localTip === '') {
854
+ return false; // no such local branch
855
+ }
856
+ // Refresh remote-tracking refs against the live arbiter (best-effort).
857
+ await gitSoft(['fetch', '--quiet', arbiter], cwd, env);
858
+ // Merged: tip is an ancestor of <arbiter>/main.
859
+ const merged = (await gitSoft(['merge-base', '--is-ancestor', localTip, `${arbiter}/main`], cwd, env)).status === 0;
860
+ if (merged) {
861
+ return true;
862
+ }
863
+ // Pushed & up-to-date: <arbiter>/<branch> exists AND its tip == the local tip
864
+ // (we verify remote-tip == local-tip so a later un-pushed amend is never lost).
865
+ const remoteTip = (await gitSoft(['rev-parse', '--verify', '--quiet', `refs/remotes/${arbiter}/${branch}`], cwd, env)).stdout.trim();
866
+ return remoteTip !== '' && remoteTip === localTip;
867
+ }
868
+ /** The short symbolic name of HEAD, or '' for a detached HEAD. */
869
+ async function currentBranch(cwd, env) {
870
+ const sym = await gitSoft(['symbolic-ref', '--quiet', '--short', 'HEAD'], cwd, env);
871
+ return sym.status === 0 ? sym.stdout.trim() : '';
872
+ }
873
+ /** Run git, returning the raw result (no throw) — for soft checks. */
874
+ function gitSoft(args, cwd, env) {
875
+ return runAsync('git', args, cwd, { env });
876
+ }
877
+ /** Run git; throw on non-zero (genuinely unexpected plumbing failures). */
878
+ async function gitHard(args, cwd, env) {
879
+ const result = await runAsync('git', args, cwd, { env });
880
+ if (result.status !== 0) {
881
+ throw new Error(`git ${args.join(' ')} failed (exit ${result.status}): ${result.stderr.trim()}`);
882
+ }
883
+ return result;
884
+ }
885
+ //# sourceMappingURL=complete.js.map