cloving 1.3.0 → 1.5.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 (484) hide show
  1. package/README.md +329 -215
  2. package/dist/src/agent-harness-shell.d.ts +2 -1
  3. package/dist/src/agent-harness-shell.js +8 -5
  4. package/dist/src/authority/command-wait-checks.d.ts +8 -0
  5. package/dist/src/authority/command-wait-checks.js +88 -0
  6. package/dist/src/authority/command-wait-results.d.ts +15 -0
  7. package/dist/src/authority/command-wait-results.js +73 -0
  8. package/dist/src/authority/command-wait-view.d.ts +21 -0
  9. package/dist/src/authority/command-wait-view.js +34 -0
  10. package/dist/src/authority/command-waits.d.ts +39 -0
  11. package/dist/src/authority/command-waits.js +97 -0
  12. package/dist/src/authority/commands.js +22 -1
  13. package/dist/src/authority/dispatch-decision.d.ts +8 -0
  14. package/dist/src/authority/dispatch-decision.js +26 -1
  15. package/dist/src/authority/effect-requests.js +6 -3
  16. package/dist/src/authority/effect-results.js +4 -2
  17. package/dist/src/authority/history-state.d.ts +2 -0
  18. package/dist/src/authority/history-state.js +13 -1
  19. package/dist/src/authority/instants.d.ts +1 -0
  20. package/dist/src/authority/instants.js +21 -0
  21. package/dist/src/authority/reduction-state.d.ts +2 -0
  22. package/dist/src/authority/reduction-state.js +5 -0
  23. package/dist/src/authority/reduction.js +24 -0
  24. package/dist/src/authority/status-input.d.ts +2 -0
  25. package/dist/src/authority/status.d.ts +2 -0
  26. package/dist/src/authority/status.js +1 -0
  27. package/dist/src/authority/stop-sequence.d.ts +1 -0
  28. package/dist/src/authority/stop-sequence.js +2 -1
  29. package/dist/src/authority/transitions.d.ts +6 -4
  30. package/dist/src/authority/transitions.js +7 -0
  31. package/dist/src/broker/actor/authority-health.d.ts +9 -0
  32. package/dist/src/broker/actor/authority-health.js +24 -0
  33. package/dist/src/broker/actor/authority-queries.d.ts +2 -1
  34. package/dist/src/broker/actor/authority-queries.js +14 -5
  35. package/dist/src/broker/actor/authority-receipts.js +1 -1
  36. package/dist/src/broker/actor/authority-runtime.d.ts +4 -2
  37. package/dist/src/broker/actor/authority-runtime.js +29 -20
  38. package/dist/src/broker/actor/authority-session.d.ts +6 -19
  39. package/dist/src/broker/actor/authority-session.js +66 -88
  40. package/dist/src/broker/actor/authority-settings.d.ts +2 -1
  41. package/dist/src/broker/actor/authority-settings.js +6 -3
  42. package/dist/src/broker/actor/authority-upgrades.d.ts +23 -2
  43. package/dist/src/broker/actor/authority-upgrades.js +8 -2
  44. package/dist/src/broker/actor/authority-watchdog.d.ts +6 -2
  45. package/dist/src/broker/actor/authority-watchdog.js +31 -9
  46. package/dist/src/broker/actor/authority.js +3 -1
  47. package/dist/src/broker/actor/delivery-intents.js +2 -0
  48. package/dist/src/broker/actor/effect-delivery.d.ts +1 -1
  49. package/dist/src/broker/actor/effect-delivery.js +15 -2
  50. package/dist/src/broker/actor/effect-plan.js +11 -2
  51. package/dist/src/broker/actor/effect-queries.d.ts +4 -0
  52. package/dist/src/broker/actor/effect-queries.js +9 -1
  53. package/dist/src/broker/actor/effect-turn.js +2 -0
  54. package/dist/src/broker/actor/projection-memory.d.ts +43 -0
  55. package/dist/src/broker/actor/projection-memory.js +207 -0
  56. package/dist/src/broker/actor/runtime-parts.d.ts +5 -0
  57. package/dist/src/broker/actor/runtime-parts.js +8 -3
  58. package/dist/src/broker/actor/stop-plan.js +9 -3
  59. package/dist/src/broker/actor/stop-turn.d.ts +2 -0
  60. package/dist/src/broker/actor/stop-turn.js +24 -11
  61. package/dist/src/broker/actor/turn-draft.d.ts +1 -0
  62. package/dist/src/broker/actor/turn-draft.js +4 -0
  63. package/dist/src/broker/actor/turn-state.js +5 -1
  64. package/dist/src/broker/authority-admission.js +60 -0
  65. package/dist/src/broker/authority-restore.d.ts +2 -0
  66. package/dist/src/broker/authority-restore.js +35 -1
  67. package/dist/src/broker/command-wait-restore.d.ts +31 -0
  68. package/dist/src/broker/command-wait-restore.js +86 -0
  69. package/dist/src/broker/decision-restore.d.ts +1 -1
  70. package/dist/src/broker/decision-restore.js +1 -0
  71. package/dist/src/broker/effect-upgrade.d.ts +3 -0
  72. package/dist/src/broker/effect-upgrade.js +11 -2
  73. package/dist/src/cli/arguments.d.ts +13 -0
  74. package/dist/src/cli/arguments.js +43 -3
  75. package/dist/src/cli/chat-stores.d.ts +2 -0
  76. package/dist/src/cli/chat-stores.js +113 -0
  77. package/dist/src/cli/client.d.ts +7 -2
  78. package/dist/src/cli/client.js +13 -7
  79. package/dist/src/cli/command-requests.d.ts +46 -0
  80. package/dist/src/cli/command-requests.js +84 -0
  81. package/dist/src/cli/command-waits.d.ts +9 -0
  82. package/dist/src/cli/command-waits.js +51 -0
  83. package/dist/src/cli/commands.js +70 -88
  84. package/dist/src/cli/config-file.d.ts +2 -2
  85. package/dist/src/cli/config-file.js +4 -2
  86. package/dist/src/cli/config-notice.js +5 -2
  87. package/dist/src/cli/doctor-retention.d.ts +2 -0
  88. package/dist/src/cli/doctor-retention.js +93 -0
  89. package/dist/src/cli/doctor-workflows.d.ts +5 -2
  90. package/dist/src/cli/doctor-workflows.js +12 -7
  91. package/dist/src/cli/doctor.js +11 -9
  92. package/dist/src/cli/harness-probes.d.ts +2 -0
  93. package/dist/src/cli/harness-probes.js +1 -1
  94. package/dist/src/cli/harness-processes.d.ts +6 -0
  95. package/dist/src/cli/harness-processes.js +36 -0
  96. package/dist/src/cli/init-command.d.ts +9 -0
  97. package/dist/src/cli/init-command.js +52 -0
  98. package/dist/src/cli/init-prompt-command.d.ts +10 -0
  99. package/dist/src/cli/init-prompt-command.js +145 -0
  100. package/dist/src/cli/init-prompt.d.ts +35 -0
  101. package/dist/src/cli/init-prompt.js +74 -0
  102. package/dist/src/cli/init-selection.d.ts +13 -0
  103. package/dist/src/cli/init-selection.js +75 -0
  104. package/dist/src/cli/main.d.ts +2 -0
  105. package/dist/src/cli/main.js +37 -7
  106. package/dist/src/cli/mcp-server.d.ts +1 -0
  107. package/dist/src/cli/mcp-server.js +156 -0
  108. package/dist/src/cli/mcp-tools.d.ts +15 -0
  109. package/dist/src/cli/mcp-tools.js +242 -0
  110. package/dist/src/cli/queue-arguments.d.ts +15 -0
  111. package/dist/src/cli/queue-arguments.js +31 -0
  112. package/dist/src/cli/queue-render.d.ts +3 -0
  113. package/dist/src/cli/queue-render.js +26 -0
  114. package/dist/src/cli/reload-render.d.ts +2 -0
  115. package/dist/src/cli/reload-render.js +12 -0
  116. package/dist/src/cli/render.d.ts +1 -1
  117. package/dist/src/cli/render.js +54 -38
  118. package/dist/src/cli/report-ci-jobs.d.ts +23 -0
  119. package/dist/src/cli/report-ci-jobs.js +103 -0
  120. package/dist/src/cli/report-ci-usage.d.ts +9 -0
  121. package/dist/src/cli/report-ci-usage.js +185 -0
  122. package/dist/src/cli/report-ci.d.ts +13 -0
  123. package/dist/src/cli/report-ci.js +74 -0
  124. package/dist/src/cli/report-closures.d.ts +6 -0
  125. package/dist/src/cli/report-closures.js +97 -0
  126. package/dist/src/cli/report-flow.d.ts +7 -0
  127. package/dist/src/cli/report-flow.js +122 -0
  128. package/dist/src/cli/report-github-reader.d.ts +29 -0
  129. package/dist/src/cli/report-github-reader.js +157 -0
  130. package/dist/src/cli/report-github.d.ts +15 -8
  131. package/dist/src/cli/report-github.js +131 -150
  132. package/dist/src/cli/report-operator.d.ts +3 -0
  133. package/dist/src/cli/report-operator.js +40 -0
  134. package/dist/src/cli/report-ready.d.ts +3 -0
  135. package/dist/src/cli/report-ready.js +96 -0
  136. package/dist/src/cli/report-text.d.ts +9 -0
  137. package/dist/src/cli/report-text.js +33 -0
  138. package/dist/src/cli/report.d.ts +1 -0
  139. package/dist/src/cli/report.js +104 -27
  140. package/dist/src/cli/runtime-arguments.d.ts +18 -3
  141. package/dist/src/cli/runtime-arguments.js +29 -20
  142. package/dist/src/cli/schedule-arguments.d.ts +14 -0
  143. package/dist/src/cli/schedule-arguments.js +26 -0
  144. package/dist/src/cli/schedule-render.d.ts +22 -0
  145. package/dist/src/cli/schedule-render.js +73 -0
  146. package/dist/src/cli/terminal-text.d.ts +3 -0
  147. package/dist/src/cli/terminal-text.js +28 -0
  148. package/dist/src/cli/toolchain.d.ts +1 -0
  149. package/dist/src/cli/toolchain.js +1 -0
  150. package/dist/src/cli/usage-render.d.ts +1 -0
  151. package/dist/src/cli/usage-render.js +28 -1
  152. package/dist/src/cli/usage.d.ts +2 -2
  153. package/dist/src/cli/usage.js +89 -11
  154. package/dist/src/cli/watch-actions.js +2 -2
  155. package/dist/src/cli/watch-api-document.d.ts +430 -0
  156. package/dist/src/cli/watch-api-document.js +291 -0
  157. package/dist/src/cli/watch-api-run.d.ts +56 -0
  158. package/dist/src/cli/watch-api-run.js +112 -0
  159. package/dist/src/cli/watch-api.d.ts +17 -0
  160. package/dist/src/cli/watch-api.js +230 -0
  161. package/dist/src/cli/watch-board-view.js +19 -2
  162. package/dist/src/cli/watch-board.d.ts +4 -2
  163. package/dist/src/cli/watch-board.js +53 -43
  164. package/dist/src/cli/watch-card-forms.d.ts +1 -1
  165. package/dist/src/cli/watch-card-forms.js +11 -10
  166. package/dist/src/cli/watch-client.d.ts +1 -1
  167. package/dist/src/cli/watch-client.js +2 -11
  168. package/dist/src/cli/watch-header-view.d.ts +1 -5
  169. package/dist/src/cli/watch-header-view.js +17 -21
  170. package/dist/src/cli/watch-model.d.ts +5 -1
  171. package/dist/src/cli/watch-model.js +20 -11
  172. package/dist/src/cli/watch-page.js +15 -15
  173. package/dist/src/cli/watch-phases.d.ts +12 -3
  174. package/dist/src/cli/watch-phases.js +68 -11
  175. package/dist/src/cli/watch-pr-board.js +1 -1
  176. package/dist/src/cli/watch-pr-command.d.ts +1 -0
  177. package/dist/src/cli/watch-pull-request.js +18 -8
  178. package/dist/src/cli/watch-reads.d.ts +3 -0
  179. package/dist/src/cli/watch-reads.js +22 -20
  180. package/dist/src/cli/watch-render.d.ts +15 -0
  181. package/dist/src/cli/watch-run-input.d.ts +0 -1
  182. package/dist/src/cli/watch-run-input.js +0 -13
  183. package/dist/src/cli/watch-server.js +24 -10
  184. package/dist/src/cli/watch-view.d.ts +0 -3
  185. package/dist/src/cli/watch-view.js +13 -13
  186. package/dist/src/cli.js +73 -12
  187. package/dist/src/command-wait.d.ts +4 -0
  188. package/dist/src/command-wait.js +45 -0
  189. package/dist/src/configuration-change.d.ts +7 -0
  190. package/dist/src/configuration-change.js +14 -0
  191. package/dist/src/configuration-display.js +2 -0
  192. package/dist/src/configuration-schema.d.ts +14 -0
  193. package/dist/src/configuration-schema.js +6 -2
  194. package/dist/src/daemon/attachment-file.d.ts +6 -0
  195. package/dist/src/daemon/attachment-file.js +86 -0
  196. package/dist/src/daemon/attachments.d.ts +14 -0
  197. package/dist/src/daemon/attachments.js +17 -0
  198. package/dist/src/daemon/claude-recovery.d.ts +50 -0
  199. package/dist/src/daemon/claude-recovery.js +208 -0
  200. package/dist/src/daemon/claude-stream.d.ts +27 -0
  201. package/dist/src/daemon/claude-stream.js +63 -0
  202. package/dist/src/daemon/claude-turn.d.ts +11 -25
  203. package/dist/src/daemon/claude-turn.js +64 -81
  204. package/dist/src/daemon/claude-worker.d.ts +5 -4
  205. package/dist/src/daemon/claude-worker.js +127 -126
  206. package/dist/src/daemon/daemon-adapters.d.ts +9 -0
  207. package/dist/src/daemon/daemon-adapters.js +33 -5
  208. package/dist/src/daemon/daemon-loops.d.ts +5 -1
  209. package/dist/src/daemon/daemon-loops.js +18 -10
  210. package/dist/src/daemon/daemon.js +80 -61
  211. package/dist/src/daemon/github-attach.d.ts +2 -0
  212. package/dist/src/daemon/github-attach.js +222 -0
  213. package/dist/src/daemon/github-calls.js +1 -1
  214. package/dist/src/daemon/github-ci-filters.d.ts +6 -0
  215. package/dist/src/daemon/github-ci-filters.js +138 -0
  216. package/dist/src/daemon/github-ci-workflows.d.ts +10 -0
  217. package/dist/src/daemon/github-ci-workflows.js +148 -0
  218. package/dist/src/daemon/github-ci.js +22 -4
  219. package/dist/src/daemon/github-issue-methods.d.ts +2 -0
  220. package/dist/src/daemon/github-issue-methods.js +37 -2
  221. package/dist/src/daemon/github-merge-call.d.ts +2 -1
  222. package/dist/src/daemon/github-merge-call.js +10 -3
  223. package/dist/src/daemon/github-merge.js +52 -24
  224. package/dist/src/daemon/github-methods.js +18 -4
  225. package/dist/src/daemon/github-operation.d.ts +4 -0
  226. package/dist/src/daemon/github-operation.js +6 -0
  227. package/dist/src/daemon/github-worker.d.ts +4 -0
  228. package/dist/src/daemon/github-worker.js +6 -0
  229. package/dist/src/daemon/issue-queue.d.ts +4 -1
  230. package/dist/src/daemon/issue-queue.js +76 -78
  231. package/dist/src/daemon/merge-turns.d.ts +10 -0
  232. package/dist/src/daemon/merge-turns.js +37 -0
  233. package/dist/src/daemon/notify-events.d.ts +6 -0
  234. package/dist/src/daemon/notify-events.js +84 -0
  235. package/dist/src/daemon/notify-journal.js +1 -1
  236. package/dist/src/daemon/notify-message.d.ts +4 -1
  237. package/dist/src/daemon/notify-message.js +36 -0
  238. package/dist/src/daemon/notify.d.ts +1 -1
  239. package/dist/src/daemon/notify.js +5 -77
  240. package/dist/src/daemon/operation-journal.d.ts +1 -0
  241. package/dist/src/daemon/queue-control.d.ts +13 -0
  242. package/dist/src/daemon/queue-control.js +86 -0
  243. package/dist/src/daemon/queue-journal.d.ts +20 -0
  244. package/dist/src/daemon/queue-journal.js +89 -0
  245. package/dist/src/daemon/queue-status.d.ts +9 -3
  246. package/dist/src/daemon/queue-status.js +17 -4
  247. package/dist/src/daemon/record-archive.d.ts +19 -0
  248. package/dist/src/daemon/record-archive.js +82 -0
  249. package/dist/src/daemon/record-sweep.d.ts +24 -0
  250. package/dist/src/daemon/record-sweep.js +147 -0
  251. package/dist/src/daemon/resume-index-lock.d.ts +13 -0
  252. package/dist/src/daemon/resume-index-lock.js +160 -0
  253. package/dist/src/daemon/resume-worktree.d.ts +7 -0
  254. package/dist/src/daemon/resume-worktree.js +29 -0
  255. package/dist/src/daemon/retrying-turn.d.ts +2 -12
  256. package/dist/src/daemon/retrying-turn.js +98 -172
  257. package/dist/src/daemon/schedule-control.d.ts +26 -0
  258. package/dist/src/daemon/schedule-control.js +110 -0
  259. package/dist/src/daemon/schedule-journal.d.ts +2 -15
  260. package/dist/src/daemon/schedule-journal.js +67 -4
  261. package/dist/src/daemon/settings-reload.d.ts +26 -0
  262. package/dist/src/daemon/settings-reload.js +82 -0
  263. package/dist/src/daemon/ticket-registration.d.ts +2 -7
  264. package/dist/src/daemon/ticket-registration.js +15 -81
  265. package/dist/src/daemon/turn-attempt-journal.d.ts +38 -0
  266. package/dist/src/daemon/turn-attempt-journal.js +136 -0
  267. package/dist/src/daemon/turn-attempts.d.ts +47 -0
  268. package/dist/src/daemon/turn-attempts.js +166 -0
  269. package/dist/src/daemon/turn-details.d.ts +4 -0
  270. package/dist/src/daemon/turn-details.js +33 -0
  271. package/dist/src/daemon/turn-report.d.ts +8 -1
  272. package/dist/src/daemon/turn-report.js +39 -0
  273. package/dist/src/daemon/work-tree.d.ts +9 -0
  274. package/dist/src/daemon/work-tree.js +20 -0
  275. package/dist/src/daemon/worker-profile.d.ts +9 -0
  276. package/dist/src/daemon/worker-profile.js +44 -0
  277. package/dist/src/daemon/workflow-generations.d.ts +7 -4
  278. package/dist/src/daemon/workflow-generations.js +22 -14
  279. package/dist/src/daemon/workflow-reload.d.ts +41 -0
  280. package/dist/src/daemon/workflow-reload.js +244 -0
  281. package/dist/src/daemon/workflow-replay.d.ts +2 -1
  282. package/dist/src/daemon/workflow-replay.js +7 -3
  283. package/dist/src/daemon/workflow-schedules.d.ts +3 -0
  284. package/dist/src/daemon/workflow-schedules.js +98 -45
  285. package/dist/src/daemon/worktree-processes.d.ts +9 -0
  286. package/dist/src/daemon/worktree-processes.js +151 -0
  287. package/dist/src/doctor-report.d.ts +4 -6
  288. package/dist/src/doctor-report.js +8 -13
  289. package/dist/src/harness-start.d.ts +15 -0
  290. package/dist/src/harness-start.js +37 -0
  291. package/dist/src/host/command-wait.d.ts +38 -0
  292. package/dist/src/host/command-wait.js +115 -0
  293. package/dist/src/host/start-delivery.d.ts +5 -7
  294. package/dist/src/host/start-delivery.js +3 -15
  295. package/dist/src/host/step-bridge-host.d.ts +2 -0
  296. package/dist/src/host/step-bridge-host.js +21 -0
  297. package/dist/src/operation-identity.d.ts +1 -0
  298. package/dist/src/operation-identity.js +4 -0
  299. package/dist/src/private-config.schema.json +71 -0
  300. package/dist/src/project-config.schema.json +40 -0
  301. package/dist/src/project-settings.d.ts +16 -2
  302. package/dist/src/project-settings.js +35 -1
  303. package/dist/src/queue-spend.d.ts +4 -0
  304. package/dist/src/queue-spend.js +21 -0
  305. package/dist/src/queue-state.d.ts +26 -0
  306. package/dist/src/queue-state.js +34 -0
  307. package/dist/src/record-retention.d.ts +46 -0
  308. package/dist/src/record-retention.js +81 -0
  309. package/dist/src/relay/relay-service.d.ts +3 -0
  310. package/dist/src/relay/relay-service.js +5 -3
  311. package/dist/src/report-ci.d.ts +164 -0
  312. package/dist/src/report-ci.js +32 -0
  313. package/dist/src/report-effects.d.ts +15 -0
  314. package/dist/src/report-effects.js +212 -0
  315. package/dist/src/report-flow-types.d.ts +38 -0
  316. package/dist/src/report-flow-types.js +7 -0
  317. package/dist/src/report-flow.d.ts +43 -0
  318. package/dist/src/report-flow.js +193 -0
  319. package/dist/src/report-operator.d.ts +67 -0
  320. package/dist/src/report-operator.js +25 -0
  321. package/dist/src/report-quality.d.ts +221 -0
  322. package/dist/src/report-quality.js +94 -0
  323. package/dist/src/report-ready.d.ts +23 -0
  324. package/dist/src/report-ready.js +117 -0
  325. package/dist/src/report-statistics.d.ts +2 -1
  326. package/dist/src/report-statistics.js +40 -4
  327. package/dist/src/report.d.ts +588 -47
  328. package/dist/src/report.js +156 -41
  329. package/dist/src/schedule-progress.d.ts +33 -0
  330. package/dist/src/schedule-progress.js +45 -0
  331. package/dist/src/service/active-tickets.js +11 -6
  332. package/dist/src/service/command-waits.d.ts +34 -0
  333. package/dist/src/service/command-waits.js +86 -0
  334. package/dist/src/service/dispatcher.d.ts +4 -0
  335. package/dist/src/service/dispatcher.js +33 -0
  336. package/dist/src/service/expiry.js +2 -0
  337. package/dist/src/service/gateway.js +3 -1
  338. package/dist/src/service/queue-handlers.d.ts +4 -0
  339. package/dist/src/service/queue-handlers.js +32 -0
  340. package/dist/src/service/queue-spend.d.ts +6 -0
  341. package/dist/src/service/queue-spend.js +68 -0
  342. package/dist/src/service/registry.d.ts +28 -2
  343. package/dist/src/service/registry.js +48 -0
  344. package/dist/src/service/replies.d.ts +22 -8
  345. package/dist/src/service/replies.js +16 -2
  346. package/dist/src/service/run-handlers.js +37 -12
  347. package/dist/src/service/run-metadata.d.ts +15 -1
  348. package/dist/src/service/run-metadata.js +93 -8
  349. package/dist/src/service/service.d.ts +11 -3
  350. package/dist/src/service/service.js +22 -37
  351. package/dist/src/service/submission-handler.js +18 -4
  352. package/dist/src/service/worker-port.js +9 -1
  353. package/dist/src/service/worker-totals.d.ts +6 -0
  354. package/dist/src/service/worker-totals.js +8 -1
  355. package/dist/src/service/workflow-reload.d.ts +35 -0
  356. package/dist/src/service/workflow-reload.js +52 -0
  357. package/dist/src/step-bridge.d.ts +20 -0
  358. package/dist/src/steps/command-wait.d.ts +12 -0
  359. package/dist/src/steps/command-wait.js +26 -0
  360. package/dist/src/steps/open-decision-wait.d.ts +2 -0
  361. package/dist/src/steps/{decision-gate.js → open-decision-wait.js} +0 -7
  362. package/dist/src/steps/review-by-person.js +4 -1
  363. package/dist/src/steps/wait-for-command.d.ts +3 -0
  364. package/dist/src/steps/wait-for-command.js +17 -0
  365. package/dist/src/steps/wait-for-decision.js +1 -1
  366. package/dist/src/storage/command-wait-rows.d.ts +30 -0
  367. package/dist/src/storage/command-wait-rows.js +96 -0
  368. package/dist/src/storage/decision-snapshots.d.ts +8 -2
  369. package/dist/src/storage/decision-snapshots.js +16 -8
  370. package/dist/src/storage/domain-changes.d.ts +3 -1
  371. package/dist/src/storage/domain-changes.js +4 -0
  372. package/dist/src/storage/domain-schema.d.ts +16 -1
  373. package/dist/src/storage/domain-schema.js +35 -4
  374. package/dist/src/storage/effect-schema.d.ts +2 -0
  375. package/dist/src/storage/effect-schema.js +9 -0
  376. package/dist/src/storage/projection-changes.d.ts +12 -0
  377. package/dist/src/storage/projection-changes.js +194 -0
  378. package/dist/src/storage/projection-lookups.d.ts +7 -0
  379. package/dist/src/storage/projection-lookups.js +30 -0
  380. package/dist/src/testing-requests.d.ts +15 -0
  381. package/dist/src/testing-requests.js +93 -0
  382. package/dist/src/testing.d.ts +4 -10
  383. package/dist/src/testing.js +33 -86
  384. package/dist/src/worker-usage.d.ts +1 -0
  385. package/dist/src/worker-usage.js +3 -0
  386. package/dist/src/workflow-candidate.d.ts +1 -0
  387. package/dist/src/workflow-candidate.js +12 -1
  388. package/dist/src/workflow-contracts.d.ts +1 -0
  389. package/dist/src/workflow-contracts.js +9 -2
  390. package/dist/src/workflow-declaration.d.ts +1 -0
  391. package/dist/src/workflow-declaration.js +3 -0
  392. package/dist/src/workflow-discovery.d.ts +2 -13
  393. package/dist/src/workflow-discovery.js +1 -13
  394. package/dist/src/workflow-refusals.js +0 -1
  395. package/dist/src/workflows.d.ts +1 -1
  396. package/dist/src/workflows.js +1 -1
  397. package/dist/tools/compile-workflows.js +3 -0
  398. package/dist/tools/project-configuration.d.ts +7 -2
  399. package/dist/tools/project-configuration.js +30 -1
  400. package/dist/tools/red-green-command.d.ts +1 -0
  401. package/dist/tools/red-green-command.js +174 -0
  402. package/dist/tools/red-green-process.d.ts +10 -0
  403. package/dist/tools/red-green-process.js +59 -0
  404. package/dist/tools/self-host-processes.d.ts +2 -0
  405. package/dist/tools/self-host-processes.js +69 -26
  406. package/dist/tools/self-host-project.d.ts +2 -0
  407. package/dist/tools/self-host-project.js +30 -77
  408. package/dist/tools/self-host-runtime.js +1 -1
  409. package/dist/tools/self-host.js +76 -34
  410. package/dist/tools/template-command.d.ts +1 -0
  411. package/dist/tools/template-command.js +160 -0
  412. package/dist/tools/template-copy.d.ts +11 -0
  413. package/dist/tools/template-copy.js +130 -0
  414. package/dist/tools/template-files.d.ts +17 -0
  415. package/dist/tools/template-files.js +74 -0
  416. package/dist/tools/workflow-bundles.js +1 -0
  417. package/dist/tools/workflow-candidate-counts.js +2 -2
  418. package/dist/tools/workflow-candidate-program.d.ts +4 -2
  419. package/dist/tools/workflow-candidate-program.js +30 -11
  420. package/dist/tools/workflow-candidate-shape.d.ts +3 -0
  421. package/dist/tools/workflow-candidate-shape.js +90 -0
  422. package/dist/tools/workflow-candidate-source.d.ts +1 -0
  423. package/dist/tools/workflow-candidate-source.js +26 -4
  424. package/dist/tools/workflow-candidate-syntax.d.ts +2 -2
  425. package/dist/tools/workflow-candidate-syntax.js +24 -94
  426. package/dist/tools/workflow-command.js +1 -2
  427. package/dist/tools/workflow-folders.d.ts +2 -3
  428. package/dist/tools/workflow-folders.js +1 -1
  429. package/package.json +17 -9
  430. package/templates/develop/README.md +93 -4
  431. package/templates/develop/develop/flow-tests.ts +38 -1
  432. package/templates/develop/develop/interruption-tests.ts +157 -0
  433. package/templates/develop/develop/local.ts +1 -0
  434. package/templates/develop/develop/option-tests.ts +138 -9
  435. package/templates/develop/develop/pull.ts +5 -1
  436. package/templates/develop/develop/red-green.ts +55 -0
  437. package/templates/develop/develop/refusal-tests.ts +77 -0
  438. package/templates/develop/develop/report.ts +31 -11
  439. package/templates/develop/develop/round.ts +1 -0
  440. package/templates/develop/develop/scenarios.ts +27 -0
  441. package/templates/develop/develop/turn.ts +28 -7
  442. package/templates/develop/develop.declaration.json +1 -1
  443. package/templates/develop/develop.test.ts +4 -0
  444. package/templates/develop/template.json +29 -2
  445. package/templates/idea-to-issue/README.md +27 -0
  446. package/templates/idea-to-issue/idea-to-issue/effects.ts +52 -0
  447. package/templates/idea-to-issue/idea-to-issue/leave.ts +10 -0
  448. package/templates/idea-to-issue/idea-to-issue/next.ts +29 -0
  449. package/templates/idea-to-issue/idea-to-issue/post-person.ts +18 -0
  450. package/templates/idea-to-issue/idea-to-issue/post.ts +50 -0
  451. package/templates/idea-to-issue/idea-to-issue/queue.ts +27 -0
  452. package/templates/idea-to-issue/idea-to-issue/review.ts +42 -0
  453. package/templates/idea-to-issue/idea-to-issue/scenarios.ts +50 -0
  454. package/templates/idea-to-issue/idea-to-issue/turn.ts +40 -0
  455. package/templates/idea-to-issue/idea-to-issue.declaration.json +16 -0
  456. package/templates/idea-to-issue/idea-to-issue.test.ts +79 -0
  457. package/templates/idea-to-issue/idea-to-issue.ts +49 -0
  458. package/templates/idea-to-issue/template.json +81 -0
  459. package/templates/review-day/README.md +97 -0
  460. package/templates/review-day/review-day/critique.ts +58 -0
  461. package/templates/review-day/review-day/decision.ts +29 -0
  462. package/templates/review-day/review-day/facts.ts +69 -0
  463. package/templates/review-day/review-day/label.ts +29 -0
  464. package/templates/review-day/review-day/next.ts +28 -0
  465. package/templates/review-day/review-day/post.ts +27 -0
  466. package/templates/review-day/review-day/report.ts +98 -0
  467. package/templates/review-day/review-day/rules.ts +12 -0
  468. package/templates/review-day/review-day/scenarios.ts +76 -0
  469. package/templates/review-day/review-day/turn.ts +73 -0
  470. package/templates/review-day/review-day.declaration.json +16 -0
  471. package/templates/review-day/review-day.test.ts +362 -0
  472. package/templates/review-day/review-day.ts +37 -0
  473. package/templates/review-day/template.json +56 -0
  474. package/dist/src/cli/watch-start-view.d.ts +0 -18
  475. package/dist/src/cli/watch-start-view.js +0 -77
  476. package/dist/src/steps/decision-gate.d.ts +0 -3
  477. package/dist/workflows/develop-prd.d.ts +0 -7
  478. package/dist/workflows/develop-prd.js +0 -165
  479. package/dist/workflows/develop-ticket.d.ts +0 -9
  480. package/dist/workflows/develop-ticket.js +0 -207
  481. package/dist/workflows/issue-labels.d.ts +0 -16
  482. package/dist/workflows/issue-labels.js +0 -70
  483. package/dist/workflows/ticket-prompts.d.ts +0 -35
  484. package/dist/workflows/ticket-prompts.js +0 -147
