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,988 @@
1
+ import {mkdirSync, writeFileSync, rmSync} from 'node:fs';
2
+ import {dirname, join} from 'node:path';
3
+ import {runAsync, type RunResult} from './git.js';
4
+ import {ledgerWrite} from './ledger-write.js';
5
+ import {resolveSidecarIdentity} from './sidecar.js';
6
+ import {acquireItemLock, releaseItemLock} from './item-lock.js';
7
+ import {realSleep, type Sleep} from './retry-backoff.js';
8
+
9
+ /**
10
+ * The **advancing-lock BORROW** (prd `advance-loop`, task
11
+ * `advancing-lock-borrow`, US #19–24).
12
+ *
13
+ * The surface/apply/triage phase's SHORT borrow. As of the capstone cut-over
14
+ * (task `cutover-retire-slicing-advancing-markers-and-trim-folder-sets`, prd
15
+ * `ledger-status-per-item-lock-refs`; ADR `ledger-status-on-per-item-lock-refs`)
16
+ * the legacy `work/advancing/<entry>.md` presence-MARKER on `main` is GONE. The
17
+ * advancing borrow now rides ONLY the UNIFIED per-item lock
18
+ * (`refs/dorfl/lock/<entry>`, `action: advance`) — there is no transient
19
+ * status in `main`'s tree anymore, so a work branch cut from `main` inherits no
20
+ * stale advancing marker.
21
+ *
22
+ * The borrow is keyed by item IDENTITY (`<type>-<slug>`, via
23
+ * {@link resolveSidecarIdentity} — the single source of truth the unified lock,
24
+ * the sidecar, and the work branch all share), so a task, a prd, and an
25
+ * observation that share a slug NEVER collide, and the SAME `<entry>` ref means an
26
+ * `advance` hold is MUTUALLY EXCLUSIVE with a claim/task hold of the SAME item BY
27
+ * CONSTRUCTION (the second acquirer loses the SAME create-only ref CAS).
28
+ *
29
+ * It is file-ORTHOGONAL to the item it locks: the item's own lifecycle file NEVER
30
+ * moves (the borrow is a LOCK, not a lifecycle transition), which is exactly why
31
+ * it can lock items resting in DIFFERENT source folders (a backlog task, a `prds/`
32
+ * prd, an `observations/` note) with one uniform mechanism.
33
+ *
34
+ * **TREE-LESS vs BUILD/TASK RUNGS (`acquireUnified`).** The advance tick sets
35
+ * `acquireUnified` PER RUNG (the policy lives in `advance.ts`, where the rung is
36
+ * known):
37
+ *
38
+ * - **TREE-LESS rungs** (`surface`/`apply`/`triage`) have NO inner `do`, so the
39
+ * advancing acquire takes the item's unified `action: advance` lock — that hold
40
+ * IS the advance∥claim / advance∥task exclusion. Acquire/release delegate to
41
+ * {@link acquireItemLock} / {@link releaseItemLock} (a parentless ref CAS, no
42
+ * working-tree write, no retry budget).
43
+ * - **BUILD-TASK / TASK-PRD rungs** never take the unified lock at the advance
44
+ * layer (`acquireUnified` false): `performAdvance` orchestrates an inner
45
+ * `performDo` that ITSELF acquires the SAME `task-<slug>`/`prd-<slug>` ref
46
+ * (the create-only CAS with NO re-entrancy/auto-steal), so taking it again here
47
+ * would DEADLOCK the tick against itself. For these rungs the acquire/release
48
+ * are a NO-OP (`acquired`/`released`); the inner `do`'s lock is the sole
49
+ * exclusion point (the POST-#9 EXCLUSION PROOF, owned by this task).
50
+ *
51
+ * A dry-run never takes the lock (it mutates nothing). This module stays
52
+ * rung-agnostic — it only knows "unified or not", never the rung kind.
53
+ *
54
+ * This module also delivers {@link createItemThroughCas}: new-item creation routed
55
+ * THROUGH the SAME write-seam CAS, keyed on the NEW item's identity (its target
56
+ * path), so the (unlikely) same-slug new-item race needs NO special case — the
57
+ * loser simply fails the CAS and backs off. The triage rung (observation→promote
58
+ * drafting a new `work/backlog/<new-slug>.md`) consumes it. It is NOT a lock and
59
+ * was never tied to the marker.
60
+ *
61
+ * Lock discipline: MANDATORY for the autonomous driver (a contender may be active),
62
+ * a no-op formality for a solo human (no contender). Every lock acquire/release is
63
+ * RUNNER-mediated (the agent never touches the lock ref). Recovery is the unified
64
+ * lock's: `release-lock <item>` + the `gc --ledger` stuck-lock report (no liveness
65
+ * heartbeat, no auto-sweep; a human asserts a lock is dead).
66
+ */
67
+
68
+ const DEFAULT_ARBITER = 'origin';
69
+
70
+ // --- Acquire --------------------------------------------------------------
71
+
72
+ /** A semantic label for the lock-acquire outcome (never the verdict itself). */
73
+ export type AcquireAdvancingLockOutcome =
74
+ | 'acquired'
75
+ | 'usage-error'
76
+ | 'lost'
77
+ | 'contended';
78
+
79
+ /** Maps onto the claim-CAS exit codes (identical semantics). */
80
+ export type AcquireAdvancingLockExitCode = 0 | 1 | 2 | 3;
81
+
82
+ export interface AcquireAdvancingLockOptions {
83
+ /**
84
+ * The NAMESPACED item identity to lock (`task:<slug>` / `prd:<slug>` /
85
+ * `obs:<slug>` / `observation:<slug>`, or a bare `<slug>` = task). The
86
+ * resolver derives the type-encoded entry `<type>-<slug>` from it.
87
+ */
88
+ item: string;
89
+ /** Working clone/worktree the lock acquire runs in. */
90
+ cwd: string;
91
+ /** Name of the arbiter remote (`--arbiter`). Defaults to `origin`. */
92
+ arbiter?: string;
93
+ /** Advisory locker id. Defaults to git user.name, then $USER. */
94
+ by?: string;
95
+ /** Cap on push retries when main merely advanced. Default 3. (Unified lock is retry-free; retained for API parity.) */
96
+ retries?: number;
97
+ /** Show the intended push without mutating the arbiter (`--dry-run`). */
98
+ dryRun?: boolean;
99
+ /**
100
+ * Acquire the item's UNIFIED per-item lock (`action: advance`). The advance tick
101
+ * sets this PER RUNG (the policy lives where the rung is known — `advance.ts`):
102
+ * `true` for the TREE-LESS rungs (`surface`/`apply`/`triage`), which have no
103
+ * inner `do` and so genuinely need the unified hold to realise advance∥claim /
104
+ * advance∥task exclusion; `false` (the default) for the build-task / task-spec
105
+ * rungs, whose inner `performDo` ALREADY takes the SAME `task-<slug>`/`prd-<slug>`
106
+ * ref — taking it again here would DEADLOCK the tick against itself, so for those
107
+ * the acquire is a NO-OP `acquired`. When `true`, a lock `lost` makes the acquire
108
+ * lose DEFINITIVELY (no retry budget). A dry-run never takes the lock (it mutates
109
+ * nothing). This module stays rung-agnostic — it only knows "unified or not".
110
+ */
111
+ acquireUnified?: boolean;
112
+ /** Environment for child git processes. */
113
+ env?: NodeJS.ProcessEnv;
114
+ /** Sink for human-readable progress notes. */
115
+ note?: (message: string) => void;
116
+ }
117
+
118
+ export interface AcquireAdvancingLockResult {
119
+ exitCode: AcquireAdvancingLockExitCode;
120
+ outcome: AcquireAdvancingLockOutcome;
121
+ /** Human-readable summary of the terminal condition. */
122
+ message: string;
123
+ /**
124
+ * The type-encoded lock entry name (`<type>-<slug>`) the borrow keyed onto —
125
+ * the unified lock ref is `refs/dorfl/lock/<entry>`. Surfaced so the
126
+ * caller hands the SAME entry back (and so tests can assert the type-encoding).
127
+ */
128
+ entry?: string;
129
+ }
130
+
131
+ /** Raised for usage/environment errors (exit 1). */
132
+ class AdvancingLockUsageError extends Error {}
133
+
134
+ /**
135
+ * Acquire the advancing borrow for `item`. For a TREE-LESS rung
136
+ * (`acquireUnified: true`) this is the item's unified `action: advance` lock (a
137
+ * parentless ref CAS); for a build/task rung (the default) it is a NO-OP
138
+ * `acquired` (the inner `do`'s claim/task lock is the exclusion). Never throws
139
+ * for the expected "lost the race" (exit 2) case — it is returned.
140
+ */
141
+ export async function acquireAdvancingLock(
142
+ options: AcquireAdvancingLockOptions,
143
+ ): Promise<AcquireAdvancingLockResult> {
144
+ const note = options.note ?? (() => {});
145
+ try {
146
+ return await runAcquire(options, note);
147
+ } catch (err) {
148
+ if (err instanceof AdvancingLockUsageError) {
149
+ return {exitCode: 1, outcome: 'usage-error', message: err.message};
150
+ }
151
+ const message = err instanceof Error ? err.message : String(err);
152
+ return {exitCode: 1, outcome: 'usage-error', message};
153
+ }
154
+ }
155
+
156
+ async function runAcquire(
157
+ options: AcquireAdvancingLockOptions,
158
+ note: (m: string) => void,
159
+ ): Promise<AcquireAdvancingLockResult> {
160
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
161
+ const dryRun = options.dryRun ?? false;
162
+ const cwd = options.cwd;
163
+ const env = options.env;
164
+
165
+ if (!options.item) {
166
+ throw new AdvancingLockUsageError(
167
+ 'missing <item>. usage: acquireAdvancingLock({item, cwd, arbiter})',
168
+ );
169
+ }
170
+ // The type-encoded entry `<type>-<slug>` — the SAME identity scheme the sidecar
171
+ // and the unified lock use, derived via the shared resolver (the source of truth).
172
+ const {type, slug} = resolveSidecarIdentity(options.item);
173
+ const entry = `${type}-${slug}`;
174
+
175
+ if ((await gitSoft(['rev-parse', '--git-dir'], cwd, env)).status !== 0) {
176
+ throw new AdvancingLockUsageError('not inside a git repository');
177
+ }
178
+ if ((await gitSoft(['remote', 'get-url', arbiter], cwd, env)).status !== 0) {
179
+ throw new AdvancingLockUsageError(
180
+ `no git remote named '${arbiter}' (set one, or pass --arbiter)`,
181
+ );
182
+ }
183
+ const by = options.by || (await resolveBy(cwd, env));
184
+
185
+ // BUILD/TASK rung (or a dry-run): NO advance-layer hold at all. The inner `do`'s
186
+ // claim/task unified lock is the sole exclusion point — taking it again here
187
+ // would deadlock the tick against itself. A NO-OP `acquired`.
188
+ const acquireUnified = (options.acquireUnified ?? false) && !dryRun;
189
+ if (!acquireUnified) {
190
+ const message = dryRun
191
+ ? `[dry-run] advancing borrow for '${entry}' (no lock taken).`
192
+ : `advancing borrow for '${entry}' rides the inner do's lock (no advance-layer hold).`;
193
+ note(message);
194
+ return {exitCode: 0, outcome: 'acquired', message, entry};
195
+ }
196
+
197
+ // TREE-LESS rung: take the item's UNIFIED per-item lock (`action: advance`,
198
+ // keyed `item` so it shares the ONE `<type>-<slug>` ref with claim/task/advance
199
+ // of the SAME item). A create-only ref CAS: the winner holds it, the loser is
200
+ // DEFINITIVELY `lost` (exit 2, no retry budget — a per-item conflict the loser
201
+ // should lose). No auto-steal of an orphaned lock, consistent with claim/task
202
+ // and the ADR's recovery model (no liveness heartbeat / auto-sweep; a human
203
+ // asserts a lock is dead via `release-lock` + `gc --ledger`).
204
+ const lock = await acquireItemLock({
205
+ item: options.item,
206
+ action: 'advance',
207
+ cwd,
208
+ arbiter,
209
+ holder: by,
210
+ env,
211
+ });
212
+ if (lock.outcome === 'error') {
213
+ throw new AdvancingLockUsageError(
214
+ `failed to acquire the item lock for '${entry}': ${lock.message}`,
215
+ );
216
+ }
217
+ if (lock.outcome === 'lost') {
218
+ note(lock.message);
219
+ return {exitCode: 2, outcome: 'lost', message: lock.message, entry};
220
+ }
221
+ const message = `LOCKED '${entry}' for advancing on ${arbiter} (unified lock).`;
222
+ note(message);
223
+ return {exitCode: 0, outcome: 'acquired', message, entry};
224
+ }
225
+
226
+ // --- Release --------------------------------------------------------------
227
+
228
+ /** A semantic label for the lock-release outcome. */
229
+ export type ReleaseAdvancingLockOutcome =
230
+ | 'released'
231
+ | 'usage-error'
232
+ | 'lost'
233
+ | 'contended';
234
+
235
+ /** Maps onto the claim-CAS exit codes (identical semantics). */
236
+ export type ReleaseAdvancingLockExitCode = 0 | 1 | 2 | 3;
237
+
238
+ export interface ReleaseAdvancingLockOptions {
239
+ /** The NAMESPACED item identity whose borrow to release (same forms as acquire). */
240
+ item: string;
241
+ /** Working clone/worktree the release runs in. */
242
+ cwd: string;
243
+ /** Name of the arbiter remote (`--arbiter`). Defaults to `origin`. */
244
+ arbiter?: string;
245
+ /** Advisory releaser id. Defaults to git user.name, then $USER. */
246
+ by?: string;
247
+ /** Cap on push retries when main merely advanced. Default 3. (Retained for API parity.) */
248
+ retries?: number;
249
+ /**
250
+ * Release the item's UNIFIED per-item lock (the complement of
251
+ * {@link AcquireAdvancingLockOptions.acquireUnified}). The advance tick sets this
252
+ * for a TREE-LESS rung (`surface`/`apply`/`triage`), where the acquire took the
253
+ * unified lock; `false` (the default) for the build-task / task-spec rungs,
254
+ * which never took it at the advance layer (the inner `performDo`'s claim/task
255
+ * lock is the exclusion point and is released by the inner `do`) — for those the
256
+ * release is a NO-OP `released`.
257
+ */
258
+ releaseUnified?: boolean;
259
+ /** Environment for child git processes. */
260
+ env?: NodeJS.ProcessEnv;
261
+ /** Sink for human-readable progress notes. */
262
+ note?: (message: string) => void;
263
+ }
264
+
265
+ export interface ReleaseAdvancingLockResult {
266
+ exitCode: ReleaseAdvancingLockExitCode;
267
+ outcome: ReleaseAdvancingLockOutcome;
268
+ /** Human-readable summary of the terminal condition. */
269
+ message: string;
270
+ /** The type-encoded lock entry name (`<type>-<slug>`) that was released. */
271
+ entry?: string;
272
+ }
273
+
274
+ /**
275
+ * Release the advancing borrow for `item`. For a TREE-LESS rung
276
+ * (`releaseUnified: true`) this DELETES the item's unified `action: advance` lock
277
+ * ref; for a build/task rung (the default) it is a NO-OP `released` (that rung
278
+ * never took an advance-layer hold — the inner `do` released its own lock). It
279
+ * moves NO lifecycle file: the borrow is a LOCK, not a lifecycle transition, so
280
+ * the item is returned exactly where it rested.
281
+ *
282
+ * Idempotent: a unified release of an already-absent ref reports `released` (the
283
+ * item is returned to rest with no held lock). Best-effort + never throws for the
284
+ * expected cases.
285
+ */
286
+ export async function releaseAdvancingLock(
287
+ options: ReleaseAdvancingLockOptions,
288
+ ): Promise<ReleaseAdvancingLockResult> {
289
+ const note = options.note ?? (() => {});
290
+ try {
291
+ return await runRelease(options, note);
292
+ } catch (err) {
293
+ if (err instanceof AdvancingLockUsageError) {
294
+ return {exitCode: 1, outcome: 'usage-error', message: err.message};
295
+ }
296
+ const message = err instanceof Error ? err.message : String(err);
297
+ return {exitCode: 1, outcome: 'usage-error', message};
298
+ }
299
+ }
300
+
301
+ async function runRelease(
302
+ options: ReleaseAdvancingLockOptions,
303
+ note: (m: string) => void,
304
+ ): Promise<ReleaseAdvancingLockResult> {
305
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
306
+ const cwd = options.cwd;
307
+ const env = options.env;
308
+
309
+ if (!options.item) {
310
+ throw new AdvancingLockUsageError(
311
+ 'missing <item>. usage: releaseAdvancingLock({item, cwd, arbiter})',
312
+ );
313
+ }
314
+ const {type, slug} = resolveSidecarIdentity(options.item);
315
+ const entry = `${type}-${slug}`;
316
+
317
+ if ((await gitSoft(['rev-parse', '--git-dir'], cwd, env)).status !== 0) {
318
+ throw new AdvancingLockUsageError('not inside a git repository');
319
+ }
320
+ if ((await gitSoft(['remote', 'get-url', arbiter], cwd, env)).status !== 0) {
321
+ throw new AdvancingLockUsageError(
322
+ `no git remote named '${arbiter}' (set one, or pass --arbiter)`,
323
+ );
324
+ }
325
+
326
+ // BUILD/TASK rung: nothing was held at the advance layer — a NO-OP `released`.
327
+ const releaseUnified = options.releaseUnified ?? false;
328
+ if (!releaseUnified) {
329
+ const message = `advancing borrow for '${entry}' released (no advance-layer hold to drop).`;
330
+ note(message);
331
+ return {exitCode: 0, outcome: 'released', message, entry};
332
+ }
333
+
334
+ // TREE-LESS rung: delete the unified `action: advance` lock ref (idempotent —
335
+ // an already-absent ref is `not-held`, still mapped to `released`: the item is
336
+ // returned to rest with no held lock).
337
+ const released = await releaseItemLock({
338
+ item: options.item,
339
+ cwd,
340
+ arbiter,
341
+ env,
342
+ });
343
+ if (released.outcome === 'error') {
344
+ throw new AdvancingLockUsageError(
345
+ `failed to release the item lock for '${entry}': ${released.message}`,
346
+ );
347
+ }
348
+ const message =
349
+ released.outcome === 'not-held'
350
+ ? `advancing borrow for '${entry}' was already released (no lock held).`
351
+ : `RELEASED '${entry}' advancing borrow on ${arbiter} (item untouched).`;
352
+ note(message);
353
+ return {exitCode: 0, outcome: 'released', message, entry};
354
+ }
355
+
356
+ // --- New-item creation through the CAS ------------------------------------
357
+
358
+ // --- Contention retry: jittered delay + widened budget (lifecycle fan-out) ---
359
+
360
+ /**
361
+ * The CONTENTION-RETRY BUDGET for the create/publish CAS in
362
+ * {@link createItemThroughCas} — the tuple of bounds that turns the loop from
363
+ * "instantly retry, tiny fixed cap" into a bounded DECORRELATED-JITTER retry with
364
+ * a wider attempt / wall-clock envelope. Deliberately DISTINCT from the OUTAGE
365
+ * regime in `retry-backoff.ts` (exponential backoff, modelling an unreachable
366
+ * remote): a rejected push against a MOVED ref just wants a small RANDOM delay to
367
+ * BREAK LOCKSTEP among parallel legs, not an exponential outage ramp. See the
368
+ * decision block at the bottom of this file for the rationale.
369
+ *
370
+ * All fields optional; the effective values fall back to the interactive default
371
+ * ({@link INTERACTIVE_CAS_CONTENTION}) — a small fixed cap with NO delay, so a
372
+ * lone caller stays prompt. The lifecycle driver passes
373
+ * {@link LIFECYCLE_CAS_CONTENTION} to widen a fan-out.
374
+ */
375
+ export interface CasContentionBudget {
376
+ /** Cap on push retries when main merely advanced. */
377
+ retries?: number;
378
+ /** Base delay (ms) for the decorrelated-jitter schedule. `0` disables the delay. */
379
+ initialDelayMs?: number;
380
+ /** Cap (ms) the growing jittered delay never exceeds. */
381
+ maxDelayMs?: number;
382
+ /** Wall-clock budget (ms) for cumulative retry delays. `0` disables the wall-clock cap. */
383
+ maxTotalMs?: number;
384
+ /** Injected sleep seam (tests). Defaults to {@link realSleep}. */
385
+ sleep?: Sleep;
386
+ /** Injected `[0,1)` RNG (tests). Defaults to `Math.random`. */
387
+ rng?: () => number;
388
+ }
389
+
390
+ /**
391
+ * The INTERACTIVE default: today's shape (3 retries, NO delay). A human-typed
392
+ * single-item caller keeps the prompt bounded give-up it always had. Preserved
393
+ * so the API change is backwards-compatible for direct callers of
394
+ * {@link createItemThroughCas} that pass nothing.
395
+ */
396
+ export const INTERACTIVE_CAS_CONTENTION: Required<
397
+ Omit<CasContentionBudget, 'sleep' | 'rng'>
398
+ > = {
399
+ retries: 3,
400
+ initialDelayMs: 0,
401
+ maxDelayMs: 0,
402
+ maxTotalMs: 0,
403
+ };
404
+
405
+ /**
406
+ * The LIFECYCLE default: the wider envelope for the parallel-legs fan-out (the
407
+ * lifecycle propose/triage tick's promote-CAS + `mint-adr` legs). A modest base
408
+ * delay grows under an AWS-style DECORRELATED-JITTER schedule up to a cap, with a
409
+ * generous wall-clock envelope so a herd of valid appends DRAINS instead of
410
+ * thrashing to exhaustion. Sized so a lone leg is barely slower than today
411
+ * (jitter is small on the first retry) while a fan-out of N legs desynchronises
412
+ * within a few retries. See the decision block at the bottom of this file.
413
+ */
414
+ export const LIFECYCLE_CAS_CONTENTION: Required<
415
+ Omit<CasContentionBudget, 'sleep' | 'rng'>
416
+ > = {
417
+ retries: 32,
418
+ initialDelayMs: 25,
419
+ maxDelayMs: 2_000,
420
+ maxTotalMs: 30_000,
421
+ };
422
+
423
+ /**
424
+ * The outcome of a single CAS attempt inside {@link runCasContentionLoop}
425
+ * (mirrors {@link CreateAttemptResult}, exported for the retry-loop seam).
426
+ */
427
+ export type CasAttemptResult =
428
+ | {kind: 'created'; message: string}
429
+ | {kind: 'lost'; message: string}
430
+ | {kind: 'rejected'; message: string};
431
+
432
+ /**
433
+ * The retry-loop terminal outcome ({@link runCasContentionLoop}). Adds
434
+ * observation counters (`attempts`, the sequence of injected `sleep(ms)` calls)
435
+ * so tests can assert the RETRY TIMELINE + the JITTER SHAPE deterministically.
436
+ */
437
+ export interface CasContentionLoopResult {
438
+ /** Terminal kind: `created` / `lost` / `contended` (bounded give-up). */
439
+ kind: 'created' | 'lost' | 'contended';
440
+ /** Human-readable summary. */
441
+ message: string;
442
+ /** Total attempts made (1 + retries taken). */
443
+ attempts: number;
444
+ /** The sequence of sleep durations (ms) between retries — the jitter timeline. */
445
+ sleeps: number[];
446
+ }
447
+
448
+ /**
449
+ * Run a CAS create/publish attempt inside the CONTENTION-RETRY loop. Reusable
450
+ * primitive shared between {@link createItemThroughCas} (the production path) and
451
+ * the unit tests that drive the loop with an injected fake attempt + injected
452
+ * `Sleep` + injected RNG so the retry timeline + jitter are fully deterministic
453
+ * with no real wall-clock waits.
454
+ *
455
+ * The `attempt` callback runs ONE CAS attempt and reports `created` / `lost` /
456
+ * `rejected`. On `rejected` the loop consults the {@link CasContentionBudget}:
457
+ * 1. if the attempts cap is reached → clean bounded give-up (`contended`);
458
+ * 2. else compute a DECORRELATED-JITTER delay ({@link nextCasContentionDelayMs});
459
+ * 3. if the delay would push cumulative sleep past `maxTotalMs` → clean bounded
460
+ * give-up (`contended` — the wall-clock terminator);
461
+ * 4. else emit the retry note, sleep, and loop.
462
+ */
463
+ export async function runCasContentionLoop(input: {
464
+ attempt: () => Promise<CasAttemptResult>;
465
+ budget: CasContentionBudget;
466
+ note?: (m: string) => void;
467
+ }): Promise<CasContentionLoopResult> {
468
+ const note = input.note ?? (() => {});
469
+ const budget = input.budget;
470
+ const retries = budget.retries ?? INTERACTIVE_CAS_CONTENTION.retries;
471
+ const initialDelayMs =
472
+ budget.initialDelayMs ?? INTERACTIVE_CAS_CONTENTION.initialDelayMs;
473
+ const maxDelayMs = budget.maxDelayMs ?? INTERACTIVE_CAS_CONTENTION.maxDelayMs;
474
+ const maxTotalMs = budget.maxTotalMs ?? INTERACTIVE_CAS_CONTENTION.maxTotalMs;
475
+ const sleep = budget.sleep ?? realSleep;
476
+ const rng = budget.rng ?? Math.random;
477
+
478
+ let attempts = 0;
479
+ let elapsed = 0;
480
+ let previousDelayMs = 0;
481
+ const sleeps: number[] = [];
482
+ // eslint-disable-next-line no-constant-condition
483
+ while (true) {
484
+ attempts += 1;
485
+ const result = await input.attempt();
486
+ if (result.kind === 'created') {
487
+ return {kind: 'created', message: result.message, attempts, sleeps};
488
+ }
489
+ if (result.kind === 'lost') {
490
+ return {kind: 'lost', message: result.message, attempts, sleeps};
491
+ }
492
+ const rejectedCount = attempts; // attempts so far == rejections so far
493
+ if (rejectedCount > retries) {
494
+ const message = `push rejected ${rejectedCount} times (main is contended). Try again shortly.`;
495
+ note(message);
496
+ return {kind: 'contended', message, attempts, sleeps};
497
+ }
498
+ const delay = nextCasContentionDelayMs({
499
+ previousDelayMs,
500
+ initialDelayMs,
501
+ maxDelayMs,
502
+ rng,
503
+ });
504
+ if (maxTotalMs > 0 && elapsed + delay > maxTotalMs) {
505
+ const message = `push rejected ${rejectedCount} times (main is contended; wall-clock budget ${maxTotalMs}ms exhausted). Try again shortly.`;
506
+ note(message);
507
+ return {kind: 'contended', message, attempts, sleeps};
508
+ }
509
+ note(
510
+ `main advanced under us — refetch and retry (${rejectedCount}/${retries})...`,
511
+ );
512
+ if (delay > 0) {
513
+ await sleep(delay);
514
+ elapsed += delay;
515
+ previousDelayMs = delay;
516
+ sleeps.push(delay);
517
+ }
518
+ }
519
+ }
520
+
521
+ /**
522
+ * Compute the NEXT contention-retry delay under AWS-style DECORRELATED JITTER:
523
+ * `delay = min(cap, floor(base + rng() * (min(cap, prev*3) - base)))`, clamped to
524
+ * `[base, cap]`. `previousDelayMs = 0` (the first retry) uses `base` as the
525
+ * seed. Returns `0` iff the delay is disabled (either bound is `0` or `NaN`),
526
+ * which preserves the interactive "instant retry" shape.
527
+ *
528
+ * Exported for the pure-unit shape test — the property is BOUNDED randomness
529
+ * that grows toward the cap.
530
+ */
531
+ export function nextCasContentionDelayMs(input: {
532
+ previousDelayMs: number;
533
+ initialDelayMs: number;
534
+ maxDelayMs: number;
535
+ rng: () => number;
536
+ }): number {
537
+ const base = input.initialDelayMs;
538
+ const cap = input.maxDelayMs;
539
+ if (!(base > 0) || !(cap > 0)) {
540
+ return 0;
541
+ }
542
+ const prev = input.previousDelayMs > 0 ? input.previousDelayMs : base;
543
+ const upper = Math.min(cap, prev * 3);
544
+ const r = Math.max(0, Math.min(1, input.rng()));
545
+ return Math.max(base, Math.min(cap, Math.floor(base + r * (upper - base))));
546
+ }
547
+
548
+ /** A semantic label for the new-item creation outcome. */
549
+ export type CreateItemOutcome =
550
+ | 'created'
551
+ | 'usage-error'
552
+ | 'lost'
553
+ | 'contended';
554
+
555
+ /** Maps onto the claim-CAS exit codes (identical semantics). */
556
+ export type CreateItemExitCode = 0 | 1 | 2 | 3;
557
+
558
+ export interface CreateItemThroughCasOptions {
559
+ /**
560
+ * The NEW item's path RELATIVE to the repo root (e.g.
561
+ * `work/backlog/<new-slug>.md`). The CAS is keyed on THIS path (the new item's
562
+ * identity): two concurrent creators of the same path race, the loser fails the
563
+ * CAS — no special case for the (unlikely) same-slug new-item collision.
564
+ */
565
+ path: string;
566
+ /** The new item's file content. */
567
+ content: string;
568
+ /**
569
+ * Extra repo-relative paths to `git rm` IN THE SAME create commit (e.g. the
570
+ * promoted observation + its answered sidecar). They ride the winning creator's
571
+ * ATOMIC commit, so a crash never leaves the source deleted without its
572
+ * successor (or the successor created with the source still live), and a CAS
573
+ * LOSER — which never reaches the commit — leaves them INTACT for a retry.
574
+ * A listed path that is absent on the create branch is skipped (best-effort rm).
575
+ */
576
+ deletePaths?: string[];
577
+ /** Working clone/worktree the creation runs in. */
578
+ cwd: string;
579
+ /** Name of the arbiter remote (`--arbiter`). Defaults to `origin`. */
580
+ arbiter?: string;
581
+ /** Advisory creator id. Defaults to git user.name, then $USER. */
582
+ by?: string;
583
+ /**
584
+ * Cap on push retries when main merely advanced. Default 3 (interactive shape).
585
+ * The lifecycle driver widens this via {@link contention} /
586
+ * {@link LIFECYCLE_CAS_CONTENTION} so a parallel fan-out DRAINS.
587
+ */
588
+ retries?: number;
589
+ /**
590
+ * The CONTENTION-RETRY BUDGET (jittered delay + wider attempt / wall-clock
591
+ * envelope). Optional: absent ⇒ {@link INTERACTIVE_CAS_CONTENTION}
592
+ * (today's instant-retry shape, so direct callers are unchanged); the
593
+ * lifecycle driver in `advance.ts` passes {@link LIFECYCLE_CAS_CONTENTION} to
594
+ * WIDEN a fan-out. Individual `contention` fields override {@link retries}.
595
+ */
596
+ contention?: CasContentionBudget;
597
+ /** Show the intended push without mutating the arbiter (`--dry-run`). */
598
+ dryRun?: boolean;
599
+ /** Environment for child git processes. */
600
+ env?: NodeJS.ProcessEnv;
601
+ /** Sink for human-readable progress notes. */
602
+ note?: (message: string) => void;
603
+ }
604
+
605
+ export interface CreateItemThroughCasResult {
606
+ exitCode: CreateItemExitCode;
607
+ outcome: CreateItemOutcome;
608
+ /** Human-readable summary of the terminal condition. */
609
+ message: string;
610
+ }
611
+
612
+ /** Internal: the result of a single create attempt. */
613
+ type CreateAttemptResult =
614
+ | {kind: 'created'; message: string}
615
+ | {kind: 'lost'; message: string}
616
+ | {kind: 'rejected'; message: string};
617
+
618
+ /**
619
+ * Create a NEW `work/` item THROUGH the CAS, keyed on the new item's identity (its
620
+ * `path`). The reusable helper the triage rung consumes when an observation→promote
621
+ * drafts a new `work/backlog/<new-slug>.md`: the (unlikely) same-slug new-item race
622
+ * needs NO special case — exactly one creator lands the file, the loser fails the
623
+ * CAS (exit 2) and backs off. Same CAS-micro-commit / force-with-lease shape as the
624
+ * unified lock; it is NOT a lock (it does not hold a borrow), it ATOMICALLY publishes
625
+ * a new file iff that path is still absent on the arbiter.
626
+ *
627
+ * An optional {@link CreateItemThroughCasOptions.deletePaths} rides the SAME create
628
+ * commit (the promote route's note + sidecar `git rm`), so create + delete are ONE
629
+ * atomic commit: a crash never strands the source without its successor, and a CAS
630
+ * LOSER — which never reaches the commit — leaves the source intact for a retry.
631
+ */
632
+ export async function createItemThroughCas(
633
+ options: CreateItemThroughCasOptions,
634
+ ): Promise<CreateItemThroughCasResult> {
635
+ const note = options.note ?? (() => {});
636
+ try {
637
+ return await runCreate(options, note);
638
+ } catch (err) {
639
+ if (err instanceof AdvancingLockUsageError) {
640
+ return {exitCode: 1, outcome: 'usage-error', message: err.message};
641
+ }
642
+ const message = err instanceof Error ? err.message : String(err);
643
+ return {exitCode: 1, outcome: 'usage-error', message};
644
+ }
645
+ }
646
+
647
+ async function runCreate(
648
+ options: CreateItemThroughCasOptions,
649
+ note: (m: string) => void,
650
+ ): Promise<CreateItemThroughCasResult> {
651
+ const arbiter = options.arbiter ?? DEFAULT_ARBITER;
652
+ const budget = options.contention ?? {};
653
+ const retries =
654
+ budget.retries ?? options.retries ?? INTERACTIVE_CAS_CONTENTION.retries;
655
+ const initialDelayMs =
656
+ budget.initialDelayMs ?? INTERACTIVE_CAS_CONTENTION.initialDelayMs;
657
+ const maxDelayMs = budget.maxDelayMs ?? INTERACTIVE_CAS_CONTENTION.maxDelayMs;
658
+ const maxTotalMs = budget.maxTotalMs ?? INTERACTIVE_CAS_CONTENTION.maxTotalMs;
659
+ const sleep = budget.sleep ?? realSleep;
660
+ const rng = budget.rng ?? Math.random;
661
+ const dryRun = options.dryRun ?? false;
662
+ const cwd = options.cwd;
663
+ const env = options.env;
664
+ const path = options.path;
665
+
666
+ if (!path) {
667
+ throw new AdvancingLockUsageError(
668
+ 'missing <path>. usage: createItemThroughCas({path, content, cwd, arbiter})',
669
+ );
670
+ }
671
+ if ((await gitSoft(['rev-parse', '--git-dir'], cwd, env)).status !== 0) {
672
+ throw new AdvancingLockUsageError('not inside a git repository');
673
+ }
674
+ if ((await gitSoft(['remote', 'get-url', arbiter], cwd, env)).status !== 0) {
675
+ throw new AdvancingLockUsageError(
676
+ `no git remote named '${arbiter}' (set one, or pass --arbiter)`,
677
+ );
678
+ }
679
+ const by = options.by || (await resolveBy(cwd, env));
680
+
681
+ const dirtyWorktree =
682
+ (await gitSoft(['diff', '--quiet'], cwd, env)).status !== 0;
683
+ const dirtyIndex =
684
+ (await gitSoft(['diff', '--cached', '--quiet'], cwd, env)).status !== 0;
685
+ if (dirtyWorktree || dirtyIndex) {
686
+ throw new AdvancingLockUsageError(
687
+ 'working tree has uncommitted changes; commit/stash them before creating',
688
+ );
689
+ }
690
+
691
+ // A throwaway create branch, slugified off the new item's path (so concurrent
692
+ // creators of DIFFERENT paths don't collide on the branch name either).
693
+ const createBranch = `create/${path.replace(/[^A-Za-z0-9._-]+/g, '-')}`;
694
+
695
+ const origRef = await originalRef(cwd, env);
696
+ try {
697
+ // The bounded DECORRELATED-JITTER contention-retry lives in
698
+ // {@link runCasContentionLoop} — the shared primitive tests drive directly
699
+ // with an injected fake attempt + `Sleep` + RNG (no real waits, no flakes).
700
+ const loop = await runCasContentionLoop({
701
+ attempt: () =>
702
+ createAttempt({
703
+ path,
704
+ content: options.content,
705
+ deletePaths: options.deletePaths ?? [],
706
+ by,
707
+ arbiter,
708
+ dryRun,
709
+ cwd,
710
+ env,
711
+ createBranch,
712
+ note,
713
+ }),
714
+ budget: {
715
+ retries,
716
+ initialDelayMs,
717
+ maxDelayMs,
718
+ maxTotalMs,
719
+ sleep,
720
+ rng,
721
+ },
722
+ note,
723
+ });
724
+ if (loop.kind === 'created') {
725
+ return {exitCode: 0, outcome: 'created', message: loop.message};
726
+ }
727
+ if (loop.kind === 'lost') {
728
+ return {exitCode: 2, outcome: 'lost', message: loop.message};
729
+ }
730
+ return {exitCode: 3, outcome: 'contended', message: loop.message};
731
+ } finally {
732
+ await cleanup(cwd, origRef, createBranch, env);
733
+ }
734
+ }
735
+
736
+ interface CreateAttemptContext {
737
+ path: string;
738
+ content: string;
739
+ deletePaths: string[];
740
+ by: string;
741
+ arbiter: string;
742
+ dryRun: boolean;
743
+ cwd: string;
744
+ env: NodeJS.ProcessEnv | undefined;
745
+ createBranch: string;
746
+ note: (m: string) => void;
747
+ }
748
+
749
+ /** One create attempt: branch off arbiter/main, add the new file, CAS-push. */
750
+ async function createAttempt(
751
+ ctx: CreateAttemptContext,
752
+ ): Promise<CreateAttemptResult> {
753
+ const {arbiter, path, content, deletePaths, createBranch, cwd, env, note} =
754
+ ctx;
755
+
756
+ await gitHard(['fetch', '--quiet', arbiter], cwd, env);
757
+
758
+ // Is the path still free on the arbiter's main? (the new item absent.)
759
+ if (await catFileExists(`${arbiter}/main:${path}`, cwd, env)) {
760
+ const message = `'${path}' already exists on ${arbiter}/main — the new item lost the create race (or the slug is taken). Back off.`;
761
+ note(message);
762
+ return {kind: 'lost', message};
763
+ }
764
+
765
+ await gitHard(
766
+ ['checkout', '--quiet', '--detach', `${arbiter}/main`],
767
+ cwd,
768
+ env,
769
+ );
770
+ await gitSoft(['branch', '-D', createBranch], cwd, env);
771
+ await gitHard(
772
+ ['checkout', '--quiet', '-b', createBranch, `${arbiter}/main`],
773
+ cwd,
774
+ env,
775
+ );
776
+
777
+ const fileAbs = join(cwd, path);
778
+ mkdirSync(dirname(fileAbs), {recursive: true});
779
+ writeFileSync(fileAbs, content);
780
+ const add = await gitSoft(['add', '--', path], cwd, env);
781
+ if (add.status !== 0) {
782
+ throw new AdvancingLockUsageError(
783
+ `git add failed for '${path}' (unexpected — aborting create)`,
784
+ );
785
+ }
786
+
787
+ // `git rm` the extra paths (the promoted source note + its sidecar) INTO this
788
+ // SAME create commit, so the create + delete are ONE atomic commit. Best-effort
789
+ // per path: a path absent on the create branch (e.g. an uncommitted local note,
790
+ // or an already-gone sidecar) is staged as a working-tree removal instead so
791
+ // the commit still carries the intended deletion without aborting.
792
+ for (const del of deletePaths) {
793
+ if (del === '' || del === path) {
794
+ continue;
795
+ }
796
+ const rm = await gitSoft(['rm', '--quiet', '--', del], cwd, env);
797
+ if (rm.status !== 0) {
798
+ // Not tracked on this branch — remove from the working tree (if present)
799
+ // and stage that removal; a never-existed path is simply skipped.
800
+ rmSync(join(cwd, del), {force: true});
801
+ await gitSoft(['add', '--', del], cwd, env);
802
+ }
803
+ }
804
+
805
+ await gitHard(
806
+ ['commit', '--quiet', '-m', `advance: create ${path} (by ${ctx.by})`],
807
+ cwd,
808
+ env,
809
+ );
810
+
811
+ const base = (
812
+ await gitHard(['rev-parse', `${arbiter}/main`], cwd, env)
813
+ ).stdout.trim();
814
+ const head = (await gitHard(['rev-parse', 'HEAD'], cwd, env)).stdout.trim();
815
+ if (head === base) {
816
+ throw new AdvancingLockUsageError(
817
+ 'advance-create commit is a no-op (nothing added) — aborting',
818
+ );
819
+ }
820
+
821
+ // Publish through the SAME seam (kind `advancing`) — keyed on the new item's
822
+ // identity (its path). The lease guards the CAS: a concurrent creator that
823
+ // landed first advanced main past our base, so our lease fails → rejected; the
824
+ // outer loop refetches and the next attempt finds the path TAKEN → lost (no
825
+ // special case). This holds EVEN for a same-identity racer who built an
826
+ // identical create commit within git's 1-second timestamp resolution: the seam
827
+ // stamps each attempt with a per-attempt `CAS-Nonce`, so the two racers'
828
+ // commits have DISTINCT shas — the loser's push is a genuine lease rejection,
829
+ // NOT an "Everything up-to-date" no-op that would spuriously verify as won.
830
+ if (ctx.dryRun) {
831
+ const result = await ledgerWrite.applyTransition({
832
+ kind: 'advancing',
833
+ arbiter,
834
+ localBranch: createBranch,
835
+ expectedBase: base,
836
+ head,
837
+ cwd,
838
+ dryRun: true,
839
+ env,
840
+ note,
841
+ });
842
+ return {kind: 'created', message: result.message};
843
+ }
844
+
845
+ const result = await ledgerWrite.applyTransition({
846
+ kind: 'advancing',
847
+ arbiter,
848
+ localBranch: createBranch,
849
+ expectedBase: base,
850
+ head,
851
+ cwd,
852
+ env,
853
+ note,
854
+ });
855
+ if (result.kind === 'published') {
856
+ const message = `CREATED '${path}' on ${arbiter}/main.`;
857
+ note(message);
858
+ return {kind: 'created', message};
859
+ }
860
+ return {kind: 'rejected', message: result.message};
861
+ }
862
+
863
+ // --- Shared helpers (same shape as claim-cas.ts) --------------------------
864
+
865
+ /** The branch (or detached HEAD sha) we should return to afterward. */
866
+ async function originalRef(
867
+ cwd: string,
868
+ env: NodeJS.ProcessEnv | undefined,
869
+ ): Promise<string> {
870
+ const sym = await gitSoft(
871
+ ['symbolic-ref', '--quiet', '--short', 'HEAD'],
872
+ cwd,
873
+ env,
874
+ );
875
+ if (sym.status === 0 && sym.stdout.trim() !== '') {
876
+ return sym.stdout.trim();
877
+ }
878
+ return (await gitHard(['rev-parse', 'HEAD'], cwd, env)).stdout.trim();
879
+ }
880
+
881
+ /** Best-effort: return to where we were and drop the throwaway branch. */
882
+ async function cleanup(
883
+ cwd: string,
884
+ origRef: string,
885
+ branch: string,
886
+ env: NodeJS.ProcessEnv | undefined,
887
+ ): Promise<void> {
888
+ await gitSoft(['checkout', '--quiet', origRef], cwd, env);
889
+ await gitSoft(['branch', '-D', branch], cwd, env);
890
+ }
891
+
892
+ /** Resolve the advisory locker: git user.name, else $USER/$USERNAME, else ''. */
893
+ async function resolveBy(
894
+ cwd: string,
895
+ env: NodeJS.ProcessEnv | undefined,
896
+ ): Promise<string> {
897
+ const name = await gitSoft(['config', 'user.name'], cwd, env);
898
+ if (name.status === 0 && name.stdout.trim() !== '') {
899
+ return name.stdout.trim();
900
+ }
901
+ const e = env ?? process.env;
902
+ return e.USER ?? e.USERNAME ?? '';
903
+ }
904
+
905
+ /** `git cat-file -e <object>` — true iff the object exists. */
906
+ async function catFileExists(
907
+ object: string,
908
+ cwd: string,
909
+ env: NodeJS.ProcessEnv | undefined,
910
+ ): Promise<boolean> {
911
+ return (await gitSoft(['cat-file', '-e', object], cwd, env)).status === 0;
912
+ }
913
+
914
+ /** Run git, returning the raw result (no throw) — for soft checks. */
915
+ function gitSoft(
916
+ args: string[],
917
+ cwd: string,
918
+ env: NodeJS.ProcessEnv | undefined,
919
+ ): Promise<RunResult> {
920
+ return runAsync('git', args, cwd, {env});
921
+ }
922
+
923
+ /** Run git; throw on non-zero (genuinely unexpected plumbing failures). */
924
+ async function gitHard(
925
+ args: string[],
926
+ cwd: string,
927
+ env: NodeJS.ProcessEnv | undefined,
928
+ ): Promise<RunResult> {
929
+ const result = await runAsync('git', args, cwd, {env});
930
+ if (result.status !== 0) {
931
+ throw new Error(
932
+ `git ${args.join(' ')} failed (exit ${result.status}): ${result.stderr.trim()}`,
933
+ );
934
+ }
935
+ return result;
936
+ }
937
+
938
+ // --- Decisions ------------------------------------------------------------
939
+ //
940
+ // **CAS contention retry: from instant-fixed-cap to bounded-JITTER + widened
941
+ // budget (fan-out drain).** The earlier recorded model for `createItemThroughCas`
942
+ // was "contention retries INSTANTLY, no delay, tiny fixed cap (3)". That is
943
+ // correct for a SINGLE contender (grab the new base and go) but PATHOLOGICAL for
944
+ // a lifecycle propose fan-out: N parallel legs (one per answered/triaged item)
945
+ // all CAS-append against the SAME `main` ref, refetch and re-push in lockstep,
946
+ // exhaust the tiny cap, and a large fraction exit `contended` (exit 3) EVEN
947
+ // THOUGH every leg's write is valid and would land if they took turns. A real
948
+ // 66-leg propose tick left ~33 legs at exit 3 \u2014 a thundering herd on one ref.
949
+ //
950
+ // The revised model, encoded here:
951
+ // 1. Add a bounded DECORRELATED-JITTER inter-retry delay
952
+ // ({@link nextCasContentionDelayMs}). Parallel legs desynchronise; the herd
953
+ // breaks; the fan-out DRAINS. Small enough that a lone leg is barely
954
+ // slower than today (first-retry delay is O(base)).
955
+ // 2. WIDEN the budget on the lifecycle path ({@link LIFECYCLE_CAS_CONTENTION}):
956
+ // more retries + a wall-clock envelope, since a lifecycle leg has no human
957
+ // waiting. The regime stays BOUNDED (attempts cap AND wall-clock cap) so
958
+ // genuine exhaustion still yields a clean `contended`, never a hang.
959
+ // 3. Scope the widened budget to the LIFECYCLE path via an explicit
960
+ // `contention` option threaded from the driver (`advance.ts` \u2192
961
+ // `promoteObservation` / `mintAdr` \u2192 `createItemThroughCas`). Direct
962
+ // callers that pass nothing keep the INTERACTIVE shape
963
+ // ({@link INTERACTIVE_CAS_CONTENTION}): 3 retries, NO delay \u2014 backwards
964
+ // compatible, so a human-typed single-item caller stays prompt.
965
+ //
966
+ // **Deliberately DISTINCT from the OUTAGE regime in `retry-backoff.ts`.** That
967
+ // helper models an unreachable remote with EXPONENTIAL backoff (the remote may
968
+ // come back). Contention is a REJECTED push against a MOVED ref \u2014 the remote is
969
+ // fine, the base just advanced. It wants a small RANDOM delay to BREAK LOCKSTEP,
970
+ // not an exponential outage ramp. Do NOT reuse `retryWithBackoff` here.
971
+ //
972
+ // **Jitter shape: AWS-style DECORRELATED JITTER**
973
+ // (`delay = uniform(base, min(cap, prev*3))`). Grows toward the cap when the
974
+ // contention persists (more spread across the herd as the tail hangs on), but
975
+ // bounded above by `cap`. The alternative \u2014 flat `uniform(0, base)` \u2014 was
976
+ // considered and rejected: it does not adapt to a long tail, so the last N legs
977
+ // still collide at similar rates once the herd shrinks; the decorrelated growth
978
+ // is what makes the tail DRAIN under sustained parallel load. Same shape as the
979
+ // recovery-rebase jitter loop in `integration-core.ts` uses (`Math.floor(random
980
+ // * (jitterMs + 1))` on a fixed cap) but with the AWS growth term so a lifecycle
981
+ // tail is not stuck on a tiny fixed window.
982
+ //
983
+ // **Composes with, does NOT duplicate, the held-lock-subtraction sibling**
984
+ // (`advance-matrix-enumerates-held-locked-items-so-legs-fail-every-tick`): that
985
+ // task reduces the NUMBER of legs entering the matrix (never enumerate a held
986
+ // item); this task handles the RESIDUAL legitimate contention among the valid
987
+ // legs that DO get scheduled. The two layers together should quiet the propose-
988
+ // matrix CI noise.