@@ -0,0 +1,20 @@
1
+ import { type QueueCommand, type QueueDrain } from "../queue-state.ts";
2
+ export declare const queueControlLimit = 32;
3
+ export interface QueueControlRecord {
4
+ readonly commandId: string;
5
+ readonly command: QueueCommand;
6
+ }
7
+ export interface SelfUpdateHold {
8
+ readonly mark: string;
9
+ readonly startedAt: number;
10
+ }
11
+ export interface QueueJournal {
12
+ readonly commands: Set<string>;
13
+ readonly failures: string[];
14
+ readonly controls: QueueControlRecord[];
15
+ readonly save: () => Promise<void>;
16
+ retryCommandId: string | undefined;
17
+ selfUpdateHold: SelfUpdateHold | undefined;
18
+ drain: QueueDrain | undefined;
19
+ }
20
+ export declare function queueJournal(directory: string): Promise<QueueJournal>;
@@ -0,0 +1,89 @@
1
+ import { readFile, rename, writeFile } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { record } from "../protocol.js";
4
+ import { queueFailureLimit, } from "../queue-state.js";
5
+ export const queueControlLimit = 32;
6
+ function savedHold(value) {
7
+ if (value === undefined)
8
+ return undefined;
9
+ const held = record(value);
10
+ if (typeof held?.["mark"] !== "string" ||
11
+ typeof held["startedAt"] !== "number" ||
12
+ !Number.isFinite(held["startedAt"]) ||
13
+ held["startedAt"] <= 0)
14
+ throw new Error("invalid-queue-commands");
15
+ return { mark: held["mark"], startedAt: held["startedAt"] };
16
+ }
17
+ function instant(value) {
18
+ return typeof value === "number" && Number.isFinite(value) && value > 0;
19
+ }
20
+ function savedDrain(value) {
21
+ if (value === undefined)
22
+ return undefined;
23
+ const drain = record(value);
24
+ const drainedAt = drain?.["drainedAt"];
25
+ if (!instant(drain?.["startedAt"]) ||
26
+ !(drainedAt === null || instant(drainedAt)))
27
+ throw new Error("invalid-queue-commands");
28
+ return { startedAt: drain["startedAt"], drainedAt };
29
+ }
30
+ function savedControls(value) {
31
+ if (value === undefined)
32
+ return [];
33
+ if (!Array.isArray(value) || value.length > queueControlLimit)
34
+ throw new Error("invalid-queue-commands");
35
+ return value.map((entry) => {
36
+ const control = record(entry);
37
+ const commandId = control?.["commandId"];
38
+ const command = control?.["command"];
39
+ if (typeof commandId !== "string" ||
40
+ commandId.length === 0 ||
41
+ (command !== "resume" && command !== "drain"))
42
+ throw new Error("invalid-queue-commands");
43
+ return { commandId, command };
44
+ });
45
+ }
46
+ function identifiers(value) {
47
+ if (!Array.isArray(value) ||
48
+ !value.every((id) => typeof id === "string" && id.length > 0))
49
+ throw new Error("invalid-queue-commands");
50
+ return value.map(String);
51
+ }
52
+ export async function queueJournal(directory) {
53
+ const file = join(directory, "queue-commands.json");
54
+ const text = await readFile(file, "utf8").catch((error) => {
55
+ if (error instanceof Error && "code" in error && error.code === "ENOENT")
56
+ return "[]";
57
+ throw error;
58
+ });
59
+ const value = JSON.parse(text);
60
+ const saved = record(value);
61
+ const commands = new Set(identifiers(Array.isArray(value) ? value : saved?.["commands"]));
62
+ const failures = Array.isArray(value) ? [] : identifiers(saved?.["failures"]);
63
+ if (failures.length > queueFailureLimit)
64
+ throw new Error("invalid-queue-commands");
65
+ let saving = Promise.resolve();
66
+ const write = async () => {
67
+ await writeFile(`${file}.tmp`, JSON.stringify({
68
+ commands: [...commands],
69
+ failures,
70
+ selfUpdateHold: pending.selfUpdateHold,
71
+ drain: pending.drain,
72
+ controls: pending.controls.length === 0 ? undefined : pending.controls,
73
+ }) + "\n", { mode: 0o600, flush: true });
74
+ await rename(`${file}.tmp`, file);
75
+ };
76
+ const pending = {
77
+ commands,
78
+ failures,
79
+ controls: savedControls(saved?.["controls"]),
80
+ retryCommandId: undefined,
81
+ selfUpdateHold: savedHold(saved?.["selfUpdateHold"]),
82
+ drain: savedDrain(saved?.["drain"]),
83
+ save: () => {
84
+ saving = saving.then(write, write);
85
+ return saving;
86
+ },
87
+ };
88
+ return pending;
89
+ }
@@ -1,4 +1,10 @@
1
- export declare function activeQueueRuns(settings: {
1
+ export declare function queueStatus(settings: {
2
2
  readonly runtimeDirectory: string;
3
- readonly workflowId?: string;
4
- }): Promise<number>;
3
+ readonly workflowId: string;
4
+ }): Promise<{
5
+ readonly busy: number;
6
+ readonly holding: boolean;
7
+ }>;
8
+ export declare function spendNotice(): (status: {
9
+ readonly holding: boolean;
10
+ }) => boolean;
@@ -8,7 +8,7 @@ function queueBusy(value) {
8
8
  throw new Error("invalid-queue-status");
9
9
  return busy;
10
10
  }
11
- export async function activeQueueRuns(settings) {
11
+ export async function queueStatus(settings) {
12
12
  let count = 0;
13
13
  let after = "";
14
14
  do {
@@ -20,13 +20,17 @@ export async function activeQueueRuns(settings) {
20
20
  throw new Error(reply.error.code);
21
21
  const busy = queueBusy(reply.result["queue"]);
22
22
  if (busy !== undefined)
23
- return busy;
23
+ return {
24
+ busy,
25
+ holding: record(record(reply.result["queue"])?.["spend"])?.["holding"] ===
26
+ true,
27
+ };
24
28
  const runs = reply.result["runs"];
25
29
  if (!Array.isArray(runs))
26
30
  throw new Error("invalid-run-list");
27
31
  count += runs.filter((value) => {
28
32
  const run = record(value);
29
- return (run?.["workflowId"] === (settings.workflowId ?? "develop-issue") &&
33
+ return (run?.["workflowId"] === settings.workflowId &&
30
34
  !["completed", "failed", "cancelled"].includes(String(run["state"])));
31
35
  }).length;
32
36
  const next = reply.result["next"];
@@ -34,5 +38,14 @@ export async function activeQueueRuns(settings) {
34
38
  throw new Error("invalid-run-page");
35
39
  after = next;
36
40
  } while (after !== null);
37
- return count;
41
+ return { busy: count, holding: false };
42
+ }
43
+ export function spendNotice() {
44
+ let held = false;
45
+ return ({ holding }) => {
46
+ if (holding && !held)
47
+ process.stderr.write("Queue holding for spend limit\n");
48
+ held = holding;
49
+ return holding;
50
+ };
38
51
  }
@@ -0,0 +1,19 @@
1
+ export interface RecordFiles {
2
+ readonly move: (source: string, target: string) => Promise<void>;
3
+ readonly copy: (source: string, target: string) => Promise<void>;
4
+ }
5
+ export interface StoredFile {
6
+ readonly path: string;
7
+ readonly size: number;
8
+ readonly changedAt: number;
9
+ }
10
+ export declare const nodeFiles: RecordFiles;
11
+ export declare function failureReason(error: unknown): string;
12
+ export declare function storedFiles(folder: string): Promise<StoredFile[]>;
13
+ export declare function archiveReady(folder: string, settings: {
14
+ readonly configured: boolean;
15
+ }): Promise<string | undefined>;
16
+ export declare function archivedFolder(place: {
17
+ readonly source: string;
18
+ readonly target: string;
19
+ }, files: RecordFiles): Promise<void>;
@@ -0,0 +1,82 @@
1
+ import { constants } from "node:fs";
2
+ import { access, cp, lstat, mkdir, readdir, rename, rm, stat, } from "node:fs/promises";
3
+ import { dirname, join, relative } from "node:path";
4
+ export const nodeFiles = {
5
+ move: rename,
6
+ copy: (source, target) => cp(source, target, {
7
+ recursive: true,
8
+ errorOnExist: true,
9
+ force: false,
10
+ preserveTimestamps: true,
11
+ }),
12
+ };
13
+ export function failureReason(error) {
14
+ if (typeof error === "object" &&
15
+ error !== null &&
16
+ "code" in error &&
17
+ typeof error.code === "string")
18
+ return error.code;
19
+ return error instanceof Error ? error.message : String(error);
20
+ }
21
+ export async function storedFiles(folder) {
22
+ const found = [];
23
+ for (const entry of await readdir(folder, {
24
+ withFileTypes: true,
25
+ recursive: true,
26
+ })) {
27
+ if (!entry.isFile())
28
+ continue;
29
+ const path = join(entry.parentPath, entry.name);
30
+ const file = await lstat(path);
31
+ found.push({
32
+ path: relative(folder, path),
33
+ size: file.size,
34
+ changedAt: file.mtimeMs,
35
+ });
36
+ }
37
+ return found;
38
+ }
39
+ export async function archiveReady(folder, settings) {
40
+ try {
41
+ if (!settings.configured)
42
+ await mkdir(folder, { recursive: true });
43
+ if (!(await stat(folder)).isDirectory())
44
+ return "not-a-folder";
45
+ await access(folder, constants.W_OK);
46
+ return undefined;
47
+ }
48
+ catch (error) {
49
+ return failureReason(error);
50
+ }
51
+ }
52
+ async function sameFiles(source, copy) {
53
+ const listed = async (folder) => JSON.stringify((await storedFiles(folder)).map((file) => [file.path, file.size]).sort());
54
+ return (await listed(source)) === (await listed(copy));
55
+ }
56
+ async function copied(place, files) {
57
+ const staged = `${place.target}.staged`;
58
+ await rm(staged, { recursive: true, force: true });
59
+ try {
60
+ await files.copy(place.source, staged);
61
+ if (!(await sameFiles(place.source, staged)))
62
+ throw new Error("archive-copy-incomplete");
63
+ await rename(staged, place.target);
64
+ }
65
+ catch (error) {
66
+ await rm(staged, { recursive: true, force: true });
67
+ throw error;
68
+ }
69
+ await rm(place.source, { recursive: true, force: true });
70
+ }
71
+ export async function archivedFolder(place, files) {
72
+ await mkdir(dirname(place.target), { recursive: true });
73
+ try {
74
+ await files.move(place.source, place.target);
75
+ return;
76
+ }
77
+ catch (error) {
78
+ if (failureReason(error) !== "EXDEV")
79
+ throw error;
80
+ }
81
+ await copied(place, files);
82
+ }
@@ -0,0 +1,24 @@
1
+ import { type RecordFiles } from "./record-archive.ts";
2
+ import { type RetentionSettings, type StoredRun } from "../record-retention.ts";
3
+ import type { HostPorts } from "../service/service.ts";
4
+ export interface SweepSettings {
5
+ readonly directory: string;
6
+ readonly retention: RetentionSettings;
7
+ readonly runs: (runIds: readonly string[]) => Promise<Readonly<Record<string, StoredRun>>>;
8
+ readonly now: () => number;
9
+ readonly log: (line: string) => void;
10
+ readonly files?: RecordFiles;
11
+ }
12
+ export interface RecordSweep {
13
+ readonly pass: () => Promise<void>;
14
+ }
15
+ export interface RetentionLoop {
16
+ readonly stop: () => Promise<void>;
17
+ }
18
+ export declare const retentionIntervalMilliseconds = 3600000;
19
+ export declare function archiveFolder(settings: Pick<SweepSettings, "directory" | "retention">): string;
20
+ export declare function recordSweep(settings: SweepSettings): RecordSweep;
21
+ export declare function recordRetention(settings: SweepSettings & {
22
+ readonly intervalMilliseconds?: number;
23
+ }): Promise<RetentionLoop>;
24
+ export declare function authorityRuns(ports: Pick<HostPorts, "runWait">): SweepSettings["runs"];
@@ -0,0 +1,147 @@
1
+ import { readdir, readFile, rm } from "node:fs/promises";
2
+ import { join } from "node:path";
3
+ import { archivedFolder, archiveReady, failureReason, nodeFiles, storedFiles, } from "./record-archive.js";
4
+ import { folderRunId, retentionVerdict, sharedRecordFolders, } from "../record-retention.js";
5
+ export const retentionIntervalMilliseconds = 3_600_000;
6
+ const kinds = ["github", "journal"];
7
+ const batchSize = 200;
8
+ async function folders(directory) {
9
+ const found = [];
10
+ for (const kind of kinds)
11
+ for (const entry of await readdir(join(directory, kind), {
12
+ withFileTypes: true,
13
+ }).catch(() => []))
14
+ if (entry.isDirectory())
15
+ found.push(`${kind}/${entry.name}`);
16
+ return found.filter((path) => !sharedRecordFolders.includes(path)).sort();
17
+ }
18
+ async function newestChange(folder) {
19
+ return Math.max(Number.NEGATIVE_INFINITY, ...(await storedFiles(folder)).map((file) => file.changedAt));
20
+ }
21
+ async function startedRecord(folder) {
22
+ try {
23
+ const value = JSON.parse(await readFile(join(folder, "started.json"), "utf8"));
24
+ return value;
25
+ }
26
+ catch {
27
+ return undefined;
28
+ }
29
+ }
30
+ async function examined(directory, path) {
31
+ const folder = join(directory, path);
32
+ try {
33
+ return {
34
+ path,
35
+ started: await startedRecord(folder),
36
+ changedAt: await newestChange(folder),
37
+ };
38
+ }
39
+ catch {
40
+ return { path, started: undefined, changedAt: Number.NaN };
41
+ }
42
+ }
43
+ export function archiveFolder(settings) {
44
+ return (settings.retention.archiveFolder ?? join(settings.directory, "archive"));
45
+ }
46
+ async function removed(settings, path) {
47
+ const source = join(settings.directory, path);
48
+ if (settings.retention.action === "delete") {
49
+ await rm(source, { recursive: true, force: true });
50
+ return;
51
+ }
52
+ await archivedFolder({ source, target: join(archiveFolder(settings), path) }, settings.files ?? nodeFiles);
53
+ }
54
+ function countLine(action, tally) {
55
+ const { counts, failures } = tally;
56
+ const moved = action === "delete" ? "deleted" : "archived";
57
+ const failed = failures.length === 0
58
+ ? ""
59
+ : `, failed ${String(failures.length)} (${failures[0] ?? ""})`;
60
+ return `retention: ${moved} ${String(counts.due)}, kept ${String(counts.kept)}, skipped ${String(counts.skipped)} (active runs), unreadable ${String(counts.unreadable)}${failed}\n`;
61
+ }
62
+ async function unavailable(settings) {
63
+ if (settings.retention.action === "delete")
64
+ return false;
65
+ const reason = await archiveReady(archiveFolder(settings), {
66
+ configured: settings.retention.archiveFolder !== null,
67
+ });
68
+ if (reason === undefined)
69
+ return false;
70
+ settings.log(`retention: archive folder not available (${reason}), moved nothing\n`);
71
+ return true;
72
+ }
73
+ async function handled(settings, folder) {
74
+ try {
75
+ await removed(settings, folder.path);
76
+ folder.tally.counts.due += 1;
77
+ }
78
+ catch (error) {
79
+ folder.tally.failures.push(`${folder.path}: ${failureReason(error)}`);
80
+ }
81
+ }
82
+ async function nextBatch(sweep) {
83
+ const found = await folders(sweep.settings.directory);
84
+ const rest = found.filter((path) => path > sweep.cursor);
85
+ const batch = (rest.length === 0 ? found : rest).slice(0, batchSize);
86
+ sweep.cursor = batch.at(-1) ?? "";
87
+ return batch;
88
+ }
89
+ async function swept(sweep) {
90
+ const { settings } = sweep;
91
+ const days = settings.retention.days;
92
+ if (days === null || (await unavailable(settings)))
93
+ return;
94
+ const batch = await Promise.all((await nextBatch(sweep)).map((path) => examined(settings.directory, path)));
95
+ const runIds = [
96
+ ...new Set(batch.flatMap((folder) => folderRunId(folder.started) ?? [])),
97
+ ];
98
+ const runs = await settings.runs(runIds);
99
+ const now = settings.now();
100
+ const tally = {
101
+ counts: { due: 0, kept: 0, skipped: 0, unreadable: 0 },
102
+ failures: [],
103
+ };
104
+ for (const folder of batch) {
105
+ const verdict = retentionVerdict(folder, { runs, now, days });
106
+ if (verdict === "due")
107
+ await handled(settings, { path: folder.path, tally });
108
+ else
109
+ tally.counts[verdict] += 1;
110
+ }
111
+ settings.log(countLine(settings.retention.action, tally));
112
+ }
113
+ export function recordSweep(settings) {
114
+ const sweep = { settings, cursor: "" };
115
+ return {
116
+ pass: () => swept(sweep).catch((error) => {
117
+ settings.log(`retention: failed (${failureReason(error)})\n`);
118
+ }),
119
+ };
120
+ }
121
+ export async function recordRetention(settings) {
122
+ const sweep = recordSweep(settings);
123
+ await sweep.pass();
124
+ let running = Promise.resolve();
125
+ const timer = setInterval(() => {
126
+ running = running.then(sweep.pass);
127
+ }, settings.intervalMilliseconds ?? retentionIntervalMilliseconds);
128
+ timer.unref();
129
+ return {
130
+ stop: async () => {
131
+ clearInterval(timer);
132
+ await running;
133
+ },
134
+ };
135
+ }
136
+ export function authorityRuns(ports) {
137
+ return async (runIds) => {
138
+ const found = [];
139
+ for (const runId of runIds) {
140
+ const target = await ports.runWait?.target(runId);
141
+ if (target === undefined)
142
+ continue;
143
+ found.push([runId, { status: target.status, endedAt: target.endedAt }]);
144
+ }
145
+ return Object.fromEntries(found);
146
+ };
147
+ }
@@ -0,0 +1,13 @@
1
+ interface LockIdentity {
2
+ readonly path: string;
3
+ readonly dev: number;
4
+ readonly ino: number;
5
+ readonly ctimeMs: number;
6
+ readonly mtimeMs: number;
7
+ }
8
+ export interface OwnedIndexLock extends LockIdentity {
9
+ readonly processIds: readonly number[];
10
+ }
11
+ export declare function ownedIndexLock(path: string): Promise<OwnedIndexLock | undefined>;
12
+ export declare function removeStoppedIndexLock(lock: OwnedIndexLock, signal: AbortSignal): Promise<boolean>;
13
+ export {};
@@ -0,0 +1,160 @@
1
+ import { execFile } from "node:child_process";
2
+ import { lstat, readdir, readFile, stat, unlink } from "node:fs/promises";
3
+ import { promisify } from "node:util";
4
+ const execute = promisify(execFile);
5
+ function disappeared(error) {
6
+ return (error instanceof Error &&
7
+ "code" in error &&
8
+ (error.code === "ENOENT" || error.code === "ESRCH"));
9
+ }
10
+ async function descriptorUsesLock(path, lock) {
11
+ try {
12
+ const opened = await stat(path);
13
+ if (opened.dev !== lock.dev || opened.ino !== lock.ino)
14
+ return false;
15
+ const information = await readFile(path.replace("/fd/", "/fdinfo/"), "utf8");
16
+ const flags = /^flags:\s+([0-7]+)$/m.exec(information)?.[1];
17
+ const inode = /^ino:\s+(\d+)$/m.exec(information)?.[1];
18
+ if (flags === undefined || Number(inode) !== lock.ino)
19
+ return undefined;
20
+ return (Number.parseInt(flags, 8) & 3) !== 0;
21
+ }
22
+ catch (error) {
23
+ return disappeared(error) ? false : undefined;
24
+ }
25
+ }
26
+ async function processUsesLock(directory, lock) {
27
+ try {
28
+ for (const descriptor of await readdir(`${directory}/fd`)) {
29
+ const used = await descriptorUsesLock(`${directory}/fd/${descriptor}`, lock);
30
+ if (used !== false)
31
+ return used;
32
+ }
33
+ return false;
34
+ }
35
+ catch (error) {
36
+ if (disappeared(error))
37
+ return false;
38
+ if (!(error instanceof Error) ||
39
+ !("code" in error) ||
40
+ (error.code !== "EACCES" && error.code !== "EPERM"))
41
+ return undefined;
42
+ return (await unrelatedProcess(directory, lock)) ? false : undefined;
43
+ }
44
+ }
45
+ async function unrelatedProcess(directory, lock) {
46
+ if (lock.uid !== process.geteuid?.() || (lock.mode & 0o022) !== 0)
47
+ return false;
48
+ try {
49
+ const status = await readFile(`${directory}/status`, "utf8");
50
+ const users = /^Uid:\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)$/m.exec(status);
51
+ return (users !== null &&
52
+ users
53
+ .slice(1)
54
+ .map(Number)
55
+ .every((user) => Number.isSafeInteger(user) && user !== lock.uid));
56
+ }
57
+ catch {
58
+ return false;
59
+ }
60
+ }
61
+ async function lockOwners(lock) {
62
+ if (process.platform === "linux") {
63
+ const processIds = [];
64
+ for (const entry of await readdir("/proc")) {
65
+ if (!/^\d+$/.test(entry))
66
+ continue;
67
+ const used = await processUsesLock(`/proc/${entry}`, lock);
68
+ if (used === undefined)
69
+ return undefined;
70
+ if (used)
71
+ processIds.push(Number(entry));
72
+ }
73
+ return processIds;
74
+ }
75
+ try {
76
+ const { stdout, stderr } = await execute("lsof", ["-Fpfa", "--", lock.path], {
77
+ encoding: "utf8",
78
+ timeout: 5000,
79
+ maxBuffer: 8 * 1024 * 1024,
80
+ });
81
+ return stderr === "" ? writableOwners(stdout) : undefined;
82
+ }
83
+ catch {
84
+ return undefined;
85
+ }
86
+ }
87
+ function writableOwner(record) {
88
+ const [header, ...descriptors] = record.trim().split(/^f/m);
89
+ const processId = Number(header?.trim().slice(1));
90
+ const access = descriptors.map((descriptor) => descriptor.split("\n")[1]);
91
+ if (!Number.isSafeInteger(processId) ||
92
+ processId < 1 ||
93
+ descriptors.length === 0 ||
94
+ access.some((mode) => mode !== "ar" && mode !== "aw" && mode !== "au"))
95
+ return undefined;
96
+ return access.some((mode) => mode === "aw" || mode === "au")
97
+ ? processId
98
+ : null;
99
+ }
100
+ function writableOwners(text) {
101
+ const owners = text
102
+ .trim()
103
+ .split(/(?=^p)/m)
104
+ .map(writableOwner);
105
+ return owners.includes(undefined)
106
+ ? undefined
107
+ : [
108
+ ...new Set(owners.filter((owner) => typeof owner === "number")),
109
+ ];
110
+ }
111
+ export async function ownedIndexLock(path) {
112
+ try {
113
+ const identity = await lstat(path);
114
+ if (!identity.isFile())
115
+ return undefined;
116
+ const lock = {
117
+ path,
118
+ dev: identity.dev,
119
+ ino: identity.ino,
120
+ ctimeMs: identity.ctimeMs,
121
+ mtimeMs: identity.mtimeMs,
122
+ uid: identity.uid,
123
+ mode: identity.mode,
124
+ };
125
+ const processIds = await lockOwners(lock);
126
+ return processIds === undefined || processIds.length === 0
127
+ ? undefined
128
+ : { ...lock, processIds };
129
+ }
130
+ catch {
131
+ return undefined;
132
+ }
133
+ }
134
+ function stopped(processId) {
135
+ try {
136
+ process.kill(processId, 0);
137
+ return false;
138
+ }
139
+ catch (error) {
140
+ return error instanceof Error && "code" in error && error.code === "ESRCH";
141
+ }
142
+ }
143
+ export async function removeStoppedIndexLock(lock, signal) {
144
+ try {
145
+ if (!lock.processIds.every(stopped))
146
+ return false;
147
+ const after = await lstat(lock.path);
148
+ if (signal.aborted ||
149
+ lock.dev !== after.dev ||
150
+ lock.ino !== after.ino ||
151
+ lock.ctimeMs !== after.ctimeMs ||
152
+ lock.mtimeMs !== after.mtimeMs)
153
+ return false;
154
+ await unlink(lock.path);
155
+ return true;
156
+ }
157
+ catch {
158
+ return false;
159
+ }
160
+ }
@@ -0,0 +1,7 @@
1
+ import { type IndexFailure } from "./retry-worktree.ts";
2
+ export declare function resumeWorktree(request: {
3
+ readonly directory: string;
4
+ readonly folder: string;
5
+ readonly signal: AbortSignal;
6
+ readonly stopMilliseconds?: number;
7
+ }): Promise<IndexFailure | null>;
@@ -0,0 +1,29 @@
1
+ import { ownedIndexLock, removeStoppedIndexLock } from "./resume-index-lock.js";
2
+ import { stopTreeProcesses } from "./worktree-processes.js";
3
+ import { indexFailure } from "./retry-worktree.js";
4
+ async function inspectLock(failure) {
5
+ if (failure?.lockPath === undefined)
6
+ return undefined;
7
+ return ownedIndexLock(failure.lockPath);
8
+ }
9
+ export async function resumeWorktree(request) {
10
+ const aborted = () => request.signal.aborted;
11
+ const before = await indexFailure(request);
12
+ const lock = await inspectLock(before);
13
+ if (aborted())
14
+ return before;
15
+ const stopped = await stopTreeProcesses(request);
16
+ const locked = await indexFailure(request);
17
+ if (!stopped)
18
+ return (locked ?? {
19
+ reason: "worktree-index-locked",
20
+ fix: "Stop the remaining work tree processes before resuming the turn.",
21
+ });
22
+ if (locked?.reason !== "worktree-index-locked" || aborted())
23
+ return locked;
24
+ if (lock === undefined ||
25
+ lock.path !== locked.lockPath ||
26
+ !(await removeStoppedIndexLock(lock, request.signal)))
27
+ return locked;
28
+ return indexFailure(request);
29
+ }
@@ -1,14 +1,4 @@
1
- import { type TokenPrices } from "../token-prices.ts";
2
1
  import { type RunningTurn, type TurnStart } from "./claude-turn.ts";
3
- export interface RetrySettings {
4
- readonly tokenPrices?: TokenPrices;
5
- readonly fallbackModels?: readonly string[];
6
- readonly retryDelay?: (milliseconds: number, signal: AbortSignal) => Promise<void>;
7
- }
8
- interface RetryOptions extends RetrySettings {
9
- readonly stopping?: AbortController;
10
- readonly stopMilliseconds?: number;
11
- readonly metadata: Readonly<Record<string, unknown>>;
12
- }
2
+ import { type RetryOptions } from "./turn-attempts.ts";
3
+ export type { RetrySettings } from "./turn-attempts.ts";
13
4
  export declare function startRetriedTurn(start: TurnStart, options: RetryOptions): Promise<RunningTurn>;
14
- export {};